Make all data persistent
This commit is contained in:
parent
9369c5012f
commit
5085a2d4b5
12 changed files with 181 additions and 50 deletions
|
@ -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()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.toolheim">
|
||||
package="net.aaronfischer.toolheim">
|
||||
<!-- Flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.toolheim">
|
||||
package="net.aaronfischer.toolheim">
|
||||
|
||||
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
|
||||
calls FlutterMain.startInitialization(this); in its onCreate method.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package com.example.toolheim;
|
||||
package net.aaronfischer.toolheim;
|
||||
|
||||
import android.os.Bundle;
|
||||
import io.flutter.app.FlutterActivity;
|
|
@ -1,7 +1,9 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.toolheim">
|
||||
package="net.aaronfischer.toolheim">
|
||||
<!-- Flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
</manifest>
|
||||
|
|
|
@ -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";
|
||||
};
|
||||
|
|
|
@ -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<String> 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<String> 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<void> save() async {
|
||||
await storage.clear();
|
||||
await storage.setItem('lastSync', _lastSync.toIso8601String());
|
||||
await storage.setItem('rosters', _rosters);
|
||||
await storage.setItem('activeRosterFilePath', _activeRosterFilePath);
|
||||
}
|
||||
|
||||
Future<void> load() async {
|
||||
await storage.ready;
|
||||
|
||||
String lastSync = storage.getItem('lastSync');
|
||||
if (lastSync != null) {
|
||||
_lastSync = DateTime.parse(lastSync);
|
||||
}
|
||||
|
||||
_activeRosterFilePath = storage.getItem('activeRosterFilePath');
|
||||
|
||||
List<dynamic> rosters = storage.getItem('rosters');
|
||||
if (rosters != null) {
|
||||
rosters.forEach((warband) {
|
||||
_rosters.add(WarbandRoster.fromJson(warband));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,10 @@ abstract class Unit {
|
|||
return list.split(new RegExp(r" *, *"));
|
||||
}
|
||||
|
||||
static String _joinListToJson(List<String> 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<String, String> 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<String> weapons;
|
||||
|
||||
@JsonKey(fromJson: Unit._splitListFromJson)
|
||||
@JsonKey(fromJson: Unit._splitListFromJson, toJson: Unit._joinListToJson)
|
||||
final List<String> armour;
|
||||
|
||||
HenchmenGroup(this.header, this.stats, this.weapons, this.armour) {
|
||||
|
@ -91,12 +104,22 @@ class HenchmenGroup extends Unit {
|
|||
return h;
|
||||
}
|
||||
|
||||
static String _henchmengroupHeaderToJson(
|
||||
HashMap<String, String> henchmenGroup) {
|
||||
return '${henchmenGroup['name']} (${henchmenGroup['number']} ${henchmenGroup['type']}) [${henchmenGroup['experience']}XP]';
|
||||
}
|
||||
|
||||
factory HenchmenGroup.fromJson(yaml) => _$HenchmenGroupFromJson(yaml);
|
||||
Map<String, dynamic> 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<String, String> 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<String> skilllists;
|
||||
|
||||
@JsonKey(fromJson: Unit._splitListFromJson)
|
||||
@JsonKey(fromJson: Unit._splitListFromJson, toJson: Unit._joinListToJson)
|
||||
final List<String> weapons;
|
||||
|
||||
@JsonKey(fromJson: Unit._splitListFromJson)
|
||||
@JsonKey(fromJson: Unit._splitListFromJson, toJson: Unit._joinListToJson)
|
||||
final List<String> armour;
|
||||
|
||||
@JsonKey(fromJson: Unit._splitListFromJson)
|
||||
@JsonKey(fromJson: Unit._splitListFromJson, toJson: Unit._joinListToJson)
|
||||
final List<String> rules;
|
||||
|
||||
@JsonKey(defaultValue: 0)
|
||||
|
@ -134,6 +158,7 @@ class Hero extends Unit {
|
|||
}
|
||||
|
||||
factory Hero.fromJson(yaml) => _$HeroFromJson(yaml);
|
||||
Map<String, dynamic> toJson() => _$HeroToJson(this);
|
||||
|
||||
static HashMap<String, String> _heroHeaderFromJson(String header) {
|
||||
HashMap<String, String> h = new HashMap();
|
||||
|
@ -153,6 +178,10 @@ class Hero extends Unit {
|
|||
|
||||
return h;
|
||||
}
|
||||
|
||||
static String _heroHeaderToJson(HashMap<String, String> 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<String, dynamic> 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<String, String> 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<String, String> _warbandNameAndRace(String nameAndRace) {
|
||||
static HashMap<String, String> _warbandNameAndRaceFromJson(
|
||||
String nameAndRace) {
|
||||
HashMap<String, String> nr = new HashMap();
|
||||
RegExp re = new RegExp(r"(.*) \((.*)\)");
|
||||
|
||||
|
@ -284,5 +335,10 @@ class WarbandRoster {
|
|||
return nr;
|
||||
}
|
||||
|
||||
static String _warbandNameAndRaceToJson(HashMap<String, String> nameAndRace) {
|
||||
return '${nameAndRace['name']} (${nameAndRace['race']})';
|
||||
}
|
||||
|
||||
factory WarbandRoster.fromJson(yaml) => _$WarbandRosterFromJson(yaml);
|
||||
Map<String, dynamic> toJson() => _$WarbandRosterToJson(this);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,14 @@ HenchmenGroup _$HenchmenGroupFromJson(Map json) {
|
|||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$HenchmenGroupToJson(HenchmenGroup instance) =>
|
||||
<String, dynamic>{
|
||||
'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<String, dynamic> _$HeroToJson(Hero instance) => <String, dynamic>{
|
||||
'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<String, dynamic> _$WarbandRosterToJson(WarbandRoster instance) =>
|
||||
<String, dynamic>{
|
||||
'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,
|
||||
};
|
||||
|
|
|
@ -17,6 +17,7 @@ class Toolheim extends StatelessWidget {
|
|||
return ChangeNotifierProvider(
|
||||
builder: (context) => GitHubAdapter(),
|
||||
child: MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'Toolheim',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.brown,
|
||||
|
|
|
@ -8,16 +8,23 @@ class SettingsScreen extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
GitHubAdapter github = Provider.of<GitHubAdapter>(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) {
|
||||
|
|
|
@ -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 + ')',
|
||||
|
|
Loading…
Reference in a new issue