diff --git a/lib/application/custom_build/custom_build_bloc.dart b/lib/application/custom_build/custom_build_bloc.dart index 85025d34b..264492cb6 100644 --- a/lib/application/custom_build/custom_build_bloc.dart +++ b/lib/application/custom_build/custom_build_bloc.dart @@ -24,13 +24,21 @@ class CustomBuildBloc extends Bloc { CharacterSkillType.elementalBurst, ]; static List excludedSkillTypes = [CharacterSkillType.others]; + static int maxNumberOfWeapons = 10; + static int maxNumberOfTeamCharacters = 10; CustomBuildBloc(this._genshinService, this._dataService) : super(const CustomBuildState.loading()) { on(_handleEvent); } +//TODO: REMOVE UPCOMING CHARACTERS ? Future _handleEvent(CustomBuildEvent event, Emitter emit) async { - //todo: SHOULD I TRHOW ON INVALID REQUEST ? + //TODO: SHOULD I TRHOW ON INVALID REQUEST ? + //IN MOST CASES THERE ARE SOME VALIDATIONS FOR THINGS LIKE + // if (!state.weapons.any((el) => el.key == e.key)) { + // return state; + // } + // WHICH SHOULD NOT HAPPEN BUT MAYBE I SHOULD THROW AN EXCEPTION IN THERE final s = await event.map( load: (e) async => _init(e.key), characterChanged: (e) async => state.maybeMap( @@ -57,11 +65,18 @@ class CustomBuildBloc extends Bloc { loaded: (state) => _addWeapon(e, state), orElse: () => state, ), + weaponRefinementChanged: (e) async => state.maybeMap( + loaded: (state) => _weaponRefinementChanged(e, state), + orElse: () => state, + ), + weaponsOrderChanged: (e) async => state.maybeMap( + loaded: (state) => _weaponsOrderChanged(e, state), + orElse: () => state, + ), deleteWeapon: (e) async => state.maybeMap( loaded: (state) => _deleteWeapon(e, state), orElse: () => state, ), - weaponOrderChanged: (e) async => state, addArtifact: (e) async => state.maybeMap( loaded: (state) => _addArtifact(e, state), orElse: () => state, @@ -107,6 +122,22 @@ class CustomBuildBloc extends Bloc { loaded: (state) => _deleteArtifact(e, state), orElse: () => state, ), + addTeamCharacter: (e) async => state.maybeMap( + loaded: (state) => _addTeamCharacter(e, state), + orElse: () => state, + ), + teamCharactersOrderChanged: (e) async => state.maybeMap( + loaded: (state) => _teamCharactersOrderChanged(e, state), + orElse: () => state, + ), + deleteTeamCharacter: (e) async => state.maybeMap( + loaded: (state) => _deleteTeamCharacter(e, state), + orElse: () => state, + ), + deleteTeamCharacters: (e) async => state.maybeMap( + loaded: (state) => state.copyWith.call(teamCharacters: []), + orElse: () => state, + ), ); emit(s); @@ -127,6 +158,7 @@ class CustomBuildBloc extends Bloc { notes: build.notes, skillPriorities: build.skillPriorities, artifacts: build.artifacts..sort((x, y) => x.type.index.compareTo(y.type.index)), + teamCharacters: build.teamCharacters, subStatsSummary: _generateSubStatSummary(build.artifacts), ); } @@ -141,7 +173,8 @@ class CustomBuildBloc extends Bloc { character: character, notes: [], weapons: [], - artifacts: []..sort((x, y) => x.type.index.compareTo(y.type.index)), + artifacts: [], + teamCharacters: [], skillPriorities: [], subStatsSummary: [], ); @@ -195,7 +228,56 @@ class CustomBuildBloc extends Bloc { throw Exception('Weapons cannot be repeated'); } final weapon = _genshinService.getWeaponForCard(e.key); - final weapons = [...state.weapons, weapon]; + final newOne = CustomBuildWeaponModel( + key: e.key, + index: state.weapons.length, + refinement: getWeaponMaxRefinementLevel(weapon.rarity) <= 0 ? 0 : 1, + name: weapon.name, + image: weapon.image, + rarity: weapon.rarity, + baseAtk: weapon.baseAtk, + subStatType: weapon.subStatType, + subStatValue: weapon.subStatValue, + ); + final weapons = [...state.weapons, newOne]; + return state.copyWith.call(weapons: weapons); + } + + CustomBuildState _weaponsOrderChanged(_WeaponsOrderChanged e, _LoadedState state) { + final weapons = []; + for (var i = 0; i < e.weapons.length; i++) { + final sortableItem = e.weapons[i]; + final current = state.weapons.firstWhereOrNull((el) => el.key == sortableItem.key); + if (current == null) { + throw Exception('Team Character with key = ${sortableItem.key} does not exist'); + } + weapons.add(current.copyWith.call(index: i)); + } + + return state.copyWith.call(weapons: weapons); + } + + CustomBuildState _weaponRefinementChanged(_WeaponRefinementChanged e, _LoadedState state) { + final current = state.weapons.firstWhereOrNull((el) => el.key == e.key); + if (current == null) { + return state; + } + + if (current.refinement == e.newValue) { + return state; + } + + final maxValue = getWeaponMaxRefinementLevel(current.rarity); + if (e.newValue > maxValue || e.newValue <= 0) { + throw Exception('The provided refinement = ${e.newValue} cannot exceed = $maxValue'); + } + + final index = state.weapons.indexOf(current); + final weapons = [...state.weapons]; + weapons.removeAt(index); + final updated = current.copyWith.call(refinement: e.newValue); + weapons.insert(index, updated); + return state.copyWith.call(weapons: weapons); } @@ -271,6 +353,63 @@ class CustomBuildBloc extends Bloc { return state.copyWith.call(artifacts: updated, subStatsSummary: _generateSubStatSummary(updated)); } + CustomBuildState _addTeamCharacter(_AddTeamCharacter e, _LoadedState state) { + if (state.teamCharacters.length + 1 == maxNumberOfTeamCharacters) { + return state; + } + + final char = _genshinService.getCharacterForCard(e.key); + final updatedTeamCharacters = [...state.teamCharacters]; + final old = updatedTeamCharacters.firstWhereOrNull((el) => el.key == e.key); + if (old != null) { + final index = updatedTeamCharacters.indexOf(old); + updatedTeamCharacters.removeAt(index); + final updated = old.copyWith.call( + key: e.key, + image: char.image, + name: char.name, + roleType: e.roleType, + subType: e.subType, + ); + updatedTeamCharacters.insert(index, updated); + } else { + final newOne = CustomBuildTeamCharacterModel( + key: e.key, + name: char.name, + image: char.image, + index: state.teamCharacters.length, + roleType: e.roleType, + subType: e.subType, + ); + updatedTeamCharacters.add(newOne); + } + return state.copyWith.call(teamCharacters: updatedTeamCharacters); + } + + CustomBuildState _teamCharactersOrderChanged(_TeamCharactersOrderChanged e, _LoadedState state) { + final teamCharacters = []; + for (var i = 0; i < e.characters.length; i++) { + final sortableItem = e.characters[i]; + final current = state.teamCharacters.firstWhereOrNull((el) => el.key == sortableItem.key); + if (current == null) { + throw Exception('Team Character with key = ${sortableItem.key} does not exist'); + } + teamCharacters.add(current.copyWith.call(index: i)); + } + + return state.copyWith.call(teamCharacters: teamCharacters); + } + + CustomBuildState _deleteTeamCharacter(_DeleteTeamCharacter e, _LoadedState state) { + if (!state.teamCharacters.any((el) => el.key == e.key)) { + return state; + } + + final updated = [...state.teamCharacters]; + updated.removeWhere((el) => el.key == e.key); + return state.copyWith.call(teamCharacters: updated); + } + Future _saveChanges(_LoadedState state) async { if (state.key != null) { await _dataService.customBuilds.updateCustomBuild( @@ -281,8 +420,9 @@ class CustomBuildBloc extends Bloc { state.showOnCharacterDetail, state.isRecommended, state.notes, - state.weapons.map((e) => e.key).toList(), + state.weapons, [], + state.teamCharacters, [], ); @@ -296,9 +436,10 @@ class CustomBuildBloc extends Bloc { state.showOnCharacterDetail, state.isRecommended, state.notes, - state.weapons.map((e) => e.key).toList(), + state.weapons, //TODO: THIS [], + state.teamCharacters, [], ); diff --git a/lib/application/custom_build/custom_build_event.dart b/lib/application/custom_build/custom_build_event.dart index 804675347..8cec27242 100644 --- a/lib/application/custom_build/custom_build_event.dart +++ b/lib/application/custom_build/custom_build_event.dart @@ -16,11 +16,26 @@ class CustomBuildEvent with _$CustomBuildEvent { const factory CustomBuildEvent.isRecommendedChanged({required bool newValue}) = _IsRecommendedChanged; + const factory CustomBuildEvent.addSkillPriority({required CharacterSkillType type}) = _AddSkillPriority; + + const factory CustomBuildEvent.deleteSkillPriority({required int index}) = _DeleteSkillPriority; + + const factory CustomBuildEvent.addNote({required String note}) = _AddNote; + + const factory CustomBuildEvent.deleteNote({required int index}) = _DeleteNote; + const factory CustomBuildEvent.addWeapon({required String key}) = _AddWeapon; + const factory CustomBuildEvent.weaponRefinementChanged({ + required String key, + required int newValue, + }) = _WeaponRefinementChanged; + + const factory CustomBuildEvent.weaponsOrderChanged({required List weapons}) = _WeaponsOrderChanged; + const factory CustomBuildEvent.deleteWeapon({required String key}) = _DeleteWeapon; - const factory CustomBuildEvent.weaponOrderChanged({required String key, required int newIndex}) = _WeaponOrderChanged; + const factory CustomBuildEvent.deleteWeapons() = _DeleteWeapons; const factory CustomBuildEvent.addArtifact({ required String key, @@ -35,17 +50,19 @@ class CustomBuildEvent with _$CustomBuildEvent { const factory CustomBuildEvent.deleteArtifact({required ArtifactType type}) = _DeleteArtifact; - const factory CustomBuildEvent.addNote({required String note}) = _AddNote; - - const factory CustomBuildEvent.deleteNote({required int index}) = _DeleteNote; + const factory CustomBuildEvent.deleteArtifacts() = _DeleteArtifacts; - const factory CustomBuildEvent.deleteWeapons() = _DeleteWeapons; + const factory CustomBuildEvent.addTeamCharacter({ + required String key, + required CharacterRoleType roleType, + required CharacterRoleSubType subType, + }) = _AddTeamCharacter; - const factory CustomBuildEvent.deleteArtifacts() = _DeleteArtifacts; + const factory CustomBuildEvent.teamCharactersOrderChanged({required List characters}) = _TeamCharactersOrderChanged; - const factory CustomBuildEvent.addSkillPriority({required CharacterSkillType type}) = _AddSkillPriority; + const factory CustomBuildEvent.deleteTeamCharacter({required String key}) = _DeleteTeamCharacter; - const factory CustomBuildEvent.deleteSkillPriority({required int index}) = _DeleteSkillPriority; + const factory CustomBuildEvent.deleteTeamCharacters() = _DeleteTeamCharacters; const factory CustomBuildEvent.saveChanges() = _SaveChanges; diff --git a/lib/application/custom_build/custom_build_state.dart b/lib/application/custom_build/custom_build_state.dart index 03a2a7147..5ce6a485b 100644 --- a/lib/application/custom_build/custom_build_state.dart +++ b/lib/application/custom_build/custom_build_state.dart @@ -12,8 +12,9 @@ class CustomBuildState with _$CustomBuildState { required bool showOnCharacterDetail, required bool isRecommended, required CharacterCardModel character, - required List weapons, + required List weapons, required List artifacts, + required List teamCharacters, required List notes, required List skillPriorities, required List subStatsSummary, diff --git a/lib/application/weapons/weapons_bloc.dart b/lib/application/weapons/weapons_bloc.dart index 113f038dd..bc7aee638 100644 --- a/lib/application/weapons/weapons_bloc.dart +++ b/lib/application/weapons/weapons_bloc.dart @@ -22,7 +22,11 @@ class WeaponsBloc extends Bloc { @override Stream mapEventToState(WeaponsEvent event) async* { final s = event.map( - init: (e) => _buildInitialState(excludeKeys: e.excludeKeys, weaponTypes: WeaponType.values), + init: (e) => _buildInitialState( + excludeKeys: e.excludeKeys, + weaponTypes: e.weaponTypes.isEmpty ? WeaponType.values : e.weaponTypes, + areWeaponTypesEnabled: e.areWeaponTypesEnabled, + ), weaponFilterTypeChanged: (e) => currentState.copyWith.call(tempWeaponFilterType: e.filterType), rarityChanged: (e) => currentState.copyWith.call(tempRarity: e.rarity), sortDirectionTypeChanged: (e) => currentState.copyWith.call(tempSortDirectionType: e.sortDirectionType), @@ -46,6 +50,7 @@ class WeaponsBloc extends Bloc { weaponSubStatType: currentState.weaponSubStatType, locationType: currentState.weaponLocationType, excludeKeys: currentState.excludeKeys, + areWeaponTypesEnabled: currentState.areWeaponTypesEnabled, ), applyFilterChanges: (_) => _buildInitialState( search: currentState.search, @@ -56,6 +61,7 @@ class WeaponsBloc extends Bloc { weaponSubStatType: currentState.tempWeaponSubStatType, locationType: currentState.tempWeaponLocationType, excludeKeys: currentState.excludeKeys, + areWeaponTypesEnabled: currentState.areWeaponTypesEnabled, ), cancelChanges: (_) => currentState.copyWith.call( tempWeaponFilterType: currentState.weaponFilterType, @@ -65,6 +71,7 @@ class WeaponsBloc extends Bloc { tempWeaponSubStatType: currentState.weaponSubStatType, tempWeaponLocationType: currentState.weaponLocationType, excludeKeys: currentState.excludeKeys, + areWeaponTypesEnabled: currentState.areWeaponTypesEnabled, ), resetFilters: (_) => _buildInitialState( excludeKeys: state.maybeMap(loaded: (state) => state.excludeKeys, orElse: () => []), @@ -84,6 +91,7 @@ class WeaponsBloc extends Bloc { SortDirectionType sortDirectionType = SortDirectionType.asc, StatType? weaponSubStatType, ItemLocationType? locationType, + bool areWeaponTypesEnabled = true, }) { final isLoaded = state is _LoadedState; var data = _genshinService.getWeaponsForCard(); @@ -92,7 +100,7 @@ class WeaponsBloc extends Bloc { } if (!isLoaded) { - final selectedWeaponTypes = WeaponType.values.toList(); + final selectedWeaponTypes = weaponTypes.isEmpty ? WeaponType.values.toList() : weaponTypes; _sortData(data, weaponFilterType, sortDirectionType); return WeaponsState.loaded( weapons: data, @@ -111,6 +119,7 @@ class WeaponsBloc extends Bloc { weaponLocationType: locationType, tempWeaponLocationType: locationType, excludeKeys: excludeKeys, + areWeaponTypesEnabled: areWeaponTypesEnabled, ); } @@ -152,6 +161,7 @@ class WeaponsBloc extends Bloc { weaponLocationType: locationType, tempWeaponLocationType: locationType, excludeKeys: excludeKeys, + areWeaponTypesEnabled: areWeaponTypesEnabled, ); return s; } diff --git a/lib/application/weapons/weapons_event.dart b/lib/application/weapons/weapons_event.dart index 7e9b37417..97e14037e 100644 --- a/lib/application/weapons/weapons_event.dart +++ b/lib/application/weapons/weapons_event.dart @@ -4,6 +4,8 @@ part of 'weapons_bloc.dart'; class WeaponsEvent with _$WeaponsEvent { const factory WeaponsEvent.init({ @Default([]) List excludeKeys, + @Default([]) List weaponTypes, + @Default(true) bool areWeaponTypesEnabled, }) = _Init; const factory WeaponsEvent.searchChanged({ diff --git a/lib/application/weapons/weapons_state.dart b/lib/application/weapons/weapons_state.dart index b7f7ed8bd..fe68cd283 100644 --- a/lib/application/weapons/weapons_state.dart +++ b/lib/application/weapons/weapons_state.dart @@ -20,5 +20,6 @@ class WeaponsState with _$WeaponsState { ItemLocationType? weaponLocationType, ItemLocationType? tempWeaponLocationType, @Default([]) List excludeKeys, + @Default(true) bool areWeaponTypesEnabled, }) = _LoadedState; } diff --git a/lib/domain/services/persistence/custom_builds_data_service.dart b/lib/domain/services/persistence/custom_builds_data_service.dart index e707c63a5..1e1f4c784 100644 --- a/lib/domain/services/persistence/custom_builds_data_service.dart +++ b/lib/domain/services/persistence/custom_builds_data_service.dart @@ -2,6 +2,7 @@ import 'package:shiori/domain/enums/enums.dart'; import 'package:shiori/domain/models/models.dart'; abstract class CustomBuildsDataService { + //TODO: THESE 2 METHODS ARE COMMON IN ALL THE SERVICES Future init(); Future deleteThemAll(); @@ -18,8 +19,9 @@ abstract class CustomBuildsDataService { bool showOnCharacterDetail, bool isRecommended, List notes, - List weaponKeys, + List weapons, List artifacts, + List teamCharacters, List talentPriority, ); @@ -31,8 +33,9 @@ abstract class CustomBuildsDataService { bool showOnCharacterDetail, bool isRecommended, List notes, - List weaponKeys, + List weapons, List artifacts, + List teamCharacters, List talentPriority, );