feat: new standard moves view

This commit is contained in:
2025-04-24 01:45:37 +03:00
parent 6a98255ed3
commit 8e9665527f
6 changed files with 393 additions and 203 deletions

View File

@@ -16,6 +16,7 @@ import 'package:dungeon_paper/app/modules/LibraryList/views/moves_library_list_v
import 'package:dungeon_paper/app/modules/LibraryList/views/notes_library_list_view.dart';
import 'package:dungeon_paper/app/modules/LibraryList/views/races_library_list_view.dart';
import 'package:dungeon_paper/app/modules/LibraryList/views/spells_library_list_view.dart';
import 'package:dungeon_paper/app/modules/StandardMoves/controllers/standard_moves_list_controller.dart';
import 'package:dungeon_paper/app/routes/app_pages.dart';
import 'package:dungeon_paper/app/widgets/forms/character_class_form.dart';
import 'package:dungeon_paper/app/widgets/forms/item_form.dart';
@@ -122,6 +123,21 @@ class ModelPages with LibraryProviderMixin, CharacterProviderMixin {
);
}
static void openStandardMovesList(
BuildContext context, {
required Character character,
required MoveCategory category,
}) {
final char = character;
Navigator.of(context).pushNamed(
Routes.standardMoves,
arguments: StandardMovesArgs(
category: category,
character: char,
),
);
}
static void openRacesList(
BuildContext context, {
Character? character,
@@ -318,4 +334,4 @@ class ModelPages with LibraryProviderMixin, CharacterProviderMixin {
characterClass == null ? FormContext.create : FormContext.edit,
),
);
}
}

View File

