From 530f20b72af7010d737140cf71ccc7fedeba8346 Mon Sep 17 00:00:00 2001 From: Efrain Date: Mon, 29 Mar 2021 22:56:16 -0500 Subject: [PATCH] When anything changes in the tier list, it gets saved into db --- lib/application/tierlist/tier_list_bloc.dart | 62 ++++++---- lib/application/tierlist/tier_list_event.dart | 6 +- lib/application/tierlist/tier_list_state.dart | 2 - lib/domain/models/entities.dart | 1 + .../entities/tierlist/tierlist_item.dart | 20 ++++ lib/domain/services/data_service.dart | 6 + lib/infrastructure/data_service.dart | 23 ++++ lib/main.dart | 3 +- .../home/widgets/sliver_tierlist_card.dart | 2 + lib/presentation/tierlist/tier_list_page.dart | 106 ++++++++---------- .../tierlist/widgets/tierlist_fab.dart | 32 +++--- .../tierlist/widgets/tierlist_row.dart | 28 +++-- 12 files changed, 183 insertions(+), 108 deletions(-) create mode 100644 lib/domain/models/entities/tierlist/tierlist_item.dart diff --git a/lib/application/tierlist/tier_list_bloc.dart b/lib/application/tierlist/tier_list_bloc.dart index ff2fc9da8..db597164b 100644 --- a/lib/application/tierlist/tier_list_bloc.dart +++ b/lib/application/tierlist/tier_list_bloc.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:genshindb/domain/models/models.dart'; +import 'package:genshindb/domain/services/data_service.dart'; import 'package:genshindb/domain/services/genshin_service.dart'; import 'package:genshindb/domain/services/logging_service.dart'; import 'package:genshindb/domain/services/telemetry_service.dart'; @@ -12,8 +13,11 @@ part 'tier_list_bloc.freezed.dart'; part 'tier_list_event.dart'; part 'tier_list_state.dart'; +const _initialState = TierListState.loaded(rows: [], charsAvailable: [], readyToSave: false); + class TierListBloc extends Bloc { final GenshinService _genshinService; + final DataService _dataService; final TelemetryService _telemetryService; final LoggingService _loggingService; final List defaultColors = [ @@ -26,16 +30,12 @@ class TierListBloc extends Bloc { _LoadedState get currentState => state as _LoadedState; - TierListBloc(this._genshinService, this._telemetryService, this._loggingService) : super(const TierListState.loading()); + TierListBloc(this._genshinService, this._dataService, this._telemetryService, this._loggingService) : super(_initialState); @override Stream mapEventToState(TierListEvent event) async* { - if (event is _Init) { - yield const TierListState.loading(); - } - final s = await event.map( - init: (_) async => _init(), + init: (e) async => _init(e.reset), rowTextChanged: (e) async => _rowTextChanged(e.index, e.newValue), rowPositionChanged: (e) async => _rowPositionChanged(e.index, e.newIndex), rowColorChanged: (e) async => _rowColorChanged(e.index, e.newColor), @@ -55,36 +55,51 @@ class TierListBloc extends Bloc { return currentState; }, + close: (e) async => _initialState, ); yield s; } - Future _init() async { + Future _init(bool reset) async { await _telemetryService.trackTierListOpened(); + if (reset) { + await _dataService.deleteTierList(); + } + + final tierList = _dataService.getTierList(); final defaultTierList = _genshinService.getDefaultCharacterTierList(defaultColors); - return TierListState.loaded(rows: defaultTierList, charsAvailable: [], readyToSave: false); + if (tierList.isEmpty) { + return TierListState.loaded(rows: defaultTierList, charsAvailable: [], readyToSave: false); + } + + final usedCharImgs = tierList.expand((el) => el.charImgs).toList(); + final availableChars = defaultTierList.expand((el) => el.charImgs).where((el) => !usedCharImgs.contains(el)).toList(); + return TierListState.loaded(rows: tierList, charsAvailable: availableChars, readyToSave: false); } - TierListState _rowTextChanged(int index, String newValue) { + Future _rowTextChanged(int index, String newValue) async { final updated = currentState.rows.elementAt(index).copyWith.call(tierText: newValue); final rows = _updateRows(updated, index, index); + await _dataService.saveTierList(rows); return currentState.copyWith.call(rows: rows); } - TierListState _rowPositionChanged(int index, int newIndex) { + Future _rowPositionChanged(int index, int newIndex) async { final updated = currentState.rows.elementAt(index); final rows = _updateRows(updated, newIndex, index); + await _dataService.saveTierList(rows); return currentState.copyWith.call(rows: rows); } - TierListState _rowColorChanged(int index, int newColor) { + Future _rowColorChanged(int index, int newColor) async { final updated = currentState.rows.elementAt(index).copyWith.call(tierColor: newColor); final rows = _updateRows(updated, index, index); + await _dataService.saveTierList(rows); return currentState.copyWith.call(rows: rows); } - TierListState _addNewRow(int index, bool above) { + Future _addNewRow(int index, bool above) async { final colorsCopy = [...defaultColors]; final color = (colorsCopy..shuffle()).first; final newIndex = above ? index : index + 1; @@ -92,10 +107,11 @@ class TierListBloc extends Bloc { final newRow = TierListRowModel.row(tierText: (currentState.rows.length + 1).toString(), tierColor: color, charImgs: []); final rows = [...currentState.rows]; rows.insert(newIndex, newRow); + await _dataService.saveTierList(rows); return currentState.copyWith.call(rows: rows); } - TierListState _deleteRow(int index) { + Future _deleteRow(int index) async { if (currentState.rows.length == 1) { return currentState; } @@ -103,35 +119,42 @@ class TierListBloc extends Bloc { final row = rows.elementAt(index); final chars = _updateAvailableChars([...currentState.charsAvailable, ...row.charImgs], []); rows.removeAt(index); + await _dataService.saveTierList(rows); return currentState.copyWith.call(rows: rows, charsAvailable: chars); } - TierListState _clearRow(int index) { + Future _clearRow(int index) async { final row = currentState.rows.elementAt(index); final updated = row.copyWith.call(charImgs: []); final rows = _updateRows(updated, index, index); final chars = _updateAvailableChars([...currentState.charsAvailable, ...row.charImgs], []); + await _dataService.saveTierList(rows); return currentState.copyWith.call(rows: rows, charsAvailable: chars); } - TierListState _clearAllRows() { + Future _clearAllRows() async { final chars = _updateAvailableChars(_genshinService.getDefaultCharacterTierList(defaultColors).expand((row) => row.charImgs).toList(), []); final updatedRows = currentState.rows.map((row) => row.copyWith.call(charImgs: [])).toList(); + await _dataService.saveTierList(updatedRows); return currentState.copyWith.call(rows: updatedRows, charsAvailable: chars, readyToSave: false); } - TierListState _addCharacterToRow(int index, String charImg) { + Future _addCharacterToRow(int index, String charImg) async { final row = currentState.rows.elementAt(index); final updated = row.copyWith.call(charImgs: [...row.charImgs, charImg]); final updatedChars = _updateAvailableChars(currentState.charsAvailable, [charImg]); - return currentState.copyWith.call(rows: _updateRows(updated, index, index), charsAvailable: updatedChars); + final updatedRows = _updateRows(updated, index, index); + await _dataService.saveTierList(updatedRows); + return currentState.copyWith.call(rows: updatedRows, charsAvailable: updatedChars); } - TierListState _deleteCharacterFromRow(int index, String charImg) { + Future _deleteCharacterFromRow(int index, String charImg) async { final row = currentState.rows.elementAt(index); final updated = row.copyWith.call(charImgs: row.charImgs.where((img) => img != charImg).toList()); final updatedChars = _updateAvailableChars([...currentState.charsAvailable, charImg], []); - return currentState.copyWith.call(rows: _updateRows(updated, index, index), charsAvailable: updatedChars, readyToSave: false); + final updatedRows = _updateRows(updated, index, index); + await _dataService.saveTierList(updatedRows); + return currentState.copyWith.call(rows: updatedRows, charsAvailable: updatedChars, readyToSave: false); } List _updateRows(TierListRowModel updated, int newIndex, int excludeIndex) { @@ -150,7 +173,6 @@ class TierListBloc extends Bloc { } rows.insert(newIndex, updated); -// rows.sort((x, y) => x.index - y.index); return rows; } diff --git a/lib/application/tierlist/tier_list_event.dart b/lib/application/tierlist/tier_list_event.dart index f95ee0847..7869cf742 100644 --- a/lib/application/tierlist/tier_list_event.dart +++ b/lib/application/tierlist/tier_list_event.dart @@ -2,7 +2,9 @@ part of 'tier_list_bloc.dart'; @freezed abstract class TierListEvent with _$TierListEvent { - const factory TierListEvent.init() = _Init; + const factory TierListEvent.init({ + @Default(false) bool reset, + }) = _Init; const factory TierListEvent.rowTextChanged({ @required int index, @@ -51,4 +53,6 @@ abstract class TierListEvent with _$TierListEvent { dynamic ex, StackTrace trace, }) = _ScreenshotTaken; + + const factory TierListEvent.close() = _Close; } diff --git a/lib/application/tierlist/tier_list_state.dart b/lib/application/tierlist/tier_list_state.dart index aaceb65f7..337d0e737 100644 --- a/lib/application/tierlist/tier_list_state.dart +++ b/lib/application/tierlist/tier_list_state.dart @@ -2,8 +2,6 @@ part of 'tier_list_bloc.dart'; @freezed abstract class TierListState with _$TierListState { - const factory TierListState.loading() = _LoadingState; - const factory TierListState.loaded({ @required List rows, @required List charsAvailable, diff --git a/lib/domain/models/entities.dart b/lib/domain/models/entities.dart index 639ec014f..66647bc9f 100644 --- a/lib/domain/models/entities.dart +++ b/lib/domain/models/entities.dart @@ -4,3 +4,4 @@ export 'entities/calculator/calculator_session.dart'; export 'entities/game_code/used_game_code.dart'; export 'entities/inventory/inventory_item.dart'; export 'entities/inventory/inventory_used_item.dart'; +export 'entities/tierlist/tierlist_item.dart'; diff --git a/lib/domain/models/entities/tierlist/tierlist_item.dart b/lib/domain/models/entities/tierlist/tierlist_item.dart new file mode 100644 index 000000000..77a0c7632 --- /dev/null +++ b/lib/domain/models/entities/tierlist/tierlist_item.dart @@ -0,0 +1,20 @@ +import 'package:hive/hive.dart'; + +part 'tierlist_item.g.dart'; + +@HiveType(typeId: 7) +class TierListItem extends HiveObject { + @HiveField(0) + final String text; + + @HiveField(1) + final int color; + + @HiveField(2) + final int position; + + @HiveField(3) + final List charsImgs; + + TierListItem(this.text, this.color, this.position, this.charsImgs); +} diff --git a/lib/domain/services/data_service.dart b/lib/domain/services/data_service.dart index 75fd5a443..f80edf063 100644 --- a/lib/domain/services/data_service.dart +++ b/lib/domain/services/data_service.dart @@ -63,4 +63,10 @@ abstract class DataService { List getAllUsedGameCodes(); Future markCodeAsUsed(String code, {bool wasUsed = true}); + + List getTierList(); + + Future saveTierList(List tierList); + + Future deleteTierList(); } diff --git a/lib/infrastructure/data_service.dart b/lib/infrastructure/data_service.dart index 6bf47a3ab..2e5961963 100644 --- a/lib/infrastructure/data_service.dart +++ b/lib/infrastructure/data_service.dart @@ -2,6 +2,7 @@ import 'package:genshindb/domain/app_constants.dart'; import 'package:genshindb/domain/assets.dart'; import 'package:genshindb/domain/enums/enums.dart'; import 'package:genshindb/domain/enums/item_type.dart'; +import 'package:genshindb/domain/extensions/iterable_extensions.dart'; import 'package:genshindb/domain/extensions/string_extensions.dart'; import 'package:genshindb/domain/models/entities.dart'; import 'package:genshindb/domain/models/models.dart'; @@ -20,6 +21,7 @@ class DataServiceImpl implements DataService { Box _inventoryBox; Box _inventoryUsedItemsBox; Box _usedGameCodesBox; + Box _tierListBox; DataServiceImpl(this._genshinService, this._calculatorService); @@ -32,6 +34,7 @@ class DataServiceImpl implements DataService { _inventoryBox = await Hive.openBox('inventory'); _inventoryUsedItemsBox = await Hive.openBox('inventoryUsedItems'); _usedGameCodesBox = await Hive.openBox('usedGameCodes'); + _tierListBox = await Hive.openBox('tierList'); } @override @@ -375,6 +378,25 @@ class DataServiceImpl implements DataService { } } + @override + List getTierList() { + final values = _tierListBox.values.toList()..sort((x, y) => x.position.compareTo(y.position)); + return values.map((e) => TierListRowModel.row(tierText: e.text, charImgs: e.charsImgs, tierColor: e.color)).toList(); + } + + @override + Future saveTierList(List tierList) async { + await deleteTierList(); + final toSave = tierList.mapIndex((e, i) => TierListItem(e.tierText, e.tierColor, i, e.charImgs)).toList(); + await _tierListBox.addAll(toSave); + } + + @override + Future deleteTierList() async { + final keys = _tierListBox.values.map((e) => e.key); + await _tierListBox.deleteAll(keys); + } + void _registerAdapters() { Hive.registerAdapter(CalculatorCharacterSkillAdapter()); Hive.registerAdapter(CalculatorItemAdapter()); @@ -382,6 +404,7 @@ class DataServiceImpl implements DataService { Hive.registerAdapter(InventoryItemAdapter()); Hive.registerAdapter(InventoryUsedItemAdapter()); Hive.registerAdapter(UsedGameCodeAdapter()); + Hive.registerAdapter(TierListItemAdapter()); } ItemAscensionMaterials _buildForCharacter(CalculatorItem item, {int calculatorItemKey, bool includeInventory = false}) { diff --git a/lib/main.dart b/lib/main.dart index a47c59090..47f81ca50 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -159,9 +159,10 @@ class MyApp extends StatelessWidget { BlocProvider( create: (_) { final genshinService = getIt(); + final dataService = getIt(); final telemetryService = getIt(); final loggingService = getIt(); - return TierListBloc(genshinService, telemetryService, loggingService); + return TierListBloc(genshinService, dataService, telemetryService, loggingService); }, ), BlocProvider( diff --git a/lib/presentation/home/widgets/sliver_tierlist_card.dart b/lib/presentation/home/widgets/sliver_tierlist_card.dart index f7b1308d2..c03e777d3 100644 --- a/lib/presentation/home/widgets/sliver_tierlist_card.dart +++ b/lib/presentation/home/widgets/sliver_tierlist_card.dart @@ -30,5 +30,7 @@ class SliverTierList extends StatelessWidget { context.read().add(const TierListEvent.init()); final route = MaterialPageRoute(builder: (c) => TierListPage()); await Navigator.push(context, route); + await route.completed; + context.read().add(const TierListEvent.close()); } } diff --git a/lib/presentation/tierlist/tier_list_page.dart b/lib/presentation/tierlist/tier_list_page.dart index e7ac6a494..e45ac804c 100644 --- a/lib/presentation/tierlist/tier_list_page.dart +++ b/lib/presentation/tierlist/tier_list_page.dart @@ -4,7 +4,6 @@ import 'package:genshindb/application/bloc.dart'; import 'package:genshindb/domain/extensions/iterable_extensions.dart'; import 'package:genshindb/domain/models/models.dart'; import 'package:genshindb/generated/l10n.dart'; -import 'package:genshindb/presentation/shared/loading.dart'; import 'package:genshindb/presentation/shared/utils/toast_utils.dart'; import 'package:genshindb/presentation/tierlist/widgets/tierlist_fab.dart'; import 'package:genshindb/presentation/tierlist/widgets/tierlist_row.dart'; @@ -27,56 +26,51 @@ class _TierListPageState extends State { appBar: PreferredSize( preferredSize: const Size(double.infinity, kToolbarHeight), child: BlocBuilder( - builder: (ctx, state) { - return state.map( - loading: (_) => AppBar(title: Text(s.tierListBuilder)), - loaded: (state) => AppBar( - title: Text(s.tierListBuilder), - actions: [ - if (!state.readyToSave) - Tooltip( - message: s.confirm, - child: IconButton( - icon: const Icon(Icons.check), - onPressed: () => ctx.read().add(const TierListEvent.readyToSave(ready: true)), - ), - ), - if (!state.readyToSave) - Tooltip( - message: s.clearAll, - child: IconButton( - icon: const Icon(Icons.clear_all), - onPressed: () => context.read().add(const TierListEvent.clearAllRows()), - ), - ), - if (!state.readyToSave) - Tooltip( - message: s.restore, - child: IconButton( - icon: const Icon(Icons.settings_backup_restore_sharp), - onPressed: () => context.read().add(const TierListEvent.init()), - ), - ), - if (state.readyToSave) - Tooltip( - message: s.save, - child: IconButton( - icon: const Icon(Icons.save), - onPressed: () => _takeScreenshot(), - ), - ), - if (state.readyToSave) - Tooltip( - message: s.cancel, - child: IconButton( - icon: const Icon(Icons.undo), - onPressed: () => context.read().add(const TierListEvent.readyToSave(ready: false)), - ), - ), - ], - ), - ); - }, + builder: (ctx, state) => AppBar( + title: Text(s.tierListBuilder), + actions: [ + if (!state.readyToSave) + Tooltip( + message: s.confirm, + child: IconButton( + icon: const Icon(Icons.check), + onPressed: () => ctx.read().add(const TierListEvent.readyToSave(ready: true)), + ), + ), + if (!state.readyToSave) + Tooltip( + message: s.clearAll, + child: IconButton( + icon: const Icon(Icons.clear_all), + onPressed: () => context.read().add(const TierListEvent.clearAllRows()), + ), + ), + if (!state.readyToSave) + Tooltip( + message: s.restore, + child: IconButton( + icon: const Icon(Icons.settings_backup_restore_sharp), + onPressed: () => context.read().add(const TierListEvent.init(reset: true)), + ), + ), + if (state.readyToSave) + Tooltip( + message: s.save, + child: IconButton( + icon: const Icon(Icons.save), + onPressed: () => _takeScreenshot(), + ), + ), + if (state.readyToSave) + Tooltip( + message: s.cancel, + child: IconButton( + icon: const Icon(Icons.undo), + onPressed: () => context.read().add(const TierListEvent.readyToSave(ready: false)), + ), + ), + ], + ), ), ), floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, @@ -88,12 +82,9 @@ class _TierListPageState extends State { child: Screenshot( controller: screenshotController, child: BlocBuilder( - builder: (ctx, state) => state.map( - loading: (_) => const Loading(useScaffold: false), - loaded: (state) => Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: state.rows.mapIndex((e, index) => _buildTierRow(index, state.rows.length, state.readyToSave, e)).toList(), - ), + builder: (ctx, state) => Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: state.rows.mapIndex((e, index) => _buildTierRow(index, state.rows.length, state.readyToSave, e)).toList(), ), ), ), @@ -113,6 +104,7 @@ class _TierListPageState extends State { isDownButtonEnabled: index != totalNumberOfItems - 1, numberOfRows: totalNumberOfItems, showButtons: !readyToSave, + isTheLastRow: totalNumberOfItems == 1, ); } diff --git a/lib/presentation/tierlist/widgets/tierlist_fab.dart b/lib/presentation/tierlist/widgets/tierlist_fab.dart index dec2fa4b0..316e120f5 100644 --- a/lib/presentation/tierlist/widgets/tierlist_fab.dart +++ b/lib/presentation/tierlist/widgets/tierlist_fab.dart @@ -2,31 +2,27 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:genshindb/application/bloc.dart'; import 'package:genshindb/presentation/shared/circle_character.dart'; -import 'package:genshindb/presentation/shared/loading.dart'; import 'package:genshindb/presentation/shared/styles.dart'; class TierListFab extends StatelessWidget { @override Widget build(BuildContext context) { return BlocBuilder( - builder: (ctx, state) => state.map( - loading: (_) => const Loading(useScaffold: false), - loaded: (state) => !state.readyToSave - ? Container( - color: Colors.black.withOpacity(0.5), - child: SizedBox( - height: 100, - child: ListView( - padding: EdgeInsets.zero, - physics: const BouncingScrollPhysics(), - shrinkWrap: true, - scrollDirection: Axis.horizontal, - children: state.charsAvailable.map((e) => _buildDraggableItem(e)).toList(), - ), + builder: (ctx, state) => !state.readyToSave + ? Container( + color: Colors.black.withOpacity(0.5), + child: SizedBox( + height: 100, + child: ListView( + padding: EdgeInsets.zero, + physics: const BouncingScrollPhysics(), + shrinkWrap: true, + scrollDirection: Axis.horizontal, + children: state.charsAvailable.map((e) => _buildDraggableItem(e)).toList(), ), - ) - : Container(), - ), + ), + ) + : Container(), ); } diff --git a/lib/presentation/tierlist/widgets/tierlist_row.dart b/lib/presentation/tierlist/widgets/tierlist_row.dart index 2bfe8c510..03ddbcb05 100644 --- a/lib/presentation/tierlist/widgets/tierlist_row.dart +++ b/lib/presentation/tierlist/widgets/tierlist_row.dart @@ -25,6 +25,7 @@ class TierListRow extends StatelessWidget { final bool isUpButtonEnabled; final bool isDownButtonEnabled; final int numberOfRows; + final bool isTheLastRow; const TierListRow({ Key key, @@ -33,6 +34,7 @@ class TierListRow extends StatelessWidget { @required this.color, @required this.images, @required this.numberOfRows, + @required this.isTheLastRow, this.showButtons = true, this.isUpButtonEnabled = true, this.isDownButtonEnabled = true, @@ -54,7 +56,13 @@ class TierListRow extends StatelessWidget { child: Container( constraints: const BoxConstraints(minHeight: 120), color: color, - child: Center(child: Text(title, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 24))), + child: Center( + child: Text( + title, + textAlign: TextAlign.center, + style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 24), + ), + ), ), ), Flexible( @@ -102,14 +110,16 @@ class TierListRow extends StatelessWidget { value: TierListRowOptionsType.rename, child: _buildOption(Icons.edit, s.rename), ), - PopupMenuItem( - value: TierListRowOptionsType.delete, - child: _buildOption(Icons.delete, s.deleteRow), - ), - PopupMenuItem( - value: TierListRowOptionsType.clear, - child: _buildOption(Icons.clear_all, s.clearRow), - ), + if (!isTheLastRow) + PopupMenuItem( + value: TierListRowOptionsType.delete, + child: _buildOption(Icons.delete, s.deleteRow), + ), + if (images.isNotEmpty) + PopupMenuItem( + value: TierListRowOptionsType.clear, + child: _buildOption(Icons.clear_all, s.clearRow), + ), PopupMenuItem( value: TierListRowOptionsType.changeColor, child: _buildOption(Icons.color_lens_outlined, s.changeColor),