From 5085a2d4b57d04b54935ee916d47195111bb9b84 Mon Sep 17 00:00:00 2001 From: Aaron Fischer Date: Thu, 8 Aug 2019 20:15:06 +0200 Subject: [PATCH] Make all data persistent --- mobile-app/android/app/build.gradle | 3 +- .../android/app/src/debug/AndroidManifest.xml | 2 +- .../android/app/src/main/AndroidManifest.xml | 2 +- .../aaronfischer}/toolheim/MainActivity.java | 2 +- .../app/src/profile/AndroidManifest.xml | 4 +- .../ios/Runner.xcodeproj/project.pbxproj | 6 +- mobile-app/lib/data/github_adapter.dart | 52 ++++++--- mobile-app/lib/data/warband_roster.dart | 100 ++++++++++++++---- mobile-app/lib/data/warband_roster.g.dart | 44 +++++++- mobile-app/lib/main.dart | 1 + mobile-app/lib/screens/settings_screen.dart | 14 ++- .../lib/widgets/warband_drawer_widget.dart | 1 + 12 files changed, 181 insertions(+), 50 deletions(-) rename mobile-app/android/app/src/main/java/{com/example => net/aaronfischer}/toolheim/MainActivity.java (90%) diff --git a/mobile-app/android/app/build.gradle b/mobile-app/android/app/build.gradle index 84be705..b64058b 100644 --- a/mobile-app/android/app/build.gradle +++ b/mobile-app/android/app/build.gradle @@ -32,8 +32,7 @@ android { } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.toolheim" + applicationId "net.aaronfischer.toolheim" minSdkVersion 16 targetSdkVersion 28 versionCode flutterVersionCode.toInteger() diff --git a/mobile-app/android/app/src/debug/AndroidManifest.xml b/mobile-app/android/app/src/debug/AndroidManifest.xml index b71dd91..c8101e9 100644 --- a/mobile-app/android/app/src/debug/AndroidManifest.xml +++ b/mobile-app/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="net.aaronfischer.toolheim"> diff --git a/mobile-app/android/app/src/main/AndroidManifest.xml b/mobile-app/android/app/src/main/AndroidManifest.xml index 75b49df..4fab12a 100644 --- a/mobile-app/android/app/src/main/AndroidManifest.xml +++ b/mobile-app/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="net.aaronfischer.toolheim"> + + diff --git a/mobile-app/ios/Runner.xcodeproj/project.pbxproj b/mobile-app/ios/Runner.xcodeproj/project.pbxproj index b2d222a..407a714 100644 --- a/mobile-app/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile-app/ios/Runner.xcodeproj/project.pbxproj @@ -323,7 +323,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.toolheim; + PRODUCT_BUNDLE_IDENTIFIER = net.aaronfischer.toolheim; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; }; @@ -448,7 +448,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.toolheim; + PRODUCT_BUNDLE_IDENTIFIER = net.aaronfischer.toolheim; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; }; @@ -471,7 +471,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.toolheim; + PRODUCT_BUNDLE_IDENTIFIER = net.aaronfischer.toolheim; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; }; diff --git a/mobile-app/lib/data/github_adapter.dart b/mobile-app/lib/data/github_adapter.dart index dbbea8d..7f05d2d 100644 --- a/mobile-app/lib/data/github_adapter.dart +++ b/mobile-app/lib/data/github_adapter.dart @@ -45,6 +45,10 @@ class GitHubAdapter extends ChangeNotifier { notifyListeners(); } + GitHubAdapter() { + load(); + } + /// Search for warband files in the GitHub repository /// /// This method will search for matching files and check their content in a @@ -91,7 +95,6 @@ class GitHubAdapter extends ChangeNotifier { } } - //storage.clear(); _syncErrors.clear(); _rosters.clear(); _syncInProgress = true; @@ -110,14 +113,6 @@ class GitHubAdapter extends ChangeNotifier { _rosters.add(roster); notifyListeners(); } - - //https://github.com/lesnitsky/flutter_localstorage/blob/master/example/lib/main.dart - // FIXME: store it correctly - //storage.setItem(player['player'] + '-warband-yaml', response.body); - //storage.setItem( - // player['player'] + '-current-version', roster.currentVersion); - //storage.setItem( - // player['player'] + '-last-sync-version', roster.lastSyncVersion); } // Sort by CP @@ -125,8 +120,8 @@ class GitHubAdapter extends ChangeNotifier { _lastSync = DateTime.now(); _syncInProgress = false; - //storage.setItem('lastSync', _lastSync.toIso8601String()); + await save(); notifyListeners(); } @@ -167,13 +162,23 @@ class GitHubAdapter extends ChangeNotifier { _lastSync = DateTime.now(); _syncInProgress = false; + await save(); notifyListeners(); } String getPlayerNameFromFilePath(String filePath) { // We try to get the name of the player from the name of the folder // in which the file resists - List pathParts = filePath.substring(path.length + 1).split('/'); + String cleanedPath = path; + if (path.startsWith('/')) { + cleanedPath = cleanedPath.substring(1); + } + + if (cleanedPath.endsWith('/')) { + cleanedPath = cleanedPath.substring(0, cleanedPath.length - 1); + } + List pathParts = + filePath.substring(cleanedPath.length + 1).split('/'); String playerName = 'Lonely Recluse'; if (pathParts.length >= 2) { @@ -248,7 +253,28 @@ class GitHubAdapter extends ChangeNotifier { return null; } - void readWarband(WarbandRoster roster, String yamlContent) { - // TODO: Read the warband from the shared preferences + Future save() async { + await storage.clear(); + await storage.setItem('lastSync', _lastSync.toIso8601String()); + await storage.setItem('rosters', _rosters); + await storage.setItem('activeRosterFilePath', _activeRosterFilePath); + } + + Future load() async { + await storage.ready; + + String lastSync = storage.getItem('lastSync'); + if (lastSync != null) { + _lastSync = DateTime.parse(lastSync); + } + + _activeRosterFilePath = storage.getItem('activeRosterFilePath'); + + List rosters = storage.getItem('rosters'); + if (rosters != null) { + rosters.forEach((warband) { + _rosters.add(WarbandRoster.fromJson(warband)); + }); + } } } diff --git a/mobile-app/lib/data/warband_roster.dart b/mobile-app/lib/data/warband_roster.dart index b6e25b7..ea68d4e 100644 --- a/mobile-app/lib/data/warband_roster.dart +++ b/mobile-app/lib/data/warband_roster.dart @@ -15,6 +15,10 @@ abstract class Unit { return list.split(new RegExp(r" *, *")); } + static String _joinListToJson(List list) { + return list.join(', '); + } + static Stats _statsFromJson(String stats) { RegExp re = new RegExp( r"\s*M([0-9]+[dD]*[6]*)\s*,\s*WS([0-9]+)\s*,\s*BS([0-9]+)\s*,\s*S([0-9]+)\s*,\s*T([0-9]+)\s*,\s*W([0-9]+)\s*,\s*I([0-9]+)\s*,\s*A([0-9]+)\s*,\s*Ld([0-9]+)\s*,\s*Sv([0-9\-]+)\s*"); @@ -38,11 +42,19 @@ abstract class Unit { int.tryParse(matches.group(9)) ?? 0, int.tryParse(matches.group(10)) ?? 0); } + + static String _statsToJson(Stats stats) { + return 'M${stats.movement}, WS${stats.weaponSkill}, BS${stats.ballisticSkill}, S${stats.strength}, T${stats.toughtness}, W${stats.wounds}, I${stats.initiative}, A${stats.attacks}, Ld${stats.leadership}, Sv${stats.save}'; + } } -@JsonSerializable(nullable: true, anyMap: true, createToJson: false) +@JsonSerializable(nullable: true, anyMap: true) class HenchmenGroup extends Unit { - @JsonKey(name: 'group', fromJson: _henchmenHeaderFromJson, required: true) + @JsonKey( + name: 'group', + fromJson: _henchmenHeaderFromJson, + toJson: _henchmengroupHeaderToJson, + required: true) final HashMap header; @JsonKey(ignore: true) String name; @@ -53,13 +65,14 @@ class HenchmenGroup extends Unit { @JsonKey(ignore: true) int experience; - @JsonKey(fromJson: Unit._statsFromJson, required: true) + @JsonKey( + fromJson: Unit._statsFromJson, toJson: Unit._statsToJson, required: true) final Stats stats; - @JsonKey(fromJson: Unit._splitListFromJson) + @JsonKey(fromJson: Unit._splitListFromJson, toJson: Unit._joinListToJson) final List weapons; - @JsonKey(fromJson: Unit._splitListFromJson) + @JsonKey(fromJson: Unit._splitListFromJson, toJson: Unit._joinListToJson) final List armour; HenchmenGroup(this.header, this.stats, this.weapons, this.armour) { @@ -91,12 +104,22 @@ class HenchmenGroup extends Unit { return h; } + static String _henchmengroupHeaderToJson( + HashMap henchmenGroup) { + return '${henchmenGroup['name']} (${henchmenGroup['number']} ${henchmenGroup['type']}) [${henchmenGroup['experience']}XP]'; + } + factory HenchmenGroup.fromJson(yaml) => _$HenchmenGroupFromJson(yaml); + Map toJson() => _$HenchmenGroupToJson(this); } -@JsonSerializable(nullable: true, anyMap: true, createToJson: false) +@JsonSerializable(nullable: true, anyMap: true) class Hero extends Unit { - @JsonKey(name: 'hero', fromJson: _heroHeaderFromJson, required: true) + @JsonKey( + name: 'hero', + fromJson: _heroHeaderFromJson, + toJson: _heroHeaderToJson, + required: true) final HashMap header; @JsonKey(ignore: true) String name; @@ -105,19 +128,20 @@ class Hero extends Unit { @JsonKey(ignore: true) int experience; - @JsonKey(fromJson: Unit._statsFromJson, required: true) + @JsonKey( + fromJson: Unit._statsFromJson, toJson: Unit._statsToJson, required: true) final Stats stats; - @JsonKey(fromJson: Unit._splitListFromJson) + @JsonKey(fromJson: Unit._splitListFromJson, toJson: Unit._joinListToJson) final List skilllists; - @JsonKey(fromJson: Unit._splitListFromJson) + @JsonKey(fromJson: Unit._splitListFromJson, toJson: Unit._joinListToJson) final List weapons; - @JsonKey(fromJson: Unit._splitListFromJson) + @JsonKey(fromJson: Unit._splitListFromJson, toJson: Unit._joinListToJson) final List armour; - @JsonKey(fromJson: Unit._splitListFromJson) + @JsonKey(fromJson: Unit._splitListFromJson, toJson: Unit._joinListToJson) final List rules; @JsonKey(defaultValue: 0) @@ -134,6 +158,7 @@ class Hero extends Unit { } factory Hero.fromJson(yaml) => _$HeroFromJson(yaml); + Map toJson() => _$HeroToJson(this); static HashMap _heroHeaderFromJson(String header) { HashMap h = new HashMap(); @@ -153,6 +178,10 @@ class Hero extends Unit { return h; } + + static String _heroHeaderToJson(HashMap heroHeader) { + return '${heroHeader['name']} (${heroHeader['type']}) [${heroHeader['experience']}XP]'; + } } @immutable @@ -189,13 +218,30 @@ class Version { String message; Version(this.gitHash, this.date, this.author, this.message); + + factory Version.fromJson(yaml) { + return Version( + yaml['gitHash'], yaml['date'], yaml['author'], yaml['message']); + } + Map toJson() { + return { + 'gitHash': gitHash, + 'date': date, + 'author': author, + 'message': message + }; + } } -@JsonSerializable(nullable: true, anyMap: true, createToJson: false) +@JsonSerializable(nullable: true, anyMap: true) class WarbandRoster { /// Store the complete string of name and race. This will split up into the /// fields name and race. - @JsonKey(name: 'warband', fromJson: _warbandNameAndRace, required: true) + @JsonKey( + name: 'warband', + fromJson: _warbandNameAndRaceFromJson, + toJson: _warbandNameAndRaceToJson, + required: true) final HashMap nameAndRace; @JsonKey(ignore: true) String name; @@ -234,17 +280,17 @@ class WarbandRoster { /// The players name is not defined in the yml file. This will be added later /// from the GitHubAdapter. Same goes for the lastSyncVersion and currentVersion. - @JsonKey(ignore: true) - String playerName = 'Lonely Recluse'; + @JsonKey(required: false, defaultValue: 'Lonely Recluse') + String playerName; - @JsonKey(ignore: true) + @JsonKey(required: false) String filePath; - @JsonKey(ignore: true) + @JsonKey(required: false) Version version; - @JsonKey(ignore: true) - bool unseen = true; + @JsonKey(required: false, defaultValue: true) + bool unseen; WarbandRoster( this.nameAndRace, @@ -256,7 +302,11 @@ class WarbandRoster { this.equipment, this.achievments, this.heros, - this.henchmenGroups) { + this.henchmenGroups, + this.playerName, + this.filePath, + this.version, + this.unseen) { this.name = this.nameAndRace['name']; this.race = this.nameAndRace['race']; } @@ -266,7 +316,8 @@ class WarbandRoster { return 1337; } - static HashMap _warbandNameAndRace(String nameAndRace) { + static HashMap _warbandNameAndRaceFromJson( + String nameAndRace) { HashMap nr = new HashMap(); RegExp re = new RegExp(r"(.*) \((.*)\)"); @@ -284,5 +335,10 @@ class WarbandRoster { return nr; } + static String _warbandNameAndRaceToJson(HashMap nameAndRace) { + return '${nameAndRace['name']} (${nameAndRace['race']})'; + } + factory WarbandRoster.fromJson(yaml) => _$WarbandRosterFromJson(yaml); + Map toJson() => _$WarbandRosterToJson(this); } diff --git a/mobile-app/lib/data/warband_roster.g.dart b/mobile-app/lib/data/warband_roster.g.dart index 5ad1616..c0d08d7 100644 --- a/mobile-app/lib/data/warband_roster.g.dart +++ b/mobile-app/lib/data/warband_roster.g.dart @@ -16,6 +16,14 @@ HenchmenGroup _$HenchmenGroupFromJson(Map json) { ); } +Map _$HenchmenGroupToJson(HenchmenGroup instance) => + { + 'group': HenchmenGroup._henchmengroupHeaderToJson(instance.header), + 'stats': Unit._statsToJson(instance.stats), + 'weapons': Unit._joinListToJson(instance.weapons), + 'armour': Unit._joinListToJson(instance.armour), + }; + Hero _$HeroFromJson(Map json) { $checkKeys(json, requiredKeys: const ['hero', 'stats']); return Hero( @@ -30,6 +38,17 @@ Hero _$HeroFromJson(Map json) { ); } +Map _$HeroToJson(Hero instance) => { + 'hero': Hero._heroHeaderToJson(instance.header), + 'stats': Unit._statsToJson(instance.stats), + 'skilllists': Unit._joinListToJson(instance.skilllists), + 'weapons': Unit._joinListToJson(instance.weapons), + 'armour': Unit._joinListToJson(instance.armour), + 'rules': Unit._joinListToJson(instance.rules), + 'warbandaddition': instance.warbandaddition, + 'hiredsword': instance.hiredSword, + }; + WarbandRoster _$WarbandRosterFromJson(Map json) { $checkKeys(json, requiredKeys: const [ 'warband', @@ -39,7 +58,7 @@ WarbandRoster _$WarbandRosterFromJson(Map json) { 'henchmen' ]); return WarbandRoster( - WarbandRoster._warbandNameAndRace(json['warband'] as String), + WarbandRoster._warbandNameAndRaceFromJson(json['warband'] as String), json['campaign'] as int ?? 0, json['objective'] as String, json['alignment'] as String, @@ -53,5 +72,28 @@ WarbandRoster _$WarbandRosterFromJson(Map json) { (json['henchmen'] as List) ?.map((e) => e == null ? null : HenchmenGroup.fromJson(e)) ?.toList(), + json['playerName'] as String ?? 'Lonely Recluse', + json['filePath'] as String, + json['version'] == null ? null : Version.fromJson(json['version']), + json['unseen'] as bool ?? true, )..active = json['active'] as bool ?? true; } + +Map _$WarbandRosterToJson(WarbandRoster instance) => + { + 'warband': WarbandRoster._warbandNameAndRaceToJson(instance.nameAndRace), + 'active': instance.active, + 'campaign': instance.campaignPoints, + 'objective': instance.objective, + 'alignment': instance.alignment, + 'achievments': instance.achievments, + 'gc': instance.gc, + 'shards': instance.shards, + 'equipment': instance.equipment, + 'heros': instance.heros, + 'henchmen': instance.henchmenGroups, + 'playerName': instance.playerName, + 'filePath': instance.filePath, + 'version': instance.version, + 'unseen': instance.unseen, + }; diff --git a/mobile-app/lib/main.dart b/mobile-app/lib/main.dart index 097d42a..f1579a6 100644 --- a/mobile-app/lib/main.dart +++ b/mobile-app/lib/main.dart @@ -17,6 +17,7 @@ class Toolheim extends StatelessWidget { return ChangeNotifierProvider( builder: (context) => GitHubAdapter(), child: MaterialApp( + debugShowCheckedModeBanner: false, title: 'Toolheim', theme: ThemeData( primarySwatch: Colors.brown, diff --git a/mobile-app/lib/screens/settings_screen.dart b/mobile-app/lib/screens/settings_screen.dart index a5ec27a..d8c6887 100644 --- a/mobile-app/lib/screens/settings_screen.dart +++ b/mobile-app/lib/screens/settings_screen.dart @@ -8,16 +8,23 @@ class SettingsScreen extends StatelessWidget { Widget build(BuildContext context) { GitHubAdapter github = Provider.of(context); + //String _repository = 'Labernator/Mordheim'; + //String _path = 'Mordheim-BorderTownBurning/Warband Rosters'; + return Scaffold( appBar: AppBar(title: Text('Settings')), body: PreferencePage([ PreferenceTitle('GitHub'), PreferenceText( 'Provide a valid GitHub repository with username and project (username/repository-name). This repository must contain warband roster files in subfolders. See the sample project for a kickstart.'), - TextFieldPreference('Repository', 'repository'), + TextFieldPreference( + 'Repository', + 'repository', + defaultVal: 'f0086/toolheim-example', + ), PreferenceText( 'If your warband folders are placed in a subfolder, you can specify it here.'), - TextFieldPreference('Path', 'path', defaultVal: '/'), + TextFieldPreference('Path', 'path', defaultVal: '/players'), PreferenceTitle('Search for Warbands'), PreferenceText( 'Search the given GitHub repository for valid Warband files (ending with .warband.yml). This step can be done at any time.'), @@ -34,9 +41,6 @@ class SettingsScreen extends StatelessWidget { child: Text('Start search', style: TextStyle(color: Colors.blue))), ])); - - //String _repository = 'Labernator/Mordheim'; - //String _path = 'Mordheim-BorderTownBurning/Warband Rosters'; } static Widget buildSyncErrors(BuildContext context) { diff --git a/mobile-app/lib/widgets/warband_drawer_widget.dart b/mobile-app/lib/widgets/warband_drawer_widget.dart index 60c2cba..68a82e2 100644 --- a/mobile-app/lib/widgets/warband_drawer_widget.dart +++ b/mobile-app/lib/widgets/warband_drawer_widget.dart @@ -89,6 +89,7 @@ class WarbandDrawerWidget extends StatelessWidget { onTap: () { roster.unseen = false; github.activeRoster = roster; + github.save(); Navigator.of(context).pop(); }, title: Text(roster.name + ' (' + roster.playerName + ')',