@@ -42,19 +42,15 @@ class HomeCharacterActionsView extends StatelessWidget
Widget build(BuildContext context) {
return PageStorage(
bucket: PageStorageBucket(),
child: CharacterProvider.consumer(
(context, controller, _) {
if (controller.maybeCurrent == null) {
return Container();
}
final builder = _getBuilder(controller);
return SizedBox(
child: builder.asListView(
padding: const EdgeInsets.only(bottom: 16),
),
);
},
),
child: CharacterProvider.consumer((context, controller, _) {
if (controller.maybeCurrent == null) {
return Container();
}
final builder = _getBuilder(controller);
return SizedBox(
child: builder.asListView(padding: const EdgeInsets.only(bottom: 16)),
);
}),
);
}
@@ -97,20 +93,22 @@ class HomeCharacterActionsView extends StatelessWidget
}
final raceCard = RaceCard(
race: char.race,
onSave: (race) => controller.updateCharacter(
char.copyWithInherited(race: race),
),
onSave:
(race) =>
controller.updateCharacter(char.copyWithInherited(race: race)),
actions: [
EntityEditMenu(
onDelete: null,
onEdit: () => ModelPages.openRacePage(
context,
race: char.race,
abilityScores: char.abilityScores,
onSave: (race) => controller.updateCharacter(
char.copyWithInherited(race: race),
),
),
onEdit:
() => ModelPages.openRacePage(
context,
race: char.race,
abilityScores: char.abilityScores,
onSave:
(race) => controller.updateCharacter(
char.copyWithInherited(race: race),
),
),
),
],
);
@@ -119,10 +117,11 @@ class HomeCharacterActionsView extends StatelessWidget
actions: [
EntityEditMenu(
onDelete: null,
onEdit: () => Navigator.of(context).pushNamed(
Routes.bio,
arguments: BioFormArguments(character: char),
),
onEdit:
() => Navigator.of(context).pushNamed(
Routes.bio,
arguments: BioFormArguments(character: char),
),
),
ElevatedButton(
onPressed: () => CharacterUtils.addXP(context, char, 1),
@@ -130,10 +129,7 @@ class HomeCharacterActionsView extends StatelessWidget
),
],
);
final list = [
raceCard,
alignmentCard,
];
final list = [raceCard, alignmentCard];
final index = char.actionCategories.toList().indexOf('ClassAction');
return CategorizedList(
initiallyExpanded: true,
@@ -143,19 +139,17 @@ class HomeCharacterActionsView extends StatelessWidget
index: index,
totalItemCount: Character.allActionCategories.length,
onReorder: _onReorder,
)
),
],
itemPadding: const EdgeInsets.only(bottom: 8),
children: [
...list.map(
(obj) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
key: PageStorageKey('type-ClassAction-${obj.key}'),
child: obj,
);
},
),
...list.map((obj) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
key: PageStorageKey('type-ClassAction-${obj.key}'),
child: obj,
);
}),
],
);
}
@@ -176,18 +170,14 @@ class HomeCharacterActionsView extends StatelessWidget
Expanded(
child: ElevatedButton(
onPressed: () => _openBasicMoves(context),
child: Text(
tr.actions.moves.basic,
),
child: Text(tr.actions.moves.basic),
),
),
const SizedBox(width: 16),
Expanded(
child: ElevatedButton(
onPressed: () => _openSpecialMoves(context),
child: Text(
tr.actions.moves.special,
),
child: Text(tr.actions.moves.special),
),
),
],
@@ -202,49 +192,56 @@ class HomeCharacterActionsView extends StatelessWidget
MenuEntry(
value: 'move_to_start',
label: Text(tr.sort.moveEntityToTop(tr.entity(tn(Race)))),
onSelect: () => controller.updateCharacter(
char.copyWith(
settings:
char.settings.copyWith(racePosition: RacePosition.start),
),
),
onSelect:
() => controller.updateCharacter(
char.copyWith(
settings: char.settings.copyWith(
racePosition: RacePosition.start,
),
),
),
),
if (char.settings.racePosition != RacePosition.end)
// Move to end of list
MenuEntry(
value: 'move_to_end',
label: Text(tr.sort.moveEntityToBottom(tr.entity(tn(Race)))),
onSelect: () => controller.updateCharacter(
char.copyWith(
settings:
char.settings.copyWith(racePosition: RacePosition.end),
),
),
onSelect:
() => controller.updateCharacter(
char.copyWith(
settings: char.settings.copyWith(
racePosition: RacePosition.end,
),
),
),
),
],
addPageArguments: ({required onSelected}) => MoveLibraryListArguments(
character: char,
onSelected: onSelected,
preSelections: char.moves,
),
cardBuilder: (move, {required onSave, required onDelete}) => MoveCard(
reorderablePadding: true,
move: move,
advancedLevelDisplay: AdvancedLevelDisplay.none,
abilityScores: char.abilityScores,
actions: [
EntityEditMenu(
onDelete: onDelete,
onEdit: () => ModelPages.openMovePage(
context,
move: move,
abilityScores: char.abilityScores,
onSave: onSave(true),
),
addPageArguments:
({required onSelected}) => MoveLibraryListArguments(
character: char,
onSelected: onSelected,
preSelections: char.moves,
),
cardBuilder:
(move, {required onSave, required onDelete}) => MoveCard(
reorderablePadding: true,
move: move,
advancedLevelDisplay: AdvancedLevelDisplay.none,
abilityScores: char.abilityScores,
actions: [
EntityEditMenu(
onDelete: onDelete,
onEdit:
() => ModelPages.openMovePage(
context,
move: move,
abilityScores: char.abilityScores,
onSave: onSave(true),
),
),
],
onSave: onSave(false),
),
],
onSave: onSave(false),
),
);
}
@@ -258,29 +255,32 @@ class HomeCharacterActionsView extends StatelessWidget
onReorder: _onReorder,
list: char.spells,
route: Routes.spells,
addPageArguments: ({required onSelected}) => SpellLibraryListArguments(
character: char,
onSelected: onSelected,
preSelections: char.spells,
),
cardBuilder: (spell, {required onSave, required onDelete}) => SpellCard(
reorderablePadding: true,
spell: spell,
abilityScores: char.abilityScores,
actions: [
EntityEditMenu(
onDelete: onDelete,
onEdit: () => ModelPages.openSpellPage(
context,
spell: spell,
classKeys: spell.classKeys,
abilityScores: char.abilityScores,
onSave: onSave(true),
),
addPageArguments:
({required onSelected}) => SpellLibraryListArguments(
character: char,
onSelected: onSelected,
preSelections: char.spells,
),
cardBuilder:
(spell, {required onSave, required onDelete}) => SpellCard(
reorderablePadding: true,
spell: spell,
abilityScores: char.abilityScores,
actions: [
EntityEditMenu(
onDelete: onDelete,
onEdit:
() => ModelPages.openSpellPage(
context,
spell: spell,
classKeys: spell.classKeys,
abilityScores: char.abilityScores,
onSave: onSave(true),
),
),
],
onSave: onSave(false),
),
],
onSave: onSave(false),
),
);
}
@@ -294,64 +294,78 @@ class HomeCharacterActionsView extends StatelessWidget
onReorder: _onReorder,
list: char.items,
route: Routes.items,
addPageArguments: ({required onSelected}) => ItemLibraryListArguments(
onSelected: (items) => onSelected(
items
.map(
(x) =>
x.copyWithInherited(amount: x.amount == 0 ? 1 : x.amount),
)
.toList(),
),
preSelections: char.items,
),
cardBuilder: (item, {required onSave, required onDelete}) => ItemCard(
reorderablePadding: true,
item: item,
actions: [
EntityEditMenu(
onDelete: onDelete,
onEdit: () => ModelPages.openItemPage(
context,
item: item,
onSave: onSave(true),
),
leading: [
ChecklistMenuEntry(
value: 'countArmor',
checked: item.settings.countArmor,
label: Text(tr.items.settings.countArmor),
onChanged: (value) => onSave(false)(
item.copyWithInherited(
settings: item.settings.copyWith(countArmor: value!),
),
addPageArguments:
({required onSelected}) => ItemLibraryListArguments(
onSelected:
(items) => onSelected(
items
.map(
(x) => x.copyWithInherited(
amount: x.amount == 0 ? 1 : x.amount,
),
)
.toList(),
),
),
ChecklistMenuEntry(
value: 'countDamage',
checked: item.settings.countDamage,
label: Text(tr.items.settings.countDamage),
onChanged: (value) => onSave(false)(
item.copyWithInherited(
settings: item.settings.copyWith(countDamage: value!),
preSelections: char.items,
),
cardBuilder:
(item, {required onSave, required onDelete}) => ItemCard(
reorderablePadding: true,
item: item,
actions: [
EntityEditMenu(
onDelete: onDelete,
onEdit:
() => ModelPages.openItemPage(
context,
item: item,
onSave: onSave(true),
),
leading: [
ChecklistMenuEntry(
value: 'countArmor',
checked: item.settings.countArmor,
label: Text(tr.items.settings.countArmor),
onChanged:
(value) => onSave(false)(
item.copyWithInherited(
settings: item.settings.copyWith(
countArmor: value!,
),
),
),
),
),
),
ChecklistMenuEntry(
value: 'countWeight',
checked: item.settings.countWeight,
label: Text(tr.items.settings.countWeight),
onChanged: (value) => onSave(false)(
item.copyWithInherited(
settings: item.settings.copyWith(countWeight: value!),
ChecklistMenuEntry(
value: 'countDamage',
checked: item.settings.countDamage,
label: Text(tr.items.settings.countDamage),
onChanged:
(value) => onSave(false)(
item.copyWithInherited(
settings: item.settings.copyWith(
countDamage: value!,
),
),
),
),
),
ChecklistMenuEntry(
value: 'countWeight',
checked: item.settings.countWeight,
label: Text(tr.items.settings.countWeight),
onChanged:
(value) => onSave(false)(
item.copyWithInherited(
settings: item.settings.copyWith(
countWeight: value!,
),
),
),
),
],
),
],
onSave: onSave(false),
),
],
onSave: onSave(false),
),
);
}
@@ -375,20 +389,18 @@ class HomeCharacterActionsView extends StatelessWidget
}
void _openBasicMoves(BuildContext context) {
ModelPages.openMovesList(
ModelPages.openStandardMovesList(
context,
category: MoveCategory.basic,
initialTab: FiltersGroup.playbook,
abilityScores: char.abilityScores,
character: char,
);
}
void _openSpecialMoves(BuildContext context) {
ModelPages.openMovesList(
ModelPages.openStandardMovesList(
context,
category: MoveCategory.special,
initialTab: FiltersGroup.playbook,
abilityScores: char.abilityScores,
character: char,
);
}
}
@@ -416,17 +428,19 @@ class ActionsCardList<T extends WithMeta> extends StatelessWidget
final List<Widget> trailing;
final LibraryListArguments<T, EntityFilters<T>> Function({
required void Function(Iterable<T> obj) onSelected,
}) addPageArguments;
})
addPageArguments;
final Widget Function(
T object, {
required void Function() onDelete,
required void Function(T object) Function(bool fork) onSave,
// required void Function() onEdit,
}) cardBuilder;
})
cardBuilder;
final List<T> list;
final int index;
final void Function(BuildContext context, int oldIndex, int newIndex)
onReorder;
onReorder;
final List<MenuEntry<String>> menuLeading;
final List<MenuEntry<String>> menuTrailing;
@@ -435,7 +449,8 @@ class ActionsCardList<T extends WithMeta> extends StatelessWidget
return CharacterProvider.consumer((context, controller, _) {
debugPrint('ActionsCardList rebuild');
debugPrint(
'Character items: \n${controller.current.items.map((e) => '- ${e.displayName}').join('\n')}');
'Character items: \n${controller.current.items.map((e) => '- ${e.displayName}').join('\n')}',
);
return CategorizedList(
initiallyExpanded: true,
title: Text(tr.entityPlural(typeName)),
@@ -443,14 +458,18 @@ class ActionsCardList<T extends WithMeta> extends StatelessWidget
titleTrailing: [
LibraryProvider.consumer(
(context, library, _) => TextButton.icon(
onPressed: () => Navigator.pushNamed(
context,
route,
arguments: addPageArguments(
onSelected: (items) => library.upsertToCharacter(items,
forkBehavior: ForkBehavior.fork),
),
),
onPressed:
() => Navigator.pushNamed(
context,
route,
arguments: addPageArguments(
onSelected:
(items) => library.upsertToCharacter(
items,
forkBehavior: ForkBehavior.fork,
),
),
),
label: Text(tr.generic.addEntity(tr.entityPlural(typeName))),
icon: const Icon(Icons.add),
),
@@ -461,40 +480,45 @@ class ActionsCardList<T extends WithMeta> extends StatelessWidget
onReorder: onReorder,
leading: menuLeading,
trailing: menuTrailing,
)
),
],
leading: leading.map((obj) => _wrapChild(child: obj)).toList(),
trailing: trailing.map((obj) => _wrapChild(child: obj)).toList(),
children: [
...list.map(
(obj) {
return _wrapChild(
key: PageStorageKey('type-$T-${obj.key}'),
child: cardBuilder(
obj,
onDelete: _confirmDeleteDlg(context, obj, obj.displayName),
onSave: (fork) => (obj) {
final library = LibraryProvider.of(context);
library.upsertToCharacter([obj],
forkBehavior: ForkBehavior.none);
},
),
);
},
),
...list.map((obj) {
return _wrapChild(
key: PageStorageKey('type-$T-${obj.key}'),
child: cardBuilder(
obj,
onDelete: _confirmDeleteDlg(context, obj, obj.displayName),
onSave:
(fork) => (obj) {
final library = LibraryProvider.of(context);
library.upsertToCharacter([
obj,
], forkBehavior: ForkBehavior.none);
},
),
);
}),
],
onReorder: (oldIndex, newIndex) => controller.updateCharacter(
CharacterUtils.reorderByType<T>(
controller.current, oldIndex, newIndex)),
onReorder:
(oldIndex, newIndex) => controller.updateCharacter(
CharacterUtils.reorderByType<T>(
controller.current,
oldIndex,
newIndex,
),
),
);
});
}
Widget _wrapChild({Key? key, required Widget child}) => Padding(
key: key,
padding: const EdgeInsets.symmetric(vertical: 4),
child: child,
);
key: key,
padding: const EdgeInsets.symmetric(vertical: 4),
child: child,
);
void Function() _confirmDeleteDlg(
BuildContext context,
@@ -505,8 +529,9 @@ class ActionsCardList<T extends WithMeta> extends StatelessWidget
awaitDeleteConfirmation<T>(
context,
name,
() => charProvider
.updateCharacter(CharacterUtils.removeByType<T>(char, [object])),
() => charProvider.updateCharacter(
CharacterUtils.removeByType<T>(char, [object]),
),
);
};
}

