diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 00000000..5c27c3e3 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,2 @@ +extensions: + - provider: true \ No newline at end of file diff --git a/lib/app/data/models/alignment.dart b/lib/app/data/models/alignment.dart index 7d0d6a79..a4c67cf4 100644 --- a/lib/app/data/models/alignment.dart +++ b/lib/app/data/models/alignment.dart @@ -121,18 +121,12 @@ class AlignmentValue extends dw.Alignment with WithIcon implements WithMeta { class AlignmentValues extends dw.AlignmentValues { AlignmentValues({ required this.meta, - required String good, - required String evil, - required String lawful, - required String neutral, - required String chaotic, - }) : super( - good: good, - evil: evil, - lawful: lawful, - neutral: neutral, - chaotic: chaotic, - ); + required super.good, + required super.evil, + required super.lawful, + required super.neutral, + required super.chaotic, + }); final Meta meta; diff --git a/lib/app/data/models/gear_choice.dart b/lib/app/data/models/gear_choice.dart index 0d24601e..e9cdc341 100644 --- a/lib/app/data/models/gear_choice.dart +++ b/lib/app/data/models/gear_choice.dart @@ -5,19 +5,12 @@ import 'item.dart'; class GearChoice extends dw.GearChoice { GearChoice({ - required String key, - required String description, - required List selections, - List preselect = const [], - int? maxSelections, - }) : _selections = selections, - super( - key: key, - description: description, - selections: selections, - preselect: preselect, - maxSelections: maxSelections, - ); + required super.key, + required super.description, + required List super.selections, + super.preselect, + super.maxSelections, + }) : _selections = selections; @override List get selections => _selections; diff --git a/lib/app/data/models/item.dart b/lib/app/data/models/item.dart index 5afe2c6f..895fb513 100644 --- a/lib/app/data/models/item.dart +++ b/lib/app/data/models/item.dart @@ -2,11 +2,11 @@ import 'dart:convert'; import 'package:dungeon_paper/app/modules/LibraryList/views/filters/item_filters.dart'; import 'package:dungeon_paper/core/dw_icons.dart'; import 'package:dungeon_paper/core/utils/icon_utils.dart'; +import 'package:dungeon_paper/core/utils/list_utils.dart'; import 'package:dungeon_paper/core/utils/string_utils.dart'; import 'package:dungeon_paper/core/utils/uuid.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'item_settings.dart'; import 'meta.dart'; diff --git a/lib/app/data/models/meta.dart b/lib/app/data/models/meta.dart index 10388334..0c01a319 100644 --- a/lib/app/data/models/meta.dart +++ b/lib/app/data/models/meta.dart @@ -5,21 +5,22 @@ import 'package:dungeon_paper/app/data/models/campaign.dart'; import 'package:dungeon_paper/app/data/models/session_marks.dart'; import 'package:dungeon_paper/app/data/models/spell.dart'; import 'package:dungeon_paper/app/data/models/user.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; -import 'package:dungeon_paper/app/data/services/user_service.dart'; +import 'package:dungeon_paper/app/data/services/repository_provider.dart'; +import 'package:dungeon_paper/app/data/services/user_provider.dart'; +import 'package:dungeon_paper/core/global_keys.dart'; import 'package:dungeon_paper/core/utils/date_utils.dart'; +import 'package:dungeon_paper/core/utils/list_utils.dart'; import 'package:dungeon_paper/core/utils/uuid.dart'; import 'package:dungeon_paper/i18n.dart'; +import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; import 'package:dungeon_world_data/gear_option.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; import 'alignment.dart'; import 'bio.dart'; +import 'character.dart'; import 'character_class.dart'; import 'character_stats.dart'; -import 'character.dart'; import 'gear_choice.dart'; import 'gear_selection.dart'; import 'item.dart'; @@ -28,7 +29,7 @@ import 'move.dart'; import 'note.dart'; import 'race.dart'; -class Meta with RepositoryServiceMixin { +class Meta with RepositoryProviderMixin { Meta._({ required this.version, DateTime? created, @@ -220,9 +221,8 @@ class Meta with RepositoryServiceMixin { ); } - static User get user => Get.find().current; - static T forkOrIncrease(T object) { + final user = UserProvider.of(appGlobalKey.currentContext!).current; if (object.meta.isOwnedBy(user)) { return increaseMetaVersion(object); } @@ -483,4 +483,3 @@ mixin WithMeta String get storageKey; dw.EntityReference get reference => Meta.referenceFor(this); } - diff --git a/lib/app/data/models/monster.dart b/lib/app/data/models/monster.dart index aed7e489..bf5d3cfc 100644 --- a/lib/app/data/models/monster.dart +++ b/lib/app/data/models/monster.dart @@ -5,23 +5,14 @@ import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; class Monster extends dw.Monster implements WithMeta { Monster({ - required Meta meta, - required String key, - required String name, - required String description, - required String instinct, - required List tags, - required List moves, - }) : _meta = meta, - super( - meta: meta, - key: key, - name: name, - description: description, - instinct: instinct, - tags: tags, - moves: moves, - ); + required Meta super.meta, + required super.key, + required super.name, + required super.description, + required super.instinct, + required super.tags, + required super.moves, + }) : _meta = meta; @override Meta get meta => _meta; diff --git a/lib/app/data/models/spell.dart b/lib/app/data/models/spell.dart index 09425ec3..e63c4b3a 100644 --- a/lib/app/data/models/spell.dart +++ b/lib/app/data/models/spell.dart @@ -1,17 +1,18 @@ import 'dart:convert'; -import 'package:dungeon_paper/app/data/models/user.dart'; -import 'package:dungeon_paper/app/data/services/user_service.dart'; +import 'package:dungeon_paper/app/data/services/user_provider.dart'; import 'package:dungeon_paper/app/modules/LibraryList/views/filters/spell_filters.dart'; +import 'package:dungeon_paper/core/global_keys.dart'; import 'package:dungeon_paper/core/utils/icon_utils.dart'; import 'package:dungeon_paper/core/utils/uuid.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import '../../../core/dw_icons.dart'; import 'meta.dart'; -class Spell extends dw.Spell with WithIcon implements WithMeta { +class Spell extends dw.Spell + with WithIcon, UserProviderMixin + implements WithMeta { Spell({ required Meta super.meta, required super.key, @@ -74,18 +75,21 @@ class Spell extends dw.Spell with WithIcon implements WithMeta { factory Spell.fromJson(Map json) => Spell.fromDwSpell(dw.Spell.fromJson(json), prepared: json['prepared']); - factory Spell.empty() => Spell( - meta: Meta.empty(createdBy: user.username), - classKeys: [], - description: '', - dice: [], - explanation: '', - level: '', - key: uuid(), - name: '', - tags: [], - prepared: false, - ); + factory Spell.empty() { + final user = UserProvider.of(appGlobalKey.currentContext!).current; + return Spell( + meta: Meta.empty(createdBy: user.username), + classKeys: [], + description: '', + dice: [], + explanation: '', + level: '', + key: uuid(), + name: '', + tags: [], + prepared: false, + ); + } @override Map toJson() => { @@ -111,8 +115,6 @@ class Spell extends dw.Spell with WithIcon implements WithMeta { return a.name.toLowerCase().compareTo(b.name.toLowerCase()); }; - static User get user => Get.find().current; - @override String get displayName => name; diff --git a/lib/app/data/models/user.dart b/lib/app/data/models/user.dart index f3649e11..b75d859a 100644 --- a/lib/app/data/models/user.dart +++ b/lib/app/data/models/user.dart @@ -43,7 +43,7 @@ class User { String toRawJson() => json.encode(toJson()); String? get documentPath => isLoggedIn ? 'Data/$email' : null; - String? get fileStoragePath => isLoggedIn ? documentPath! + '/Uploads' : null; + String? get fileStoragePath => isLoggedIn ? '${documentPath!}/Uploads' : null; factory User.fromJson(Map json) => User( username: json['username'], diff --git a/lib/app/data/models/user_settings.dart b/lib/app/data/models/user_settings.dart index d48e3117..22e794e5 100644 --- a/lib/app/data/models/user_settings.dart +++ b/lib/app/data/models/user_settings.dart @@ -1,12 +1,13 @@ import 'dart:convert'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; -import 'package:dungeon_paper/app/themes/themes.dart'; +import 'package:dungeon_paper/core/utils/list_utils.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:wakelock/wakelock.dart'; -class UserSettings with CharacterServiceMixin { +import '../../themes/themes.dart'; +import '../services/character_provider.dart'; + +class UserSettings with CharacterProviderMixin { final bool keepScreenAwake; final int defaultLightTheme; final int defaultDarkTheme; @@ -80,7 +81,7 @@ class UserSettings with CharacterServiceMixin { Wakelock.toggle(enable: keepScreenAwake); if (maybeChar != null) { - charService.switchToCharacterTheme(char); + charProvider.switchToCharacterTheme(char); } else {} } } diff --git a/lib/app/data/services/auth_service.dart b/lib/app/data/services/auth_provider.dart similarity index 74% rename from lib/app/data/services/auth_service.dart rename to lib/app/data/services/auth_provider.dart index 7c60fdcf..8c152370 100644 --- a/lib/app/data/services/auth_service.dart +++ b/lib/app/data/services/auth_provider.dart @@ -1,25 +1,36 @@ import 'dart:async'; -import 'package:dungeon_paper/app/data/services/loading_service.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; -import 'package:dungeon_paper/app/data/services/user_service.dart'; +import 'package:dungeon_paper/app/data/services/loading_provider.dart'; +import 'package:dungeon_paper/app/data/services/repository_provider.dart'; +import 'package:dungeon_paper/core/global_keys.dart'; import 'package:dungeon_paper/core/platform_helper.dart'; import 'package:firebase_auth/firebase_auth.dart'; -import 'package:flutter/foundation.dart'; -import 'package:get/get.dart'; +import 'package:flutter/widgets.dart'; import 'package:google_sign_in/google_sign_in.dart'; +import 'package:provider/provider.dart'; import 'package:sign_in_with_apple/sign_in_with_apple.dart'; import '../../model_utils/user_utils.dart'; +import 'user_provider.dart'; -class AuthService extends GetxService - with UserServiceMixin, LoadingServiceMixin, RepositoryServiceMixin { +class AuthProvider extends ChangeNotifier + with UserProviderMixin, RepositoryProviderMixin { + static AuthProvider of(BuildContext context, {bool listen = false}) => + Provider.of(context, listen: listen); + static Widget consumer( + Widget Function(BuildContext, AuthProvider, Widget?) builder) => + Consumer(builder: builder); StreamSubscription? _sub; FirebaseAuth get auth => FirebaseAuth.instance; final gSignIn = GoogleSignIn.standard(); - final _fbUser = Rx(null); - User? get fbUser => _fbUser.value; + User? _fbUser; + User? get fbUser => _fbUser; + + AuthProvider() { + debugPrint('[AUTH PROVIDER] init'); + _registerAuthListener(); + } Future loginWithPassword({ required String email, @@ -111,9 +122,10 @@ class AuthService extends GetxService return credential; } - Future logout() async { + Future logout(BuildContext context) async { _clearAuthListener(); // await StorageHandler.instance.local.clear(); + await repo.my.dispose(); await auth.signOut(); try { @@ -127,12 +139,7 @@ class AuthService extends GetxService debugPrint('Error while logging out: $e'); } user.applyDefaultTheme(); - _registerAuthListener(); - } - - @override - void onInit() { - super.onInit(); + notifyListeners(); _registerAuthListener(); } @@ -146,16 +153,25 @@ class AuthService extends GetxService return; } debugPrint('fb user changed: $user'); - _fbUser.value = user; + _fbUser = user; + final context = appGlobalKey.currentContext!; + final userProvider = Provider.of(context, listen: false); if (user != null) { - loadingService.loadingCharacters = !loadingService.afterFirstLoad; - loadingService.afterFirstLoad = false; - userService.loadUserData(user); + final loadingProvider = Provider.of( + context, + listen: false, + ); + loadingProvider.loadingCharacters = !loadingProvider.afterFirstLoad; + loadingProvider.afterFirstLoad = false; + + userProvider.loadUserData(user); + notifyListeners(); return; } - userService.loadGuestData(); + userProvider.loadGuestData(); + notifyListeners(); } Future signUp( @@ -169,6 +185,8 @@ class AuthService extends GetxService } } -mixin AuthServiceMixin { - AuthService get authService => Get.find(); +mixin AuthProviderMixin { + AuthProvider get authProvider => + AuthProvider.of(appGlobalKey.currentContext!); } + diff --git a/lib/app/data/services/character_service.dart b/lib/app/data/services/character_provider.dart similarity index 59% rename from lib/app/data/services/character_service.dart rename to lib/app/data/services/character_provider.dart index 803976c2..72a0d4f0 100644 --- a/lib/app/data/services/character_service.dart +++ b/lib/app/data/services/character_provider.dart @@ -1,35 +1,41 @@ import 'dart:async'; -import 'package:dungeon_paper/app/data/services/user_service.dart'; +import 'package:dungeon_paper/app/data/services/user_provider.dart'; import 'package:dungeon_paper/app/themes/themes.dart'; +import 'package:dungeon_paper/core/global_keys.dart'; import 'package:dungeon_paper/core/storage_handler/storage_handler.dart'; import 'package:dungeon_paper/core/utils/date_utils.dart'; import 'package:dungeon_paper/core/utils/enums.dart'; import 'package:dynamic_themes/dynamic_themes.dart'; import 'package:flutter/widgets.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; import '../models/character.dart'; -import 'loading_service.dart'; +import 'loading_provider.dart'; -class CharacterService extends GetxService - with LoadingServiceMixin, UserServiceMixin { - static CharacterService find() => Get.find(); +class CharacterProvider extends ChangeNotifier + with LoadingProviderMixin, UserProviderMixin { + static CharacterProvider of(BuildContext context, {bool listen = false}) => + Provider.of(context, listen: listen); - final all = {}.obs; - final _currentKey = Rx(null); + static Widget consumer( + Widget Function(BuildContext, CharacterProvider, Widget?) builder, + ) => + Consumer(builder: builder); + + final all = {}; + String? _currentKey; final _pageController = PageController(initialPage: 1, viewportFraction: 1.1); StreamSubscription? _sub; - @override - void onInit() async { - super.onInit(); - // pageController.addListener(refreshPage); - await registerCharacterListener(); + CharacterProvider() { + debugPrint('[PROVIDER] initializing character provider'); + registerCharacterListener(); } @override - void onClose() { + void dispose() { + super.dispose(); // pageController.removeListener(refreshPage); _sub?.cancel(); } @@ -40,8 +46,7 @@ class CharacterService extends GetxService ? pageController.page ?? 0 : 0; - Character? get maybeCurrent => - _currentKey.value != null ? all[_currentKey.value] : null; + Character? get maybeCurrent => _currentKey != null ? all[_currentKey] : null; Character get current => maybeCurrent!; List get allAsList => all.values.toList(); @@ -68,19 +73,21 @@ class CharacterService extends GetxService Future registerCharacterListener() async { _clearCharListener(); - debugPrint('registering character listener'); + debugPrint('[PROVIDER] registering character listener'); _sub = StorageHandler.instance.collectionListener('Characters', charsListener); } void clear() { all.clear(); - _currentKey.value = null; + _currentKey = null; + notifyListeners(); } void setCurrent(String key) { if (all.containsKey(key)) { - _currentKey.value = key; + _currentKey = key; + notifyListeners(); switchToCharacterTheme(current); updateCharacter( current.copyWith( @@ -96,13 +103,17 @@ class CharacterService extends GetxService switchToTheme(character.getCurrentTheme(user)); void switchToTheme(int themeId) { - final dynamicTheme = DynamicTheme.of(Get.context!)!; + if (appGlobalKey.currentContext == null) { + debugPrint('[PROVIDER] no context, cannot switch theme'); + return; + } + final dynamicTheme = DynamicTheme.of(appGlobalKey.currentContext!)!; final currentTheme = dynamicTheme.themeId; if (currentTheme == themeId) { return; } - debugPrint('switching to theme $themeId'); + debugPrint('[PROVIDER] switching to theme $themeId'); AppThemes.setTheme(themeId); } @@ -111,7 +122,7 @@ class CharacterService extends GetxService all.addAll(Map.fromIterable(list, key: (c) => c.key)); - if (all.isNotEmpty && _currentKey.value == null) { + if (all.isNotEmpty && _currentKey == null) { switchToLastUsedChar(); } @@ -119,33 +130,38 @@ class CharacterService extends GetxService switchToCharacterTheme(current); } - loadingService.loadingCharacters = false; - loadingService.afterFirstLoad = !loadingService.loadingUser; + loadingProvider.loadingCharacters = false; + loadingProvider.afterFirstLoad = !loadingProvider.loadingUser; + notifyListeners(); } void switchToLastUsedChar() { final hasLastChar = all.values.any((c) => c.meta.data?.lastUsed != null); if (hasLastChar) { final lastChar = charsByLastUsed.first; - _currentKey.value = lastChar.key; + _currentKey = lastChar.key; } else if (all.isNotEmpty) { - _currentKey.value = all.keys.first; + _currentKey = all.keys.first; } else { - _currentKey.value = null; + _currentKey = null; } + notifyListeners(); } - Future updateCharacter(Character character, - {bool switchToCharacter = false}) { - // (StorageHandler.instance.delegate as LocalStorageDelegate).storage.collection('Characters'); + Future updateCharacter( + Character character, { + bool switchToCharacter = false, + }) { character = character.copyWithInherited(meta: character.meta.stampUpdate()); all[character.key] = character; + notifyListeners(); if (switchToCharacter || - _currentKey.value == null || - !all.containsKey(_currentKey.value)) { + _currentKey == null || + !all.containsKey(_currentKey)) { setCurrent(character.key); } - debugPrint('Updated char: ${character.key} (${character.displayName})'); + debugPrint( + '[PROVIDER] Updated char: ${character.key} (${character.displayName})'); debugPrint(character.toRawJson()); return StorageHandler.instance .update('Characters', character.key, character.toJson()); @@ -155,11 +171,13 @@ class CharacterService extends GetxService all[character.key] = character; StorageHandler.instance .create('Characters', character.key, character.toJson()); - if (switchToCharacter || _currentKey.value == null) { - _currentKey.value = character.key; + if (switchToCharacter || _currentKey == null) { + _currentKey = character.key; } - debugPrint('Created char: ${character.key} (${character.displayName})'); + debugPrint( + '[PROVIDER] Created char: ${character.key} (${character.displayName})'); debugPrint(character.toRawJson()); + notifyListeners(); } void deleteCharacter(Character character) { @@ -167,12 +185,14 @@ class CharacterService extends GetxService try { StorageHandler.instance.delete('Characters', character.key); } catch (e) { - debugPrint('Error deleting character: $e'); + debugPrint('[PROVIDER] Error deleting character: $e'); } - if (character.key == _currentKey.value) { - _currentKey.value = all.keys.first; + if (character.key == _currentKey) { + _currentKey = all.keys.first; } - debugPrint('Deleted char: ${character.key} (${character.displayName})'); + debugPrint( + '[PROVIDER] Deleted char: ${character.key} (${character.displayName})'); + notifyListeners(); } void updateAll(Iterable chars) { @@ -182,18 +202,19 @@ class CharacterService extends GetxService } void _clearCharListener() { - debugPrint('clearing char listener'); + debugPrint('[PROVIDER] clearing char listener'); _sub?.cancel(); _sub = null; } } -mixin CharacterServiceMixin { - CharacterService get characterService => Get.find(); - CharacterService get charService => characterService; +mixin CharacterProviderMixin { + CharacterProvider get characterProvider => + CharacterProvider.of(appGlobalKey.currentContext!); + CharacterProvider get charProvider => characterProvider; - Character get character => characterService.current; - Character? get maybeCharacter => characterService.maybeCurrent; + Character get character => characterProvider.current; + Character? get maybeCharacter => characterProvider.maybeCurrent; Character get char => character; Character? get maybeChar => maybeCharacter; } diff --git a/lib/app/data/services/intl_service.dart b/lib/app/data/services/intl_service.dart index d332f4e5..b94d3129 100644 --- a/lib/app/data/services/intl_service.dart +++ b/lib/app/data/services/intl_service.dart @@ -1,19 +1,17 @@ import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import '../../../i18n/messages.i18n.dart'; -class IntlService extends GetxService { +class IntlService extends ChangeNotifier { static final Map _m = {}; - static late Locale _locale; + static Locale _locale = const Locale('en'); + static Locale get locale => _locale; - static Messages get m => _m[Get.locale] ?? _loadMessages(_locale); + static Messages get m => _m[locale] ?? _loadMessages(_locale); List get supportedLocales => _m.keys.toList(); - @override - void onInit() { - super.onInit(); - _loadMessages(Get.deviceLocale ?? const Locale('en')); + IntlService() { + _loadMessages(locale); } static void changeLocale(Locale locale) { diff --git a/lib/app/data/services/library_service.dart b/lib/app/data/services/library_provider.dart similarity index 65% rename from lib/app/data/services/library_service.dart rename to lib/app/data/services/library_provider.dart index ceb9a56c..25c60f24 100644 --- a/lib/app/data/services/library_service.dart +++ b/lib/app/data/services/library_provider.dart @@ -1,18 +1,27 @@ import 'package:dungeon_paper/app/data/models/character.dart'; import 'package:dungeon_paper/app/data/models/meta.dart'; -import 'package:dungeon_paper/app/data/models/user.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; -import 'package:dungeon_paper/app/data/services/user_service.dart'; import 'package:dungeon_paper/app/model_utils/character_utils.dart'; +import 'package:dungeon_paper/core/global_keys.dart'; import 'package:dungeon_paper/core/storage_handler/storage_handler.dart'; import 'package:dungeon_paper/core/utils/uuid.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; -class LibraryService extends GetxService { +import 'character_provider.dart'; +import 'user_provider.dart'; + +class LibraryProvider extends ChangeNotifier + with CharacterProviderMixin, UserProviderMixin { StorageHandler get storage => StorageHandler.instance; - CharacterService get chars => Get.find(); - User get user => Get.find().current; + + static LibraryProvider of(BuildContext context, {bool listen = false}) => + Provider.of(context, listen: listen); + + static Widget consumer( + Widget Function( + BuildContext context, LibraryProvider library, Widget? child) + builder) => + Consumer(builder: builder); Future existsInLibrary(T item) async { final res = await storage.getDocument(item.storageKey, item.key); @@ -46,6 +55,7 @@ class LibraryService extends GetxService { // items = items.map((e) => (e.meta.createdBy == user.username) // ? increaseMetaVersion(e) // : forkMeta(e, user)); + items = items.map( (e) { debugPrint('forking $e: $forkBehavior'); @@ -59,17 +69,21 @@ class LibraryService extends GetxService { }, ); } + var m = items.elementAt(0).meta; debugPrint('upserting meta ${m.toJson()}'); - chars.updateCharacter( - CharacterUtils.upsertByType(char ?? chars.current, items), + charProvider.updateCharacter( + CharacterUtils.upsertByType(char ?? charProvider.current, items), ); } - void removeFromCharacter(Iterable items, - [Character? char]) async { - chars.updateCharacter( - CharacterUtils.removeByType(char ?? chars.current, items), + void removeFromCharacter( + BuildContext context, + Iterable items, [ + Character? char, + ]) async { + charProvider.updateCharacter( + CharacterUtils.removeByType(char ?? charProvider.current, items), ); } } @@ -81,6 +95,7 @@ enum ForkBehavior { both, } -mixin LibraryServiceMixin { - LibraryService get library => Get.find(); +mixin LibraryProviderMixin { + LibraryProvider get libraryProvider => + LibraryProvider.of(appGlobalKey.currentContext!); } diff --git a/lib/app/data/services/loading_provider.dart b/lib/app/data/services/loading_provider.dart new file mode 100644 index 00000000..b7618cc2 --- /dev/null +++ b/lib/app/data/services/loading_provider.dart @@ -0,0 +1,62 @@ +import 'package:dungeon_paper/core/global_keys.dart'; +import 'package:flutter/widgets.dart'; +import 'package:provider/provider.dart'; + +enum LoadKey { + user, + characters, + repo, + library, + afterFirstLoad, +} + +class LoadingProvider extends ChangeNotifier { + static LoadingProvider of(BuildContext context, {bool listen = false}) => + Provider.of(context, listen: listen); + + static Widget consumer( + Widget Function(BuildContext, LoadingProvider, Widget?) builder) => + Consumer(builder: builder); + final _map = { + LoadKey.user: true, + LoadKey.characters: false, + LoadKey.repo: true, + LoadKey.library: true, + LoadKey.afterFirstLoad: false, + }; + + bool get loadingUser => _map[LoadKey.user] == true; + set loadingUser(bool value) { + _map[LoadKey.user] = value; + notifyListeners(); + } + + bool get loadingCharacters => _map[LoadKey.characters] == true; + set loadingCharacters(bool value) { + _map[LoadKey.characters] = value; + notifyListeners(); + } + + bool get loadingRepo => _map[LoadKey.repo] == true; + set loadingRepo(bool value) { + _map[LoadKey.repo] = value; + notifyListeners(); + } + + bool get loadingLibrary => _map[LoadKey.library] == true; + set loadingLibrary(bool value) { + _map[LoadKey.library] = value; + notifyListeners(); + } + + bool get afterFirstLoad => _map[LoadKey.afterFirstLoad] == true; + set afterFirstLoad(bool value) { + _map[LoadKey.afterFirstLoad] = value; + notifyListeners(); + } +} + +mixin LoadingProviderMixin { + LoadingProvider get loadingProvider => + LoadingProvider.of(appGlobalKey.currentContext!); +} diff --git a/lib/app/data/services/loading_service.dart b/lib/app/data/services/loading_service.dart deleted file mode 100644 index f9975659..00000000 --- a/lib/app/data/services/loading_service.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:get/get.dart'; - -enum LoadKey { - user, - characters, - repo, - library, - afterFirstLoad, -} - -class LoadingService extends GetxService { - final _map = { - LoadKey.user: true, - LoadKey.characters: false, - LoadKey.repo: true, - LoadKey.library: true, - LoadKey.afterFirstLoad: false, - }.obs; - - bool get loadingUser => _map[LoadKey.user] == true; - set loadingUser(bool value) => _map[LoadKey.user] = value; - - bool get loadingCharacters => _map[LoadKey.characters] == true; - set loadingCharacters(bool value) => _map[LoadKey.characters] = value; - - bool get loadingRepo => _map[LoadKey.repo] == true; - set loadingRepo(bool value) => _map[LoadKey.repo] = value; - - bool get loadingLibrary => _map[LoadKey.library] == true; - set loadingLibrary(bool value) => _map[LoadKey.library] = value; - - bool get afterFirstLoad => _map[LoadKey.afterFirstLoad] == true; - set afterFirstLoad(bool value) => _map[LoadKey.afterFirstLoad] = value; -} - -mixin LoadingServiceMixin { - LoadingService get loadingService => Get.find(); -} diff --git a/lib/app/data/services/repository_service.dart b/lib/app/data/services/repository_provider.dart similarity index 84% rename from lib/app/data/services/repository_service.dart rename to lib/app/data/services/repository_provider.dart index 1282078d..106cb7c6 100644 --- a/lib/app/data/services/repository_service.dart +++ b/lib/app/data/services/repository_provider.dart @@ -1,5 +1,3 @@ -// ignore_for_file: curly_braces_in_flow_control_structures - import 'dart:async'; import 'package:dungeon_paper/app/data/models/character_class.dart'; @@ -10,6 +8,7 @@ import 'package:dungeon_paper/app/data/models/move.dart'; import 'package:dungeon_paper/app/data/models/note.dart'; import 'package:dungeon_paper/app/data/models/race.dart'; import 'package:dungeon_paper/app/data/models/spell.dart'; +import 'package:dungeon_paper/core/global_keys.dart'; import 'package:dungeon_paper/core/http/api.dart'; import 'package:dungeon_paper/core/http/api_requests/search.dart'; import 'package:dungeon_paper/core/storage_handler/storage_handler.dart'; @@ -17,22 +16,41 @@ import 'package:dungeon_paper/core/utils/list_utils.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; -class RepositoryService extends GetxService { +class RepositoryProvider extends ChangeNotifier { final builtIn = BuiltInRepository(id: 'playbook'); final my = PersonalRepository(id: 'personal'); + static RepositoryProvider of(BuildContext context) => + Provider.of(context, listen: false); + + static Widget consumer( + Widget Function( + BuildContext context, + RepositoryProvider repo, + Widget? child, + ) builder, + ) => + Consumer(builder: builder); StorageDelegate get storage => StorageHandler.instance; + RepositoryProvider() { + // loadAllData(); + builtIn.addListener(notifyListeners); + my.addListener(notifyListeners); + } + void clear() { builtIn._clearValues(); my._clearValues(); } @override - void onClose() async { - super.onClose(); + void dispose() async { + super.dispose(); + builtIn.removeListener(notifyListeners); + my.removeListener(notifyListeners); await Future.wait([builtIn.dispose(), my.dispose()]); } @@ -63,21 +81,21 @@ enum RepositoryStatus { error, } -abstract class RepositoryCache { +abstract class RepositoryCache extends ChangeNotifier { RepositoryCache({required this.id}); String? get cachePrefix; final String id; abstract final RemoteBehavior loadRemote; - final classes = {}.obs; - final items = {}.obs; - final monsters = {}.obs; - final moves = {}.obs; - final races = {}.obs; - final spells = {}.obs; - final tags = {}.obs; - final notes = {}.obs; + var classes = {}; + var items = {}; + var monsters = {}; + var moves = {}; + var races = {}; + var spells = {}; + var tags = {}; + var notes = {}; final subs = []; @@ -115,8 +133,8 @@ abstract class RepositoryCache { try { resp = await getFromRemote; await setAllFrom(resp, saveIntoCache: true); - } catch (e) { - debugPrint('[$id] Error loading from remote: $e'); + } catch (e, stack) { + debugPrint('[$id] Error loading from remote: $e\n$stack'); resp = SearchResponse.empty(); } } else { @@ -183,7 +201,7 @@ abstract class RepositoryCache { _parseMap( d, key: (x) => x.key, - save: (x) => classes.value = x, + save: (x) => classes = x, parse: (x) => CharacterClass.fromJson(x), ); }, @@ -194,7 +212,7 @@ abstract class RepositoryCache { _parseMap( d, key: (x) => x.key, - save: (x) => items.value = x, + save: (x) => items = x, parse: (x) => Item.fromJson(x), ); }, @@ -205,7 +223,7 @@ abstract class RepositoryCache { _parseMap( d, key: (x) => x.key, - save: (x) => monsters.value = x, + save: (x) => monsters = x, parse: (x) => Monster.fromJson(x), ); }, @@ -216,7 +234,7 @@ abstract class RepositoryCache { _parseMap( d, key: (x) => x.key, - save: (x) => moves.value = x, + save: (x) => moves = x, parse: (x) => Move.fromJson(x), ); }, @@ -227,7 +245,7 @@ abstract class RepositoryCache { _parseMap( d, key: (x) => x.key, - save: (x) => races.value = x, + save: (x) => races = x, parse: (x) => Race.fromJson(x), ); }, @@ -238,7 +256,7 @@ abstract class RepositoryCache { _parseMap( d, key: (x) => x.key, - save: (x) => spells.value = x, + save: (x) => spells = x, parse: (x) => Spell.fromJson(x), ); }, @@ -249,7 +267,7 @@ abstract class RepositoryCache { _parseMap( d, key: (x) => x.name, - save: (x) => tags.value = x, + save: (x) => tags = x, parse: (x) => dw.Tag.fromJson(x), ); }, @@ -260,7 +278,7 @@ abstract class RepositoryCache { _parseMap( d, key: (x) => x.key, - save: (x) => notes.value = x, + save: (x) => notes = x, parse: (x) => Note.fromJson(x), ); }, @@ -300,7 +318,9 @@ abstract class RepositoryCache { subs.clear(); } + @override Future dispose() async { + super.dispose(); clearListeners(); _clearValues(); await cache.clear(); @@ -345,34 +365,34 @@ abstract class RepositoryCache { tags.clear(); } - RxMap listByType([Type? type]) { + Map listByType([Type? type]) { assert(T != dynamic || type != null); final t = T != dynamic ? T : type; switch (t) { case == CharacterClass: - return classes as RxMap; + return classes as Map; case == Item: - return items as RxMap; + return items as Map; case == Monster: - return monsters as RxMap; + return monsters as Map; case == Move: - return moves as RxMap; + return moves as Map; case == Race: - return races as RxMap; + return races as Map; case == Spell: - return spells as RxMap; + return spells as Map; case == Note: - return notes as RxMap; + return notes as Map; case == dw.Tag: - return tags as RxMap; + return tags as Map; } throw TypeError(); } Future updateList( String collectionName, - RxMap list, + Map list, Iterable? resp, { required bool saveIntoCache, }) async { @@ -383,8 +403,10 @@ abstract class RepositoryCache { list.addAll(Map.fromIterable(resp, key: (x) => x.key)); if (saveIntoCache && list.isNotEmpty) { - for (final x in list.values) + for (final x in list.values) { await cache.create(collectionName, Meta.keyFor(x), Meta.toJsonFor(x)); + notifyListeners(); + } } } } @@ -451,7 +473,9 @@ class PersonalRepository extends RepositoryCache { RemoteBehavior get loadRemote => RemoteBehavior.always; } -mixin RepositoryServiceMixin { - RepositoryService get repository => Get.find(); - RepositoryService get repo => repository; +mixin RepositoryProviderMixin { + RepositoryProvider get repo => + RepositoryProvider.of(appGlobalKey.currentContext!); + RepositoryProvider get repository => repo; } + diff --git a/lib/app/data/services/services.dart b/lib/app/data/services/services.dart deleted file mode 100644 index c4563072..00000000 --- a/lib/app/data/services/services.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:get/get.dart'; - -import 'character_service.dart'; -import 'intl_service.dart'; -import 'library_service.dart'; -import 'loading_service.dart'; -import 'repository_service.dart'; -import 'user_service.dart'; -import 'auth_service.dart'; - -Future initServices() async { - debugPrint('Starting services...'); - - /// Here is where you put get_storage, hive, shared_pref initialization. - /// or moor connection, or whatever that's async. - await Get.putAsync(() => Future.value(IntlService())); - await Get.putAsync(() => Future.value(LoadingService())); - await Get.putAsync(() => Future.value(RepositoryService())); - await Get.putAsync(() => Future.value(LibraryService())); - await Get.putAsync(() => Future.value(UserService())); - await Get.putAsync(() => Future.value(AuthService())); - await Get.putAsync(() => Future.value(CharacterService())); - debugPrint('All services started'); -} diff --git a/lib/app/data/services/user_service.dart b/lib/app/data/services/user_provider.dart similarity index 60% rename from lib/app/data/services/user_service.dart rename to lib/app/data/services/user_provider.dart index 34857ac5..fbb881b7 100644 --- a/lib/app/data/services/user_service.dart +++ b/lib/app/data/services/user_provider.dart @@ -2,12 +2,13 @@ import 'dart:async'; import 'package:dungeon_paper/app/data/models/user.dart'; import 'package:dungeon_paper/app/data/models/user_settings.dart'; -import 'package:dungeon_paper/app/data/services/auth_service.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; -import 'package:dungeon_paper/app/data/services/loading_service.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; +import 'package:dungeon_paper/app/data/services/auth_provider.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; +import 'package:dungeon_paper/app/data/services/loading_provider.dart'; +import 'package:dungeon_paper/app/data/services/repository_provider.dart'; import 'package:dungeon_paper/app/modules/Migration/controllers/migration_controller.dart'; import 'package:dungeon_paper/app/routes/app_pages.dart'; +import 'package:dungeon_paper/core/global_keys.dart'; import 'package:dungeon_paper/core/http/api.dart'; import 'package:dungeon_paper/core/http/api_requests/migration.dart'; import 'package:dungeon_paper/core/storage_handler/storage_handler.dart'; @@ -15,30 +16,37 @@ import 'package:dungeon_paper/i18n.dart'; import 'package:email_validator/email_validator.dart'; import 'package:firebase_auth/firebase_auth.dart' as fba; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:provider/provider.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import '../../../core/utils/secrets_base.dart'; -class UserService extends GetxService - with - RepositoryServiceMixin, - AuthServiceMixin, - CharacterServiceMixin, - LoadingServiceMixin { - final _current = User.guest().obs; +class UserProvider extends ChangeNotifier with RepositoryProviderMixin { + var _current = User.guest(); - User get current => _current.value; + User get current => _current; StreamSubscription? _userDataSub; + UserProvider() { + // loadBuiltInRepo(); + } + + static UserProvider of(BuildContext context) => + Provider.of(context, listen: false); + + static consumer(BuildContext context, Widget Function(User) builder) => + Consumer( + builder: (context, provider, _) => builder(provider.current), + ); + Future loadBuiltInRepo({bool ignoreCache = false}) async { - await repo.builtIn.dispose(); + // await repo.builtIn.dispose(); return repo.builtIn.init(ignoreCache: ignoreCache); } Future loadMyRepo({bool ignoreCache = false}) async { - await repo.my.dispose(); + // await repo.my.dispose(); return repo.my.init(ignoreCache: ignoreCache); } @@ -54,14 +62,20 @@ class UserService extends GetxService .getDocument('Data', email!); await _setUserAfterMigration(user, dbUser); _registerUserListener(); - charService.registerCharacterListener(); + final charProvider = Provider.of( + appGlobalKey.currentContext!, + listen: false); + charProvider.registerCharacterListener(); if (shouldLoadRepo) { loadMyRepo(); } - loadingService.loadingUser = false; - loadingService.afterFirstLoad = !loadingService.loadingCharacters; + final loadingProvider = Provider.of( + appGlobalKey.currentContext!, + listen: false); + loadingProvider.loadingUser = false; + loadingProvider.afterFirstLoad = !loadingProvider.loadingCharacters; } bool get isGuest => current.isGuest; @@ -71,28 +85,39 @@ class UserService extends GetxService _clearUserListener(); StorageHandler.instance.currentDelegate = 'local'; StorageHandler.instance.setCollectionPrefix(null); + notifyListeners(); await loadMyRepo(ignoreCache: true); - charService.registerCharacterListener(); - loadingService.loadingUser = false; - loadingService.afterFirstLoad = !loadingService.loadingCharacters; + final charProvider = Provider.of( + appGlobalKey.currentContext!, + listen: false); + final loadingProvider = Provider.of( + appGlobalKey.currentContext!, + listen: false); + charProvider.registerCharacterListener(); + loadingProvider.loadingUser = false; + loadingProvider.afterFirstLoad = !loadingProvider.loadingCharacters; + notifyListeners(); } void logout() async { _clearUserListener(); - charService.clear(); - _current.value = User.guest(); - await authService.logout(); + final charProvider = Provider.of( + appGlobalKey.currentContext!, + listen: false); + charProvider.clear(); + _current = User.guest(); + final context = appGlobalKey.currentContext!; + final authService = Provider.of( + context, + listen: false, + ); + await authService.logout(context); + notifyListeners(); } @override - void onInit() { - super.onInit(); - loadBuiltInRepo(); - } - - @override - void onClose() { - super.onClose(); + void dispose() { + super.dispose(); _userDataSub?.cancel(); } @@ -106,7 +131,8 @@ class UserService extends GetxService } Future _migrateUser(fba.User user) async { - final migrationDetails = await Get.toNamed( + final context = appGlobalKey.currentContext!; + final migrationDetails = await Navigator.of(context).pushNamed( Routes.migration, arguments: MigrationArguments(email: user.email ?? ''), ) as MigrationDetails?; @@ -142,7 +168,8 @@ class UserService extends GetxService return; } final user = User.fromJson(data); - _current.value = user; + _current = user; + notifyListeners(); user.applySettings(); _initUserExternal(user); } @@ -150,6 +177,10 @@ class UserService extends GetxService void _initUserExternal(User user) async { final pkg = await PackageInfo.fromPlatform(); if (secrets.sentryDsn.isNotEmpty) { + final authService = Provider.of( + appGlobalKey.currentContext!, + listen: false, + ); Sentry.configureScope( (scope) => scope.setUser( SentryUser( @@ -170,17 +201,24 @@ class UserService extends GetxService final needsMigration = dbUser == null; if (needsMigration) { + final context = appGlobalKey.currentContext!; + final messenger = ScaffoldMessenger.of(context); + final loadingProvider = + Provider.of(context, listen: false); final resp = await _migrateUser(user); if (resp == null) { - Get.rawSnackbar(title: tr.errors.userOperationCanceled); - loadingService.loadingUser = false; - loadingService.afterFirstLoad = !loadingService.loadingCharacters; + messenger.showSnackBar( + SnackBar(content: Text(tr.errors.userOperationCanceled))); + + loadingProvider.loadingUser = false; + loadingProvider.afterFirstLoad = !loadingProvider.loadingCharacters; return; } - _current.value = resp; + _current = resp; } else { - _current.value = User.fromJson(dbUser); + _current = User.fromJson(dbUser); } + notifyListeners(); } Future updateEmail(String email) async { @@ -189,6 +227,10 @@ class UserService extends GetxService } assert(EmailValidator.validate(email)); final updatedUser = current.copyWith(email: email); + final authService = Provider.of( + appGlobalKey.currentContext!, + listen: false, + ); await authService.fbUser!.updateEmail(email); await updateUser(updatedUser); loadUserData(fba.FirebaseAuth.instance.currentUser!); @@ -201,7 +243,8 @@ class UserService extends GetxService } } -mixin UserServiceMixin { - UserService get userService => Get.find(); - User get user => userService.current; +mixin UserProviderMixin { + UserProvider get userProvider => + UserProvider.of(appGlobalKey.currentContext!); + User get user => userProvider.current; } diff --git a/lib/app/model_utils/dice_utils.dart b/lib/app/model_utils/dice_utils.dart index 26ba8f43..90c8dffe 100644 --- a/lib/app/model_utils/dice_utils.dart +++ b/lib/app/model_utils/dice_utils.dart @@ -2,13 +2,12 @@ import 'package:dungeon_paper/app/routes/app_pages.dart'; import 'package:dungeon_paper/core/dw_icons.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; class DiceUtils { static Widget iconOf(dw.Dice? tag) => const Icon(DwIcons.dice_d6); - static void openRollDialog(List dice) { - Get.toNamed(Routes.rollDice, arguments: dice); + static void openRollDialog(BuildContext context, List dice) { + Navigator.of(context).pushNamed(Routes.rollDice, arguments: dice); } static Offset iconCenterOffset(dw.Dice dice) => diff --git a/lib/app/model_utils/model_pages.dart b/lib/app/model_utils/model_pages.dart index cb83075f..8fa9f50a 100644 --- a/lib/app/model_utils/model_pages.dart +++ b/lib/app/model_utils/model_pages.dart @@ -7,8 +7,8 @@ import 'package:dungeon_paper/app/data/models/character.dart'; import 'package:dungeon_paper/app/data/models/ability_scores.dart'; import 'package:dungeon_paper/app/data/models/race.dart'; import 'package:dungeon_paper/app/data/models/spell.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; -import 'package:dungeon_paper/app/data/services/library_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; +import 'package:dungeon_paper/app/data/services/library_provider.dart'; import 'package:dungeon_paper/app/modules/LibraryList/controllers/library_list_controller.dart'; import 'package:dungeon_paper/app/modules/LibraryList/views/character_classes_library_list_view.dart'; import 'package:dungeon_paper/app/modules/LibraryList/views/items_library_list_view.dart'; @@ -23,16 +23,15 @@ import 'package:dungeon_paper/app/widgets/forms/move_form.dart'; import 'package:dungeon_paper/app/widgets/forms/note_form.dart'; import 'package:dungeon_paper/app/widgets/forms/race_form.dart'; import 'package:dungeon_paper/app/widgets/forms/spell_form.dart'; +import 'package:dungeon_paper/core/global_keys.dart'; import 'package:dungeon_paper/core/utils/enums.dart'; import 'package:dungeon_paper/core/utils/list_utils.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; -import 'package:get/get.dart'; +import 'package:flutter/material.dart'; -class ModelPages { - static CharacterService get controller => Get.find(); - static LibraryService get library => Get.find(); - - static void openLibraryList({ +class ModelPages with LibraryProviderMixin, CharacterProviderMixin { + static void openLibraryList( + BuildContext context, { Character? character, void Function(Iterable list)? onSelected, Iterable? preSelections, @@ -44,6 +43,7 @@ class ModelPages { }) { final map = { Move: () => openMovesList( + context, character: character, onSelected: onSelected as void Function(Iterable)?, preSelections: preSelections as Iterable?, @@ -53,6 +53,7 @@ class ModelPages { initialTab: initialTab, ), Spell: () => openSpellsList( + context, character: character, onSelected: onSelected as void Function(Iterable)?, preSelections: preSelections as Iterable?, @@ -61,12 +62,14 @@ class ModelPages { initialTab: initialTab, ), Item: () => openItemsList( + context, character: character, onSelected: onSelected as void Function(Iterable)?, preSelections: preSelections as Iterable?, initialTab: initialTab, ), CharacterClass: () => openCharacterClassesList( + context, character: character, onSelected: onSelected != null ? (x) => onSelected.call(asList(x)) @@ -75,6 +78,7 @@ class ModelPages { initialTab: initialTab, ), Race: () => openRacesList( + context, character: character, onSelected: onSelected != null ? (x) => onSelected.call(asList(x)) @@ -93,7 +97,8 @@ class ModelPages { map[t]!.call(); } - static void openMovesList({ + static void openMovesList( + BuildContext context, { Character? character, Iterable? preSelections, MoveCategory? category, @@ -103,7 +108,7 @@ class ModelPages { List? classKeys, }) { final char = character; - Get.toNamed( + Navigator.of(context).pushNamed( Routes.moves, arguments: MoveLibraryListArguments( initialTab: initialTab, @@ -117,14 +122,15 @@ class ModelPages { ); } - static void openRacesList({ + static void openRacesList( + BuildContext context, { Character? character, Race? preSelection, void Function(Race race)? onSelected, FiltersGroup? initialTab, }) { final char = character; - Get.toNamed( + Navigator.of(context).pushNamed( Routes.races, arguments: RaceLibraryListArguments( initialTab: initialTab, @@ -135,12 +141,13 @@ class ModelPages { ); } - static void openMovePage({ + static void openMovePage( + BuildContext context, { required Move? move, required void Function(Move move) onSave, required AbilityScores abilityScores, }) => - Get.toNamed( + Navigator.of(context).pushNamed( Routes.editMove, arguments: MoveFormArguments( entity: move, @@ -150,12 +157,13 @@ class ModelPages { ), ); - static void openRacePage({ + static void openRacePage( + BuildContext context, { required Race? race, required void Function(Race race) onSave, required AbilityScores abilityScores, }) => - Get.toNamed( + Navigator.of(context).pushNamed( Routes.editRace, arguments: RaceFormArguments( entity: race, @@ -165,7 +173,8 @@ class ModelPages { ), ); - static void openSpellsList({ + static void openSpellsList( + BuildContext context, { Character? character, Iterable? list, void Function(Iterable list)? onSelected, @@ -175,7 +184,7 @@ class ModelPages { List? classKeys, }) { final char = character; - Get.toNamed( + Navigator.of(context).pushNamed( Routes.spells, arguments: SpellLibraryListArguments( initialTab: initialTab, @@ -188,13 +197,14 @@ class ModelPages { ); } - static void openSpellPage({ + static void openSpellPage( + BuildContext context, { required Spell? spell, required void Function(Spell spell) onSave, required AbilityScores abilityScores, required List classKeys, }) => - Get.toNamed( + Navigator.of(context).pushNamed( Routes.editSpell, arguments: SpellFormArguments( entity: spell, @@ -204,7 +214,8 @@ class ModelPages { ), ); - static void openItemsList({ + static void openItemsList( + BuildContext context, { Character? character, Iterable? list, void Function(Iterable list)? onSelected, @@ -212,7 +223,7 @@ class ModelPages { Iterable? preSelections, }) { final char = character; - Get.toNamed( + Navigator.of(context).pushNamed( Routes.items, arguments: ItemLibraryListArguments( initialTab: initialTab, @@ -222,11 +233,12 @@ class ModelPages { ); } - static void openItemPage({ + static void openItemPage( + BuildContext context, { required Item? item, required void Function(Item item) onSave, }) => - Get.toNamed( + Navigator.of(context).pushNamed( Routes.editItem, arguments: ItemFormArgumentsNew( entity: item, @@ -235,7 +247,8 @@ class ModelPages { ), ); - static void openNotesList({ + static void openNotesList( + BuildContext context, { Character? character, Iterable? list, void Function(Iterable list)? onSelected, @@ -243,7 +256,7 @@ class ModelPages { Iterable? preSelections, }) { final char = character; - Get.toNamed( + Navigator.of(context).pushNamed( Routes.notes, arguments: NoteLibraryListArguments( initialTab: initialTab, @@ -253,11 +266,12 @@ class ModelPages { ); } - static void openNotePage({ + static void openNotePage( + BuildContext context, { required Note? note, required void Function(Note note) onSave, }) => - Get.toNamed( + Navigator.of(context).pushNamed( Routes.editNote, arguments: NoteFormArguments( entity: note, @@ -266,20 +280,23 @@ class ModelPages { ), ); - static void openCharacterClassesList({ + static void openCharacterClassesList( + BuildContext context, { Character? character, CharacterClass? preSelection, void Function(CharacterClass cls)? onSelected, FiltersGroup? initialTab, }) { final char = character; - Get.toNamed( + final charProvider = CharacterProvider.of(appGlobalKey.currentContext!); + + Navigator.of(context).pushNamed( Routes.classes, arguments: CharacterClassLibraryListArguments( initialTab: initialTab, onSelected: onSelected ?? (char != null - ? (cls) => controller + ? (cls) => charProvider .updateCharacter(char.copyWith(characterClass: cls)) : null), preSelections: asList(preSelection ?? char?.characterClass), @@ -287,11 +304,12 @@ class ModelPages { ); } - static void openCharacterClassPage({ + static void openCharacterClassPage( + BuildContext context, { required CharacterClass? characterClass, required void Function(CharacterClass item) onSave, }) => - Get.toNamed( + Navigator.of(context).pushNamed( Routes.editClass, arguments: CharacterClassFormArguments( entity: characterClass, diff --git a/lib/app/model_utils/tag_utils.dart b/lib/app/model_utils/tag_utils.dart index 8efae391..65353394 100644 --- a/lib/app/model_utils/tag_utils.dart +++ b/lib/app/model_utils/tag_utils.dart @@ -9,7 +9,7 @@ class TagUtils { .toList(); static Widget iconOf(dw.Tag tag) => Transform.rotate( - child: const Icon(Icons.label), angle: degToRad(-45.0), + child: const Icon(Icons.label), ); } diff --git a/lib/app/modules/AbilityScoreForm/bindings/ability_score_form_binding.dart b/lib/app/modules/AbilityScoreForm/bindings/ability_score_form_binding.dart deleted file mode 100644 index 570e0352..00000000 --- a/lib/app/modules/AbilityScoreForm/bindings/ability_score_form_binding.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:get/get.dart'; - -import '../controllers/ability_score_form_controller.dart'; - -class AbilityScoreFormBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut( - () => AbilityScoreFormController(), - ); - } -} diff --git a/lib/app/modules/AbilityScoreForm/controllers/ability_score_form_controller.dart b/lib/app/modules/AbilityScoreForm/controllers/ability_score_form_controller.dart index 6810c795..fee9520f 100644 --- a/lib/app/modules/AbilityScoreForm/controllers/ability_score_form_controller.dart +++ b/lib/app/modules/AbilityScoreForm/controllers/ability_score_form_controller.dart @@ -1,71 +1,60 @@ import 'package:dungeon_paper/app/data/models/ability_scores.dart'; +import 'package:dungeon_paper/core/route_arguments.dart'; import 'package:dungeon_paper/core/utils/enums.dart'; import 'package:dungeon_paper/core/utils/string_validator.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -class AbilityScoreFormController extends GetxController { - final entity = AbilityScore.empty().obs; +class AbilityScoreFormController extends ChangeNotifier { + late final AbilityScore entity; - late final Rx _key = TextEditingController().obs; - TextEditingController get key => _key.value; + late final TextEditingController _key; + TextEditingController get key => _key; - late final Rx _name = TextEditingController().obs; - TextEditingController get name => _name.value; + late final TextEditingController _name; + TextEditingController get name => _name; - late final Rx _description = - TextEditingController().obs; - TextEditingController get description => _description.value; + late final TextEditingController _description; + TextEditingController get description => _description; - late final Rx _debilityName = - TextEditingController().obs; - TextEditingController get debilityName => _debilityName.value; + late final TextEditingController _debilityName; + TextEditingController get debilityName => _debilityName; - late final Rx _debilityDescription = - TextEditingController().obs; - TextEditingController get debilityDescription => _debilityDescription.value; + late final TextEditingController _debilityDescription; + TextEditingController get debilityDescription => _debilityDescription; - late final Rx _icon = Rx(null); - IconData? get icon => _icon.value; + late final IconData? _icon; + IconData? get icon => _icon; late final void Function(AbilityScore abilityScore) onSave; late final FormContext formContext; - @override - void onInit() { - super.onInit(); - final AbilityScoreFormArguments args = Get.arguments; + AbilityScoreFormController(BuildContext context) { + final AbilityScoreFormArguments args = getArgs(context); formContext = args.abilityScore != null ? FormContext.edit : FormContext.create; if (args.abilityScore != null) { - entity.value = args.abilityScore!; + entity = args.abilityScore!; } onSave = args.onSave; - _key.value = TextEditingController(text: entity.value.key) - ..addListener(_update); - _name.value = TextEditingController(text: entity.value.name) - ..addListener(_update); - _description.value = TextEditingController(text: entity.value.description) - ..addListener(_update); - _debilityName.value = TextEditingController(text: entity.value.debilityName) - ..addListener(_update); - _debilityDescription.value = - TextEditingController(text: entity.value.debilityDescription) - ..addListener(_update); - _icon.value = entity.value.customIcon; + _key = TextEditingController(text: entity.key); + _name = TextEditingController(text: entity.name); + _description = TextEditingController(text: entity.description); + _debilityName = TextEditingController(text: entity.debilityName); + _debilityDescription = + TextEditingController(text: entity.debilityDescription); + _icon = entity.customIcon; } @override - void onClose() { - _key.value.dispose(); - _name.value.dispose(); - _description.value.dispose(); - _debilityName.value.dispose(); - _debilityDescription.value.dispose(); - _icon.close(); - super.onClose(); + void dispose() { + super.dispose(); + _key.dispose(); + _name.dispose(); + _description.dispose(); + _debilityName.dispose(); + _debilityDescription.dispose(); } bool get isValid => [ @@ -89,18 +78,9 @@ class AbilityScoreFormController extends GetxController { String? requiredValidator(String? value) => StringValidator(minLength: 1).validator(value); - void _update() { - _key.refresh(); - _name.refresh(); - _description.refresh(); - _debilityName.refresh(); - _debilityDescription.refresh(); - _icon.refresh(); - } - - void save() { + void save(BuildContext context) { onSave( - entity.value.copyWith( + entity.copyWith( key: key.text, name: name.text, description: description.text, @@ -109,7 +89,7 @@ class AbilityScoreFormController extends GetxController { icon: icon, ), ); - Get.back(); + Navigator.of(context).pop(); } } @@ -122,3 +102,4 @@ class AbilityScoreFormArguments { required this.onSave, }); } + diff --git a/lib/app/modules/AbilityScoreForm/views/ability_score_form_view.dart b/lib/app/modules/AbilityScoreForm/views/ability_score_form_view.dart index 9bb34e2a..2f7630db 100644 --- a/lib/app/modules/AbilityScoreForm/views/ability_score_form_view.dart +++ b/lib/app/modules/AbilityScoreForm/views/ability_score_form_view.dart @@ -4,105 +4,110 @@ import 'package:dungeon_paper/app/widgets/atoms/help_text.dart'; import 'package:dungeon_paper/core/utils/enums.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; import '../controllers/ability_score_form_controller.dart'; -class AbilityScoreFormView extends GetView { +class AbilityScoreFormView extends StatelessWidget { const AbilityScoreFormView({super.key}); + @override Widget build(BuildContext context) { const separator = SizedBox(height: 16); return Scaffold( appBar: AppBar( - title: Text( - controller.formContext == FormContext.create - ? tr.generic.addEntity(tr.entity(tn(AbilityScore))) - : tr.generic.editEntity(tr.entity(tn(AbilityScore))), + title: Consumer( + builder: (context, controller, _) => Text( + controller.formContext == FormContext.create + ? tr.generic.addEntity(tr.entity(tn(AbilityScore))) + : tr.generic.editEntity(tr.entity(tn(AbilityScore))), + ), ), centerTitle: true, ), - floatingActionButton: Obx( - () => AdvancedFloatingActionButton.extended( - onPressed: controller.isValid ? controller.save : null, + floatingActionButton: Consumer( + builder: (context, controller, _) => + AdvancedFloatingActionButton.extended( + onPressed: controller.isValid ? () => controller.save(context) : null, label: Text(tr.generic.save), icon: const Icon(Icons.save), ), ), body: Form( autovalidateMode: AutovalidateMode.onUserInteraction, - child: ListView( - padding: const EdgeInsets.all(16).copyWith(bottom: 80), - children: [ - TextFormField( - controller: controller.key, - decoration: InputDecoration( - labelText: tr.abilityScores.form.key.label, + child: Consumer( + builder: (context, controller, _) => ListView( + padding: const EdgeInsets.all(16).copyWith(bottom: 80), + children: [ + TextFormField( + controller: controller.key, + decoration: InputDecoration( + labelText: tr.abilityScores.form.key.label, + ), + validator: controller.keyValidator, ), - validator: controller.keyValidator, - ), - Padding( - padding: const EdgeInsets.only(top: 4), - child: HelpText(text: tr.abilityScores.form.key.description), - ), - separator, - TextFormField( - controller: controller.name, - decoration: InputDecoration( - labelText: tr.abilityScores.form.name.label, - hintText: tr.abilityScores.form.name.description, + Padding( + padding: const EdgeInsets.only(top: 4), + child: HelpText(text: tr.abilityScores.form.key.description), ), - validator: controller.requiredValidator, - ), - separator, - TextFormField( - controller: controller.description, - minLines: 3, - maxLines: 3, - decoration: InputDecoration( - labelText: tr.abilityScores.form.description.label, - hintText: tr.abilityScores.form.description.description, + separator, + TextFormField( + controller: controller.name, + decoration: InputDecoration( + labelText: tr.abilityScores.form.name.label, + hintText: tr.abilityScores.form.name.description, + ), + validator: controller.requiredValidator, ), - validator: controller.requiredValidator, - ), - const Divider(height: 48), - TextFormField( - controller: controller.debilityName, - decoration: InputDecoration( - labelText: tr.abilityScores.form.debilityName.label, - hintText: tr.abilityScores.form.debilityName.description, + separator, + TextFormField( + controller: controller.description, + minLines: 3, + maxLines: 3, + decoration: InputDecoration( + labelText: tr.abilityScores.form.description.label, + hintText: tr.abilityScores.form.description.description, + ), + validator: controller.requiredValidator, ), - validator: controller.requiredValidator, - ), - separator, - TextFormField( - controller: controller.debilityDescription, - minLines: 3, - maxLines: 3, - decoration: InputDecoration( - labelText: tr.abilityScores.form.debilityDescription.label, - hintText: tr.abilityScores.form.debilityDescription.description, + const Divider(height: 48), + TextFormField( + controller: controller.debilityName, + decoration: InputDecoration( + labelText: tr.abilityScores.form.debilityName.label, + hintText: tr.abilityScores.form.debilityName.description, + ), + validator: controller.requiredValidator, ), - validator: controller.requiredValidator, - ), - separator, - Text(tr.abilityScores.form.icon.label), - separator, - Obx( - () => Align( + separator, + TextFormField( + controller: controller.debilityDescription, + minLines: 3, + maxLines: 3, + decoration: InputDecoration( + labelText: tr.abilityScores.form.debilityDescription.label, + hintText: + tr.abilityScores.form.debilityDescription.description, + ), + validator: controller.requiredValidator, + ), + separator, + Text(tr.abilityScores.form.icon.label), + separator, + Align( alignment: Alignment.centerLeft, child: Icon(controller.icon ?? AbilityScore.iconFor(controller.key.text)), ), - ), - separator, - ElevatedButton( - onPressed: () => controller.pickIcon(context), - child: Text( - tr.abilityScores.form.icon.button, + separator, + ElevatedButton( + onPressed: () => controller.pickIcon(context), + child: Text( + tr.abilityScores.form.icon.button, + ), ), - ), - ], + ], + ), ), ), ); diff --git a/lib/app/modules/AbilityScoresForm/bindings/ability_scores_form_binding.dart b/lib/app/modules/AbilityScoresForm/bindings/ability_scores_form_binding.dart deleted file mode 100644 index 4029ebb1..00000000 --- a/lib/app/modules/AbilityScoresForm/bindings/ability_scores_form_binding.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:get/get.dart'; - -import '../controllers/ability_scores_form_controller.dart'; - -class AbilityScoresFormBinding extends Bindings { - @override - void dependencies() { - Get.put( - AbilityScoresFormController(), - ); - } -} diff --git a/lib/app/modules/AbilityScoresForm/controllers/ability_scores_form_controller.dart b/lib/app/modules/AbilityScoresForm/controllers/ability_scores_form_controller.dart index 9582b7f1..9332fe38 100644 --- a/lib/app/modules/AbilityScoresForm/controllers/ability_scores_form_controller.dart +++ b/lib/app/modules/AbilityScoresForm/controllers/ability_scores_form_controller.dart @@ -1,31 +1,25 @@ import 'package:dungeon_paper/app/data/models/ability_scores.dart'; +import 'package:dungeon_paper/core/route_arguments.dart'; import 'package:dungeon_paper/core/utils/list_utils.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -class AbilityScoresFormController extends GetxController { - final dirty = false.obs; +class AbilityScoresFormController extends ChangeNotifier { + var dirty = false; - final Rx abilityScores = AbilityScores.dungeonWorld( - dex: 10, str: 10, wis: 10, con: 10, intl: 10, cha: 10) - .obs; + AbilityScores abilityScores = AbilityScores.dungeonWorldAll(10); final textControllers = {}; late final void Function(AbilityScores abilityScores) onChanged; - AbilityScoresFormController(); - - @override - void onReady() { - super.onReady(); - final AbilityScoresFormArguments args = Get.arguments; + AbilityScoresFormController(BuildContext context) { + final AbilityScoresFormArguments args = getArgs(context); if (args.abilityScores != null) { - abilityScores.value = args.abilityScores!; + abilityScores = args.abilityScores!; } for (final ctrl in textControllers.values) { ctrl.removeListener(validate); } textControllers.clear(); - for (final stat in abilityScores.value.stats) { + for (final stat in abilityScores.stats) { textControllers[stat.key] = TextEditingController(text: stat.value.toString()) ..addListener(validate); @@ -34,16 +28,17 @@ class AbilityScoresFormController extends GetxController { } void validate() { - dirty.value = true; - abilityScores.value = abilityScores.value.copyWithStatValues({ - for (final stat in abilityScores.value.stats) + dirty = true; + abilityScores = abilityScores.copyWithStatValues({ + for (final stat in abilityScores.stats) stat.key: int.tryParse(textControllers[stat.key]!.text) ?? stat.value }); + notifyListeners(); } void updateStat(AbilityScore stat) { - abilityScores.value = abilityScores.value - .copyWith(stats: updateByKey(abilityScores.value.stats, [stat])); + abilityScores = + abilityScores.copyWith(stats: updateByKey(abilityScores.stats, [stat])); textControllers[stat.key] ??= TextEditingController(text: stat.value.toString()) ..addListener(validate); @@ -51,8 +46,8 @@ class AbilityScoresFormController extends GetxController { } void removeStat(AbilityScore stat) { - abilityScores.value = abilityScores.value - .copyWith(stats: removeByKey(abilityScores.value.stats, [stat])); + abilityScores = + abilityScores.copyWith(stats: removeByKey(abilityScores.stats, [stat])); textControllers.remove(stat.key); } @@ -60,12 +55,19 @@ class AbilityScoresFormController extends GetxController { if (textControllers.containsKey(abilityScore.key)) { return; } - abilityScores.value = abilityScores.value - .copyWith(stats: [...abilityScores.value.stats, abilityScore]); + abilityScores = + abilityScores.copyWith(stats: [...abilityScores.stats, abilityScore]); textControllers[abilityScore.key] = TextEditingController(text: abilityScore.value.toString()) ..addListener(validate); } + + void onReorder(int oldIndex, int newIndex) { + abilityScores = abilityScores.copyWith( + stats: reorder(abilityScores.stats, oldIndex, newIndex), + ); + notifyListeners(); + } } class AbilityScoresFormArguments { @@ -77,3 +79,4 @@ class AbilityScoresFormArguments { required this.onChanged, }); } + diff --git a/lib/app/modules/AbilityScoresForm/views/ability_scores_form_view.dart b/lib/app/modules/AbilityScoresForm/views/ability_scores_form_view.dart index a6496adb..13419e7b 100644 --- a/lib/app/modules/AbilityScoresForm/views/ability_scores_form_view.dart +++ b/lib/app/modules/AbilityScoresForm/views/ability_scores_form_view.dart @@ -16,25 +16,23 @@ import 'package:dungeon_paper/core/utils/list_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; -class AbilityScoresFormView extends GetView { - const AbilityScoresFormView({ - super.key, - }); +class AbilityScoresFormView extends StatelessWidget { + const AbilityScoresFormView({super.key}); @override Widget build(BuildContext context) { - return Obx( - () => ConfirmExitView( - dirty: controller.dirty.value, + return Consumer( + builder: (context, controller, _) => ConfirmExitView( + dirty: controller.dirty, child: Scaffold( appBar: AppBar( title: Text(tr.entityPlural(tn(AbilityScore))), centerTitle: true, ), floatingActionButton: AdvancedFloatingActionButton.extended( - onPressed: _save, + onPressed: () => _save(context), label: Text(tr.generic.save), icon: const Icon(Icons.save), ), @@ -47,22 +45,19 @@ class AbilityScoresFormView extends GetView { child: ReorderableListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - itemCount: controller.abilityScores.value.stats.length, - onReorder: (int oldIndex, int newIndex) { - controller.abilityScores.value = - controller.abilityScores.value.copyWith( - stats: reorder(controller.abilityScores.value.stats, - oldIndex, newIndex)); - }, - itemBuilder: (context, index) => _buildCard(context, index), + itemCount: controller.abilityScores.stats.length, + onReorder: controller.onReorder, + itemBuilder: (context, index) => + _buildCard(context, controller, index), ), ), ElevatedButton.icon( - onPressed: () => Get.toNamed(Routes.abilityScoreForm, - arguments: AbilityScoreFormArguments( - abilityScore: null, - onSave: controller.addStat, - )), + onPressed: () => + Navigator.of(context).pushNamed(Routes.abilityScoreForm, + arguments: AbilityScoreFormArguments( + abilityScore: null, + onSave: controller.addStat, + )), icon: const Icon(Icons.add), label: Text( tr.generic.addEntity( @@ -77,15 +72,15 @@ class AbilityScoresFormView extends GetView { ); } - Widget _buildCard(BuildContext context, int index) { + Widget _buildCard( + BuildContext context, AbilityScoresFormController controller, int index) { final theme = Theme.of(context); final textTheme = theme.textTheme; final statKey = sortByPredefined( controller.textControllers.keys.toList(), - order: - controller.abilityScores.value.stats.map((stat) => stat.key).toList(), + order: controller.abilityScores.stats.map((stat) => stat.key).toList(), ).elementAt(index); - final stat = controller.abilityScores.value.stats + final stat = controller.abilityScores.stats .firstWhere((stat) => stat.key == statKey); return Padding( key: Key('stat-$statKey'), @@ -151,19 +146,18 @@ class AbilityScoresFormView extends GetView { Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: EntityEditMenu( - onEdit: () => Get.toNamed( + onEdit: () => Navigator.of(context).pushNamed( Routes.abilityScoreForm, arguments: AbilityScoreFormArguments( abilityScore: stat, onSave: (stat) => controller.updateStat(stat), ), ), - onDelete: () => deleteDialog.confirm( + onDelete: () => awaitDeleteConfirmation( context, - DeleteDialogOptions( - entityName: stat.name, - entityKind: tr.entity(tn(AbilityScore))), + stat.name, () => controller.removeStat(stat), + AbilityScore, ), ), ), @@ -176,8 +170,10 @@ class AbilityScoresFormView extends GetView { ); } - _save() { - controller.onChanged(controller.abilityScores.value); - Get.back(); + _save(BuildContext context) { + final controller = + Provider.of(context, listen: false); + controller.onChanged(controller.abilityScores); + Navigator.of(context).pop(); } } diff --git a/lib/app/modules/About/bindings/about_binding.dart b/lib/app/modules/About/bindings/about_binding.dart deleted file mode 100644 index 388e1fea..00000000 --- a/lib/app/modules/About/bindings/about_binding.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:get/get.dart'; - -import '../controllers/about_controller.dart'; - -class AboutBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut( - () => AboutController(), - ); - } -} diff --git a/lib/app/modules/About/controllers/about_controller.dart b/lib/app/modules/About/controllers/about_controller.dart index 47e254bc..5887ba71 100644 --- a/lib/app/modules/About/controllers/about_controller.dart +++ b/lib/app/modules/About/controllers/about_controller.dart @@ -1,18 +1,17 @@ -import 'package:get/get.dart'; +import 'package:flutter/widgets.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:pub_semver/pub_semver.dart'; -class AboutController extends GetxController { - final version = Rx(null); +class AboutController extends ChangeNotifier { + Version? version; - @override - void onInit() { - super.onInit(); + AboutController() { getVersion(); } Future getVersion() async { final info = await PackageInfo.fromPlatform(); - version.value = Version.parse(info.version + '+' + info.buildNumber); + version = Version.parse('${info.version}+${info.buildNumber}'); + notifyListeners(); } } diff --git a/lib/app/modules/About/views/about_view.dart b/lib/app/modules/About/views/about_view.dart index d25535a6..0401a5fa 100644 --- a/lib/app/modules/About/views/about_view.dart +++ b/lib/app/modules/About/views/about_view.dart @@ -7,14 +7,14 @@ import 'package:dungeon_paper/core/dw_icons.dart'; import 'package:dungeon_paper/core/utils/builder_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:dungeon_paper/core/utils/list_utils.dart'; import '../../../model_utils/user_utils.dart'; import '../controllers/about_controller.dart'; -class AboutView extends GetView { +class AboutView extends StatelessWidget { const AboutView({super.key}); @override @@ -34,9 +34,9 @@ class AboutView extends GetView { textAlign: TextAlign.center, style: textTheme.headlineMedium, ), - () => Obx( - () => Text( - tr.about.version(controller.version.value?.toString() ?? '-'), + () => Consumer( + builder: (context, controller, _) => Text( + tr.about.version(controller.version?.toString() ?? '-'), textAlign: TextAlign.center, style: textTheme.bodySmall, ), @@ -71,7 +71,8 @@ class AboutView extends GetView { title: Text(tr.about.feedback.title), subtitle: Text(tr.about.feedback.subtitle, style: textTheme.bodySmall), - onTap: () => Get.toNamed(Routes.sendFeedback), + onTap: () => + Navigator.of(context).pushNamed(Routes.sendFeedback), isThreeLine: true, visualDensity: VisualDensity.compact, ), diff --git a/lib/app/modules/Account/bindings/account_binding.dart b/lib/app/modules/Account/bindings/account_binding.dart deleted file mode 100644 index 86ba92c3..00000000 --- a/lib/app/modules/Account/bindings/account_binding.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:get/get.dart'; - -import '../controllers/account_controller.dart'; - -class AccountBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut( - () => AccountController(), - ); - } -} diff --git a/lib/app/modules/Account/controllers/account_controller.dart b/lib/app/modules/Account/controllers/account_controller.dart index b8c12233..f4dea3b1 100644 --- a/lib/app/modules/Account/controllers/account_controller.dart +++ b/lib/app/modules/Account/controllers/account_controller.dart @@ -1,19 +1,21 @@ -import 'package:dungeon_paper/app/data/services/auth_service.dart'; -import 'package:dungeon_paper/app/data/services/user_service.dart'; +import 'package:dungeon_paper/app/data/services/auth_provider.dart'; +import 'package:dungeon_paper/app/data/services/user_provider.dart'; import 'package:dungeon_paper/core/utils/upload_utils.dart'; import 'package:dungeon_paper/core/utils/uuid.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:image_cropper/image_cropper.dart'; -class AccountController extends GetxController - with UserServiceMixin, AuthServiceMixin { - final uploading = false.obs; +class AccountController extends ChangeNotifier + with UserProviderMixin, AuthProviderMixin { + var uploading = false; - void updateEmail(String email) async { - await userService.updateEmail(email); - Get.rawSnackbar(message: tr.account.details.email.success); + void updateEmail(BuildContext context, String email) async { + final messenger = ScaffoldMessenger.of(context); + await userProvider.updateEmail(email); + messenger.showSnackBar( + SnackBar(content: Text(tr.account.details.email.success)), + ); } void uploadPhoto(BuildContext context) { @@ -22,15 +24,23 @@ class AccountController extends GetxController UploadSettings( uploadPath: '/UserPhoto/${uuid()}', cropStyle: CropStyle.circle, - onUploadFile: (_) => uploading.value = true, - onSuccess: (url) { - uploading.value = false; - userService.updateUser( - user.copyWith(photoUrl: url), - ); + onUploadFile: (_) { + uploading = true; + notifyListeners(); + }, + onSuccess: (url) { + uploading = false; + userProvider.updateUser(user.copyWith(photoUrl: url)); + notifyListeners(); + }, + onCancel: () { + uploading = false; + notifyListeners(); + }, + onError: (error) { + uploading = false; + notifyListeners(); }, - onCancel: () => uploading.value = false, - onError: (error) => uploading.value = false, ), ); } diff --git a/lib/app/modules/Account/views/account_view.dart b/lib/app/modules/Account/views/account_view.dart index 00c7ff16..5537187a 100644 --- a/lib/app/modules/Account/views/account_view.dart +++ b/lib/app/modules/Account/views/account_view.dart @@ -11,14 +11,14 @@ import 'package:dungeon_paper/core/utils/password_validator.dart'; import 'package:dungeon_paper/core/utils/string_validator.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; import '../../../../core/dw_icons.dart'; import '../../../../core/http/api.dart'; import '../../../model_utils/user_utils.dart'; import '../controllers/account_controller.dart'; -class AccountView extends GetView { +class AccountView extends StatelessWidget { const AccountView({super.key}); @override Widget build(BuildContext context) { @@ -28,8 +28,8 @@ class AccountView extends GetView { final builder = ItemBuilder.lazyChildren( children: [ () => Center( - child: Obx( - () => UserAvatar( + child: Consumer( + builder: (context, controller, _) => UserAvatar( user: controller.user, size: 100, ), @@ -45,19 +45,19 @@ class AccountView extends GetView { style: textTheme.bodySmall, ), ), - () => Obx( - () => ListTile( + () => Consumer( + builder: (context, controller, _) => ListTile( title: Text(tr.account.details.displayName.title), subtitle: Text(controller.user.displayName), leading: const Icon(Icons.abc), - onTap: _openNameDialog, + onTap: () => _openNameDialog(context), ), ), - () => Obx( - () => ListTile( + () => Consumer( + builder: (context, controller, _) => ListTile( title: Text(tr.account.details.image.title), subtitle: Text(tr.account.details.image.subtitle), - leading: controller.uploading.value + leading: controller.uploading ? const SizedBox.square( dimension: 24, child: CircularProgressIndicator.adaptive( @@ -65,24 +65,23 @@ class AccountView extends GetView { ), ) : const Icon(Icons.image), - enabled: !controller.uploading.value, - onTap: !controller.uploading.value - ? () => _uploadImage(context) - : null, + enabled: !controller.uploading, + onTap: + !controller.uploading ? () => _uploadImage(context) : null, ), ), - () => Obx( - () => ListTile( + () => Consumer( + builder: (context, controller, _) => ListTile( title: Text(tr.account.details.email.title), subtitle: Text(controller.user.email), - onTap: _openEmailDialog, + onTap: () => _openEmailDialog(context), leading: const Icon(Icons.email), ), ), () => ListTile( title: Text(tr.account.details.password.title), subtitle: Text(tr.account.details.password.subtitle), - onTap: _openPasswordDialog, + onTap: () => _openPasswordDialog(context), leading: const Icon(Icons.key), ), () => const Divider(), @@ -94,7 +93,7 @@ class AccountView extends GetView { style: textTheme.bodySmall, ), ), - // ...(controller.authService.fbUser?.providerData ?? []).map((provider) { + // ...(controller.authProvider.fbUser?.providerData ?? []).map((provider) { ...([ // ProviderName.password, // if (PlatformHelper.canUseGoogleSignIn) @@ -103,8 +102,8 @@ class AccountView extends GetView { ProviderName.apple ]).map( (provider) { - return () => Obx( - () => ListTile( + return () => Consumer( + builder: (context, controller, _) => ListTile( title: Text(tr.auth.providers.name(provider.name)), // subtitle: Text(provider.), leading: Icon(DwIcons.providerIcon(provider)), @@ -112,19 +111,19 @@ class AccountView extends GetView { ? Text( tr.auth.providers.unusable( tr.auth.providers.name(provider.name)), - textScaleFactor: 0.8, + textScaler: const TextScaler.linear(0.8), ) : null, trailing: ElevatedButton( - onPressed: providerCount > 1 - ? isProviderLinked(provider) + onPressed: providerCount(controller) > 1 + ? isProviderLinked(controller, provider) ? unlinkProvider(context, provider) : PlatformHelper.canUseProvider(provider) - ? linkProvider(provider) + ? linkProvider(context, provider) : null : null, child: Text( - isProviderLinked(provider) + isProviderLinked(controller, provider) ? tr.auth.providers.unlink : tr.auth.providers.link, ), @@ -134,22 +133,25 @@ class AccountView extends GetView { }, ), // delete account - () => ListTile( - title: Text(tr.account.deleteAccount.title), - leading: const Icon(Icons.delete_forever), - onTap: () => awaitDeleteAccountConfirmation( - context, - () { - api.requests.sendFeedback( - email: controller.user.email, - subject: 'Account Deletion Request', - body: - 'Automated: Request Account Deletion for ${controller.user.email}', - username: controller.user.username, - ); - // A deletion request for your account was sent successfully - Get.rawSnackbar(message: tr.account.deleteAccount.success); - }, + () => Consumer( + builder: (context, controller, _) => ListTile( + title: Text(tr.account.deleteAccount.title), + leading: const Icon(Icons.delete_forever), + onTap: () => awaitDeleteAccountConfirmation( + context, + () { + api.requests.sendFeedback( + email: controller.user.email, + subject: 'Account Deletion Request', + body: + 'Automated: Request Account Deletion for ${controller.user.email}', + username: controller.user.username, + ); + // A deletion request for your account was sent successfully + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(tr.account.deleteAccount.success))); + }, + ), ), ), // () => const SizedBox(height: 32), @@ -173,65 +175,92 @@ class AccountView extends GetView { ], ); - return Scaffold( - appBar: AppBar( - title: Text(controller.user.username), - centerTitle: true, + return Consumer( + builder: (context, controller, _) => Scaffold( + appBar: AppBar( + title: Text(controller.user.username), + centerTitle: true, + ), + body: builder.asListView(), ), - body: builder.asListView(), ); } - int get providerCount => - controller.authService.fbUser?.providerData.length ?? 0; + int providerCount(AccountController controller) { + return controller.authProvider.fbUser?.providerData.length ?? 0; + } - bool isProviderLinked(ProviderName provider) => - controller.authService.fbUser?.providerData + bool isProviderLinked(AccountController controller, ProviderName provider) => + controller.authProvider.fbUser?.providerData .any((pr) => pr.providerId == domainFromProviderName(provider)) == true; - void _openNameDialog() { - Get.dialog( - SingleTextFieldDialog( - title: tr.account.details.displayName.title, - inputLabel: tr.account.details.displayName.label, - inputHint: tr.account.details.displayName.placeholder, - value: controller.user.displayName, - onSave: (displayName) { - Get.rawSnackbar(message: tr.account.details.displayName.success); - controller.userService.updateUser( - controller.user.copyWith(displayName: displayName), - ); - }, - ), + void _openNameDialog(BuildContext context) { + showDialog( + context: context, + builder: (context) { + final controller = + Provider.of(context, listen: false); + return SingleTextFieldDialog( + title: tr.account.details.displayName.title, + inputLabel: tr.account.details.displayName.label, + inputHint: tr.account.details.displayName.placeholder, + value: controller.user.displayName, + onSave: (displayName) { + controller.userProvider.updateUser( + controller.user.copyWith(displayName: displayName), + ); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(tr.account.details.displayName.success), + ), + ); + }, + ); + }, ); } - void _openEmailDialog() { - Get.dialog( - SingleTextFieldDialog( - title: tr.account.details.email.title, - inputLabel: tr.account.details.email.label, - inputHint: tr.account.details.email.placeholder, - value: controller.user.email, - validator: EmailAddressValidator().validator, - onSave: controller.updateEmail, - ), + void _openEmailDialog(BuildContext context) { + showDialog( + context: context, + builder: (context) { + final controller = + Provider.of(context, listen: false); + return SingleTextFieldDialog( + title: tr.account.details.email.title, + inputLabel: tr.account.details.email.label, + inputHint: tr.account.details.email.placeholder, + value: controller.user.email, + validator: EmailAddressValidator().validator, + onSave: (email) => controller.updateEmail(context, email), + ); + }, ); } - void _openPasswordDialog() { - Get.dialog( - PasswordFieldDialog( - onSave: (password) { - Get.rawSnackbar(message: tr.account.details.password.success); - controller.authService.fbUser!.updatePassword(password); - }, - ), + void _openPasswordDialog(BuildContext context) { + showDialog( + context: context, + builder: (context) { + final controller = + Provider.of(context, listen: false); + return PasswordFieldDialog( + onSave: (password) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(tr.account.details.password.success), + ), + ); + controller.authProvider.fbUser!.updatePassword(password); + }, + ); + }, ); } void _uploadImage(BuildContext context) { + final controller = Provider.of(context, listen: false); controller.uploadPhoto(context); } @@ -241,16 +270,22 @@ class AccountView extends GetView { context, provider, () { - controller.authService.logoutFromProvider(provider); - controller.authService.fbUser! + final controller = + Provider.of(context, listen: false); + controller.authProvider.logoutFromProvider(provider); + controller.authProvider.fbUser! .unlink(domainFromProviderName(provider)); }, ); - Future Function() linkProvider(ProviderName provider) => () async { + Future Function() linkProvider( + BuildContext context, ProviderName provider) => + () async { + final controller = + Provider.of(context, listen: false); final cred = - await controller.authService.getProviderCredential(provider); - controller.authService.fbUser!.linkWithCredential(cred); + await controller.authProvider.getProviderCredential(provider); + controller.authProvider.fbUser!.linkWithCredential(cred); }; } @@ -335,9 +370,9 @@ class _SingleTextFieldDialogState extends State { context, onSave: () { widget.onSave(widget.value.text); - Get.back(); + Navigator.of(context).pop(); }, - onCancel: () => Get.back(), + onCancel: () => Navigator.of(context).pop(), ), ); } @@ -415,10 +450,10 @@ class _PasswordFieldDialogState extends State { onSave: valid ? () { widget.onSave(password.text); - Get.back(); + Navigator.of(context).pop(); } : null, - onCancel: () => Get.back(), + onCancel: () => Navigator.of(context).pop(), ), ); } diff --git a/lib/app/modules/BasicInfoForm/bindings/basic_info_form_binding.dart b/lib/app/modules/BasicInfoForm/bindings/basic_info_form_binding.dart deleted file mode 100644 index 023ad76d..00000000 --- a/lib/app/modules/BasicInfoForm/bindings/basic_info_form_binding.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:get/get.dart'; - -import '../controllers/basic_info_form_controller.dart'; - -class BasicInfoFormBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut( - () => BasicInfoFormController(), - ); - } -} diff --git a/lib/app/modules/BasicInfoForm/controllers/basic_info_form_controller.dart b/lib/app/modules/BasicInfoForm/controllers/basic_info_form_controller.dart index 346840d7..2af36ea4 100644 --- a/lib/app/modules/BasicInfoForm/controllers/basic_info_form_controller.dart +++ b/lib/app/modules/BasicInfoForm/controllers/basic_info_form_controller.dart @@ -1,75 +1,76 @@ import 'dart:io'; -import 'package:dungeon_paper/app/data/services/user_service.dart'; +import 'package:dungeon_paper/app/data/services/user_provider.dart'; +import 'package:dungeon_paper/core/route_arguments.dart'; import 'package:dungeon_paper/core/utils/upload_utils.dart'; import 'package:dungeon_paper/core/utils/uuid.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -class BasicInfoFormController extends GetxController with UserServiceMixin { - final Rx name = TextEditingController().obs; - final Rx avatarUrl = TextEditingController().obs; +class BasicInfoFormController extends ChangeNotifier with UserProviderMixin { + final TextEditingController name = TextEditingController(); + final TextEditingController avatarUrl = TextEditingController(); late final void Function(String name, String avatar) onChanged; - final uploading = false.obs; + var uploading = false; + File? photoFile; + var dirty = false; - final Rx photoFile = Rx(null); - final dirty = false.obs; - - bool get hasPhotoFile => photoFile.value != null; - bool get isUploading => uploading.value; + bool get hasPhotoFile => photoFile != null; + bool get isUploading => uploading; void startUploadFlow(BuildContext context) { cropAndUploadPhoto( context, UploadSettings( - uploadPath: '/CharacterPhoto/' + uuid(), - onUploadFile: (_) => uploading.value = true, - onSuccess: (url) { - avatarUrl.value.text = url; - uploading.value = false; + uploadPath: '/CharacterPhoto/${uuid()}', + onUploadFile: (_) { + uploading = true; + notifyListeners(); + }, + onSuccess: (url) { + avatarUrl.text = url; + uploading = false; + notifyListeners(); + }, + onCancel: () { + uploading = false; + notifyListeners(); + }, + onError: (error) { + uploading = false; + notifyListeners(); }, - onCancel: () => uploading.value = false, - onError: (error) => uploading.value = false, ), ); } void resetPhoto() { - photoFile.value = null; - avatarUrl.value.text = ''; + photoFile = null; + avatarUrl.text = ''; + notifyListeners(); } - @override - void onReady() { - super.onReady(); - final BasicInfoFormArguments args = Get.arguments; + BasicInfoFormController(BuildContext context) { + final BasicInfoFormArguments args = getArgs(context); onChanged = args.onChanged; - name.value = TextEditingController(text: args.name); - avatarUrl.value = TextEditingController(text: args.avatarUrl); + name.text = args.name; + avatarUrl.text = args.avatarUrl; - name.value.addListener(_refreshName); - avatarUrl.value.addListener(_refreshAvatarUrl); + name.addListener(_setDirty); + avatarUrl.addListener(_setDirty); } @override - void onClose() { - name.value.removeListener(_refreshName); - avatarUrl.value.removeListener(_refreshAvatarUrl); - super.onClose(); - } - - void _refreshName() { - name.refresh(); - _setDirty(); - } - - void _refreshAvatarUrl() { - avatarUrl.refresh(); - _setDirty(); + void dispose() { + super.dispose(); + name.removeListener(_setDirty); + avatarUrl.removeListener(_setDirty); } void _setDirty() { - dirty.value = true; + if (!dirty) { + dirty = true; + notifyListeners(); + } } } diff --git a/lib/app/modules/BasicInfoForm/views/basic_info_form_view.dart b/lib/app/modules/BasicInfoForm/views/basic_info_form_view.dart index 3346af35..6148cba1 100644 --- a/lib/app/modules/BasicInfoForm/views/basic_info_form_view.dart +++ b/lib/app/modules/BasicInfoForm/views/basic_info_form_view.dart @@ -1,5 +1,5 @@ import 'package:dungeon_paper/app/data/models/character.dart'; -import 'package:dungeon_paper/app/data/services/user_service.dart'; +import 'package:dungeon_paper/app/data/services/user_provider.dart'; import 'package:dungeon_paper/app/routes/app_pages.dart'; import 'package:dungeon_paper/app/themes/colors.dart'; import 'package:dungeon_paper/app/widgets/atoms/advanced_floating_action_button.dart'; @@ -12,12 +12,11 @@ import 'package:dungeon_paper/core/platform_helper.dart'; import 'package:dungeon_paper/core/utils/content_generators/character_name_generator.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; import '../controllers/basic_info_form_controller.dart'; -class BasicInfoFormView extends GetView - with UserServiceMixin { +class BasicInfoFormView extends StatelessWidget with UserProviderMixin { const BasicInfoFormView({ super.key, }); @@ -25,16 +24,16 @@ class BasicInfoFormView extends GetView @override Widget build(BuildContext context) { final theme = Theme.of(context); - return Obx( - () => ConfirmExitView( - dirty: controller.dirty.value, + return Consumer( + builder: (context, controller, _) => ConfirmExitView( + dirty: controller.dirty, child: Scaffold( appBar: AppBar( title: Text(tr.basicInfo.title), centerTitle: true, ), floatingActionButton: AdvancedFloatingActionButton.extended( - onPressed: _save, + onPressed: () => _save(context), label: Text(tr.generic.save), icon: const Icon(Icons.save), ), @@ -46,7 +45,7 @@ class BasicInfoFormView extends GetView children: [ TextFormField( autovalidateMode: AutovalidateMode.onUserInteraction, - controller: controller.name.value, + controller: controller.name, textInputAction: TextInputAction.next, validator: (val) => val == null || val.isEmpty ? 'Cannot be empty' : null, @@ -64,7 +63,7 @@ class BasicInfoFormView extends GetView ), icon: const Icon(DwIcons.dice_d6_numbered), onPressed: () { - controller.name.value.text = + controller.name.text = CharacterNameGenerator().generate(); }, ), @@ -113,14 +112,14 @@ class BasicInfoFormView extends GetView height: 40, child: ElevatedButton.icon( onPressed: - !controller.isUploading && userService.isLoggedIn + !controller.isUploading && userProvider.isLoggedIn ? () => controller.startUploadFlow(context) : null, icon: const Icon(Icons.upload_file), label: Text(tr.basicInfo.form.photo.choose), ), ), - if (userService.isGuest) ...[ + if (userProvider.isGuest) ...[ Padding( padding: const EdgeInsets.only(top: 8), child: RichText( @@ -137,7 +136,8 @@ class BasicInfoFormView extends GetView Hyperlink.textSpan( context, tr.basicInfo.form.photo.guest.label, - onTap: () => Get.toNamed(Routes.login), + onTap: () => + Navigator.of(context).pushNamed(Routes.login), ), TextSpan(text: tr.basicInfo.form.photo.guest.suffix), ], @@ -162,7 +162,7 @@ class BasicInfoFormView extends GetView : Text(tr.basicInfo.form.photo.orSeparator), ), TextFormField( - controller: controller.avatarUrl.value, + controller: controller.avatarUrl, textInputAction: TextInputAction.done, enabled: !controller.isUploading, // onChanged: (val) => updateControllers(), @@ -181,9 +181,11 @@ class BasicInfoFormView extends GetView ); } - _save() { + _save(BuildContext context) { + final controller = + Provider.of(context, listen: false); controller.onChanged( controller.name.value.text, controller.avatarUrl.value.text); - Get.back(); + Navigator.of(context).pop(); } } diff --git a/lib/app/modules/BioForm/bindings/bio_form_binding.dart b/lib/app/modules/BioForm/bindings/bio_form_binding.dart deleted file mode 100644 index c52a24fd..00000000 --- a/lib/app/modules/BioForm/bindings/bio_form_binding.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:get/get.dart'; - -import '../controllers/bio_form_controller.dart'; - -class BioFormBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut( - () => BioFormController(), - ); - } -} diff --git a/lib/app/modules/BioForm/controllers/bio_form_controller.dart b/lib/app/modules/BioForm/controllers/bio_form_controller.dart index ee52f171..65160d30 100644 --- a/lib/app/modules/BioForm/controllers/bio_form_controller.dart +++ b/lib/app/modules/BioForm/controllers/bio_form_controller.dart @@ -1,49 +1,47 @@ import 'package:dungeon_paper/app/data/models/character.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; import 'package:dungeon_paper/core/utils/enum_utils.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; -class BioFormController extends GetxController with CharacterServiceMixin { - final bioDesc = TextEditingController().obs; - final looks = TextEditingController().obs; - final alignmentName = 'good'.obs; - final alignmentValue = TextEditingController().obs; - final bonds = [].obs; - final dirty = false.obs; +class BioFormController extends ChangeNotifier with CharacterProviderMixin { + var bioDesc = TextEditingController(); + var looks = TextEditingController(); + var alignmentName = 'good'; + var alignmentValue = TextEditingController(); + var bonds = []; + var dirty = false; - @override - void onReady() { - super.onReady(); - final BioFormArguments args = Get.arguments; - final char = args.character ?? this.char; - bioDesc.value = TextEditingController(text: char.bio.description); - looks.value = TextEditingController(text: char.bio.looks); - alignmentName.value = char.bio.alignment.key; - alignmentValue.value = + BioFormController(BuildContext context) { + bioDesc = TextEditingController(text: char.bio.description); + looks = TextEditingController(text: char.bio.looks); + alignmentName = char.bio.alignment.key; + alignmentValue = TextEditingController(text: char.bio.alignment.description); - bonds.value = char.sessionMarks + bonds = char.sessionMarks .map((e) => TextEditingController(text: e.description)) .toList(); } - void save() { - charService.updateCharacter(char.copyWith( + void save(BuildContext context) { + final charProvider = CharacterProvider.of(context); + final char = charProvider.current; + charProvider.updateCharacter(char.copyWith( bio: char.bio.copyWith( description: bioDesc.value.text, looks: looks.value.text.replaceAll(RegExp('\\s*\n'), ' \n'), alignment: char.bio.alignment.copyWith( description: alignmentValue.value.text, - type: getEnumByName(dw.AlignmentType.values, alignmentName.value), + type: getEnumByName(dw.AlignmentType.values, alignmentName), ), ), )); } void setDirty([String? value]) { - if (!dirty.value) { - dirty.value = true; + if (!dirty) { + dirty = true; + notifyListeners(); } } } diff --git a/lib/app/modules/BioForm/views/bio_form_view.dart b/lib/app/modules/BioForm/views/bio_form_view.dart index 1c8b71ce..f1d6e373 100644 --- a/lib/app/modules/BioForm/views/bio_form_view.dart +++ b/lib/app/modules/BioForm/views/bio_form_view.dart @@ -1,5 +1,4 @@ import 'package:dungeon_paper/app/data/models/alignment.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; import 'package:dungeon_paper/app/modules/BioForm/controllers/bio_form_controller.dart'; import 'package:dungeon_paper/app/widgets/atoms/advanced_floating_action_button.dart'; import 'package:dungeon_paper/app/widgets/atoms/confirm_exit_view.dart'; @@ -7,23 +6,22 @@ import 'package:dungeon_paper/app/widgets/atoms/rich_text_field.dart'; import 'package:dungeon_paper/app/widgets/atoms/select_box.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; -class BioFormView extends GetView - with CharacterServiceMixin { +class BioFormView extends StatelessWidget { const BioFormView({super.key}); @override Widget build(BuildContext context) { - return Obx( - () => ConfirmExitView( - dirty: controller.dirty.value, + return Consumer( + builder: (context, controller, _) => ConfirmExitView( + dirty: controller.dirty, child: Scaffold( appBar: AppBar( title: Text(tr.bio.dialog.title), ), floatingActionButton: AdvancedFloatingActionButton.extended( - onPressed: _save, + onPressed: _save(context), label: Text(tr.generic.save), icon: const Icon(Icons.save), ), @@ -31,7 +29,7 @@ class BioFormView extends GetView padding: const EdgeInsets.all(16), children: [ RichTextField( - controller: controller.bioDesc.value, + controller: controller.bioDesc, minLines: 5, maxLines: 10, textCapitalization: TextCapitalization.sentences, @@ -43,7 +41,7 @@ class BioFormView extends GetView ), const SizedBox(height: 8), RichTextField( - controller: controller.looks.value, + controller: controller.looks, minLines: 4, maxLines: 8, textCapitalization: TextCapitalization.sentences, @@ -55,7 +53,7 @@ class BioFormView extends GetView ), const SizedBox(height: 24), SelectBox( - value: controller.alignmentName.value, + value: controller.alignmentName, items: AlignmentValue.allKeys .map( (a) => DropdownMenuItem( @@ -71,7 +69,7 @@ class BioFormView extends GetView ) .toList(), onChanged: (v) { - controller.alignmentName.value = v!; + controller.alignmentName = v!; controller.setDirty(); }, isExpanded: true, @@ -79,7 +77,7 @@ class BioFormView extends GetView ), const SizedBox(height: 8), RichTextField( - controller: controller.alignmentValue.value, + controller: controller.alignmentValue, minLines: 4, maxLines: 8, textCapitalization: TextCapitalization.sentences, @@ -97,8 +95,11 @@ class BioFormView extends GetView ); } - void _save() { - controller.save(); - Get.back(); + void Function() _save(BuildContext context) { + return () { + final controller = Provider.of(context, listen: false); + controller.save(context); + Navigator.of(context).pop(); + }; } } diff --git a/lib/app/modules/BondsFlagsForm/bindings/bonds_flags_form_binding.dart b/lib/app/modules/BondsFlagsForm/bindings/bonds_flags_form_binding.dart deleted file mode 100644 index d0fc7d5a..00000000 --- a/lib/app/modules/BondsFlagsForm/bindings/bonds_flags_form_binding.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:get/get.dart'; - -import '../controllers/bonds_flags_form_controller.dart'; - -class BondsFlagsFormBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut( - () => BondsFlagsFormController(), - ); - } -} diff --git a/lib/app/modules/BondsFlagsForm/controllers/bonds_flags_form_controller.dart b/lib/app/modules/BondsFlagsForm/controllers/bonds_flags_form_controller.dart index 6dc41c3f..36890526 100644 --- a/lib/app/modules/BondsFlagsForm/controllers/bonds_flags_form_controller.dart +++ b/lib/app/modules/BondsFlagsForm/controllers/bonds_flags_form_controller.dart @@ -1,32 +1,30 @@ import 'package:dungeon_paper/app/data/models/session_marks.dart'; +import 'package:dungeon_paper/core/route_arguments.dart'; import 'package:dungeon_paper/core/utils/list_utils.dart'; import 'package:dungeon_paper/core/utils/uuid.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -class BondsFlagsFormController extends GetxController { - final bonds = [].obs; - final flags = [].obs; - final bondsDesc = [].obs; - final flagsDesc = [].obs; +class BondsFlagsFormController extends ChangeNotifier { + final bonds = []; + final flags = []; + final bondsDesc = []; + final flagsDesc = []; late final void Function(List bonds, List flags) onChanged; - final dirty = false.obs; + var dirty = false; - @override - void onReady() { - super.onReady(); - final BondsFlagsFormArguments args = Get.arguments; - bonds.value = args.bonds; - bondsDesc.value = args.bonds + BondsFlagsFormController(BuildContext context) { + final BondsFlagsFormArguments args = getArgs(context); + bonds.addAll(args.bonds); + bondsDesc.addAll(args.bonds .map((e) => TextEditingController(text: e.description)..addListener(_setDirty)) - .toList(); - flags.value = args.flags; - flagsDesc.value = args.flags + .toList()); + flags.addAll(args.flags); + flagsDesc.addAll(args.flags .map((e) => TextEditingController(text: e.description)..addListener(_setDirty)) - .toList(); + .toList()); onChanged = args.onChanged; } @@ -61,14 +59,14 @@ class BondsFlagsFormController extends GetxController { } @override - void onClose() { + void dispose() { + super.dispose(); for (final ctrl in [...bondsDesc, ...flagsDesc]) { ctrl.removeListener(_setDirty); } - super.onClose(); } - void save() { + void save(BuildContext context) { final newBonds = enumerate(bonds) .map((e) => e.value.copyWithInherited(description: bondsDesc[e.index].text)) @@ -81,12 +79,13 @@ class BondsFlagsFormController extends GetxController { .toList(); onChanged(newBonds, newFlags); - Get.back(); + Navigator.of(context).pop(); } void _setDirty() { - if (!dirty.value) { - dirty.value = true; + if (!dirty) { + dirty = true; + notifyListeners(); } } } diff --git a/lib/app/modules/BondsFlagsForm/views/bonds_flags_form_view.dart b/lib/app/modules/BondsFlagsForm/views/bonds_flags_form_view.dart index 1373ed13..8173d8a8 100644 --- a/lib/app/modules/BondsFlagsForm/views/bonds_flags_form_view.dart +++ b/lib/app/modules/BondsFlagsForm/views/bonds_flags_form_view.dart @@ -3,26 +3,26 @@ import 'package:dungeon_paper/app/widgets/atoms/confirm_exit_view.dart'; import 'package:dungeon_paper/core/utils/list_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; import '../controllers/bonds_flags_form_controller.dart'; -class BondsFlagsFormView extends GetView { +class BondsFlagsFormView extends StatelessWidget { const BondsFlagsFormView({super.key}); @override Widget build(BuildContext context) { final textTheme = Theme.of(context).textTheme; - return Obx( - () => ConfirmExitView( - dirty: controller.dirty.value, + return Consumer( + builder: (context, controller, _) => ConfirmExitView( + dirty: controller.dirty, child: Scaffold( appBar: AppBar( title: Text(tr.sessionMarks.title), centerTitle: true, ), floatingActionButton: AdvancedFloatingActionButton.extended( - onPressed: controller.save, + onPressed: () => controller.save(context), icon: const Icon(Icons.save), label: Text(tr.generic.save), ), diff --git a/lib/app/modules/Campaign/CampaignsList/bindings/campaigns_list_binding.dart b/lib/app/modules/Campaign/CampaignsList/bindings/campaigns_list_binding.dart deleted file mode 100644 index c9796b4e..00000000 --- a/lib/app/modules/Campaign/CampaignsList/bindings/campaigns_list_binding.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:get/get.dart'; - -import '../controllers/campaigns_list_controller.dart'; - -class CampaignsListBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut( - () => CampaignsListController(), - ); - } -} diff --git a/lib/app/modules/Campaign/CampaignsList/controllers/campaigns_list_controller.dart b/lib/app/modules/Campaign/CampaignsList/controllers/campaigns_list_controller.dart index 685dd64b..4768c69c 100644 --- a/lib/app/modules/Campaign/CampaignsList/controllers/campaigns_list_controller.dart +++ b/lib/app/modules/Campaign/CampaignsList/controllers/campaigns_list_controller.dart @@ -2,29 +2,32 @@ import 'dart:async'; import 'package:dungeon_paper/app/data/models/campaign.dart'; import 'package:dungeon_paper/core/storage_handler/storage_handler.dart'; -import 'package:get/get.dart'; +import 'package:flutter/widgets.dart'; -class CampaignsListController extends GetxController { +class CampaignsListController extends ChangeNotifier { StreamSubscription? _campaignsListenerSubscription; - final _campaigns = [].obs; + final _campaigns = []; + var count = 0; List get campaigns => _campaigns.toList(); - final count = 0.obs; - @override - void onInit() { - super.onInit(); + CampaignsListController() { _campaignsListenerSubscription = StorageHandler.instance .collectionListener('Campaigns', _campaignsListener); } @override - void onClose() { + void dispose() { + super.dispose(); _campaignsListenerSubscription?.cancel(); - super.onClose(); } void _campaignsListener(List data) { - _campaigns.value = data.map((e) => Campaign.fromJson(e)).toList(); + _campaigns + ..clear() + ..addAll( + data.map((e) => Campaign.fromJson(e)).toList(), + ); + notifyListeners(); } } diff --git a/lib/app/modules/Campaign/CampaignsList/views/campaigns_list_view.dart b/lib/app/modules/Campaign/CampaignsList/views/campaigns_list_view.dart index b0d6f82d..88f81a2c 100644 --- a/lib/app/modules/Campaign/CampaignsList/views/campaigns_list_view.dart +++ b/lib/app/modules/Campaign/CampaignsList/views/campaigns_list_view.dart @@ -1,11 +1,11 @@ import 'package:dungeon_paper/app/data/models/campaign.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; import '../controllers/campaigns_list_controller.dart'; -class CampaignsListView extends GetView { +class CampaignsListView extends StatelessWidget { const CampaignsListView({super.key}); @override Widget build(BuildContext context) { @@ -14,8 +14,8 @@ class CampaignsListView extends GetView { title: Text(tr.generic.myEntity(tr.entityPlural(tn(Campaign)))), centerTitle: true, ), - body: Obx( - () => controller.campaigns.isEmpty + body: Consumer( + builder: (context, controller, _) => controller.campaigns.isEmpty ? Center( child: Text(tr.generic.noEntity(tr.entityPlural(tn(Campaign)))), ) diff --git a/lib/app/modules/Campaign/bindings/campaign_binding.dart b/lib/app/modules/Campaign/bindings/campaign_binding.dart deleted file mode 100644 index 917fa93f..00000000 --- a/lib/app/modules/Campaign/bindings/campaign_binding.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:get/get.dart'; - -import '../controllers/campaign_controller.dart'; - -class CampaignBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut( - () => CampaignController(), - ); - } -} diff --git a/lib/app/modules/Campaign/controllers/campaign_controller.dart b/lib/app/modules/Campaign/controllers/campaign_controller.dart index 74d2e55b..092b08f6 100644 --- a/lib/app/modules/Campaign/controllers/campaign_controller.dart +++ b/lib/app/modules/Campaign/controllers/campaign_controller.dart @@ -1,23 +1,5 @@ -import 'package:get/get.dart'; +import 'package:flutter/material.dart'; -class CampaignController extends GetxController { - //TODO: Implement CampaignController - - final count = 0.obs; - @override - void onInit() { - super.onInit(); - } - - @override - void onReady() { - super.onReady(); - } - - @override - void onClose() { - super.onClose(); - } - - void increment() => count.value++; +class CampaignController extends ChangeNotifier { + // } diff --git a/lib/app/modules/Campaign/views/campaign_view.dart b/lib/app/modules/Campaign/views/campaign_view.dart index 85c9b59e..d97422a4 100644 --- a/lib/app/modules/Campaign/views/campaign_view.dart +++ b/lib/app/modules/Campaign/views/campaign_view.dart @@ -1,11 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:get/get.dart'; - -import '../controllers/campaign_controller.dart'; - -class CampaignView extends GetView { - const CampaignView({Key? key}) : super(key: key); +class CampaignView extends StatelessWidget { + const CampaignView({super.key}); @override Widget build(BuildContext context) { return Scaffold( diff --git a/lib/app/modules/CharacterList/bindings/character_list_binding.dart b/lib/app/modules/CharacterList/bindings/character_list_binding.dart deleted file mode 100644 index e14cdef6..00000000 --- a/lib/app/modules/CharacterList/bindings/character_list_binding.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:get/get.dart'; - -class CharacterListPageBinding extends Bindings { - @override - void dependencies() { - // - } -} diff --git a/lib/app/modules/CharacterList/views/character_list_view.dart b/lib/app/modules/CharacterList/views/character_list_view.dart index 93f83594..68006847 100644 --- a/lib/app/modules/CharacterList/views/character_list_view.dart +++ b/lib/app/modules/CharacterList/views/character_list_view.dart @@ -1,6 +1,6 @@ import 'package:dungeon_paper/app/data/models/character.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; -import 'package:dungeon_paper/app/data/services/user_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; +import 'package:dungeon_paper/app/data/services/user_provider.dart'; import 'package:dungeon_paper/app/model_utils/character_utils.dart'; import 'package:dungeon_paper/app/routes/app_pages.dart'; import 'package:dungeon_paper/app/themes/themes.dart'; @@ -13,10 +13,8 @@ import 'package:dungeon_paper/app/widgets/molecules/character_subtitle.dart'; import 'package:dungeon_paper/core/utils/builder_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -class CharacterListPageView extends GetView - with UserServiceMixin { +class CharacterListPageView extends StatelessWidget with UserProviderMixin { const CharacterListPageView({super.key}); @override Widget build(BuildContext context) { @@ -26,81 +24,78 @@ class CharacterListPageView extends GetView centerTitle: true, ), floatingActionButton: AdvancedFloatingActionButton.extended( - onPressed: () => Get.toNamed(Routes.createCharacter), + onPressed: () => Navigator.pushNamed(context, Routes.createCharacter), label: Text(tr.generic.createEntity(tr.entity(tn(Character)))), icon: const Icon(Icons.add), ), - body: Obx( - () { + body: CharacterProvider.consumer( + (context, controller, _) { final builder = ItemBuilder.lazyChildren( children: [ for (final cat in controller.charsByCategory.keys) - () => CategorizedList( - title: - Text(cat.isNotEmpty ? cat : tr.character.noCategory), - onReorder: (oldIndex, newIndex) => controller.updateAll( - CharacterUtils.reorderCharacters( - controller.charsByCategory[cat]!) - .call(oldIndex, newIndex), - ), - children: [ - for (var char in controller.charsByCategory[cat]!) - Builder( - key: Key(char.key), - builder: (context) { - final charTheme = AppThemes.getTheme( - char.getCurrentTheme(user)); - return Padding( - padding: - const EdgeInsets.symmetric(vertical: 4), - child: Card( - margin: EdgeInsets.zero, - color: charTheme.scaffoldBackgroundColor, - child: ListTileTheme.merge( - minLeadingWidth: 48, - minVerticalPadding: 16, - horizontalTitleGap: 10, - textColor: - charTheme.colorScheme.onBackground, - // textColor: ThemeData.estimateBrightnessForColor(charTheme.scaffoldBackgroundColor) == Brightness.light ? Colors.black : Colors.white, - child: InkWell( - borderRadius: borderRadius, - splashColor: - Theme.of(context).splashColor, - onTap: () { - controller.setCurrent(char.key); - Get.offAllNamed(Routes.home); - }, - child: ListTile( - leading: CharacterAvatar.squircle( - character: char, size: 48), - title: Text(char.displayName), - subtitle: CharacterSubtitle( - character: char, - textAlign: TextAlign.start, - ), - trailing: EntityEditMenu( - onEdit: null, - onDelete: () => deleteDialog.confirm( + () { + return CategorizedList( + title: Text(cat.isNotEmpty ? cat : tr.character.noCategory), + onReorder: (oldIndex, newIndex) => controller.updateAll( + CharacterUtils.reorderCharacters( + controller.charsByCategory[cat]!) + .call(oldIndex, newIndex), + ), + children: [ + for (var char in controller.charsByCategory[cat]!) + Builder( + key: Key(char.key), + builder: (context) { + final charTheme = + AppThemes.getTheme(char.getCurrentTheme(user)); + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Card( + margin: EdgeInsets.zero, + color: charTheme.scaffoldBackgroundColor, + child: ListTileTheme.merge( + minLeadingWidth: 48, + minVerticalPadding: 16, + horizontalTitleGap: 10, + textColor: charTheme.colorScheme.onBackground, + // textColor: ThemeData.estimateBrightnessForColor(charTheme.scaffoldBackgroundColor) == Brightness.light ? Colors.black : Colors.white, + child: InkWell( + borderRadius: borderRadius, + splashColor: Theme.of(context).splashColor, + onTap: () { + controller.setCurrent(char.key); + Navigator.of(context) + .popUntil((route) => route.isFirst); + }, + child: ListTile( + leading: CharacterAvatar.squircle( + character: char, size: 48), + title: Text(char.displayName), + subtitle: CharacterSubtitle( + character: char, + textAlign: TextAlign.start, + ), + trailing: EntityEditMenu( + onEdit: null, + onDelete: () { + awaitDeleteConfirmation( context, - DeleteDialogOptions( - entityName: char.displayName, - entityKind: - tr.entity(tn(Character)), - ), + char.displayName, () => controller .deleteCharacter(char), - ), - ), + ); + }, ), ), ), ), - ); - }, - ), - ], - ), + ), + ); + }, + ), + ], + ); + }, ], ); return ListView.builder( diff --git a/lib/app/modules/ClassAlignments/bindings/class_alignments_binding.dart b/lib/app/modules/ClassAlignments/bindings/class_alignments_binding.dart deleted file mode 100644 index 6b044820..00000000 --- a/lib/app/modules/ClassAlignments/bindings/class_alignments_binding.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:get/get.dart'; - -import '../controllers/class_alignments_controller.dart'; - -class ClassAlignmentsBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut( - () => ClassAlignmentsController(), - ); - } -} diff --git a/lib/app/modules/ClassAlignments/controllers/class_alignments_controller.dart b/lib/app/modules/ClassAlignments/controllers/class_alignments_controller.dart index 2c211b0b..54b5e463 100644 --- a/lib/app/modules/ClassAlignments/controllers/class_alignments_controller.dart +++ b/lib/app/modules/ClassAlignments/controllers/class_alignments_controller.dart @@ -1,38 +1,36 @@ import 'package:dungeon_paper/app/data/models/alignment.dart'; +import 'package:dungeon_paper/core/route_arguments.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -class ClassAlignmentsController extends GetxController { - final alignments = AlignmentValues.empty().obs; - final selected = Rx(null); +class ClassAlignmentsController extends ChangeNotifier { + var alignments = AlignmentValues.empty(); + dw.AlignmentType? selected; bool selectable = false; bool editable = false; late final void Function( AlignmentValues alignments, dw.AlignmentType? selected)? onChanged; final sortedAlignmentTypes = dw.AlignmentType.values.toList(); - final editing = {}.obs; - final textControllers = {}.obs; + final editing = {}; + final textControllers = {}; - @override - void onInit() { - super.onInit(); - final ClassAlignmentsArguments? args = Get.arguments; + ClassAlignmentsController(BuildContext context) { + final ClassAlignmentsArguments? args = getArgs(context, nullOk: true); if (args != null) { if (args.alignments != null) { - alignments.value = args.alignments!; + alignments = args.alignments!; } selectable = args.selectable; editable = args.editable; onChanged = args.onChanged; if (args.preselected != null) { - selected.value = args.preselected!; + selected = args.preselected!; } } textControllers.addAll({ for (final alignment in sortedAlignmentTypes) alignment: TextEditingController( - text: alignments.value.byType(alignment), + text: alignments.byType(alignment), ), }); } @@ -43,15 +41,15 @@ class ClassAlignmentsController extends GetxController { } void select(dw.AlignmentType type) { - selected.value = type; + selected = type; + notifyListeners(); } bool isEditing(dw.AlignmentType type) => editable && editing[type] == true; - bool isSelected(dw.AlignmentType type) => - selectable && selected.value == type; + bool isSelected(dw.AlignmentType type) => selectable && selected == type; - void save() { - final updated = alignments.value.copyWithInherited( + void save(BuildContext context) { + final updated = alignments.copyWithInherited( good: textControllers[dw.AlignmentType.good]!.text, lawful: textControllers[dw.AlignmentType.lawful]!.text, neutral: textControllers[dw.AlignmentType.neutral]!.text, @@ -59,8 +57,8 @@ class ClassAlignmentsController extends GetxController { evil: textControllers[dw.AlignmentType.evil]!.text, ); - onChanged?.call(updated, selected.value); - Get.back(); + onChanged?.call(updated, selected); + Navigator.of(context).pop(); } } @@ -80,3 +78,4 @@ class ClassAlignmentsArguments { this.preselected, }); } + diff --git a/lib/app/modules/ClassAlignments/views/class_alignments_view.dart b/lib/app/modules/ClassAlignments/views/class_alignments_view.dart index 5d553bfc..43c1499c 100644 --- a/lib/app/modules/ClassAlignments/views/class_alignments_view.dart +++ b/lib/app/modules/ClassAlignments/views/class_alignments_view.dart @@ -5,97 +5,101 @@ import 'package:dungeon_paper/app/widgets/atoms/advanced_floating_action_button. import 'package:dungeon_paper/app/widgets/molecules/dialog_controls.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; import '../controllers/class_alignments_controller.dart'; -class ClassAlignmentsView extends GetView { +class ClassAlignmentsView extends StatelessWidget { const ClassAlignmentsView({super.key}); @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(tr.generic.selectEntity(tr.entity(tn(AlignmentValue)))), - centerTitle: true, - ), - floatingActionButton: controller.onChanged != null - ? AdvancedFloatingActionButton.extended( - onPressed: controller.save, - label: Text(tr.generic.save), - icon: const Icon(Icons.save), - ) - : null, - body: ListView( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 0) - .copyWith(bottom: 80), - children: [ - for (final alignment in controller.sortedAlignmentTypes) - Padding( - padding: const EdgeInsets.only(bottom: 8), - child: Obx(() { - final description = - controller.alignments.value.byType(alignment); - final isEditing = controller.isEditing(alignment); - final isSelected = controller.isSelected(alignment); + return Consumer( + builder: (context, controller, _) => Scaffold( + appBar: AppBar( + title: Text(tr.generic.selectEntity(tr.entity(tn(AlignmentValue)))), + centerTitle: true, + ), + floatingActionButton: controller.onChanged != null + ? AdvancedFloatingActionButton.extended( + onPressed: () => controller.save(context), + label: Text(tr.generic.save), + icon: const Icon(Icons.save), + ) + : null, + body: ListView( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 0) + .copyWith(bottom: 80), + children: [ + for (final alignment in controller.sortedAlignmentTypes) + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Builder( + builder: (context) { + final description = controller.alignments.byType(alignment); + final isEditing = controller.isEditing(alignment); + final isSelected = controller.isSelected(alignment); - return _wrapWithSelection( - isSelected, - Card( - margin: EdgeInsets.zero, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - ListTile( - minLeadingWidth: 16, - leading: Icon(AlignmentValue.iconOf(alignment)), - title: Text(tr.alignment.name(alignment.name)), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: !isEditing - ? [ - if (controller.editable) - IconButton( - icon: const Icon(Icons.edit), - onPressed: () => controller.toggleEdit( - alignment, true), - iconSize: 16, - ), - if (controller.selectable) - ElevatedButton.icon( - icon: const Icon(Icons.check), - label: Text(!isSelected - ? tr.generic.select - : tr.generic.selected), - onPressed: !isSelected - ? () => controller.select(alignment) - : null, - ), - ] - : DialogControls.done( - context, - () => controller.toggleEdit( - alignment, false)), - ), + return _wrapWithSelection( + isSelected, + Card( + margin: EdgeInsets.zero, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ListTile( + minLeadingWidth: 16, + leading: Icon(AlignmentValue.iconOf(alignment)), + title: Text(tr.alignment.name(alignment.name)), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: !isEditing + ? [ + if (controller.editable) + IconButton( + icon: const Icon(Icons.edit), + onPressed: () => controller + .toggleEdit(alignment, true), + iconSize: 16, + ), + if (controller.selectable) + ElevatedButton.icon( + icon: const Icon(Icons.check), + label: Text(!isSelected + ? tr.generic.select + : tr.generic.selected), + onPressed: !isSelected + ? () => + controller.select(alignment) + : null, + ), + ] + : DialogControls.done( + context, + () => controller.toggleEdit( + alignment, false)), + ), + ), + Padding( + padding: const EdgeInsets.all(8) + .copyWith(left: 56, top: 0), + child: !isEditing + ? Text(description.isEmpty + ? tr.generic.noDescription + : description) + : TextField( + controller: controller + .textControllers[alignment]!, + ), + ) + ], ), - Padding( - padding: const EdgeInsets.all(8) - .copyWith(left: 56, top: 0), - child: !isEditing - ? Text(description.isEmpty - ? tr.generic.noDescription - : description) - : TextField( - controller: - controller.textControllers[alignment]!, - ), - ) - ], - ), - ), - ); - }), - ), - ], + ), + ); + }, + ), + ), + ], + ), ), ); } diff --git a/lib/app/modules/CreateCharacter/SelectMovesSpells/bindings/select_moves_spells_binding.dart b/lib/app/modules/CreateCharacter/SelectMovesSpells/bindings/select_moves_spells_binding.dart deleted file mode 100644 index 0e464294..00000000 --- a/lib/app/modules/CreateCharacter/SelectMovesSpells/bindings/select_moves_spells_binding.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:get/get.dart'; - -import '../controllers/select_moves_spells_controller.dart'; - -class SelectMovesSpellsBinding extends Bindings { - @override - void dependencies() { - Get.put( - SelectMovesSpellsController(), - ); - } -} diff --git a/lib/app/modules/CreateCharacter/SelectMovesSpells/controllers/select_moves_spells_controller.dart b/lib/app/modules/CreateCharacter/SelectMovesSpells/controllers/select_moves_spells_controller.dart index 841f9ee0..bb6f9986 100644 --- a/lib/app/modules/CreateCharacter/SelectMovesSpells/controllers/select_moves_spells_controller.dart +++ b/lib/app/modules/CreateCharacter/SelectMovesSpells/controllers/select_moves_spells_controller.dart @@ -1,29 +1,27 @@ -import 'package:dungeon_paper/app/data/models/character_class.dart'; import 'package:dungeon_paper/app/data/models/ability_scores.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; -import 'package:dungeon_paper/core/utils/string_utils.dart'; -import 'package:get/get.dart'; +import 'package:dungeon_paper/app/data/models/character_class.dart'; import 'package:dungeon_paper/app/data/models/move.dart'; import 'package:dungeon_paper/app/data/models/spell.dart'; +import 'package:dungeon_paper/core/route_arguments.dart'; +import 'package:dungeon_paper/core/utils/list_utils.dart'; +import 'package:dungeon_paper/core/utils/string_utils.dart'; +import 'package:flutter/widgets.dart'; -class SelectMovesSpellsController extends GetxController { - final dirty = false.obs; - final repo = Get.find(); +class SelectMovesSpellsController extends ChangeNotifier { + var dirty = false; - final moves = [].obs; - final spells = [].obs; - late final Rx abilityScores; - late final Rx characterClass; + var moves = []; + var spells = []; + late AbilityScores abilityScores; + late CharacterClass characterClass; late final void Function(List moves, List spells) onChanged; - @override - void onReady() { - super.onReady(); - final SelectMovesSpellsArguments args = Get.arguments; - moves.value = args.moves.toList(); - spells.value = args.spells.toList(); - abilityScores = args.abilityScores.obs; - characterClass = args.characterClass.obs; + SelectMovesSpellsController(BuildContext context) { + final SelectMovesSpellsArguments args = getArgs(context); + moves = args.moves.toList(); + spells = args.spells.toList(); + abilityScores = args.abilityScores; + characterClass = args.characterClass; onChanged = args.onChanged; } @@ -35,6 +33,42 @@ class SelectMovesSpellsController extends GetxController { : b.category == MoveCategory.basic ? 1 : 0); + + void addMoves(Iterable added) { + moves = addByKey(moves, added); + dirty = true; + notifyListeners(); + } + + void updateMove(Move move) { + moves = updateByKey(moves, [move]); + dirty = true; + notifyListeners(); + } + + void deleteMove(Move move) { + moves = removeByKey(moves, [move]); + dirty = true; + notifyListeners(); + } + + void addSpells(Iterable added) { + spells = addByKey(spells, added); + dirty = true; + notifyListeners(); + } + + void updateSpell(Spell spell) { + spells = updateByKey(spells, [spell]); + dirty = true; + notifyListeners(); + } + + void deleteSpell(Spell spell) { + spells = removeByKey(spells, [spell]); + dirty = true; + notifyListeners(); + } } class SelectMovesSpellsArguments { @@ -52,3 +86,4 @@ class SelectMovesSpellsArguments { required this.characterClass, }); } + diff --git a/lib/app/modules/CreateCharacter/SelectMovesSpells/views/select_moves_spells_view.dart b/lib/app/modules/CreateCharacter/SelectMovesSpells/views/select_moves_spells_view.dart index 3a68ae09..8c14572f 100644 --- a/lib/app/modules/CreateCharacter/SelectMovesSpells/views/select_moves_spells_view.dart +++ b/lib/app/modules/CreateCharacter/SelectMovesSpells/views/select_moves_spells_view.dart @@ -8,14 +8,13 @@ import 'package:dungeon_paper/app/widgets/atoms/confirm_exit_view.dart'; import 'package:dungeon_paper/app/widgets/cards/move_card.dart'; import 'package:dungeon_paper/app/widgets/cards/spell_card.dart'; import 'package:dungeon_paper/app/widgets/menus/entity_edit_menu.dart'; -import 'package:dungeon_paper/core/utils/list_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; import '../controllers/select_moves_spells_controller.dart'; -class SelectMovesSpellsView extends GetView { +class SelectMovesSpellsView extends StatelessWidget { const SelectMovesSpellsView({ super.key, }); @@ -23,38 +22,38 @@ class SelectMovesSpellsView extends GetView { @override Widget build(BuildContext context) { var titleStyle = Theme.of(context).textTheme.titleLarge; - return ConfirmExitView( - dirty: controller.dirty.value, - child: Scaffold( - appBar: AppBar( - title: Text( - tr.generic.selectEntity(tr.createCharacter.movesSpells.title)), - centerTitle: true, - ), - floatingActionButton: AdvancedFloatingActionButton.extended( - onPressed: _save, - label: Text(tr.generic.save), - icon: const Icon(Icons.save), - ), - body: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // MOVES TITLE - Obx(() => Padding( - padding: - const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Text( - tr.entityCountNum( - tn(Move), - controller.moves.length, - ), - style: titleStyle), - )), - // MOVES CARDS - Obx( - () => ListView( + return Consumer( + builder: (context, controller, _) => ConfirmExitView( + dirty: controller.dirty, + child: Scaffold( + appBar: AppBar( + title: Text( + tr.generic.selectEntity(tr.createCharacter.movesSpells.title)), + centerTitle: true, + ), + floatingActionButton: AdvancedFloatingActionButton.extended( + onPressed: () => _save(context), + label: Text(tr.generic.save), + icon: const Icon(Icons.save), + ), + body: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // MOVES TITLE + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Text( + tr.entityCountNum( + tn(Move), + controller.moves.length, + ), + style: titleStyle), + ), + // MOVES CARDS + ListView( shrinkWrap: true, padding: const EdgeInsets.all(8), physics: const NeverScrollableScrollPhysics(), @@ -68,63 +67,60 @@ class SelectMovesSpellsView extends GetView { actions: [ EntityEditMenu( onEdit: () => ModelPages.openMovePage( - abilityScores: - controller.abilityScores.value, + context, + abilityScores: controller.abilityScores, move: move, - onSave: (move) => controller.moves.value = - updateByKey(controller.moves, [move]), + onSave: controller.updateMove, ), - onDelete: () => controller.moves.value = - removeByKey(controller.moves, [move]), + onDelete: () => controller.deleteMove(move), ), ], ), )) .toList(), ), - ), - // ADD MOVES - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: SizedBox( - height: 48, - child: OutlinedButton.icon( - style: ButtonThemes.primaryOutlined(context), - onPressed: () => ModelPages.openMovesList( - character: Character.empty().copyWith( - characterClass: controller.characterClass.value, + // ADD MOVES + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: SizedBox( + height: 48, + child: OutlinedButton.icon( + style: ButtonThemes.primaryOutlined(context), + onPressed: () => ModelPages.openMovesList( + context, + character: Character.empty().copyWith( + characterClass: controller.characterClass, + ), + preSelections: controller.moves, + category: MoveCategory.advanced1, + onSelected: (moves) { + controller.addMoves( + moves.map( + (m) => m.copyWithInherited(favorite: true), + ), + ); + }, ), - preSelections: controller.moves, - category: MoveCategory.advanced1, - onSelected: (moves) { - controller.dirty.value = true; - controller.moves.value = addByKey( - controller.moves, - moves.map((m) => m.copyWithInherited(favorite: true)), - ); - }, + label: + Text(tr.generic.addEntity(tr.entityPlural(tn(Move)))), + icon: const Icon(Icons.add), ), - label: - Text(tr.generic.addEntity(tr.entityPlural(tn(Move)))), - icon: const Icon(Icons.add), ), ), - ), - // SPELLS TITLE - Obx(() => Padding( - padding: - const EdgeInsets.symmetric(horizontal: 16, vertical: 8) - .copyWith(top: 24), - child: Text( - tr.entityCount( - tn(Spell), - controller.spells.length, - ), - style: titleStyle), - )), - // SPELL CARDS - Obx( - () => ListView( + // SPELLS TITLE + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 8) + .copyWith(top: 24), + child: Text( + tr.entityCount( + tn(Spell), + controller.spells.length, + ), + style: titleStyle), + ), + // SPELL CARDS + ListView( shrinkWrap: true, padding: const EdgeInsets.all(8), physics: const NeverScrollableScrollPhysics(), @@ -139,8 +135,7 @@ class SelectMovesSpellsView extends GetView { ElevatedButton.icon( style: ButtonThemes.primaryElevated(context), onPressed: () { - controller.spells.value = - removeByKey(controller.spells, [spell]); + controller.deleteSpell(spell); }, label: Text(tr.generic.remove), icon: const Icon(Icons.remove), @@ -150,44 +145,50 @@ class SelectMovesSpellsView extends GetView { )) .toList(), ), - ), - // ADD SPELLS - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: SizedBox( - height: 48, - child: OutlinedButton.icon( - style: ButtonThemes.primaryOutlined(context), - onPressed: () => ModelPages.openSpellsList( - character: Character.empty().copyWith( - characterClass: controller.characterClass.value, + // ADD SPELLS + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: SizedBox( + height: 48, + child: OutlinedButton.icon( + style: ButtonThemes.primaryOutlined(context), + onPressed: () => ModelPages.openSpellsList( + context, + character: Character.empty().copyWith( + characterClass: controller.characterClass, + ), + list: controller.spells, + onSelected: (spells) { + controller.addSpells( + spells.map( + (m) => m.copyWithInherited(prepared: true), + ), + ); + }, ), - list: controller.spells, - onSelected: (spells) { - controller.dirty.value = true; - controller.spells.value = addByKey( - controller.spells, - spells - .map((m) => m.copyWithInherited(prepared: true)), - ); - }, + label: Text( + tr.generic.addEntity(tr.entityPlural(tn(Spell))), + ), + icon: const Icon(Icons.add), ), - label: - Text(tr.generic.addEntity(tr.entityPlural(tn(Spell)))), - icon: const Icon(Icons.add), ), ), - ), - const SizedBox(height: 80), - ], + const SizedBox(height: 80), + ], + ), ), ), ), ); } - _save() { + _save(BuildContext context) { + final controller = Provider.of( + context, + listen: false, + ); controller.onChanged(controller.moves, controller.spells); - Get.back(); + Navigator.of(context).pop(); } } + diff --git a/lib/app/modules/CreateCharacter/bindings/create_character_binding.dart b/lib/app/modules/CreateCharacter/bindings/create_character_binding.dart deleted file mode 100644 index 195fa1c9..00000000 --- a/lib/app/modules/CreateCharacter/bindings/create_character_binding.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:get/get.dart'; - -import '../controllers/create_character_controller.dart'; - -class CreateCharacterBinding extends Bindings { - @override - void dependencies() { - Get.put( - CreateCharacterController(), - ); - } -} diff --git a/lib/app/modules/CreateCharacter/controllers/create_character_controller.dart b/lib/app/modules/CreateCharacter/controllers/create_character_controller.dart index 6274962b..ef1e0a70 100644 --- a/lib/app/modules/CreateCharacter/controllers/create_character_controller.dart +++ b/lib/app/modules/CreateCharacter/controllers/create_character_controller.dart @@ -1,3 +1,4 @@ +import 'package:dungeon_paper/app/data/models/ability_scores.dart'; import 'package:dungeon_paper/app/data/models/alignment.dart'; import 'package:dungeon_paper/app/data/models/bio.dart'; import 'package:dungeon_paper/app/data/models/character.dart'; @@ -7,40 +8,40 @@ import 'package:dungeon_paper/app/data/models/gear_choice.dart'; import 'package:dungeon_paper/app/data/models/gear_selection.dart'; import 'package:dungeon_paper/app/data/models/item.dart'; import 'package:dungeon_paper/app/data/models/move.dart'; -import 'package:dungeon_paper/app/data/models/ability_scores.dart'; import 'package:dungeon_paper/app/data/models/race.dart'; import 'package:dungeon_paper/app/data/models/session_marks.dart'; import 'package:dungeon_paper/app/data/models/spell.dart'; -import 'package:dungeon_paper/app/data/models/user.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; -import 'package:dungeon_paper/app/data/services/user_service.dart'; +import 'package:dungeon_paper/app/data/services/repository_provider.dart'; import 'package:dungeon_paper/core/utils/uuid.dart'; -import 'package:get/get.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; +import 'package:flutter/widgets.dart'; +import 'package:provider/provider.dart'; -class CreateCharacterController extends GetxController { - final name = ''.obs; - final avatarUrl = ''.obs; - final characterClass = Rx(null); - final abilityScores = AbilityScores.dungeonWorld( - dex: 10, str: 10, wis: 10, con: 10, intl: 10, cha: 10) - .obs; - final startingGear = [].obs; - final moves = [].obs; - final spells = [].obs; - final alignment = Rx(null); - final race = Rx(null); +class CreateCharacterController extends ChangeNotifier with RepositoryProviderMixin { + var name = ''; + var avatarUrl = ''; + CharacterClass? characterClass; + var abilityScores = AbilityScores.dungeonWorldAll(10); + var startingGear = []; + var moves = []; + var spells = []; + AlignmentValue? alignment; + Race? race; - final repo = Get.find(); - final dirty = false.obs; + var dirty = false; - User get user => Get.find().current; + static CreateCharacterController of(BuildContext context, {bool listen = false}) => + Provider.of(context, listen: listen); + static Widget consumer( + Widget Function(BuildContext, CreateCharacterController, Widget?) builder, + ) => + Consumer(builder: builder); bool get isValid => [ name.isNotEmpty, - characterClass.value != null, - alignment.value != null, - race.value != null, + characterClass != null, + alignment != null, + race != null, ].every((element) => element == true); List get items => @@ -49,23 +50,23 @@ class CreateCharacterController extends GetxController { double get coins => GearChoice.selectionToCoins(startingGear); void setBasicInfo(String name, String avatar) { - this.name.value = name; - avatarUrl.value = avatar; + this.name = name; + avatarUrl = avatar; setDirty(); } - void setClass(CharacterClass cls) { - characterClass.value = cls; + void setClass(BuildContext context, CharacterClass cls) { + characterClass = cls; setStartingGear( cls.gearChoices .fold([], (all, cur) => [...all, ...cur.preselectedGearSelections]), ); - addStartingMoves(); + addStartingMoves(context); setDirty(); } void setAbilityScores(AbilityScores stats) { - abilityScores.value = stats; + abilityScores = stats; setDirty(); } @@ -73,13 +74,13 @@ class CreateCharacterController extends GetxController { if (selected == null) { return; } - alignment.value = AlignmentValue.empty(type: selected).copyWith( + alignment = AlignmentValue.empty(type: selected).copyWith( description: alignments.byType(selected), ); setDirty(); } - void setMovesSpells(List moves, List spells) { + void setMovesAndSpells(List moves, List spells) { this.moves.clear(); this.spells.clear(); this.moves.addAll(moves.map((e) => e.copyWithInherited(favorite: true))); @@ -87,7 +88,7 @@ class CreateCharacterController extends GetxController { } void setDirty() { - dirty.value = true; + dirty = true; } void setStartingGear(List selections) { @@ -95,13 +96,12 @@ class CreateCharacterController extends GetxController { startingGear.addAll(selections); } - void addStartingMoves() { + void addStartingMoves(BuildContext context) { moves.clear(); moves.addAll( [...repo.builtIn.moves.values, ...repo.my.moves.values] - .where((m) => - (m.classKeys.contains(characterClass.value!.reference) && - m.category == MoveCategory.starting)) + .where((m) => (m.classKeys.contains(characterClass!.reference) && + m.category == MoveCategory.starting)) .map( // favorite: move.category != MoveCategory.basic (move) => Move.fromDwMove(move, favorite: true), @@ -111,22 +111,22 @@ class CreateCharacterController extends GetxController { } Character getAsCharacter() => Character.empty().copyWith( - displayName: name.value, - avatarUrl: avatarUrl.value, - characterClass: characterClass.value, - abilityScores: abilityScores.value, + displayName: name, + avatarUrl: avatarUrl, + characterClass: characterClass, + abilityScores: abilityScores, moves: moves, spells: spells, items: items, coins: coins, - race: race.value, + race: race, stats: CharacterStats( level: 1, - currentHp: characterClass.value!.hp + abilityScores.value.conMod!, + currentHp: characterClass!.hp + abilityScores.conMod!, currentXp: 0, ), sessionMarks: [ - ...(characterClass.value?.bonds + ...(characterClass?.bonds .map((bond) => SessionMark.bond( description: bond, completed: false, key: uuid())) .toList() ?? @@ -136,7 +136,7 @@ class CreateCharacterController extends GetxController { bio: Bio( looks: '', description: '', - alignment: alignment.value ?? AlignmentValue.empty(), + alignment: alignment ?? AlignmentValue.empty(), ), ); } diff --git a/lib/app/modules/CreateCharacter/views/create_character_view.dart b/lib/app/modules/CreateCharacter/views/create_character_view.dart index 1e109cf0..6b95a04b 100644 --- a/lib/app/modules/CreateCharacter/views/create_character_view.dart +++ b/lib/app/modules/CreateCharacter/views/create_character_view.dart @@ -7,7 +7,7 @@ import 'package:dungeon_paper/app/data/models/character_class.dart'; import 'package:dungeon_paper/app/data/models/gear_selection.dart'; import 'package:dungeon_paper/app/data/models/move.dart'; import 'package:dungeon_paper/app/data/models/race.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; import 'package:dungeon_paper/app/model_utils/model_pages.dart'; import 'package:dungeon_paper/app/modules/AbilityScoresForm/controllers/ability_scores_form_controller.dart'; import 'package:dungeon_paper/app/modules/BasicInfoForm/controllers/basic_info_form_controller.dart'; @@ -22,41 +22,40 @@ import 'package:dungeon_paper/app/widgets/atoms/character_avatar.dart'; import 'package:dungeon_paper/app/widgets/atoms/confirm_exit_view.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:intl/intl.dart'; import '../../../../core/dw_icons.dart'; import '../../../widgets/chips/advanced_chip.dart'; import '../controllers/create_character_controller.dart'; -class CreateCharacterView extends GetView { +class CreateCharacterView extends StatelessWidget with CharacterProviderMixin { const CreateCharacterView({super.key}); - CharacterClass? get cls => controller.characterClass.value; @override Widget build(BuildContext context) { - return Obx( - () => ConfirmExitView( - dirty: controller.dirty.value, - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), - // blendMode: BlendMode.darken, - child: Scaffold( - backgroundColor: Colors.black.withOpacity(0.85), - appBar: AppBar( - title: Container(), - backgroundColor: Colors.transparent, - foregroundColor: Colors.white, - ), - floatingActionButton: Obx( - () => AdvancedFloatingActionButton.extended( + return CreateCharacterController.consumer( + (context, controller, _) { + final cls = controller.characterClass; + return ConfirmExitView( + dirty: controller.dirty, + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), + // blendMode: BlendMode.darken, + child: Scaffold( + backgroundColor: Colors.black.withOpacity(0.85), + appBar: AppBar( + title: Container(), + backgroundColor: Colors.transparent, + foregroundColor: Colors.white, + ), + floatingActionButton: AdvancedFloatingActionButton.extended( onPressed: controller.isValid ? () { - Get.find().createCharacter( + charProvider.createCharacter( controller.getAsCharacter(), switchToCharacter: true, ); - Get.back(); + Navigator.of(context).pop(); } : null, icon: const Icon(Icons.person_add), @@ -64,266 +63,267 @@ class CreateCharacterView extends GetView { tr.generic.createEntity(tr.entity(tn(Character))), ), ), - ), - body: Center( - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.only(bottom: 80), - child: SizedBox( - width: 340, - child: Card( - child: Padding( - padding: const EdgeInsets.all(8), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Basic Info - _Card( - leading: CharacterAvatar.squircle( - size: 48, - character: Character.empty().copyWith( - avatarUrl: controller.avatarUrl.value, + body: Center( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.only(bottom: 80), + child: SizedBox( + width: 340, + child: Card( + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Basic Info + _Card( + leading: CharacterAvatar.squircle( + size: 48, + character: Character.empty().copyWith( + avatarUrl: controller.avatarUrl, + ), ), - ), - title: controller.name.isEmpty - ? Text( - tr.createCharacter.basicInfo.defaultName, - ) - : Text(controller.name.value), - subtitle: controller.name.isEmpty - ? Text(tr.createCharacter.basicInfo.helpText) - : Text( - tr.createCharacter.basicInfo.description( - cls?.name ?? '', - ), - ), - valid: controller.name.isNotEmpty, - onTap: () => Get.toNamed( - Routes.createCharacterBasicInfo, - arguments: BasicInfoFormArguments( - avatarUrl: controller.avatarUrl.value, - name: controller.name.value, - onChanged: controller.setBasicInfo, - ), - preventDuplicates: false, - ), - ), - // Class - _Card( - title: cls == null - ? Text(tr.generic.selectEntity( - tr.entity(tn(CharacterClass)))) - : Text(cls!.name), - subtitle: cls == null - ? Text(tr.createCharacter.characterClass - .noSelection) - : Text( - tr.createCharacter.characterClass - .description( - cls!.hp, - cls!.load, - cls!.damageDice.toString(), - ), - ), - valid: cls != null, - onTap: () => Get.toNamed( - Routes.createCharacterSelectClass, - arguments: CharacterClassLibraryListArguments( - preSelections: - controller.characterClass.value != null - ? [controller.characterClass.value!] - : [], - onSelected: (cls) => controller.setClass(cls), - ), - preventDuplicates: false, - ), - ), - // Race - _Card( - title: controller.race.value == null - ? Text(tr.generic - .selectEntity(tr.entity(tn(Race)))) - : Text(controller.race.value!.name), - subtitle: controller.race.value == null - ? Text(tr.generic - .noEntitySelected(tr.entity(tn(Race)))) - : Text( - controller.race.value!.description, - overflow: TextOverflow.ellipsis, - ), - onTap: cls != null - ? () => ModelPages.openRacesList( - character: controller.getAsCharacter(), - preSelection: controller.race.value, - onSelected: (race) => - controller.race.value = race, + title: controller.name.isEmpty + ? Text( + tr.createCharacter.basicInfo + .defaultName, ) - : null, - valid: controller.race.value != null, - ), - // Ability Scores - _Card( - contentPadding: const EdgeInsets.symmetric( - horizontal: 16, vertical: 8), - title: Text(tr.generic.selectEntity( - tr.entityPlural(tn(AbilityScore)))), - // subtitle: Text( - // controller.abilityScores.value.stats - // .map((stat) => '${stat.key}: ${stat.value}') - // .join(', '), - subtitle: Padding( - padding: const EdgeInsets.only(top: 4.0), - child: _AbilityScoreChipList( - controller: controller), - ), - onTap: () => Get.toNamed( - Routes.createCharacterAbilityScores, - arguments: AbilityScoresFormArguments( - onChanged: (abilityScores) => controller - .setAbilityScores(abilityScores), - abilityScores: controller.abilityScores.value, - ), - preventDuplicates: false, - ), - ), - // Alignment - _Card( - contentPadding: const EdgeInsets.symmetric( - horizontal: 16, vertical: 0), - valid: controller.alignment.value != null, - title: Text( - controller.alignment.value != null - ? [ - tr.entity(tn(AlignmentValue)), - tr.alignment.name(controller - .alignment.value!.type.name) - ].join(': ') - : tr.generic.selectEntity( - tr.entity(tn(AlignmentValue)), - ), - ), - subtitle: controller.alignment.value != null - ? Text( - controller.alignment.value!.description - .isNotEmpty - ? controller - .alignment.value!.description - : tr.generic.noDescription, - overflow: TextOverflow.ellipsis, - maxLines: 1, - ) - : Text( - tr.generic.noEntitySelectedRequired( - tr.entity(tn(AlignmentValue))), - ), - onTap: cls != null - ? () => Get.toNamed( - Routes.classAlignments, - arguments: ClassAlignmentsArguments( - onChanged: controller.setAlignment, - alignments: controller - .characterClass.value!.alignments, - preselected: - controller.alignment.value?.type, - selectable: true, - editable: true, + : Text(controller.name), + subtitle: controller.name.isEmpty + ? Text( + tr.createCharacter.basicInfo.helpText) + : Text( + tr.createCharacter.basicInfo + .description( + cls?.name ?? '', ), - preventDuplicates: false, + ), + valid: controller.name.isNotEmpty, + onTap: () => Navigator.of(context).pushNamed( + Routes.createCharacterBasicInfo, + arguments: BasicInfoFormArguments( + avatarUrl: controller.avatarUrl, + name: controller.name, + onChanged: controller.setBasicInfo, + ), + ), + ), + // Class + _Card( + title: cls == null + ? Text(tr.generic.selectEntity( + tr.entity(tn(CharacterClass)))) + : Text(cls.name), + subtitle: cls == null + ? Text(tr.createCharacter.characterClass + .noSelection) + : Text( + tr.createCharacter.characterClass + .description( + cls.hp, + cls.load, + cls.damageDice.toString(), + ), + ), + valid: cls != null, + onTap: () => Navigator.of(context).pushNamed( + Routes.createCharacterSelectClass, + arguments: CharacterClassLibraryListArguments( + preSelections: + controller.characterClass != null + ? [controller.characterClass!] + : [], + onSelected: (cls) => + controller.setClass(context, cls), + ), + ), + ), + // Race + _Card( + title: controller.race == null + ? Text(tr.generic + .selectEntity(tr.entity(tn(Race)))) + : Text(controller.race!.name), + subtitle: controller.race == null + ? Text(tr.generic + .noEntitySelected(tr.entity(tn(Race)))) + : Text( + controller.race!.description, + overflow: TextOverflow.ellipsis, + ), + onTap: cls != null + ? () => ModelPages.openRacesList( + context, + character: + controller.getAsCharacter(), + preSelection: controller.race, + onSelected: (race) => + controller.race = race, + ) + : null, + valid: controller.race != null, + ), + // Ability Scores + _Card( + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 8), + title: Text(tr.generic.selectEntity( + tr.entityPlural(tn(AbilityScore)))), + // subtitle: Text( + // controller.abilityScores.stats + // .map((stat) => '${stat.key}: ${stat}') + // .join(', '), + subtitle: Padding( + padding: const EdgeInsets.only(top: 4.0), + child: _AbilityScoreChipList( + controller: controller), + ), + onTap: () => Navigator.of(context).pushNamed( + Routes.createCharacterAbilityScores, + arguments: AbilityScoresFormArguments( + onChanged: (abilityScores) => controller + .setAbilityScores(abilityScores), + abilityScores: controller.abilityScores, + ), + ), + ), + // Alignment + _Card( + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 0), + valid: controller.alignment != null, + title: Text( + controller.alignment != null + ? [ + tr.entity(tn(AlignmentValue)), + tr.alignment.name( + controller.alignment!.type.name) + ].join(': ') + : tr.generic.selectEntity( + tr.entity(tn(AlignmentValue)), + ), + ), + subtitle: controller.alignment != null + ? Text( + controller.alignment!.description + .isNotEmpty + ? controller.alignment!.description + : tr.generic.noDescription, + overflow: TextOverflow.ellipsis, + maxLines: 1, ) - : null, - ), + : Text( + tr.generic.noEntitySelectedRequired( + tr.entity(tn(AlignmentValue))), + ), + onTap: cls != null + ? () => Navigator.of(context).pushNamed( + Routes.classAlignments, + arguments: ClassAlignmentsArguments( + onChanged: controller.setAlignment, + alignments: controller + .characterClass!.alignments, + preselected: + controller.alignment?.type, + selectable: true, + editable: true, + ), + ) + : null, + ), - // Starting Gear - _Card( - contentPadding: const EdgeInsets.symmetric( - horizontal: 16, vertical: 8), - title: Text( - tr.generic.selectEntity( - tr.entity(tn(GearSelection)), + // Starting Gear + _Card( + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 8), + title: Text( + tr.generic.selectEntity( + tr.entity(tn(GearSelection)), + ), ), - ), - subtitle: Text(controller.items.isEmpty && - controller.coins == 0 - ? tr.createCharacter.startingGear.helpText - : [ - controller.coins > 0 - ? tr.createCharacter.startingGear - .coins( - NumberFormat('#0.#') - .format(controller.coins), - ) - : null, - controller.items - .map((i) => tr - .createCharacter.startingGear - .item( + subtitle: Text(controller.items.isEmpty && + controller.coins == 0 + ? tr.createCharacter.startingGear.helpText + : [ + controller.coins > 0 + ? tr.createCharacter.startingGear + .coins( NumberFormat('#0.#') - .format(i.amount), - i.name, - )) - .join(', '), - ].whereType().join(', ')), - onTap: cls != null - ? () => Get.toNamed( - Routes.createCharacterStartingGear, - arguments: StartingGearFormArguments( - onChanged: controller.setStartingGear, - selectedOptions: - controller.startingGear, - characterClass: cls!, + .format(controller.coins), + ) + : null, + controller.items + .map((i) => tr.createCharacter + .startingGear + .item( + NumberFormat('#0.#') + .format(i.amount), + i.name, + )) + .join(', '), + ].whereType().join(', ')), + onTap: cls != null + ? () => Navigator.of(context).pushNamed( + Routes.createCharacterStartingGear, + arguments: StartingGearFormArguments( + onChanged: + controller.setStartingGear, + selectedOptions: + controller.startingGear, + characterClass: cls, + ), + ) + : null, + valid: cls == null + ? false + : cls.gearChoices.every( + (c) => c.selections.any( + (s) => controller.startingGear + .map((x) => x.key) + .contains(s.key), ), - preventDuplicates: false, - ) - : null, - valid: cls == null - ? false - : cls!.gearChoices.every( - (c) => c.selections.any( - (s) => controller.startingGear - .map((x) => x.key) - .contains(s.key), ), - ), - ), - // Moves & Spells - _Card( - // contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), - title: Text( - tr.generic.selectEntity( - (cls?.isSpellcaster ?? false) - ? tr.createCharacter.movesSpells.title - : tr.entityPlural(tn(Move)), + ), + // Moves & Spells + _Card( + // contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), + title: Text( + tr.generic.selectEntity( + (cls?.isSpellcaster ?? false) + ? tr.createCharacter.movesSpells.title + : tr.entityPlural(tn(Move)), + ), ), - ), - subtitle: Text( - (cls?.isSpellcaster ?? false) - ? tr.createCharacter.movesSpells - .description( - controller.moves.length, - controller.spells.length, - ) - : tr.entityCountNum( - tn(Move), - controller.moves.length, - ), - ), - onTap: cls != null - ? () => Get.toNamed( - Routes.createCharacterMovesSpells, - arguments: SelectMovesSpellsArguments( - onChanged: controller.setMovesSpells, - moves: controller.moves, - spells: controller.spells, - abilityScores: - controller.abilityScores.value, - characterClass: - controller.characterClass.value!, + subtitle: Text( + (cls?.isSpellcaster ?? false) + ? tr.createCharacter.movesSpells + .description( + controller.moves.length, + controller.spells.length, + ) + : tr.entityCountNum( + tn(Move), + controller.moves.length, ), - preventDuplicates: false, - ) - : null, - ), - ], + ), + onTap: cls != null + ? () => Navigator.of(context).pushNamed( + Routes.createCharacterMovesSpells, + arguments: SelectMovesSpellsArguments( + onChanged: + controller.setMovesAndSpells, + moves: controller.moves, + spells: controller.spells, + abilityScores: + controller.abilityScores, + characterClass: + controller.characterClass!, + ), + ) + : null, + ), + ], + ), ), ), ), @@ -332,8 +332,8 @@ class CreateCharacterView extends GetView { ), ), ), - ), - ), + ); + }, ); } } @@ -350,7 +350,7 @@ class _AbilityScoreChipList extends StatelessWidget { return Wrap( spacing: 2, runSpacing: 2, - children: controller.abilityScores.value.stats + children: controller.abilityScores.stats .map( (stat) => ConstrainedBox( constraints: const BoxConstraints( @@ -371,7 +371,7 @@ class _AbilityScoreChipList extends StatelessWidget { materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, label: Text( '${stat.key}: ${stat.value}', - textScaleFactor: 0.8, + textScaler: const TextScaler.linear(0.8), ), ), ), @@ -437,3 +437,4 @@ class _Card extends StatelessWidget { ); } } + diff --git a/lib/app/modules/Home/bindings/home_binding.dart b/lib/app/modules/Home/bindings/home_binding.dart deleted file mode 100644 index 7f33acbb..00000000 --- a/lib/app/modules/Home/bindings/home_binding.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:get/get.dart'; - -class HomeBinding extends Bindings { - @override - void dependencies() { - // - } -} diff --git a/lib/app/modules/Home/views/expanded_card_dialog_view.dart b/lib/app/modules/Home/views/expanded_card_dialog_view.dart index 748d3d35..c36b453a 100644 --- a/lib/app/modules/Home/views/expanded_card_dialog_view.dart +++ b/lib/app/modules/Home/views/expanded_card_dialog_view.dart @@ -1,13 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:get/get.dart'; - -class ExpandedCardDialogView extends GetView { +class ExpandedCardDialogView extends StatelessWidget { const ExpandedCardDialogView({ - Key? key, + super.key, required this.builder, required this.heroTag, - }) : super(key: key); + }); final Widget Function(BuildContext context) builder; final String? heroTag; @@ -16,7 +14,7 @@ class ExpandedCardDialogView extends GetView { Widget build(BuildContext context) { return SafeArea( child: GestureDetector( - onTap: () => Get.back(), + onTap: () => Navigator.of(context).pop(), child: Container( // color: Colors.black.withOpacity(0.75), padding: const EdgeInsets.all(32), diff --git a/lib/app/modules/Home/views/home_app_bar.dart b/lib/app/modules/Home/views/home_app_bar.dart index 4bfadc67..c6eff01c 100644 --- a/lib/app/modules/Home/views/home_app_bar.dart +++ b/lib/app/modules/Home/views/home_app_bar.dart @@ -1,13 +1,12 @@ -import 'package:dungeon_paper/app/data/services/loading_service.dart'; -import 'package:dungeon_paper/app/data/services/user_service.dart'; +import 'package:dungeon_paper/app/data/services/loading_provider.dart'; +import 'package:dungeon_paper/app/data/services/user_provider.dart'; import 'package:dungeon_paper/app/routes/app_pages.dart'; import 'package:dungeon_paper/app/widgets/atoms/user_menu.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; class HomeAppBar extends StatelessWidget - with LoadingServiceMixin, UserServiceMixin + with LoadingProviderMixin, UserProviderMixin implements PreferredSizeWidget { const HomeAppBar({super.key}); @@ -18,7 +17,8 @@ class HomeAppBar extends StatelessWidget automaticallyImplyLeading: false, leading: IconButton( icon: const Icon(Icons.search), - onPressed: () => Get.toNamed(Routes.universalSearch), + onPressed: () => + Navigator.of(context).pushNamed(Routes.universalSearch), ), actions: const [ // if (user.flags['su'] == true) @@ -37,4 +37,3 @@ class HomeAppBar extends StatelessWidget @override Size get preferredSize => const Size.fromHeight(64); } - diff --git a/lib/app/modules/Home/views/home_character_actions_view.dart b/lib/app/modules/Home/views/home_character_actions_view.dart index b1d74e19..46f02cb1 100644 --- a/lib/app/modules/Home/views/home_character_actions_view.dart +++ b/lib/app/modules/Home/views/home_character_actions_view.dart @@ -5,9 +5,8 @@ import 'package:dungeon_paper/app/data/models/meta.dart'; import 'package:dungeon_paper/app/data/models/move.dart'; import 'package:dungeon_paper/app/data/models/race.dart'; import 'package:dungeon_paper/app/data/models/spell.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; -import 'package:dungeon_paper/app/data/services/library_service.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; +import 'package:dungeon_paper/app/data/services/library_provider.dart'; import 'package:dungeon_paper/app/model_utils/character_utils.dart'; import 'package:dungeon_paper/app/model_utils/model_pages.dart'; import 'package:dungeon_paper/app/modules/LibraryList/controllers/library_list_controller.dart'; @@ -30,41 +29,23 @@ import 'package:dungeon_paper/core/utils/builder_utils.dart'; import 'package:dungeon_paper/core/utils/list_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'local_widgets/home_character_actions_summary.dart'; -class HomeCharacterActionsView extends GetView { +class HomeCharacterActionsView extends StatelessWidget + with CharacterProviderMixin { const HomeCharacterActionsView({super.key}); - Character get char => controller.current; - @override Widget build(BuildContext context) { - final builder = ItemBuilder.builder( - leadingBuilder: (context, index) => const HomeCharacterActionsSummary(), - leadingCount: 1, - itemBuilder: (context, index) { - switch (char.actionCategories.elementAt(index)) { - case 'Move': - return movesList ?? const SizedBox.shrink(); - case 'Spell': - return spellsList ?? const SizedBox.shrink(); - case 'Item': - return itemsList ?? const SizedBox.shrink(); - } - - return const SizedBox.shrink(); - }, - itemCount: char.actionCategories.length, - ); return PageStorage( bucket: PageStorageBucket(), - child: Obx( - () { + child: CharacterProvider.consumer( + (context, controller, _) { if (controller.maybeCurrent == null) { return Container(); } + final builder = _getBuilder(controller); return builder.asListView( padding: const EdgeInsets.only(bottom: 16), ); @@ -73,7 +54,28 @@ class HomeCharacterActionsView extends GetView { ); } - Widget? get movesList { + ItemBuilder _getBuilder(CharacterProvider ctrl) { + final char = ctrl.current; + return ItemBuilder.builder( + leadingBuilder: (context, index) => const HomeCharacterActionsSummary(), + leadingCount: 1, + itemCount: char.actionCategories.length, + itemBuilder: (context, index) { + switch (char.actionCategories.elementAt(index)) { + case 'Move': + return movesList(context, ctrl) ?? const SizedBox.shrink(); + case 'Spell': + return spellsList(context, ctrl) ?? const SizedBox.shrink(); + case 'Item': + return itemsList(context, ctrl) ?? const SizedBox.shrink(); + } + + return const SizedBox.shrink(); + }, + ); + } + + Widget? movesList(BuildContext context, CharacterProvider controller) { if (char.settings.actionCategories.hidden.contains('Move')) { return null; } @@ -86,6 +88,7 @@ class HomeCharacterActionsView extends GetView { EntityEditMenu( onDelete: null, onEdit: () => ModelPages.openRacePage( + context, race: char.race, abilityScores: char.abilityScores, onSave: (race) => controller.updateCharacter( @@ -106,7 +109,7 @@ class HomeCharacterActionsView extends GetView { children: [ Expanded( child: ElevatedButton( - onPressed: _openBasicMoves, + onPressed: () => _openBasicMoves(context), child: Text( tr.actions.moves.basic, ), @@ -115,7 +118,7 @@ class HomeCharacterActionsView extends GetView { const SizedBox(width: 16), Expanded( child: ElevatedButton( - onPressed: _openSpecialMoves, + onPressed: () => _openSpecialMoves(context), child: Text( tr.actions.moves.special, ), @@ -170,6 +173,7 @@ class HomeCharacterActionsView extends GetView { EntityEditMenu( onDelete: onDelete, onEdit: () => ModelPages.openMovePage( + context, move: move, abilityScores: char.abilityScores, onSave: onSave(true), @@ -181,7 +185,7 @@ class HomeCharacterActionsView extends GetView { ); } - Widget? get spellsList { + Widget? spellsList(BuildContext context, CharacterProvider controller) { if (char.settings.actionCategories.hidden.contains('Spell')) { return null; } @@ -204,6 +208,7 @@ class HomeCharacterActionsView extends GetView { EntityEditMenu( onDelete: onDelete, onEdit: () => ModelPages.openSpellPage( + context, spell: spell, classKeys: spell.classKeys, abilityScores: char.abilityScores, @@ -216,7 +221,7 @@ class HomeCharacterActionsView extends GetView { ); } - Widget? get itemsList { + Widget? itemsList(BuildContext context, CharacterProvider controller) { if (char.settings.actionCategories.hidden.contains('Item')) { return null; } @@ -244,6 +249,7 @@ class HomeCharacterActionsView extends GetView { EntityEditMenu( onDelete: onDelete, onEdit: () => ModelPages.openItemPage( + context, item: item, onSave: onSave(true), ), @@ -286,8 +292,8 @@ class HomeCharacterActionsView extends GetView { ); } - void _onReorder(int oldIndex, int newIndex) { - controller.updateCharacter( + void _onReorder(BuildContext context, int oldIndex, int newIndex) { + charProvider.updateCharacter( char.copyWith( settings: char.settings.copyWith( actionCategories: char.settings.actionCategories.copyWithInherited( @@ -305,16 +311,18 @@ class HomeCharacterActionsView extends GetView { ); } - void _openBasicMoves() { + void _openBasicMoves(BuildContext context) { ModelPages.openMovesList( + context, category: MoveCategory.basic, initialTab: FiltersGroup.playbook, abilityScores: char.abilityScores, ); } - void _openSpecialMoves() { + void _openSpecialMoves(BuildContext context) { ModelPages.openMovesList( + context, category: MoveCategory.special, initialTab: FiltersGroup.playbook, abilityScores: char.abilityScores, @@ -322,8 +330,8 @@ class HomeCharacterActionsView extends GetView { } } -class ActionsCardList extends GetView - with LibraryServiceMixin, RepositoryServiceMixin { +class ActionsCardList extends StatelessWidget + with CharacterProviderMixin { const ActionsCardList({ super.key, required this.route, @@ -354,58 +362,62 @@ class ActionsCardList extends GetView }) cardBuilder; final List list; final int index; - final void Function(int oldIndex, int newIndex) onReorder; + final void Function(BuildContext context, int oldIndex, int newIndex) + onReorder; final List> menuLeading; final List> menuTrailing; - Character get char => controller.current; - @override Widget build(BuildContext context) { - return CategorizedList( - initiallyExpanded: true, - title: Text(tr.entityPlural(typeName)), - itemPadding: const EdgeInsets.only(bottom: 8), - titleTrailing: [ - TextButton.icon( - onPressed: () => Get.toNamed( - route, - arguments: addPageArguments( - onSelected: (items) => library.upsertToCharacter(items, - forkBehavior: ForkBehavior.fork), + return CharacterProvider.consumer((context, controller, _) { + return CategorizedList( + initiallyExpanded: true, + title: Text(tr.entityPlural(typeName)), + itemPadding: const EdgeInsets.only(bottom: 8), + titleTrailing: [ + LibraryProvider.consumer((context, library, _) => TextButton.icon( + 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), + )), + GroupSortMenu( + index: index, + totalItemCount: Character.allActionCategories.length, + 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) => _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); + }, + ), ), ), - label: Text(tr.generic.addEntity(tr.entityPlural(typeName))), - icon: const Icon(Icons.add), - ), - GroupSortMenu( - index: index, - totalItemCount: Character.allActionCategories.length, - 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) => _wrapChild( - key: PageStorageKey('type-$T-${obj.key}'), - child: cardBuilder( - obj, - onDelete: _confirmDeleteDlg(context, obj, obj.displayName), - onSave: (fork) => (obj) { - library - .upsertToCharacter([obj], forkBehavior: ForkBehavior.none); - }, - ), - ), - ), - ], - onReorder: (oldIndex, newIndex) => controller.updateCharacter( - CharacterUtils.reorderByType(char, oldIndex, newIndex)), - ); + ], + onReorder: (oldIndex, newIndex) => controller.updateCharacter( + CharacterUtils.reorderByType( + controller.current, oldIndex, newIndex)), + ); + }); } Widget _wrapChild({Key? key, required Widget child}) => Padding( @@ -415,16 +427,18 @@ class ActionsCardList extends GetView ); void Function() _confirmDeleteDlg( - BuildContext context, T object, String name) { - return () => deleteDialog.confirm( - context, - DeleteDialogOptions( - entityName: name, - entityKind: tr.entity(typeName), - ), - () => controller.updateCharacter( - CharacterUtils.removeByType(char, [object]), + BuildContext context, + T object, + String name, + ) { + return () { + awaitDeleteConfirmation(context, name, () { + charProvider.updateCharacter( + char.copyWithInherited( + moves: char.moves.where((x) => x.key != object.key).toList(), ), ); + }); + }; } } diff --git a/lib/app/modules/Home/views/home_character_journal_view.dart b/lib/app/modules/Home/views/home_character_journal_view.dart index 9dc7c38c..1b3293ba 100644 --- a/lib/app/modules/Home/views/home_character_journal_view.dart +++ b/lib/app/modules/Home/views/home_character_journal_view.dart @@ -1,12 +1,9 @@ -import 'package:dungeon_paper/app/data/models/character.dart'; -import 'package:dungeon_paper/app/data/models/item.dart'; import 'package:dungeon_paper/app/data/models/note.dart'; -import 'package:dungeon_paper/app/data/models/spell.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; import 'package:dungeon_paper/app/model_utils/character_utils.dart'; import 'package:dungeon_paper/app/model_utils/model_pages.dart'; -import 'package:dungeon_paper/app/themes/button_themes.dart'; import 'package:dungeon_paper/app/widgets/cards/note_card.dart'; +import 'package:dungeon_paper/app/widgets/dialogs/confirm_delete_dialog.dart'; import 'package:dungeon_paper/app/widgets/menus/entity_edit_menu.dart'; import 'package:dungeon_paper/app/widgets/menus/group_sort_menu.dart'; import 'package:dungeon_paper/app/widgets/molecules/categorized_list.dart'; @@ -14,21 +11,20 @@ import 'package:dungeon_paper/core/storage_handler/storage_handler.dart'; import 'package:dungeon_paper/core/utils/list_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -class HomeCharacterJournalView extends GetView { +class HomeCharacterJournalView extends StatelessWidget + with CharacterProviderMixin { const HomeCharacterJournalView({super.key}); - Character get char => controller.current; - @override Widget build(BuildContext context) { return PageStorage( bucket: PageStorageBucket(), - child: Obx(() { + child: CharacterProvider.consumer((context, controller, _) { if (controller.maybeCurrent == null) { return Container(); } + final char = controller.current; // return ReorderableListView( return ListView( // physics: const NeverScrollableScrollPhysics(), @@ -79,6 +75,7 @@ class HomeCharacterJournalView extends GetView { tn(Note), ), onEdit: () => ModelPages.openNotePage( + context, note: note, onSave: (note) { controller.updateCharacter( @@ -105,7 +102,6 @@ class HomeCharacterJournalView extends GetView { ); } - // TODO use existing confirmDelete void Function() confirmDelete( BuildContext context, T object, @@ -113,56 +109,17 @@ class HomeCharacterJournalView extends GetView { String typeName, ) { return () async { - final result = await Get.dialog( - AlertDialog( - title: - Text(tr.dialogs.confirmations.delete.title(tr.entity(typeName))), - content: Text( - tr.dialogs.confirmations.delete.body(tr.entity(typeName), name)), - actions: [ - ElevatedButton.icon( - icon: const Icon(Icons.close), - label: Text(tr.generic.cancel), - onPressed: () => Get.back(result: false), - style: ButtonThemes.primaryElevated(context), - ), - ElevatedButton.icon( - icon: const Icon(Icons.delete), - label: Text(tr.generic.remove), - onPressed: () => Get.back(result: true), - style: ButtonThemes.errorElevated(context), - ), - const SizedBox(width: 0), - ], - ), - ); - - if (result == true) { - switch (T) { - case == Note: - controller.updateCharacter( - char.copyWith(notes: removeByKey(char.notes, [object as Note])), - ); - break; - case == Spell: - controller.updateCharacter( - char.copyWith( - spells: removeByKey(char.spells, [object as Spell])), - ); - break; - case == Item: - controller.updateCharacter( - char.copyWith(items: removeByKey(char.items, [object as Item])), - ); - break; - default: - throw TypeError(); - } - } + awaitDeleteConfirmation(context, name, () { + charProvider.updateCharacter( + char.copyWith(notes: removeByKey(char.notes, [object as Note])), + ); + }); }; } - Future _move(int oldIndex, int newIndex) { + Future _move(BuildContext context, int oldIndex, int newIndex) { + final controller = CharacterProvider.of(context); + final char = controller.current; return controller.updateCharacter( char.copyWith( settings: char.settings.copyWith( @@ -181,3 +138,4 @@ class HomeCharacterJournalView extends GetView { ); } } + diff --git a/lib/app/modules/Home/views/home_character_view.dart b/lib/app/modules/Home/views/home_character_view.dart index 1ad5ae82..8650d1fd 100644 --- a/lib/app/modules/Home/views/home_character_view.dart +++ b/lib/app/modules/Home/views/home_character_view.dart @@ -1,4 +1,4 @@ -import 'package:dungeon_paper/app/data/services/character_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; import 'package:dungeon_paper/app/model_utils/dice_utils.dart'; import 'package:dungeon_paper/app/modules/Home/views/local_widgets/home_character_extras.dart'; import 'package:dungeon_paper/app/themes/button_themes.dart'; @@ -12,32 +12,33 @@ import 'package:dungeon_paper/core/utils/builder_utils.dart'; import 'package:dungeon_paper/core/utils/math_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'local_widgets/home_character_dynamic_cards.dart'; import 'local_widgets/home_character_header_view.dart'; import 'local_widgets/home_character_hp_xp_view.dart'; -class HomeCharacterView extends GetView - with HomeCharacterPaddingMixin { +class HomeCharacterView extends StatelessWidget with HomeCharacterPaddingMixin { const HomeCharacterView({super.key}); @override Widget build(BuildContext context) { - return Obx( - () { + return CharacterProvider.consumer( + (context, controller, _) { final char = controller.maybeCurrent; if (char == null) { return Container(); } return HomeCharacterLayout( - leftCol: _buildLeftCol(context), + leftCol: _buildLeftCol(context, controller), rightCol: const HomeCharacterDynamicCards(), ); }, ); } - List _buildLeftCol(BuildContext context) { + List _buildLeftCol( + BuildContext context, + CharacterProvider controller, + ) { final char = controller.current; final abilityScores = char.abilityScores.stats; @@ -71,8 +72,9 @@ class HomeCharacterView extends GetView // visualDensity: VisualDensity.compact, label: char.damageDice.toString(), tooltip: tr.character.data.damageDice, - onPressed: () => Get.dialog( - DamageDiceDialog( + onPressed: () => showDialog( + context: context, + builder: (context) => DamageDiceDialog( damage: char.stats.damageDice, defaultDamage: char.defaultDamageDice, abilityScores: char.abilityScores, @@ -90,8 +92,9 @@ class HomeCharacterView extends GetView icon: const Icon(DwIcons.armor), // visualDensity: VisualDensity.compact, label: char.armor.toString(), - onPressed: () => Get.dialog( - ArmorDialog( + onPressed: () => showDialog( + context: context, + builder: (context) => ArmorDialog( armor: char.stats.armor, defaultArmor: char.defaultArmor, onChanged: (armor) => controller.updateCharacter( @@ -122,7 +125,9 @@ class HomeCharacterView extends GetView Expanded( child: ElevatedButton.icon( onPressed: () => DiceUtils.openRollDialog( - char.rollButtons[0].diceFor(char)), + context, + char.rollButtons[0].diceFor(char), + ), style: ButtonThemes.primaryElevated(context), label: Text(char.rollButtons[0].label), icon: const Icon(DwIcons.dice_d6), @@ -132,7 +137,9 @@ class HomeCharacterView extends GetView Expanded( child: ElevatedButton.icon( onPressed: () => DiceUtils.openRollDialog( - char.rollButtons[1].diceFor(char)), + context, + char.rollButtons[1].diceFor(char), + ), style: ButtonThemes.primaryElevated(context), label: Text(char.rollButtons[1].label), icon: const Icon(DwIcons.dice_d6), diff --git a/lib/app/modules/Home/views/home_fab.dart b/lib/app/modules/Home/views/home_fab.dart index 3c2c7b48..c8581619 100644 --- a/lib/app/modules/Home/views/home_fab.dart +++ b/lib/app/modules/Home/views/home_fab.dart @@ -1,5 +1,5 @@ import 'package:dungeon_paper/app/data/models/note.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; import 'package:dungeon_paper/app/model_utils/character_utils.dart'; import 'package:dungeon_paper/app/model_utils/model_pages.dart'; import 'package:dungeon_paper/app/widgets/atoms/advanced_floating_action_button.dart'; @@ -13,24 +13,24 @@ class HomeFAB extends StatefulWidget { State createState() => _HomeFABState(); } -class _HomeFABState extends State with CharacterServiceMixin { +class _HomeFABState extends State with CharacterProviderMixin { late bool inPageRange; @override void initState() { super.initState(); inPageRange = getPageIsInRange(); - charService.pageController.addListener(_refresh); + charProvider.pageController.addListener(_refresh); } @override void dispose() { - charService.pageController.removeListener(_refresh); + charProvider.pageController.removeListener(_refresh); super.dispose(); } bool getPageIsInRange() { - final distance = (charService.page - pageNum.toDouble()).abs(); + final distance = (charProvider.page - pageNum.toDouble()).abs(); return distance <= 0.5; } @@ -60,8 +60,9 @@ class _HomeFABState extends State with CharacterServiceMixin { ), onPressed: inPageRange ? () => ModelPages.openNotePage( + context, note: null, - onSave: (note) => charService.updateCharacter( + onSave: (note) => charProvider.updateCharacter( CharacterUtils.addByType(char, [note]), ), ) @@ -77,3 +78,4 @@ class _HomeFABState extends State with CharacterServiceMixin { } } } + diff --git a/lib/app/modules/Home/views/home_loader_view.dart b/lib/app/modules/Home/views/home_loader_view.dart index b8553157..e7a59ff1 100644 --- a/lib/app/modules/Home/views/home_loader_view.dart +++ b/lib/app/modules/Home/views/home_loader_view.dart @@ -1,22 +1,29 @@ -import 'package:dungeon_paper/app/data/services/loading_service.dart'; +import 'package:dungeon_paper/app/data/services/loading_provider.dart'; import 'package:dungeon_paper/app/modules/Home/views/home_character_view.dart'; import 'package:dungeon_paper/app/modules/Home/views/local_widgets/home_character_header_view.dart'; import 'package:dungeon_paper/app/widgets/atoms/character_avatar.dart'; +import 'package:dungeon_paper/core/global_keys.dart'; import 'package:dungeon_paper/core/utils/math_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; import 'package:skeleton_loader/skeleton_loader.dart'; -class HomeLoaderView extends GetView with LoadingServiceMixin { +class HomeLoaderView extends StatelessWidget { const HomeLoaderView({super.key}); String get title { - if (loadingService.loadingUser) { + final context = appGlobalKey.currentContext!; + final loadingProvider = Provider.of( + context, + listen: false, + ); + + if (loadingProvider.loadingUser) { return tr.loading.user; } - if (loadingService.loadingCharacters) { + if (loadingProvider.loadingCharacters) { return tr.loading.characters; } diff --git a/lib/app/modules/Home/views/home_view.dart b/lib/app/modules/Home/views/home_view.dart index 26ad396e..3951e2c0 100644 --- a/lib/app/modules/Home/views/home_view.dart +++ b/lib/app/modules/Home/views/home_view.dart @@ -1,6 +1,7 @@ import 'package:dungeon_paper/app/data/models/character.dart'; -import 'package:dungeon_paper/app/data/services/loading_service.dart'; -import 'package:dungeon_paper/app/data/services/user_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; +import 'package:dungeon_paper/app/data/services/loading_provider.dart'; +import 'package:dungeon_paper/app/data/services/user_provider.dart'; import 'package:dungeon_paper/app/modules/Home/views/home_app_bar.dart'; import 'package:dungeon_paper/app/modules/Home/views/home_character_actions_view.dart'; import 'package:dungeon_paper/app/modules/Home/views/home_character_journal_view.dart'; @@ -11,24 +12,21 @@ import 'package:dungeon_paper/app/widgets/atoms/icon_span.dart'; import 'package:dungeon_paper/app/widgets/atoms/page_controller_fractional_box.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -// import '../../../widgets/atoms/debug_menu.dart'; -import '../../../data/services/character_service.dart'; import 'home_character_view.dart'; import 'home_fab.dart'; import 'home_nav_bar.dart'; -class HomeView extends GetView - with UserServiceMixin, LoadingServiceMixin, CharacterServiceMixin { +class HomeView extends StatelessWidget + with UserProviderMixin, LoadingProviderMixin, CharacterProviderMixin { const HomeView({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: const HomeAppBar(), - body: Obx( - () { + body: CharacterProvider.consumer( + (context, controller, _) { const children = [ HomeCharacterActionsView(), HomeCharacterView(), @@ -37,7 +35,7 @@ class HomeView extends GetView return isLoading ? const HomeLoaderView() - : maybeChar != null + : controller.maybeCurrent != null ? PageView( controller: controller.pageController, children: children.map(_fractionalSizedBox).toList(), @@ -45,32 +43,36 @@ class HomeView extends GetView : const HomeEmptyState(); }, ), - floatingActionButton: Obx( - () => maybeChar != null ? const HomeFAB() : const SizedBox.shrink()), - bottomNavigationBar: Obx( - () => maybeChar != null - ? HomeNavBar(pageController: controller.pageController) + floatingActionButton: CharacterProvider.consumer( + (context, controller, _) => + maybeChar != null ? const HomeFAB() : const SizedBox.shrink(), + ), + bottomNavigationBar: CharacterProvider.consumer( + (context, controller, _) => maybeChar != null + ? CharacterProvider.consumer((context, controller, _) => + HomeNavBar(pageController: controller.pageController)) : const SizedBox.shrink(), ), ); } - PageControllerFractionalBox _fractionalSizedBox(Widget child) => - PageControllerFractionalBox( - controller: controller.pageController, - child: child, + Widget _fractionalSizedBox(Widget child) => CharacterProvider.consumer( + (context, controller, _) => PageControllerFractionalBox( + controller: controller.pageController, + child: child, + ), ); bool get isLoading { - debugPrint('afterFirstLoad: ${loadingService.afterFirstLoad}, ' - 'loadingUser: ${loadingService.loadingUser}, ' - 'loadingCharacters: ${loadingService.loadingCharacters}'); - return !loadingService.afterFirstLoad && - (loadingService.loadingUser || loadingService.loadingCharacters); + debugPrint('afterFirstLoad: ${loadingProvider.afterFirstLoad}, ' + 'loadingUser: ${loadingProvider.loadingUser}, ' + 'loadingCharacters: ${loadingProvider.loadingCharacters}'); + return !loadingProvider.afterFirstLoad && + (loadingProvider.loadingUser || loadingProvider.loadingCharacters); } } -class HomeEmptyState extends StatelessWidget with UserServiceMixin { +class HomeEmptyState extends StatelessWidget with UserProviderMixin { const HomeEmptyState({super.key}); @override @@ -113,7 +115,8 @@ class HomeEmptyState extends StatelessWidget with UserServiceMixin { ElevatedButton.icon( label: Text(tr.auth.login.button), icon: const Icon(Icons.login), - onPressed: () => Get.toNamed(Routes.login), + onPressed: () => + Navigator.of(context).pushNamed(Routes.login), style: ButtonThemes.primaryElevated(context), ), ], @@ -139,7 +142,8 @@ class HomeEmptyState extends StatelessWidget with UserServiceMixin { ElevatedButton.icon( label: Text(tr.generic.createEntity(tr.entity(tn(Character)))), icon: const Icon(Icons.person_add), - onPressed: () => Get.toNamed(Routes.createCharacter), + onPressed: () => + Navigator.of(context).pushNamed(Routes.createCharacter), ), ], ), diff --git a/lib/app/modules/Home/views/local_widgets/home_character_actions_summary.dart b/lib/app/modules/Home/views/local_widgets/home_character_actions_summary.dart index 5d1c94c6..7adc7056 100644 --- a/lib/app/modules/Home/views/local_widgets/home_character_actions_summary.dart +++ b/lib/app/modules/Home/views/local_widgets/home_character_actions_summary.dart @@ -1,89 +1,88 @@ -import 'package:dungeon_paper/app/data/models/character.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; import 'package:dungeon_paper/app/widgets/chips/primary_chip.dart'; import 'package:dungeon_paper/app/widgets/dialogs/coins_dialog.dart'; import 'package:dungeon_paper/app/widgets/dialogs/load_dialog.dart'; import 'package:dungeon_paper/core/dw_icons.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:intl/intl.dart'; import 'home_character_actions_filters.dart'; -class HomeCharacterActionsSummary extends GetView { - const HomeCharacterActionsSummary({ - super.key, - }); - - Character get char => controller.current; +class HomeCharacterActionsSummary extends StatelessWidget { + const HomeCharacterActionsSummary({super.key}); @override Widget build(BuildContext context) { - return Obx( - () => Row( - children: [ - Expanded( - child: Wrap( - alignment: WrapAlignment.center, - crossAxisAlignment: WrapCrossAlignment.center, - spacing: 4, - runSpacing: 4, - children: [ - PrimaryChip( - // visualDensity: VisualDensity.compact, - icon: const Icon(DwIcons.dumbbell, size: 16), - label: tr.home.summary.load - .label(char.currentLoad, char.maxLoad), - tooltip: tr.home.summary.load.tooltip, - backgroundColor: _loadColor(char.currentLoad, char.maxLoad), - onPressed: () => Get.dialog( - LoadDialog( - load: char.stats.load, - defaultLoad: char.defaultMaxLoad, - onChanged: (load) => controller.updateCharacter( - char.copyWith( - stats: char.stats.copyWithLoad(load), + return CharacterProvider.consumer( + (context, charProvider, _) { + final char = charProvider.current; + return Row( + children: [ + Expanded( + child: Wrap( + alignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 4, + runSpacing: 4, + children: [ + PrimaryChip( + // visualDensity: VisualDensity.compact, + icon: const Icon(DwIcons.dumbbell, size: 16), + label: tr.home.summary.load + .label(char.currentLoad, char.maxLoad), + tooltip: tr.home.summary.load.tooltip, + backgroundColor: _loadColor(char.currentLoad, char.maxLoad), + onPressed: () => showDialog( + context: context, + builder: (_) => LoadDialog( + load: char.stats.load, + defaultLoad: char.defaultMaxLoad, + onChanged: (load) => charProvider.updateCharacter( + char.copyWith( + stats: char.stats.copyWithLoad(load), + ), ), ), ), ), - ), - PrimaryChip( - // visualDensity: VisualDensity.compact, - icon: const Icon(DwIcons.coin_stack, size: 16), - label: tr.home.summary.coins.label( - NumberFormat.compact().format(char.coins), - ), - tooltip: tr.home.summary.coins.tooltip, - onPressed: () => Get.dialog( - CoinsDialog( - coins: char.coins, - onChanged: (coins) => controller - .updateCharacter(char.copyWith(coins: coins)), + PrimaryChip( + // visualDensity: VisualDensity.compact, + icon: const Icon(DwIcons.coin_stack, size: 16), + label: tr.home.summary.coins.label( + NumberFormat.compact().format(char.coins), + ), + tooltip: tr.home.summary.coins.tooltip, + onPressed: () => showDialog( + context: context, + builder: (_) => CoinsDialog( + coins: char.coins, + onChanged: (coins) => charProvider + .updateCharacter(char.copyWith(coins: coins)), + ), ), ), - ), - ], + ], + ), ), - ), - HomeCharacterActionsFilters( - hidden: char.settings.actionCategories.hidden, - onUpdateHidden: (filters) { - controller.updateCharacter( - char.copyWith( - settings: char.settings.copyWith( - actionCategories: - char.settings.actionCategories.copyWithInherited( - hidden: filters, + HomeCharacterActionsFilters( + hidden: char.settings.actionCategories.hidden, + onUpdateHidden: (filters) { + charProvider.updateCharacter( + char.copyWith( + settings: char.settings.copyWith( + actionCategories: + char.settings.actionCategories.copyWithInherited( + hidden: filters, + ), ), ), - ), - ); - }, - ), - ], - ), + ); + }, + ), + ], + ); + }, ); } @@ -97,4 +96,3 @@ class HomeCharacterActionsSummary extends GetView { return null; } } - diff --git a/lib/app/modules/Home/views/local_widgets/home_character_dynamic_cards.dart b/lib/app/modules/Home/views/local_widgets/home_character_dynamic_cards.dart index 1b2ca935..1282a2ac 100644 --- a/lib/app/modules/Home/views/local_widgets/home_character_dynamic_cards.dart +++ b/lib/app/modules/Home/views/local_widgets/home_character_dynamic_cards.dart @@ -4,8 +4,8 @@ import 'package:dungeon_paper/app/data/models/move.dart'; import 'package:dungeon_paper/app/data/models/note.dart'; import 'package:dungeon_paper/app/data/models/race.dart'; import 'package:dungeon_paper/app/data/models/spell.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; -import 'package:dungeon_paper/app/data/services/library_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; +import 'package:dungeon_paper/app/data/services/library_provider.dart'; import 'package:dungeon_paper/app/model_utils/character_utils.dart'; import 'package:dungeon_paper/app/model_utils/model_pages.dart'; import 'package:dungeon_paper/app/widgets/atoms/checklist_menu_entry.dart'; @@ -23,116 +23,109 @@ import 'package:dungeon_paper/app/widgets/dialogs/confirm_delete_dialog.dart'; import 'package:dungeon_paper/app/widgets/menus/entity_edit_menu.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import '../expanded_card_dialog_view.dart'; import 'horizontal_list_card_view.dart'; -class HomeCharacterDynamicCards extends GetView - with LibraryServiceMixin { +class HomeCharacterDynamicCards extends StatelessWidget + with CharacterProviderMixin { const HomeCharacterDynamicCards({super.key}); - List get moves => (controller.maybeCurrent?.moves ?? []) - .where((m) => m.favorite) - .toList(); - List get spells => (controller.maybeCurrent?.spells ?? []) + List get moves => + (maybeChar?.moves ?? []).where((m) => m.favorite).toList(); + List get spells => (charProvider.maybeCurrent?.spells ?? []) .where((m) => m.prepared) .toList(); - List get items => (controller.maybeCurrent?.items ?? []) - .where((m) => m.equipped) - .toList(); - List get notes => (controller.maybeCurrent?.notes ?? []) - .where((n) => n.favorite) - .toList(); + List get items => + (maybeChar?.items ?? []).where((m) => m.equipped).toList(); + List get notes => + (maybeChar?.notes ?? []).where((n) => n.favorite).toList(); @override Widget build(BuildContext context) { const cardSize = Size(210, 151); final maxContentHeight = MediaQuery.of(context).size.height - 250; - return Obx( - () => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // - // NOTES - // - if (notes.isNotEmpty) ...[ - const SizedBox(height: 10), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text(tr.home.categories.notes), - ), - ], - HorizontalCardListView( - cardSize: cardSize, - items: notes, - cardBuilder: (context, note, index, onTap) => Obx( - () => NoteCardMini( - note: notes[index], - onTap: onTap, - onSave: (note) => controller.updateCharacter( - CharacterUtils.updateNotes(controller.current, [note]), - ), - ), - ), - expandedCardBuilder: (context, note, index) => Obx( - () { - return notes.isNotEmpty && index < notes.length - ? NoteCard( - maxContentHeight: maxContentHeight, - expandable: false, - initiallyExpanded: true, - note: notes[index], - actions: [ - EntityEditMenu( - onEdit: () => ModelPages.openNotePage( - note: notes[index], - onSave: (note) => controller.updateCharacter( - CharacterUtils.updateNotes( - controller.current, [note]), - ), - ), - onDelete: _delete( - context, - note, - note.title, - tn(Note), - () => controller.updateCharacter( - CharacterUtils.removeNotes( - controller.current, [note]), - ), - ), - ), - ], - onSave: (note) { - controller.updateCharacter( - CharacterUtils.updateNotes( - controller.current, [note]), - ); - if (!note.favorite) { - Get.back(); - } - }, - ) - : const SizedBox.shrink(); - }, + return CharacterProvider.consumer( + (context, controller, _) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // + // NOTES + // + if (notes.isNotEmpty) ...[ + const SizedBox(height: 10), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text(tr.home.categories.notes), + ), + ], + HorizontalCardListView( + cardSize: cardSize, + items: notes, + cardBuilder: (context, note, index, onTap) => NoteCardMini( + note: notes[index], + onTap: onTap, + onSave: (note) => controller.updateCharacter( + CharacterUtils.updateNotes(controller.current, [note]), ), ), - // - // MOVES - // - if (moves.isNotEmpty) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text(tr.home.categories.moves), - ), - Builder(builder: (context) { + expandedCardBuilder: (context, note, index) => notes.isNotEmpty && + index < notes.length + ? NoteCard( + maxContentHeight: maxContentHeight, + expandable: false, + initiallyExpanded: true, + note: notes[index], + actions: [ + EntityEditMenu( + onEdit: () => ModelPages.openNotePage( + context, + note: notes[index], + onSave: (note) => controller.updateCharacter( + CharacterUtils.updateNotes( + controller.current, [note]), + ), + ), + onDelete: _delete( + context, + note, + note.title, + tn(Note), + () => controller.updateCharacter( + CharacterUtils.removeNotes( + controller.current, [note]), + ), + ), + ), + ], + onSave: (note) { + controller.updateCharacter( + CharacterUtils.updateNotes(controller.current, [note]), + ); + if (!note.favorite) { + Navigator.of(context).pop(); + } + }, + ) + : const SizedBox.shrink(), + ), + // + // MOVES + // + if (moves.isNotEmpty) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text(tr.home.categories.moves), + ), + Builder( + builder: (context) { final raceCardMini = controller.current.race.favorite ? RaceCardMini( race: controller.current.race, - onTap: () => Get.dialog( - ExpandedCardDialogView( + onTap: () => showDialog( + context: context, + builder: (context) => ExpandedCardDialogView( // heroTag: getKeyFor(item.value), heroTag: null, builder: (context) => RaceCard( @@ -143,6 +136,7 @@ class HomeCharacterDynamicCards extends GetView actions: [ EntityEditMenu( onEdit: () => ModelPages.openRacePage( + context, abilityScores: controller.current.abilityScores, race: controller.current.race, @@ -167,56 +161,61 @@ class HomeCharacterDynamicCards extends GetView return HorizontalCardListView( cardSize: cardSize, items: moves, - cardBuilder: (context, move, index, onTap) => Obx( - () => MoveCardMini( + cardBuilder: (context, move, index, onTap) => + MoveCardMini( move: moves[index], onTap: onTap, onSave: (move) => controller.updateCharacter( CharacterUtils.updateMoves(controller.current, [move]), ), abilityScores: controller.current.abilityScores, - ), ), - expandedCardBuilder: (context, move, index) => Obx( - () => moves.isNotEmpty && index < moves.length - ? MoveCard( - maxContentHeight: maxContentHeight, - expandable: false, - initiallyExpanded: true, - move: moves[index], - abilityScores: controller.current.abilityScores, - actions: [ - EntityEditMenu( - onEdit: () => ModelPages.openMovePage( - abilityScores: controller.current.abilityScores, - move: moves[index], - onSave: (move) => library.upsertToCharacter( - [move], - forkBehavior: ForkBehavior.increaseVersion), - ), - onDelete: _delete( - context, - move, - move.name, - tn(Move), - () => controller.updateCharacter( - CharacterUtils.removeMoves( - controller.current, [move]), + expandedCardBuilder: (context, move, index) => + LibraryProvider.consumer( + (context, library, _) { + return moves.isNotEmpty && index < moves.length + ? MoveCard( + maxContentHeight: maxContentHeight, + expandable: false, + initiallyExpanded: true, + move: moves[index], + abilityScores: controller.current.abilityScores, + actions: [ + EntityEditMenu( + onEdit: () => ModelPages.openMovePage( + context, + abilityScores: + controller.current.abilityScores, + move: moves[index], + onSave: (move) => library.upsertToCharacter( + [move], + forkBehavior: + ForkBehavior.increaseVersion), + ), + onDelete: _delete( + context, + move, + move.name, + tn(Move), + () => controller.updateCharacter( + CharacterUtils.removeMoves( + controller.current, [move]), + ), ), ), - ), - ], - onSave: (move) { - controller.updateCharacter( - CharacterUtils.updateMoves( - controller.current, [move]), - ); - if (!move.favorite) { - Get.back(); - } - }, - ) - : const SizedBox.shrink(), + ], + onSave: (move) { + controller.updateCharacter( + CharacterUtils.updateMoves( + controller.current, [move]), + ); + if (!move.favorite) { + Navigator.of(context).pop(); + } + }, + ) + : const SizedBox.shrink(); + }, ), leading: raceCardMini != null && controller.current.settings.racePosition == @@ -229,32 +228,31 @@ class HomeCharacterDynamicCards extends GetView ? [raceCardMini] : [], ); - }), - // - // SPELLS - // - if (spells.isNotEmpty) ...[ - const SizedBox(height: 10), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text(tr.home.categories.spells), + }, + ), + // + // SPELLS + // + if (spells.isNotEmpty) ...[ + const SizedBox(height: 10), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text(tr.home.categories.spells), + ), + ], + HorizontalCardListView( + cardSize: cardSize, + items: spells, + cardBuilder: (context, spell, index, onTap) => SpellCardMini( + spell: spells[index], + onTap: onTap, + onSave: (spell) => controller.updateCharacter( + CharacterUtils.updateSpells(controller.current, [spell]), ), - ], - HorizontalCardListView( - cardSize: cardSize, - items: spells, - cardBuilder: (context, spell, index, onTap) => Obx( - () => SpellCardMini( - spell: spells[index], - onTap: onTap, - onSave: (spell) => controller.updateCharacter( - CharacterUtils.updateSpells(controller.current, [spell]), - ), - abilityScores: controller.current.abilityScores, - ), - ), - expandedCardBuilder: (context, spell, index) => Obx( - () => spells.isNotEmpty && index < spells.length + abilityScores: controller.current.abilityScores, + ), + expandedCardBuilder: (context, spell, index) => + spells.isNotEmpty && index < spells.length ? SpellCard( maxContentHeight: maxContentHeight, expandable: false, @@ -264,6 +262,7 @@ class HomeCharacterDynamicCards extends GetView actions: [ EntityEditMenu( onEdit: () => ModelPages.openSpellPage( + context, abilityScores: controller.current.abilityScores, classKeys: spells[index].classKeys, spell: spells[index], @@ -290,137 +289,134 @@ class HomeCharacterDynamicCards extends GetView controller.current, [spell]), ); if (!spell.prepared) { - Get.back(); + Navigator.of(context).pop(); } }, ) : const SizedBox.shrink(), + ), + // + // ITEMS + // + if (items.isNotEmpty) ...[ + const SizedBox(height: 10), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text(tr.home.categories.items), + ), + ], + HorizontalCardListView( + cardSize: cardSize, + items: items, + cardBuilder: (context, item, index, onTap) => ItemCardMini( + item: items[index], + onTap: onTap, + onSave: (item) => controller.updateCharacter( + CharacterUtils.updateItems(controller.current, [item]), ), ), - // - // ITEMS - // - if (items.isNotEmpty) ...[ - const SizedBox(height: 10), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text(tr.home.categories.items), - ), - ], - HorizontalCardListView( - cardSize: cardSize, - items: items, - cardBuilder: (context, item, index, onTap) => Obx( - () => ItemCardMini( - item: items[index], - onTap: onTap, - onSave: (item) => controller.updateCharacter( - CharacterUtils.updateItems(controller.current, [item]), - ), - ), - ), - expandedCardBuilder: (context, item, index) => Obx( - () => items.isNotEmpty && index < items.length - ? ItemCard( - maxContentHeight: maxContentHeight, - expandable: false, - initiallyExpanded: true, - item: items[index], - actions: [ - EntityEditMenu( - onEdit: () => ModelPages.openItemPage( - item: items[index], - onSave: (item) => controller.updateCharacter( - CharacterUtils.updateItems( - controller.current, [item]), - ), - ), - onDelete: _delete( - context, - item, - item.name, - tn(Item), - () => controller.updateCharacter( - CharacterUtils.removeItems( - controller.current, [item]), - ), - ), - leading: [ - ChecklistMenuEntry( - value: 'countArmor', - checked: item.settings.countArmor, - label: Text(tr.items.settings.countArmor), - onChanged: (value) => - controller.updateCharacter( - CharacterUtils.updateItems( - controller.current, [ - item.copyWithInherited( - settings: item.settings - .copyWith(countArmor: value!), - ) - ]), - ), - ), - ChecklistMenuEntry( - value: 'countDamage', - checked: item.settings.countDamage, - label: Text(tr.items.settings.countDamage), - onChanged: (value) => - controller.updateCharacter( - CharacterUtils.updateItems( - controller.current, [ - item.copyWithInherited( - settings: item.settings - .copyWith(countDamage: value!), - ) - ]), - ), - ), - ChecklistMenuEntry( - value: 'countWeight', - checked: item.settings.countWeight, - label: Text(tr.items.settings.countWeight), - onChanged: (value) => - controller.updateCharacter( - CharacterUtils.updateItems( - controller.current, [ - item.copyWithInherited( - settings: item.settings - .copyWith(countWeight: value!), - ) - ]), - ), - ), - ], - ), - ], - onSave: (item) { - controller.updateCharacter( + expandedCardBuilder: (context, item, index) => items.isNotEmpty && + index < items.length + ? ItemCard( + maxContentHeight: maxContentHeight, + expandable: false, + initiallyExpanded: true, + item: items[index], + actions: [ + EntityEditMenu( + onEdit: () => ModelPages.openItemPage( + context, + item: items[index], + onSave: (item) => controller.updateCharacter( CharacterUtils.updateItems( controller.current, [item]), - ); - if (!item.equipped) { - Get.back(); - } - }, - ) - : const SizedBox.shrink(), - ), - ), - ]), + ), + ), + onDelete: _delete( + context, + item, + item.name, + tn(Item), + () => controller.updateCharacter( + CharacterUtils.removeItems( + controller.current, [item]), + ), + ), + leading: [ + ChecklistMenuEntry( + value: 'countArmor', + checked: item.settings.countArmor, + label: Text(tr.items.settings.countArmor), + onChanged: (value) => controller.updateCharacter( + CharacterUtils.updateItems(controller.current, [ + item.copyWithInherited( + settings: item.settings + .copyWith(countArmor: value!), + ) + ]), + ), + ), + ChecklistMenuEntry( + value: 'countDamage', + checked: item.settings.countDamage, + label: Text(tr.items.settings.countDamage), + onChanged: (value) => controller.updateCharacter( + CharacterUtils.updateItems(controller.current, [ + item.copyWithInherited( + settings: item.settings + .copyWith(countDamage: value!), + ) + ]), + ), + ), + ChecklistMenuEntry( + value: 'countWeight', + checked: item.settings.countWeight, + label: Text(tr.items.settings.countWeight), + onChanged: (value) => controller.updateCharacter( + CharacterUtils.updateItems(controller.current, [ + item.copyWithInherited( + settings: item.settings + .copyWith(countWeight: value!), + ) + ]), + ), + ), + ], + ), + ], + onSave: (item) { + controller.updateCharacter( + CharacterUtils.updateItems(controller.current, [item]), + ); + if (!item.equipped) { + Navigator.of(context).pop(); + } + }, + ) + : const SizedBox.shrink(), + ), + ], + ), ); } - void Function() _delete(BuildContext context, T item, String itemName, - String typeName, void Function() onRemove) { - return () => deleteDialog.confirm( + void Function() _delete( + BuildContext context, + T item, + String itemName, + String typeName, + void Function() onRemove, + ) { + return () => awaitDeleteConfirmation( context, - DeleteDialogOptions( - entityName: itemName, entityKind: tr.entity(typeName)), + itemName, () { onRemove(); - Get.back(); + Navigator.of(context).pop(); }, + T, ); } } + diff --git a/lib/app/modules/Home/views/local_widgets/home_character_extras.dart b/lib/app/modules/Home/views/local_widgets/home_character_extras.dart index 2cc8327b..8771b6b8 100644 --- a/lib/app/modules/Home/views/local_widgets/home_character_extras.dart +++ b/lib/app/modules/Home/views/local_widgets/home_character_extras.dart @@ -2,7 +2,7 @@ import 'package:dungeon_paper/app/data/models/campaign.dart'; import 'package:dungeon_paper/app/data/models/character_class.dart'; import 'package:dungeon_paper/app/data/models/race.dart'; import 'package:dungeon_paper/app/data/models/session_marks.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; import 'package:dungeon_paper/app/model_utils/model_pages.dart'; import 'package:dungeon_paper/app/modules/AbilityScoresForm/controllers/ability_scores_form_controller.dart'; import 'package:dungeon_paper/app/modules/BasicInfoForm/controllers/basic_info_form_controller.dart'; @@ -15,9 +15,8 @@ import 'package:dungeon_paper/app/widgets/dialogs/debilities_dialog.dart'; import 'package:dungeon_paper/core/dw_icons.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -class HomeCharacterExtras extends GetView { +class HomeCharacterExtras extends StatelessWidget with CharacterProviderMixin { const HomeCharacterExtras({super.key}); @override @@ -33,49 +32,49 @@ class HomeCharacterExtras extends GetView { value: 'name_photo', icon: const Icon(Icons.photo), label: Text(tr.home.menu.character.basicInfo), - onSelect: _openBasicInfo, + onSelect: () => _openBasicInfo(context), ), MenuEntry( value: 'ability_scores', icon: const Icon(Icons.format_list_numbered_rtl), label: Text(tr.home.menu.character.abilityScores), - onSelect: _openAbilityScores, + onSelect: () => _openAbilityScores(context), ), MenuEntry( value: 'class', icon: Icon(CharacterClass.genericIcon), label: Text(tr.generic.changeEntity(tr.entity(tn(CharacterClass)))), - onSelect: _openCharClass, + onSelect: () => _openCharClass(context), ), MenuEntry( value: 'race', icon: Icon(Race.genericIcon), label: Text(tr.generic.changeEntity(tr.entity(tn(Race)))), - onSelect: _openRace, + onSelect: () => _openRace(context), ), MenuEntry( value: 'roll_buttons', icon: const Icon(DwIcons.dice_d6), label: Text(tr.home.menu.character.customRolls), - onSelect: _openRollButtons, + onSelect: () => _openRollButtons(context), ), MenuEntry( value: 'theme', icon: const Icon(Icons.brush), label: Text(tr.home.menu.character.theme), - onSelect: _openThemeSelect, + onSelect: () => _openThemeSelect(context), ), ], ), IconButton( icon: const Icon(Icons.text_snippet), tooltip: tr.home.menu.bio, - onPressed: _openBio, + onPressed: () => _openBio(context), ), - Obx( - () => IconButton( - onPressed: _openBondsFlags, + CharacterProvider.consumer( + (context, controller, _) => IconButton( + onPressed: () => _openBondsFlags(context), icon: Transform.scale(scaleX: -1, child: const Icon(Icons.handshake)), tooltip: SessionMark.categoryTitle( @@ -85,7 +84,7 @@ class HomeCharacterExtras extends GetView { ), ), IconButton( - onPressed: _openDebilities, + onPressed: () => _openDebilities(context), icon: const Icon(Icons.personal_injury), tooltip: tr.home.menu.debilities, ), @@ -98,81 +97,87 @@ class HomeCharacterExtras extends GetView { ); } - void _openAbilityScores() => Get.toNamed( - Routes.abilityScores, - arguments: AbilityScoresFormArguments( - abilityScores: controller.current.abilityScores, - onChanged: (abilityScores) => controller.updateCharacter( - controller.current.copyWith(abilityScores: abilityScores)), - ), - preventDuplicates: false, - ); + void _openAbilityScores(BuildContext context) { + Navigator.of(context).pushNamed( + Routes.abilityScores, + arguments: AbilityScoresFormArguments( + abilityScores: charProvider.current.abilityScores, + onChanged: (abilityScores) => charProvider.updateCharacter( + charProvider.current.copyWith(abilityScores: abilityScores)), + ), + ); + } - void _openBasicInfo() { - Get.toNamed( + void _openBasicInfo(BuildContext context) { + Navigator.of(context).pushNamed( Routes.basicInfo, arguments: BasicInfoFormArguments( - onChanged: (name, avatar) => controller.updateCharacter( - controller.current.copyWith(displayName: name, avatarUrl: avatar), + onChanged: (name, avatar) => charProvider.updateCharacter( + charProvider.current.copyWith(displayName: name, avatarUrl: avatar), ), - name: controller.current.displayName, - avatarUrl: controller.current.avatarUrl, + name: charProvider.current.displayName, + avatarUrl: charProvider.current.avatarUrl, ), ); } - void _openBio() { - Get.dialog(const CharacterBioDialog()); + void _openBio(BuildContext context) { + showDialog(context: context, builder: (_) => const CharacterBioDialog()); } - void _openRace() { + void _openRace(BuildContext context) { ModelPages.openRacesList( - character: controller.current, - preSelection: controller.current.race, - onSelected: (race) => controller.updateCharacter( - controller.current.copyWithInherited( + context, + character: charProvider.current, + preSelection: charProvider.current.race, + onSelected: (race) => charProvider.updateCharacter( + charProvider.current.copyWithInherited( race: race.copyWithInherited( - favorite: controller.current.race.favorite), + favorite: charProvider.current.race.favorite), ), ), ); } - void _openCharClass() { + void _openCharClass(BuildContext context) { ModelPages.openCharacterClassesList( - character: controller.current, - onSelected: (cls) => controller.updateCharacter( + context, + character: charProvider.current, + onSelected: (cls) => charProvider.updateCharacter( // TODO add a reset dialog to confirm + ask what to reset: moves, spells, alignment, rac - controller.current.copyWithInherited( + charProvider.current.copyWithInherited( characterClass: cls, ), ), ); } - void _openBondsFlags() { - Get.dialog(const CharacterBondsFlagsDialog()); + void _openBondsFlags(BuildContext context) { + showDialog( + context: context, builder: (_) => const CharacterBondsFlagsDialog()); } - void _openDebilities() { - Get.dialog(const CharacterDebilitiesDialog()); + void _openDebilities(BuildContext context) { + showDialog( + context: context, builder: (_) => const CharacterDebilitiesDialog()); } - void _openRollButtons() { - Get.dialog( - CustomRollButtonsDialog( - character: controller.current, - onChanged: (rollButtons) => controller.updateCharacter( - controller.current.copyWith( - settings: - controller.current.settings.copyWith(rollButtons: rollButtons), + void _openRollButtons(BuildContext context) { + showDialog( + context: context, + builder: (_) => CustomRollButtonsDialog( + character: charProvider.current, + onChanged: (rollButtons) => charProvider.updateCharacter( + charProvider.current.copyWith( + settings: charProvider.current.settings + .copyWith(rollButtons: rollButtons), ), ), ), ); } - void _openThemeSelect() { - Get.toNamed(Routes.selectCharacterTheme); + void _openThemeSelect(BuildContext context) { + Navigator.of(context).pushNamed(Routes.selectCharacterTheme); } } diff --git a/lib/app/modules/Home/views/local_widgets/home_character_header_view.dart b/lib/app/modules/Home/views/local_widgets/home_character_header_view.dart index e6f0c8d6..06df581a 100644 --- a/lib/app/modules/Home/views/local_widgets/home_character_header_view.dart +++ b/lib/app/modules/Home/views/local_widgets/home_character_header_view.dart @@ -1,10 +1,8 @@ -import 'package:dungeon_paper/app/data/services/character_service.dart'; import 'package:dungeon_paper/app/widgets/atoms/character_avatar.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -class HomeCharacterHeaderView extends GetView { - const HomeCharacterHeaderView({Key? key}) : super(key: key); +class HomeCharacterHeaderView extends StatelessWidget { + const HomeCharacterHeaderView({super.key}); @override Widget build(BuildContext context) { diff --git a/lib/app/modules/Home/views/local_widgets/home_character_hp_xp_view.dart b/lib/app/modules/Home/views/local_widgets/home_character_hp_xp_view.dart index ea87e823..82683c16 100644 --- a/lib/app/modules/Home/views/local_widgets/home_character_hp_xp_view.dart +++ b/lib/app/modules/Home/views/local_widgets/home_character_hp_xp_view.dart @@ -1,14 +1,11 @@ -import 'package:dungeon_paper/app/data/services/character_service.dart'; import 'package:dungeon_paper/app/widgets/atoms/xp_bar.dart'; import 'package:dungeon_paper/app/widgets/atoms/hp_bar.dart'; import 'package:dungeon_paper/app/widgets/dialogs/xp_dialog.dart'; import 'package:dungeon_paper/app/widgets/dialogs/hp_dialog.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; - -class HomeCharacterHpExpView extends GetView { - const HomeCharacterHpExpView({Key? key}) : super(key: key); +class HomeCharacterHpExpView extends StatelessWidget { + const HomeCharacterHpExpView({super.key}); @override Widget build(BuildContext context) { @@ -17,24 +14,30 @@ class HomeCharacterHpExpView extends GetView { Expanded( child: InkWell( splashColor: Theme.of(context).splashColor, + borderRadius: BorderRadius.circular(10), + onTap: () => showDialog( + context: context, + builder: (context) => const HPDialog(), + ), child: const Padding( padding: EdgeInsets.all(4), child: HpBar(), ), - borderRadius: BorderRadius.circular(10), - onTap: () => Get.dialog(const HPDialog()), ), ), const SizedBox(width: 16), Expanded( child: InkWell( splashColor: Theme.of(context).splashColor, + borderRadius: BorderRadius.circular(10), + onTap: () => showDialog( + context: context, + builder: (context) => const EXPDialog(), + ), child: const Padding( padding: EdgeInsets.all(4), child: ExpBar(), ), - borderRadius: BorderRadius.circular(10), - onTap: () => Get.dialog(const EXPDialog()), ), ), ], diff --git a/lib/app/modules/Home/views/local_widgets/horizontal_list_card_view.dart b/lib/app/modules/Home/views/local_widgets/horizontal_list_card_view.dart index d8d6e7f3..e7283c3f 100644 --- a/lib/app/modules/Home/views/local_widgets/horizontal_list_card_view.dart +++ b/lib/app/modules/Home/views/local_widgets/horizontal_list_card_view.dart @@ -3,18 +3,17 @@ import 'package:dungeon_paper/app/modules/Home/views/expanded_card_dialog_view.d import 'package:dungeon_paper/core/utils/builder_utils.dart'; import 'package:dungeon_paper/core/utils/list_utils.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; class HorizontalCardListView extends StatelessWidget { HorizontalCardListView({ - Key? key, + super.key, required this.cardSize, required this.items, required this.cardBuilder, required this.expandedCardBuilder, this.leading = const [], this.trailing = const [], - }) : super(key: key); + }); final Size cardSize; final Widget Function( @@ -27,7 +26,7 @@ class HorizontalCardListView extends StatelessWidget { // void Function(Iterable) onUpdate ) expandedCardBuilder; final Iterable items; - final itemsObs = [].obs; + final itemsObs = ValueNotifier>([]); final List leading; final List trailing; @@ -36,7 +35,8 @@ class HorizontalCardListView extends StatelessWidget { if (items.isEmpty) { return const SizedBox.shrink(); } - final displayedItems = enumerate(itemsObs.isEmpty ? items : itemsObs); + final displayedItems = + enumerate(itemsObs.value.isEmpty ? items : itemsObs.value); final builder = ItemBuilder.builder( leadingCount: leading.length, @@ -87,8 +87,9 @@ class HorizontalCardListView extends StatelessWidget { context, item.value, item.index, - () => Get.dialog( - ExpandedCardDialogView( + () => showDialog( + context: context, + builder: (_) => ExpandedCardDialogView( // heroTag: getKeyFor(item.value), heroTag: null, builder: (context) => diff --git a/lib/app/modules/ImportExport/bindings/import_export_binding.dart b/lib/app/modules/ImportExport/bindings/import_export_binding.dart deleted file mode 100644 index ff4dbd05..00000000 --- a/lib/app/modules/ImportExport/bindings/import_export_binding.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:get/get.dart'; - -import '../controllers/export_controller.dart'; -import '../controllers/import_controller.dart'; -import '../controllers/import_export_controller.dart'; - -class ImportExportBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut( - () => ImportExportController(), - ); - Get.lazyPut( - () => ExportController(), - ); - Get.lazyPut( - () => ImportController(), - ); - } -} diff --git a/lib/app/modules/ImportExport/controllers/export_controller.dart b/lib/app/modules/ImportExport/controllers/export_controller.dart index c1d45bc3..1bd17bf8 100644 --- a/lib/app/modules/ImportExport/controllers/export_controller.dart +++ b/lib/app/modules/ImportExport/controllers/export_controller.dart @@ -7,40 +7,34 @@ import 'package:dungeon_paper/app/data/models/meta.dart'; import 'package:dungeon_paper/app/data/models/move.dart'; import 'package:dungeon_paper/app/data/models/race.dart'; import 'package:dungeon_paper/app/data/models/spell.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; +import 'package:dungeon_paper/app/data/services/repository_provider.dart'; import 'package:dungeon_paper/core/utils/list_utils.dart'; -import 'package:get/get.dart'; +import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import '../../../data/services/character_provider.dart'; import '../platforms/platform_export.dart'; import 'import_export_controller.dart'; - -class ExportController extends GetxController - with - GetSingleTickerProviderStateMixin, - CharacterServiceMixin, - RepositoryServiceMixin +class ExportController extends ChangeNotifier + with CharacterProviderMixin, RepositoryProviderMixin implements ImportExportSelectionData { - final toExport = ExportSelections().obs; + final toExport = ExportSelections(); - List get characters => characterService.allAsList; + List get characters => characterProvider.allAsList; List get moves => repo.my.moves.values.toList(); List get spells => repo.my.spells.values.toList(); List get items => repo.my.items.values.toList(); List get classes => repo.my.classes.values.toList(); List get races => repo.my.races.values.toList(); - @override - void onInit() { - super.onInit(); - toExport.value.characters = List.from(characters); - toExport.value.moves = List.from(moves); - toExport.value.spells = List.from(spells); - toExport.value.items = List.from(items); - toExport.value.classes = List.from(classes); - toExport.value.races = List.from(races); + ExportController() { + toExport.characters = List.from(characters); + toExport.moves = List.from(moves); + toExport.spells = List.from(spells); + toExport.items = List.from(items); + toExport.classes = List.from(classes); + toExport.races = List.from(races); } @override @@ -54,54 +48,57 @@ class ExportController extends GetxController void _toggleExportList(List items, bool state) { switch (T) { case == Character: - toExport.value.characters = _toggleInList( - toExport.value.characters, items.cast(), state); + toExport.characters = + _toggleInList(toExport.characters, items.cast(), state); break; case == Move: - toExport.value.moves = - _toggleInList(toExport.value.moves, items.cast(), state); + toExport.moves = + _toggleInList(toExport.moves, items.cast(), state); break; case == Spell: - toExport.value.spells = - _toggleInList(toExport.value.spells, items.cast(), state); + toExport.spells = + _toggleInList(toExport.spells, items.cast(), state); break; case == Item: - toExport.value.items = - _toggleInList(toExport.value.items, items.cast(), state); + toExport.items = + _toggleInList(toExport.items, items.cast(), state); break; case == CharacterClass: - toExport.value.classes = _toggleInList( - toExport.value.classes, items.cast(), state); + toExport.classes = _toggleInList( + toExport.classes, items.cast(), state); break; case == Race: - toExport.value.races = - _toggleInList(toExport.value.races, items.cast(), state); + toExport.races = + _toggleInList(toExport.races, items.cast(), state); break; } - toExport.refresh(); + notifyListeners(); } @override bool isSelected(T item) { - return toExport.value.listByType().map((x) => x.key).contains(item.key); + return toExport.listByType().map((x) => x.key).contains(item.key); } List _toggleInList(List list, List items, bool state) { + late List res; if (state) { - return addByKey(list, items); + res = addByKey(list, items); } else { - return removeByKey(list, items); + res = removeByKey(list, items); } + notifyListeners(); + return res; } - void Function()? getDoExport() { + void Function()? getDoExport(BuildContext context) { return () async { - final strData = utf8.encode(json.encode(toExport.value.toJson())); + final strData = utf8.encode(json.encode(toExport.toJson())); final dt = DateFormat('yy-MM-dd_HH.mm.ss').format(DateTime.now()); final fileName = 'DungeonPaperV2_$dt.json'; - Exporter().export(strData, fileName); + Exporter().export(context, strData, fileName); }; } diff --git a/lib/app/modules/ImportExport/controllers/import_controller.dart b/lib/app/modules/ImportExport/controllers/import_controller.dart index 52f5531b..ccbbad76 100644 --- a/lib/app/modules/ImportExport/controllers/import_controller.dart +++ b/lib/app/modules/ImportExport/controllers/import_controller.dart @@ -12,29 +12,28 @@ import 'package:dungeon_paper/core/storage_handler/storage_handler.dart'; import 'package:dungeon_paper/core/utils/list_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:file_picker/file_picker.dart'; -import 'package:get/get.dart'; +import 'package:flutter/material.dart'; import 'import_export_controller.dart'; -class ImportController extends GetxController - with GetSingleTickerProviderStateMixin +class ImportController extends ChangeNotifier implements ImportExportSelectionData { - final Rx toImport = Rx(null); + ImportSelections? toImport; - List get characters => toImport.value!.allCharacters.toList(); - List get moves => toImport.value!.allMoves.toList(); - List get spells => toImport.value!.allSpells.toList(); - List get items => toImport.value!.allItems.toList(); - List get classes => toImport.value!.allClasses.toList(); - List get races => toImport.value!.allRaces.toList(); + List get characters => toImport!.allCharacters.toList(); + List get moves => toImport!.allMoves.toList(); + List get spells => toImport!.allSpells.toList(); + List get items => toImport!.allItems.toList(); + List get classes => toImport!.allClasses.toList(); + List get races => toImport!.allRaces.toList(); int get selectionsCount => [characters, moves, spells, items, classes] .fold(0, (total, list) => total + list.length); - bool get hasData => toImport.value != null; + bool get hasData => toImport != null; - final importStep = Rx(null); - final leftCount = 0.obs; + Type? importStep; + var leftCount = 0; @override void toggle(T item, bool state) => @@ -47,36 +46,36 @@ class ImportController extends GetxController void _toggleImportList(List items, bool state) { switch (T) { case == Character: - toImport.value!.characters = _toggleInList( - toImport.value!.characters, items.cast(), state); + toImport!.characters = + _toggleInList(toImport!.characters, items.cast(), state); break; case == Move: - toImport.value!.moves = - _toggleInList(toImport.value!.moves, items.cast(), state); + toImport!.moves = + _toggleInList(toImport!.moves, items.cast(), state); break; case == Spell: - toImport.value!.spells = - _toggleInList(toImport.value!.spells, items.cast(), state); + toImport!.spells = + _toggleInList(toImport!.spells, items.cast(), state); break; case == Item: - toImport.value!.items = - _toggleInList(toImport.value!.items, items.cast(), state); + toImport!.items = + _toggleInList(toImport!.items, items.cast(), state); break; case == CharacterClass: - toImport.value!.classes = _toggleInList( - toImport.value!.classes, items.cast(), state); + toImport!.classes = _toggleInList( + toImport!.classes, items.cast(), state); break; case == Race: - toImport.value!.races = - _toggleInList(toImport.value!.races, items.cast(), state); + toImport!.races = + _toggleInList(toImport!.races, items.cast(), state); break; } - toImport.refresh(); + notifyListeners(); } @override bool isSelected(T item) { - return toImport.value! + return toImport! .listByType(selected: true) .map((x) => x.key) .contains(item.key); @@ -109,13 +108,24 @@ class ImportController extends GetxController throw TypeError(); } - void pickImportFile() async { - var result = - await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['json']); + void pickImportFile(BuildContext context) async { + final messenger = ScaffoldMessenger.of(context); + final theme = Theme.of(context); + final result = await FilePicker.platform + .pickFiles(type: FileType.custom, allowedExtensions: ['json']); if (result == null) { - Get.rawSnackbar( - title: tr.backup.importing.error.title, - message: tr.backup.importing.error.message, + messenger.showSnackBar( + SnackBar( + content: Column( + children: [ + Text( + tr.backup.importing.error.title, + style: theme.textTheme.bodyLarge, + ), + Text(tr.backup.importing.error.message), + ], + ), + ), ); return; } @@ -123,56 +133,78 @@ class ImportController extends GetxController final filedata = result.files.single.bytes; final filestring = utf8.decode(filedata as List); final filejson = json.decode(filestring); - toImport.value = ImportSelections.fromJson(filejson); + toImport = ImportSelections.fromJson(filejson); } - void Function()? getDoImport() { - if (!hasData || toImport.value?.hasSelections != true) { + void Function()? getDoImport(BuildContext context) { + if (!hasData || toImport?.hasSelections != true) { return null; } + final messenger = ScaffoldMessenger.of(context); + final theme = Theme.of(context); return () async { - leftCount.value = selectionsCount; + leftCount = selectionsCount; + final navigator = Navigator.of(context); - Get.dialog(const ImportProgressDialog(), barrierDismissible: false); - importStep.value = Character; + showDialog( + context: context, + builder: (_) => const ImportProgressDialog(), + barrierDismissible: false); + importStep = Character; await Future.delayed(const Duration(milliseconds: 500)); for (final char in characters) { await StorageHandler.instance .create('Characters', char.key, char.toJson()); - leftCount.value -= 1; + leftCount -= 1; + notifyListeners(); } - importStep.value = CharacterClass; + importStep = CharacterClass; for (final cls in classes) { await StorageHandler.instance .create('CharacterClasses', cls.key, cls.toJson()); - leftCount.value -= 1; + leftCount -= 1; + notifyListeners(); } - importStep.value = Move; + importStep = Move; for (final move in moves) { await StorageHandler.instance.create('Moves', move.key, move.toJson()); - leftCount.value -= 1; + leftCount -= 1; + notifyListeners(); } - importStep.value = Spell; + importStep = Spell; for (final spell in spells) { await StorageHandler.instance .create('Spells', spell.key, spell.toJson()); - leftCount.value -= 1; + leftCount -= 1; + notifyListeners(); } - importStep.value = Item; + importStep = Item; for (final items in items) { await StorageHandler.instance .create('Items', items.key, items.toJson()); - leftCount.value -= 1; + leftCount -= 1; + notifyListeners(); } await Future.delayed(const Duration(milliseconds: 500)); - Get.back(); + navigator.pop(); - Get.rawSnackbar( - title: tr.backup.importing.success.title, - message: tr.backup.importing.success.message, + messenger.showSnackBar( + SnackBar( + content: Column( + children: [ + Text( + tr.backup.importing.success.title, + style: theme.textTheme.bodyLarge, + ), + Text( + tr.backup.importing.success.message, + ), + ], + ), + ), ); }; } diff --git a/lib/app/modules/ImportExport/controllers/import_export_controller.dart b/lib/app/modules/ImportExport/controllers/import_export_controller.dart index f58b20fd..b05f233e 100644 --- a/lib/app/modules/ImportExport/controllers/import_export_controller.dart +++ b/lib/app/modules/ImportExport/controllers/import_export_controller.dart @@ -1,32 +1,16 @@ import 'package:dungeon_paper/app/data/models/meta.dart'; import 'package:dungeon_paper/app/modules/ImportExport/controllers/export_controller.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; import 'import_controller.dart'; -class ImportExportController extends GetxController - with GetSingleTickerProviderStateMixin { - late final Rx tab; - - @override - void onInit() { - super.onInit(); - tab = (TabController(length: 2, vsync: this)..addListener(_refresh)).obs; - } - - @override - void dispose() { - super.dispose(); - tab.value.removeListener(_refresh); - } - - void Function()? get doExport => Get.find().getDoExport(); - void Function()? get doImport => Get.find().getDoImport(); - - void _refresh() { - tab.refresh(); - } +// TODO remove? +class ImportExportController extends ChangeNotifier { + void Function()? doExport(BuildContext context) => + Provider.of(context, listen: false).getDoExport(context); + void Function()? doImport(BuildContext context) => + Provider.of(context, listen: false).getDoImport(context); } abstract class ImportExportSelectionData { diff --git a/lib/app/modules/ImportExport/local_widgets/import_progress_dialog.dart b/lib/app/modules/ImportExport/local_widgets/import_progress_dialog.dart index d52a68b0..0657e853 100644 --- a/lib/app/modules/ImportExport/local_widgets/import_progress_dialog.dart +++ b/lib/app/modules/ImportExport/local_widgets/import_progress_dialog.dart @@ -1,17 +1,17 @@ import 'package:dungeon_paper/app/modules/ImportExport/controllers/import_controller.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; -class ImportProgressDialog extends GetView { +class ImportProgressDialog extends StatelessWidget { const ImportProgressDialog({super.key}); @override Widget build(BuildContext context) { - return Obx( - () { + return Consumer( + builder: (context, controller, _) { final completedCount = - controller.selectionsCount - controller.leftCount.value; + controller.selectionsCount - controller.leftCount; final totalCount = controller.selectionsCount; return SimpleDialog( title: Text(tr.backup.importing.progress.title), @@ -21,7 +21,7 @@ class ImportProgressDialog extends GetView { children: [ Text( tr.backup.importing.progress.processing( - tr.entityPlural(tn(controller.importStep.value!)), + tr.entityPlural(tn(controller.importStep!)), ), ), const SizedBox(height: 8), diff --git a/lib/app/modules/ImportExport/local_widgets/list_card.dart b/lib/app/modules/ImportExport/local_widgets/list_card.dart index a6fff8d5..2200c208 100644 --- a/lib/app/modules/ImportExport/local_widgets/list_card.dart +++ b/lib/app/modules/ImportExport/local_widgets/list_card.dart @@ -4,7 +4,7 @@ import 'package:dungeon_paper/app/widgets/atoms/custom_expansion_panel.dart'; import 'package:dungeon_paper/app/widgets/atoms/menu_button.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; import '../../../../core/dw_icons.dart'; import '../../../data/models/character.dart'; @@ -17,90 +17,94 @@ import '../../../widgets/chips/primary_chip.dart'; enum ListCardType { import, export } class ListCard - extends GetView { + extends StatelessWidget { const ListCard({ super.key, required this.type, }); final ListCardType type; - List get list => controller.listByType(); + List list(BuildContext context) => + Provider.of(context, listen: false).listByType(); @override Widget build(BuildContext context) { final textTheme = Theme.of(context).textTheme; - return Obx( - () => Card( - margin: const EdgeInsets.only(top: 16), - child: CustomExpansionPanel( - initiallyExpanded: true, - title: Row( - children: [ - Icon( - Meta.genericIconFor(T), - color: textTheme.titleLarge!.color, - ), - const SizedBox(width: 8), - Expanded( - child: Text( - type == ListCardType.import - ? tr.entityPlural(tn(T)) - : tr.generic.myEntity(tr.entityPlural(tn(T))), - style: textTheme.titleLarge, + return Consumer( + builder: (context, controller, _) { + final list = this.list(context); + return Card( + margin: const EdgeInsets.only(top: 16), + child: CustomExpansionPanel( + initiallyExpanded: true, + title: Row( + children: [ + Icon( + Meta.genericIconFor(T), + color: textTheme.titleLarge!.color, ), - ), - ], - ), - trailing: [ - MenuButton( - items: [ - MenuEntry( - value: true, - icon: const Icon(Icons.select_all), - label: Text(tr.generic.selectAll), - onSelect: () => controller.toggleAll(true), - ), - MenuEntry( - value: false, - icon: const Icon(Icons.clear), - label: Text(tr.generic.selectNone), - onSelect: () => controller.toggleAll(false), + const SizedBox(width: 8), + Expanded( + child: Text( + type == ListCardType.import + ? tr.entityPlural(tn(T)) + : tr.generic.myEntity(tr.entityPlural(tn(T))), + style: textTheme.titleLarge, + ), ), ], ), - ], - children: [ - for (final item in list) - Builder(builder: (context) { - final tags = tagsByType(item); - return ListTile( - onTap: () => controller.toggle( - item, !controller.isSelected(item)), - title: Text(item.displayName), - subtitle: tags.isNotEmpty - ? Wrap( - spacing: 8, - children: tags, - ) - : null, - leading: Checkbox( - value: controller.isSelected(item), - onChanged: (state) => controller.toggle(item, state!), + trailing: [ + MenuButton( + items: [ + MenuEntry( + value: true, + icon: const Icon(Icons.select_all), + label: Text(tr.generic.selectAll), + onSelect: () => controller.toggleAll(true), ), - ); - }), - if (list.isEmpty) - Padding( - padding: const EdgeInsets.all(8), - child: Text( - tr.generic.noEntity(tr.entityPlural(tn(T))), - textAlign: TextAlign.center, - ), + MenuEntry( + value: false, + icon: const Icon(Icons.clear), + label: Text(tr.generic.selectNone), + onSelect: () => controller.toggleAll(false), + ), + ], ), - ], - ), - ), + ], + children: [ + for (final item in list) + Builder(builder: (context) { + final tags = tagsByType(item); + return ListTile( + onTap: () => controller.toggle( + item, !controller.isSelected(item)), + title: Text(item.displayName), + subtitle: tags.isNotEmpty + ? Wrap( + spacing: 8, + children: tags, + ) + : null, + leading: Checkbox( + value: controller.isSelected(item), + onChanged: (state) => controller.toggle(item, state!), + ), + ); + }), + if (list.isEmpty) + Padding( + padding: const EdgeInsets.all(8), + child: Text( + tr.generic.noEntity(tr.entityPlural(tn(T))), + textAlign: TextAlign.center, + ), + ), + ], + ), + ); + }, ); } @@ -167,4 +171,3 @@ class ListCard } } } - diff --git a/lib/app/modules/ImportExport/platforms/abstract_export.dart b/lib/app/modules/ImportExport/platforms/abstract_export.dart index e6cf9190..107ee8ce 100644 --- a/lib/app/modules/ImportExport/platforms/abstract_export.dart +++ b/lib/app/modules/ImportExport/platforms/abstract_export.dart @@ -1,12 +1,14 @@ import 'dart:typed_data'; +import 'package:flutter/material.dart'; + abstract class AbstractExporter { - void export(Uint8List data, String filename); + void export(BuildContext context, Uint8List data, String filename); } class Exporter extends AbstractExporter { @override - void export(Uint8List data, String filename) { + void export(BuildContext context, Uint8List data, String filename) { throw UnimplementedError('Unsupported platform is unsupported'); } } diff --git a/lib/app/modules/ImportExport/platforms/native_export.dart b/lib/app/modules/ImportExport/platforms/native_export.dart index 0b17b0ed..218de236 100644 --- a/lib/app/modules/ImportExport/platforms/native_export.dart +++ b/lib/app/modules/ImportExport/platforms/native_export.dart @@ -2,15 +2,17 @@ import 'dart:io'; import 'dart:typed_data'; import 'package:dungeon_paper/app/modules/ImportExport/platforms/abstract_export.dart'; +import 'package:dungeon_paper/app/widgets/atoms/custom_snack_bar.dart'; import 'package:dungeon_paper/i18n.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_file_dialog/flutter_file_dialog.dart'; -import 'package:get/get.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; class Exporter extends AbstractExporter { @override - void export(Uint8List data, String filename) async { + void export(BuildContext context, Uint8List data, String filename) async { + final snackBar = CustomSnackBar.deferred(context); final tmp = await getTemporaryDirectory(); final tmpFile = File(path.join(tmp.path, filename)); @@ -20,20 +22,20 @@ class Exporter extends AbstractExporter { try { final path = await FlutterFileDialog.saveFile(params: params); if (path == null) { - Get.rawSnackbar( + snackBar.show( title: tr.backup.exporting.error.title, - message: tr.errors.userOperationCanceled, + content: tr.errors.userOperationCanceled, ); } else { - Get.rawSnackbar( + snackBar.show( title: tr.backup.exporting.success.title, - message: tr.backup.exporting.success.message, + content: tr.backup.exporting.success.message, ); } } catch (e) { - Get.rawSnackbar( + snackBar.show( title: tr.backup.exporting.error.title, - message: tr.backup.exporting.error.message, + content: tr.backup.exporting.error.message, ); rethrow; } diff --git a/lib/app/modules/ImportExport/platforms/platform_export.dart b/lib/app/modules/ImportExport/platforms/platform_export.dart index 0714aafe..7707703d 100644 --- a/lib/app/modules/ImportExport/platforms/platform_export.dart +++ b/lib/app/modules/ImportExport/platforms/platform_export.dart @@ -1,4 +1,3 @@ export './abstract_export.dart' if (dart.library.io) 'package:dungeon_paper/app/modules/ImportExport/platforms/native_export.dart' if (dart.library.html) 'package:dungeon_paper/app/modules/ImportExport/platforms/web_export.dart'; - diff --git a/lib/app/modules/ImportExport/views/export_view.dart b/lib/app/modules/ImportExport/views/export_view.dart index fb8661bc..a172ae3b 100644 --- a/lib/app/modules/ImportExport/views/export_view.dart +++ b/lib/app/modules/ImportExport/views/export_view.dart @@ -8,13 +8,11 @@ import 'package:dungeon_paper/core/utils/builder_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; - import '../../../widgets/dialogs/export_class_dialog.dart'; import '../controllers/export_controller.dart'; import '../local_widgets/list_card.dart'; -class ExportView extends GetView { +class ExportView extends StatelessWidget { const ExportView({super.key}); @override @@ -34,10 +32,13 @@ class ExportView extends GetView { ), ], ), - () => const ListCard(type: ListCardType.export), - () => const ListCard(type: ListCardType.export), + () => const ListCard( + type: ListCardType.export), + () => const ListCard( + type: ListCardType.export), () => const ListCard(type: ListCardType.export), - () => const ListCard(type: ListCardType.export), + () => + const ListCard(type: ListCardType.export), () => const ListCard(type: ListCardType.export), () => const ListCard(type: ListCardType.export), ], @@ -52,4 +53,3 @@ class ExportView extends GetView { ); } } - diff --git a/lib/app/modules/ImportExport/views/import_export_view.dart b/lib/app/modules/ImportExport/views/import_export_view.dart index 97085283..684ca00e 100644 --- a/lib/app/modules/ImportExport/views/import_export_view.dart +++ b/lib/app/modules/ImportExport/views/import_export_view.dart @@ -1,15 +1,29 @@ import 'package:dungeon_paper/app/widgets/atoms/advanced_floating_action_button.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; import '../controllers/import_export_controller.dart'; import 'export_view.dart'; import 'import_view.dart'; -class ImportExportView extends GetView { +class ImportExportView extends StatefulWidget { const ImportExportView({super.key}); + @override + State createState() => _ImportExportViewState(); +} + +class _ImportExportViewState extends State + with SingleTickerProviderStateMixin { + late final TabController tab; + + @override + void initState() { + super.initState(); + tab = TabController(length: 2, vsync: this); + } + @override Widget build(BuildContext context) { final textStyle = TextStyle(color: Theme.of(context).colorScheme.onSurface); @@ -22,7 +36,7 @@ class ImportExportView extends GetView { body: Column( children: [ TabBar( - controller: controller.tab.value, + controller: tab, tabs: [ Tab(child: Text(tr.backup.exporting.title, style: textStyle)), Tab(child: Text(tr.backup.importing.title, style: textStyle)), @@ -30,7 +44,7 @@ class ImportExportView extends GetView { ), Expanded( child: TabBarView( - controller: controller.tab.value, + controller: tab, children: const [ ExportView(), ImportView(), @@ -39,18 +53,19 @@ class ImportExportView extends GetView { ) ], ), - floatingActionButton: Obx( - () => AdvancedFloatingActionButton.extended( - label: Text(controller.tab.value.index == 0 + floatingActionButton: Consumer( + builder: (context, controller, _) => + AdvancedFloatingActionButton.extended( + label: Text(tab.index == 0 ? tr.backup.exporting.button : tr.backup.importing.button), - icon: Icon( - controller.tab.value.index == 0 ? Icons.upload : Icons.download), - onPressed: controller.tab.value.index == 0 - ? controller.doExport - : controller.doImport, + icon: Icon(tab.index == 0 ? Icons.upload : Icons.download), + onPressed: tab.index == 0 + ? () => controller.doExport(context) + : () => controller.doImport(context), ), ), ); } } + diff --git a/lib/app/modules/ImportExport/views/import_view.dart b/lib/app/modules/ImportExport/views/import_view.dart index 14b137ff..860f7b56 100644 --- a/lib/app/modules/ImportExport/views/import_view.dart +++ b/lib/app/modules/ImportExport/views/import_view.dart @@ -8,40 +8,46 @@ import 'package:dungeon_paper/app/modules/ImportExport/controllers/import_contro import 'package:dungeon_paper/core/utils/builder_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; import '../local_widgets/list_card.dart'; -class ImportView extends GetView { +class ImportView extends StatelessWidget { const ImportView({super.key}); @override Widget build(BuildContext context) { return ListTileTheme.merge( contentPadding: EdgeInsets.zero, - child: Obx( - () { + child: Consumer( + builder: (context, controller, _) { final builder = controller.hasData ? ItemBuilder.lazyChildren( children: [ () => ElevatedButton.icon( - onPressed: () => controller.toImport.value = null, + onPressed: () => controller.toImport = null, icon: const Icon(Icons.clear), label: Text(tr.backup.importing.file.clearFile), ), - () => const ListCard(type: ListCardType.import), - () => const ListCard(type: ListCardType.import), - () => const ListCard(type: ListCardType.import), - () => const ListCard(type: ListCardType.import), - () => const ListCard(type: ListCardType.import), - () => const ListCard(type: ListCardType.import), + () => const ListCard( + type: ListCardType.import), + () => const ListCard( + type: ListCardType.import), + () => const ListCard( + type: ListCardType.import), + () => const ListCard( + type: ListCardType.import), + () => const ListCard( + type: ListCardType.import), + () => const ListCard( + type: ListCardType.import), ], ) : ItemBuilder.lazyChildren( children: [ () => Text(tr.backup.importing.file.info), () => ElevatedButton.icon( - onPressed: controller.pickImportFile, + onPressed: () => controller.pickImportFile(context), icon: const Icon(Icons.file_open), label: Text(tr.backup.importing.file.browse), ) diff --git a/lib/app/modules/LibraryList/bindings/library_collection_binding.dart b/lib/app/modules/LibraryList/bindings/library_collection_binding.dart deleted file mode 100644 index dda25fc0..00000000 --- a/lib/app/modules/LibraryList/bindings/library_collection_binding.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:get/get.dart'; - -// import '../controllers/library_collection_controller.dart'; - -class LibraryCollectionBinding extends Bindings { - @override - void dependencies() { - // Get.lazyPut( - // () => LibraryCollectionController(), - // ); - } -} diff --git a/lib/app/modules/LibraryList/bindings/library_form_binding.dart b/lib/app/modules/LibraryList/bindings/library_form_binding.dart deleted file mode 100644 index bf47ac76..00000000 --- a/lib/app/modules/LibraryList/bindings/library_form_binding.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:dungeon_paper/app/data/models/character_class.dart'; -import 'package:dungeon_paper/app/data/models/item.dart'; -import 'package:dungeon_paper/app/data/models/move.dart'; -import 'package:dungeon_paper/app/data/models/note.dart'; -import 'package:dungeon_paper/app/data/models/race.dart'; -import 'package:dungeon_paper/app/data/models/spell.dart'; -import 'package:dungeon_paper/app/widgets/forms/character_class_form.dart'; -import 'package:dungeon_paper/app/widgets/forms/item_form.dart'; -import 'package:dungeon_paper/app/widgets/forms/move_form.dart'; -import 'package:dungeon_paper/app/widgets/forms/note_form.dart'; -import 'package:dungeon_paper/app/widgets/forms/race_form.dart'; -import 'package:dungeon_paper/app/widgets/forms/spell_form.dart'; -import 'package:get/get.dart'; - -class LibraryFormBinding extends Bindings { - LibraryFormBinding(); - - @override - void dependencies() { - switch (T) { - case == Move: - Get.put(MoveFormController()); - break; - case == Spell: - Get.put(SpellFormController()); - break; - case == Item: - Get.put(ItemFormController()); - break; - case == Note: - Get.put(NoteFormController()); - break; - case == CharacterClass: - Get.put(CharacterClassFormController()); - break; - case == Race: - Get.put(RaceFormController()); - break; - default: - throw UnsupportedError('Type $T is unsupported'); - } - } -} diff --git a/lib/app/modules/LibraryList/bindings/library_list_binding.dart b/lib/app/modules/LibraryList/bindings/library_list_binding.dart deleted file mode 100644 index 8aeb67f2..00000000 --- a/lib/app/modules/LibraryList/bindings/library_list_binding.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:dungeon_paper/app/data/models/character_class.dart'; -import 'package:dungeon_paper/app/data/models/item.dart'; -import 'package:dungeon_paper/app/data/models/move.dart'; -import 'package:dungeon_paper/app/data/models/race.dart'; -import 'package:dungeon_paper/app/data/models/spell.dart'; -import 'package:dungeon_paper/app/modules/LibraryList/views/filters/race_filters.dart'; -import 'package:get/get.dart'; - -import 'package:dungeon_paper/app/modules/LibraryList/controllers/library_list_controller.dart'; - -import '../views/filters/character_class_filters.dart'; -import '../views/filters/item_filters.dart'; -import '../views/filters/move_filters.dart'; -import '../views/filters/spell_filters.dart'; - -class LibraryListBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut>( - () => LibraryListController(), - ); - Get.lazyPut>( - () => LibraryListController(), - ); - Get.lazyPut>( - () => LibraryListController(), - ); - Get.lazyPut>( - () => LibraryListController(), - ); - Get.lazyPut>( - () => LibraryListController(), - ); - } -} diff --git a/lib/app/modules/LibraryList/controllers/library_collection_controller.dart b/lib/app/modules/LibraryList/controllers/library_collection_controller.dart deleted file mode 100644 index a373fb88..00000000 --- a/lib/app/modules/LibraryList/controllers/library_collection_controller.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'package:get/get.dart'; - -class LibraryCollectionController extends GetxController { - // -} diff --git a/lib/app/modules/LibraryList/controllers/library_list_controller.dart b/lib/app/modules/LibraryList/controllers/library_list_controller.dart index 77abccbf..60a7a183 100644 --- a/lib/app/modules/LibraryList/controllers/library_list_controller.dart +++ b/lib/app/modules/LibraryList/controllers/library_list_controller.dart @@ -1,11 +1,13 @@ import 'dart:async'; import 'package:dungeon_paper/app/data/models/meta.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; -import 'package:dungeon_paper/app/data/services/library_service.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; +import 'package:dungeon_paper/app/data/services/library_provider.dart'; +import 'package:dungeon_paper/app/data/services/repository_provider.dart'; +import 'package:dungeon_paper/core/global_keys.dart'; +import 'package:dungeon_paper/core/route_arguments.dart'; +import 'package:dungeon_paper/core/utils/list_utils.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; enum FiltersGroup { playbook, @@ -14,63 +16,41 @@ enum FiltersGroup { } class LibraryListController> - extends GetxController - with - GetSingleTickerProviderStateMixin, - LibraryServiceMixin, - CharacterServiceMixin { - final repo = Get.find().obs; - final chars = Get.find().obs; + extends ChangeNotifier + with CharacterProviderMixin, RepositoryProviderMixin { + late final LibraryListArguments arguments; - final selected = [].obs; - final removed = [].obs; - final filters = {}.obs; - final search = {}.obs; + final selected = []; + final removed = []; + final filters = {}; + final search = {}; + // late final TabController tabController; - late final void Function(Iterable items)? onSelected; - late final bool Function(T item, F filters) filterFn; - late final int Function(T a, T b) Function(F filters) sortFn; - late final bool multiple; - late final Iterable preSelections; - late final Map extraData; - late final TabController tabController; + bool get selectable => arguments.onSelected != null; - bool get selectable => onSelected != null; - - Iterable get builtInList => - filterList(builtInListRaw, FiltersGroup.playbook, filterFn, sortFn); + Iterable get builtInList => filterList(builtInListRaw, + FiltersGroup.playbook, arguments.filterFn, arguments.sortFn); Iterable get builtInListRaw => - repo.value.builtIn.listByType().values.toList(); + repo.builtIn.listByType().values.toList(); - Iterable get myList => - filterList(myListRaw, FiltersGroup.my, filterFn, sortFn); + Iterable get myList => filterList( + myListRaw, FiltersGroup.my, arguments.filterFn, arguments.sortFn); - Iterable get myListRaw => repo.value.my.listByType().values.toList(); + Iterable get myListRaw => repo.my.listByType().values.toList(); String get storageKey => Meta.storageKeyFor(T); - @override - void onInit() { - super.onInit(); - assert(Get.arguments != null); - final LibraryListArguments args = Get.arguments; - filters.addAll(args.filters.cast()); - onSelected = args.onSelected; - filterFn = args.filterFn; - sortFn = args.sortFn; - multiple = args.multiple; - preSelections = args.preSelections; - extraData = args.extraData; + bool get multiple => arguments.multiple; + Map get extraData => arguments.extraData; + void Function(Iterable items)? get onSelected => arguments.onSelected; + + LibraryListController(BuildContext context) { + arguments = getArgs(context); + filters.addAll(arguments.filters.cast()); search[FiltersGroup.playbook] ??= TextEditingController(); search[FiltersGroup.playbook]!.addListener(_updatePlaybookSearch); search[FiltersGroup.my] ??= TextEditingController(); search[FiltersGroup.my]!.addListener(_updateMySearch); - tabController = TabController( - initialIndex: FiltersGroup.values.indexOf(args.initialTab), - // length: 3, - length: 2, - vsync: this, - ); } @override @@ -82,7 +62,7 @@ class LibraryListController> void setFilters(FiltersGroup group, F? filters) { this.filters[group] = filters; - this.filters.refresh(); + notifyListeners(); } void toggleItem(T item, bool state) { @@ -90,29 +70,27 @@ class LibraryListController> return; } - if (!multiple) { + if (!arguments.multiple) { selected.clear(); - for (final sel in preSelections) { - removed.addIf( - removed.firstWhereOrNull(_compare(sel)) == null, - sel, - ); + for (final sel in arguments.preSelections) { + if (removed.firstWhereOrNull(_compare(sel)) == null) { + removed.add(sel); + } } } if (state) { - selected.addIf( - selected.firstWhereOrNull(_compare(item)) == null, - item, - ); + if (selected.firstWhereOrNull(_compare(item)) == null) { + selected.add(item); + } removed.removeWhere(_compare(item)); } else { selected.removeWhere(_compare(item)); - removed.addIf( - removed.firstWhereOrNull(_compare(item)) == null, - item, - ); + if (removed.firstWhereOrNull(_compare(item)) == null) { + removed.add(item); + } } + notifyListeners(); } _compare(T item) { @@ -125,19 +103,23 @@ class LibraryListController> void saveCustomItem(String storageKey, T item) { toggleItem(item, true); debugPrint('Saving $item'); + final library = LibraryProvider.of(appGlobalKey.currentContext!); library.upsertToLibrary([item]); + notifyListeners(); } void deleteCustomItem(String storageKey, T item) { toggleItem(item, false); debugPrint('Deleting $item'); + final library = LibraryProvider.of(appGlobalKey.currentContext!); library.removeFromLibrary([item]); + notifyListeners(); } List get selectedWithMeta => selected; // selected.map((e) => forkMeta(e, Get.find().current)).toList(); - bool isSelected(T item) => multiple + bool isSelected(T item) => arguments.multiple ? // multiple: if is selected or pre-selected isInCurrentSelectedList(item) || isPreSelected(item) @@ -155,9 +137,9 @@ class LibraryListController> bool isRemoved(T item) => removed.firstWhereOrNull(_compare(item)) != null; bool isPreSelected(T item) => - preSelections.toList().firstWhereOrNull(_compare(item)) != null; + arguments.preSelections.toList().firstWhereOrNull(_compare(item)) != null; - bool isEnabled(T item) => multiple + bool isEnabled(T item) => arguments.multiple ? // multiple: if is not pre-selected !isPreSelected(item) @@ -187,14 +169,12 @@ class LibraryListController> void _updatePlaybookSearch() { filters[FiltersGroup.playbook] ?.setSearch(search[FiltersGroup.playbook]!.text); - search.refresh(); - repo.refresh(); + notifyListeners(); } void _updateMySearch() { filters[FiltersGroup.my]?.setSearch(search[FiltersGroup.my]!.text); - search.refresh(); - repo.refresh(); + notifyListeners(); } } @@ -243,3 +223,4 @@ abstract class LibraryListArguments> { +class CharacterClassesLibraryListView extends StatelessWidget { const CharacterClassesLibraryListView({super.key}); - RepositoryService get service => controller.repo.value; - Character get char => controller.chars.value.current; - @override Widget build(BuildContext context) { - return LibraryListView( - filtersBuilder: (group, filters, onChange) => CharacterClassFiltersView( - group: group, - filters: filters, - onChange: (f) => onChange(group, f), - searchController: controller.search[group]!, - ), - cardBuilder: (ctx, data) => CharacterClassCard( - characterClass: data.item, - showDice: false, - showStar: false, - highlightWords: data.highlightWords, - trailing: [ - if (controller.selectable) - LibrarySelectButton.icon( - selected: data.selected, - onPressed: data.onToggle, - ) - ], - actions: [ - EntityEditMenu( - onEdit: data.onUpdate != null - ? () => ModelPages.openCharacterClassPage( - characterClass: data.item, - onSave: data.onUpdate!, - ) - : null, - onDelete: - data.onDelete != null ? () => data.onDelete!(data.item) : null, - ), - if (controller.selectable) - LibrarySelectButton( - selected: data.selected, - onPressed: data.onToggle, - ) - ], + return Consumer< + LibraryListController>( + builder: (context, controller, _) => + LibraryListView( + filtersBuilder: (group, filters, onChange) => CharacterClassFiltersView( + group: group, + filters: filters, + onChange: (f) => onChange(group, f), + searchController: controller.search[group]!, + ), + cardBuilder: (ctx, data) => CharacterClassCard( + characterClass: data.item, + showDice: false, + showStar: false, + highlightWords: data.highlightWords, + trailing: [ + if (controller.selectable) + LibrarySelectButton.icon( + selected: data.selected, + onPressed: data.onToggle, + ) + ], + actions: [ + EntityEditMenu( + onEdit: data.onUpdate != null + ? () => ModelPages.openCharacterClassPage( + context, + characterClass: data.item, + onSave: data.onUpdate!, + ) + : null, + onDelete: data.onDelete != null + ? () => data.onDelete!(data.item) + : null, + ), + if (controller.selectable) + LibrarySelectButton( + selected: data.selected, + onPressed: data.onToggle, + ) + ], + ), ), ); } @@ -83,4 +81,3 @@ class CharacterClassLibraryListArguments multiple: false, ); } - diff --git a/lib/app/modules/LibraryList/views/entity_filters.dart b/lib/app/modules/LibraryList/views/entity_filters.dart index d2ef7c1d..bd962b9c 100644 --- a/lib/app/modules/LibraryList/views/entity_filters.dart +++ b/lib/app/modules/LibraryList/views/entity_filters.dart @@ -1,17 +1,15 @@ import 'dart:math'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; import 'package:dungeon_paper/app/modules/LibraryList/controllers/library_list_controller.dart'; import 'package:dungeon_paper/app/widgets/atoms/search_field.dart'; import 'package:dungeon_paper/app/widgets/chips/primary_chip.dart'; import 'package:dungeon_paper/core/utils/list_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:popover/popover.dart'; class EntityFiltersView> extends StatelessWidget { - EntityFiltersView({ + const EntityFiltersView({ super.key, required this.typeName, required this.filters, @@ -27,7 +25,6 @@ class EntityFiltersView> extends StatelessWidget { final F emptyFilters; final List Function(BuildContext context, F filters)? filterWidgetsBuilder; - final service = Get.find(); final void Function(F) onChange; final TextEditingController searchController; final Iterable leading; diff --git a/lib/app/modules/LibraryList/views/filters/character_class_filters.dart b/lib/app/modules/LibraryList/views/filters/character_class_filters.dart index 265bdbf3..4cf860c6 100644 --- a/lib/app/modules/LibraryList/views/filters/character_class_filters.dart +++ b/lib/app/modules/LibraryList/views/filters/character_class_filters.dart @@ -1,5 +1,4 @@ import 'package:dungeon_paper/app/data/models/character_class.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; import 'package:dungeon_paper/app/modules/LibraryList/controllers/library_list_controller.dart'; import 'package:dungeon_paper/app/modules/LibraryList/views/entity_filters.dart'; import 'package:dungeon_paper/core/utils/math_utils.dart'; @@ -7,11 +6,10 @@ import 'package:dungeon_paper/core/utils/string_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:string_similarity/string_similarity.dart'; class CharacterClassFiltersView extends StatelessWidget { - CharacterClassFiltersView({ + const CharacterClassFiltersView({ super.key, required this.filters, required this.group, @@ -21,7 +19,6 @@ class CharacterClassFiltersView extends StatelessWidget { final CharacterClassFilters filters; final FiltersGroup group; - final repo = Get.find(); final void Function(CharacterClassFilters) onChange; final TextEditingController searchController; diff --git a/lib/app/modules/LibraryList/views/filters/item_filters.dart b/lib/app/modules/LibraryList/views/filters/item_filters.dart index 6adf4c7d..fa40619d 100644 --- a/lib/app/modules/LibraryList/views/filters/item_filters.dart +++ b/lib/app/modules/LibraryList/views/filters/item_filters.dart @@ -1,16 +1,14 @@ import 'package:dungeon_paper/app/data/models/item.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; import 'package:dungeon_paper/app/modules/LibraryList/controllers/library_list_controller.dart'; import 'package:dungeon_paper/app/modules/LibraryList/views/entity_filters.dart'; import 'package:dungeon_paper/core/utils/math_utils.dart'; import 'package:dungeon_paper/core/utils/string_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:string_similarity/string_similarity.dart'; class ItemFiltersView extends StatelessWidget { - ItemFiltersView({ + const ItemFiltersView({ super.key, required this.filters, required this.onChange, @@ -18,7 +16,6 @@ class ItemFiltersView extends StatelessWidget { }); final ItemFilters filters; - final service = Get.find(); final void Function(ItemFilters) onChange; final TextEditingController searchController; diff --git a/lib/app/modules/LibraryList/views/filters/move_filters.dart b/lib/app/modules/LibraryList/views/filters/move_filters.dart index c3692720..ed5f5672 100644 --- a/lib/app/modules/LibraryList/views/filters/move_filters.dart +++ b/lib/app/modules/LibraryList/views/filters/move_filters.dart @@ -1,6 +1,6 @@ import 'package:dungeon_paper/app/data/models/character_class.dart'; import 'package:dungeon_paper/app/data/models/move.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; +import 'package:dungeon_paper/app/data/services/repository_provider.dart'; import 'package:dungeon_paper/app/modules/LibraryList/controllers/library_list_controller.dart'; import 'package:dungeon_paper/app/modules/LibraryList/views/entity_filters.dart'; import 'package:dungeon_paper/app/widgets/atoms/select_box.dart'; @@ -8,11 +8,10 @@ import 'package:dungeon_paper/core/utils/math_utils.dart'; import 'package:dungeon_paper/core/utils/string_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:string_similarity/string_similarity.dart'; class MoveFiltersView extends StatelessWidget { - MoveFiltersView({ + const MoveFiltersView({ super.key, required this.filters, required this.group, @@ -22,67 +21,68 @@ class MoveFiltersView extends StatelessWidget { final MoveFilters filters; final FiltersGroup group; - final repo = Get.find(); final void Function(MoveFilters) onChange; final TextEditingController searchController; @override Widget build(BuildContext context) { - return EntityFiltersView( - filters: filters, - emptyFilters: MoveFilters(classKey: null), - onChange: onChange, - searchController: searchController, - typeName: tn(Move), - filterWidgetsBuilder: (context, f) => [ - SelectBox( - isExpanded: true, - label: Text(tr.entityPlural('MoveCategory')), - value: f.category, - items: [ - DropdownMenuItem( - value: null, - child: - Text(tr.generic.allEntities(tr.entityPlural('MoveCategory'))), - ), - ...MoveCategory.values.map( - (cat) => DropdownMenuItem( - value: cat, - child: Text(tr.moves.category.longName(cat.name)), + return RepositoryProvider.consumer( + (context, repo, _) => EntityFiltersView( + filters: filters, + emptyFilters: MoveFilters(classKey: null), + onChange: onChange, + searchController: searchController, + typeName: tn(Move), + filterWidgetsBuilder: (context, f) => [ + SelectBox( + isExpanded: true, + label: Text(tr.entityPlural('MoveCategory')), + value: f.category, + items: [ + DropdownMenuItem( + value: null, + child: Text( + tr.generic.allEntities(tr.entityPlural('MoveCategory'))), ), - ), - ], - onChanged: (cat) { - onChange(f..category = cat); - f.controller.add(f); - }, - ), - SelectBox( - label: Text(tr.entityPlural(tn(CharacterClass))), - isExpanded: true, - value: f.classKey, - items: [ - DropdownMenuItem( - value: null, - child: Text( - tr.generic.allEntities(tr.entityPlural(tn(CharacterClass)))), - ), - ...{ - ...repo.builtIn.classes.values, - ...repo.my.classes.values - }.map( - (cls) => DropdownMenuItem( - value: cls.key, - child: Text(cls.name), + ...MoveCategory.values.map( + (cat) => DropdownMenuItem( + value: cat, + child: Text(tr.moves.category.longName(cat.name)), + ), ), - ), - ], - onChanged: (key) { - onChange(f..classKey = key); - f.controller.add(f); - }, - ), - ], + ], + onChanged: (cat) { + onChange(f..category = cat); + f.controller.add(f); + }, + ), + SelectBox( + label: Text(tr.entityPlural(tn(CharacterClass))), + isExpanded: true, + value: f.classKey, + items: [ + DropdownMenuItem( + value: null, + child: Text(tr.generic + .allEntities(tr.entityPlural(tn(CharacterClass)))), + ), + ...{ + ...repo.builtIn.classes.values, + ...repo.my.classes.values + }.map( + (cls) => DropdownMenuItem( + value: cls.key, + child: Text(cls.name), + ), + ), + ], + onChanged: (key) { + onChange(f..classKey = key); + f.controller.add(f); + }, + ), + ], + ), ); } } diff --git a/lib/app/modules/LibraryList/views/filters/note_filters.dart b/lib/app/modules/LibraryList/views/filters/note_filters.dart index 6ca4e02f..a20d6f0b 100644 --- a/lib/app/modules/LibraryList/views/filters/note_filters.dart +++ b/lib/app/modules/LibraryList/views/filters/note_filters.dart @@ -1,24 +1,21 @@ import 'package:dungeon_paper/app/data/models/note.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; import 'package:dungeon_paper/app/modules/LibraryList/controllers/library_list_controller.dart'; import 'package:dungeon_paper/app/modules/LibraryList/views/entity_filters.dart'; import 'package:dungeon_paper/core/utils/math_utils.dart'; import 'package:dungeon_paper/core/utils/string_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:string_similarity/string_similarity.dart'; class NoteFiltersView extends StatelessWidget { - NoteFiltersView({ - Key? key, + const NoteFiltersView({ + super.key, required this.filters, required this.onChange, required this.searchController, - }) : super(key: key); + }); final NoteFilters filters; - final service = Get.find(); final void Function(NoteFilters) onChange; final TextEditingController searchController; diff --git a/lib/app/modules/LibraryList/views/filters/race_filters.dart b/lib/app/modules/LibraryList/views/filters/race_filters.dart index 1cf8014f..041ce659 100644 --- a/lib/app/modules/LibraryList/views/filters/race_filters.dart +++ b/lib/app/modules/LibraryList/views/filters/race_filters.dart @@ -1,6 +1,6 @@ import 'package:dungeon_paper/app/data/models/character_class.dart'; import 'package:dungeon_paper/app/data/models/race.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; +import 'package:dungeon_paper/app/data/services/repository_provider.dart'; import 'package:dungeon_paper/app/modules/LibraryList/controllers/library_list_controller.dart'; import 'package:dungeon_paper/app/modules/LibraryList/views/entity_filters.dart'; import 'package:dungeon_paper/app/widgets/atoms/select_box.dart'; @@ -8,11 +8,10 @@ import 'package:dungeon_paper/core/utils/math_utils.dart'; import 'package:dungeon_paper/core/utils/string_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:string_similarity/string_similarity.dart'; class RaceFiltersView extends StatelessWidget { - RaceFiltersView({ + const RaceFiltersView({ super.key, required this.filters, required this.group, @@ -22,45 +21,46 @@ class RaceFiltersView extends StatelessWidget { final RaceFilters filters; final FiltersGroup group; - final repo = Get.find(); final void Function(RaceFilters) onChange; final TextEditingController searchController; @override Widget build(BuildContext context) { - return EntityFiltersView( - filters: filters, - emptyFilters: RaceFilters(classKey: null), - onChange: onChange, - searchController: searchController, - typeName: tn(Race), - filterWidgetsBuilder: (context, f) => [ - SelectBox( - label: Text(tr.entityPlural(tn(CharacterClass))), - isExpanded: true, - value: f.classKey, - items: [ - DropdownMenuItem( - value: null, - child: Text( - tr.generic.allEntities(tr.entityPlural(tn(CharacterClass)))), - ), - ...{ - ...repo.builtIn.classes.values, - ...repo.my.classes.values - }.map( - (cls) => DropdownMenuItem( - value: cls.key, - child: Text(cls.name), + return RepositoryProvider.consumer( + (context, repo, _) => EntityFiltersView( + filters: filters, + emptyFilters: RaceFilters(classKey: null), + onChange: onChange, + searchController: searchController, + typeName: tn(Race), + filterWidgetsBuilder: (context, f) => [ + SelectBox( + label: Text(tr.entityPlural(tn(CharacterClass))), + isExpanded: true, + value: f.classKey, + items: [ + DropdownMenuItem( + value: null, + child: Text(tr.generic + .allEntities(tr.entityPlural(tn(CharacterClass)))), ), - ), - ], - onChanged: (key) { - onChange(f..classKey = key); - f.controller.add(f); - }, - ), - ], + ...{ + ...repo.builtIn.classes.values, + ...repo.my.classes.values + }.map( + (cls) => DropdownMenuItem( + value: cls.key, + child: Text(cls.name), + ), + ), + ], + onChanged: (key) { + onChange(f..classKey = key); + f.controller.add(f); + }, + ), + ], + ), ); } } diff --git a/lib/app/modules/LibraryList/views/filters/spell_filters.dart b/lib/app/modules/LibraryList/views/filters/spell_filters.dart index b5543503..01b2a9c0 100644 --- a/lib/app/modules/LibraryList/views/filters/spell_filters.dart +++ b/lib/app/modules/LibraryList/views/filters/spell_filters.dart @@ -1,6 +1,6 @@ import 'package:dungeon_paper/app/data/models/character_class.dart'; import 'package:dungeon_paper/app/data/models/spell.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; +import 'package:dungeon_paper/app/data/services/repository_provider.dart'; import 'package:dungeon_paper/app/modules/LibraryList/controllers/library_list_controller.dart'; import 'package:dungeon_paper/app/modules/LibraryList/views/entity_filters.dart'; import 'package:dungeon_paper/app/widgets/atoms/select_box.dart'; @@ -8,11 +8,10 @@ import 'package:dungeon_paper/core/utils/math_utils.dart'; import 'package:dungeon_paper/core/utils/string_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:string_similarity/string_similarity.dart'; class SpellFiltersView extends StatelessWidget { - SpellFiltersView({ + const SpellFiltersView({ super.key, required this.group, required this.filters, @@ -22,44 +21,45 @@ class SpellFiltersView extends StatelessWidget { final SpellFilters filters; final FiltersGroup group; - final repo = Get.find(); final void Function(SpellFilters) onChange; final TextEditingController searchController; @override Widget build(BuildContext context) { - return EntityFiltersView( - filters: filters, - emptyFilters: SpellFilters(classKey: null, level: null), - onChange: onChange, - searchController: searchController, - typeName: tn(Spell), - filterWidgetsBuilder: (context, f) => [ - SelectBox( - label: Text(tr.entityPlural(tn(Spell))), - value: f.classKey, - items: [ - DropdownMenuItem( - value: null, - child: Text( - tr.generic.allEntities(tr.entityPlural(tn(CharacterClass)))), - ), - ...{ - ...repo.builtIn.classes.values, - ...repo.my.classes.values - }.map( - (cls) => DropdownMenuItem( - value: cls.key, - child: Text(cls.name), + return RepositoryProvider.consumer( + (context, repo, _) => EntityFiltersView( + filters: filters, + emptyFilters: SpellFilters(classKey: null, level: null), + onChange: onChange, + searchController: searchController, + typeName: tn(Spell), + filterWidgetsBuilder: (context, f) => [ + SelectBox( + label: Text(tr.entityPlural(tn(Spell))), + value: f.classKey, + items: [ + DropdownMenuItem( + value: null, + child: Text(tr.generic + .allEntities(tr.entityPlural(tn(CharacterClass)))), ), - ), - ], - onChanged: (key) { - onChange(f..classKey = key); - f.controller.add(f); - }, - ), - ], + ...{ + ...repo.builtIn.classes.values, + ...repo.my.classes.values + }.map( + (cls) => DropdownMenuItem( + value: cls.key, + child: Text(cls.name), + ), + ), + ], + onChanged: (key) { + onChange(f..classKey = key); + f.controller.add(f); + }, + ), + ], + ), ); } } diff --git a/lib/app/modules/LibraryList/views/items_library_list_view.dart b/lib/app/modules/LibraryList/views/items_library_list_view.dart index f0910902..a174ae32 100644 --- a/lib/app/modules/LibraryList/views/items_library_list_view.dart +++ b/lib/app/modules/LibraryList/views/items_library_list_view.dart @@ -1,61 +1,59 @@ -import 'package:dungeon_paper/app/data/models/character.dart'; import 'package:dungeon_paper/app/data/models/item.dart'; import 'package:dungeon_paper/app/model_utils/model_pages.dart'; import 'package:dungeon_paper/app/modules/LibraryList/controllers/library_list_controller.dart'; import 'package:dungeon_paper/app/modules/LibraryList/views/library_list_view.dart'; -import 'package:dungeon_paper/app/themes/button_themes.dart'; import 'package:dungeon_paper/app/widgets/cards/item_card.dart'; import 'package:dungeon_paper/app/widgets/menus/entity_edit_menu.dart'; import 'package:flutter/material.dart'; - -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; import 'filters/item_filters.dart'; import 'library_select_button.dart'; -class ItemsLibraryListView - extends GetView> { +class ItemsLibraryListView extends StatelessWidget { const ItemsLibraryListView({super.key}); - Character get char => controller.chars.value.current; - @override Widget build(BuildContext context) { - return LibraryListView( - filtersBuilder: (group, filters, onChange) => ItemFiltersView( - filters: filters, - onChange: (f) => onChange(group, f), - searchController: controller.search[group]!, - ), - cardBuilder: (ctx, data) => ItemCard( - item: data.item, - showStar: false, - highlightWords: data.highlightWords, - hideCount: true, - trailing: [ - if (controller.selectable) - LibrarySelectButton.icon( - selected: data.selected, - onPressed: data.onToggle, - ) - ], - actions: [ - EntityEditMenu( - onEdit: data.onUpdate != null - ? () => ModelPages.openItemPage( - item: data.item, - onSave: data.onUpdate!, - ) - : null, - onDelete: - data.onDelete != null ? () => data.onDelete!(data.item) : null, - ), - if (controller.selectable) - LibrarySelectButton( - selected: data.selected, - onPressed: data.onToggle, - ) - ], + return Consumer>( + builder: (context, controller, _) => LibraryListView( + filtersBuilder: (group, filters, onChange) => ItemFiltersView( + filters: filters, + onChange: (f) => onChange(group, f), + searchController: controller.search[group]!, + ), + cardBuilder: (ctx, data) => ItemCard( + item: data.item, + showStar: false, + highlightWords: data.highlightWords, + hideCount: true, + trailing: [ + if (controller.selectable) + LibrarySelectButton.icon( + selected: data.selected, + onPressed: data.onToggle, + ) + ], + actions: [ + EntityEditMenu( + onEdit: data.onUpdate != null + ? () => ModelPages.openItemPage( + context, + item: data.item, + onSave: data.onUpdate!, + ) + : null, + onDelete: data.onDelete != null + ? () => data.onDelete!(data.item) + : null, + ), + if (controller.selectable) + LibrarySelectButton( + selected: data.selected, + onPressed: data.onToggle, + ) + ], + ), ), ); } @@ -76,4 +74,3 @@ class ItemLibraryListArguments extends LibraryListArguments { extraData: const {}, ); } - diff --git a/lib/app/modules/LibraryList/views/library_card_list.dart b/lib/app/modules/LibraryList/views/library_card_list.dart index b0207fe8..f64cc545 100644 --- a/lib/app/modules/LibraryList/views/library_card_list.dart +++ b/lib/app/modules/LibraryList/views/library_card_list.dart @@ -18,10 +18,10 @@ import 'package:dungeon_paper/core/utils/builder_utils.dart'; import 'package:dungeon_paper/core/utils/enums.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; class LibraryCardList> - extends GetView> { + extends StatelessWidget { LibraryCardList({ super.key, required this.useFilters, @@ -64,32 +64,34 @@ class LibraryCardList> @override Widget build(BuildContext context) { - return Stack( - children: [ - Positioned.fill( - child: Padding( - padding: const EdgeInsets.only(top: 32), - child: ItemBuilder.listViewBuilder( - padding: const EdgeInsets.all(8).copyWith( - top: 0, - bottom: controller.selectable ? 80 : 4, + return Consumer>( + builder: (context, controller, _) => Stack( + children: [ + Positioned.fill( + child: Padding( + padding: const EdgeInsets.only(top: 32), + child: ItemBuilder.listViewBuilder( + padding: const EdgeInsets.all(8).copyWith( + top: 0, + bottom: controller.selectable ? 80 : 4, + ), + itemBuilder: (context, index) => Padding( + padding: const EdgeInsets.only(bottom: 8), + child: itemBuilder(context, index), + ), + itemCount: itemCount, + leadingCount: _leadingCount(context), + leadingBuilder: _leadingBuilder, ), - itemBuilder: (context, index) => Padding( - padding: const EdgeInsets.only(bottom: 8), - child: itemBuilder(context, index), - ), - itemCount: itemCount, - leadingCount: _leadingCount(context), - leadingBuilder: _leadingBuilder, ), ), - ), - if (useFilters) - Padding( - padding: const EdgeInsets.all(8), - child: filtersBuilder!(group, filters, controller.setFilters), - ), - ], + if (useFilters) + Padding( + padding: const EdgeInsets.all(8), + child: filtersBuilder!(group, filters, controller.setFilters), + ), + ], + ), ); } @@ -136,7 +138,7 @@ class LibraryCardList> height: 48, child: ElevatedButton.icon( style: ButtonThemes.primaryElevated(context), - onPressed: () => Get.toNamed( + onPressed: () => Navigator.of(context).pushNamed( Routes.editByType(), arguments: createPageArgsByType(extraData), ), diff --git a/lib/app/modules/LibraryList/views/library_collection_view.dart b/lib/app/modules/LibraryList/views/library_collection_view.dart index d9a65138..e29c4c4f 100644 --- a/lib/app/modules/LibraryList/views/library_collection_view.dart +++ b/lib/app/modules/LibraryList/views/library_collection_view.dart @@ -5,20 +5,17 @@ import 'package:dungeon_paper/app/data/models/meta.dart'; import 'package:dungeon_paper/app/data/models/move.dart'; import 'package:dungeon_paper/app/data/models/race.dart'; import 'package:dungeon_paper/app/data/models/spell.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; -import 'package:dungeon_paper/app/data/services/user_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; +import 'package:dungeon_paper/app/data/services/repository_provider.dart'; +import 'package:dungeon_paper/app/data/services/user_provider.dart'; import 'package:dungeon_paper/app/model_utils/model_pages.dart'; import 'package:dungeon_paper/app/widgets/atoms/menu_button.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:intl/intl.dart'; -import '../controllers/library_collection_controller.dart'; - -class LibraryCollectionView extends GetView - with RepositoryServiceMixin, UserServiceMixin, CharacterServiceMixin { +class LibraryCollectionView extends StatelessWidget + with UserProviderMixin, CharacterProviderMixin { static const List types = [Move, Spell, Item, CharacterClass, Race]; const LibraryCollectionView({super.key}); @@ -37,11 +34,11 @@ class LibraryCollectionView extends GetView MenuEntry( label: Text(tr.myLibrary.reload), icon: const Icon(Icons.refresh), - disabled: repo.my.isLoading || repo.builtIn.isLoading, + // disabled: repo.my.isLoading || repo.builtIn.isLoading, value: 'refresh', onSelect: () { - userService.loadBuiltInRepo(ignoreCache: true); - userService.loadMyRepo(ignoreCache: true); + userProvider.loadBuiltInRepo(ignoreCache: true); + userProvider.loadMyRepo(ignoreCache: true); }, ), ], @@ -52,12 +49,13 @@ class LibraryCollectionView extends GetView padding: const EdgeInsets.all(8), itemCount: types.length, itemBuilder: (context, index) { - return Obx( - () { + return RepositoryProvider.consumer( + (context, repo, _) { final type = types[index]; return Card( child: ListTile( onTap: () => ModelPages.openLibraryList( + context, type: type, abilityScores: maybeChar?.abilityScores ?? AbilityScores.dungeonWorldAll(10), @@ -106,3 +104,4 @@ class LibraryCollectionView extends GetView ); } } + diff --git a/lib/app/modules/LibraryList/views/library_list_view.dart b/lib/app/modules/LibraryList/views/library_list_view.dart index b01fb03c..13727cea 100644 --- a/lib/app/modules/LibraryList/views/library_list_view.dart +++ b/lib/app/modules/LibraryList/views/library_list_view.dart @@ -6,7 +6,7 @@ import 'package:dungeon_paper/app/widgets/atoms/advanced_floating_action_button. import 'package:dungeon_paper/app/widgets/dialogs/confirm_delete_dialog.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; import 'library_card_list.dart'; @@ -38,8 +38,8 @@ class CardBuilderData { } class LibraryListView> - extends GetView> { - LibraryListView({ + extends StatefulWidget { + const LibraryListView({ super.key, this.title, required this.cardBuilder, @@ -48,125 +48,152 @@ class LibraryListView> final Widget? title; final CardBuilder cardBuilder; - final pageStorageBucket = PageStorageBucket(); final Widget Function(FiltersGroup group, F filters, void Function(FiltersGroup group, F filters) update)? filtersBuilder; - bool get useFilters => filtersBuilder != null; - F get playbookFilters => controller.filters[FiltersGroup.playbook]!; - F get myFilters => controller.filters[FiltersGroup.my]!; + @override + State> createState() => _LibraryListViewState(); +} + +class _LibraryListViewState> + extends State> with SingleTickerProviderStateMixin { + late final TabController tabController; + final pageStorageBucket = PageStorageBucket(); + + @override + void initState() { + super.initState(); + tabController = TabController(length: 2, vsync: this); + } + + bool get useFilters => widget.filtersBuilder != null; + + F playbookFilters(BuildContext context) { + final controller = + Provider.of>(context, listen: false); + return controller.filters[FiltersGroup.playbook]!; + } + + F myFilters(BuildContext context) { + final controller = + Provider.of>(context, listen: false); + + return controller.filters[FiltersGroup.my]!; + } @override Widget build(BuildContext context) { - final entityTitleName = controller.multiple || !controller.selectable - ? tr.entityPlural(tn(T)) - : tr.entity(tn(T)); - - return Scaffold( - appBar: AppBar( - title: title ?? - Text( - controller.selectable - ? tr.generic.selectEntity(entityTitleName) - : tr.generic.viewEntity(entityTitleName), - ), - centerTitle: true, - ), - body: PageStorage( - bucket: pageStorageBucket, - child: Obx( - () => Column( - mainAxisSize: MainAxisSize.min, - children: [ - TabBar( - controller: controller.tabController, - labelColor: Theme.of(context).colorScheme.onSurface, - unselectedLabelColor: Theme.of(context).colorScheme.onSurface, - tabs: [ - Tab(child: Text(tr.myLibrary.itemTab.playbook)), - Tab(child: Text(tr.generic.myEntity(tr.entityPlural(tn(T))))), - // Tab(child: Text(tr.myLibrary.itemTab.online)), - ], - ), - Expanded( - child: TabBarView( - controller: controller.tabController, - children: [ - LibraryCardList.builder( - group: FiltersGroup.playbook, - useFilters: useFilters, - filtersBuilder: filtersBuilder, - filters: playbookFilters, - extraData: controller.extraData, - totalItemCount: controller.builtInListRaw.length, - itemCount: controller.builtInList.length, - itemBuilder: (context, index) { - final item = controller.builtInList.elementAt(index); - return _wrapWithSelection( - context, item, FiltersGroup.playbook); - }, - ), - LibraryCardList.builder( - group: FiltersGroup.my, - onSave: (item) => controller.saveCustomItem( - controller.storageKey, item), - extraData: controller.extraData, - useFilters: useFilters, - filtersBuilder: filtersBuilder, - filters: myFilters, - totalItemCount: controller.myListRaw.length, - itemCount: controller.myList.length, - itemBuilder: (context, index) { - final item = controller.myList.elementAt(index); - return _wrapWithSelection( - context, item, FiltersGroup.my); - }, - ), - // Container(), + return Consumer>( + builder: (context, controller, _) { + final entityTitleName = controller.multiple || !controller.selectable + ? tr.entityPlural(tn(T)) + : tr.entity(tn(T)); + return Scaffold( + appBar: AppBar( + title: widget.title ?? + Text( + controller.selectable + ? tr.generic.selectEntity(entityTitleName) + : tr.generic.viewEntity(entityTitleName), + ), + centerTitle: true, + ), + body: PageStorage( + bucket: pageStorageBucket, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TabBar( + controller: tabController, + labelColor: Theme.of(context).colorScheme.onSurface, + unselectedLabelColor: Theme.of(context).colorScheme.onSurface, + tabs: [ + Tab(child: Text(tr.myLibrary.itemTab.playbook)), + Tab( + child: + Text(tr.generic.myEntity(tr.entityPlural(tn(T))))), + // Tab(child: Text(tr.myLibrary.itemTab.online)), ], ), - ), - ], - ), - ), - ), - floatingActionButton: Obx( - () => controller.onSelected.obs.value != null - ? AdvancedFloatingActionButton.extended( - icon: controller.selected.isNotEmpty - ? controller.multiple - ? const Icon(Icons.add) - : const Icon(Icons.check) - : null, - onPressed: controller.selected.isNotEmpty - ? () { - controller.onSelected!(controller.selectedWithMeta); - Get.back(); - } - : null, - label: Text( - controller.selected.isNotEmpty - ? controller.multiple - ? tr.generic.addEntity( - tr.entityCount(tn(T), controller.selected.length), - ) - : tr.generic.selectEntity( - controller.selected.first.displayName) - : tr.generic.selectToAdd( - controller.multiple - ? tr.entityPlural(tn(T)) - : tr.entity(tn(T)), - ), + Expanded( + child: TabBarView( + controller: tabController, + children: [ + LibraryCardList.builder( + group: FiltersGroup.playbook, + useFilters: useFilters, + filtersBuilder: widget.filtersBuilder, + filters: playbookFilters(context), + extraData: controller.extraData, + totalItemCount: controller.builtInListRaw.length, + itemCount: controller.builtInList.length, + itemBuilder: (context, index) { + final item = controller.builtInList.elementAt(index); + return _wrapWithSelection( + context, item, FiltersGroup.playbook); + }, + ), + LibraryCardList.builder( + group: FiltersGroup.my, + onSave: (item) => controller.saveCustomItem( + controller.storageKey, item), + extraData: controller.extraData, + useFilters: useFilters, + filtersBuilder: widget.filtersBuilder, + filters: myFilters(context), + totalItemCount: controller.myListRaw.length, + itemCount: controller.myList.length, + itemBuilder: (context, index) { + final item = controller.myList.elementAt(index); + return _wrapWithSelection( + context, item, FiltersGroup.my); + }, + ), + // Container(), + ], + ), ), - ) - : Container(), - ), + ], + ), + ), + floatingActionButton: controller.onSelected != null + ? AdvancedFloatingActionButton.extended( + icon: controller.selected.isNotEmpty + ? controller.multiple + ? const Icon(Icons.add) + : const Icon(Icons.check) + : null, + onPressed: controller.selected.isNotEmpty + ? () { + controller.onSelected!(controller.selectedWithMeta); + Navigator.of(context).pop(); + } + : null, + label: Text( + controller.selected.isNotEmpty + ? controller.multiple + ? tr.generic.addEntity( + tr.entityCount( + tn(T), controller.selected.length), + ) + : tr.generic.selectEntity( + controller.selected.first.displayName) + : tr.generic.selectToAdd( + controller.multiple + ? tr.entityPlural(tn(T)) + : tr.entity(tn(T)), + ), + ), + ) + : Container(), + ); + }, ); } Widget _wrapWithSelection(BuildContext context, T item, FiltersGroup group) => - Obx( - () { + Consumer>( + builder: (context, controller, _) { final isPreSelected = controller.isPreSelected(item); final selected = controller.isSelected(item); final enabled = controller.isEnabled(item); @@ -205,16 +232,14 @@ class LibraryListView> controller.saveCustomItem(controller.storageKey, item) : null, onDelete: group == FiltersGroup.my - ? (item) => deleteDialog.confirm( + ? (item) => awaitDeleteConfirmation( context, - DeleteDialogOptions( - entityName: item.displayName, - entityKind: tr.entity(tn(item.runtimeType)), - ), + item.displayName, () => controller.deleteCustomItem( controller.storageKey, item, ), + item.runtimeType, ) : null, highlightWords: [controller.search[group]!.value.text]); @@ -233,8 +258,9 @@ class LibraryListView> : Colors.transparent, ), ), - child: cardBuilder(context, cardData), + child: widget.cardBuilder(context, cardData), ); }, ); } + diff --git a/lib/app/modules/LibraryList/views/library_select_button.dart b/lib/app/modules/LibraryList/views/library_select_button.dart index d52ec580..8f4b4fb2 100644 --- a/lib/app/modules/LibraryList/views/library_select_button.dart +++ b/lib/app/modules/LibraryList/views/library_select_button.dart @@ -69,4 +69,3 @@ class LibrarySelectButton extends StatelessWidget { } } } - diff --git a/lib/app/modules/LibraryList/views/moves_library_list_view.dart b/lib/app/modules/LibraryList/views/moves_library_list_view.dart index 44a43392..921368e7 100644 --- a/lib/app/modules/LibraryList/views/moves_library_list_view.dart +++ b/lib/app/modules/LibraryList/views/moves_library_list_view.dart @@ -1,69 +1,69 @@ import 'package:dungeon_paper/app/data/models/ability_scores.dart'; 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/character_service.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; +import 'package:dungeon_paper/app/data/services/repository_provider.dart'; import 'package:dungeon_paper/app/model_utils/model_pages.dart'; import 'package:dungeon_paper/app/modules/LibraryList/controllers/library_list_controller.dart'; import 'package:dungeon_paper/app/modules/LibraryList/views/library_list_view.dart'; import 'package:dungeon_paper/app/modules/LibraryList/views/library_select_button.dart'; import 'package:dungeon_paper/app/widgets/cards/move_card.dart'; import 'package:dungeon_paper/app/widgets/menus/entity_edit_menu.dart'; -import 'package:flutter/material.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; - -import 'package:get/get.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'filters/move_filters.dart'; -class MovesLibraryListView - extends GetView> - with CharacterServiceMixin { +class MovesLibraryListView extends StatelessWidget + with CharacterProviderMixin, RepositoryProviderMixin { const MovesLibraryListView({super.key}); - RepositoryService get service => controller.repo.value; - @override Widget build(BuildContext context) { - return LibraryListView( - filtersBuilder: (group, filters, onChange) => MoveFiltersView( - group: group, - filters: filters, - onChange: (f) => onChange(group, f), - searchController: controller.search[group]!, - ), - cardBuilder: (ctx, data) => MoveCard( - move: data.item, - showDice: false, - showStar: false, - showClasses: true, - highlightWords: data.highlightWords, - trailing: [ - if (controller.selectable) - LibrarySelectButton.icon( - selected: data.selected, - onPressed: data.onToggle, - ) - ], - actions: [ - EntityEditMenu( - onEdit: data.onUpdate != null - ? () => ModelPages.openMovePage( - abilityScores: maybeChar?.abilityScores ?? - AbilityScores.dungeonWorldAll(10), - move: data.item, - onSave: data.onUpdate!, - ) - : null, - onDelete: - data.onDelete != null ? () => data.onDelete!(data.item) : null, - ), - if (controller.selectable) - LibrarySelectButton( - selected: data.selected, - onPressed: data.onToggle, - ) - ], + return Consumer>( + builder: (context, controller, _) => LibraryListView( + filtersBuilder: (group, filters, onChange) => MoveFiltersView( + group: group, + filters: filters, + onChange: (f) => onChange(group, f), + searchController: controller.search[group]!, + ), + cardBuilder: (ctx, data) => MoveCard( + move: data.item, + showDice: false, + showStar: false, + showClasses: true, + highlightWords: data.highlightWords, + trailing: [ + if (controller.selectable) + LibrarySelectButton.icon( + selected: data.selected, + onPressed: data.onToggle, + ) + ], + actions: [ + EntityEditMenu( + onEdit: data.onUpdate != null + ? () => ModelPages.openMovePage( + context, + abilityScores: maybeChar?.abilityScores ?? + AbilityScores.dungeonWorldAll(10), + move: data.item, + onSave: data.onUpdate!, + ) + : null, + onDelete: data.onDelete != null + ? () => data.onDelete!(data.item) + : null, + ), + if (controller.selectable) + LibrarySelectButton( + selected: data.selected, + onPressed: data.onToggle, + ) + ], + ), ), ); } diff --git a/lib/app/modules/LibraryList/views/notes_library_list_view.dart b/lib/app/modules/LibraryList/views/notes_library_list_view.dart index d9f2f67e..f060fda9 100644 --- a/lib/app/modules/LibraryList/views/notes_library_list_view.dart +++ b/lib/app/modules/LibraryList/views/notes_library_list_view.dart @@ -1,6 +1,5 @@ -import 'package:dungeon_paper/app/data/models/character.dart'; import 'package:dungeon_paper/app/data/models/note.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; +import 'package:dungeon_paper/app/data/services/repository_provider.dart'; import 'package:dungeon_paper/app/model_utils/model_pages.dart'; import 'package:dungeon_paper/app/modules/LibraryList/controllers/library_list_controller.dart'; import 'package:dungeon_paper/app/modules/LibraryList/views/library_list_view.dart'; @@ -8,51 +7,49 @@ import 'package:dungeon_paper/app/themes/button_themes.dart'; import 'package:dungeon_paper/app/widgets/cards/note_card.dart'; import 'package:dungeon_paper/app/widgets/menus/entity_edit_menu.dart'; import 'package:flutter/material.dart'; - -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; import 'filters/note_filters.dart'; -class NotesLibraryListView - extends GetView> { - const NotesLibraryListView({ - Key? key, - }) : super(key: key); - - RepositoryService get service => controller.repo.value; - Character get character => controller.chars.value.current; +class NotesLibraryListView extends StatelessWidget + with RepositoryProviderMixin { + const NotesLibraryListView({super.key}); @override Widget build(BuildContext context) { - return LibraryListView( - filtersBuilder: (group, filters, onChange) => NoteFiltersView( - filters: filters, - onChange: (f) => onChange(group, f), - searchController: controller.search[group]!, - ), - cardBuilder: (ctx, data) => NoteCard( - note: data.item, - showStar: false, - highlightWords: data.highlightWords, - actions: [ - EntityEditMenu( - onEdit: data.onUpdate != null - ? () => ModelPages.openNotePage( - note: data.item, - onSave: data.onUpdate!, - ) - : null, - onDelete: - data.onDelete != null ? () => data.onDelete!(data.item) : null, - ), - if (data.selectable) - ElevatedButton.icon( - style: ButtonThemes.primaryElevated(context), - onPressed: data.onToggle, - label: data.label, - icon: data.icon, + return Consumer>( + builder: (context, controller, _) => LibraryListView( + filtersBuilder: (group, filters, onChange) => NoteFiltersView( + filters: filters, + onChange: (f) => onChange(group, f), + searchController: controller.search[group]!, + ), + cardBuilder: (ctx, data) => NoteCard( + note: data.item, + showStar: false, + highlightWords: data.highlightWords, + actions: [ + EntityEditMenu( + onEdit: data.onUpdate != null + ? () => ModelPages.openNotePage( + context, + note: data.item, + onSave: data.onUpdate!, + ) + : null, + onDelete: data.onDelete != null + ? () => data.onDelete!(data.item) + : null, ), - ], + if (data.selectable) + ElevatedButton.icon( + style: ButtonThemes.primaryElevated(context), + onPressed: data.onToggle, + label: data.label, + icon: data.icon, + ), + ], + ), ), ); } diff --git a/lib/app/modules/LibraryList/views/races_library_list_view.dart b/lib/app/modules/LibraryList/views/races_library_list_view.dart index 644fc20f..e81b5328 100644 --- a/lib/app/modules/LibraryList/views/races_library_list_view.dart +++ b/lib/app/modules/LibraryList/views/races_library_list_view.dart @@ -1,67 +1,67 @@ import 'package:dungeon_paper/app/data/models/ability_scores.dart'; import 'package:dungeon_paper/app/data/models/character.dart'; import 'package:dungeon_paper/app/data/models/race.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; +import 'package:dungeon_paper/app/data/services/repository_provider.dart'; import 'package:dungeon_paper/app/model_utils/model_pages.dart'; import 'package:dungeon_paper/app/modules/LibraryList/controllers/library_list_controller.dart'; import 'package:dungeon_paper/app/modules/LibraryList/views/library_list_view.dart'; import 'package:dungeon_paper/app/widgets/cards/race_card.dart'; import 'package:dungeon_paper/app/widgets/menus/entity_edit_menu.dart'; import 'package:flutter/material.dart'; - -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; import 'filters/race_filters.dart'; import 'library_select_button.dart'; -class RacesLibraryListView - extends GetView> - with CharacterServiceMixin { +class RacesLibraryListView extends StatelessWidget + with CharacterProviderMixin, RepositoryProviderMixin { const RacesLibraryListView({super.key}); - RepositoryService get service => controller.repo.value; - @override Widget build(BuildContext context) { - return LibraryListView( - filtersBuilder: (group, filters, onChange) => RaceFiltersView( - group: group, - filters: filters, - onChange: (f) => onChange(group, f), - searchController: controller.search[group]!, - ), - cardBuilder: (ctx, data) => RaceCard( - race: data.item, - showStar: false, - showClasses: true, - highlightWords: data.highlightWords, - trailing: [ - if (controller.selectable) - LibrarySelectButton.icon( - selected: data.selected, - onPressed: data.onToggle, - ) - ], - actions: [ - EntityEditMenu( - onEdit: data.onUpdate != null - ? () => ModelPages.openRacePage( - abilityScores: maybeChar?.abilityScores ?? - AbilityScores.dungeonWorldAll(10), - race: data.item, - onSave: data.onUpdate!, - ) - : null, - onDelete: - data.onDelete != null ? () => data.onDelete!(data.item) : null, - ), - if (controller.selectable) - LibrarySelectButton( - selected: data.selected, - onPressed: data.onToggle, - ) - ], + return Consumer>( + builder: (context, controller, _) => LibraryListView( + filtersBuilder: (group, filters, onChange) => RaceFiltersView( + group: group, + filters: filters, + onChange: (f) => onChange(group, f), + searchController: controller.search[group]!, + ), + cardBuilder: (ctx, data) => RaceCard( + race: data.item, + showStar: false, + showClasses: true, + highlightWords: data.highlightWords, + trailing: [ + if (controller.selectable) + LibrarySelectButton.icon( + selected: data.selected, + onPressed: data.onToggle, + ) + ], + actions: [ + EntityEditMenu( + onEdit: data.onUpdate != null + ? () => ModelPages.openRacePage( + context, + abilityScores: maybeChar?.abilityScores ?? + AbilityScores.dungeonWorldAll(10), + race: data.item, + onSave: data.onUpdate!, + ) + : null, + onDelete: data.onDelete != null + ? () => data.onDelete!(data.item) + : null, + ), + if (controller.selectable) + LibrarySelectButton( + selected: data.selected, + onPressed: data.onToggle, + ) + ], + ), ), ); } @@ -92,4 +92,3 @@ class RaceLibraryListArguments extends LibraryListArguments { multiple: false, ); } - diff --git a/lib/app/modules/LibraryList/views/spells_library_list_view.dart b/lib/app/modules/LibraryList/views/spells_library_list_view.dart index eb3192fc..39e57f32 100644 --- a/lib/app/modules/LibraryList/views/spells_library_list_view.dart +++ b/lib/app/modules/LibraryList/views/spells_library_list_view.dart @@ -1,65 +1,68 @@ import 'package:dungeon_paper/app/data/models/ability_scores.dart'; import 'package:dungeon_paper/app/data/models/character.dart'; import 'package:dungeon_paper/app/data/models/spell.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; import 'package:dungeon_paper/app/model_utils/model_pages.dart'; import 'package:dungeon_paper/app/modules/LibraryList/controllers/library_list_controller.dart'; import 'package:dungeon_paper/app/modules/LibraryList/views/library_list_view.dart'; import 'package:dungeon_paper/app/widgets/cards/spell_card.dart'; import 'package:dungeon_paper/app/widgets/menus/entity_edit_menu.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; +import 'package:provider/provider.dart'; import 'filters/spell_filters.dart'; import 'library_select_button.dart'; -class SpellsLibraryListView - extends GetView> { +class SpellsLibraryListView extends StatelessWidget + with CharacterProviderMixin { const SpellsLibraryListView({super.key}); - Character get character => controller.chars.value.current; - @override Widget build(BuildContext context) { - return LibraryListView( - filtersBuilder: (group, filters, onChange) => SpellFiltersView( - group: group, - filters: filters, - onChange: (f) => onChange(group, f), - searchController: controller.search[group]!, - ), - cardBuilder: (ctx, data) => SpellCard( - spell: data.item, - showDice: false, - showStar: false, - showClasses: true, - highlightWords: data.highlightWords, - trailing: [ - if (controller.selectable) - LibrarySelectButton.icon( - selected: data.selected, - onPressed: data.onToggle, - ) - ], - actions: [ - EntityEditMenu( - onEdit: data.onUpdate != null - ? () => ModelPages.openSpellPage( - abilityScores: character.abilityScores, - classKeys: data.item.classKeys, - spell: data.item, - onSave: data.onUpdate!, - ) - : null, - onDelete: - data.onDelete != null ? () => data.onDelete!(data.item) : null, - ), - if (controller.selectable) - LibrarySelectButton( - selected: data.selected, - onPressed: data.onToggle, - ) - ], + return Consumer>( + builder: (context, controller, _) => LibraryListView( + filtersBuilder: (group, filters, onChange) => SpellFiltersView( + group: group, + filters: filters, + onChange: (f) => onChange(group, f), + searchController: controller.search[group]!, + ), + cardBuilder: (ctx, data) => SpellCard( + spell: data.item, + showDice: false, + showStar: false, + showClasses: true, + highlightWords: data.highlightWords, + trailing: [ + if (controller.selectable) + LibrarySelectButton.icon( + selected: data.selected, + onPressed: data.onToggle, + ) + ], + actions: [ + EntityEditMenu( + onEdit: data.onUpdate != null + ? () => ModelPages.openSpellPage( + context, + abilityScores: character.abilityScores, + classKeys: data.item.classKeys, + spell: data.item, + onSave: data.onUpdate!, + ) + : null, + onDelete: data.onDelete != null + ? () => data.onDelete!(data.item) + : null, + ), + if (controller.selectable) + LibrarySelectButton( + selected: data.selected, + onPressed: data.onToggle, + ) + ], + ), ), ); } @@ -95,4 +98,3 @@ class SpellLibraryListArguments }, ); } - diff --git a/lib/app/modules/Login/bindings/login_binding.dart b/lib/app/modules/Login/bindings/login_binding.dart deleted file mode 100644 index ac119f4a..00000000 --- a/lib/app/modules/Login/bindings/login_binding.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:get/get.dart'; - -import '../controllers/login_controller.dart'; - -class LoginBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut( - () => LoginController(), - ); - } -} diff --git a/lib/app/modules/Login/controllers/login_controller.dart b/lib/app/modules/Login/controllers/login_controller.dart index b5d2c8c4..3031ab5b 100644 --- a/lib/app/modules/Login/controllers/login_controller.dart +++ b/lib/app/modules/Login/controllers/login_controller.dart @@ -1,88 +1,96 @@ -import 'package:dungeon_paper/app/data/services/auth_service.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; -import 'package:dungeon_paper/app/data/services/loading_service.dart'; -import 'package:dungeon_paper/app/routes/app_pages.dart'; +import 'package:dungeon_paper/app/data/services/auth_provider.dart'; +import 'package:dungeon_paper/app/data/services/loading_provider.dart'; import 'package:dungeon_paper/core/utils/secrets_base.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; -class LoginController extends GetxController - with AuthServiceMixin, LoadingServiceMixin, CharacterServiceMixin { +class LoginController extends ChangeNotifier with AuthProviderMixin { final formKey = GlobalKey(debugLabel: 'loginForm'); final email = TextEditingController(); final password = TextEditingController(); final passwordConfirm = TextEditingController(); - final _valid = false.obs; - final _signup = false.obs; + var _valid = false; + var _signup = false; - bool get valid => _valid.value; - bool get isSignUp => _signup.value; + bool get valid => _valid; + bool get isSignUp => _signup; bool get isLogin => !isSignUp; - void toggleSignup() => _signup.value = !_signup.value; - - void _loginWrapper(Future Function() cb) async { - try { - loadingService.loadingUser = true; - loadingService.loadingCharacters = false; - await cb(); - Get.offAllNamed(Routes.home); - } catch (e) { - if (secrets.sentryDsn.isNotEmpty) { - Sentry.captureException(e); - } - printError(info: e.toString()); - loadingService.loadingUser = false; - loadingService.loadingCharacters = false; - // TODO intl - Get.rawSnackbar(message: 'Login failed'); - } - } - - void loginWithPassword() async { - _loginWrapper( - () => authService.loginWithPassword( - email: email.text, password: password.text), - ); - } - - void loginWithGoogle() async { - _loginWrapper( - () => authService.loginWithGoogle(), - ); - } - - void loginWithApple() async { - _loginWrapper( - () => authService.loginWithApple(), - ); - } - - void signUp() { - _loginWrapper( - () => authService.signUp(email: email.text, password: password.text), - ); - } - - @override - void onInit() { - super.onInit(); + LoginController() { email.addListener(validate); password.addListener(validate); passwordConfirm.addListener(validate); } + void toggleSignup() => _signup = !_signup; + + void _loginWrapper(BuildContext context, Future Function() cb) async { + final messenger = ScaffoldMessenger.of(context); + final loadingProvider = LoadingProvider.of(context); + try { + final navigator = Navigator.of(context); + loadingProvider.loadingUser = true; + loadingProvider.loadingCharacters = false; + await cb(); + navigator.popUntil((route) => route.isFirst); + } catch (e) { + if (secrets.sentryDsn.isNotEmpty) { + Sentry.captureException(e); + } + debugPrint('ERROR: $e'); + loadingProvider.loadingUser = false; + loadingProvider.loadingCharacters = false; + // TODO intl + messenger.showSnackBar(const SnackBar(content: Text('Login failed'))); + } + } + + void loginWithPassword( + BuildContext context, + ) async { + _loginWrapper( + context, + () => authProvider.loginWithPassword( + email: email.text, password: password.text), + ); + } + + void loginWithGoogle( + BuildContext context, + ) async { + _loginWrapper( + context, + () => authProvider.loginWithGoogle(), + ); + } + + void loginWithApple( + BuildContext context, + ) async { + _loginWrapper( + context, + () => authProvider.loginWithApple(), + ); + } + + void signUp( + BuildContext context, + ) { + _loginWrapper( + context, + () => authProvider.signUp(email: email.text, password: password.text), + ); + } + @override - void onClose() { + void dispose() { + super.dispose(); email.dispose(); password.dispose(); passwordConfirm.dispose(); } bool validate() { - _valid.value = formKey.currentState?.validate() ?? false; - _valid.refresh(); - return _valid.value; + return _valid = formKey.currentState?.validate() ?? false; } } diff --git a/lib/app/modules/Login/views/login_progress_dialog_view.dart b/lib/app/modules/Login/views/login_progress_dialog_view.dart index 2b8db95e..7688b40c 100644 --- a/lib/app/modules/Login/views/login_progress_dialog_view.dart +++ b/lib/app/modules/Login/views/login_progress_dialog_view.dart @@ -1,24 +1,23 @@ -import 'package:dungeon_paper/app/data/services/character_service.dart'; -import 'package:dungeon_paper/app/data/services/loading_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; +import 'package:dungeon_paper/app/data/services/loading_provider.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; enum LoginProgressStep { signin, loadChars, } -class LoginProgressDialogView extends GetView - with CharacterServiceMixin { +class LoginProgressDialogView extends StatelessWidget + with CharacterProviderMixin, LoadingProviderMixin { const LoginProgressDialogView({super.key}); String get title { - if (controller.loadingUser) { + if (loadingProvider.loadingUser) { return tr.loading.user; } - if (controller.loadingCharacters) { + if (loadingProvider.loadingCharacters) { return tr.loading.characters; } @@ -27,19 +26,21 @@ class LoginProgressDialogView extends GetView @override Widget build(BuildContext context) { - return Obx( - () => AlertDialog( - title: Text(title), - content: const SizedBox.square( - dimension: 100, - child: Center( - child: Padding( - padding: EdgeInsets.all(24), - child: CircularProgressIndicator.adaptive(), + return LoadingProvider.consumer( + (context, loading, _) { + return AlertDialog( + title: Text(title), + content: const SizedBox.square( + dimension: 100, + child: Center( + child: Padding( + padding: EdgeInsets.all(24), + child: CircularProgressIndicator.adaptive(), + ), ), ), - ), - ), + ); + }, ); } } diff --git a/lib/app/modules/Login/views/login_view.dart b/lib/app/modules/Login/views/login_view.dart index 03b90d28..3a9b83c9 100644 --- a/lib/app/modules/Login/views/login_view.dart +++ b/lib/app/modules/Login/views/login_view.dart @@ -1,3 +1,4 @@ +import 'package:dungeon_paper/app/data/services/loading_provider.dart'; import 'package:dungeon_paper/app/widgets/atoms/labeled_divider.dart'; import 'package:dungeon_paper/app/widgets/atoms/password_field.dart'; import 'package:dungeon_paper/core/dw_icons.dart'; @@ -7,12 +8,12 @@ import 'package:dungeon_paper/core/utils/password_validator.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:email_validator/email_validator.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; import '../controllers/login_controller.dart'; -class LoginView extends GetView { +class LoginView extends StatelessWidget { const LoginView({super.key}); @override @@ -28,191 +29,198 @@ class LoginView extends GetView { child: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(24), - child: Form( - key: controller.formKey, - autovalidateMode: AutovalidateMode.onUserInteraction, - child: AutofillGroup( - child: SizedBox( - width: 400, - child: Obx( - () { - final providerSignIns = [ - if (PlatformHelper.canUseGoogleSignIn) ...[ - ElevatedButton.icon( - onPressed: !controller.loadingService.loadingUser - ? controller.loginWithGoogle - : null, - label: Text( - controller.isLogin - ? tr.auth.providers.loginWith( - tr.auth.providers.name('google'), - ) - : tr.auth.providers.signupWith( - tr.auth.providers.name('google'), - ), + child: Consumer( + builder: (context, controller, _) => Form( + key: controller.formKey, + autovalidateMode: AutovalidateMode.onUserInteraction, + child: AutofillGroup( + child: SizedBox( + width: 400, + child: LoadingProvider.consumer( + (context, loadingProvider, _) { + final providerSignIns = [ + if (PlatformHelper.canUseGoogleSignIn) ...[ + ElevatedButton.icon( + onPressed: !loadingProvider.loadingUser + ? () => controller.loginWithGoogle(context) + : null, + label: Text( + controller.isLogin + ? tr.auth.providers.loginWith( + tr.auth.providers.name('google'), + ) + : tr.auth.providers.signupWith( + tr.auth.providers.name('google'), + ), + ), + icon: const Icon(DwIcons.google), ), - icon: const Icon(DwIcons.google), - ), - ], - if (PlatformHelper.canUseAppleSignIn) ...[ - ElevatedButton.icon( - onPressed: !controller.loadingService.loadingUser - ? controller.loginWithApple - : null, - label: Text( - controller.isLogin - ? tr.auth.providers.loginWith( - tr.auth.providers.name('apple'), - ) - : tr.auth.providers.signupWith( - tr.auth.providers.name('apple'), - ), - ), - icon: const Icon(DwIcons.apple), - ), - ], - ]; - return Column( - children: [ - Text( - controller.isLogin - ? tr.auth.login.title - : tr.auth.signup.title, - style: textTheme.headlineMedium, - textAlign: TextAlign.center, - ), - const SizedBox(height: 8), - Text( - controller.isLogin - ? tr.auth.login.subtitle - : tr.auth.signup.subtitle, - style: textTheme.titleMedium, - textAlign: TextAlign.center, - ), - const SizedBox(height: 24), - ...providerSignIns - .joinObjects(const SizedBox(height: 8)), - if (providerSignIns.isNotEmpty) ...[ - const SizedBox(height: 16), - LabeledDivider(label: Text(tr.auth.orSeparator)), ], - TextFormField( - controller: controller.email, - keyboardType: TextInputType.emailAddress, - autofillHints: const [AutofillHints.email], - decoration: InputDecoration( - filled: true, - label: Text(tr.auth.signup.email.label), - enabled: !controller.loadingService.loadingUser, - // floatingLabelBehavior: FloatingLabelBehavior.auto, - hintText: tr.auth.signup.email.placeholder, + if (PlatformHelper.canUseAppleSignIn) ...[ + ElevatedButton.icon( + onPressed: !loadingProvider.loadingUser + ? () => controller.loginWithApple(context) + : null, + label: Text( + controller.isLogin + ? tr.auth.providers.loginWith( + tr.auth.providers.name('apple'), + ) + : tr.auth.providers.signupWith( + tr.auth.providers.name('apple'), + ), + ), + icon: const Icon(DwIcons.apple), ), - validator: (email) => - email == null || EmailValidator.validate(email) - ? null - : tr.auth.signup.email.error, - ), - const SizedBox(height: 16), - PasswordField( - controller: controller.password, - obscureText: true, - autofillHints: [ - controller.isSignUp - ? AutofillHints.newPassword - : AutofillHints.password + ], + ]; + return Column( + children: [ + Text( + controller.isLogin + ? tr.auth.login.title + : tr.auth.signup.title, + style: textTheme.headlineMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + controller.isLogin + ? tr.auth.login.subtitle + : tr.auth.signup.subtitle, + style: textTheme.titleMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + ...providerSignIns + .joinObjects(const SizedBox(height: 8)), + if (providerSignIns.isNotEmpty) ...[ + const SizedBox(height: 16), + LabeledDivider(label: Text(tr.auth.orSeparator)), ], - decoration: InputDecoration( - filled: true, - label: Text(tr.auth.signup.password.label), - hintText: tr.auth.signup.password.placeholder, - enabled: !controller.loadingService.loadingUser, - // floatingLabelBehavior: FloatingLabelBehavior.auto, - ), - validator: PasswordValidator().validator, - ), - if (controller.isSignUp) ...[ - const SizedBox(height: 16), - PasswordField( - controller: controller.passwordConfirm, - obscureText: true, - autofillHints: const [AutofillHints.newPassword], + TextFormField( + controller: controller.email, + keyboardType: TextInputType.emailAddress, + autofillHints: const [AutofillHints.email], decoration: InputDecoration( filled: true, - label: - Text(tr.auth.signup.password.confirm.label), - hintText: - tr.auth.signup.password.confirm.placeholder, - enabled: !controller.loadingService.loadingUser, + label: Text(tr.auth.signup.email.label), + enabled: !loadingProvider.loadingUser, + // floatingLabelBehavior: FloatingLabelBehavior.auto, + hintText: tr.auth.signup.email.placeholder, + ), + validator: (email) => email == null || + EmailValidator.validate(email) + ? null + : tr.auth.signup.email.error, + ), + const SizedBox(height: 16), + PasswordField( + controller: controller.password, + obscureText: true, + autofillHints: [ + controller.isSignUp + ? AutofillHints.newPassword + : AutofillHints.password + ], + decoration: InputDecoration( + filled: true, + label: Text(tr.auth.signup.password.label), + hintText: tr.auth.signup.password.placeholder, + enabled: !loadingProvider.loadingUser, // floatingLabelBehavior: FloatingLabelBehavior.auto, ), - validator: (pwd) => - PasswordValidator().validator(pwd) ?? - (pwd == controller.password.text - ? null - : tr.auth.signup.password.confirm.error), + validator: PasswordValidator().validator, + ), + if (controller.isSignUp) ...[ + const SizedBox(height: 16), + PasswordField( + controller: controller.passwordConfirm, + obscureText: true, + autofillHints: const [ + AutofillHints.newPassword + ], + decoration: InputDecoration( + filled: true, + label: Text( + tr.auth.signup.password.confirm.label), + hintText: tr + .auth.signup.password.confirm.placeholder, + enabled: !loadingProvider.loadingUser, + // floatingLabelBehavior: FloatingLabelBehavior.auto, + ), + validator: (pwd) => + PasswordValidator().validator(pwd) ?? + (pwd == controller.password.text + ? null + : tr.auth.signup.password.confirm + .error), + ), + ], + const SizedBox(height: 16), + ElevatedButton.icon( + onPressed: controller.valid + ? controller.isLogin + ? () => + controller.loginWithPassword(context) + : () => controller.signUp(context) + : null, + label: Text( + controller.isLogin + ? tr.auth.login.button + : tr.auth.signup.button, + textScaler: const TextScaler.linear(1.5), + ), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 24, vertical: 12), + ), + icon: loadingProvider.loadingUser + ? const SizedBox.square( + dimension: 24, + child: + CircularProgressIndicator.adaptive(), + ) + : const Icon(Icons.login, size: 24), + ), + const Divider(height: 48), + Wrap( + spacing: 8, + runSpacing: 8, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + Text(tr.auth.login.noAccount.label), + ElevatedButton( + onPressed: controller.toggleSignup, + child: Text(tr.auth.login.noAccount.button), + ) + ], + ), + const Divider(height: 48), + ElevatedButton.icon( + onPressed: () => launchUrl( + Uri.parse( + 'https://dungeonpaper.app/privacy-policy.html?utm_medium=app&utm_source=login', + ), + ), + label: Text(tr.auth.privacyPolicy), + icon: const Icon(Icons.lock), + ), + const SizedBox(height: 8), + ElevatedButton.icon( + onPressed: () => launchUrl( + // TODO make changelog view that uses current version + Uri.parse( + 'https://dungeonpaper.app/changelog.html', + ), + ), + label: Text(tr.auth.changelog), + icon: const Icon(Icons.new_releases), ), ], - const SizedBox(height: 16), - ElevatedButton.icon( - onPressed: controller.valid - ? controller.isLogin - ? controller.loginWithPassword - : controller.signUp - : null, - label: Text( - controller.isLogin - ? tr.auth.login.button - : tr.auth.signup.button, - textScaler: const TextScaler.linear(1.5), - ), - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric( - horizontal: 24, vertical: 12), - ), - icon: controller.loadingService.loadingUser - ? const SizedBox.square( - dimension: 24, - child: CircularProgressIndicator.adaptive(), - ) - : const Icon(Icons.login, size: 24), - ), - const Divider(height: 48), - Wrap( - spacing: 8, - runSpacing: 8, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - Text(tr.auth.login.noAccount.label), - ElevatedButton( - onPressed: controller.toggleSignup, - child: Text(tr.auth.login.noAccount.button), - ) - ], - ), - const Divider(height: 48), - ElevatedButton.icon( - onPressed: () => launchUrl( - Uri.parse( - 'https://dungeonpaper.app/privacy-policy.html?utm_medium=app&utm_source=login', - ), - ), - label: Text(tr.auth.privacyPolicy), - icon: const Icon(Icons.lock), - ), - const SizedBox(height: 8), - ElevatedButton.icon( - onPressed: () => launchUrl( - // TODO make changelog view that uses current version - Uri.parse( - 'https://dungeonpaper.app/changelog.html', - ), - ), - label: Text(tr.auth.changelog), - icon: const Icon(Icons.new_releases), - ), - ], - ); - }, + ); + }, + ), ), ), ), diff --git a/lib/app/modules/Migration/bindings/migration_binding.dart b/lib/app/modules/Migration/bindings/migration_binding.dart deleted file mode 100644 index d36c746f..00000000 --- a/lib/app/modules/Migration/bindings/migration_binding.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:get/get.dart'; - -import '../controllers/migration_controller.dart'; - -class MigrationBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut( - () => MigrationController(), - ); - } -} diff --git a/lib/app/modules/Migration/controllers/migration_controller.dart b/lib/app/modules/Migration/controllers/migration_controller.dart index e1463c38..c70004e8 100644 --- a/lib/app/modules/Migration/controllers/migration_controller.dart +++ b/lib/app/modules/Migration/controllers/migration_controller.dart @@ -1,20 +1,20 @@ import 'package:dungeon_paper/core/http/api_requests/migration.dart'; +import 'package:dungeon_paper/core/route_arguments.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -class MigrationController extends GetxController { - final _username = TextEditingController(text: '').obs; - final _language = 'EN'.obs; +class MigrationController extends ChangeNotifier { + final _username = TextEditingController(text: ''); + final _language = 'EN'; late final String email; - TextEditingController get username => _username.value; - String get language => _language.value; + TextEditingController get username => _username; + String get language => _language; bool get isValid => username.text.isNotEmpty && language.isNotEmpty; - void done() { - Get.back( - result: MigrationDetails( + void done(BuildContext context) { + Navigator.of(context).pop( + MigrationDetails( email: email, username: username.text, language: language, @@ -22,23 +22,21 @@ class MigrationController extends GetxController { ); } - @override - void onInit() { - super.onInit(); - final args = Get.arguments as MigrationArguments; + MigrationController(BuildContext context) { + final args = getArgs(context); email = args.email; username.addListener(_refreshUsername); } @override - onClose() { + dispose() { + super.dispose(); username.removeListener(_refreshUsername); username.dispose(); - super.onClose(); } _refreshUsername() { - _username.refresh(); + notifyListeners(); } } diff --git a/lib/app/modules/Migration/views/migration_view.dart b/lib/app/modules/Migration/views/migration_view.dart index d1be6353..b9dc5ae0 100644 --- a/lib/app/modules/Migration/views/migration_view.dart +++ b/lib/app/modules/Migration/views/migration_view.dart @@ -2,11 +2,11 @@ import 'package:dungeon_paper/app/widgets/atoms/advanced_floating_action_button. import 'package:dungeon_paper/app/widgets/atoms/select_box.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; import '../controllers/migration_controller.dart'; -class MigrationView extends GetView { +class MigrationView extends StatelessWidget { const MigrationView({super.key}); @override @@ -14,57 +14,54 @@ class MigrationView extends GetView { final theme = Theme.of(context); final textTheme = theme.textTheme; - return Scaffold( - appBar: AppBar(automaticallyImplyLeading: false), - floatingActionButton: Obx( - () => AdvancedFloatingActionButton.extended( - onPressed: controller.isValid ? controller.done : null, + return Consumer( + builder: (context, controller, _) => Scaffold( + appBar: AppBar(automaticallyImplyLeading: false), + floatingActionButton: AdvancedFloatingActionButton.extended( + onPressed: controller.isValid ? () => controller.done(context) : null, label: Text(tr.generic.continue_), icon: const Icon(Icons.check), ), - ), - body: Center( - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(8).copyWith(bottom: 96), - child: SizedBox( - width: 400, - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Image.asset( - 'assets/images/logo.png', - height: 100, - ), - const SizedBox(height: 16), - Text( - tr.migration.title, - style: textTheme.headlineMedium, - textAlign: TextAlign.center, - ), - const SizedBox(height: 16), - Text( - tr.migration.subtitle, - style: textTheme.titleMedium, - textAlign: TextAlign.center, - ), - const SizedBox(height: 32), - Obx( - () => TextFormField( + body: Center( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(8).copyWith(bottom: 96), + child: SizedBox( + width: 400, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.asset( + 'assets/images/logo.png', + height: 100, + ), + const SizedBox(height: 16), + Text( + tr.migration.title, + style: textTheme.headlineMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + Text( + tr.migration.subtitle, + style: textTheme.titleMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 32), + TextFormField( controller: controller.username, decoration: InputDecoration( label: Text(tr.migration.username.label), hintText: tr.migration.username.placeholder, ), ), - ), - const SizedBox(height: 8), - Text(tr.migration.username.info, style: textTheme.bodySmall), - const SizedBox(height: 16), - Obx( - () => SelectBox( + const SizedBox(height: 8), + Text(tr.migration.username.info, + style: textTheme.bodySmall), + const SizedBox(height: 16), + SelectBox( value: controller.language, label: Text(tr.migration.language.data), items: const [ @@ -72,8 +69,8 @@ class MigrationView extends GetView { ], onChanged: null, ), - ), - ], + ], + ), ), ), ), diff --git a/lib/app/modules/SelectCharacterTheme/bindings/select_character_theme_binding.dart b/lib/app/modules/SelectCharacterTheme/bindings/select_character_theme_binding.dart deleted file mode 100644 index 2b2d1e7f..00000000 --- a/lib/app/modules/SelectCharacterTheme/bindings/select_character_theme_binding.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:get/get.dart'; - -import '../controllers/select_character_theme_controller.dart'; - -class SelectCharacterThemeBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut( - () => SelectCharacterThemeController(), - ); - } -} diff --git a/lib/app/modules/SelectCharacterTheme/controllers/select_character_theme_controller.dart b/lib/app/modules/SelectCharacterTheme/controllers/select_character_theme_controller.dart index cacb5f51..58863a8a 100644 --- a/lib/app/modules/SelectCharacterTheme/controllers/select_character_theme_controller.dart +++ b/lib/app/modules/SelectCharacterTheme/controllers/select_character_theme_controller.dart @@ -1,40 +1,34 @@ -import 'package:dungeon_paper/app/data/services/character_service.dart'; -import 'package:dungeon_paper/app/data/services/user_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; import 'package:dungeon_paper/app/themes/themes.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -class SelectCharacterThemeController extends GetxController - with CharacterServiceMixin, UserServiceMixin { - final seeAll = {Brightness.light: false, Brightness.dark: false}.obs; - final lightTheme = Rx(AppThemes.parchment); - final darkTheme = Rx(AppThemes.dark); +class SelectCharacterThemeController extends ChangeNotifier with CharacterProviderMixin { + final seeAll = {Brightness.light: false, Brightness.dark: false}; + int? lightTheme = AppThemes.parchment; + int? darkTheme = AppThemes.dark; - @override - void onReady() { - super.onReady(); - lightTheme.value = char.settings.lightTheme; - darkTheme.value = char.settings.darkTheme; - if (lightTheme.value != null && - !AppThemes.allLightThemes.contains(lightTheme.value)) { + SelectCharacterThemeController() { + lightTheme = char.settings.lightTheme; + darkTheme = char.settings.darkTheme; + if (lightTheme != null && !AppThemes.allLightThemes.contains(lightTheme)) { seeAll[Brightness.light] = true; } - if (darkTheme.value != null && - !AppThemes.allDarkThemes.contains(darkTheme.value)) { + if (darkTheme != null && !AppThemes.allDarkThemes.contains(darkTheme)) { seeAll[Brightness.dark] = true; } } void save() async { - await charService.updateCharacter( + await charProvider.updateCharacter( char.copyWith( settings: char.settings.copyWithThemes( - lightTheme: lightTheme.value, - darkTheme: darkTheme.value, + lightTheme: lightTheme, + darkTheme: darkTheme, ), ), ); - charService.switchToCharacterTheme(char); + notifyListeners(); + charProvider.switchToCharacterTheme(char); // Get.back(); } } diff --git a/lib/app/modules/SelectCharacterTheme/views/select_character_theme_view.dart b/lib/app/modules/SelectCharacterTheme/views/select_character_theme_view.dart index 7ed5f219..8a35d99c 100644 --- a/lib/app/modules/SelectCharacterTheme/views/select_character_theme_view.dart +++ b/lib/app/modules/SelectCharacterTheme/views/select_character_theme_view.dart @@ -2,11 +2,11 @@ import 'package:dungeon_paper/app/modules/Settings/views/theme_selector.dart'; import 'package:dungeon_paper/app/themes/themes.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; import '../controllers/select_character_theme_controller.dart'; -class SelectCharacterThemeView extends GetView { +class SelectCharacterThemeView extends StatelessWidget { const SelectCharacterThemeView({super.key}); @override Widget build(BuildContext context) { @@ -17,57 +17,57 @@ class SelectCharacterThemeView extends GetView { ), body: ListView( children: [ - Obx( - () => _sectionTitle( + Consumer( + builder: (context, controller, _) => _sectionTitle( context, tr.character.theme.defaultLight, onReset: () { - controller.lightTheme.value = null; + controller.lightTheme = null; controller.save(); }, - resetEnabled: controller.lightTheme.value != null, + resetEnabled: controller.lightTheme != null, onChangeSeeAll: (val) => controller.seeAll[Brightness.light] = val, seeAll: controller.seeAll[Brightness.light]!, ), ), _pad( - Obx( - () => ThemeSelector( + Consumer( + builder: (context, controller, _) => ThemeSelector( themes: controller.seeAll[Brightness.light]! ? AppThemes.allThemes : AppThemes.allLightThemes, - selected: controller.lightTheme.value, + selected: controller.lightTheme, onSelected: (theme) async { - controller.lightTheme.value = theme; + controller.lightTheme = theme; controller.save(); }, ), ), horizontal: 8, ), - Obx( - () => _sectionTitle( + Consumer( + builder: (context, controller, _) => _sectionTitle( context, tr.character.theme.defaultDark, onReset: () { - controller.darkTheme.value = null; + controller.darkTheme = null; controller.save(); }, - resetEnabled: controller.darkTheme.value != null, + resetEnabled: controller.darkTheme != null, onChangeSeeAll: (val) => controller.seeAll[Brightness.dark] = val, seeAll: controller.seeAll[Brightness.dark]!, ), ), _pad( - Obx( - () => ThemeSelector( + Consumer( + builder: (context, controller, _) => ThemeSelector( themes: controller.seeAll[Brightness.dark]! ? AppThemes.allThemes : AppThemes.allDarkThemes, - selected: controller.darkTheme.value, + selected: controller.darkTheme, onSelected: (theme) async { - controller.darkTheme.value = theme; + controller.darkTheme = theme; controller.save(); }, ), diff --git a/lib/app/modules/SendFeedback/bindings/send_feedback_binding.dart b/lib/app/modules/SendFeedback/bindings/send_feedback_binding.dart deleted file mode 100644 index 7e11ae7d..00000000 --- a/lib/app/modules/SendFeedback/bindings/send_feedback_binding.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:get/get.dart'; - -import '../controllers/send_feedback_controller.dart'; - -class SendFeedbackBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut( - () => SendFeedbackController(), - ); - } -} diff --git a/lib/app/modules/SendFeedback/controllers/send_feedback_controller.dart b/lib/app/modules/SendFeedback/controllers/send_feedback_controller.dart index 8366ff13..fdc4656e 100644 --- a/lib/app/modules/SendFeedback/controllers/send_feedback_controller.dart +++ b/lib/app/modules/SendFeedback/controllers/send_feedback_controller.dart @@ -1,47 +1,46 @@ -import 'package:dungeon_paper/app/data/services/user_service.dart'; import 'package:dungeon_paper/core/http/api.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -class SendFeedbackController extends GetxController with UserServiceMixin { - final email = TextEditingController().obs; - final title = TextEditingController().obs; - final body = TextEditingController().obs; - final sending = false.obs; +import '../../../data/services/user_provider.dart'; + +class SendFeedbackController extends ChangeNotifier with UserProviderMixin { + final email = TextEditingController(); + final title = TextEditingController(); + final body = TextEditingController(); + var sending = false; final GlobalKey formKey = GlobalKey(); - Future send() async { - sending.value = true; + Future send(BuildContext context) async { + sending = true; + final navigator = Navigator.of(context); + final messenger = ScaffoldMessenger.of(context); + final theme = Theme.of(context); await api.requests.sendFeedback( - email: user.isLoggedIn ? user.email : email.value.text, - subject: title.value.text, - body: body.value.text, + email: user.isLoggedIn ? user.email : email.text, + subject: title.text, + body: body.text, username: user.isLoggedIn ? user.username : null, ); - Get.back(); - Get.rawSnackbar( - title: tr.feedback.success.title, - message: tr.feedback.success.message, + navigator.pop(); + messenger.showSnackBar( + SnackBar( + content: Column( + children: [ + Text(tr.feedback.success.title, style: theme.textTheme.bodyLarge), + Text(tr.feedback.success.message), + ], + ), + ), ); } @override - void onInit() { - super.onInit(); + void dispose() { + super.dispose(); for (var element in [email, title, body]) { - element.value.addListener(() { - element.refresh(); - }); + element.dispose(); } } - - @override - void onClose() { - for (var element in [email, title, body]) { - element.value.dispose(); - } - super.onClose(); - } } diff --git a/lib/app/modules/SendFeedback/views/send_feedback_view.dart b/lib/app/modules/SendFeedback/views/send_feedback_view.dart index 7b3dd619..bec82171 100644 --- a/lib/app/modules/SendFeedback/views/send_feedback_view.dart +++ b/lib/app/modules/SendFeedback/views/send_feedback_view.dart @@ -4,11 +4,12 @@ import 'package:dungeon_paper/core/utils/email_address_validator.dart'; import 'package:dungeon_paper/core/utils/string_validator.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; +import '../../../data/services/user_provider.dart'; import '../controllers/send_feedback_controller.dart'; -class SendFeedbackView extends GetView { +class SendFeedbackView extends StatelessWidget { const SendFeedbackView({super.key}); @override Widget build(BuildContext context) { @@ -17,26 +18,30 @@ class SendFeedbackView extends GetView { title: Text(tr.feedback.title), centerTitle: true, ), - floatingActionButton: Obx( - () => AdvancedFloatingActionButton.extended( - onPressed: !controller.sending.value ? controller.send : null, + floatingActionButton: Consumer( + builder: (context, controller, _) => + AdvancedFloatingActionButton.extended( + onPressed: + !controller.sending ? () => controller.send(context) : null, label: Text(tr.feedback.send), - icon: controller.sending.value + icon: controller.sending ? const SizedBox.square( - dimension: 24, child: CircularProgressIndicator.adaptive()) + dimension: 24, + child: CircularProgressIndicator.adaptive(), + ) : const Icon(Icons.send), ), ), - body: Form( - key: controller.formKey, - autovalidateMode: AutovalidateMode.onUserInteraction, - child: ListView( - padding: const EdgeInsets.all(16), - children: [ - if (controller.user.isGuest) ...[ - Obx( - () => TextFormField( - controller: controller.email.value, + body: Consumer2( + builder: (context, controller, userProvider, _) => Form( + key: controller.formKey, + autovalidateMode: AutovalidateMode.onUserInteraction, + child: ListView( + padding: const EdgeInsets.all(16), + children: [ + if (userProvider.isGuest) ...[ + TextFormField( + controller: controller.email, validator: (email) => email?.isEmpty ?? true ? null : EmailAddressValidator().validator(email), @@ -44,31 +49,31 @@ class SendFeedbackView extends GetView { labelText: tr.feedback.form.email.label, ), ), + const SizedBox(height: 16), + ], + TextFormField( + controller: controller.title, + textCapitalization: TextCapitalization.sentences, + validator: + StringValidator(minLength: 1, maxLength: 100).validator, + decoration: InputDecoration( + labelText: tr.feedback.form.title.label, + ), ), const SizedBox(height: 16), + RichTextField( + controller: controller.body, + minLines: 10, + maxLines: 10, + textCapitalization: TextCapitalization.sentences, + validator: + StringValidator(minLength: 1, maxLength: 100).validator, + decoration: InputDecoration( + labelText: tr.feedback.form.body.label, + ), + ), ], - TextFormField( - controller: controller.title.value, - textCapitalization: TextCapitalization.sentences, - validator: - StringValidator(minLength: 1, maxLength: 100).validator, - decoration: InputDecoration( - labelText: tr.feedback.form.title.label, - ), - ), - const SizedBox(height: 16), - RichTextField( - controller: controller.body.value, - minLines: 10, - maxLines: 10, - textCapitalization: TextCapitalization.sentences, - validator: - StringValidator(minLength: 1, maxLength: 100).validator, - decoration: InputDecoration( - labelText: tr.feedback.form.body.label, - ), - ), - ], + ), ), ), ); diff --git a/lib/app/modules/Settings/bindings/settings_binding.dart b/lib/app/modules/Settings/bindings/settings_binding.dart deleted file mode 100644 index fb567f07..00000000 --- a/lib/app/modules/Settings/bindings/settings_binding.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:get/get.dart'; - -import '../controllers/settings_controller.dart'; - -class SettingsBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut( - () => SettingsController(), - ); - } -} diff --git a/lib/app/modules/Settings/controllers/settings_controller.dart b/lib/app/modules/Settings/controllers/settings_controller.dart index 385a9902..20f00995 100644 --- a/lib/app/modules/Settings/controllers/settings_controller.dart +++ b/lib/app/modules/Settings/controllers/settings_controller.dart @@ -1,15 +1,14 @@ import 'package:dungeon_paper/app/data/models/user_settings.dart'; -import 'package:dungeon_paper/app/data/services/user_service.dart'; +import 'package:dungeon_paper/app/data/services/user_provider.dart'; import 'package:dungeon_paper/app/themes/themes.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -class SettingsController extends GetxController with UserServiceMixin { - final seeAll = {Brightness.light: false, Brightness.dark: false}.obs; +class SettingsController extends ChangeNotifier with UserProviderMixin { + final seeAll = {Brightness.light: false, Brightness.dark: false}; - @override - void onReady() { - super.onReady(); + UserSettings get settings => user.settings; + + SettingsController(BuildContext context) { if (!AppThemes.allLightThemes.contains(settings.defaultLightTheme)) { seeAll[Brightness.light] = true; } @@ -18,9 +17,12 @@ class SettingsController extends GetxController with UserServiceMixin { } } - Future updateSettings(UserSettings settings) { - return userService.updateUser(user.copyWith(settings: settings)); + void toggleSeeAll(Brightness brightness) { + seeAll[brightness] = !seeAll[brightness]!; + notifyListeners(); } - UserSettings get settings => user.settings; + Future updateSettings(UserSettings settings) { + return userProvider.updateUser(user.copyWith(settings: settings)); + } } diff --git a/lib/app/modules/Settings/views/settings_view.dart b/lib/app/modules/Settings/views/settings_view.dart index d2a94fa1..c232b8eb 100644 --- a/lib/app/modules/Settings/views/settings_view.dart +++ b/lib/app/modules/Settings/views/settings_view.dart @@ -1,15 +1,16 @@ -import 'package:dungeon_paper/app/data/services/character_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; import 'package:dungeon_paper/app/themes/themes.dart'; import 'package:dungeon_paper/core/platform_helper.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; +import '../../../data/services/user_provider.dart'; import '../controllers/settings_controller.dart'; import 'theme_selector.dart'; -class SettingsView extends GetView - with CharacterServiceMixin { +class SettingsView extends StatelessWidget + with CharacterProviderMixin, UserProviderMixin { const SettingsView({super.key}); @override @@ -26,8 +27,8 @@ class SettingsView extends GetView tr.settings.categories.general, ), if (PlatformHelper.isAndroid) - Obx( - () => SwitchListTile.adaptive( + Consumer( + builder: (context, controller, _) => SwitchListTile.adaptive( title: Text(tr.settings.keepAwake), value: controller.settings.keepScreenAwake, onChanged: (value) => controller.updateSettings( @@ -35,8 +36,8 @@ class SettingsView extends GetView ), ), ), - Obx( - () => _sectionTitle( + Consumer( + builder: (context, controller, _) => _sectionTitle( context, tr.settings.defaultTheme.light, onChangeSeeAll: (val) => @@ -45,8 +46,8 @@ class SettingsView extends GetView ), ), _pad( - Obx( - () => ThemeSelector( + Consumer( + builder: (context, controller, _) => ThemeSelector( themes: controller.seeAll[Brightness.light]! ? AppThemes.allThemes : AppThemes.allLightThemes, @@ -55,16 +56,17 @@ class SettingsView extends GetView await controller.updateSettings( controller.settings.copyWith(defaultLightTheme: theme), ); - if (controller.user.brightness == Brightness.light) { - charService.switchToCharacterTheme(character); + + if (user.brightness == Brightness.light) { + charProvider.switchToCharacterTheme(charProvider.current); } }, ), ), horizontal: 8, ), - Obx( - () => _sectionTitle( + Consumer( + builder: (context, controller, _) => _sectionTitle( context, tr.settings.defaultTheme.dark, onChangeSeeAll: (val) => controller.seeAll[Brightness.dark] = val, @@ -72,8 +74,8 @@ class SettingsView extends GetView ), ), _pad( - Obx( - () => ThemeSelector( + Consumer( + builder: (context, controller, _) => ThemeSelector( themes: controller.seeAll[Brightness.dark]! ? AppThemes.allThemes : AppThemes.allDarkThemes, @@ -82,11 +84,12 @@ class SettingsView extends GetView await controller.updateSettings( controller.settings.copyWith(defaultDarkTheme: theme), ); - if (controller.user.brightness == Brightness.dark) { + if (user.brightness == Brightness.dark) { if (maybeChar != null) { - charService.switchToCharacterTheme(character); + final character = charProvider.current; + charProvider.switchToCharacterTheme(character); } else { - charService.switchToTheme(theme); + charProvider.switchToTheme(theme); } } }, @@ -126,4 +129,3 @@ class SettingsView extends GetView ); } } - diff --git a/lib/app/modules/Settings/views/theme_selector.dart b/lib/app/modules/Settings/views/theme_selector.dart index a86f4f95..a16018d7 100644 --- a/lib/app/modules/Settings/views/theme_selector.dart +++ b/lib/app/modules/Settings/views/theme_selector.dart @@ -137,6 +137,7 @@ class _ThemePreview extends StatelessWidget { bottom: 6, child: const Material( type: MaterialType.circle, + color: DwColors.success, child: Padding( padding: EdgeInsets.all(3), child: Icon( @@ -145,7 +146,6 @@ class _ThemePreview extends StatelessWidget { color: Colors.white, ), ), - color: DwColors.success, ), ), ], diff --git a/lib/app/modules/StartingGearForm/bindings/starting_gear_form_binding.dart b/lib/app/modules/StartingGearForm/bindings/starting_gear_form_binding.dart deleted file mode 100644 index 2cfaca39..00000000 --- a/lib/app/modules/StartingGearForm/bindings/starting_gear_form_binding.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:get/get.dart'; - -import '../controllers/starting_gear_form_controller.dart'; - -class StartingGearFormBinding extends Bindings { - @override - void dependencies() { - Get.put( - StartingGearFormController(), - ); - } -} diff --git a/lib/app/modules/StartingGearForm/controllers/starting_gear_form_controller.dart b/lib/app/modules/StartingGearForm/controllers/starting_gear_form_controller.dart index 6eb1e71c..df2e2b87 100644 --- a/lib/app/modules/StartingGearForm/controllers/starting_gear_form_controller.dart +++ b/lib/app/modules/StartingGearForm/controllers/starting_gear_form_controller.dart @@ -1,21 +1,21 @@ import 'package:dungeon_paper/app/data/models/character_class.dart'; import 'package:dungeon_paper/app/data/models/gear_choice.dart'; import 'package:dungeon_paper/app/data/models/gear_selection.dart'; -import 'package:get/get.dart'; +import 'package:dungeon_paper/core/route_arguments.dart'; +import 'package:dungeon_paper/core/utils/list_utils.dart'; +import 'package:flutter/widgets.dart'; -class StartingGearFormController extends GetxController { - final availableGear = [].obs; - final dirty = false.obs; +class StartingGearFormController extends ChangeNotifier { + final availableGear = []; + var dirty = false; - late final RxList selectedOptions; - late final CharacterClass characterClass; - late final void Function(List selectedOptions) onChanged; + late List selectedOptions; + late CharacterClass characterClass; + late void Function(List selectedOptions) onChanged; - @override - void onReady() { - super.onReady(); - final StartingGearFormArguments args = Get.arguments; - selectedOptions = args.selectedOptions.obs; + StartingGearFormController(BuildContext context) { + final StartingGearFormArguments args = getArgs(context); + selectedOptions = args.selectedOptions; characterClass = args.characterClass; onChanged = args.onChanged; getGear(); @@ -27,11 +27,13 @@ class StartingGearFormController extends GetxController { } void getGear() async { - availableGear.value = characterClass.gearChoices; + availableGear.clear(); + availableGear.addAll(characterClass.gearChoices); + notifyListeners(); } void toggleSelect(GearSelection selection) { - dirty.value = true; + dirty = true; final found = selectedOptions.firstWhereOrNull((item) => item.key == selection.key); if (found == null) { @@ -39,6 +41,7 @@ class StartingGearFormController extends GetxController { } else { selectedOptions.remove(found); } + notifyListeners(); } bool isSelected(GearSelection selection) => diff --git a/lib/app/modules/StartingGearForm/views/starting_gear_form_view.dart b/lib/app/modules/StartingGearForm/views/starting_gear_form_view.dart index 6b48500c..9fd255e7 100644 --- a/lib/app/modules/StartingGearForm/views/starting_gear_form_view.dart +++ b/lib/app/modules/StartingGearForm/views/starting_gear_form_view.dart @@ -4,27 +4,27 @@ import 'package:dungeon_paper/app/widgets/atoms/confirm_exit_view.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; import '../controllers/starting_gear_form_controller.dart'; -class StartingGearFormView extends GetView { +class StartingGearFormView extends StatelessWidget { const StartingGearFormView({ super.key, }); @override Widget build(BuildContext context) { - return Obx( - () => ConfirmExitView( - dirty: controller.dirty.value, + return Consumer( + builder: (context, controller, _) => ConfirmExitView( + dirty: controller.dirty, child: Scaffold( appBar: AppBar( title: Text(tr.generic.selectEntity(tr.entity(tn(GearSelection)))), ), floatingActionButton: AdvancedFloatingActionButton.extended( - onPressed: _save, + onPressed: () => _save(context), label: Text(tr.generic.save), icon: const Icon(Icons.save), ), @@ -91,8 +91,10 @@ class StartingGearFormView extends GetView { ); } - _save() { + _save(BuildContext context) { + final controller = + Provider.of(context, listen: false); controller.onChanged(controller.selectedOptions); - Get.back(); + Navigator.of(context).pop(); } } diff --git a/lib/app/modules/UniversalSearch/bindings/universal_search_binding.dart b/lib/app/modules/UniversalSearch/bindings/universal_search_binding.dart deleted file mode 100644 index 7915bba5..00000000 --- a/lib/app/modules/UniversalSearch/bindings/universal_search_binding.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:get/get.dart'; - -import '../controllers/universal_search_controller.dart'; - -class UniversalSearchBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut( - () => UniversalSearchController(), - ); - } -} diff --git a/lib/app/modules/UniversalSearch/controllers/universal_search_controller.dart b/lib/app/modules/UniversalSearch/controllers/universal_search_controller.dart index 3e116e2a..b07427d4 100644 --- a/lib/app/modules/UniversalSearch/controllers/universal_search_controller.dart +++ b/lib/app/modules/UniversalSearch/controllers/universal_search_controller.dart @@ -3,8 +3,8 @@ import 'package:dungeon_paper/app/data/models/item.dart'; import 'package:dungeon_paper/app/data/models/move.dart'; import 'package:dungeon_paper/app/data/models/race.dart'; import 'package:dungeon_paper/app/data/models/spell.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; +import 'package:dungeon_paper/app/data/services/repository_provider.dart'; import 'package:dungeon_paper/app/model_utils/model_search.dart'; import 'package:dungeon_paper/app/modules/LibraryList/views/filters/character_class_filters.dart'; import 'package:dungeon_paper/app/modules/LibraryList/views/filters/item_filters.dart'; @@ -14,7 +14,6 @@ import 'package:dungeon_paper/app/modules/LibraryList/views/filters/spell_filter import 'package:dungeon_paper/core/utils/list_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; enum SourceType { character, @@ -22,24 +21,29 @@ enum SourceType { builtInLibrary, } -class UniversalSearchController extends GetxController - with RepositoryServiceMixin, CharacterServiceMixin { - final _search = TextEditingController(text: '').obs; +class UniversalSearchController extends ChangeNotifier + with RepositoryProviderMixin, CharacterProviderMixin { + final _search = TextEditingController(text: ''); - TextEditingController get search => _search.value; + TextEditingController get search => _search; - bool get hasCharacter => charService.all.isNotEmpty; + bool get hasCharacter => charProvider.all.isNotEmpty; final enabledSources = { SourceType.character, SourceType.myLibrary, SourceType.builtInLibrary - }.obs; + }; + + @override + UniversalSearchController() { + search.addListener(_update); + } Map> get sources => { Move: >[ sourceEnabled(SourceType.character) - ? charService.maybeCurrent?.moves ?? [] + ? charProvider.maybeCurrent?.moves ?? [] : [], sourceEnabled(SourceType.myLibrary) ? repo.my.moves.values : [], sourceEnabled(SourceType.builtInLibrary) @@ -48,7 +52,7 @@ class UniversalSearchController extends GetxController ], Spell: >[ sourceEnabled(SourceType.character) - ? charService.maybeCurrent?.spells ?? [] + ? charProvider.maybeCurrent?.spells ?? [] : [], sourceEnabled(SourceType.myLibrary) ? repo.my.spells.values : [], sourceEnabled(SourceType.builtInLibrary) @@ -57,7 +61,7 @@ class UniversalSearchController extends GetxController ], Item: >[ sourceEnabled(SourceType.character) - ? charService.maybeCurrent?.items ?? [] + ? charProvider.maybeCurrent?.items ?? [] : [], sourceEnabled(SourceType.myLibrary) ? repo.my.items.values : [], sourceEnabled(SourceType.builtInLibrary) @@ -79,22 +83,17 @@ class UniversalSearchController extends GetxController }; void toggleSource(SourceType type, [bool? value]) { - final value0 = value ?? !sourceEnabled(type); - if (value0) { + final effectiveValue = value ?? !sourceEnabled(type); + if (effectiveValue) { enabledSources.add(type); } else { enabledSources.remove(type); } + notifyListeners(); } bool sourceEnabled(SourceType type) => enabledSources.contains(type); - @override - void onInit() { - super.onInit(); - search.addListener(_update); - } - List getSource() => flatten(sources[T] as List>); List flatten(List> list) => @@ -150,13 +149,13 @@ class UniversalSearchController extends GetxController } @override - void onClose() { - super.onClose(); + void dispose() { + super.dispose(); search.removeListener(_update); } void _update() { - _search.refresh(); + notifyListeners(); } } diff --git a/lib/app/modules/UniversalSearch/views/universal_search_view.dart b/lib/app/modules/UniversalSearch/views/universal_search_view.dart index 8a6c88e4..7fba8a41 100644 --- a/lib/app/modules/UniversalSearch/views/universal_search_view.dart +++ b/lib/app/modules/UniversalSearch/views/universal_search_view.dart @@ -7,6 +7,7 @@ import 'package:dungeon_paper/app/data/models/item.dart'; import 'package:dungeon_paper/app/data/models/move.dart'; import 'package:dungeon_paper/app/data/models/race.dart'; import 'package:dungeon_paper/app/data/models/spell.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; import 'package:dungeon_paper/app/widgets/atoms/search_field.dart'; import 'package:dungeon_paper/app/widgets/cards/character_class_card.dart'; import 'package:dungeon_paper/app/widgets/cards/item_card.dart'; @@ -16,11 +17,11 @@ import 'package:dungeon_paper/app/widgets/cards/spell_card.dart'; import 'package:dungeon_paper/app/widgets/chips/primary_chip.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; import '../controllers/universal_search_controller.dart'; -class UniversalSearchView extends GetView { +class UniversalSearchView extends StatelessWidget { UniversalSearchView({super.key}); final bucket = PageStorageBucket(); @@ -32,50 +33,51 @@ class UniversalSearchView extends GetView { child: Scaffold( backgroundColor: Colors.black54, appBar: AppBar( - title: SearchField( - controller: controller.search, - autofocus: true, - ), + title: Consumer( + builder: (context, controller, _) { + return SearchField( + controller: controller.search, + autofocus: true, + ); + }), centerTitle: true, backgroundColor: Colors.transparent, foregroundColor: Colors.white, automaticallyImplyLeading: true, ), - body: Obx( - () => PageStorage( + body: Consumer2( + builder: (context, charProvider, controller, _) => PageStorage( bucket: bucket, child: Column( children: [ - Obx( - () => Padding( - padding: const EdgeInsets.all(8.0), - child: Wrap( - spacing: 8, - runSpacing: 0, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - Text( - '${tr.search.searchIn}: ', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Colors.white), - ), - if (controller.hasCharacter) - _FilterChip( - label: tr.entity(tn(Character)), - sourceType: SourceType.character, - ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Wrap( + spacing: 8, + runSpacing: 0, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + Text( + '${tr.search.searchIn}: ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.white), + ), + if (controller.hasCharacter) _FilterChip( - label: tr.myLibrary.title, - sourceType: SourceType.myLibrary, + label: tr.entity(tn(Character)), + sourceType: SourceType.character, ), - _FilterChip( - label: tr.playbook.title, - sourceType: SourceType.builtInLibrary, - ), - ], - ), + _FilterChip( + label: tr.myLibrary.title, + sourceType: SourceType.myLibrary, + ), + _FilterChip( + label: tr.playbook.title, + sourceType: SourceType.builtInLibrary, + ), + ], ), ), FutureBuilder>( @@ -196,7 +198,7 @@ class _CardByType extends StatelessWidget { ); } -class _FilterChip extends GetView { +class _FilterChip extends StatelessWidget { const _FilterChip({ // ignore: unused_element super.key, @@ -209,6 +211,8 @@ class _FilterChip extends GetView { @override Widget build(BuildContext context) { + final UniversalSearchController controller = + Provider.of(context, listen: false); final theme = Theme.of(context); final selected = controller.sourceEnabled(sourceType); @@ -216,9 +220,15 @@ class _FilterChip extends GetView { icon: selected ? const Icon(Icons.check) : null, label: label, backgroundColor: selected ? null : theme.disabledColor, - onPressed: onPressed, + onPressed: onPressed(context), ); } - void onPressed() => controller.toggleSource(sourceType); + void Function() onPressed(BuildContext context) { + return () { + final UniversalSearchController controller = + Provider.of(context, listen: false); + controller.toggleSource(sourceType); + }; + } } diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index 6f064fc7..43541b2d 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -1,6 +1,7 @@ -import 'package:flutter/widgets.dart'; - -import 'package:get/get.dart'; +import 'package:dungeon_paper/app/routes/custom_transitions.dart'; +import 'package:dungeon_paper/core/route_arguments.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import '../data/models/character_class.dart'; import '../data/models/item.dart'; @@ -9,59 +10,59 @@ import '../data/models/move.dart'; import '../data/models/note.dart'; import '../data/models/race.dart'; import '../data/models/spell.dart'; -import '../data/services/character_service.dart'; -import '../modules/AbilityScoreForm/bindings/ability_score_form_binding.dart'; +import '../modules/AbilityScoreForm/controllers/ability_score_form_controller.dart'; import '../modules/AbilityScoreForm/views/ability_score_form_view.dart'; -import '../modules/AbilityScoresForm/bindings/ability_scores_form_binding.dart'; +import '../modules/AbilityScoresForm/controllers/ability_scores_form_controller.dart'; import '../modules/AbilityScoresForm/views/ability_scores_form_view.dart'; -import '../modules/About/bindings/about_binding.dart'; +import '../modules/About/controllers/about_controller.dart'; import '../modules/About/views/about_view.dart'; -import '../modules/Account/bindings/account_binding.dart'; +import '../modules/Account/controllers/account_controller.dart'; import '../modules/Account/views/account_view.dart'; -import '../modules/BasicInfoForm/bindings/basic_info_form_binding.dart'; +import '../modules/BasicInfoForm/controllers/basic_info_form_controller.dart'; import '../modules/BasicInfoForm/views/basic_info_form_view.dart'; -import '../modules/BioForm/bindings/bio_form_binding.dart'; +import '../modules/BioForm/controllers/bio_form_controller.dart'; import '../modules/BioForm/views/bio_form_view.dart'; -import '../modules/BondsFlagsForm/bindings/bonds_flags_form_binding.dart'; +import '../modules/BondsFlagsForm/controllers/bonds_flags_form_controller.dart'; import '../modules/BondsFlagsForm/views/bonds_flags_form_view.dart'; -import '../modules/Campaign/CampaignsList/bindings/campaigns_list_binding.dart'; +import '../modules/Campaign/CampaignsList/controllers/campaigns_list_controller.dart'; import '../modules/Campaign/CampaignsList/views/campaigns_list_view.dart'; -import '../modules/Campaign/bindings/campaign_binding.dart'; -import '../modules/Campaign/views/campaign_view.dart'; -import '../modules/CharacterList/bindings/character_list_binding.dart'; import '../modules/CharacterList/views/character_list_view.dart'; -import '../modules/ClassAlignments/bindings/class_alignments_binding.dart'; +import '../modules/ClassAlignments/controllers/class_alignments_controller.dart'; import '../modules/ClassAlignments/views/class_alignments_view.dart'; -import '../modules/CreateCharacter/SelectMovesSpells/bindings/select_moves_spells_binding.dart'; +import '../modules/CreateCharacter/SelectMovesSpells/controllers/select_moves_spells_controller.dart'; import '../modules/CreateCharacter/SelectMovesSpells/views/select_moves_spells_view.dart'; -import '../modules/CreateCharacter/bindings/create_character_binding.dart'; +import '../modules/CreateCharacter/controllers/create_character_controller.dart'; import '../modules/CreateCharacter/views/create_character_view.dart'; -import '../modules/Home/bindings/home_binding.dart'; import '../modules/Home/views/home_view.dart'; -import '../modules/ImportExport/bindings/import_export_binding.dart'; +import '../modules/ImportExport/controllers/export_controller.dart'; +import '../modules/ImportExport/controllers/import_controller.dart'; +import '../modules/ImportExport/controllers/import_export_controller.dart'; import '../modules/ImportExport/views/import_export_view.dart'; -import '../modules/LibraryList/bindings/library_collection_binding.dart'; -import '../modules/LibraryList/bindings/library_form_binding.dart'; -import '../modules/LibraryList/bindings/library_list_binding.dart'; +import '../modules/LibraryList/controllers/library_list_controller.dart'; import '../modules/LibraryList/views/character_classes_library_list_view.dart'; +import '../modules/LibraryList/views/filters/character_class_filters.dart'; +import '../modules/LibraryList/views/filters/item_filters.dart'; +import '../modules/LibraryList/views/filters/move_filters.dart'; +import '../modules/LibraryList/views/filters/race_filters.dart'; +import '../modules/LibraryList/views/filters/spell_filters.dart'; import '../modules/LibraryList/views/items_library_list_view.dart'; import '../modules/LibraryList/views/library_collection_view.dart'; import '../modules/LibraryList/views/moves_library_list_view.dart'; import '../modules/LibraryList/views/races_library_list_view.dart'; import '../modules/LibraryList/views/spells_library_list_view.dart'; -import '../modules/Login/bindings/login_binding.dart'; +import '../modules/Login/controllers/login_controller.dart'; import '../modules/Login/views/login_view.dart'; -import '../modules/Migration/bindings/migration_binding.dart'; +import '../modules/Migration/controllers/migration_controller.dart'; import '../modules/Migration/views/migration_view.dart'; -import '../modules/SelectCharacterTheme/bindings/select_character_theme_binding.dart'; +import '../modules/SelectCharacterTheme/controllers/select_character_theme_controller.dart'; import '../modules/SelectCharacterTheme/views/select_character_theme_view.dart'; -import '../modules/SendFeedback/bindings/send_feedback_binding.dart'; +import '../modules/SendFeedback/controllers/send_feedback_controller.dart'; import '../modules/SendFeedback/views/send_feedback_view.dart'; -import '../modules/Settings/bindings/settings_binding.dart'; +import '../modules/Settings/controllers/settings_controller.dart'; import '../modules/Settings/views/settings_view.dart'; -import '../modules/StartingGearForm/bindings/starting_gear_form_binding.dart'; +import '../modules/StartingGearForm/controllers/starting_gear_form_controller.dart'; import '../modules/StartingGearForm/views/starting_gear_form_view.dart'; -import '../modules/UniversalSearch/bindings/universal_search_binding.dart'; +import '../modules/UniversalSearch/controllers/universal_search_controller.dart'; import '../modules/UniversalSearch/views/universal_search_view.dart'; import '../widgets/forms/character_class_form.dart'; import '../widgets/forms/item_form.dart'; @@ -71,7 +72,7 @@ import '../widgets/forms/race_form.dart'; import '../widgets/forms/spell_form.dart'; import '../widgets/molecules/user_menu_popover.dart'; import '../widgets/views/roll_dice_view.dart'; -import 'custom_transitions.dart'; +// import 'custom_transitions.dart'; part 'app_routes.dart'; @@ -80,286 +81,237 @@ class AppPages { static final pageController = PageController(); - static final CharacterService characterService = Get.find(); - static const initial = Routes.home; - static final routes = [ - GetPage( - name: Routes.login, - page: () => const LoginView(), - binding: LoginBinding(), - ), - GetPage( - name: Routes.userMenu, - page: () => UserMenuPopover(), - preventDuplicates: false, - fullscreenDialog: true, - opaque: false, - customTransition: CustomTransitions.circularReveal( - alignment: Alignment.topRight, - offset: const Offset(-24, 64), - ), - transition: Transition.circularReveal, - ), - GetPage( - name: Routes.home, - page: () => const HomeView(), - binding: HomeBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.characterList, - page: () => const CharacterListPageView(), - binding: CharacterListPageBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.settings, - page: () => const SettingsView(), - binding: SettingsBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.importExport, - page: () => const ImportExportView(), - binding: ImportExportBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.rollDice, - page: () => RollDiceView(dice: Get.arguments), - opaque: false, - fullscreenDialog: true, - customTransition: CustomTransitions.circularReveal( - alignment: Alignment.bottomCenter, - ), - transition: Transition.circularReveal, - ), - - // - - GetPage( - name: Routes.library, - page: () => const LibraryCollectionView(), - binding: LibraryCollectionBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.moves, - page: () => const MovesLibraryListView(), - binding: LibraryListBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.editMove, - page: () => const MoveForm(), - binding: LibraryFormBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.spells, - page: () => const SpellsLibraryListView(), - binding: LibraryListBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.editSpell, - page: () => const SpellForm(), - binding: LibraryFormBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.items, - page: () => const ItemsLibraryListView(), - binding: LibraryListBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.editItem, - page: () => const ItemForm(), - binding: LibraryFormBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.classes, - page: () => const CharacterClassesLibraryListView(), - binding: LibraryListBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.editClass, - page: () => const CharacterClassForm(), - binding: LibraryFormBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.races, - page: () => const RacesLibraryListView(), - binding: LibraryListBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.editRace, - page: () => const RaceForm(), - binding: LibraryFormBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.editNote, - page: () => const NoteForm(), - binding: LibraryFormBinding(), - preventDuplicates: false, - ), - - // - - GetPage( - name: Routes.bondsFlags, - page: () => const BondsFlagsFormView(), - binding: BondsFlagsFormBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.bio, - page: () => const BioFormView(), - binding: BioFormBinding(), - ), - GetPage( - name: Routes.basicInfo, - page: () => const BasicInfoFormView(), - binding: BasicInfoFormBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.abilityScores, - page: () => const AbilityScoresFormView(), - binding: AbilityScoresFormBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.abilityScoreForm, - page: () => const AbilityScoreFormView(), - binding: AbilityScoreFormBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.bondsFlags, - page: () => const BondsFlagsFormView(), - binding: AbilityScoresFormBinding(), - preventDuplicates: false, - ), - - // - - GetPage( - name: Routes.createCharacter, - page: () => const CreateCharacterView(), - binding: CreateCharacterBinding(), - preventDuplicates: false, - opaque: false, - fullscreenDialog: true, - ), - GetPage( - name: Routes.createCharacterSelectClass, - page: () => const CharacterClassesLibraryListView(), - binding: LibraryListBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.createCharacterStartingGear, - page: () => const StartingGearFormView(), - binding: StartingGearFormBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.createCharacterMovesSpells, - page: () => const SelectMovesSpellsView(), - binding: SelectMovesSpellsBinding(), - preventDuplicates: false, - ), - - GetPage( - name: Routes.createCharacterBasicInfo, - page: () => const BasicInfoFormView(), - binding: BasicInfoFormBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.createCharacterAbilityScores, - page: () => const AbilityScoresFormView(), - binding: AbilityScoresFormBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.classAlignments, - page: () => const ClassAlignmentsView(), - binding: ClassAlignmentsBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.universalSearch, - page: () => UniversalSearchView(), - binding: UniversalSearchBinding(), - opaque: false, - fullscreenDialog: true, - customTransition: CustomTransitions.circularReveal( - // alignment: Alignment.topRight, - // offset: const Offset(-76, 64), - alignment: Alignment.topLeft, - offset: const Offset(26, 64), - ), - // transitionDuration: const Duration(seconds: 1), - transition: Transition.circularReveal, - preventDuplicates: false, - ), - GetPage( - name: Routes.migration, - page: () => const MigrationView(), - binding: MigrationBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.about, - page: () => const AboutView(), - binding: AboutBinding(), - preventDuplicates: false, - ), - GetPage( - name: Routes.selectCharacterTheme, - page: () => const SelectCharacterThemeView(), - binding: SelectCharacterThemeBinding(), - preventDuplicates: false, - ), - GetPage( - name: _Paths.abilityScoreForm, - page: () => const AbilityScoreFormView(), - binding: AbilityScoreFormBinding(), - preventDuplicates: false, - ), - GetPage( - name: _Paths.account, - page: () => const AccountView(), - binding: AccountBinding(), - ), - GetPage( - name: _Paths.sendFeedback, - page: () => const SendFeedbackView(), - binding: SendFeedbackBinding(), - ), - GetPage( - name: _Paths.campaigns, - page: () => const CampaignsListView(), - binding: CampaignsListBinding(), - ), - ]; -} - -class CustomTransitions { - static CustomTransition circularReveal({ - Offset? offset, - Alignment? alignment, - }) { - return CustomCircularRevealTransition(offset: offset, alignment: alignment); + /// https://github.com/DungeonPaper/dungeon-paper-app/blob/86b660037e2e0aeb7b83930efb33785a0d829ebe/lib/app/routes/app_pages.dart#L99 + static Route onGenerateRoute(RouteSettings settings) { + final route = settings.name; + final builder = routes[route]; + debugPrint('[ROUTER] Building route $route'); + if (builder == null) { + throw Exception('No route defined for $route'); + } + if (transitionsMap.containsKey(route)) { + return transitionsMap[route]!(builder, settings); + } + return MaterialPageRoute( + builder: builder, + settings: settings, + ); } + + static final transitionsMap = + { + Routes.rollDice: (builder, settings) => CircularRevealTransitionBuilder( + builder: builder, + settings: settings, + alignment: Alignment.bottomCenter, + ), + Routes.userMenu: (builder, settings) => CircularRevealTransitionBuilder( + builder: builder, + settings: settings, + alignment: Alignment.topRight, + offset: const Offset(-24, 64), + ), + Routes.universalSearch: (builder, settings) => + CircularRevealTransitionBuilder( + builder: builder, + settings: settings, + alignment: Alignment.topLeft, + offset: const Offset(26, 64), + ), + }; + + static final routes = { + Routes.login: (context) => ChangeNotifierProvider( + create: (_) => LoginController(), + child: const LoginView(), + ), + + Routes.userMenu: (context) => const UserMenuPopover(), + + Routes.home: (context) => const HomeView(), + + Routes.characterList: (context) => const CharacterListPageView(), + + Routes.settings: (context) => ChangeNotifierProvider( + create: (_) => SettingsController(context), + child: const SettingsView(), + ), + + Routes.importExport: (context) => MultiProvider( + providers: [ + ChangeNotifierProvider(create: (_) => ImportExportController()), + ChangeNotifierProvider(create: (_) => ImportController()), + ChangeNotifierProvider(create: (_) => ExportController()), + ], + child: const ImportExportView(), + ), + + Routes.rollDice: (context) => RollDiceView(dice: getArgs(context)), + + // + + Routes.library: (context) => const LibraryCollectionView(), + + Routes.moves: (context) => ChangeNotifierProvider( + create: (_) => LibraryListController(context), + child: const MovesLibraryListView(), + ), + + Routes.editMove: (context) => ChangeNotifierProvider( + create: (_) => MoveFormController(context), + child: const MoveForm(), + ), + + Routes.spells: (context) => ChangeNotifierProvider( + create: (_) => LibraryListController(context), + child: const SpellsLibraryListView(), + ), + + Routes.editSpell: (context) => ChangeNotifierProvider( + create: (_) => SpellFormController(context), + child: const SpellForm(), + ), + + Routes.items: (context) => ChangeNotifierProvider( + create: (_) => LibraryListController(context), + child: const ItemsLibraryListView(), + ), + + Routes.editItem: (context) => ChangeNotifierProvider( + create: (_) => ItemFormController(context), + child: const ItemForm(), + ), + + Routes.classes: (context) => ChangeNotifierProvider( + create: (_) => + LibraryListController( + context), + child: const CharacterClassesLibraryListView(), + ), + + Routes.editClass: (context) => ChangeNotifierProvider( + create: (_) => CharacterClassFormController(context), + child: const CharacterClassForm(), + ), + + Routes.races: (context) => ChangeNotifierProvider( + create: (_) => LibraryListController(context), + child: const RacesLibraryListView(), + ), + + Routes.editRace: (context) => ChangeNotifierProvider( + create: (_) => RaceFormController(context), + child: const RaceForm(), + ), + + Routes.editNote: (context) => ChangeNotifierProvider( + create: (_) => NoteFormController(context), + child: const NoteForm(), + ), + + // + + Routes.bondsFlags: (context) => ChangeNotifierProvider( + create: (_) => BondsFlagsFormController(context), + child: const BondsFlagsFormView(), + ), + + Routes.bio: (context) => ChangeNotifierProvider( + create: (_) => BioFormController(context), + child: const BioFormView(), + ), + + Routes.basicInfo: (context) => ChangeNotifierProvider( + create: (_) => BasicInfoFormController(context), + child: const BasicInfoFormView(), + ), + + Routes.abilityScores: (context) => ChangeNotifierProvider( + create: (_) => AbilityScoresFormController(context), + child: const AbilityScoresFormView(), + ), + + Routes.abilityScoreForm: (context) => ChangeNotifierProvider( + child: const AbilityScoreFormView(), + create: (_) => AbilityScoreFormController(context), + ), + + // + + Routes.createCharacter: (context) => ChangeNotifierProvider( + create: (_) => CreateCharacterController(), + child: const CreateCharacterView(), + ), + + Routes.createCharacterSelectClass: (context) => + const CharacterClassesLibraryListView(), + + Routes.createCharacterStartingGear: (context) => ChangeNotifierProvider( + child: const StartingGearFormView(), + create: (_) => StartingGearFormController(context), + ), + + Routes.createCharacterMovesSpells: (context) => ChangeNotifierProvider( + child: const SelectMovesSpellsView(), + create: (_) => SelectMovesSpellsController(context), + ), + + Routes.createCharacterBasicInfo: (context) => ChangeNotifierProvider( + child: const BasicInfoFormView(), + create: (_) => BasicInfoFormController(context), + ), + + Routes.createCharacterAbilityScores: (context) => ChangeNotifierProvider( + child: const AbilityScoresFormView(), + create: (_) => AbilityScoresFormController(context), + ), + + Routes.classAlignments: (context) => ChangeNotifierProvider( + child: const ClassAlignmentsView(), + create: (_) => ClassAlignmentsController(context), + ), + + Routes.universalSearch: (context) => ChangeNotifierProvider( + create: (_) => UniversalSearchController(), + child: UniversalSearchView(), + ), + + Routes.migration: (context) => ChangeNotifierProvider( + child: const MigrationView(), + create: (_) => MigrationController(context), + ), + + Routes.about: (context) => ChangeNotifierProvider( + child: const AboutView(), + create: (_) => AboutController(), + ), + + Routes.selectCharacterTheme: (context) => ChangeNotifierProvider( + child: const SelectCharacterThemeView(), + create: (_) => SelectCharacterThemeController(), + ), + + _Paths.abilityScoreForm: (context) => ChangeNotifierProvider( + child: const AbilityScoreFormView(), + create: (_) => AbilityScoreFormController(context), + ), + + _Paths.account: (context) => ChangeNotifierProvider( + child: const AccountView(), + create: (_) => AccountController(), + ), + + _Paths.sendFeedback: (context) => ChangeNotifierProvider( + child: const SendFeedbackView(), + create: (_) => SendFeedbackController(), + ), + + _Paths.campaigns: (context) => ChangeNotifierProvider( + child: const CampaignsListView(), + create: (_) => CampaignsListController(), + ), + }; } + diff --git a/lib/app/routes/custom_transitions.dart b/lib/app/routes/custom_transitions.dart index f4f5e36e..ad72265c 100644 --- a/lib/app/routes/custom_transitions.dart +++ b/lib/app/routes/custom_transitions.dart @@ -2,16 +2,15 @@ import 'dart:math' show sqrt, max; import 'dart:ui' show lerpDouble; import 'package:flutter/material.dart'; -import 'package:get/get_navigation/src/routes/custom_transition.dart'; -class CustomCircularRevealClipper extends CustomClipper { +class CircularRevealClipper extends CustomClipper { final double fraction; final Alignment? centerAlignment; final Offset? centerOffset; final double? minRadius; final double? maxRadius; - CustomCircularRevealClipper({ + CircularRevealClipper({ required this.fraction, this.centerAlignment, this.centerOffset, @@ -46,28 +45,41 @@ class CustomCircularRevealClipper extends CustomClipper { } } -class CustomCircularRevealTransition extends CustomTransition { - CustomCircularRevealTransition({ - this.alignment, - this.offset, - }); +class CircularRevealTransitionBuilder extends PageRouteBuilder { + CircularRevealTransitionBuilder({ + super.settings, + required WidgetBuilder? builder, + super.transitionsBuilder, + super.transitionDuration = const Duration(milliseconds: 300), + super.reverseTransitionDuration = const Duration(milliseconds: 300), + super.opaque = false, + super.barrierDismissible = false, + super.barrierColor, + super.barrierLabel, + super.maintainState = true, + super.fullscreenDialog, + super.allowSnapshotting = true, + this.alignment = Alignment.center, + this.offset = Offset.zero, + this.curve = Curves.easeInOut, + }) : super(pageBuilder: (context, __, ___) => builder!(context)); - final Alignment? alignment; - final Offset? offset; + final Alignment alignment; + final Offset offset; + final Curve curve; @override - Widget buildTransition( - BuildContext context, - Curve? curve, - Alignment? alignment, - Animation animation, - Animation secondaryAnimation, - Widget child) { + Widget buildTransitions( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child, + ) { return ClipPath( - clipper: CustomCircularRevealClipper( + clipper: CircularRevealClipper( fraction: animation.value, - centerAlignment: this.alignment ?? Alignment.center, - centerOffset: offset ?? Offset.zero, + centerAlignment: alignment, + centerOffset: offset, minRadius: 0, maxRadius: MediaQuery.of(context).size.longestSide * 2, ), @@ -75,3 +87,4 @@ class CustomCircularRevealTransition extends CustomTransition { ); } } + diff --git a/lib/app/themes/theme_utils.dart b/lib/app/themes/theme_utils.dart index cd2ab890..bad656a1 100644 --- a/lib/app/themes/theme_utils.dart +++ b/lib/app/themes/theme_utils.dart @@ -108,7 +108,13 @@ ThemeData createTheme( ); return base.copyWith( - useMaterial3: true, + // pageTransitionsTheme: const PageTransitionsTheme( + // builders: { + // TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(), + // TargetPlatform.iOS: CupertinoPageTransitionsBuilder(), + // TargetPlatform.macOS: CupertinoPageTransitionsBuilder(), + // }, + // ), splashColor: colorScheme.secondary.withOpacity(0.1), scaffoldBackgroundColor: scaffoldBackgroundColor, appBarTheme: base.appBarTheme.copyWith( @@ -167,10 +173,10 @@ ThemeData createTheme( } Brightness getCurrentPlatformBrightness() => - MediaQueryData.fromWindow(WidgetsBinding.instance.window) - .platformBrightness; + MediaQueryData.fromView(WidgetsBinding.instance.window).platformBrightness; SystemUiOverlayStyle getUiOverlayStyleFor(ThemeData theme) => theme.brightness == Brightness.light ? SystemUiOverlayStyle.dark : SystemUiOverlayStyle.light; + diff --git a/lib/app/themes/themes.dart b/lib/app/themes/themes.dart index c875d319..187ce107 100644 --- a/lib/app/themes/themes.dart +++ b/lib/app/themes/themes.dart @@ -1,6 +1,6 @@ +import 'package:dungeon_paper/core/global_keys.dart'; import 'package:dynamic_themes/dynamic_themes.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'theme_utils.dart'; export 'theme_utils.dart'; @@ -175,7 +175,7 @@ class AppThemes { static ThemeData getTheme(int theme) => themeCollection[theme]; static String getThemeName(int theme) => _themeNames[theme]!; static void setTheme(int theme) { - final dynamicTheme = DynamicTheme.of(Get.context!)!; + final dynamicTheme = DynamicTheme.of(appGlobalKey.currentContext!)!; dynamicTheme.setTheme(theme); } diff --git a/lib/app/widgets/atoms/advanced_floating_action_button.dart b/lib/app/widgets/atoms/advanced_floating_action_button.dart index e0a675d1..ecdd3213 100644 --- a/lib/app/widgets/atoms/advanced_floating_action_button.dart +++ b/lib/app/widgets/atoms/advanced_floating_action_button.dart @@ -10,7 +10,7 @@ class AdvancedFloatingActionButton extends StatelessWidget { /// [elevation], [highlightElevation], and [disabledElevation] (if specified) /// must be non-negative. const AdvancedFloatingActionButton({ - Key? key, + super.key, this.child, this.tooltip, this.foregroundColor, @@ -49,8 +49,7 @@ class AdvancedFloatingActionButton extends StatelessWidget { _extendedLabel = null, extendedIconLabelSpacing = null, extendedPadding = null, - extendedTextStyle = null, - super(key: key); + extendedTextStyle = null; /// Creates a small circular floating action button. /// @@ -62,7 +61,7 @@ class AdvancedFloatingActionButton extends StatelessWidget { /// [highlightElevation], and [disabledElevation] (if specified) must be /// non-negative. const AdvancedFloatingActionButton.small({ - Key? key, + super.key, this.child, this.tooltip, this.foregroundColor, @@ -97,8 +96,7 @@ class AdvancedFloatingActionButton extends StatelessWidget { _extendedLabel = null, extendedIconLabelSpacing = null, extendedPadding = null, - extendedTextStyle = null, - super(key: key); + extendedTextStyle = null; /// Creates a large circular floating action button. /// @@ -110,7 +108,7 @@ class AdvancedFloatingActionButton extends StatelessWidget { /// [highlightElevation], and [disabledElevation] (if specified) must be /// non-negative. const AdvancedFloatingActionButton.large({ - Key? key, + super.key, this.child, this.tooltip, this.foregroundColor, @@ -145,8 +143,7 @@ class AdvancedFloatingActionButton extends StatelessWidget { _extendedLabel = null, extendedIconLabelSpacing = null, extendedPadding = null, - extendedTextStyle = null, - super(key: key); + extendedTextStyle = null; /// Creates a wider [StadiumBorder]-shaped floating action button with /// an optional [icon] and a [label]. @@ -155,7 +152,7 @@ class AdvancedFloatingActionButton extends StatelessWidget { /// Additionally, [elevation], [highlightElevation], and [disabledElevation] /// (if specified) must be non-negative. const AdvancedFloatingActionButton.extended({ - Key? key, + super.key, this.tooltip, this.foregroundColor, this.backgroundColor, @@ -193,8 +190,7 @@ class AdvancedFloatingActionButton extends StatelessWidget { mini = false, _floatingActionButtonType = _FloatingActionButtonType.extended, child = icon, - _extendedLabel = label, - super(key: key); + _extendedLabel = label; /// The widget below this widget in the tree. /// @@ -425,9 +421,9 @@ class AdvancedFloatingActionButton extends StatelessWidget { @override Widget build(BuildContext context) { - final _fgColor = foregroundColor ?? + final fgColor = foregroundColor ?? (backgroundColor == DwColors.success ? Colors.white : null); - final _bgColor = onPressed == null + final bgColor = onPressed == null ? Theme.of(context).disabledColor.withOpacity(0.3) // ? Theme.of(context).brightness == Brightness.light // ? Theme.of(context).disabledColor.withOpacity(0.3) @@ -438,10 +434,9 @@ class AdvancedFloatingActionButton extends StatelessWidget { case _FloatingActionButtonType.small: return FloatingActionButton.small( key: key, - child: child, tooltip: tooltip, - foregroundColor: _fgColor, - backgroundColor: _bgColor, + foregroundColor: fgColor, + backgroundColor: bgColor, focusColor: focusColor, hoverColor: hoverColor, splashColor: splashColor, @@ -459,14 +454,14 @@ class AdvancedFloatingActionButton extends StatelessWidget { autofocus: autofocus, materialTapTargetSize: materialTapTargetSize, enableFeedback: enableFeedback, + child: child, ); case _FloatingActionButtonType.large: return FloatingActionButton.large( key: key, - child: child, tooltip: tooltip, - foregroundColor: _fgColor, - backgroundColor: _bgColor, + foregroundColor: fgColor, + backgroundColor: bgColor, focusColor: focusColor, hoverColor: hoverColor, splashColor: splashColor, @@ -484,13 +479,14 @@ class AdvancedFloatingActionButton extends StatelessWidget { autofocus: autofocus, materialTapTargetSize: materialTapTargetSize, enableFeedback: enableFeedback, + child: child, ); case _FloatingActionButtonType.extended: return FloatingActionButton.extended( key: key, tooltip: tooltip, - foregroundColor: _fgColor, - backgroundColor: _bgColor, + foregroundColor: fgColor, + backgroundColor: bgColor, focusColor: focusColor, hoverColor: hoverColor, heroTag: heroTag, @@ -516,10 +512,9 @@ class AdvancedFloatingActionButton extends StatelessWidget { ); default: return FloatingActionButton( - child: child, tooltip: tooltip, - foregroundColor: _fgColor, - backgroundColor: _bgColor, + foregroundColor: fgColor, + backgroundColor: bgColor, focusColor: focusColor, hoverColor: hoverColor, splashColor: splashColor, @@ -539,6 +534,7 @@ class AdvancedFloatingActionButton extends StatelessWidget { autofocus: autofocus, materialTapTargetSize: materialTapTargetSize, enableFeedback: enableFeedback, + child: child, ); } } diff --git a/lib/app/widgets/atoms/avatar_circular_progress.dart b/lib/app/widgets/atoms/avatar_circular_progress.dart index cc70a890..1a5c818e 100644 --- a/lib/app/widgets/atoms/avatar_circular_progress.dart +++ b/lib/app/widgets/atoms/avatar_circular_progress.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; class AvatarCircularProgress extends StatelessWidget { - const AvatarCircularProgress({Key? key, required this.size}) - : super(key: key); + const AvatarCircularProgress({super.key, required this.size}); final double size; diff --git a/lib/app/widgets/atoms/background_icon_button.dart b/lib/app/widgets/atoms/background_icon_button.dart index 970fb358..a55a0123 100644 --- a/lib/app/widgets/atoms/background_icon_button.dart +++ b/lib/app/widgets/atoms/background_icon_button.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; class BackgroundIconButton extends StatelessWidget { const BackgroundIconButton({ - Key? key, + super.key, required this.icon, required this.onPressed, this.iconSize, @@ -27,7 +27,7 @@ class BackgroundIconButton extends StatelessWidget { this.decoration, this.shadows, this.elevation, - }) : super(key: key); + }); final Widget icon; diff --git a/lib/app/widgets/atoms/buffer_progress_bar.dart b/lib/app/widgets/atoms/buffer_progress_bar.dart index fc0c899d..ae6d516a 100644 --- a/lib/app/widgets/atoms/buffer_progress_bar.dart +++ b/lib/app/widgets/atoms/buffer_progress_bar.dart @@ -23,7 +23,7 @@ class BufferProgressBar extends StatelessWidget { @override Widget build(BuildContext context) { - final _duration = duration ?? const Duration(milliseconds: 250); + final duration = this.duration ?? const Duration(milliseconds: 250); return SizedBox( height: height, child: ClipRRect( @@ -32,7 +32,7 @@ class BufferProgressBar extends StatelessWidget { return Stack( children: [ AnimatedContainer( - duration: _duration, + duration: duration, height: height, width: constraints.maxWidth, color: backgroundColor ?? @@ -40,14 +40,14 @@ class BufferProgressBar extends StatelessWidget { ), if (bufferValue != null) AnimatedContainer( - duration: _duration, + duration: duration, height: height, width: constraints.maxWidth * clamp(bufferValue!, 0, 1), color: bufferColor ?? Theme.of(context).colorScheme.primary.withOpacity(0.3), ), AnimatedContainer( - duration: _duration, + duration: duration, height: height, width: constraints.maxWidth * value, color: color ?? Theme.of(context).primaryColor, diff --git a/lib/app/widgets/atoms/character_avatar.dart b/lib/app/widgets/atoms/character_avatar.dart index 3ebe024c..c8a381b6 100644 --- a/lib/app/widgets/atoms/character_avatar.dart +++ b/lib/app/widgets/atoms/character_avatar.dart @@ -1,23 +1,21 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:dungeon_paper/app/data/models/character.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; import 'package:dungeon_paper/app/widgets/atoms/avatar_circular_progress.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; - -class CharacterAvatar extends GetView { +class CharacterAvatar extends StatelessWidget { final Character? character; final Widget Function(BuildContext context, String avatarUrl, double size) builder; final double size; const CharacterAvatar._({ - Key? key, + super.key, this.character, required this.size, required this.builder, - }) : super(key: key); + }); factory CharacterAvatar({ Key? key, @@ -62,8 +60,7 @@ class CharacterAvatar extends GetView { if (character != null) { return _renderForChar(context, character); } - return Obx(() { - final ctrl = Get.find(); + return CharacterProvider.consumer((context, ctrl, _) { return _renderForChar(context, ctrl.maybeCurrent); }); } diff --git a/lib/app/widgets/atoms/confirm_exit_view.dart b/lib/app/widgets/atoms/confirm_exit_view.dart index 5b3286d0..bd3c49f7 100644 --- a/lib/app/widgets/atoms/confirm_exit_view.dart +++ b/lib/app/widgets/atoms/confirm_exit_view.dart @@ -1,7 +1,6 @@ import 'package:dungeon_paper/app/widgets/molecules/dialog_controls.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import '../../../core/utils/dialog_utils.dart'; @@ -28,19 +27,25 @@ class ConfirmExitView extends StatelessWidget { @override Widget build(BuildContext context) { - return WillPopScope( + final navigator = Navigator.of(context); + return PopScope( + canPop: !dirty, child: child, - onWillPop: () async { - if (!dirty) { - return true; + onPopInvoked: (didPop) async { + if (didPop) { + return; } - return confirmExit( + final res = await confirmExit( context, title: title, text: text, okLabel: okLabel, cancelLabel: cancelLabel, ); + + if (res) { + navigator.pop(); + } }, ); } @@ -53,7 +58,9 @@ Future confirmExit( String? okLabel, String? cancelLabel, }) { - return Get.dialog(const ConfirmExitDialog()).then((res) => res == true); + return showDialog( + context: context, + builder: (_) => const ConfirmExitDialog()).then((res) => res == true); } class ConfirmExitDialog extends StatelessWidget { @@ -78,9 +85,9 @@ class ConfirmExitDialog extends StatelessWidget { actions: DialogControls.confirmExit( context, exitLabel: okLabel, - onExit: () => Get.back(result: true), + onExit: () => Navigator.of(context).pop(true), continueLabel: cancelLabel, - onContinue: () => Get.back(result: false), + onContinue: () => Navigator.of(context).pop(false), ), ); } diff --git a/lib/app/widgets/atoms/custom_expansion_panel.dart b/lib/app/widgets/atoms/custom_expansion_panel.dart index 382d67db..6a922e8b 100644 --- a/lib/app/widgets/atoms/custom_expansion_panel.dart +++ b/lib/app/widgets/atoms/custom_expansion_panel.dart @@ -28,7 +28,7 @@ class CustomExpansionPanel extends StatelessWidget { static const defaultPadding = EdgeInsets.symmetric(horizontal: 8); const CustomExpansionPanel({ - Key? key, + super.key, this.expandable = true, this.expansionKey, this.title, @@ -50,7 +50,7 @@ class CustomExpansionPanel extends StatelessWidget { this.textColor, this.collapsedTextColor, this.reorderablePadding = false, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -64,7 +64,6 @@ class CustomExpansionPanel extends StatelessWidget { icon: icon, minIconWidth: minIconWidth, subtitle: subtitle, - children: children, tilePadding: titlePadding ?? defaultPadding, childrenPadding: childrenPadding ?? defaultPadding, expandedCrossAxisAlignment: CrossAxisAlignment.stretch, @@ -77,6 +76,7 @@ class CustomExpansionPanel extends StatelessWidget { textColor: textColor, collapsedTextColor: collapsedTextColor, reorderablePadding: reorderablePadding, + children: children, ); } } diff --git a/lib/app/widgets/atoms/custom_expansion_tile.dart b/lib/app/widgets/atoms/custom_expansion_tile.dart index 1568f60f..48620ed5 100644 --- a/lib/app/widgets/atoms/custom_expansion_tile.dart +++ b/lib/app/widgets/atoms/custom_expansion_tile.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'package:dungeon_paper/app/themes/themes.dart'; -import 'package:dungeon_paper/app/widgets/atoms/custom_list_tile.dart'; import 'package:dungeon_paper/core/platform_helper.dart'; import 'package:dungeon_paper/core/utils/math_utils.dart'; import 'package:flutter/material.dart'; @@ -45,7 +44,7 @@ class CustomExpansionTile extends StatefulWidget { /// the tile to reveal or hide the [children]. The [initiallyExpanded] property must /// be non-null. CustomExpansionTile({ - Key? key, + super.key, this.expandable = true, this.title, this.titleBuilder, @@ -83,11 +82,10 @@ class CustomExpansionTile extends StatefulWidget { ), assert(title != null || titleBuilder != null), itemBuilder = ((BuildContext context, int index) => children[index]), - itemCount = children.length, - super(key: key); + itemCount = children.length; const CustomExpansionTile.builder({ - Key? key, + super.key, this.expandable = true, this.title, this.titleBuilder, @@ -124,8 +122,7 @@ class CustomExpansionTile extends StatefulWidget { 'CrossAxisAlignment.baseline is not supported since the expanded children ' 'are aligned in a column, not a row. Try to use another constant.', ), - assert(title != null || titleBuilder != null), - super(key: key); + assert(title != null || titleBuilder != null); /// CUSTOM - is expansion even enabled? final bool expandable; diff --git a/lib/app/widgets/atoms/custom_snack_bar.dart b/lib/app/widgets/atoms/custom_snack_bar.dart new file mode 100644 index 00000000..f738c2c1 --- /dev/null +++ b/lib/app/widgets/atoms/custom_snack_bar.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; + +class CustomSnackBar extends SnackBar { + CustomSnackBar({ + super.key, + required BuildContext context, + this.title, + required String content, + }) : super(content: _getContent(context, title, content)); + + final String? title; + + static Widget _getContent( + BuildContext context, String? title, String content) { + if (title != null && title.isNotEmpty) { + return Column( + children: [ + Text(title, style: Theme.of(context).textTheme.bodyLarge), + Text(content), + ], + ); + } + return Text(content); + } + + static show(BuildContext context, {String? title, required String content}) => + ScaffoldMessenger.of(context).showSnackBar( + CustomSnackBar(context: context, title: title, content: content), + ); + + static deferred(BuildContext context) => DeferredCustomSnackBar._(context); +} + +class DeferredCustomSnackBar { + DeferredCustomSnackBar._(this.context); + BuildContext context; + + show({ + String? title, + required String content, + }) => + ScaffoldMessenger.of(context).showSnackBar( + CustomSnackBar(context: context, title: title, content: content), + ); +} diff --git a/lib/app/widgets/atoms/debug_dialog.dart b/lib/app/widgets/atoms/debug_dialog.dart index ec354db3..f62fda80 100644 --- a/lib/app/widgets/atoms/debug_dialog.dart +++ b/lib/app/widgets/atoms/debug_dialog.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; class DebugDialog extends StatefulWidget { final String text; - const DebugDialog({Key? key, required this.text}) : super(key: key); + const DebugDialog({super.key, required this.text}); @override State createState() => _DebugDialogState(); diff --git a/lib/app/widgets/atoms/debug_menu.dart b/lib/app/widgets/atoms/debug_menu.dart index 10dd8066..99459393 100644 --- a/lib/app/widgets/atoms/debug_menu.dart +++ b/lib/app/widgets/atoms/debug_menu.dart @@ -1,78 +1,79 @@ +import 'package:dungeon_paper/app/data/services/character_provider.dart'; import 'package:dungeon_paper/app/routes/app_pages.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; import 'package:dungeon_paper/app/widgets/atoms/debug_dialog.dart'; import 'package:dungeon_paper/app/widgets/atoms/menu_button.dart'; import 'package:dungeon_paper/core/storage_handler/storage_handler.dart'; import 'package:dynamic_themes/dynamic_themes.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import '../../themes/themes.dart'; -class DebugMenu extends StatelessWidget { - const DebugMenu({Key? key}) : super(key: key); +class DebugMenu extends StatelessWidget with CharacterProviderMixin { + const DebugMenu({super.key}); @override Widget build(BuildContext context) { return MenuButton( - child: const Padding( - padding: EdgeInsets.all(8), - child: Icon(Icons.bug_report), - ), items: [ MenuEntry( label: const Text('Toggle dark mode'), value: 'toggleDarkMode', - onSelect: _toggleTheme, + onSelect: () => _toggleTheme(context), ), MenuEntry( label: const Text('Clear all Char Data'), value: 'clearChars', - onSelect: _clearChars, + onSelect: () => _clearChars(context), ), MenuEntry( label: const Text('Open create char page'), value: 'createChar', - onSelect: _createChar, + onSelect: () => _createChar(context), ), MenuEntry( label: const Text('View JSON'), value: 'viewCharJson', - onSelect: _viewCharJson, + onSelect: () => _viewCharJson(context), ), ], + child: const Padding( + padding: EdgeInsets.all(8), + child: Icon(Icons.bug_report), + ), ); } - Map get actionMap => { + Map get actionMap => { 'toggleDarkMode': _toggleTheme, 'createChar': _createChar, 'clearChars': _clearChars, 'viewCharJson': _viewCharJson, }; - void _toggleTheme() { - var theme = DynamicTheme.of(Get.context!)!; + void _toggleTheme(BuildContext context) { + var theme = DynamicTheme.of(context)!; theme.setTheme( theme.themeId == AppThemes.dark ? AppThemes.parchment : AppThemes.dark); } - void _clearChars() async { - final CharacterService controller = Get.find(); - controller.clear(); + void _clearChars(BuildContext context) async { + charProvider.clear(); final all = await StorageHandler.instance.getCollection('Characters'); for (var c in all) { StorageHandler.instance.delete('Characters', c['key']); } } - void _createChar() { - Get.toNamed(Routes.createCharacter); + void _createChar(BuildContext context) { + Navigator.of(context).pushNamed(Routes.createCharacter); } - void _viewCharJson() { - final CharacterService controller = Get.find(); - - Get.dialog(DebugDialog(text: controller.current.toRawJson())); + void _viewCharJson(BuildContext context) { + showDialog( + context: context, + builder: (_) => DebugDialog( + text: charProvider.current.toRawJson(), + ), + ); } } diff --git a/lib/app/widgets/atoms/dice_icon.dart b/lib/app/widgets/atoms/dice_icon.dart index e06e8173..a2c63d5a 100644 --- a/lib/app/widgets/atoms/dice_icon.dart +++ b/lib/app/widgets/atoms/dice_icon.dart @@ -4,11 +4,11 @@ import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; class DiceIcon extends StatelessWidget { const DiceIcon({ - Key? key, + super.key, required this.sides, this.size, this.color, - }) : super(key: key); + }); final int sides; final double? size; @@ -16,11 +16,10 @@ class DiceIcon extends StatelessWidget { DiceIcon.from( dw.Dice dice, { - Key? key, + super.key, this.size, this.color, - }) : sides = dice.sides, - super(key: key); + }) : sides = dice.sides; @override Widget build(BuildContext context) { diff --git a/lib/app/widgets/atoms/help_text.dart b/lib/app/widgets/atoms/help_text.dart index 6ab9d1b2..c559ad36 100644 --- a/lib/app/widgets/atoms/help_text.dart +++ b/lib/app/widgets/atoms/help_text.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; class HelpText extends StatelessWidget { const HelpText({ - Key? key, + super.key, required this.text, this.icon, - }) : super(key: key); + }); final String text; final Widget? icon; @@ -20,6 +20,10 @@ class HelpText extends StatelessWidget { Transform.translate( offset: const Offset(0, 2), child: Container( + decoration: BoxDecoration( + color: textTheme.bodySmall!.color, + shape: BoxShape.circle, + ), child: IconTheme.merge( child: icon ?? const Icon(Icons.question_mark), data: IconThemeData( @@ -27,10 +31,6 @@ class HelpText extends StatelessWidget { color: theme.cardColor, ), ), - decoration: BoxDecoration( - color: textTheme.bodySmall!.color, - shape: BoxShape.circle, - ), ), ), const SizedBox(width: 8), diff --git a/lib/app/widgets/atoms/hp_bar.dart b/lib/app/widgets/atoms/hp_bar.dart index 5c63311b..9eddfe39 100644 --- a/lib/app/widgets/atoms/hp_bar.dart +++ b/lib/app/widgets/atoms/hp_bar.dart @@ -1,10 +1,9 @@ -import 'package:dungeon_paper/app/data/services/character_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; import 'package:dungeon_paper/app/widgets/atoms/buffer_progress_bar.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -class HpBar extends StatelessWidget with CharacterServiceMixin { +class HpBar extends StatelessWidget { const HpBar({ super.key, this.currentHp, @@ -16,8 +15,8 @@ class HpBar extends StatelessWidget with CharacterServiceMixin { @override Widget build(BuildContext context) { - return Obx(() { - final char = maybeChar; + return CharacterProvider.consumer((context, controller, _) { + final char = controller.maybeCurrent; final curValue = currentHp ?? char?.currentHp; final maxValue = maxHp ?? char?.maxHp; final curPercent = diff --git a/lib/app/widgets/atoms/labeled_divider.dart b/lib/app/widgets/atoms/labeled_divider.dart index 8757c4a1..be68ecfa 100644 --- a/lib/app/widgets/atoms/labeled_divider.dart +++ b/lib/app/widgets/atoms/labeled_divider.dart @@ -2,11 +2,11 @@ import 'package:flutter/material.dart'; class LabeledDivider extends StatelessWidget { const LabeledDivider({ - Key? key, + super.key, required this.label, this.height = 48, this.gap = 8, - }) : super(key: key); + }); final Widget label; final double height; diff --git a/lib/app/widgets/atoms/labeled_icon_button.dart b/lib/app/widgets/atoms/labeled_icon_button.dart index 9c163b85..e27104b3 100644 --- a/lib/app/widgets/atoms/labeled_icon_button.dart +++ b/lib/app/widgets/atoms/labeled_icon_button.dart @@ -3,12 +3,12 @@ import 'package:flutter/material.dart'; class LabeledIconButton extends StatelessWidget { const LabeledIconButton({ - Key? key, + super.key, required this.icon, required this.label, required this.onPressed, required this.shadowOffset, - }) : super(key: key); + }); final Widget icon; final void Function() onPressed; diff --git a/lib/app/widgets/atoms/lifecycle_builder.dart b/lib/app/widgets/atoms/lifecycle_builder.dart index d26d3dd2..bb6c37ab 100644 --- a/lib/app/widgets/atoms/lifecycle_builder.dart +++ b/lib/app/widgets/atoms/lifecycle_builder.dart @@ -7,21 +7,19 @@ class LifecycleView extends StatefulWidget { final Widget? child; LifecycleView({ - Key? key, + super.key, this.onInit, this.onDispose, required this.child, }) : assert(child != null), - builder = childBuilder(child!), - super(key: key); + builder = childBuilder(child!); const LifecycleView.builder({ - Key? key, + super.key, this.onInit, this.onDispose, required this.builder, - }) : child = null, - super(key: key); + }) : child = null; static Widget Function(BuildContext context) childBuilder(Widget child) => (_) => child; diff --git a/lib/app/widgets/atoms/menu_button.dart b/lib/app/widgets/atoms/menu_button.dart index 35fac15b..2bee93be 100644 --- a/lib/app/widgets/atoms/menu_button.dart +++ b/lib/app/widgets/atoms/menu_button.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; class MenuButton extends StatelessWidget { const MenuButton({ - Key? key, + super.key, required Iterable> items, this.initialValue, this.onCanceled, @@ -23,11 +23,10 @@ class MenuButton extends StatelessWidget { this.position = PopupMenuPosition.over, this.onSelected, }) : _items = items, - builder = null, - super(key: key); + builder = null; const MenuButton.builder({ - Key? key, + super.key, required this.builder, this.initialValue, this.onCanceled, @@ -47,8 +46,7 @@ class MenuButton extends StatelessWidget { this.position = PopupMenuPosition.over, this.onSelected, }) : _items = null, - assert(builder != null), - super(key: key); + assert(builder != null); final Iterable>? _items; final Iterable> Function()? builder; @@ -178,11 +176,11 @@ class MenuButton extends StatelessWidget { itemBuilder: (context) => items.map( (e) { if (e is MenuEntry) { - final MenuEntry _e = e as MenuEntry; + final MenuEntry entry = e as MenuEntry; return PopupMenuItem( - child: PopupMenuItemListTile(icon: _e.icon, label: _e.label), - value: _e.value, - enabled: !_e.disabled, + value: entry.value, + enabled: !entry.disabled, + child: PopupMenuItemListTile(icon: entry.icon, label: entry.label), ); } else { return e; @@ -205,7 +203,6 @@ class MenuButton extends StatelessWidget { tooltip: tooltip, elevation: elevation, padding: padding, - child: child, splashRadius: splashRadius, icon: icon, iconSize: iconSize, @@ -216,6 +213,7 @@ class MenuButton extends StatelessWidget { enableFeedback: enableFeedback, constraints: constraints, position: position, + child: child, ); } } diff --git a/lib/app/widgets/atoms/meta_sync_menu.dart b/lib/app/widgets/atoms/meta_sync_menu.dart index c509a2e7..a13c9a80 100644 --- a/lib/app/widgets/atoms/meta_sync_menu.dart +++ b/lib/app/widgets/atoms/meta_sync_menu.dart @@ -5,9 +5,9 @@ import 'package:flutter/material.dart'; @Deprecated('Use EntityShareForm') class MetaSyncMenu extends StatelessWidget { const MetaSyncMenu({ - Key? key, + super.key, required this.entity, - }) : super(key: key); + }); final WithMeta entity; diff --git a/lib/app/widgets/atoms/number_text_field.dart b/lib/app/widgets/atoms/number_text_field.dart index 6f47645b..929a3517 100644 --- a/lib/app/widgets/atoms/number_text_field.dart +++ b/lib/app/widgets/atoms/number_text_field.dart @@ -15,7 +15,7 @@ class NumberTextField extends StatelessWidget { final NumberType numberType; const NumberTextField({ - Key? key, + super.key, this.controller, this.initialValue, this.focusNode, @@ -74,8 +74,7 @@ class NumberTextField extends StatelessWidget { }) : keyboardType = keyboardType ?? (numberType == NumberType.double ? const TextInputType.numberWithOptions(decimal: true) - : const TextInputType.numberWithOptions(decimal: false)), - super(key: key); + : const TextInputType.numberWithOptions(decimal: false)); final String? initialValue; final FocusNode? focusNode; diff --git a/lib/app/widgets/atoms/page_controller_fractional_box.dart b/lib/app/widgets/atoms/page_controller_fractional_box.dart index ed45f328..923bf88f 100644 --- a/lib/app/widgets/atoms/page_controller_fractional_box.dart +++ b/lib/app/widgets/atoms/page_controller_fractional_box.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; class PageControllerFractionalBox extends StatelessWidget { const PageControllerFractionalBox({ - Key? key, + super.key, required this.controller, required this.child, - }) : super(key: key); + }); final PageController controller; final Widget child; diff --git a/lib/app/widgets/atoms/popover_builder.dart b/lib/app/widgets/atoms/popover_builder.dart index 99eb5a6d..cf50e266 100644 --- a/lib/app/widgets/atoms/popover_builder.dart +++ b/lib/app/widgets/atoms/popover_builder.dart @@ -1,14 +1,12 @@ import 'package:flutter/material.dart'; -import 'package:get/get.dart'; - -class PopoverBuilder extends GetView { +class PopoverBuilder extends StatelessWidget { const PopoverBuilder({ - Key? key, + super.key, required this.builder, required this.heroTag, this.padding = const EdgeInsets.all(32), - }) : super(key: key); + }); final Widget Function() builder; final String heroTag; @@ -18,7 +16,7 @@ class PopoverBuilder extends GetView { Widget build(BuildContext context) { return SafeArea( child: GestureDetector( - onTap: () => Get.back(), + onTap: () => Navigator.of(context).pop(), behavior: HitTestBehavior.opaque, child: Container( color: Colors.black.withOpacity(0.75), diff --git a/lib/app/widgets/atoms/popup_menu_item_list_tile.dart b/lib/app/widgets/atoms/popup_menu_item_list_tile.dart index bbbeacd1..106873d4 100644 --- a/lib/app/widgets/atoms/popup_menu_item_list_tile.dart +++ b/lib/app/widgets/atoms/popup_menu_item_list_tile.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; class PopupMenuItemListTile extends StatelessWidget { const PopupMenuItemListTile({ - Key? key, + super.key, required this.label, this.icon, - }) : super(key: key); + }); final Widget label; final Widget? icon; diff --git a/lib/app/widgets/atoms/rainbow_text.dart b/lib/app/widgets/atoms/rainbow_text.dart index 560e9221..ab861cd2 100644 --- a/lib/app/widgets/atoms/rainbow_text.dart +++ b/lib/app/widgets/atoms/rainbow_text.dart @@ -62,7 +62,7 @@ class RainbowText extends StatelessWidget { : pattern!; final split = text.split(splitPattern); final rainbow = ColorUtils.generateRainbow(split.length); - final _joiner = joiner ?? + final joiner = this.joiner ?? (splitMode == SplitMode.characters ? '' : splitMode == SplitMode.words @@ -73,7 +73,7 @@ class RainbowText extends StatelessWidget { children: [ for (final i in enumerate(split)) TextSpan( - text: i.value + _joiner, + text: i.value + joiner, style: textTheme.bodyMedium!.copyWith(color: rainbow[i.index]), ), ], @@ -82,8 +82,7 @@ class RainbowText extends StatelessWidget { textDirection: textDirection, softWrap: softWrap, overflow: overflow, - textScaleFactor: textScaleFactor, - maxLines: maxLines, + maxLines: maxLines, textScaler: TextScaler.linear(textScaleFactor), locale: locale, strutStyle: strutStyle, textWidthBasis: textWidthBasis, diff --git a/lib/app/widgets/atoms/rich_text_field.dart b/lib/app/widgets/atoms/rich_text_field.dart index ac308361..cd3081ab 100644 --- a/lib/app/widgets/atoms/rich_text_field.dart +++ b/lib/app/widgets/atoms/rich_text_field.dart @@ -5,7 +5,6 @@ import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:get/get.dart'; import 'package:url_launcher/url_launcher.dart'; class RichTextField extends StatelessWidget { @@ -124,9 +123,8 @@ class RichTextField extends StatelessWidget { final ScrollController? scrollController; final String? restorationId; final bool enableIMEPersonalizedLearning; - final _defaultController = TextEditingController().obs; - TextEditingController get _controller => - controller ?? _defaultController.value; + final _defaultController = TextEditingController(); + TextEditingController get _controller => controller ?? _defaultController; final List? customButtons; @override @@ -356,8 +354,9 @@ class RichTextField extends StatelessWidget { } void _openPreview(BuildContext context) { - Get.dialog( - MarkdownPreviewDialog(text: _controller.text), + showDialog( + context: context, + builder: (_) => MarkdownPreviewDialog(text: _controller.text), ); } } diff --git a/lib/app/widgets/atoms/round_icon_button.dart b/lib/app/widgets/atoms/round_icon_button.dart index e23a26b4..7cb1a376 100644 --- a/lib/app/widgets/atoms/round_icon_button.dart +++ b/lib/app/widgets/atoms/round_icon_button.dart @@ -2,14 +2,14 @@ import 'package:flutter/material.dart'; class RoundIconButton extends StatelessWidget { const RoundIconButton({ - Key? key, + super.key, required this.icon, required this.onPressed, this.backgroundColor, this.foregroundColor, this.size = 50, this.tooltip, - }) : super(key: key); + }); final Widget icon; final void Function()? onPressed; @@ -44,10 +44,6 @@ class RoundIconButton extends StatelessWidget { : Colors.white), ); return ElevatedButton( - child: IconTheme.merge( - child: icon, - data: IconThemeData(size: size / 2), - ), style: ElevatedButton.styleFrom( padding: EdgeInsets.zero, minimumSize: Size.square(size), @@ -55,6 +51,10 @@ class RoundIconButton extends StatelessWidget { foregroundColor: fgColor, ), onPressed: onPressed, + child: IconTheme.merge( + child: icon, + data: IconThemeData(size: size / 2), + ), ); } } diff --git a/lib/app/widgets/atoms/round_roll_button.dart b/lib/app/widgets/atoms/round_roll_button.dart index f8d6bbab..47b21a46 100644 --- a/lib/app/widgets/atoms/round_roll_button.dart +++ b/lib/app/widgets/atoms/round_roll_button.dart @@ -32,7 +32,7 @@ class RoundRollButton extends StatelessWidget { backgroundColor: abilityScores != null && isRollingWithDebility ? DwColors.error.withOpacity(0.5) : null, - onPressed: () => DiceUtils.openRollDialog(dice), + onPressed: () => DiceUtils.openRollDialog(context, dice), tooltip: isRollingWithDebility ? tr.customRolls.tooltip.rollWithDebility(diceStr) : tr.customRolls.tooltip.rollNormal(diceStr), diff --git a/lib/app/widgets/atoms/select_box.dart b/lib/app/widgets/atoms/select_box.dart index 14e154bc..e83359eb 100644 --- a/lib/app/widgets/atoms/select_box.dart +++ b/lib/app/widgets/atoms/select_box.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; class SelectBox extends StatelessWidget { const SelectBox({ - Key? key, + super.key, // this.label, // @@ -33,7 +33,7 @@ class SelectBox extends StatelessWidget { this.enableFeedback, this.alignment = AlignmentDirectional.centerStart, this.borderRadius, - }) : super(key: key); + }); // final Widget? label; diff --git a/lib/app/widgets/atoms/svg_icon.dart b/lib/app/widgets/atoms/svg_icon.dart index c75edfab..f0e81ec1 100644 --- a/lib/app/widgets/atoms/svg_icon.dart +++ b/lib/app/widgets/atoms/svg_icon.dart @@ -4,10 +4,10 @@ import 'package:flutter_svg/svg.dart'; class SvgIcon extends StatelessWidget { const SvgIcon( this.icon, { - Key? key, + super.key, this.color, this.size, - }) : super(key: key); + }); final IconData icon; final Color? color; diff --git a/lib/app/widgets/atoms/theme_brightness_switch.dart b/lib/app/widgets/atoms/theme_brightness_switch.dart index 40ef2bd5..b8bee15f 100644 --- a/lib/app/widgets/atoms/theme_brightness_switch.dart +++ b/lib/app/widgets/atoms/theme_brightness_switch.dart @@ -1,13 +1,13 @@ import 'package:dungeon_paper/app/data/models/character.dart'; import 'package:dungeon_paper/app/data/models/user.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; -import 'package:dungeon_paper/app/data/services/user_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; + +import '../../data/services/user_provider.dart'; class ThemeBrightnessSwitch extends StatelessWidget - with UserServiceMixin, CharacterServiceMixin { + with CharacterProviderMixin, UserProviderMixin { const ThemeBrightnessSwitch({ super.key, required this.builder, @@ -85,22 +85,28 @@ class ThemeBrightnessSwitch extends StatelessWidget BuildContext context, User user, Character? character) { final currentIsDark = user.brightness == Brightness.dark; final brightness = currentIsDark ? Brightness.light : Brightness.dark; - _updateThemeOnUser(user, character); + _updateThemeOnUser(context, user, character); return brightness; } - static void _updateThemeOnUser(User user, Character? character) async { + static void _updateThemeOnUser( + BuildContext context, + User user, + Character? character, + ) async { final currentIsDark = user.brightness == Brightness.dark; final brightness = currentIsDark ? Brightness.light : Brightness.dark; - final userService = Get.find(); - final charService = Get.find(); + final userProvider = UserProvider.of(context); + final charProvider = CharacterProvider.of(context); - await userService.updateUser(user.copyWith( - settings: user.settings.copyWith(brightnessOverride: brightness), - )); + await userProvider.updateUser( + user.copyWith( + settings: user.settings.copyWith(brightnessOverride: brightness), + ), + ); if (character != null) { - charService.switchToCharacterTheme(character); + charProvider.switchToCharacterTheme(character); } } } diff --git a/lib/app/widgets/atoms/user_avatar.dart b/lib/app/widgets/atoms/user_avatar.dart index 5f6ffd23..d2ec7781 100644 --- a/lib/app/widgets/atoms/user_avatar.dart +++ b/lib/app/widgets/atoms/user_avatar.dart @@ -4,10 +4,10 @@ import 'package:flutter/material.dart'; class UserAvatar extends StatelessWidget { const UserAvatar({ - Key? key, + super.key, required this.user, this.size, - }) : super(key: key); + }); final double? size; final User user; @@ -16,7 +16,6 @@ class UserAvatar extends StatelessWidget { Widget build(BuildContext context) { return CircleAvatar( radius: size != null ? size! / 2 : null, - child: const Icon(Icons.person), foregroundImage: user.photoUrl.isNotEmpty ? CachedNetworkImageProvider( user.photoUrl, @@ -25,6 +24,7 @@ class UserAvatar extends StatelessWidget { maxHeight: size?.toInt(), ) : null, + child: const Icon(Icons.person), ); } } diff --git a/lib/app/widgets/atoms/user_menu.dart b/lib/app/widgets/atoms/user_menu.dart index 17e82ef2..688397cf 100644 --- a/lib/app/widgets/atoms/user_menu.dart +++ b/lib/app/widgets/atoms/user_menu.dart @@ -1,11 +1,12 @@ -import 'package:dungeon_paper/app/data/services/user_service.dart'; import 'package:dungeon_paper/app/routes/app_pages.dart'; import 'package:dungeon_paper/app/widgets/atoms/user_avatar.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; -class UserMenu extends StatelessWidget with UserServiceMixin { - const UserMenu({Key? key}) : super(key: key); +import '../../data/services/user_provider.dart'; + +class UserMenu extends StatelessWidget { + const UserMenu({super.key}); @override Widget build(BuildContext context) { @@ -22,10 +23,13 @@ class UserMenu extends StatelessWidget with UserServiceMixin { clipBehavior: Clip.antiAlias, child: InkWell( splashColor: Theme.of(context).splashColor, - onTap: () => Get.toNamed(Routes.userMenu), + onTap: () => Navigator.of(context).pushNamed(Routes.userMenu), child: Padding( padding: const EdgeInsets.all(8), - child: Obx(() => UserAvatar(user: user)), + child: Consumer( + builder: (context, controller, _) => + UserAvatar(user: controller.current), + ), ), ), ); diff --git a/lib/app/widgets/atoms/value_notifier_builder.dart b/lib/app/widgets/atoms/value_notifier_builder.dart index 33072b43..480301d7 100644 --- a/lib/app/widgets/atoms/value_notifier_builder.dart +++ b/lib/app/widgets/atoms/value_notifier_builder.dart @@ -2,10 +2,10 @@ import 'package:flutter/widgets.dart'; class ValueNotifierBuilder extends StatefulWidget { const ValueNotifierBuilder({ - Key? key, + super.key, required this.value, required this.builder, - }) : super(key: key); + }); final ValueNotifier value; final Widget Function(BuildContext context, dynamic value) builder; diff --git a/lib/app/widgets/atoms/xp_bar.dart b/lib/app/widgets/atoms/xp_bar.dart index 7183dab1..c6287d03 100644 --- a/lib/app/widgets/atoms/xp_bar.dart +++ b/lib/app/widgets/atoms/xp_bar.dart @@ -1,10 +1,9 @@ -import 'package:dungeon_paper/app/data/services/character_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; import 'package:dungeon_paper/app/widgets/atoms/buffer_progress_bar.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -class ExpBar extends StatelessWidget with CharacterServiceMixin { +class ExpBar extends StatelessWidget { const ExpBar({ super.key, this.currentXp, @@ -18,9 +17,9 @@ class ExpBar extends StatelessWidget with CharacterServiceMixin { @override Widget build(BuildContext context) { - return Obx( - () { - final char = maybeChar; + return CharacterProvider.consumer( + (context, controller, _) { + final char = controller.maybeCurrent; final maxValue = maxXp ?? char?.maxXp; final curValue = currentXp ?? char?.currentXp ?? 0; diff --git a/lib/app/widgets/cards/character_class_card.dart b/lib/app/widgets/cards/character_class_card.dart index 7c68378d..9fb95983 100644 --- a/lib/app/widgets/cards/character_class_card.dart +++ b/lib/app/widgets/cards/character_class_card.dart @@ -68,4 +68,3 @@ class CharacterClassCard extends StatelessWidget { return '${characterClass.description}\n\n$table'; } } - diff --git a/lib/app/widgets/cards/item_card.dart b/lib/app/widgets/cards/item_card.dart index 9ec6fc0d..1e6fb6cd 100644 --- a/lib/app/widgets/cards/item_card.dart +++ b/lib/app/widgets/cards/item_card.dart @@ -72,7 +72,7 @@ class ItemCard extends StatelessWidget { ...chips, ...trailing, ].joinObjects(const SizedBox(width: 8)), - chips: item.tags.map((t) => TagChip.openDescription(tag: t)), + chips: item.tags.map((t) => TagChip.openDescription(context, tag: t)), dice: const [], icon: showIcon ? Icon(item.icon, size: 16) : null, starred: item.equipped, @@ -85,4 +85,3 @@ class ItemCard extends StatelessWidget { ); } } - diff --git a/lib/app/widgets/cards/item_card_mini.dart b/lib/app/widgets/cards/item_card_mini.dart index 9495fa32..7e36d313 100644 --- a/lib/app/widgets/cards/item_card_mini.dart +++ b/lib/app/widgets/cards/item_card_mini.dart @@ -9,13 +9,13 @@ import 'dynamic_action_card_mini.dart'; class ItemCardMini extends StatelessWidget { const ItemCardMini({ - Key? key, + super.key, required this.item, this.showStar = true, this.showIcon = true, this.onSave, this.onTap, - }) : super(key: key); + }); final Item item; final bool showStar; diff --git a/lib/app/widgets/cards/move_card.dart b/lib/app/widgets/cards/move_card.dart index 3fb43172..90992a50 100644 --- a/lib/app/widgets/cards/move_card.dart +++ b/lib/app/widgets/cards/move_card.dart @@ -57,7 +57,7 @@ class MoveCard extends StatelessWidget { expandable: expandable, reorderablePadding: reorderablePadding, expansionKey: expansionKey ?? PageStorageKey(move.key), - chips: move.tags.map((t) => TagChip.openDescription(tag: t)), + chips: move.tags.map((t) => TagChip.openDescription(context, tag: t)), dice: showDice ? move.dice : [], icon: showIcon ? Icon(move.icon, size: 16) : null, starred: move.favorite, @@ -79,4 +79,3 @@ class MoveCard extends StatelessWidget { ); } } - diff --git a/lib/app/widgets/cards/move_card_mini.dart b/lib/app/widgets/cards/move_card_mini.dart index a2b1c362..ba51fccd 100644 --- a/lib/app/widgets/cards/move_card_mini.dart +++ b/lib/app/widgets/cards/move_card_mini.dart @@ -7,7 +7,7 @@ import 'dynamic_action_card_mini.dart'; class MoveCardMini extends StatelessWidget { const MoveCardMini({ - Key? key, + super.key, required this.move, this.onSave, this.showStar = true, @@ -15,7 +15,7 @@ class MoveCardMini extends StatelessWidget { this.onTap, this.advancedLevelDisplay = AdvancedLevelDisplay.short, this.abilityScores, - }) : super(key: key); + }); final Move move; final bool showStar; diff --git a/lib/app/widgets/cards/note_card.dart b/lib/app/widgets/cards/note_card.dart index 90a8b627..5b06fe0b 100644 --- a/lib/app/widgets/cards/note_card.dart +++ b/lib/app/widgets/cards/note_card.dart @@ -6,7 +6,7 @@ import 'dynamic_action_card.dart'; class NoteCard extends StatelessWidget { const NoteCard({ - Key? key, + super.key, required this.note, this.showStar = true, this.showIcon = true, @@ -18,7 +18,7 @@ class NoteCard extends StatelessWidget { this.expandable = true, this.highlightWords = const [], this.reorderablePadding = false, - }) : super(key: key); + }); final Note note; final bool showStar; @@ -42,7 +42,7 @@ class NoteCard extends StatelessWidget { maxContentHeight: maxContentHeight, expandable: expandable, reorderablePadding: reorderablePadding, - chips: note.tags.map((t) => TagChip.openDescription(tag: t)), + chips: note.tags.map((t) => TagChip.openDescription(context, tag: t)), dice: const [], icon: showIcon ? Icon(note.icon, size: 16) : null, starred: note.favorite, diff --git a/lib/app/widgets/cards/note_card_mini.dart b/lib/app/widgets/cards/note_card_mini.dart index 4a14178b..6db8e7ac 100644 --- a/lib/app/widgets/cards/note_card_mini.dart +++ b/lib/app/widgets/cards/note_card_mini.dart @@ -5,13 +5,13 @@ import 'dynamic_action_card_mini.dart'; class NoteCardMini extends StatelessWidget { const NoteCardMini({ - Key? key, + super.key, required this.note, this.showStar = true, this.showIcon = true, this.onSave, this.onTap, - }) : super(key: key); + }); final Note note; final bool showStar; diff --git a/lib/app/widgets/cards/race_card.dart b/lib/app/widgets/cards/race_card.dart index efc34e53..4564ea5d 100644 --- a/lib/app/widgets/cards/race_card.dart +++ b/lib/app/widgets/cards/race_card.dart @@ -50,7 +50,7 @@ class RaceCard extends StatelessWidget { maxContentHeight: maxContentHeight, expandable: expandable, expansionKey: expansionKey ?? PageStorageKey(race.key), - chips: race.tags.map((t) => TagChip.openDescription(tag: t)), + chips: race.tags.map((t) => TagChip.openDescription(context, tag: t)), dice: const [], icon: showIcon ? Icon(race.icon, size: 16) : null, starred: race.favorite, @@ -72,4 +72,3 @@ class RaceCard extends StatelessWidget { ); } } - diff --git a/lib/app/widgets/cards/race_card_mini.dart b/lib/app/widgets/cards/race_card_mini.dart index 814d6e96..ed620c21 100644 --- a/lib/app/widgets/cards/race_card_mini.dart +++ b/lib/app/widgets/cards/race_card_mini.dart @@ -5,13 +5,13 @@ import 'dynamic_action_card_mini.dart'; class RaceCardMini extends StatelessWidget { const RaceCardMini({ - Key? key, + super.key, required this.race, this.onSave, this.showStar = true, this.showIcon = true, this.onTap, - }) : super(key: key); + }); final Race race; final bool showStar; diff --git a/lib/app/widgets/cards/spell_card.dart b/lib/app/widgets/cards/spell_card.dart index ae5faabd..7ad5f37c 100644 --- a/lib/app/widgets/cards/spell_card.dart +++ b/lib/app/widgets/cards/spell_card.dart @@ -78,4 +78,3 @@ class SpellCard extends StatelessWidget { ); } } - diff --git a/lib/app/widgets/cards/spell_card_mini.dart b/lib/app/widgets/cards/spell_card_mini.dart index 323d407f..2424cc6e 100644 --- a/lib/app/widgets/cards/spell_card_mini.dart +++ b/lib/app/widgets/cards/spell_card_mini.dart @@ -7,7 +7,7 @@ import 'dynamic_action_card_mini.dart'; class SpellCardMini extends StatelessWidget { const SpellCardMini({ - Key? key, + super.key, required this.spell, this.showDice = true, this.showStar = true, @@ -15,7 +15,7 @@ class SpellCardMini extends StatelessWidget { this.onSave, this.onTap, required this.abilityScores, - }) : super(key: key); + }); final Spell spell; final bool showDice; diff --git a/lib/app/widgets/chips/ability_score_chip.dart b/lib/app/widgets/chips/ability_score_chip.dart index f8c4454c..354e937d 100644 --- a/lib/app/widgets/chips/ability_score_chip.dart +++ b/lib/app/widgets/chips/ability_score_chip.dart @@ -10,8 +10,7 @@ class AbilityScoreChip extends StatelessWidget { final AbilityScore stat; final bool showDice; - const AbilityScoreChip({Key? key, required this.stat, this.showDice = true}) - : super(key: key); + const AbilityScoreChip({super.key, required this.stat, this.showDice = true}); @override Widget build(BuildContext context) { @@ -36,7 +35,7 @@ class AbilityScoreChip extends StatelessWidget { child: InkWell( splashColor: Theme.of(context).splashColor, onTap: () => DiceUtils.openRollDialog( - [dw.Dice(amount: 2, sides: 6, modifierStat: stat.key)]), + context, [dw.Dice(amount: 2, sides: 6, modifierStat: stat.key)]), borderRadius: borderRadius, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), @@ -48,16 +47,16 @@ class AbilityScoreChip extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ IconTheme( - child: Icon(stat.icon), data: IconThemeData( size: 18, color: theme.colorScheme.onSurface), + child: Icon(stat.icon), ), const SizedBox(width: 4), SizedBox( width: 32, child: Text( valStr, - textScaleFactor: 1.5, + textScaler: const TextScaler.linear(1.5), style: const TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center, ), @@ -74,12 +73,12 @@ class AbilityScoreChip extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ IconTheme( - child: const Icon(DwIcons.dice_d6), data: IconTheme.of(context).copyWith( size: 12, color: theme.colorScheme.onSurface .withOpacity(rollBadgeModifierOpacity), ), + child: const Icon(DwIcons.dice_d6), ), const SizedBox(width: 2), Text( @@ -103,3 +102,4 @@ class AbilityScoreChip extends StatelessWidget { ); } } + diff --git a/lib/app/widgets/chips/advanced_chip.dart b/lib/app/widgets/chips/advanced_chip.dart index bea9d5fe..8f733502 100644 --- a/lib/app/widgets/chips/advanced_chip.dart +++ b/lib/app/widgets/chips/advanced_chip.dart @@ -9,7 +9,7 @@ class AdvancedChip extends StatelessWidget DisabledChipAttributes, TappableChipAttributes { const AdvancedChip({ - Key? key, + super.key, required this.label, this.avatar, this.labelStyle, @@ -43,7 +43,7 @@ class AdvancedChip extends StatelessWidget this.avatarBorder = const CircleBorder(), this.iconTheme, this.surfaceTintColor, - }) : super(key: key); + }); @override final Widget? avatar; diff --git a/lib/app/widgets/chips/dice_chip.dart b/lib/app/widgets/chips/dice_chip.dart index 3ffef3b8..3adbf7ed 100644 --- a/lib/app/widgets/chips/dice_chip.dart +++ b/lib/app/widgets/chips/dice_chip.dart @@ -6,7 +6,7 @@ import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; class DiceChip extends StatelessWidget { const DiceChip({ - Key? key, + super.key, required this.dice, this.onPressed, this.onDeleted, @@ -14,7 +14,7 @@ class DiceChip extends StatelessWidget { this.backgroundColor, this.visualDensity, this.label, - }) : super(key: key); + }); final dw.Dice dice; final void Function()? onPressed; diff --git a/lib/app/widgets/chips/item_armor_chip.dart b/lib/app/widgets/chips/item_armor_chip.dart index c79f5499..9e18039a 100644 --- a/lib/app/widgets/chips/item_armor_chip.dart +++ b/lib/app/widgets/chips/item_armor_chip.dart @@ -7,10 +7,10 @@ import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; class ItemArmorChip extends StatelessWidget { const ItemArmorChip({ - Key? key, + super.key, required this.item, this.visualDensity, - }) : super(key: key); + }); final Item item; final VisualDensity? visualDensity; diff --git a/lib/app/widgets/chips/item_damage_chip.dart b/lib/app/widgets/chips/item_damage_chip.dart index e3720d7c..f8dd038f 100644 --- a/lib/app/widgets/chips/item_damage_chip.dart +++ b/lib/app/widgets/chips/item_damage_chip.dart @@ -7,10 +7,10 @@ import 'package:intl/intl.dart'; class ItemDamageChip extends StatelessWidget { const ItemDamageChip({ - Key? key, + super.key, required this.item, this.visualDensity, - }) : super(key: key); + }); final Item item; final VisualDensity? visualDensity; diff --git a/lib/app/widgets/chips/item_weight_chip.dart b/lib/app/widgets/chips/item_weight_chip.dart index 8b9aa9f2..e548de78 100644 --- a/lib/app/widgets/chips/item_weight_chip.dart +++ b/lib/app/widgets/chips/item_weight_chip.dart @@ -7,10 +7,10 @@ import 'package:intl/intl.dart'; class ItemWeightChip extends StatelessWidget { const ItemWeightChip({ - Key? key, + super.key, required this.item, this.visualDensity, - }) : super(key: key); + }); final Item item; final VisualDensity? visualDensity; diff --git a/lib/app/widgets/chips/primary_chip.dart b/lib/app/widgets/chips/primary_chip.dart index 6de40483..ae61403c 100644 --- a/lib/app/widgets/chips/primary_chip.dart +++ b/lib/app/widgets/chips/primary_chip.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; class PrimaryChip extends StatelessWidget { const PrimaryChip({ - Key? key, + super.key, required this.label, this.onPressed, this.onDeleted, @@ -13,7 +13,7 @@ class PrimaryChip extends StatelessWidget { this.backgroundColor, this.tooltip, this.deleteButtonTooltip, - }) : super(key: key); + }); final String label; final void Function()? onPressed; diff --git a/lib/app/widgets/chips/tag_chip.dart b/lib/app/widgets/chips/tag_chip.dart index 0825a980..034f356a 100644 --- a/lib/app/widgets/chips/tag_chip.dart +++ b/lib/app/widgets/chips/tag_chip.dart @@ -4,20 +4,20 @@ import 'package:dungeon_paper/app/widgets/dialogs/view_tag_dialog.dart'; import 'package:flutter/material.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; -import 'package:get/get.dart'; class TagChip extends StatelessWidget { const TagChip({ - Key? key, + super.key, required this.tag, this.onPressed, this.onDeleted, this.icon, this.backgroundColor, this.visualDensity, - }) : super(key: key); + }); - factory TagChip.openDescription({ + factory TagChip.openDescription( + BuildContext context, { Key? key, required dw.Tag tag, void Function()? onPressed, @@ -32,8 +32,9 @@ class TagChip extends StatelessWidget { onDeleted: onDeleted, icon: icon, backgroundColor: backgroundColor, - onPressed: () => Get.dialog( - ViewTagDialog( + onPressed: () => showDialog( + context: context, + builder: (_) => ViewTagDialog( tag: tag, ), ), diff --git a/lib/app/widgets/dialogs/add_dice_dialog.dart b/lib/app/widgets/dialogs/add_dice_dialog.dart index 129b1d63..6922d491 100644 --- a/lib/app/widgets/dialogs/add_dice_dialog.dart +++ b/lib/app/widgets/dialogs/add_dice_dialog.dart @@ -1,10 +1,9 @@ import 'package:dungeon_paper/app/data/models/ability_scores.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; +import 'package:dungeon_paper/app/data/services/repository_provider.dart'; import 'package:dungeon_paper/app/widgets/forms/dice_form.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; enum ModifierType { stat, fixed } @@ -25,7 +24,7 @@ class AddDiceDialog extends StatefulWidget { } class _AddDiceDialogState extends State - with RepositoryServiceMixin { + with RepositoryProviderMixin { late dw.Dice dice; @override @@ -53,7 +52,7 @@ class _AddDiceDialogState extends State ElevatedButton( onPressed: () { widget.onSave?.call(dice); - Get.back(); + Navigator.of(context).pop(); }, child: Text(tr.generic.save), ), diff --git a/lib/app/widgets/dialogs/add_tag_dialog.dart b/lib/app/widgets/dialogs/add_tag_dialog.dart index 36d7fa00..f5a81870 100644 --- a/lib/app/widgets/dialogs/add_tag_dialog.dart +++ b/lib/app/widgets/dialogs/add_tag_dialog.dart @@ -1,10 +1,10 @@ -import 'package:dungeon_paper/app/data/services/repository_service.dart'; +import 'package:dungeon_paper/app/data/services/repository_provider.dart'; import 'package:dungeon_paper/app/widgets/atoms/menu_button.dart'; +import 'package:dungeon_paper/core/global_keys.dart'; import 'package:dungeon_paper/core/utils/string_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; class AddTagDialog extends StatefulWidget { const AddTagDialog({ @@ -21,7 +21,6 @@ class AddTagDialog extends StatefulWidget { } class _AddTagDialogState extends State { - final RepositoryService repo = Get.find(); late final TextEditingController name; late final TextEditingController desc; late final TextEditingController value; @@ -104,7 +103,7 @@ class _AddTagDialogState extends State { ElevatedButton( onPressed: () { widget.onSave?.call(createTag()); - Get.back(); + Navigator.of(context).pop(); }, child: Text(tr.generic.save), ), @@ -118,9 +117,11 @@ class _AddTagDialogState extends State { value: tryParse(value.text), ); - List get allTags => - {...repo.my.tags.values, ...repo.builtIn.tags.values}.toList() - ..sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase())); + List get allTags { + final repo = RepositoryProvider.of(appGlobalKey.currentContext!); + return {...repo.my.tags.values, ...repo.builtIn.tags.values}.toList() + ..sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase())); + } dynamic tryParse(String text) { if (RegExp(r'[a-z]').hasMatch(text)) { diff --git a/lib/app/widgets/dialogs/armor_dialog.dart b/lib/app/widgets/dialogs/armor_dialog.dart index 3781cad7..fb60100b 100644 --- a/lib/app/widgets/dialogs/armor_dialog.dart +++ b/lib/app/widgets/dialogs/armor_dialog.dart @@ -3,7 +3,6 @@ import 'package:dungeon_paper/app/widgets/molecules/dialog_controls.dart'; import 'package:dungeon_paper/core/dw_icons.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; class ArmorDialog extends StatefulWidget { const ArmorDialog({ @@ -84,11 +83,11 @@ class _ArmorDialogState extends State { void save() { widget.onChanged(useDefault ? null : int.parse(controller.text)); - Get.back(); + Navigator.of(context).pop(); } void cancel() { - Get.back(); + Navigator.of(context).pop(); } void _listener() { diff --git a/lib/app/widgets/dialogs/character_bio_dialog.dart b/lib/app/widgets/dialogs/character_bio_dialog.dart index c8a02d8b..56f8167d 100644 --- a/lib/app/widgets/dialogs/character_bio_dialog.dart +++ b/lib/app/widgets/dialogs/character_bio_dialog.dart @@ -1,4 +1,4 @@ -import 'package:dungeon_paper/app/data/services/character_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; import 'package:dungeon_paper/app/modules/BioForm/controllers/bio_form_controller.dart'; import 'package:dungeon_paper/app/routes/app_pages.dart'; import 'package:dungeon_paper/app/widgets/molecules/dialog_controls.dart'; @@ -6,10 +6,9 @@ import 'package:dungeon_paper/core/utils/markdown_styles.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:get/get.dart'; import 'package:url_launcher/url_launcher.dart'; -class CharacterBioDialog extends GetView with CharacterServiceMixin { +class CharacterBioDialog extends StatelessWidget with CharacterProviderMixin { const CharacterBioDialog({super.key}); @override @@ -24,7 +23,7 @@ class CharacterBioDialog extends GetView with CharacterServiceMixin { Expanded(child: Text(tr.bio.dialog.title)), IconButton( onPressed: () { - Get.toNamed(Routes.bio, + Navigator.of(context).pushNamed(Routes.bio, arguments: BioFormArguments(character: character)); }, icon: const Icon(Icons.edit, size: 20), @@ -32,78 +31,81 @@ class CharacterBioDialog extends GetView with CharacterServiceMixin { ], ), contentPadding: const EdgeInsets.all(16), - actions: DialogControls.done(context, () => Get.back()), - content: Obx( - () => SizedBox( - width: 500, - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - // shrinkWrap: true, - children: [ - Text(tr.bio.dialog.description.label, - style: textTheme.bodySmall), - char.bio.description.isNotEmpty - ? MarkdownBody( - data: char.bio.description, - onTapLink: (_, url, __) => launchUrl(Uri.parse(url!)), - styleSheet: MarkdownStyles.of(context), - ) - : Text(tr.generic.noDescription), - const SizedBox(height: 16), - Text(tr.bio.dialog.looks.label, style: textTheme.bodySmall), - char.bio.looks.isNotEmpty - ? Text(char.bio.looks, style: textTheme.bodyLarge) - // TODO broken...?! - // ? ConstrainedBox( - // constraints: BoxConstraints.loose(Size.fromHeight(maxContentHeight)), - // ? SizedBox( - // height: 120, - // width: MediaQuery.of(context).size.width - 100, - // // ? IntrinsicWidth( - // // child: IntrinsicHeight( - // child: - // ? MarkdownBody( - // shrinkWrap: true, - // // fitContent: true, - // data: char.bio.looks, - // onTapLink: (_, url, __) => launch(url!), - // ) - // , - // ) - // ) - : Text(tr.generic.noDescription), - const SizedBox(height: 16), - Row( - children: [ - Text( - '${tr.bio.dialog.alignment.label}: ', - style: textTheme.bodySmall, - ), - const SizedBox(width: 4), - IconTheme.merge( - data: IconThemeData( - size: 14, color: textTheme.bodySmall!.color!), - child: Icon(char.bio.alignment.icon), - ), - const SizedBox(width: 4), - Text( - tr.alignment.name(char.bio.alignment.key), - style: textTheme.bodySmall, - ), - ], - ), - char.bio.alignment.description.isNotEmpty - ? MarkdownBody( - data: char.bio.alignment.description, - onTapLink: (_, url, __) => launchUrl(Uri.parse(url!)), - ) - : Text(tr.generic.noDescription), - ], + actions: DialogControls.done(context, () => Navigator.of(context).pop()), + content: CharacterProvider.consumer( + (context, charProvider, _) { + final char = charProvider.current; + return SizedBox( + width: 500, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + // shrinkWrap: true, + children: [ + Text(tr.bio.dialog.description.label, + style: textTheme.bodySmall), + char.bio.description.isNotEmpty + ? MarkdownBody( + data: char.bio.description, + onTapLink: (_, url, __) => launchUrl(Uri.parse(url!)), + styleSheet: MarkdownStyles.of(context), + ) + : Text(tr.generic.noDescription), + const SizedBox(height: 16), + Text(tr.bio.dialog.looks.label, style: textTheme.bodySmall), + char.bio.looks.isNotEmpty + ? Text(char.bio.looks, style: textTheme.bodyLarge) + // TODO broken...?! + // ? ConstrainedBox( + // constraints: BoxConstraints.loose(Size.fromHeight(maxContentHeight)), + // ? SizedBox( + // height: 120, + // width: MediaQuery.of(context).size.width - 100, + // // ? IntrinsicWidth( + // // child: IntrinsicHeight( + // child: + // ? MarkdownBody( + // shrinkWrap: true, + // // fitContent: true, + // data: char.bio.looks, + // onTapLink: (_, url, __) => launch(url!), + // ) + // , + // ) + // ) + : Text(tr.generic.noDescription), + const SizedBox(height: 16), + Row( + children: [ + Text( + '${tr.bio.dialog.alignment.label}: ', + style: textTheme.bodySmall, + ), + const SizedBox(width: 4), + IconTheme.merge( + data: IconThemeData( + size: 14, color: textTheme.bodySmall!.color!), + child: Icon(char.bio.alignment.icon), + ), + const SizedBox(width: 4), + Text( + tr.alignment.name(char.bio.alignment.key), + style: textTheme.bodySmall, + ), + ], + ), + char.bio.alignment.description.isNotEmpty + ? MarkdownBody( + data: char.bio.alignment.description, + onTapLink: (_, url, __) => launchUrl(Uri.parse(url!)), + ) + : Text(tr.generic.noDescription), + ], + ), ), - ), - ), + ); + }, ), ); } diff --git a/lib/app/widgets/dialogs/character_bonds_flags_dialog.dart b/lib/app/widgets/dialogs/character_bonds_flags_dialog.dart index 3cddf9c7..b2c1ad44 100644 --- a/lib/app/widgets/dialogs/character_bonds_flags_dialog.dart +++ b/lib/app/widgets/dialogs/character_bonds_flags_dialog.dart @@ -1,5 +1,5 @@ import 'package:dungeon_paper/app/data/models/session_marks.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; import 'package:dungeon_paper/app/modules/BondsFlagsForm/controllers/bonds_flags_form_controller.dart'; import 'package:dungeon_paper/app/routes/app_pages.dart'; import 'package:dungeon_paper/app/widgets/atoms/help_text.dart'; @@ -7,14 +7,11 @@ import 'package:dungeon_paper/app/widgets/molecules/dialog_controls.dart'; import 'package:dungeon_paper/core/utils/list_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -class CharacterBondsFlagsDialog extends GetView - with CharacterServiceMixin { +class CharacterBondsFlagsDialog extends StatelessWidget + with CharacterProviderMixin { const CharacterBondsFlagsDialog({super.key}); - List get bonds => char.bonds; - List get flags => char.flags; List get sessionMarks => char.sessionMarks; @override @@ -27,17 +24,23 @@ class CharacterBondsFlagsDialog extends GetView mainAxisSize: MainAxisSize.max, children: [ Expanded( - child: Obx(() => - Text(SessionMark.categoryTitle(bonds: bonds, flags: flags))), + child: CharacterProvider.consumer( + (context, charProvider, _) => Text( + SessionMark.categoryTitle( + bonds: charProvider.current.bonds, + flags: charProvider.current.flags, + ), + ), + ), ), IconButton( onPressed: () { - Get.toNamed( + Navigator.of(context).pushNamed( Routes.bondsFlags, arguments: BondsFlagsFormArguments( - bonds: bonds, - flags: flags, - onChanged: (bonds, flags) => controller.updateCharacter( + bonds: charProvider.current.bonds, + flags: charProvider.current.flags, + onChanged: (bonds, flags) => charProvider.updateCharacter( character.copyWithSessionMarks( bonds: bonds, flags: flags, @@ -51,69 +54,72 @@ class CharacterBondsFlagsDialog extends GetView ], ), contentPadding: const EdgeInsets.all(16), - actions: DialogControls.done(context, () => Get.back()), - content: Obx( - () => SingleChildScrollView( - child: ListTileTheme.merge( - minLeadingWidth: 20, - contentPadding: const EdgeInsets.all(0), - minVerticalPadding: 8, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (bonds.isEmpty && flags.isEmpty) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: HelpText( - text: tr.sessionMarks.noData, + actions: DialogControls.done(context, () => Navigator.of(context).pop()), + content: CharacterProvider.consumer( + (context, charProvider, _) { + final char = charProvider.current; + return SingleChildScrollView( + child: ListTileTheme.merge( + minLeadingWidth: 20, + contentPadding: const EdgeInsets.all(0), + minVerticalPadding: 8, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (char.bonds.isEmpty && char.flags.isEmpty) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: HelpText( + text: tr.sessionMarks.noData, + ), ), - ), - if (bonds.isNotEmpty || flags.isNotEmpty) ...[ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: HelpText( - text: tr.sessionMarks.info, + if (char.bonds.isNotEmpty || char.flags.isNotEmpty) ...[ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: HelpText( + text: tr.sessionMarks.info, + ), ), - ), - const Divider(height: 32), + const Divider(height: 32), + ], + if (char.bonds.isNotEmpty && char.flags.isNotEmpty) + Text(tr.sessionMarks.bond, style: textTheme.bodySmall), + for (final bond in char.bonds) ...[ + CheckboxListTile( + title: Text(bond.description), + visualDensity: VisualDensity.compact, + dense: true, + value: bond.completed, + onChanged: (val) => onChecked(bond, val), + controlAffinity: ListTileControlAffinity.leading, + ), + ], + if (char.bonds.isNotEmpty && char.flags.isNotEmpty) ...[ + const Divider(height: 32), + Text(tr.sessionMarks.flags, style: textTheme.bodySmall), + ], + for (final flag in char.flags) ...[ + CheckboxListTile( + title: Text(flag.description), + visualDensity: VisualDensity.compact, + dense: true, + value: flag.completed, + onChanged: (val) => onChecked(flag, val), + controlAffinity: ListTileControlAffinity.leading, + ), + ], ], - if (bonds.isNotEmpty && flags.isNotEmpty) - Text(tr.sessionMarks.bond, style: textTheme.bodySmall), - for (final bond in bonds) ...[ - CheckboxListTile( - title: Text(bond.description), - visualDensity: VisualDensity.compact, - dense: true, - value: bond.completed, - onChanged: (val) => onChecked(bond, val), - controlAffinity: ListTileControlAffinity.leading, - ), - ], - if (bonds.isNotEmpty && flags.isNotEmpty) ...[ - const Divider(height: 32), - Text(tr.sessionMarks.flags, style: textTheme.bodySmall), - ], - for (final flag in flags) ...[ - CheckboxListTile( - title: Text(flag.description), - visualDensity: VisualDensity.compact, - dense: true, - value: flag.completed, - onChanged: (val) => onChecked(flag, val), - controlAffinity: ListTileControlAffinity.leading, - ), - ], - ], + ), ), - ), - ), + ); + }, ), ); } Future onChecked(SessionMark sessionMark, [bool? val]) { - return controller.updateCharacter( + return charProvider.updateCharacter( char.copyWith( sessionMarks: updateByKey( sessionMarks, diff --git a/lib/app/widgets/dialogs/coins_dialog.dart b/lib/app/widgets/dialogs/coins_dialog.dart index bc0245dd..76e57892 100644 --- a/lib/app/widgets/dialogs/coins_dialog.dart +++ b/lib/app/widgets/dialogs/coins_dialog.dart @@ -1,6 +1,5 @@ import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import '../../../core/dw_icons.dart'; import '../atoms/number_text_field.dart'; @@ -58,10 +57,10 @@ class _CoinsDialogState extends State { void save() { widget.onChanged(double.parse(controller.text)); - Get.back(); + Navigator.of(context).pop(); } void cancel() { - Get.back(); + Navigator.of(context).pop(); } } diff --git a/lib/app/widgets/dialogs/confirm_delete_account_dialog.dart b/lib/app/widgets/dialogs/confirm_delete_account_dialog.dart index 0847d209..9f2341a0 100644 --- a/lib/app/widgets/dialogs/confirm_delete_account_dialog.dart +++ b/lib/app/widgets/dialogs/confirm_delete_account_dialog.dart @@ -1,30 +1,35 @@ import 'package:dungeon_paper/app/widgets/molecules/dialog_controls.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import '../../../core/utils/dialog_utils.dart'; Future confirmDeleteAccount1(BuildContext context) { - return Get.dialog( - AlertDialog( + return showDialog( + context: context, + builder: (_) => AlertDialog( title: Text(tr.dialogs.confirmations.deleteAccount.step1.title), content: Text(tr.dialogs.confirmations.deleteAccount.step1.body), - actions: DialogControls.delete(context, - onDelete: () => Get.back(result: true), - onCancel: () => Get.back(result: false)), + actions: DialogControls.delete( + context, + onDelete: () => Navigator.of(context).pop(true), + onCancel: () => Navigator.of(context).pop(false), + ), ), ).then((res) => res == true); } Future confirmDeleteAccount2(BuildContext context) { - return Get.dialog( - AlertDialog( + return showDialog( + context: context, + builder: (_) => AlertDialog( title: Text(tr.dialogs.confirmations.deleteAccount.step2.title), content: Text(tr.dialogs.confirmations.deleteAccount.step2.body), - actions: DialogControls.delete(context, - onDelete: () => Get.back(result: true), - onCancel: () => Get.back(result: false)), + actions: DialogControls.delete( + context, + onDelete: () => Navigator.of(context).pop(true), + onCancel: () => Navigator.of(context).pop(false), + ), ), ).then((res) => res == true); } diff --git a/lib/app/widgets/dialogs/confirm_delete_dialog.dart b/lib/app/widgets/dialogs/confirm_delete_dialog.dart index 7ea140b5..e232c248 100644 --- a/lib/app/widgets/dialogs/confirm_delete_dialog.dart +++ b/lib/app/widgets/dialogs/confirm_delete_dialog.dart @@ -1,34 +1,30 @@ import 'package:dungeon_paper/app/widgets/molecules/dialog_controls.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import '../../../core/utils/dialog_utils.dart'; -class _DeleteDialog extends ConfirmationDialog { - @override - Widget createConfirmation( - BuildContext context, DeleteDialogOptions options) { - return AlertDialog( - title: Text(tr.dialogs.confirmations.delete.title(options.entityKind)), - content: Text( - tr.dialogs.confirmations.delete - .body(options.entityKind, options.entityName), - ), +Future confirmDelete(BuildContext context, String name, [Type? t]) { + final type = t ?? T; + return showDialog( + context: context, + builder: (_) => AlertDialog( + title: Text(tr.dialogs.confirmations.delete.title(tr.entity(tn(type)))), + content: + Text(tr.dialogs.confirmations.delete.body(tr.entity(tn(type)), name)), actions: DialogControls.delete( context, - onDelete: () => Get.back(result: true), - onCancel: () => Get.back(result: false), + onDelete: () => Navigator.of(context).pop(true), + onCancel: () => Navigator.of(context).pop(false), ), - ); - } + ), + ).then((res) => res == true); } -class DeleteDialogOptions { - final String entityKind; - final String entityName; - - DeleteDialogOptions({required this.entityKind, required this.entityName}); -} - -final deleteDialog = _DeleteDialog(); +Future awaitDeleteConfirmation( + BuildContext context, + String name, + void Function() onConfirmed, [ + Type? t, +]) => + awaitConfirmation(confirmDelete(context, name, t), onConfirmed); diff --git a/lib/app/widgets/dialogs/confirm_unlink_provider_dialog.dart b/lib/app/widgets/dialogs/confirm_unlink_provider_dialog.dart index c1f0050c..926c3435 100644 --- a/lib/app/widgets/dialogs/confirm_unlink_provider_dialog.dart +++ b/lib/app/widgets/dialogs/confirm_unlink_provider_dialog.dart @@ -1,25 +1,27 @@ import 'package:dungeon_paper/app/widgets/molecules/dialog_controls.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import '../../../core/utils/dialog_utils.dart'; import '../../model_utils/user_utils.dart'; Future confirmUnlinkProvider( BuildContext context, ProviderName provider) { - return Get.dialog( - AlertDialog( + return showDialog( + context: context, + builder: (_) => AlertDialog( title: Text( - tr.auth.confirmUnlink.title(tr.auth.providers.name(provider.name))), + tr.auth.confirmUnlink.title(tr.auth.providers.name(provider.name)), + ), content: Text( - tr.auth.confirmUnlink.body(tr.auth.providers.name(provider.name))), + tr.auth.confirmUnlink.body(tr.auth.providers.name(provider.name)), + ), actions: DialogControls.negative( context, confirmLabel: tr.auth.providers.unlink, cancelLabel: tr.generic.cancel, - onConfirm: () => Get.back(result: true), - onCancel: () => Get.back(result: false), + onConfirm: () => Navigator.of(context).pop(true), + onCancel: () => Navigator.of(context).pop(false), ), ), ).then((res) => res == true); diff --git a/lib/app/widgets/dialogs/custom_roll_buttons_dialog.dart b/lib/app/widgets/dialogs/custom_roll_buttons_dialog.dart index 883e1a56..290d5e6e 100644 --- a/lib/app/widgets/dialogs/custom_roll_buttons_dialog.dart +++ b/lib/app/widgets/dialogs/custom_roll_buttons_dialog.dart @@ -3,7 +3,7 @@ import 'dart:math'; import 'package:dungeon_paper/app/data/models/character.dart'; import 'package:dungeon_paper/app/data/models/move.dart'; import 'package:dungeon_paper/app/data/models/roll_button.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; +import 'package:dungeon_paper/app/data/services/repository_provider.dart'; import 'package:dungeon_paper/app/widgets/atoms/select_box.dart'; import 'package:dungeon_paper/app/widgets/molecules/dialog_controls.dart'; import 'package:dungeon_paper/app/widgets/molecules/dice_list_input.dart'; @@ -13,7 +13,6 @@ import 'package:dungeon_paper/core/utils/list_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; class CustomRollButtonsDialog extends StatefulWidget { const CustomRollButtonsDialog({ @@ -101,14 +100,14 @@ class _CustomRollButtonsDialogState extends State actions: DialogControls.save( context, onSave: save, - onCancel: () => Get.back(), + onCancel: () => Navigator.of(context).pop(), ), ); } void save() { widget.onChanged(rollButtons); - Get.back(); + Navigator.of(context).pop(); } } @@ -132,7 +131,7 @@ class _RollButtonListTile extends StatefulWidget { } class _RollButtonListTileState extends State<_RollButtonListTile> - with RepositoryServiceMixin { + with RepositoryProviderMixin { late TextEditingController label; late ValueNotifier> dice; late ValueNotifier> specialDice; diff --git a/lib/app/widgets/dialogs/damage_dice_dialog.dart b/lib/app/widgets/dialogs/damage_dice_dialog.dart index 28fad1d3..574b58ae 100644 --- a/lib/app/widgets/dialogs/damage_dice_dialog.dart +++ b/lib/app/widgets/dialogs/damage_dice_dialog.dart @@ -5,7 +5,6 @@ import 'package:dungeon_paper/core/dw_icons.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; class DamageDiceDialog extends StatefulWidget { const DamageDiceDialog({ @@ -79,10 +78,10 @@ class _DamageDiceDialogState extends State { void save() { widget.onChanged(useDefault ? null : damage); - Get.back(); + Navigator.of(context).pop(); } void cancel() { - Get.back(); + Navigator.of(context).pop(); } } diff --git a/lib/app/widgets/dialogs/debilities_dialog.dart b/lib/app/widgets/dialogs/debilities_dialog.dart index e02e630a..13ed5bd1 100644 --- a/lib/app/widgets/dialogs/debilities_dialog.dart +++ b/lib/app/widgets/dialogs/debilities_dialog.dart @@ -1,13 +1,12 @@ import 'package:dungeon_paper/app/data/models/session_marks.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; import 'package:dungeon_paper/app/widgets/atoms/help_text.dart'; import 'package:dungeon_paper/app/widgets/molecules/dialog_controls.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -class CharacterDebilitiesDialog extends GetView - with CharacterServiceMixin { +class CharacterDebilitiesDialog extends StatelessWidget + with CharacterProviderMixin { const CharacterDebilitiesDialog({super.key}); List get bonds => char.bonds; @@ -21,60 +20,64 @@ class CharacterDebilitiesDialog extends GetView return AlertDialog( title: Text(tr.debilities.dialog.title), contentPadding: const EdgeInsets.all(16), - actions: DialogControls.done(context, () => Get.back()), - content: Obx( - () => SizedBox( - width: 500, - child: SingleChildScrollView( - child: ListTileTheme.merge( - minLeadingWidth: 20, - contentPadding: const EdgeInsets.all(0), - minVerticalPadding: 8, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - HelpText(text: tr.debilities.dialog.info), - const SizedBox(height: 6), - const Divider(), - for (final ability in char.abilityScores.stats) - ListTile( - contentPadding: const EdgeInsets.all(0), - title: Text(tr.debilities - .label(ability.debilityName, ability.key)), - subtitle: Text(ability.debilityDescription), - dense: true, - leading: Icon(ability.icon, size: 20), - onTap: () => charService.updateCharacter( - char.copyWith( - abilityScores: char.abilityScores.copyWith( - stats: char.abilityScores.stats.map( - (e) => e.key == ability.key - ? e.copyWith(isDebilitated: !e.isDebilitated) - : e, - ), - ), - ), - ), - trailing: Switch.adaptive( - value: ability.isDebilitated, - onChanged: (checked) => charService.updateCharacter( + actions: DialogControls.done(context, () => Navigator.of(context).pop()), + content: CharacterProvider.consumer( + (context, charProvider, _) { + final char = charProvider.current; + return SizedBox( + width: 500, + child: SingleChildScrollView( + child: ListTileTheme.merge( + minLeadingWidth: 20, + contentPadding: const EdgeInsets.all(0), + minVerticalPadding: 8, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + HelpText(text: tr.debilities.dialog.info), + const SizedBox(height: 6), + const Divider(), + for (final ability in char.abilityScores.stats) + ListTile( + contentPadding: const EdgeInsets.all(0), + title: Text(tr.debilities + .label(ability.debilityName, ability.key)), + subtitle: Text(ability.debilityDescription), + dense: true, + leading: Icon(ability.icon, size: 20), + onTap: () => charProvider.updateCharacter( char.copyWith( abilityScores: char.abilityScores.copyWith( - stats: char.abilityScores.stats.map((e) => - e.key == ability.key - ? e.copyWith(isDebilitated: checked) - : e), + stats: char.abilityScores.stats.map( + (e) => e.key == ability.key + ? e.copyWith( + isDebilitated: !e.isDebilitated) + : e, + ), + ), + ), + ), + trailing: Switch.adaptive( + value: ability.isDebilitated, + onChanged: (checked) => charProvider.updateCharacter( + char.copyWith( + abilityScores: char.abilityScores.copyWith( + stats: char.abilityScores.stats.map((e) => + e.key == ability.key + ? e.copyWith(isDebilitated: checked) + : e), + ), ), ), ), ), - ), - ], + ], + ), ), ), - ), - ), + ); + }, ), ); } diff --git a/lib/app/widgets/dialogs/export_class_dialog.dart b/lib/app/widgets/dialogs/export_class_dialog.dart index 32a394a3..40045ff9 100644 --- a/lib/app/widgets/dialogs/export_class_dialog.dart +++ b/lib/app/widgets/dialogs/export_class_dialog.dart @@ -1,5 +1,5 @@ import 'dart:convert'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; +import 'package:dungeon_paper/app/data/services/repository_provider.dart'; import 'package:dungeon_paper/app/modules/ImportExport/controllers/export_controller.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; @@ -18,7 +18,7 @@ class ExportClassDialog extends StatefulWidget { } class _ExportClassDialogState extends State - with RepositoryServiceMixin { + with RepositoryProviderMixin { CharacterClass? cls; @override @@ -81,7 +81,6 @@ class _ExportClassDialogState extends State final dt = DateFormat('yy-MM-dd_HH.mm.ss').format(DateTime.now()); final fileName = 'DungeonPaperV2_$dt.json'; - Exporter().export(strData, fileName); + Exporter().export(context, strData, fileName); } } - diff --git a/lib/app/widgets/dialogs/hp_dialog.dart b/lib/app/widgets/dialogs/hp_dialog.dart index 68b30b97..80bf5a57 100644 --- a/lib/app/widgets/dialogs/hp_dialog.dart +++ b/lib/app/widgets/dialogs/hp_dialog.dart @@ -1,13 +1,12 @@ import 'dart:math'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; import 'package:dungeon_paper/app/widgets/atoms/hp_bar.dart'; import 'package:dungeon_paper/app/widgets/atoms/number_text_field.dart'; import 'package:dungeon_paper/app/widgets/molecules/dialog_controls.dart'; import 'package:dungeon_paper/app/widgets/molecules/value_change_slider.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; enum ValueChange { positive, neutral, negative } @@ -18,7 +17,7 @@ class HPDialog extends StatefulWidget { State createState() => _HPDialogState(); } -class _HPDialogState extends State with CharacterServiceMixin { +class _HPDialogState extends State with CharacterProviderMixin { late int overrideHP; late bool shouldOverrideMaxHP; late TextEditingController overrideMaxHp; @@ -39,8 +38,8 @@ class _HPDialogState extends State with CharacterServiceMixin { return AlertDialog( title: Text(tr.hp.dialog.title), content: SingleChildScrollView( - child: Obx( - () => Column( + child: CharacterProvider.consumer( + (context, controller, _) => Column( mainAxisSize: MainAxisSize.min, children: [ SizedBox( @@ -119,7 +118,7 @@ class _HPDialogState extends State with CharacterServiceMixin { } void save() { - charService.updateCharacter( + charProvider.updateCharacter( char.copyWith( stats: char.stats .copyWith(currentHp: overrideHP) @@ -130,6 +129,6 @@ class _HPDialogState extends State with CharacterServiceMixin { } void close() async { - Get.back(); + Navigator.of(context).pop(); } } diff --git a/lib/app/widgets/dialogs/load_dialog.dart b/lib/app/widgets/dialogs/load_dialog.dart index 75a33f0e..831dae1b 100644 --- a/lib/app/widgets/dialogs/load_dialog.dart +++ b/lib/app/widgets/dialogs/load_dialog.dart @@ -3,7 +3,6 @@ import 'package:dungeon_paper/app/widgets/molecules/dialog_controls.dart'; import 'package:dungeon_paper/core/dw_icons.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; class LoadDialog extends StatefulWidget { const LoadDialog({ @@ -77,11 +76,11 @@ class _LoadDialogState extends State { void save() { widget.onChanged(useDefault ? null : int.parse(controller.text)); - Get.back(); + Navigator.of(context).pop(); } void cancel() { - Get.back(); + Navigator.of(context).pop(); } void _listener() { diff --git a/lib/app/widgets/dialogs/view_tag_dialog.dart b/lib/app/widgets/dialogs/view_tag_dialog.dart index d52a0805..a94eec67 100644 --- a/lib/app/widgets/dialogs/view_tag_dialog.dart +++ b/lib/app/widgets/dialogs/view_tag_dialog.dart @@ -3,7 +3,6 @@ import 'package:dungeon_paper/core/utils/string_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; class ViewTagDialog extends StatelessWidget { const ViewTagDialog({ @@ -82,7 +81,7 @@ class ViewTagDialog extends StatelessWidget { ], ), ), - actions: DialogControls.done(context, () => Get.back()), + actions: DialogControls.done(context, () => Navigator.of(context).pop()), ); } } diff --git a/lib/app/widgets/dialogs/xp_dialog.dart b/lib/app/widgets/dialogs/xp_dialog.dart index cfbae6b1..884bec10 100644 --- a/lib/app/widgets/dialogs/xp_dialog.dart +++ b/lib/app/widgets/dialogs/xp_dialog.dart @@ -1,6 +1,6 @@ import 'package:dungeon_paper/app/data/models/character_stats.dart'; import 'package:dungeon_paper/app/data/models/session_marks.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; import 'package:dungeon_paper/app/widgets/atoms/custom_expansion_panel.dart'; import 'package:dungeon_paper/app/widgets/atoms/help_text.dart'; import 'package:dungeon_paper/app/widgets/atoms/number_text_field.dart'; @@ -12,7 +12,7 @@ import 'package:dungeon_paper/core/utils/math_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; enum ValueChange { positive, neutral, negative } @@ -23,7 +23,7 @@ class EXPDialog extends StatefulWidget { State createState() => _EXPDialogState(); } -class _EXPDialogState extends State with CharacterServiceMixin { +class _EXPDialogState extends State with CharacterProviderMixin { late TextEditingController overrideXp; late bool manualExpExpanded; late TextEditingController overrideLevel; @@ -57,8 +57,8 @@ class _EXPDialogState extends State with CharacterServiceMixin { title: Text( !hasOverrides ? tr.xp.dialog.title : tr.xp.dialog.overridingTitle), content: SingleChildScrollView( - child: Obx( - () { + child: Consumer( + builder: (context, charProvider, child) { final level = maxXp - 7; return Column( @@ -223,7 +223,7 @@ class _EXPDialogState extends State with CharacterServiceMixin { updatedLevel++; } - charService.updateCharacter( + charProvider.updateCharacter( char.copyWith( stats: char.stats.copyWith( currentXp: updatedXp, @@ -244,14 +244,14 @@ class _EXPDialogState extends State with CharacterServiceMixin { } void close() { - Get.back(); + Navigator.of(context).pop(); } void _toggleEosMark(SessionMark eos, bool? val) { setState(() { eosMarks = updateByKey( eosMarks, [eos.copyWithInherited(completed: val ?? !eos.completed)]); - charService.updateCharacter(char.copyWith( + charProvider.updateCharacter(char.copyWith( sessionMarks: upsertByKey(char.sessionMarks, eosMarks))); }); } diff --git a/lib/app/widgets/forms/character_class_form.dart b/lib/app/widgets/forms/character_class_form.dart index 9567ff5b..4cd2bc90 100644 --- a/lib/app/widgets/forms/character_class_form.dart +++ b/lib/app/widgets/forms/character_class_form.dart @@ -8,94 +8,95 @@ import 'package:dungeon_paper/core/utils/list_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; -class CharacterClassForm extends GetView { +class CharacterClassForm extends StatelessWidget { const CharacterClassForm({super.key}); @override Widget build(BuildContext context) { - return LibraryEntityForm( - children: [ - () => TextFormField( - controller: controller.name, - textCapitalization: TextCapitalization.words, - decoration: InputDecoration( + return Consumer( + builder: (context, controller, _) => + LibraryEntityForm( + children: [ + () => TextFormField( + controller: controller.name, + textCapitalization: TextCapitalization.words, + decoration: InputDecoration( + label: Text( + tr.generic.entityName(tr.entity(tn(CharacterClass)))), + ), + ), + () => const SizedBox(height: 16), + () => RichTextField( + controller: controller.description, label: - Text(tr.generic.entityName(tr.entity(tn(CharacterClass)))), + tr.generic.entityDescription(tr.entity(tn(CharacterClass))), + maxLines: 10, + minLines: 5, + textCapitalization: TextCapitalization.sentences, ), - ), - () => const SizedBox(height: 16), - () => RichTextField( - controller: controller.description, - label: - tr.generic.entityDescription(tr.entity(tn(CharacterClass))), - maxLines: 10, - minLines: 5, - textCapitalization: TextCapitalization.sentences, - ), - () => DiceListInput( - controller: controller.damageDice, - label: Text(tr.characterClass.damageDice), - abilityScores: AbilityScores.dungeonWorld( - cha: 12, - con: 12, - dex: 12, - str: 12, - wis: 12, - intl: 12, + () => DiceListInput( + controller: controller.damageDice, + label: Text(tr.characterClass.damageDice), + abilityScores: AbilityScores.dungeonWorld( + cha: 12, + con: 12, + dex: 12, + str: 12, + wis: 12, + intl: 12, + ), + guessFrom: [controller.description], + maxCount: 1, ), - guessFrom: [controller.description], - maxCount: 1, - ), - () => Row( - children: [ - Expanded( - child: NumberTextField( - numberType: NumberType.int, - controller: controller.hp, - decoration: InputDecoration( - label: Text(tr.characterClass.baseHp), + () => Row( + children: [ + Expanded( + child: NumberTextField( + numberType: NumberType.int, + controller: controller.hp, + decoration: InputDecoration( + label: Text(tr.characterClass.baseHp), + ), ), ), - ), - const SizedBox(width: 16), - Expanded( - child: NumberTextField( - numberType: NumberType.int, - controller: controller.load, - decoration: InputDecoration( - label: Text(tr.characterClass.baseLoad), + const SizedBox(width: 16), + Expanded( + child: NumberTextField( + numberType: NumberType.int, + controller: controller.load, + decoration: InputDecoration( + label: Text(tr.characterClass.baseLoad), + ), ), ), - ), - ], - ), - ], + ], + ), + ], + ), ); } } class CharacterClassFormController extends LibraryEntityFormController< CharacterClass, CharacterClassFormArguments> { - final _name = TextEditingController().obs; - final _description = TextEditingController().obs; - final _damageDice = ValueNotifier>([dw.Dice.d4]).obs; - final _hp = TextEditingController().obs; - final _load = TextEditingController().obs; + final _name = TextEditingController(); + final _description = TextEditingController(); + final _damageDice = ValueNotifier>([dw.Dice.d4]); + final _hp = TextEditingController(); + final _load = TextEditingController(); @override - List> get fields => [_name, _description, _damageDice]; + List get fields => [_name, _description, _damageDice]; - TextEditingController get name => _name.value; - TextEditingController get description => _description.value; - ValueNotifier> get damageDice => _damageDice.value; - TextEditingController get hp => _hp.value; - TextEditingController get load => _load.value; + TextEditingController get name => _name; + TextEditingController get description => _description; + ValueNotifier> get damageDice => _damageDice; + TextEditingController get hp => _hp; + TextEditingController get load => _load; - @override - void onInit() { - super.onInit(); + CharacterClassFormController(super.context) { name.text = args.entity?.name ?? ''; description.text = args.entity?.description ?? ''; damageDice.value = asList(args.entity?.damageDice ?? dw.Dice.d4); diff --git a/lib/app/widgets/forms/dice_form.dart b/lib/app/widgets/forms/dice_form.dart index def6ce61..bebadff8 100644 --- a/lib/app/widgets/forms/dice_form.dart +++ b/lib/app/widgets/forms/dice_form.dart @@ -1,5 +1,4 @@ import 'package:dungeon_paper/app/data/models/ability_scores.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; import 'package:dungeon_paper/app/themes/button_themes.dart'; import 'package:dungeon_paper/app/themes/themes.dart'; import 'package:dungeon_paper/app/widgets/atoms/number_text_field.dart'; @@ -7,7 +6,6 @@ import 'package:dungeon_paper/app/widgets/atoms/select_box.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; -import 'package:get/get.dart'; enum ModifierType { stat, fixed } @@ -30,7 +28,6 @@ class DiceForm extends StatefulWidget { } class _DiceFormState extends State { - final RepositoryService repo = Get.find(); // late int amount; late final TextEditingController amount; late int sides; diff --git a/lib/app/widgets/forms/entity_share_form.dart b/lib/app/widgets/forms/entity_share_form.dart index 2f8ddec3..70038907 100644 --- a/lib/app/widgets/forms/entity_share_form.dart +++ b/lib/app/widgets/forms/entity_share_form.dart @@ -1,6 +1,6 @@ import 'package:dungeon_paper/app/data/models/meta.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; -import 'package:dungeon_paper/app/data/services/user_service.dart'; +import 'package:dungeon_paper/app/data/services/repository_provider.dart'; +import 'package:dungeon_paper/app/data/services/user_provider.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; @@ -24,7 +24,7 @@ enum SyncStatus { } class _EntityShareFormState extends State - with RepositoryServiceMixin, UserServiceMixin { + with UserProviderMixin, RepositoryProviderMixin { T? source; IconData? get syncStatusIcon => { @@ -94,7 +94,7 @@ class _EntityShareFormState extends State Text( syncStatusText, style: TextStyle(color: syncStatusColor(context)), - textScaleFactor: 0.9, + textScaler: const TextScaler.linear(0.9), ), if (syncStatus == SyncStatus.outOfSync) ...[ ElevatedButton.icon( @@ -122,3 +122,4 @@ class _EntityShareFormState extends State widget.onChange(Meta.forkMeta(source!, user)); } } + diff --git a/lib/app/widgets/forms/item_form.dart b/lib/app/widgets/forms/item_form.dart index 05bedfa1..70c1a4d7 100644 --- a/lib/app/widgets/forms/item_form.dart +++ b/lib/app/widgets/forms/item_form.dart @@ -5,59 +5,54 @@ import 'package:dungeon_paper/app/widgets/molecules/tag_list_input.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; -class ItemForm extends GetView { +class ItemForm extends StatelessWidget { const ItemForm({super.key}); @override Widget build(BuildContext context) { - return LibraryEntityForm( - children: [ - () => Obx( - () => TextFormField( + return Consumer( + builder: (context, controller, _) => + LibraryEntityForm( + children: [ + () => TextFormField( controller: controller.name, textCapitalization: TextCapitalization.words, decoration: InputDecoration( label: Text(tr.generic.entityName(tr.entity(tn(Item)))), ), ), - ), - () => Obx( - () => RichTextField( + () => RichTextField( controller: controller.description, label: tr.generic.entityDescription(tr.entity(tn(Item))), maxLines: 10, minLines: 5, textCapitalization: TextCapitalization.sentences, ), - ), - () => Obx( - () => TagListInput( + () => TagListInput( controller: controller.tags, ), - ), - ], + ], + ), ); } } class ItemFormController extends LibraryEntityFormController { - final _name = TextEditingController().obs; - final _description = TextEditingController().obs; - final _tags = ValueNotifier>([]).obs; + final _name = TextEditingController(); + final _description = TextEditingController(); + final _tags = ValueNotifier>([]); @override - List> get fields => [_name, _description, _tags]; + List get fields => [_name, _description, _tags]; - TextEditingController get name => _name.value; - TextEditingController get description => _description.value; - ValueNotifier> get tags => _tags.value; + TextEditingController get name => _name; + TextEditingController get description => _description; + ValueNotifier> get tags => _tags; - @override - void onInit() { - super.onInit(); + ItemFormController(super.context) { name.text = args.entity?.name ?? ''; description.text = args.entity?.description ?? ''; tags.value = args.entity?.tags ?? []; diff --git a/lib/app/widgets/forms/library_entity_form.dart b/lib/app/widgets/forms/library_entity_form.dart index d615dfdc..b02d4c23 100644 --- a/lib/app/widgets/forms/library_entity_form.dart +++ b/lib/app/widgets/forms/library_entity_form.dart @@ -1,20 +1,19 @@ import 'package:dungeon_paper/app/data/models/meta.dart'; -import 'package:dungeon_paper/app/data/models/user.dart'; -import 'package:dungeon_paper/app/data/services/user_service.dart'; import 'package:dungeon_paper/app/widgets/atoms/advanced_floating_action_button.dart'; import 'package:dungeon_paper/app/widgets/atoms/confirm_exit_view.dart'; import 'package:dungeon_paper/app/widgets/forms/entity_share_form.dart'; +import 'package:dungeon_paper/core/route_arguments.dart'; import 'package:dungeon_paper/core/utils/builder_utils.dart'; import 'package:dungeon_paper/core/utils/enums.dart'; import 'package:dungeon_paper/core/utils/list_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; class LibraryEntityForm< T extends WithMeta, Ctrl extends LibraryEntityFormController>> extends GetView { + LibraryEntityFormArguments>> extends StatelessWidget { const LibraryEntityForm({ super.key, required this.children, @@ -24,12 +23,12 @@ class LibraryEntityForm< @override Widget build(BuildContext context) { - return Obx( - () => ConfirmExitView( - dirty: controller.dirty.value, + return Consumer( + builder: (context, controller, _) => ConfirmExitView( + dirty: controller.dirty, child: Scaffold( appBar: AppBar( - title: title, + title: title(controller), ), body: ItemBuilder.lazyListView( padding: const EdgeInsets.all(16).copyWith(bottom: 80), @@ -37,16 +36,14 @@ class LibraryEntityForm< children.joinObjects(() => const SizedBox(height: 16)).toList(), trailing: [ () => const Divider(height: 64), - () => Obx( - () => EntityShareForm( - entity: controller.asEntity.value, - onChange: controller.updateFromEntity, - ), - ) + () => EntityShareForm( + entity: controller.asEntity, + onChange: controller.updateFromEntity, + ), ], ), floatingActionButton: AdvancedFloatingActionButton.extended( - onPressed: controller.onSave, + onPressed: () => controller.onSave(context), label: Text(tr.generic.save), icon: const Icon(Icons.save), ), @@ -55,9 +52,7 @@ class LibraryEntityForm< ); } - User get user => Get.find().current; - - Widget get title => Text( + Widget title(Ctrl controller) => Text( controller.args.formContext == FormContext.create ? tr.generic.addEntity(tr.entity( tn(controller.empty().runtimeType), @@ -69,43 +64,40 @@ class LibraryEntityForm< } abstract class LibraryEntityFormController> extends GetxController { - final dirty = false.obs; + Args extends LibraryEntityFormArguments> extends ChangeNotifier { + var dirty = false; late final Args args; bool afterInit = false; - List> get fields; - late final Rx meta; + List get fields; + late Meta meta; late final List _initialValueCache; - late Rx asEntity; + late T asEntity; - @override - @mustCallSuper - void onInit() { - assert(Get.arguments is LibraryEntityFormArguments); - args = Get.arguments; - asEntity = Rx(args.entity ?? empty()); - meta = Rx(args.entity?.meta ?? _forkMeta()); + LibraryEntityFormController(BuildContext context) { + args = getArgs(context); + asEntity = args.entity ?? empty(); + meta = args.entity?.meta ?? _forkMeta(); _initialValueCache = List.generate(fields.length, (i) => ''); for (var field in enumerate(fields)) { - field.value.value.addListener(_fieldListener(field)); + field.value.addListener(_fieldListener(field)); } - super.onInit(); + afterInit = true; } - void Function() _fieldListener(Enumerated>> field) { + void Function() _fieldListener(Enumerated> field) { return () { - final asStr = _toString(field.value.value.value); + final asStr = _toString(field.value.value); final cached = _initialValueCache[field.index]; if (!afterInit) { _initialValueCache[field.index] = asStr; } if (afterInit && asStr != cached) { - dirty.value = true; - meta.value = _forkMeta(); + dirty = true; + meta = _forkMeta(); } - field.value.refresh(); - asEntity.value = toEntity(); + asEntity = toEntity(); + notifyListeners(); }; } @@ -126,23 +118,16 @@ abstract class LibraryEntityFormController (args.entity ?? empty()).copyWithInherited(meta: meta.value); + T toEntity() => (args.entity ?? empty()).copyWithInherited(meta: meta); @mustCallSuper void updateFromEntity(T entity) { - meta.value = entity.meta; + meta = entity.meta; + notifyListeners(); } - void onSave() { + void onSave(BuildContext context) { debugPrint('onSave: ${args.onSave}'); cb(dynamic obj) { args.onSave(obj); @@ -165,7 +151,7 @@ abstract class LibraryEntityFormController { required this.formContext, }); } + diff --git a/lib/app/widgets/forms/move_form.dart b/lib/app/widgets/forms/move_form.dart index 96619d13..ca2c4e8c 100644 --- a/lib/app/widgets/forms/move_form.dart +++ b/lib/app/widgets/forms/move_form.dart @@ -2,7 +2,7 @@ import 'package:dungeon_paper/app/data/models/ability_scores.dart'; import 'package:dungeon_paper/app/data/models/character_class.dart'; import 'package:dungeon_paper/app/data/models/move.dart'; import 'package:dungeon_paper/app/data/models/move_templates.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; +import 'package:dungeon_paper/app/data/services/repository_provider.dart'; import 'package:dungeon_paper/app/widgets/atoms/rich_text_field.dart'; import 'package:dungeon_paper/app/widgets/atoms/select_box.dart'; import 'package:dungeon_paper/app/widgets/forms/library_entity_form.dart'; @@ -11,17 +11,17 @@ import 'package:dungeon_paper/app/widgets/molecules/tag_list_input.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; -class MoveForm extends GetView with RepositoryServiceMixin { +class MoveForm extends StatelessWidget with RepositoryProviderMixin { const MoveForm({super.key}); @override Widget build(BuildContext context) { return LibraryEntityForm( children: [ - () => Obx( - () => TextFormField( + () => Consumer( + builder: (context, controller, _) => TextFormField( decoration: InputDecoration( label: Text(tr.generic.entityName(tr.entity(tn(Move)))), ), @@ -29,8 +29,8 @@ class MoveForm extends GetView with RepositoryServiceMixin { controller: controller.name, ), ), - () => Obx( - () => Row( + () => Consumer( + builder: (context, controller, _) => Row( children: [ Expanded( child: SelectBox( @@ -75,8 +75,8 @@ class MoveForm extends GetView with RepositoryServiceMixin { ], ), ), - () => Obx( - () => RichTextField( + () => Consumer( + builder: (context, controller, _) => RichTextField( decoration: InputDecoration( label: Text( tr.generic.entityDescription(tr.entity(tn(Move))), @@ -114,8 +114,8 @@ class MoveForm extends GetView with RepositoryServiceMixin { ], ), ), - () => Obx( - () => RichTextField( + () => Consumer( + builder: (context, controller, _) => RichTextField( decoration: InputDecoration( label: Text( tr.generic.entityExplanation(tr.entity(tn(Move))), @@ -128,16 +128,16 @@ class MoveForm extends GetView with RepositoryServiceMixin { controller: controller.explanation, ), ), - () => Obx( - () => DiceListInput( + () => Consumer( + builder: (context, controller, _) => DiceListInput( controller: controller.dice, abilityScores: controller.args.abilityScores ?? AbilityScores.dungeonWorldAll(10), guessFrom: [controller.description, controller.explanation], ), ), - () => Obx( - () => TagListInput( + () => Consumer( + builder: (context, controller, _) => TagListInput( controller: controller.tags, ), ), @@ -148,29 +148,27 @@ class MoveForm extends GetView with RepositoryServiceMixin { class MoveFormController extends LibraryEntityFormController { - final _name = TextEditingController().obs; - final _description = TextEditingController().obs; - final _explanation = TextEditingController().obs; - final _dice = ValueNotifier>([]).obs; - final _tags = ValueNotifier>([]).obs; - final _category = ValueNotifier(MoveCategory.basic).obs; - final _classKeys = ValueNotifier>([]).obs; + final _name = TextEditingController(); + final _description = TextEditingController(); + final _explanation = TextEditingController(); + final _dice = ValueNotifier>([]); + final _tags = ValueNotifier>([]); + final _category = ValueNotifier(MoveCategory.basic); + final _classKeys = ValueNotifier>([]); - TextEditingController get name => _name.value; - TextEditingController get description => _description.value; - TextEditingController get explanation => _explanation.value; - ValueNotifier> get dice => _dice.value; - ValueNotifier> get tags => _tags.value; - ValueNotifier get category => _category.value; - ValueNotifier> get classKeys => _classKeys.value; + TextEditingController get name => _name; + TextEditingController get description => _description; + TextEditingController get explanation => _explanation; + ValueNotifier> get dice => _dice; + ValueNotifier> get tags => _tags; + ValueNotifier get category => _category; + ValueNotifier> get classKeys => _classKeys; @override - List> get fields => + List get fields => [_name, _description, _explanation, _dice, _tags, _category, _classKeys]; - @override - void onInit() { - super.onInit(); + MoveFormController(super.context) { name.text = args.entity?.name ?? ''; description.text = args.entity?.description ?? ''; explanation.text = args.entity?.explanation ?? ''; diff --git a/lib/app/widgets/forms/note_form.dart b/lib/app/widgets/forms/note_form.dart index 7192017e..5507d4d6 100644 --- a/lib/app/widgets/forms/note_form.dart +++ b/lib/app/widgets/forms/note_form.dart @@ -1,22 +1,23 @@ import 'package:dungeon_paper/app/data/models/note.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; +import 'package:dungeon_paper/app/data/services/repository_provider.dart'; import 'package:dungeon_paper/app/widgets/atoms/rich_text_field.dart'; import 'package:dungeon_paper/app/widgets/forms/library_entity_form.dart'; import 'package:dungeon_paper/app/widgets/molecules/tag_list_input.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; -class NoteForm extends GetView with RepositoryServiceMixin { +class NoteForm extends StatelessWidget with RepositoryProviderMixin { const NoteForm({super.key}); @override Widget build(BuildContext context) { - return LibraryEntityForm( - children: [ - () => Obx( - () => TextFormField( + return Consumer( + builder: (context, controller, _) => + LibraryEntityForm( + children: [ + () => TextFormField( decoration: InputDecoration( label: Text( tr.generic.entityName(tr.entity(tn(Note))), @@ -25,9 +26,7 @@ class NoteForm extends GetView with RepositoryServiceMixin { textCapitalization: TextCapitalization.words, controller: controller.title, ), - ), - () => Obx( - () => RichTextField( + () => RichTextField( decoration: InputDecoration( label: Text( tr.generic.entityDescription(tr.entity(tn(Note))), @@ -39,37 +38,33 @@ class NoteForm extends GetView with RepositoryServiceMixin { textCapitalization: TextCapitalization.sentences, controller: controller.description, ), - ), - () => Obx( - () => TagListInput( + () => TagListInput( controller: controller.tags, ), - ), - ], + ], + ), ); } } class NoteFormController extends LibraryEntityFormController { - final _title = TextEditingController().obs; - final _description = TextEditingController().obs; - final _category = TextEditingController().obs; - final _dice = ValueNotifier>([]).obs; - final _tags = ValueNotifier>([]).obs; + final _title = TextEditingController(); + final _description = TextEditingController(); + final _category = TextEditingController(); + final _dice = ValueNotifier>([]); + final _tags = ValueNotifier>([]); - TextEditingController get title => _title.value; - TextEditingController get description => _description.value; - TextEditingController get category => _category.value; - ValueNotifier> get tags => _tags.value; + TextEditingController get title => _title; + TextEditingController get description => _description; + TextEditingController get category => _category; + ValueNotifier> get tags => _tags; @override - List> get fields => + List get fields => [_title, _description, _category, _dice, _tags]; - @override - void onInit() { - super.onInit(); + NoteFormController(super.context) { debugPrint('NoteFormController onInit, ${args.entity?.description ?? ''}'); title.text = args.entity?.title ?? ''; description.text = args.entity?.description ?? ''; diff --git a/lib/app/widgets/forms/race_form.dart b/lib/app/widgets/forms/race_form.dart index 1301afd1..153f48bb 100644 --- a/lib/app/widgets/forms/race_form.dart +++ b/lib/app/widgets/forms/race_form.dart @@ -1,7 +1,7 @@ import 'package:dungeon_paper/app/data/models/ability_scores.dart'; import 'package:dungeon_paper/app/data/models/character_class.dart'; import 'package:dungeon_paper/app/data/models/race.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; +import 'package:dungeon_paper/app/data/services/repository_provider.dart'; import 'package:dungeon_paper/app/widgets/atoms/rich_text_field.dart'; import 'package:dungeon_paper/app/widgets/atoms/select_box.dart'; import 'package:dungeon_paper/app/widgets/forms/library_entity_form.dart'; @@ -10,17 +10,17 @@ import 'package:dungeon_paper/app/widgets/molecules/tag_list_input.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; -class RaceForm extends GetView with RepositoryServiceMixin { +class RaceForm extends StatelessWidget with RepositoryProviderMixin { const RaceForm({super.key}); @override Widget build(BuildContext context) { return LibraryEntityForm( children: [ - () => Obx( - () => TextFormField( + () => Consumer( + builder: (context, controller, _) => TextFormField( decoration: InputDecoration( label: Text(tr.generic.entityName(tr.entity(tn(Race)))), ), @@ -28,8 +28,9 @@ class RaceForm extends GetView with RepositoryServiceMixin { controller: controller.name, ), ), - () => Obx( - () => SelectBox( + () => Consumer( + builder: (context, controller, _) => + SelectBox( value: controller.classKeys.value.isNotEmpty ? controller.classKeys.value.first : null, @@ -47,8 +48,8 @@ class RaceForm extends GetView with RepositoryServiceMixin { .toList(), ), ), - () => Obx( - () => RichTextField( + () => Consumer( + builder: (context, controller, _) => RichTextField( decoration: InputDecoration( label: Text(tr.generic.entityDescription(tr.entity(tn(Race)))), @@ -60,8 +61,8 @@ class RaceForm extends GetView with RepositoryServiceMixin { controller: controller.description, ), ), - () => Obx( - () => RichTextField( + () => Consumer( + builder: (context, controller, _) => RichTextField( decoration: InputDecoration( label: Text(tr.generic.entityExplanation(tr.entity(tn(Race)))), @@ -73,16 +74,16 @@ class RaceForm extends GetView with RepositoryServiceMixin { controller: controller.explanation, ), ), - () => Obx( - () => DiceListInput( + () => Consumer( + builder: (context, controller, _) => DiceListInput( controller: controller.dice, abilityScores: controller.args.abilityScores ?? AbilityScores.dungeonWorldAll(10), guessFrom: [controller.description, controller.explanation], ), ), - () => Obx( - () => TagListInput( + () => Consumer( + builder: (context, controller, _) => TagListInput( controller: controller.tags, ), ), @@ -93,27 +94,25 @@ class RaceForm extends GetView with RepositoryServiceMixin { class RaceFormController extends LibraryEntityFormController { - final _name = TextEditingController().obs; - final _description = TextEditingController().obs; - final _explanation = TextEditingController().obs; - final _dice = ValueNotifier>([]).obs; - final _tags = ValueNotifier>([]).obs; - final _classKeys = ValueNotifier>([]).obs; + final _name = TextEditingController(); + final _description = TextEditingController(); + final _explanation = TextEditingController(); + final _dice = ValueNotifier>([]); + final _tags = ValueNotifier>([]); + final _classKeys = ValueNotifier>([]); - TextEditingController get name => _name.value; - TextEditingController get description => _description.value; - TextEditingController get explanation => _explanation.value; - ValueNotifier> get dice => _dice.value; - ValueNotifier> get tags => _tags.value; - ValueNotifier> get classKeys => _classKeys.value; + TextEditingController get name => _name; + TextEditingController get description => _description; + TextEditingController get explanation => _explanation; + ValueNotifier> get dice => _dice; + ValueNotifier> get tags => _tags; + ValueNotifier> get classKeys => _classKeys; @override - List> get fields => + List get fields => [_name, _description, _explanation, _dice, _tags, _classKeys]; - @override - void onInit() { - super.onInit(); + RaceFormController(super.context) { name.text = args.entity?.name ?? ''; description.text = args.entity?.description ?? ''; explanation.text = args.entity?.explanation ?? ''; diff --git a/lib/app/widgets/forms/spell_form.dart b/lib/app/widgets/forms/spell_form.dart index fb4a06e6..926a4150 100644 --- a/lib/app/widgets/forms/spell_form.dart +++ b/lib/app/widgets/forms/spell_form.dart @@ -1,7 +1,7 @@ import 'package:dungeon_paper/app/data/models/ability_scores.dart'; import 'package:dungeon_paper/app/data/models/character_class.dart'; import 'package:dungeon_paper/app/data/models/spell.dart'; -import 'package:dungeon_paper/app/data/services/repository_service.dart'; +import 'package:dungeon_paper/app/data/services/repository_provider.dart'; import 'package:dungeon_paper/app/widgets/atoms/rich_text_field.dart'; import 'package:dungeon_paper/app/widgets/atoms/select_box.dart'; import 'package:dungeon_paper/app/widgets/forms/library_entity_form.dart'; @@ -11,18 +11,17 @@ import 'package:dungeon_paper/core/utils/math_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; -class SpellForm extends GetView - with RepositoryServiceMixin { +class SpellForm extends StatelessWidget with RepositoryProviderMixin { const SpellForm({super.key}); @override Widget build(BuildContext context) { return LibraryEntityForm( children: [ - () => Obx( - () => TextFormField( + () => Consumer( + builder: (context, controller, _) => TextFormField( decoration: InputDecoration( label: Text(tr.generic.entityName(tr.entity(tn(Spell)))), ), @@ -33,8 +32,8 @@ class SpellForm extends GetView () => Row( children: [ Expanded( - child: Obx( - () => SelectBox( + child: Consumer( + builder: (context, controller, _) => SelectBox( value: controller.level.value, label: Text(tr.character.data.level), isExpanded: true, @@ -56,8 +55,9 @@ class SpellForm extends GetView ), const SizedBox(width: 16), Expanded( - child: Obx( - () => SelectBox( + child: Consumer( + builder: (context, controller, _) => + SelectBox( value: controller.classKeys.value.isNotEmpty ? controller.classKeys.value.first : null, @@ -81,8 +81,8 @@ class SpellForm extends GetView ), ], ), - () => Obx( - () => RichTextField( + () => Consumer( + builder: (context, controller, _) => RichTextField( decoration: InputDecoration( label: Text(tr.generic.entityDescription(tr.entity(tn(Spell)))), @@ -94,8 +94,8 @@ class SpellForm extends GetView controller: controller.description, ), ), - () => Obx( - () => RichTextField( + () => Consumer( + builder: (context, controller, _) => RichTextField( decoration: InputDecoration( label: Text(tr.generic.entityExplanation(tr.entity(tn(Spell)))), @@ -107,16 +107,16 @@ class SpellForm extends GetView controller: controller.explanation, ), ), - () => Obx( - () => DiceListInput( + () => Consumer( + builder: (context, controller, _) => DiceListInput( controller: controller.dice, abilityScores: controller.args.abilityScores ?? AbilityScores.dungeonWorldAll(10), guessFrom: [controller.description, controller.explanation], ), ), - () => Obx( - () => TagListInput( + () => Consumer( + builder: (context, controller, _) => TagListInput( controller: controller.tags, ), ), @@ -127,29 +127,27 @@ class SpellForm extends GetView class SpellFormController extends LibraryEntityFormController { - final _name = TextEditingController().obs; - final _description = TextEditingController().obs; - final _explanation = TextEditingController().obs; - final _dice = ValueNotifier>([]).obs; - final _tags = ValueNotifier>([]).obs; - final _category = ValueNotifier('').obs; - final _classKeys = ValueNotifier>([]).obs; + final _name = TextEditingController(); + final _description = TextEditingController(); + final _explanation = TextEditingController(); + final _dice = ValueNotifier>([]); + final _tags = ValueNotifier>([]); + final _category = ValueNotifier(''); + final _classKeys = ValueNotifier>([]); - TextEditingController get name => _name.value; - TextEditingController get description => _description.value; - TextEditingController get explanation => _explanation.value; - ValueNotifier> get dice => _dice.value; - ValueNotifier> get tags => _tags.value; - ValueNotifier get level => _category.value; - ValueNotifier> get classKeys => _classKeys.value; + TextEditingController get name => _name; + TextEditingController get description => _description; + TextEditingController get explanation => _explanation; + ValueNotifier> get dice => _dice; + ValueNotifier> get tags => _tags; + ValueNotifier get level => _category; + ValueNotifier> get classKeys => _classKeys; @override - List> get fields => + List get fields => [_name, _description, _explanation, _dice, _tags, _category, _classKeys]; - @override - void onInit() { - super.onInit(); + SpellFormController(super.context) { name.text = args.entity?.name ?? ''; description.text = args.entity?.description ?? ''; explanation.text = args.entity?.explanation ?? ''; diff --git a/lib/app/widgets/menus/group_sort_menu.dart b/lib/app/widgets/menus/group_sort_menu.dart index 695f2d1c..b1e48f31 100644 --- a/lib/app/widgets/menus/group_sort_menu.dart +++ b/lib/app/widgets/menus/group_sort_menu.dart @@ -16,7 +16,8 @@ class GroupSortMenu extends StatelessWidget { final int totalItemCount; final List> leading; final List> trailing; - final void Function(int oldIndex, int newIndex) onReorder; + final void Function(BuildContext context, int oldIndex, int newIndex) + onReorder; @override Widget build(BuildContext context) { @@ -29,14 +30,14 @@ class GroupSortMenu extends StatelessWidget { value: 'up', icon: const Icon(Icons.move_up), label: Text(tr.sort.moveUp), - onSelect: () => onReorder(index, index - 1), + onSelect: () => onReorder(context, index, index - 1), ), MenuEntry( disabled: index >= totalItemCount - 1, value: 'down', icon: const Icon(Icons.move_down), label: Text(tr.sort.moveDown), - onSelect: () => onReorder(index, index + 1), + onSelect: () => onReorder(context, index, index + 1), ), ...trailing, ], diff --git a/lib/app/widgets/molecules/ability_scores_grid.dart b/lib/app/widgets/molecules/ability_scores_grid.dart index a8df5d94..f4568c20 100644 --- a/lib/app/widgets/molecules/ability_scores_grid.dart +++ b/lib/app/widgets/molecules/ability_scores_grid.dart @@ -5,10 +5,10 @@ import '../chips/ability_score_chip.dart'; class AbilityScoresGrid extends StatelessWidget { const AbilityScoresGrid({ - Key? key, + super.key, required this.abilityScores, this.showDice = true, - }) : super(key: key); + }); final List abilityScores; final bool showDice; diff --git a/lib/app/widgets/molecules/categorized_list.dart b/lib/app/widgets/molecules/categorized_list.dart index e3ce67e8..b275a34d 100644 --- a/lib/app/widgets/molecules/categorized_list.dart +++ b/lib/app/widgets/molecules/categorized_list.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; class CategorizedList extends StatelessWidget { CategorizedList({ - Key? key, + super.key, required List children, required this.title, this.titleLeading = const [], @@ -15,11 +15,10 @@ class CategorizedList extends StatelessWidget { this.leading = const [], this.trailing = const [], }) : itemBuilder = ((BuildContext context, int index) => children[index]), - itemCount = children.length, - super(key: key); + itemCount = children.length; const CategorizedList.builder({ - Key? key, + super.key, required this.itemBuilder, required this.itemCount, required this.title, @@ -30,7 +29,7 @@ class CategorizedList extends StatelessWidget { this.itemPadding, this.leading = const [], this.trailing = const [], - }) : super(key: key); + }); final Widget title; final Widget Function(BuildContext context, int index) itemBuilder; diff --git a/lib/app/widgets/molecules/chip_list_input.dart b/lib/app/widgets/molecules/chip_list_input.dart index e747d25e..d799d9b8 100644 --- a/lib/app/widgets/molecules/chip_list_input.dart +++ b/lib/app/widgets/molecules/chip_list_input.dart @@ -1,7 +1,6 @@ import 'package:dungeon_paper/core/utils/list_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; class ChipListInput extends StatefulWidget { const ChipListInput({ @@ -30,7 +29,7 @@ class ChipListInput extends StatefulWidget { final Widget Function( BuildContext context, Enumerated? value, { - required void Function(T _dice) onSave, + required void Function(T dice) onSave, })? dialogBuilder; final List trailing; final List leading; @@ -90,8 +89,9 @@ class _ChipListInputState extends State> { ...controller.value..removeAt(dice.index) ]), onTapChip: widget.dialogBuilder != null - ? () => Get.dialog( - widget.dialogBuilder!( + ? () => showDialog( + context: context, + builder: (_) => widget.dialogBuilder!( context, dice, onSave: (value) { @@ -108,8 +108,9 @@ class _ChipListInputState extends State> { context, null, onTapChip: widget.dialogBuilder != null - ? () => Get.dialog( - widget.dialogBuilder!( + ? () => showDialog( + context: context, + builder: (_) => widget.dialogBuilder!( context, null, onSave: (value) { diff --git a/lib/app/widgets/molecules/user_menu_popover.dart b/lib/app/widgets/molecules/user_menu_popover.dart index d06b183d..20cdacf1 100644 --- a/lib/app/widgets/molecules/user_menu_popover.dart +++ b/lib/app/widgets/molecules/user_menu_popover.dart @@ -3,22 +3,20 @@ import 'dart:ui'; import 'package:dungeon_paper/app/data/models/campaign.dart'; import 'package:dungeon_paper/app/data/models/character.dart'; -import 'package:dungeon_paper/app/data/services/auth_service.dart'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; -import 'package:dungeon_paper/app/data/services/user_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; +import 'package:dungeon_paper/app/data/services/user_provider.dart'; import 'package:dungeon_paper/app/routes/app_pages.dart'; import 'package:dungeon_paper/app/themes/themes.dart'; import 'package:dungeon_paper/app/widgets/atoms/theme_brightness_switch.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:provider/provider.dart'; import '../atoms/character_avatar.dart'; import '../atoms/user_avatar.dart'; -class UserMenuPopover extends GetView - with AuthServiceMixin, UserServiceMixin { - UserMenuPopover({super.key}); +class UserMenuPopover extends StatelessWidget with CharacterProviderMixin, UserProviderMixin { + const UserMenuPopover({super.key}); @override Widget build(BuildContext context) { @@ -33,7 +31,7 @@ class UserMenuPopover extends GetView dense: true, contentPadding: EdgeInsets.zero, child: GestureDetector( - onTap: () => Get.back(), + onTap: () => Navigator.of(context).pop(), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), child: Material( @@ -53,197 +51,215 @@ class UserMenuPopover extends GetView behavior: HitTestBehavior.opaque, // ignore: avoid_returning_null_for_void onTap: () => null, - child: Obx( - () => ListView( - padding: const EdgeInsets.symmetric( - horizontal: 16, vertical: 8), - shrinkWrap: true, - children: [ - // User details - ListTile( - onTap: userService.isLoggedIn - ? () => Get.toNamed(Routes.account) - : null, - visualDensity: VisualDensity.compact, - title: Text( - '${userService.current.displayName} (@${userService.current.username})', - style: textStyle, - ), - subtitle: Text( - userService.current.email.isNotEmpty - ? userService.current.email - : tr.auth.signup.notLoggedIn.label, - ), - trailing: userService.isGuest - ? Row( - mainAxisSize: MainAxisSize.min, - children: [ - ElevatedButton.icon( - onPressed: () { - Get.back(); - Get.toNamed(Routes.login); - }, - icon: const Icon(Icons.login), - label: Text(tr.auth.login.button), - ), - const SizedBox(width: 16), - UserAvatar(user: user), - ], - ) - : UserAvatar(user: user), + child: Consumer( + builder: (context, userProvider, _) { + return ListView( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, ), - const Divider(), - // Recent Characters - if (controller.charsByLastUsed.isNotEmpty) ...[ - const SizedBox(height: 8), - Text( - tr.user.recentCharacters, - style: - Theme.of(context).textTheme.bodySmall, + shrinkWrap: true, + children: [ + // User details + ListTile( + onTap: userProvider.isLoggedIn + ? () => Navigator.of(context) + .pushNamed(Routes.account) + : null, + visualDensity: VisualDensity.compact, + title: Text( + '${userProvider.current.displayName} (@${userProvider.current.username})', + style: textStyle, + ), + subtitle: Text( + userProvider.current.email.isNotEmpty + ? userProvider.current.email + : tr.auth.signup.notLoggedIn.label, + ), + trailing: userProvider.isGuest + ? Row( + mainAxisSize: MainAxisSize.min, + children: [ + ElevatedButton.icon( + onPressed: () { + Navigator.of(context).pop(); + Navigator.of(context) + .pushNamed(Routes.login); + }, + icon: const Icon(Icons.login), + label: + Text(tr.auth.login.button), + ), + const SizedBox(width: 16), + UserAvatar(user: user), + ], + ) + : UserAvatar(user: user), ), - Padding( - padding: const EdgeInsets.only(top: 8), - child: Wrap( - spacing: 4, - runSpacing: 4, - children: [ - for (final char in controller - .charsByLastUsed - .take(4)) - InkWell( - splashColor: - Theme.of(context).splashColor, - borderRadius: borderRadius, - onTap: () { - controller.setCurrent(char.key); - Get.back(); - }, - child: Padding( - padding: const EdgeInsets.all(4), - child: Column( - children: [ - CharacterAvatar.squircle( - character: char, - size: avatarSize), - const SizedBox(height: 4), - SizedBox( - width: 60, - child: Text( - char.displayName, - overflow: - TextOverflow.ellipsis, - textScaler: - const TextScaler - .linear(0.8), - textAlign: - TextAlign.center, - style: textStyle, + const Divider(), + // Recent Characters + if (charProvider + .charsByLastUsed.isNotEmpty) ...[ + const SizedBox(height: 8), + Text( + tr.user.recentCharacters, + style: + Theme.of(context).textTheme.bodySmall, + ), + Padding( + padding: const EdgeInsets.only(top: 8), + child: Wrap( + spacing: 4, + runSpacing: 4, + children: [ + for (final char in charProvider + .charsByLastUsed + .take(4)) + InkWell( + splashColor: + Theme.of(context).splashColor, + borderRadius: borderRadius, + onTap: () { + charProvider + .setCurrent(char.key); + Navigator.of(context).pop(); + }, + child: Padding( + padding: + const EdgeInsets.all(4), + child: Column( + children: [ + CharacterAvatar.squircle( + character: char, + size: avatarSize), + const SizedBox(height: 4), + SizedBox( + width: 60, + child: Text( + char.displayName, + overflow: TextOverflow + .ellipsis, + textScaler: + const TextScaler + .linear(0.8), + textAlign: + TextAlign.center, + style: textStyle, + ), ), - ), - ], + ], + ), ), ), - ), - ], + ], + ), ), - ), - const SizedBox(height: 8), - const Divider(), - ], - // All Characters - ListTile( - visualDensity: VisualDensity.compact, - dense: true, - title: Text(tr.generic.allEntities( - tr.entityPlural(tn(Character)))), - leading: const Icon(Icons.group), - onTap: () { - Get.back(); - Get.toNamed(Routes.characterList); - }, - ), - // Create Character - ListTile( - visualDensity: VisualDensity.compact, - title: Text(tr.generic - .createEntity(tr.entity(tn(Character)))), - leading: const Icon(Icons.person_add), - onTap: () { - Get.back(); - Get.toNamed(Routes.createCharacter); - }, - ), - const Divider(), - // My Library - ListTile( - visualDensity: VisualDensity.compact, - title: Text(tr.playbook.myLibrary), - leading: const Icon(Icons.local_library), - onTap: () { - Get.back(); - Get.toNamed(Routes.library); - }, - ), - if (user.isDm) - // My Campaigns + const SizedBox(height: 8), + const Divider(), + ], + // All Characters ListTile( visualDensity: VisualDensity.compact, - title: Text(tr.playbook.myCampaigns), - leading: Icon(Campaign.genericIcon), + dense: true, + title: Text(tr.generic.allEntities( + tr.entityPlural(tn(Character)))), + leading: const Icon(Icons.group), onTap: () { - Get.back(); - Get.toNamed(Routes.campaigns); + Navigator.of(context).pop(); + Navigator.of(context) + .pushNamed(Routes.characterList); }, ), - // Export/Import - ListTile( - visualDensity: VisualDensity.compact, - title: Text(tr.settings.importExport), - leading: const Icon(Icons.import_export), - onTap: () { - Get.back(); - Get.toNamed(Routes.importExport); - }, - ), - const Divider(), - ThemeBrightnessSwitch.listTile( - onChanged: (_) => Get.back(), - ), - // Settings - ListTile( - visualDensity: VisualDensity.compact, - title: Text(tr.settings.title), - leading: const Icon(Icons.settings), - onTap: () { - Get.back(); - Get.toNamed(Routes.settings); - }, - ), - // About - ListTile( - visualDensity: VisualDensity.compact, - title: Text(tr.about.title), - leading: const Icon(Icons.info), - onTap: () { - Get.back(); - Get.toNamed(Routes.about); - }, - ), - // Logout - if (!userService.isGuest) ...[ - const Divider(), + // Create Character ListTile( visualDensity: VisualDensity.compact, - title: Text(tr.auth.logout.button), - leading: const Icon(Icons.logout), + title: Text(tr.generic.createEntity( + tr.entity(tn(Character)))), + leading: const Icon(Icons.person_add), onTap: () { - Get.back(); - userService.logout(); + Navigator.of(context).pop(); + Navigator.of(context) + .pushNamed(Routes.createCharacter); }, ), + const Divider(), + // My Library + ListTile( + visualDensity: VisualDensity.compact, + title: Text(tr.playbook.myLibrary), + leading: const Icon(Icons.local_library), + onTap: () { + Navigator.of(context).pop(); + Navigator.of(context) + .pushNamed(Routes.library); + }, + ), + if (user.isDm) + // My Campaigns + ListTile( + visualDensity: VisualDensity.compact, + title: Text(tr.playbook.myCampaigns), + leading: Icon(Campaign.genericIcon), + onTap: () { + Navigator.of(context).pop(); + Navigator.of(context) + .pushNamed(Routes.campaigns); + }, + ), + // Export/Import + ListTile( + visualDensity: VisualDensity.compact, + title: Text(tr.settings.importExport), + leading: const Icon(Icons.import_export), + onTap: () { + Navigator.of(context).pop(); + Navigator.of(context) + .pushNamed(Routes.importExport); + }, + ), + const Divider(), + ThemeBrightnessSwitch.listTile( + onChanged: (_) => + Navigator.of(context).pop(), + ), + // Settings + ListTile( + visualDensity: VisualDensity.compact, + title: Text(tr.settings.title), + leading: const Icon(Icons.settings), + onTap: () { + Navigator.of(context).pop(); + Navigator.of(context) + .pushNamed(Routes.settings); + }, + ), + // About + ListTile( + visualDensity: VisualDensity.compact, + title: Text(tr.about.title), + leading: const Icon(Icons.info), + onTap: () { + Navigator.of(context).pop(); + Navigator.of(context) + .pushNamed(Routes.about); + }, + ), + // Logout + if (!userProvider.isGuest) ...[ + const Divider(), + ListTile( + visualDensity: VisualDensity.compact, + title: Text(tr.auth.logout.button), + leading: const Icon(Icons.logout), + onTap: () { + Navigator.of(context).pop(); + userProvider.logout(); + }, + ), + ], ], - ], - ), + ); + }, ), ), ), diff --git a/lib/app/widgets/views/roll_dice_view.dart b/lib/app/widgets/views/roll_dice_view.dart index 19035dd3..e5c08e12 100644 --- a/lib/app/widgets/views/roll_dice_view.dart +++ b/lib/app/widgets/views/roll_dice_view.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'dart:ui'; -import 'package:dungeon_paper/app/data/services/character_service.dart'; +import 'package:dungeon_paper/app/data/services/character_provider.dart'; import 'package:dungeon_paper/app/model_utils/dice_utils.dart'; import 'package:dungeon_paper/app/themes/colors.dart'; import 'package:dungeon_paper/app/widgets/atoms/advanced_floating_action_button.dart'; @@ -12,7 +12,6 @@ import 'package:dungeon_paper/core/utils/math_utils.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:dungeon_world_data/dungeon_world_data.dart' as dw; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; class RollDiceView extends StatefulWidget { final List dice; @@ -30,7 +29,7 @@ class RollDiceView extends StatefulWidget { } class _RollDiceViewState extends State - with TickerProviderStateMixin { + with TickerProviderStateMixin, CharacterProviderMixin { final diceSize = 56.0; final diceSpacing = 24.0; late AnimationStatus rollStatus; @@ -40,7 +39,6 @@ class _RollDiceViewState extends State late ScrollController scrollController; var results = []; - CharacterService get charService => Get.find(); int get totalResult => results.fold( 0, (previousValue, element) => previousValue + element.total); List get flat => dw.Dice.flatten(dice.value); @@ -88,7 +86,7 @@ class _RollDiceViewState extends State padding: const EdgeInsets.all(16).copyWith(top: 0), child: DiceListInput( controller: withoutModDice, - abilityScores: charService.current.abilityScores, + abilityScores: charProvider.current.abilityScores, guessFrom: const [], labelColor: Colors.white, ), @@ -281,7 +279,7 @@ class _RollDiceViewState extends State .map( (d) => d.needsModifier ? d.copyWithModifierValue( - charService.current.abilityScores + charProvider.current.abilityScores .getStat(d.modifierStat!) .modifier, ) diff --git a/lib/core/dw_icons.dart b/lib/core/dw_icons.dart index 87328636..164797ff 100644 --- a/lib/core/dw_icons.dart +++ b/lib/core/dw_icons.dart @@ -11,6 +11,7 @@ /// Author: Dave Gandy /// License: SIL (https://github.com/FortAwesome/Font-Awesome/blob/master/LICENSE.txt) /// Homepage: http://fortawesome.github.com/Font-Awesome/ +library; import 'package:flutter/material.dart'; diff --git a/lib/core/global_keys.dart b/lib/core/global_keys.dart new file mode 100644 index 00000000..15185794 --- /dev/null +++ b/lib/core/global_keys.dart @@ -0,0 +1,5 @@ +import 'package:flutter/material.dart'; + +final navigatorKey = GlobalKey(); +final appGlobalKey = GlobalKey(debugLabel: 'App'); +final animatableGlobalKey = GlobalKey(debugLabel: 'Animatable'); diff --git a/lib/core/http/api.dart b/lib/core/http/api.dart index 5924bf93..2059bbc6 100644 --- a/lib/core/http/api.dart +++ b/lib/core/http/api.dart @@ -36,22 +36,14 @@ class Api { class Response extends http.Response { Response( - String body, - int statusCode, { - http.BaseRequest? request, - Map headers = const {}, - bool isRedirect = false, - bool persistentConnection = true, - String? reasonPhrase, - }) : super( - body, - statusCode, - request: request, - headers: headers, - isRedirect: isRedirect, - persistentConnection: persistentConnection, - reasonPhrase: reasonPhrase, - ); + super.body, + super.statusCode, { + super.request, + super.headers, + super.isRedirect, + super.persistentConnection, + super.reasonPhrase, + }); factory Response.fromHttp(http.Response response) => Response( response.body, diff --git a/lib/core/platform_helper.dart b/lib/core/platform_helper.dart index e1def0f5..b5455d48 100644 --- a/lib/core/platform_helper.dart +++ b/lib/core/platform_helper.dart @@ -110,7 +110,7 @@ class PlatformHelper { T? tablet, DeviceType fallback = DeviceType.mobile, }) { - final _fallback = _getDeviceTypeFallback( + final fallback0 = _getDeviceTypeFallback( mobile: mobile, desktop: desktop, tablet: tablet, @@ -118,20 +118,20 @@ class PlatformHelper { ); if (isWeb) { if (MediaQuery.of(context).size.shortestSide < 800) { - return mobile ?? _fallback; + return mobile ?? fallback0; } if (MediaQuery.of(context).size.shortestSide < 1100) { - return tablet ?? _fallback; + return tablet ?? fallback0; } - return desktop ?? _fallback; + return desktop ?? fallback0; } if (isAndroid || isIOS || isFuchsia) { if (MediaQuery.of(context).size.shortestSide < 800) { - return mobile ?? _fallback; + return mobile ?? fallback0; } - return tablet ?? _fallback; + return tablet ?? fallback0; } - return desktop ?? _fallback; + return desktop ?? fallback0; } static T byOS({ @@ -143,7 +143,7 @@ class PlatformHelper { T? web, OS fallback = OS.android, }) { - final _fallback = _getOSFallback( + final fallback0 = _getOSFallback( android: android, iOS: iOS, windows: windows, @@ -153,24 +153,24 @@ class PlatformHelper { fallback: fallback, ); if (isAndroid) { - return android ?? _fallback; + return android ?? fallback0; } if (isIOS) { - return iOS ?? _fallback; + return iOS ?? fallback0; } if (isWindows) { - return windows ?? _fallback; + return windows ?? fallback0; } if (kIsWeb) { - return web ?? _fallback; + return web ?? fallback0; } if (isMacOS) { - return mac ?? _fallback; + return mac ?? fallback0; } if (isLinux) { - return linux ?? _fallback; + return linux ?? fallback0; } - return iOS ?? _fallback; + return iOS ?? fallback0; } static T byInteractionType( @@ -179,21 +179,21 @@ class PlatformHelper { T? mouse, InteractionType fallback = InteractionType.touch, }) { - final _fallback = _getInteractionTypeFallback( + final fallback0 = _getInteractionTypeFallback( touch: touch, mouse: mouse, fallback: fallback, ); if (isWeb) { if (MediaQuery.of(context).size.shortestSide < 800) { - return touch ?? _fallback; + return touch ?? fallback0; } - return mouse ?? _fallback; + return mouse ?? fallback0; } if (isAndroid || isIOS || isFuchsia) { - return touch ?? _fallback; + return touch ?? fallback0; } - return mouse ?? _fallback; + return mouse ?? fallback0; } static T _getInteractionTypeFallback({ diff --git a/lib/core/route_arguments.dart b/lib/core/route_arguments.dart new file mode 100644 index 00000000..de306d17 --- /dev/null +++ b/lib/core/route_arguments.dart @@ -0,0 +1,9 @@ +import 'package:flutter/material.dart'; + +T getArgs(BuildContext context, {bool nullOk = false}) { + final route = ModalRoute.of(context); + assert(nullOk || route != null); + final args = route!.settings.arguments; + assert(nullOk || args != null); + return args as T; +} diff --git a/lib/core/task_runner/task.dart b/lib/core/task_runner/task.dart index 1f98fa4a..7e2238a7 100644 --- a/lib/core/task_runner/task.dart +++ b/lib/core/task_runner/task.dart @@ -40,13 +40,11 @@ class TaskGroup extends Task { final List> tasks; TaskGroup({ - bool Function(ArgOptions)? condition, + super.condition, required this.tasks, - ArgOptions? options, + super.options, }) : super( - condition: condition, run: _runTasks(tasks, options, condition), - options: options, ); TaskGroup.staticArgs({ @@ -102,27 +100,23 @@ class TaskGroup extends Task { class DeviceTaskGroup extends TaskGroup { DeviceTaskGroup({ required Device device, - required List> tasks, + required super.tasks, bool Function(ArgOptions)? condition, - ArgOptions? options, + super.options, }) : super( condition: condition != null ? (o) => condition.call(o) != false && _isDeviceIncluded(o, device) : (o) => _isDeviceIncluded(o, device), - tasks: tasks, - options: options, ); DeviceTaskGroup.staticArgs({ required Device device, - required List> tasks, + required super.tasks, bool? condition, - ArgOptions? options, + super.options, }) : super( condition: (o) => condition != false && _isDeviceIncluded(o, device), - tasks: tasks, - options: options, ); static bool _isDeviceIncluded(ArgOptions o, Device device) => @@ -133,15 +127,13 @@ class ProcessTask extends Task { ProcessTask( FutureOr Function(ArgOptions) process, { FutureOr> Function(ArgOptions)? args, - bool Function(ArgOptions)? condition, - ArgOptions? options, + super.condition, + super.options, FutureOr Function(ArgOptions)? beforeAll, FutureOr Function(ArgOptions)? afterAll, FutureOr Function(ArgOptions, Exception, StackTrace)? onError, }) : super( - condition: condition, run: _runProcess(process, args, onError), - options: options, ); factory ProcessTask.staticArgs( @@ -168,19 +160,19 @@ class ProcessTask extends Task { FutureOr Function(ArgOptions, Exception, StackTrace)? onError, ) => (o) async { - final _process = await process(o); - final _args = await (args?.call(o) ?? []); - final _argsStr = - _args.map((a) => a.contains(' ') ? '"$a"' : a).join(' '); - print('\n\nRunning process: $_process $_argsStr\n\n'); + final process0 = await process(o); + final args0 = await (args?.call(o) ?? []); + final argsStr = + args0.map((a) => a.contains(' ') ? '"$a"' : a).join(' '); + print('\n\nRunning process: $process0 $argsStr\n\n'); try { - final result = await Process.run(_process, _args); + final result = await Process.run(process0, args0); stdout.write(result.stdout); stdout.write(result.stderr); final exitCode = result.exitCode; if (exitCode != 0) { final stack = StackTrace.current; - final e = ProcessException(_process, _args, + final e = ProcessException(process0, args0, 'Process exited with error code: $exitCode', exitCode); _handleError(o, e, stack, onError); } @@ -213,22 +205,18 @@ class ProcessTask extends Task { class LogTask extends Task { LogTask( FutureOr Function(ArgOptions) message, { - bool Function(ArgOptions)? condition, - ArgOptions? options, + super.condition, + super.options, }) : super( - condition: condition, run: _log(message), - options: options, ); LogTask.staticArgs( String message, { - bool Function(ArgOptions)? condition, - ArgOptions? options, + super.condition, + super.options, }) : super( - condition: condition, run: _log((o) => message), - options: options, ); FutureOr log(String message) => _log((o) => message); diff --git a/lib/core/utils/date_utils.dart b/lib/core/utils/date_utils.dart index 5348473c..fca8eba7 100644 --- a/lib/core/utils/date_utils.dart +++ b/lib/core/utils/date_utils.dart @@ -8,25 +8,25 @@ int Function(T? date1, T? date2) createSortByDate({ DateTime? Function(T? object)? parse, }) { int orderMultiplier = order == SortOrder.asc ? 1 : -1; - DateTime? _parse(T? x) => parse != null ? parse(x) : x as DateTime; + DateTime? parse0(T? x) => parse != null ? parse(x) : x as DateTime; - return (_a, _b) { - final DateTime? a = _parse(_a); - final DateTime? b = _parse(_b); + return (a, b) { + final DateTime? parsedA = parse0(a); + final DateTime? parsedB = parse0(b); - if (a == null && b == null) { + if (parsedA == null && parsedB == null) { return 0; } - if (a == null) { + if (parsedA == null) { return -1 * orderMultiplier; } - if (b == null) { + if (parsedB == null) { return 1 * orderMultiplier; } - return a.compareTo(b) * orderMultiplier; + return parsedA.compareTo(parsedB) * orderMultiplier; }; } diff --git a/lib/core/utils/filter_sort.dart b/lib/core/utils/filter_sort.dart index b154c3e4..e1903659 100644 --- a/lib/core/utils/filter_sort.dart +++ b/lib/core/utils/filter_sort.dart @@ -9,11 +9,11 @@ int Function(T1? date1, T1? date2) Function({ return ({order = SortOrder.asc, parse}) { int orderMultiplier = order == SortOrder.asc ? 1 : -1; - T2? _parse(T1? x) => parse != null ? parse(x) : x as T2?; + T2? parseFn(T1? x) => parse != null ? parse(x) : x as T2?; return (_a, _b) { - final T2 a = _postParse(_parse(_a)); - final T2 b = _postParse(_parse(_b)); + final T2 a = _postParse(parseFn(_a)); + final T2 b = _postParse(parseFn(_b)); return a.compareTo(b) * orderMultiplier; }; diff --git a/lib/core/utils/list_utils.dart b/lib/core/utils/list_utils.dart index 6f731c71..ab51fc42 100644 --- a/lib/core/utils/list_utils.dart +++ b/lib/core/utils/list_utils.dart @@ -50,6 +50,15 @@ extension IterableUtils on Iterable { } return out; } + + T? firstWhereOrNull(bool Function(T element) test) { + for (final element in this) { + if (test(element)) { + return element; + } + } + return null; + } } T sample(Iterable list) { @@ -153,7 +162,7 @@ List upsertByKey(List list, Iterable items, .map((x) => keys.contains(keyGetter(x)) ? items.firstWhere((y) => keyGetter(x) == keyGetter(y)) : x) - .toList(), + , ...items.where((x) { return !existingKeys.contains(keyGetter(x)); }) diff --git a/lib/core/utils/markdown_highlight.dart b/lib/core/utils/markdown_highlight.dart index 583ead1a..f0079aec 100644 --- a/lib/core/utils/markdown_highlight.dart +++ b/lib/core/utils/markdown_highlight.dart @@ -124,7 +124,7 @@ class HighlightText extends StatelessWidget { @override Widget build(BuildContext context) { - final _text = highlight(text, highlightWords).split('=='); + final text = highlight(this.text, highlightWords).split('=='); final normalStyle = HighlightBuilder.getNormalStyle(context, normalTextStyle); final hlStyle = HighlightBuilder.getHighlightStyle( @@ -144,7 +144,7 @@ class HighlightText extends StatelessWidget { softWrap: softWrap, text: TextSpan( children: [ - for (final word in _text) + for (final word in text) if (words.contains(word.toLowerCase())) TextSpan( text: word, diff --git a/lib/core/utils/math_utils.dart b/lib/core/utils/math_utils.dart index a3f4b99d..b0e29e5e 100644 --- a/lib/core/utils/math_utils.dart +++ b/lib/core/utils/math_utils.dart @@ -8,8 +8,8 @@ double degToRad(double deg) => deg * (math.pi / 180.0); Iterable range(int start, [int? end]) { final min = end != null ? start : 0; - final num _max = end ?? start; - return Iterable.generate((_max - min).toInt(), (i) => min + i); + final num max = end ?? start; + return Iterable.generate((max - min).toInt(), (i) => min + i); } T avg(Iterable values) => diff --git a/lib/core/utils/string_validator.dart b/lib/core/utils/string_validator.dart index 02501a50..5de385d8 100644 --- a/lib/core/utils/string_validator.dart +++ b/lib/core/utils/string_validator.dart @@ -1,5 +1,5 @@ +import 'package:dungeon_paper/core/utils/list_utils.dart'; import 'package:dungeon_paper/i18n.dart'; -import 'package:get/get.dart'; abstract class Validation { Validation(this.message); diff --git a/lib/core/utils/upload_utils.dart b/lib/core/utils/upload_utils.dart index a007f977..1b3c2106 100644 --- a/lib/core/utils/upload_utils.dart +++ b/lib/core/utils/upload_utils.dart @@ -1,9 +1,9 @@ -import 'package:dungeon_paper/app/data/services/user_service.dart'; +import 'package:dungeon_paper/app/data/services/user_provider.dart'; +import 'package:dungeon_paper/app/widgets/atoms/custom_snack_bar.dart'; import 'package:dungeon_paper/i18n.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:flutter/material.dart'; import 'package:flutter_file_dialog/flutter_file_dialog.dart'; -import 'package:get/get.dart'; import 'package:image_cropper/image_cropper.dart'; import 'package:path/path.dart' as path; @@ -72,32 +72,40 @@ Future _pickAndCrop( return cropped; } -Future _uploadPhoto(CroppedFile file, String uploadPath) async { - final UserService userService = Get.find(); - assert(userService.current.isLoggedIn); - final ext = path.extension(file.path); - if (!uploadPath.startsWith('/')) { - uploadPath = '/$uploadPath'; - } - final ref = FirebaseStorage.instance - .ref(userService.current.fileStoragePath! + uploadPath + ext); - await ref.putData(await file.readAsBytes()); - return ref; +Future Function(CroppedFile file, String uploadPath) + _uploadPhotoFactory(BuildContext context) { + return (file, uploadPath) async { + final userProvider = UserProvider.of(context); + assert(userProvider.current.isLoggedIn); + + final ext = path.extension(file.path); + if (!uploadPath.startsWith('/')) { + uploadPath = '/$uploadPath'; + } + final ref = FirebaseStorage.instance + .ref(userProvider.current.fileStoragePath! + uploadPath + ext); + await ref.putData(await file.readAsBytes()); + return ref; + }; } Future cropAndUploadPhoto( - BuildContext context, UploadSettings settings) async { + BuildContext context, + UploadSettings settings, +) async { CroppedFile? file; + final uploadPhoto = _uploadPhotoFactory(context); + final snackBar = CustomSnackBar.deferred(context); try { file = await _pickAndCrop(context); if (file == null) { - Get.rawSnackbar(message: tr.errors.userOperationCanceled); + snackBar.show(content: tr.errors.userOperationCanceled); settings.onCancel?.call(); return null; } settings.onUploadFile?.call(file); } catch (e) { - Get.rawSnackbar(message: tr.errors.uploadError); + snackBar.show(content: tr.errors.uploadError); settings.onError?.call(e); return null; } @@ -105,12 +113,12 @@ Future cropAndUploadPhoto( Reference fileRef; String downloadURL; try { - fileRef = await _uploadPhoto(file, settings.uploadPath); + fileRef = await uploadPhoto(file, settings.uploadPath); downloadURL = await fileRef.getDownloadURL(); settings.onSuccess?.call(downloadURL); } catch (e) { - Get.rawSnackbar( + snackBar.show( message: 'Error while uploading photo. Try again later, or contact support using the "About" page.', ); diff --git a/lib/i18n/messages.i18n.dart b/lib/i18n/messages.i18n.dart index f2d98dba..acda6ea6 100644 --- a/lib/i18n/messages.i18n.dart +++ b/lib/i18n/messages.i18n.dart @@ -735,7 +735,7 @@ class SettingsMessages { /// ```dart /// "Switch to ${mode} Mode" /// ``` - String _switchMode(String mode) => """Switch to ${mode} Mode"""; + String _switchMode(String mode) => """Switch to $mode Mode"""; /// ```dart /// "${_switchMode('Dark')}" diff --git a/lib/main.dart b/lib/main.dart index a748a5a1..1f6a9ca5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,10 @@ +import 'package:dungeon_paper/app/data/services/auth_provider.dart'; +import 'package:dungeon_paper/app/data/services/library_provider.dart'; +import 'package:dungeon_paper/app/data/services/loading_provider.dart'; +import 'package:dungeon_paper/app/data/services/repository_provider.dart'; +import 'package:dungeon_paper/app/data/services/user_provider.dart'; import 'package:dungeon_paper/app/routes/app_pages.dart'; -import 'package:dungeon_paper/app/data/services/services.dart'; +import 'package:dungeon_paper/core/global_keys.dart'; import 'package:dungeon_paper/core/multi_platform_scroll_behavior.dart'; import 'package:dungeon_paper/core/pref_keys.dart'; import 'package:dungeon_paper/core/remote_config.dart'; @@ -11,10 +16,11 @@ import 'package:dynamic_themes/dynamic_themes.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; -import 'package:get/route_manager.dart'; import 'package:firebase_core/firebase_core.dart'; +import 'package:provider/provider.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; +import 'app/data/services/character_provider.dart'; import 'app/themes/themes.dart'; import 'firebase_options.dart'; @@ -50,12 +56,29 @@ Future _init() async { await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); await loadSharedPrefs(); await initRemoteConfig(); - await initServices(); } -class DungeonPaperApp extends StatelessWidget { +final _loadingProvider = LoadingProvider(); +final _authProvider = AuthProvider(); +final _characterProvider = CharacterProvider(); +final _userProvider = UserProvider(); +final _repositoryProvider = RepositoryProvider(); +final _libraryProvider = LibraryProvider(); + +class DungeonPaperApp extends StatefulWidget { const DungeonPaperApp({super.key}); + @override + State createState() => _DungeonPaperAppState(); +} + +class _DungeonPaperAppState extends State { + @override + void initState() { + super.initState(); + _userProvider.loadBuiltInRepo(); + } + @override Widget build(BuildContext context) { final platformBrightness = getCurrentPlatformBrightness(); @@ -63,18 +86,29 @@ class DungeonPaperApp extends StatelessWidget { ? AppThemes.parchment : AppThemes.dark; - return DynamicTheme( - themeCollection: themeCollection, - defaultThemeId: prefs.getInt(PrefKeys.selectedThemeId) ?? defaultTheme, - builder: (context, value) { - return GetMaterialApp( + return MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: _loadingProvider), + ChangeNotifierProvider.value(value: _authProvider), + ChangeNotifierProvider.value(value: _characterProvider), + ChangeNotifierProvider.value(value: _userProvider), + ChangeNotifierProvider.value(value: _repositoryProvider), + ChangeNotifierProvider.value(value: _libraryProvider), + ], + child: DynamicTheme( + themeCollection: themeCollection, + defaultThemeId: prefs.getInt(PrefKeys.selectedThemeId) ?? defaultTheme, + builder: (context, value) => MaterialApp( scrollBehavior: MultiPlatformScrollBehavior(), title: tr.app.name, theme: value, + key: appGlobalKey, + // navigatorKey: navigatorKey, + onGenerateRoute: AppPages.onGenerateRoute, initialRoute: AppPages.initial, - getPages: AppPages.routes, - ); - }, + // routes: AppPages.routes, + ), + ), ); } } diff --git a/pubspec.lock b/pubspec.lock index f1b8d601..f1589cf8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -552,14 +552,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.0" - get: - dependency: "direct main" - description: - name: get - sha256: e4e7335ede17452b391ed3b2ede016545706c01a02292a6c97619705e7d2a85e - url: "https://pub.dev" - source: hosted - version: "4.6.6" glob: dependency: transitive description: @@ -762,7 +754,7 @@ packages: source: hosted version: "1.2.0" markdown: - dependency: transitive + dependency: "direct main" description: name: markdown sha256: acf35edccc0463a9d7384e437c015a3535772e09714cf60e07eeef3a15870dcd @@ -801,6 +793,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" node_preamble: dependency: transitive description: @@ -961,8 +961,16 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.9" + provider: + dependency: "direct main" + description: + name: provider + sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" + url: "https://pub.dev" + source: hosted + version: "6.1.1" pub_semver: - dependency: "direct dev" + dependency: "direct main" description: name: pub_semver sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" diff --git a/pubspec.yaml b/pubspec.yaml index a73b33fc..3451f9ca 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: dungeon_paper description: A Dungeon World character sheet app written with Flutter. -version: 2.0.4+299 +version: 2.0.5+300 # The above field defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 # followed by an optional build number separated by a +. @@ -46,7 +46,6 @@ dependencies: flutter_markdown: ^0.6.10+2 flutter_native_splash: ^2.2.5 flutter_svg: ^2.0.9 - get: ^4.6.1 google_sign_in: ^5.3.3 google_sign_in_ios: ^5.7.0 http: ^0.13.4 @@ -75,6 +74,9 @@ dependencies: uuid: ^4.2.2 wakelock: ^0.6.2 wheel_spinner: ^0.7.2 + provider: ^6.1.1 + pub_semver: ^2.1.4 + markdown: ^7.1.1 # wheel_spinner: # path: ../wheel_spinner @@ -85,7 +87,6 @@ dev_dependencies: flutter_lints: ^3.0.1 flutter_test: sdk: flutter - pub_semver: ^2.1.0 test: ^1.19.5 # For information on the generic Dart part of this file, see the