diff --git a/lib/presentation/custom_build/custom_build_page.dart b/lib/presentation/custom_build/custom_build_page.dart index e77c8d0ef..f8565c70b 100644 --- a/lib/presentation/custom_build/custom_build_page.dart +++ b/lib/presentation/custom_build/custom_build_page.dart @@ -6,8 +6,6 @@ import 'package:responsive_builder/responsive_builder.dart'; import 'package:screenshot/screenshot.dart'; import 'package:shiori/application/bloc.dart'; import 'package:shiori/domain/enums/enums.dart'; -import 'package:shiori/domain/extensions/string_extensions.dart'; -import 'package:shiori/domain/models/models.dart'; import 'package:shiori/generated/l10n.dart'; import 'package:shiori/injection.dart'; import 'package:shiori/presentation/custom_build/widgets/artifact_section.dart'; @@ -15,164 +13,106 @@ import 'package:shiori/presentation/custom_build/widgets/character_section.dart' import 'package:shiori/presentation/custom_build/widgets/team_section.dart'; import 'package:shiori/presentation/custom_build/widgets/weapon_section.dart'; import 'package:shiori/presentation/shared/dialogs/confirm_dialog.dart'; -import 'package:shiori/presentation/shared/extensions/element_type_extensions.dart'; -import 'package:shiori/presentation/shared/loading.dart'; import 'package:shiori/presentation/shared/styles.dart'; import 'package:shiori/presentation/shared/utils/toast_utils.dart'; const double _maxItemImageWidth = 130; -class CustomBuildPage extends StatelessWidget { +class CustomBuildPage extends StatefulWidget { final int? itemKey; - bool get newBuild => itemKey == null; - const CustomBuildPage({ Key? key, this.itemKey, }) : super(key: key); @override - Widget build(BuildContext context) { - final s = S.of(context); - return BlocProvider( - create: (ctx) => Injection.getCustomBuildBloc(context.read())..add(CustomBuildEvent.load(key: itemKey, initialTitle: s.dps)), - child: _Page( - newBuild: newBuild, - ), - ); - } -} - -class _Page extends StatefulWidget { - final bool newBuild; - - const _Page({Key? key, required this.newBuild}) : super(key: key); - - @override - State<_Page> createState() => _PageState(); + State createState() => _CustomBuildPageState(); } -class _PageState extends State<_Page> { +class _CustomBuildPageState extends State { final _screenshotController = ScreenshotController(); + bool get newBuild => widget.itemKey == null; + @override Widget build(BuildContext context) { final s = S.of(context); final width = MediaQuery.of(context).size.width; - return BlocBuilder( - builder: (ctx, state) => state.maybeMap( - loaded: (state) => Scaffold( - appBar: _AppBar( - buildKey: state.key, - newBuild: widget.newBuild, - canSave: state.artifacts.length == ArtifactType.values.length && state.weapons.isNotEmpty, - screenshotController: _screenshotController, - ), - body: SingleChildScrollView( - padding: const EdgeInsets.only(bottom: 10), - child: Screenshot( - controller: _screenshotController, - child: OrientationLayoutBuilder( - portrait: (context) => _PortraitLayout( - title: state.title, - roleType: state.type, - roleSubType: state.subType, - showOnCharacterDetail: state.showOnCharacterDetail, - isRecommended: state.isRecommended, - character: state.character, - weapons: state.weapons, - artifacts: state.artifacts, - teamCharacters: state.teamCharacters, - notes: state.notes, - skillPriorities: state.skillPriorities, - subStatsSummary: state.subStatsSummary, - ), - landscape: (context) => width > 1280 - ? _LandscapeLayout( - title: state.title, - roleType: state.type, - roleSubType: state.subType, - showOnCharacterDetail: state.showOnCharacterDetail, - isRecommended: state.isRecommended, - character: state.character, - weapons: state.weapons, - artifacts: state.artifacts, - teamCharacters: state.teamCharacters, - notes: state.notes, - skillPriorities: state.skillPriorities, - subStatsSummary: state.subStatsSummary, - ) - : _PortraitLayout( - title: state.title.isNullEmptyOrWhitespace ? s.dps : state.title, - roleType: state.type, - roleSubType: state.subType, - showOnCharacterDetail: state.showOnCharacterDetail, - isRecommended: state.isRecommended, - character: state.character, - weapons: state.weapons, - artifacts: state.artifacts, - teamCharacters: state.teamCharacters, - notes: state.notes, - skillPriorities: state.skillPriorities, - subStatsSummary: state.subStatsSummary, - ), - ), + return BlocProvider( + create: (ctx) => Injection.getCustomBuildBloc(context.read()) + ..add( + CustomBuildEvent.load(key: widget.itemKey, initialTitle: s.dps), + ), + child: Scaffold( + appBar: _AppBar( + newBuild: newBuild, + screenshotController: _screenshotController, + ), + body: SingleChildScrollView( + padding: const EdgeInsets.only(bottom: 10), + child: Screenshot( + controller: _screenshotController, + child: OrientationLayoutBuilder( + portrait: (context) => const _PortraitLayout(), + landscape: (context) => width > 1280 ? const _LandscapeLayout() : const _PortraitLayout(), ), ), ), - orElse: () => const Loading(), ), ); } } class _AppBar extends StatelessWidget implements PreferredSizeWidget { - final int? buildKey; final bool newBuild; - final bool canSave; final ScreenshotController screenshotController; const _AppBar({ Key? key, - required this.buildKey, required this.newBuild, - required this.canSave, required this.screenshotController, }) : super(key: key); @override Widget build(BuildContext context) { final s = S.of(context); - return AppBar( - title: Text(newBuild ? s.add : s.edit), - actions: [ - Tooltip( - message: s.save, - child: IconButton( - splashRadius: Styles.mediumButtonSplashRadius, - icon: const Icon(Icons.save), - onPressed: !canSave ? null : () => _saveChanges(context), - ), - ), - if (!newBuild && buildKey != null) - Tooltip( - message: s.delete, - child: IconButton( - splashRadius: Styles.mediumButtonSplashRadius, - icon: const Icon(Icons.delete), - onPressed: () => _showDeleteDialog(context), + return BlocBuilder( + builder: (ctx, state) => state.maybeMap( + loaded: (state) => AppBar( + title: Text(newBuild ? s.add : s.edit), + actions: [ + Tooltip( + message: s.save, + child: IconButton( + splashRadius: Styles.mediumButtonSplashRadius, + icon: const Icon(Icons.save), + onPressed: !(state.artifacts.length == ArtifactType.values.length && state.weapons.isNotEmpty) ? null : () => _saveChanges(context), + ), ), - ), - Tooltip( - message: s.share, - child: IconButton( - splashRadius: Styles.mediumButtonSplashRadius, - icon: const Icon(Icons.share), - onPressed: () => _takeScreenshot(context), - ), + if (!newBuild && state.key != null) + Tooltip( + message: s.delete, + child: IconButton( + splashRadius: Styles.mediumButtonSplashRadius, + icon: const Icon(Icons.delete), + onPressed: () => _showDeleteDialog(context, state.key), + ), + ), + Tooltip( + message: s.share, + child: IconButton( + splashRadius: Styles.mediumButtonSplashRadius, + icon: const Icon(Icons.share), + onPressed: () => _takeScreenshot(context), + ), + ), + ], ), - ], + orElse: () => AppBar( + title: Text(newBuild ? s.add : s.edit), + ), + ), ); } @@ -202,7 +142,7 @@ class _AppBar extends StatelessWidget implements PreferredSizeWidget { } } - Future _showDeleteDialog(BuildContext context) { + Future _showDeleteDialog(BuildContext context, int? buildKey) { final s = S.of(context); return showDialog( context: context, @@ -225,34 +165,7 @@ class _AppBar extends StatelessWidget implements PreferredSizeWidget { } class _PortraitLayout extends StatelessWidget { - final String title; - final CharacterRoleType roleType; - final CharacterRoleSubType roleSubType; - final bool showOnCharacterDetail; - final bool isRecommended; - final CharacterCardModel character; - final List weapons; - final List artifacts; - final List teamCharacters; - final List notes; - final List skillPriorities; - final List subStatsSummary; - - const _PortraitLayout({ - Key? key, - required this.title, - required this.roleType, - required this.roleSubType, - required this.showOnCharacterDetail, - required this.isRecommended, - required this.character, - required this.weapons, - required this.artifacts, - required this.teamCharacters, - required this.notes, - required this.skillPriorities, - required this.subStatsSummary, - }) : super(key: key); + const _PortraitLayout({Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -260,44 +173,12 @@ class _PortraitLayout extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - CharacterSection( - title: title, - type: roleType, - subType: roleSubType, - showOnCharacterDetail: showOnCharacterDetail, - isRecommended: isRecommended, - character: character, - notes: notes, - skillPriorities: skillPriorities, - ), + const CharacterSection(), ScreenTypeLayout.builder( - desktop: (context) => _WeaponsAndArtifacts( - mainCharKey: character.key, - weaponType: character.weaponType, - color: character.elementType.getElementColorFromContext(context), - weapons: weapons, - artifacts: artifacts, - teamCharacters: teamCharacters, - subStatsSummary: subStatsSummary, - ), - tablet: (context) => _WeaponsAndArtifacts( - mainCharKey: character.key, - weaponType: character.weaponType, - color: character.elementType.getElementColorFromContext(context), - weapons: weapons, - artifacts: artifacts, - teamCharacters: teamCharacters, - subStatsSummary: subStatsSummary, - ), + desktop: (context) => const _WeaponsAndArtifacts(), + tablet: (context) => const _WeaponsAndArtifacts(), mobile: (context) => _WeaponsAndArtifacts( useColumn: isPortrait, - mainCharKey: character.key, - weaponType: character.weaponType, - color: character.elementType.getElementColorFromContext(context), - weapons: weapons, - artifacts: artifacts, - teamCharacters: teamCharacters, - subStatsSummary: subStatsSummary, ), ), ], @@ -306,34 +187,7 @@ class _PortraitLayout extends StatelessWidget { } class _LandscapeLayout extends StatelessWidget { - final String title; - final CharacterRoleType roleType; - final CharacterRoleSubType roleSubType; - final bool showOnCharacterDetail; - final bool isRecommended; - final CharacterCardModel character; - final List weapons; - final List artifacts; - final List teamCharacters; - final List notes; - final List skillPriorities; - final List subStatsSummary; - - const _LandscapeLayout({ - Key? key, - required this.title, - required this.roleType, - required this.roleSubType, - required this.showOnCharacterDetail, - required this.isRecommended, - required this.character, - required this.weapons, - required this.artifacts, - required this.teamCharacters, - required this.notes, - required this.skillPriorities, - required this.subStatsSummary, - }) : super(key: key); + const _LandscapeLayout({Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -341,47 +195,28 @@ class _LandscapeLayout extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded( + const Expanded( flex: 40, - child: CharacterSection( - title: title, - type: roleType, - subType: roleSubType, - showOnCharacterDetail: showOnCharacterDetail, - isRecommended: isRecommended, - character: character, - notes: notes, - skillPriorities: skillPriorities, - ), + child: CharacterSection(), ), Expanded( flex: 60, child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, - children: [ + children: const [ Expanded( child: WeaponSection( - weapons: weapons, - weaponType: character.weaponType, - color: character.elementType.getElementColorFromContext(context), maxItemImageWidth: _maxItemImageWidth, ), ), Expanded( child: ArtifactSection( - artifacts: artifacts, - color: character.elementType.getElementColorFromContext(context), maxItemImageWidth: _maxItemImageWidth, - subStatsSummary: subStatsSummary, ), ), Expanded( - child: TeamSection( - mainCharKey: character.key, - teamCharacters: teamCharacters, - color: character.elementType.getElementColorFromContext(context), - ), + child: TeamSection(), ), ], ), @@ -392,49 +227,25 @@ class _LandscapeLayout extends StatelessWidget { } class _WeaponsAndArtifacts extends StatelessWidget { - final String mainCharKey; - final WeaponType weaponType; - final List weapons; - final List artifacts; - final List teamCharacters; final bool useColumn; - final List subStatsSummary; - final Color color; const _WeaponsAndArtifacts({ Key? key, - required this.mainCharKey, - required this.weaponType, - required this.weapons, - required this.artifacts, - required this.teamCharacters, this.useColumn = false, - required this.subStatsSummary, - required this.color, }) : super(key: key); @override Widget build(BuildContext context) { if (useColumn) { return Column( - children: [ + children: const [ WeaponSection( - weapons: weapons, - weaponType: weaponType, - color: color, maxItemImageWidth: _maxItemImageWidth, ), ArtifactSection( - artifacts: artifacts, - color: color, maxItemImageWidth: _maxItemImageWidth, - subStatsSummary: subStatsSummary, - ), - TeamSection( - mainCharKey: mainCharKey, - teamCharacters: teamCharacters, - color: color, ), + TeamSection(), ], ); } @@ -444,30 +255,20 @@ class _WeaponsAndArtifacts extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, - children: [ + children: const [ Expanded( child: WeaponSection( - weapons: weapons, - weaponType: weaponType, - color: color, maxItemImageWidth: _maxItemImageWidth, ), ), Expanded( child: ArtifactSection( - artifacts: artifacts, - color: color, maxItemImageWidth: _maxItemImageWidth, - subStatsSummary: subStatsSummary, ), ), ], ), - TeamSection( - mainCharKey: mainCharKey, - teamCharacters: teamCharacters, - color: color, - ), + const TeamSection(), ], ); } diff --git a/lib/presentation/custom_build/widgets/artifact_section.dart b/lib/presentation/custom_build/widgets/artifact_section.dart index 97355881f..4db77ddf7 100644 --- a/lib/presentation/custom_build/widgets/artifact_section.dart +++ b/lib/presentation/custom_build/widgets/artifact_section.dart @@ -1,32 +1,26 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:shiori/application/bloc.dart'; import 'package:shiori/domain/app_constants.dart'; import 'package:shiori/domain/enums/enums.dart'; import 'package:shiori/domain/extensions/string_extensions.dart'; -import 'package:shiori/domain/models/models.dart'; import 'package:shiori/generated/l10n.dart'; import 'package:shiori/presentation/artifacts/artifacts_page.dart'; import 'package:shiori/presentation/custom_build/widgets/artifact_row.dart'; import 'package:shiori/presentation/shared/dialogs/select_artifact_type_dialog.dart'; import 'package:shiori/presentation/shared/dialogs/select_stat_type_dialog.dart'; +import 'package:shiori/presentation/shared/extensions/element_type_extensions.dart'; +import 'package:shiori/presentation/shared/loading.dart'; import 'package:shiori/presentation/shared/nothing_found.dart'; import 'package:shiori/presentation/shared/styles.dart'; import 'package:shiori/presentation/shared/sub_stats_to_focus.dart'; class ArtifactSection extends StatelessWidget { - final List artifacts; - final Color color; final double maxItemImageWidth; - final List subStatsSummary; const ArtifactSection({ Key? key, - required this.artifacts, - required this.color, required this.maxItemImageWidth, - required this.subStatsSummary, }) : super(key: key); @override @@ -34,66 +28,76 @@ class ArtifactSection extends StatelessWidget { final s = S.of(context); final theme = Theme.of(context); final isPortrait = MediaQuery.of(context).orientation == Orientation.portrait; - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - padding: Styles.edgeInsetVertical10, - // margin: const EdgeInsets.only(bottom: 10), - decoration: BoxDecoration( - color: color, - border: isPortrait ? Border(top: BorderSide(color: Colors.white)) : null, - ), - child: Text( - '${s.artifacts} (${artifacts.length} / ${ArtifactType.values.length})', - textAlign: TextAlign.center, - style: theme.textTheme.subtitle1!.copyWith(fontWeight: FontWeight.bold), - ), - ), - ButtonBar( - buttonPadding: EdgeInsets.zero, - children: [ - Tooltip( - message: s.add, - child: IconButton( - padding: EdgeInsets.zero, - splashRadius: Styles.smallButtonSplashRadius, - icon: const Icon(Icons.add), - onPressed: artifacts.length < ArtifactType.values.length ? () => _addArtifact(context) : null, + return BlocBuilder( + builder: (context, state) => state.maybeMap( + loaded: (state) { + final color = state.character.elementType.getElementColorFromContext(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: Styles.edgeInsetVertical10, + // margin: const EdgeInsets.only(bottom: 10), + decoration: BoxDecoration( + color: color, + border: isPortrait ? const Border(top: BorderSide(color: Colors.white)) : null, + ), + child: Text( + '${s.artifacts} (${state.artifacts.length} / ${ArtifactType.values.length})', + textAlign: TextAlign.center, + style: theme.textTheme.subtitle1!.copyWith(fontWeight: FontWeight.bold), + ), ), - ), - Tooltip( - message: s.clearAll, - child: IconButton( - padding: EdgeInsets.zero, - splashRadius: Styles.smallButtonSplashRadius, - icon: const Icon(Icons.clear_all), - onPressed: artifacts.isEmpty ? null : () => context.read().add(const CustomBuildEvent.deleteArtifacts()), + ButtonBar( + buttonPadding: EdgeInsets.zero, + children: [ + Tooltip( + message: s.add, + child: IconButton( + padding: EdgeInsets.zero, + splashRadius: Styles.smallButtonSplashRadius, + icon: const Icon(Icons.add), + onPressed: state.artifacts.length < ArtifactType.values.length + ? () => _addArtifact(context, state.artifacts.map((e) => e.type).toList()) + : null, + ), + ), + Tooltip( + message: s.clearAll, + child: IconButton( + padding: EdgeInsets.zero, + splashRadius: Styles.smallButtonSplashRadius, + icon: const Icon(Icons.clear_all), + onPressed: state.artifacts.isEmpty ? null : () => context.read().add(const CustomBuildEvent.deleteArtifacts()), + ), + ), + ], ), - ), - ], - ), - if (artifacts.isEmpty) - NothingFound(msg: s.startByAddingArtifacts) - else - ...artifacts.map((e) => ArtifactRow(artifact: e, color: color, maxImageWidth: maxItemImageWidth)), - if (subStatsSummary.isNotEmpty) - SubStatToFocus( - subStatsToFocus: subStatsSummary, - color: color, - fontSize: 14, - ), - ], + if (state.artifacts.isEmpty) + NothingFound(msg: s.startByAddingArtifacts) + else + ...state.artifacts.map((e) => ArtifactRow(artifact: e, color: color, maxImageWidth: maxItemImageWidth)), + if (state.subStatsSummary.isNotEmpty) + SubStatToFocus( + subStatsToFocus: state.subStatsSummary, + color: color, + fontSize: 14, + ), + ], + ); + }, + orElse: () => const Loading(useScaffold: false), + ), ); } - Future _addArtifact(BuildContext context) async { + Future _addArtifact(BuildContext context, List selectedValues) async { final bloc = context.read(); final selectedType = await showDialog( context: context, builder: (ctx) => SelectArtifactTypeDialog( - selectedValues: artifacts.map((e) => e.type).toList(), + selectedValues: selectedValues, ), ); if (selectedType == null) { diff --git a/lib/presentation/custom_build/widgets/character_section.dart b/lib/presentation/custom_build/widgets/character_section.dart index 6bfc6f934..6d3f5a21c 100644 --- a/lib/presentation/custom_build/widgets/character_section.dart +++ b/lib/presentation/custom_build/widgets/character_section.dart @@ -1,11 +1,9 @@ import 'package:collection/collection.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:shiori/application/bloc.dart'; import 'package:shiori/domain/enums/enums.dart'; import 'package:shiori/domain/extensions/string_extensions.dart'; -import 'package:shiori/domain/models/models.dart'; import 'package:shiori/generated/l10n.dart'; import 'package:shiori/presentation/characters/characters_page.dart'; import 'package:shiori/presentation/shared/bullet_list.dart'; @@ -15,30 +13,12 @@ import 'package:shiori/presentation/shared/dialogs/text_dialog.dart'; import 'package:shiori/presentation/shared/dropdown_button_with_title.dart'; import 'package:shiori/presentation/shared/extensions/element_type_extensions.dart'; import 'package:shiori/presentation/shared/extensions/i18n_extensions.dart'; +import 'package:shiori/presentation/shared/loading.dart'; import 'package:shiori/presentation/shared/styles.dart'; import 'package:shiori/presentation/shared/utils/enum_utils.dart'; class CharacterSection extends StatelessWidget { - final String title; - final CharacterRoleType type; - final CharacterRoleSubType subType; - final bool showOnCharacterDetail; - final bool isRecommended; - final CharacterCardModel character; - final List notes; - final List skillPriorities; - - const CharacterSection({ - Key? key, - required this.title, - required this.type, - required this.subType, - required this.showOnCharacterDetail, - required this.isRecommended, - required this.character, - required this.notes, - required this.skillPriorities, - }) : super(key: key); + const CharacterSection({Key? key}) : super(key: key); //TODO: FIGURE OUT A WAY TO SHOW THE IMAGE PROPERLY @override @@ -53,174 +33,183 @@ class CharacterSection extends StatelessWidget { } final flexA = width < 400 ? 55 : 45; final flexB = width < 400 ? 45 : 55; - final canAddNotes = notes.map((e) => e.note.length).sum < 300 && notes.length < CustomBuildBloc.maxNumberOfNotes; - final canAddSkillPriorities = CustomBuildBloc.validSkillTypes.length == skillPriorities.length; - return Container( - color: character.elementType.getElementColorFromContext(context), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - flex: flexA, - child: CharacterStackImage( - name: character.name, - image: character.image, - rarity: character.stars, - height: imgHeight, - onTap: () => _openCharacterPage(context), - ), - ), - Expanded( - flex: flexB, - child: Padding( - padding: Styles.edgeInsetHorizontal5, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Row( - children: [ - Expanded( - child: Text( - title, - maxLines: 3, - overflow: TextOverflow.ellipsis, - style: theme.textTheme.headline5!.copyWith(fontWeight: FontWeight.bold), + return BlocBuilder( + builder: (context, state) => state.maybeMap( + loaded: (state) { + final canAddNotes = state.notes.map((e) => e.note.length).sum < 300 && state.notes.length < CustomBuildBloc.maxNumberOfNotes; + final canAddSkillPriorities = CustomBuildBloc.validSkillTypes.length == state.skillPriorities.length; + return Container( + color: state.character.elementType.getElementColorFromContext(context), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: flexA, + child: CharacterStackImage( + name: state.character.name, + image: state.character.image, + rarity: state.character.stars, + height: imgHeight, + onTap: () => _openCharacterPage(context, state.character.key), + ), + ), + Expanded( + flex: flexB, + child: Padding( + padding: Styles.edgeInsetHorizontal5, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + Expanded( + child: Text( + state.title, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: theme.textTheme.headline5!.copyWith(fontWeight: FontWeight.bold), + ), + ), + Tooltip( + message: s.recommended, + child: IconButton( + splashRadius: Styles.smallButtonSplashRadius, + icon: Icon(state.isRecommended ? Icons.star : Icons.star_border_outlined), + onPressed: () => context.read().add( + CustomBuildEvent.isRecommendedChanged(newValue: !state.isRecommended), + ), + ), + ), + Tooltip( + message: s.edit, + child: IconButton( + splashRadius: Styles.smallButtonSplashRadius, + icon: const Icon(Icons.edit), + onPressed: () => showDialog( + context: context, + builder: (_) => TextDialog.update( + title: s.title, + value: state.title, + maxLength: CustomBuildBloc.maxTitleLength, + onSave: (newTitle) => context.read().add(CustomBuildEvent.titleChanged(newValue: newTitle)), + ), + ), + ), + ) + ], ), - ), - Tooltip( - message: s.recommended, - child: IconButton( - splashRadius: Styles.smallButtonSplashRadius, - icon: Icon(isRecommended ? Icons.star : Icons.star_border_outlined), - onPressed: () => context.read().add(CustomBuildEvent.isRecommendedChanged(newValue: !isRecommended)), + DropdownButtonWithTitle( + margin: EdgeInsets.zero, + title: s.role, + currentValue: state.type, + items: EnumUtils.getTranslatedAndSortedEnum( + CharacterRoleType.values.where((el) => el != CharacterRoleType.na).toList(), + (val, _) => s.translateCharacterRoleType(val), + ), + onChanged: (v) => context.read().add(CustomBuildEvent.roleChanged(newValue: v)), ), - ), - Tooltip( - message: s.edit, - child: IconButton( - splashRadius: Styles.smallButtonSplashRadius, - icon: const Icon(Icons.edit), - onPressed: () => showDialog( - context: context, - builder: (_) => TextDialog.update( - title: s.title, - value: title, - maxLength: CustomBuildBloc.maxTitleLength, - onSave: (newTitle) => context.read().add(CustomBuildEvent.titleChanged(newValue: newTitle)), - ), + DropdownButtonWithTitle( + margin: EdgeInsets.zero, + title: s.subType, + currentValue: state.subType, + items: EnumUtils.getTranslatedAndSortedEnum( + CharacterRoleSubType.values, + (val, _) => s.translateCharacterRoleSubType(val), ), + onChanged: (v) => context.read().add(CustomBuildEvent.subRoleChanged(newValue: v)), ), - ) - ], - ), - DropdownButtonWithTitle( - margin: EdgeInsets.zero, - title: s.role, - currentValue: type, - items: EnumUtils.getTranslatedAndSortedEnum( - CharacterRoleType.values.where((el) => el != CharacterRoleType.na).toList(), - (val, _) => s.translateCharacterRoleType(val), - ), - onChanged: (v) => context.read().add(CustomBuildEvent.roleChanged(newValue: v)), - ), - DropdownButtonWithTitle( - margin: EdgeInsets.zero, - title: s.subType, - currentValue: subType, - items: EnumUtils.getTranslatedAndSortedEnum( - CharacterRoleSubType.values, - (val, _) => s.translateCharacterRoleSubType(val), - ), - onChanged: (v) => context.read().add(CustomBuildEvent.subRoleChanged(newValue: v)), - ), - SwitchListTile( - activeColor: theme.colorScheme.secondary, - contentPadding: EdgeInsets.zero, - title: Text(s.showOnCharacterDetail), - value: showOnCharacterDetail, - onChanged: (v) => context.read().add(CustomBuildEvent.showOnCharacterDetailChanged(newValue: v)), - ), - Row( - children: [ - Expanded( - child: Text( - s.talentPriority, - style: theme.textTheme.subtitle1, + SwitchListTile( + activeColor: theme.colorScheme.secondary, + contentPadding: EdgeInsets.zero, + title: Text(s.showOnCharacterDetail), + value: state.showOnCharacterDetail, + onChanged: (v) => context.read().add(CustomBuildEvent.showOnCharacterDetailChanged(newValue: v)), ), - ), - IconButton( - splashRadius: Styles.smallButtonSplashRadius, - icon: const Icon(Icons.add), - onPressed: canAddSkillPriorities - ? null - : () => showDialog( - context: context, - builder: (_) => SelectCharacterSkillTypeDialog( - excluded: CustomBuildBloc.excludedSkillTypes, - selectedValues: skillPriorities, - onSave: (type) { - if (type == null) { - return; - } + Row( + children: [ + Expanded( + child: Text( + s.talentPriority, + style: theme.textTheme.subtitle1, + ), + ), + IconButton( + splashRadius: Styles.smallButtonSplashRadius, + icon: const Icon(Icons.add), + onPressed: canAddSkillPriorities + ? null + : () => showDialog( + context: context, + builder: (_) => SelectCharacterSkillTypeDialog( + excluded: CustomBuildBloc.excludedSkillTypes, + selectedValues: state.skillPriorities, + onSave: (type) { + if (type == null) { + return; + } - context.read().add(CustomBuildEvent.addSkillPriority(type: type)); - }, - ), - ), - ), - ], - ), - BulletList( - iconSize: 14, - items: skillPriorities.map((e) => s.translateCharacterSkillType(e)).toList(), - iconResolver: (index) => Text('#${index + 1}', style: theme.textTheme.subtitle2!.copyWith(fontSize: 12)), - fontSize: 10, - padding: const EdgeInsets.only(right: 16, left: 5, bottom: 5, top: 5), - onDelete: (index) => context.read().add(CustomBuildEvent.deleteSkillPriority(index: index)), - ), - Row( - children: [ - Expanded( - child: Text( - s.notes, - style: theme.textTheme.subtitle1, + context.read().add(CustomBuildEvent.addSkillPriority(type: type)); + }, + ), + ), + ), + ], ), - ), - IconButton( - splashRadius: Styles.smallButtonSplashRadius, - icon: const Icon(Icons.add), - onPressed: !canAddNotes - ? null - : () => showDialog( - context: context, - builder: (_) => TextDialog.create( - title: s.note, - onSave: (note) => context.read().add(CustomBuildEvent.addNote(note: note)), - maxLength: CustomBuildBloc.maxNoteLength, - ), - ), - ), - ], - ), - BulletList( - iconSize: 14, - items: notes.map((e) => e.note).toList(), - fontSize: 10, - padding: const EdgeInsets.only(right: 16, left: 5, bottom: 5, top: 5), - onDelete: (index) => context.read().add(CustomBuildEvent.deleteNote(index: index)), + BulletList( + iconSize: 14, + items: state.skillPriorities.map((e) => s.translateCharacterSkillType(e)).toList(), + iconResolver: (index) => Text('#${index + 1}', style: theme.textTheme.subtitle2!.copyWith(fontSize: 12)), + fontSize: 10, + padding: const EdgeInsets.only(right: 16, left: 5, bottom: 5, top: 5), + onDelete: (index) => context.read().add(CustomBuildEvent.deleteSkillPriority(index: index)), + ), + Row( + children: [ + Expanded( + child: Text( + s.notes, + style: theme.textTheme.subtitle1, + ), + ), + IconButton( + splashRadius: Styles.smallButtonSplashRadius, + icon: const Icon(Icons.add), + onPressed: !canAddNotes + ? null + : () => showDialog( + context: context, + builder: (_) => TextDialog.create( + title: s.note, + onSave: (note) => context.read().add(CustomBuildEvent.addNote(note: note)), + maxLength: CustomBuildBloc.maxNoteLength, + ), + ), + ), + ], + ), + BulletList( + iconSize: 14, + items: state.notes.map((e) => e.note).toList(), + fontSize: 10, + padding: const EdgeInsets.only(right: 16, left: 5, bottom: 5, top: 5), + onDelete: (index) => context.read().add(CustomBuildEvent.deleteNote(index: index)), + ), + ], + ), ), - ], - ), + ), + ], ), - ), - ], + ); + }, + orElse: () => const Loading(useScaffold: false), ), ); } - Future _openCharacterPage(BuildContext context) async { + Future _openCharacterPage(BuildContext context, String currentCharKey) async { final bloc = context.read(); - final selectedKey = await CharactersPage.forSelection(context, excludeKeys: [character.key]); + final selectedKey = await CharactersPage.forSelection(context, excludeKeys: [currentCharKey]); if (selectedKey.isNullEmptyOrWhitespace) { return; } diff --git a/lib/presentation/custom_build/widgets/team_section.dart b/lib/presentation/custom_build/widgets/team_section.dart index 061cc6fc6..b924044dd 100644 --- a/lib/presentation/custom_build/widgets/team_section.dart +++ b/lib/presentation/custom_build/widgets/team_section.dart @@ -10,100 +10,105 @@ import 'package:shiori/presentation/custom_build/widgets/team_character_row.dart import 'package:shiori/presentation/shared/dialogs/select_character_role_sub_type_dialog.dart'; import 'package:shiori/presentation/shared/dialogs/select_character_role_type_dialog.dart'; import 'package:shiori/presentation/shared/dialogs/sort_items_dialog.dart'; +import 'package:shiori/presentation/shared/extensions/element_type_extensions.dart'; +import 'package:shiori/presentation/shared/loading.dart'; import 'package:shiori/presentation/shared/nothing_found.dart'; import 'package:shiori/presentation/shared/styles.dart'; class TeamSection extends StatelessWidget { - final String mainCharKey; - final List teamCharacters; - final Color color; - - const TeamSection({ - Key? key, - required this.mainCharKey, - required this.color, - required this.teamCharacters, - }) : super(key: key); + const TeamSection({Key? key}) : super(key: key); @override Widget build(BuildContext context) { final s = S.of(context); final theme = Theme.of(context); final isPortrait = MediaQuery.of(context).orientation == Orientation.portrait; - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - padding: Styles.edgeInsetVertical10, - // margin: const EdgeInsets.only(bottom: 10), - decoration: BoxDecoration( - color: color, - border: isPortrait ? const Border(top: BorderSide(color: Colors.white)) : null, - ), - child: Text( - '${s.teamComposition} (${teamCharacters.length} / ${CustomBuildBloc.maxNumberOfTeamCharacters})', - textAlign: TextAlign.center, - style: theme.textTheme.subtitle1!.copyWith(fontWeight: FontWeight.bold), - ), - ), - ButtonBar( - buttonPadding: EdgeInsets.zero, - children: [ - Tooltip( - message: s.add, - child: IconButton( - padding: EdgeInsets.zero, - splashRadius: Styles.smallButtonSplashRadius, - icon: const Icon(Icons.add), - onPressed: teamCharacters.length <= CustomBuildBloc.maxNumberOfTeamCharacters ? () => _addTeamCharacter(context) : null, + return BlocBuilder( + builder: (context, state) => state.maybeMap( + loaded: (state) { + final color = state.character.elementType.getElementColorFromContext(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: Styles.edgeInsetVertical10, + // margin: const EdgeInsets.only(bottom: 10), + decoration: BoxDecoration( + color: color, + border: isPortrait ? const Border(top: BorderSide(color: Colors.white)) : null, + ), + child: Text( + '${s.teamComposition} (${state.teamCharacters.length} / ${CustomBuildBloc.maxNumberOfTeamCharacters})', + textAlign: TextAlign.center, + style: theme.textTheme.subtitle1!.copyWith(fontWeight: FontWeight.bold), + ), ), - ), - Tooltip( - message: s.sort, - child: IconButton( - padding: EdgeInsets.zero, - splashRadius: Styles.smallButtonSplashRadius, - icon: const Icon(Icons.sort), - onPressed: teamCharacters.length < 2 - ? null - : () => showDialog( - context: context, - builder: (_) => SortItemsDialog( - items: teamCharacters.map((e) => SortableItem(e.key, e.name)).toList(), - onSave: (result) { - if (!result.somethingChanged) { - return; - } + ButtonBar( + buttonPadding: EdgeInsets.zero, + children: [ + Tooltip( + message: s.add, + child: IconButton( + padding: EdgeInsets.zero, + splashRadius: Styles.smallButtonSplashRadius, + icon: const Icon(Icons.add), + onPressed: state.teamCharacters.length <= CustomBuildBloc.maxNumberOfTeamCharacters + ? () => _addTeamCharacter(context, state.teamCharacters.map((e) => e.key).toList(), state.character.key) + : null, + ), + ), + Tooltip( + message: s.sort, + child: IconButton( + padding: EdgeInsets.zero, + splashRadius: Styles.smallButtonSplashRadius, + icon: const Icon(Icons.sort), + onPressed: state.teamCharacters.length < 2 + ? null + : () => showDialog( + context: context, + builder: (_) => SortItemsDialog( + items: state.teamCharacters.map((e) => SortableItem(e.key, e.name)).toList(), + onSave: (result) { + if (!result.somethingChanged) { + return; + } - context.read().add(CustomBuildEvent.teamCharactersOrderChanged(characters: result.items)); - }, - ), - ), - ), - ), - Tooltip( - message: s.clearAll, - child: IconButton( - padding: EdgeInsets.zero, - splashRadius: Styles.smallButtonSplashRadius, - icon: const Icon(Icons.clear_all), - onPressed: teamCharacters.isEmpty ? null : () => context.read().add(const CustomBuildEvent.deleteTeamCharacters()), + context.read().add(CustomBuildEvent.teamCharactersOrderChanged(characters: result.items)); + }, + ), + ), + ), + ), + Tooltip( + message: s.clearAll, + child: IconButton( + padding: EdgeInsets.zero, + splashRadius: Styles.smallButtonSplashRadius, + icon: const Icon(Icons.clear_all), + onPressed: state.teamCharacters.isEmpty + ? null + : () => context.read().add(const CustomBuildEvent.deleteTeamCharacters()), + ), + ), + ], ), - ), - ], - ), - if (teamCharacters.isEmpty) - NothingFound(msg: s.startByAddingCharacters) - else - ...teamCharacters.map((e) => TeamCharacterRow(character: e, teamCount: teamCharacters.length, color: color)).toList(), - ], + if (state.teamCharacters.isEmpty) + NothingFound(msg: s.startByAddingCharacters) + else + ...state.teamCharacters.map((e) => TeamCharacterRow(character: e, teamCount: state.teamCharacters.length, color: color)).toList(), + ], + ); + }, + orElse: () => const Loading(useScaffold: false), + ), ); } - Future _addTeamCharacter(BuildContext context) async { + Future _addTeamCharacter(BuildContext context, List teamCharacterKeys, String characterKey) async { final bloc = context.read(); - final exclude = [...teamCharacters.map((e) => e.key), mainCharKey]; + final exclude = [...teamCharacterKeys, characterKey]; final key = await CharactersPage.forSelection(context, excludeKeys: exclude); if (key.isNullEmptyOrWhitespace) { return; diff --git a/lib/presentation/custom_build/widgets/weapon_section.dart b/lib/presentation/custom_build/widgets/weapon_section.dart index a92250e9d..5af14ae71 100644 --- a/lib/presentation/custom_build/widgets/weapon_section.dart +++ b/lib/presentation/custom_build/widgets/weapon_section.dart @@ -8,21 +8,17 @@ import 'package:shiori/domain/models/models.dart'; import 'package:shiori/generated/l10n.dart'; import 'package:shiori/presentation/custom_build/widgets/weapon_row.dart'; import 'package:shiori/presentation/shared/dialogs/sort_items_dialog.dart'; +import 'package:shiori/presentation/shared/extensions/element_type_extensions.dart'; +import 'package:shiori/presentation/shared/loading.dart'; import 'package:shiori/presentation/shared/nothing_found.dart'; import 'package:shiori/presentation/shared/styles.dart'; import 'package:shiori/presentation/weapons/weapons_page.dart'; class WeaponSection extends StatelessWidget { - final List weapons; - final WeaponType weaponType; - final Color color; final double maxItemImageWidth; const WeaponSection({ Key? key, - required this.weapons, - required this.weaponType, - required this.color, required this.maxItemImageWidth, }) : super(key: key); @@ -31,91 +27,99 @@ class WeaponSection extends StatelessWidget { final s = S.of(context); final theme = Theme.of(context); final isPortrait = MediaQuery.of(context).orientation == Orientation.portrait; - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - padding: Styles.edgeInsetVertical10, - // margin: const EdgeInsets.only(bottom: 10), - decoration: BoxDecoration( - color: color, - border: isPortrait ? const Border(top: BorderSide(color: Colors.white)) : null, - ), - child: Text( - '${s.weapons} (${weapons.length} / ${CustomBuildBloc.maxNumberOfWeapons})', - textAlign: TextAlign.center, - style: theme.textTheme.subtitle1!.copyWith(fontWeight: FontWeight.bold), - ), - ), - ButtonBar( - buttonPadding: EdgeInsets.zero, - children: [ - Tooltip( - message: s.add, - child: IconButton( - padding: EdgeInsets.zero, - splashRadius: Styles.smallButtonSplashRadius, - icon: const Icon(Icons.add), - onPressed: () => _openWeaponsPage(context), + return BlocBuilder( + builder: (context, state) => state.maybeMap( + loaded: (state) { + final color = state.character.elementType.getElementColorFromContext(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: Styles.edgeInsetVertical10, + // margin: const EdgeInsets.only(bottom: 10), + decoration: BoxDecoration( + color: color, + border: isPortrait ? const Border(top: BorderSide(color: Colors.white)) : null, + ), + child: Text( + '${s.weapons} (${state.weapons.length} / ${CustomBuildBloc.maxNumberOfWeapons})', + textAlign: TextAlign.center, + style: theme.textTheme.subtitle1!.copyWith(fontWeight: FontWeight.bold), + ), ), - ), - Tooltip( - message: s.sort, - child: IconButton( - padding: EdgeInsets.zero, - splashRadius: Styles.smallButtonSplashRadius, - icon: const Icon(Icons.sort), - onPressed: weapons.length < 2 - ? null - : () => showDialog( - context: context, - builder: (_) => SortItemsDialog( - items: weapons.map((e) => SortableItem(e.key, e.name)).toList(), - onSave: (result) { - if (!result.somethingChanged) { - return; - } + ButtonBar( + buttonPadding: EdgeInsets.zero, + children: [ + Tooltip( + message: s.add, + child: IconButton( + padding: EdgeInsets.zero, + splashRadius: Styles.smallButtonSplashRadius, + icon: const Icon(Icons.add), + onPressed: () => _openWeaponsPage(context, state.weapons.map((e) => e.key).toList(), state.character.weaponType), + ), + ), + Tooltip( + message: s.sort, + child: IconButton( + padding: EdgeInsets.zero, + splashRadius: Styles.smallButtonSplashRadius, + icon: const Icon(Icons.sort), + onPressed: state.weapons.length < 2 + ? null + : () => showDialog( + context: context, + builder: (_) => SortItemsDialog( + items: state.weapons.map((e) => SortableItem(e.key, e.name)).toList(), + onSave: (result) { + if (!result.somethingChanged) { + return; + } - context.read().add(CustomBuildEvent.weaponsOrderChanged(weapons: result.items)); - }, - ), - ), + context.read().add(CustomBuildEvent.weaponsOrderChanged(weapons: result.items)); + }, + ), + ), + ), + ), + Tooltip( + message: s.clearAll, + child: IconButton( + padding: EdgeInsets.zero, + splashRadius: Styles.smallButtonSplashRadius, + icon: const Icon(Icons.clear_all), + onPressed: state.weapons.isEmpty ? null : () => context.read().add(const CustomBuildEvent.deleteWeapons()), + ), + ), + ], ), - ), - Tooltip( - message: s.clearAll, - child: IconButton( - padding: EdgeInsets.zero, - splashRadius: Styles.smallButtonSplashRadius, - icon: const Icon(Icons.clear_all), - onPressed: weapons.isEmpty ? null : () => context.read().add(const CustomBuildEvent.deleteWeapons()), - ), - ), - ], - ), - if (weapons.isEmpty) - NothingFound(msg: s.startByAddingWeapons) - else - ...weapons - .map( - (e) => WeaponRow( - weapon: e, - color: color, - maxImageWidth: maxItemImageWidth, - weaponCount: weapons.length, - ), - ) - .toList(), - ], + if (state.weapons.isEmpty) + NothingFound(msg: s.startByAddingWeapons) + else + ...state.weapons + .map( + (e) => WeaponRow( + weapon: e, + color: color, + maxImageWidth: maxItemImageWidth, + weaponCount: state.weapons.length, + ), + ) + .toList(), + ], + ); + }, + orElse: () => const Loading(useScaffold: false), + ), ); } - Future _openWeaponsPage(BuildContext context) async { + Future _openWeaponsPage(BuildContext context, List excludedKeys, WeaponType weaponType) async { final bloc = context.read(); final selectedKey = await WeaponsPage.forSelection( context, - excludeKeys: weapons.map((e) => e.key).toList(), + excludeKeys: excludedKeys, weaponTypes: [weaponType], areWeaponTypesEnabled: false, );