View File

@@ -0,0 +1,72 @@
import 'package:dungeon_paper/app/data/models/character.dart';
import 'package:dungeon_paper/app/data/models/move.dart';
import 'package:dungeon_paper/app/data/services/repository_provider.dart';
import 'package:dungeon_paper/core/route_arguments.dart';
import 'package:dungeon_paper/i18n.dart';
import 'package:flutter/material.dart';
class StandardMovesListController extends ChangeNotifier {
late StandardMovesArgs args;
StandardMovesListController(BuildContext context) {
args = getArgs(context);
}
String get title {
switch (args.category) {
case MoveCategory.basic:
return tr.actions.moves.basic;
case MoveCategory.special:
return tr.actions.moves.special;
default:
return '';
}
}
List<Move> builtInMoves(BuildContext context) {
final repo = RepositoryProvider.of(context);
switch (args.category) {
case MoveCategory.basic:
return repo.builtIn
.listByType<Move>()
.values
.where((move) => move.category == MoveCategory.basic)
.toList();
case MoveCategory.special:
return repo.builtIn
.listByType<Move>()
.values
.where((move) => move.category == MoveCategory.special)
.toList();
default:
return [];
}
}
List<Move> playbookMoves(BuildContext context) {
final repo = RepositoryProvider.of(context);
switch (args.category) {
case MoveCategory.basic:
return repo.my
.listByType<Move>()
.values
.where((move) => move.category == MoveCategory.basic)
.toList();
case MoveCategory.special:
return repo.my
.listByType<Move>()
.values
.where((move) => move.category == MoveCategory.special)
.toList();
default:
return [];
}
}
}
class StandardMovesArgs {
final MoveCategory category;
final Character character;
StandardMovesArgs({required this.category, required this.character});
}

View File

@@ -0,0 +1,64 @@
import 'package:dungeon_paper/app/modules/StandardMoves/controllers/standard_moves_list_controller.dart';
import 'package:dungeon_paper/app/widgets/cards/move_card.dart';
import 'package:dungeon_paper/i18n.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class StandardMovesListView extends StatelessWidget {
const StandardMovesListView({super.key});
@override
Widget build(BuildContext context) {
return Consumer<StandardMovesListController>(
builder: (context, controller, _) {
final textTheme = Theme.of(context).textTheme.titleMedium!;
return Scaffold(
appBar: AppBar(title: Text(controller.title)),
body: Center(
child: SizedBox(
width: 800,
child: ListView(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
tr.myLibrary.libraryType('builtIn'),
style: textTheme,
),
),
for (final move in controller.builtInMoves(context))
Padding(
padding: const EdgeInsets.all(8.0),
child: MoveCard(
move: move,
showStar: false,
showClasses: false,
abilityScores: controller.args.character.abilityScores,
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
tr.myLibrary.libraryType('my'),
style: textTheme,
),
),
for (final move in controller.playbookMoves(context))
Padding(
padding: const EdgeInsets.all(8.0),
child: MoveCard(
move: move,
showStar: false,
showClasses: false,
abilityScores: controller.args.character.abilityScores,
),
),
],
),
),
),
);
},
);
}
}

View File

@@ -1,3 +1,5 @@
import 'package:dungeon_paper/app/modules/StandardMoves/controllers/standard_moves_list_controller.dart';
import 'package:dungeon_paper/app/modules/StandardMoves/views/standard_moves_list.dart';
import 'package:dungeon_paper/app/widgets/atoms/platform_scaffold_wrapper.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@@ -196,6 +198,11 @@ class AppPages {
child: const MovesLibraryListView(),
),
Routes.standardMoves: (context) => ChangeNotifierProvider(
create: (_) => StandardMovesListController(context),
child: const StandardMovesListView(),
),
Routes.editMove: (context) => ChangeNotifierProvider(
create: (_) => MoveFormController(context),
child: const MoveForm(),
@@ -365,4 +372,4 @@ class AppPages {
create: (_) => ChangelogController(),
),
};
}
}

View File

@@ -40,6 +40,9 @@ abstract class Routes {
/// `/library/moves`
static const moves = _Paths.library + _Paths.moves;
/// `/library/standard-moves`
static const standardMoves = _Paths.library + _Paths.standardMoves;
/// `/library/moves/edit`
static const editMove = _Paths.library + _Paths.moves + _Paths.edit;
@@ -221,6 +224,9 @@ abstract class _Paths {
/// `/moves`
static const moves = '/moves';
/// `/standard-moves`
static const standardMoves = '/standard-moves';
/// `/spells`
static const spells = '/spells';
@@ -256,4 +262,4 @@ abstract class _Paths {
/// `/changelog`
static const changelog = '/changelog';
}
}