More error handling
This commit is contained in:
parent
82682b3d16
commit
d34a0d4b32
7 changed files with 149 additions and 64 deletions
|
@ -1,4 +1,3 @@
|
|||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -59,6 +58,9 @@ class GitHubAdapter extends ChangeNotifier {
|
|||
Stream<String> warbandFileStream() async* {
|
||||
// Get all files which could be potential warband files (end with
|
||||
// mordheim.yml and contain the word "heros").
|
||||
|
||||
// TODO: Check rate limit
|
||||
// TODO: Extract github access to separate class
|
||||
http.Response response = await http.get(
|
||||
"https://api.github.com/search/code?q=heros+repo:" +
|
||||
repository +
|
||||
|
@ -101,11 +103,14 @@ class GitHubAdapter extends ChangeNotifier {
|
|||
WarbandRoster roster = await fetchWarband(filePath);
|
||||
Version latestVersion = await getLatestVersion(filePath);
|
||||
|
||||
if (roster != null && latestVersion != null) {
|
||||
roster.playerName = getPlayerNameFromFilePath(filePath);
|
||||
roster.version = latestVersion;
|
||||
roster.filePath = filePath;
|
||||
|
||||
_rosters.add(roster);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
//https://github.com/lesnitsky/flutter_localstorage/blob/master/example/lib/main.dart
|
||||
// FIXME: store it correctly
|
||||
|
@ -114,7 +119,6 @@ class GitHubAdapter extends ChangeNotifier {
|
|||
// player['player'] + '-current-version', roster.currentVersion);
|
||||
//storage.setItem(
|
||||
// player['player'] + '-last-sync-version', roster.lastSyncVersion);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,7 +131,7 @@ class GitHubAdapter extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
void update() async {
|
||||
Future<void> update() async {
|
||||
_syncinProgress = true;
|
||||
_syncErrors.clear();
|
||||
|
||||
|
@ -136,10 +140,13 @@ class GitHubAdapter extends ChangeNotifier {
|
|||
for (var i = 0; i < rosters.length; i++) {
|
||||
Version newVersion = await getLatestVersion(rosters[i].filePath);
|
||||
|
||||
// File does not exist any more, we remove the roster
|
||||
if (newVersion == null) {
|
||||
rosters.removeAt(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
// New version found, so we fetch the updated roster
|
||||
if (newVersion.gitHash != rosters[i].version.gitHash) {
|
||||
WarbandRoster newRoster = await fetchWarband(rosters[i].filePath);
|
||||
|
||||
|
@ -159,10 +166,6 @@ class GitHubAdapter extends ChangeNotifier {
|
|||
_lastSync = DateTime.now();
|
||||
_syncinProgress = false;
|
||||
|
||||
if (_syncErrors.length != 0) {
|
||||
// TODO: Show sync errors.
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
@ -187,7 +190,8 @@ class GitHubAdapter extends ChangeNotifier {
|
|||
filePath);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
_syncErrors.add('Could not load the warband metadata from GitHub.');
|
||||
_syncErrors
|
||||
.add(filePath + ': Could not load the warband metadata from GitHub.');
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -196,7 +200,8 @@ class GitHubAdapter extends ChangeNotifier {
|
|||
try {
|
||||
commits = jsonDecode(response.body);
|
||||
} on FormatException catch (e) {
|
||||
_syncErrors.add('Could not parse GitHub response. ' + e.toString());
|
||||
_syncErrors
|
||||
.add(filePath + ': Could not parse GitHub response. ' + e.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -225,13 +230,15 @@ class GitHubAdapter extends ChangeNotifier {
|
|||
// later (see below).
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (response != null) {
|
||||
YamlMap yamlObject = loadYaml(response.body);
|
||||
return WarbandRoster.fromJson(yamlObject);
|
||||
}
|
||||
} catch (e) {
|
||||
_syncErrors.add(e.toString());
|
||||
_syncErrors.add(filePath + ': ' + e.message);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
@ -10,7 +10,7 @@ part 'warband_roster.g.dart';
|
|||
abstract class Unit {
|
||||
static List<String> _splitListFromJson(String list) {
|
||||
if (list == null) {
|
||||
return new List();
|
||||
return [];
|
||||
}
|
||||
return list.split(new RegExp(r" *, *"));
|
||||
}
|
||||
|
@ -18,7 +18,14 @@ abstract class Unit {
|
|||
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*");
|
||||
var matches = re.allMatches(stats).toList().first;
|
||||
var matchList = re.allMatches(stats).toList();
|
||||
|
||||
if (matchList.isEmpty) {
|
||||
throw FormatException(
|
||||
'The stats "' + stats + '" are not in the right format.');
|
||||
}
|
||||
|
||||
var matches = matchList.first;
|
||||
return Stats(
|
||||
int.tryParse(matches.group(1)) ?? 0,
|
||||
int.tryParse(matches.group(2)) ?? 0,
|
||||
|
@ -35,7 +42,7 @@ abstract class Unit {
|
|||
|
||||
@JsonSerializable(nullable: true, anyMap: true, createToJson: false)
|
||||
class HenchmenGroup extends Unit {
|
||||
@JsonKey(name: 'group', fromJson: _henchmenHeaderFromJson)
|
||||
@JsonKey(name: 'group', fromJson: _henchmenHeaderFromJson, required: true)
|
||||
final HashMap<String, String> header;
|
||||
@JsonKey(ignore: true)
|
||||
String name;
|
||||
|
@ -46,16 +53,16 @@ class HenchmenGroup extends Unit {
|
|||
@JsonKey(ignore: true)
|
||||
int experience;
|
||||
|
||||
@JsonKey(fromJson: Unit._statsFromJson)
|
||||
@JsonKey(fromJson: Unit._statsFromJson, required: true)
|
||||
final Stats stats;
|
||||
|
||||
@JsonKey(fromJson: Unit._splitListFromJson)
|
||||
final List<String> weapons;
|
||||
|
||||
@JsonKey(fromJson: Unit._splitListFromJson)
|
||||
final List<String> amour;
|
||||
final List<String> armour;
|
||||
|
||||
HenchmenGroup(this.header, this.stats, this.weapons, this.amour) {
|
||||
HenchmenGroup(this.header, this.stats, this.weapons, this.armour) {
|
||||
this.name = this.header['name'];
|
||||
this.type = this.header['type'];
|
||||
this.number = int.tryParse(this.header['number']) ?? 1;
|
||||
|
@ -66,7 +73,15 @@ class HenchmenGroup extends Unit {
|
|||
HashMap<String, String> h = new HashMap();
|
||||
RegExp re =
|
||||
new RegExp(r"([^\(]+)\(([0-9]+)x?\s+([^\)]+)\)\s*\[([0-9]+)XP\]\s*");
|
||||
var matches = re.allMatches(header).toList().first;
|
||||
var matchList = re.allMatches(header).toList();
|
||||
|
||||
if (matchList.isEmpty) {
|
||||
throw FormatException('The henchmen group header "' +
|
||||
header +
|
||||
'" is not in the right format.');
|
||||
}
|
||||
|
||||
var matches = matchList.first;
|
||||
|
||||
h['name'] = matches.group(1);
|
||||
h['number'] = matches.group(2);
|
||||
|
@ -81,7 +96,7 @@ class HenchmenGroup extends Unit {
|
|||
|
||||
@JsonSerializable(nullable: true, anyMap: true, createToJson: false)
|
||||
class Hero extends Unit {
|
||||
@JsonKey(name: 'hero', fromJson: _heroHeaderFromJson)
|
||||
@JsonKey(name: 'hero', fromJson: _heroHeaderFromJson, required: true)
|
||||
final HashMap<String, String> header;
|
||||
@JsonKey(ignore: true)
|
||||
String name;
|
||||
|
@ -90,7 +105,7 @@ class Hero extends Unit {
|
|||
@JsonKey(ignore: true)
|
||||
int experience;
|
||||
|
||||
@JsonKey(fromJson: Unit._statsFromJson)
|
||||
@JsonKey(fromJson: Unit._statsFromJson, required: true)
|
||||
final Stats stats;
|
||||
|
||||
@JsonKey(fromJson: Unit._splitListFromJson)
|
||||
|
@ -100,14 +115,15 @@ class Hero extends Unit {
|
|||
final List<String> weapons;
|
||||
|
||||
@JsonKey(fromJson: Unit._splitListFromJson)
|
||||
final List<String> amour;
|
||||
final List<String> armour;
|
||||
|
||||
@JsonKey(fromJson: Unit._splitListFromJson)
|
||||
final List<String> rules;
|
||||
|
||||
@JsonKey(defaultValue: 0)
|
||||
final int warbandaddition;
|
||||
|
||||
Hero(this.stats, this.skilllists, this.weapons, this.amour, this.rules,
|
||||
Hero(this.stats, this.skilllists, this.weapons, this.armour, this.rules,
|
||||
this.warbandaddition, this.header) {
|
||||
this.name = this.header['name'];
|
||||
this.type = this.header['type'];
|
||||
|
@ -119,7 +135,14 @@ class Hero extends Unit {
|
|||
static HashMap<String, String> _heroHeaderFromJson(String header) {
|
||||
HashMap<String, String> h = new HashMap();
|
||||
RegExp re = new RegExp(r"([^\(]+)\(([^\)]+)\)\s*\[([0-9]+)XP\]\s*");
|
||||
var matches = re.allMatches(header).toList().first;
|
||||
var matchList = re.allMatches(header).toList();
|
||||
|
||||
if (matchList.isEmpty) {
|
||||
throw FormatException(
|
||||
'The hero header "' + header + '" is not in the right format.');
|
||||
}
|
||||
|
||||
var matches = matchList.first;
|
||||
|
||||
h['name'] = matches.group(1);
|
||||
h['type'] = matches.group(2);
|
||||
|
@ -169,7 +192,7 @@ class Version {
|
|||
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)
|
||||
@JsonKey(name: 'warband', fromJson: _warbandNameAndRace, required: true)
|
||||
final HashMap<String, String> nameAndRace;
|
||||
@JsonKey(ignore: true)
|
||||
String name;
|
||||
|
@ -182,8 +205,10 @@ class WarbandRoster {
|
|||
@JsonKey(name: 'campaign', defaultValue: 0)
|
||||
final int campaignPoints;
|
||||
|
||||
@JsonKey(required: true)
|
||||
final String objective;
|
||||
|
||||
@JsonKey(required: true)
|
||||
final String alignment;
|
||||
|
||||
@JsonKey(defaultValue: '')
|
||||
|
@ -198,9 +223,10 @@ class WarbandRoster {
|
|||
@JsonKey(defaultValue: '')
|
||||
final String equipment;
|
||||
|
||||
@JsonKey(required: true)
|
||||
final List<Hero> heros;
|
||||
|
||||
@JsonKey(name: 'henchmen')
|
||||
@JsonKey(name: 'henchmen', required: true)
|
||||
final List<HenchmenGroup> henchmenGroups;
|
||||
|
||||
/// The players name is not defined in the yml file. This will be added later
|
||||
|
@ -241,9 +267,16 @@ class WarbandRoster {
|
|||
HashMap<String, String> nr = new HashMap();
|
||||
RegExp re = new RegExp(r"(.*) \((.*)\)");
|
||||
|
||||
var matches = re.allMatches(nameAndRace);
|
||||
nr['name'] = matches.toList().first.group(1).toString();
|
||||
nr['race'] = matches.toList().first.group(2).toString();
|
||||
var matchList = re.allMatches(nameAndRace).toList();
|
||||
|
||||
if (matchList.isEmpty) {
|
||||
throw FormatException(
|
||||
'Name and race "' + nameAndRace + '" are not in the right format.');
|
||||
}
|
||||
|
||||
var matches = matchList.first;
|
||||
nr['name'] = matches.group(1).toString();
|
||||
nr['race'] = matches.group(2).toString();
|
||||
|
||||
return nr;
|
||||
}
|
||||
|
|
|
@ -7,27 +7,36 @@ part of 'warband_roster.dart';
|
|||
// **************************************************************************
|
||||
|
||||
HenchmenGroup _$HenchmenGroupFromJson(Map json) {
|
||||
$checkKeys(json, requiredKeys: const ['group', 'stats']);
|
||||
return HenchmenGroup(
|
||||
HenchmenGroup._henchmenHeaderFromJson(json['group'] as String),
|
||||
Unit._statsFromJson(json['stats'] as String),
|
||||
Unit._splitListFromJson(json['weapons'] as String),
|
||||
Unit._splitListFromJson(json['amour'] as String),
|
||||
Unit._splitListFromJson(json['armour'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
Hero _$HeroFromJson(Map json) {
|
||||
$checkKeys(json, requiredKeys: const ['hero', 'stats']);
|
||||
return Hero(
|
||||
Unit._statsFromJson(json['stats'] as String),
|
||||
Unit._splitListFromJson(json['skilllists'] as String),
|
||||
Unit._splitListFromJson(json['weapons'] as String),
|
||||
Unit._splitListFromJson(json['amour'] as String),
|
||||
Unit._splitListFromJson(json['armour'] as String),
|
||||
Unit._splitListFromJson(json['rules'] as String),
|
||||
json['warbandaddition'] as int,
|
||||
json['warbandaddition'] as int ?? 0,
|
||||
Hero._heroHeaderFromJson(json['hero'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
WarbandRoster _$WarbandRosterFromJson(Map json) {
|
||||
$checkKeys(json, requiredKeys: const [
|
||||
'warband',
|
||||
'objective',
|
||||
'alignment',
|
||||
'heros',
|
||||
'henchmen'
|
||||
]);
|
||||
return WarbandRoster(
|
||||
WarbandRoster._warbandNameAndRace(json['warband'] as String),
|
||||
json['campaign'] as int ?? 0,
|
||||
|
|
|
@ -3,8 +3,6 @@ import 'package:preferences/preferences.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:toolheim/data/github_adapter.dart';
|
||||
|
||||
// TODO: Display possible errors here
|
||||
|
||||
class SettingsScreen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -73,7 +71,7 @@ class SettingsScreen extends StatelessWidget {
|
|||
//String _path = 'Mordheim-BorderTownBurning/Warband Rosters';
|
||||
}
|
||||
|
||||
Widget buildSyncErrors(BuildContext context) {
|
||||
static Widget buildSyncErrors(BuildContext context) {
|
||||
List<Widget> syncErrors = new List();
|
||||
|
||||
GitHubAdapter github = Provider.of<GitHubAdapter>(context);
|
||||
|
|
|
@ -3,6 +3,8 @@ import 'package:provider/provider.dart';
|
|||
import 'package:badges/badges.dart';
|
||||
import 'package:toolheim/data/github_adapter.dart';
|
||||
import 'package:toolheim/data/warband_roster.dart';
|
||||
import 'package:toolheim/screens/settings_screen.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class WarbandDrawerWidget extends StatelessWidget {
|
||||
@override
|
||||
|
@ -10,12 +12,20 @@ class WarbandDrawerWidget extends StatelessWidget {
|
|||
GitHubAdapter github = Provider.of<GitHubAdapter>(context);
|
||||
|
||||
// No settings at all
|
||||
if (github.repository == null) {
|
||||
String settingsText =
|
||||
'There is no repository set up. Please open the settings and provide a valid GitHub repository.';
|
||||
|
||||
// Never fetched any data
|
||||
if (github.activeRoster == null) {
|
||||
settingsText =
|
||||
'The repository is set, but no warbands are found. Try to search for warbands on the settings screen.';
|
||||
}
|
||||
|
||||
if (github.repository == null || github.activeRoster == null) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 100, left: 30, right: 30),
|
||||
child: Column(children: <Widget>[
|
||||
Text(
|
||||
'There is no repository set up. Please open the settings and provide a valid GitHub repository.'),
|
||||
Text(settingsText),
|
||||
FlatButton(
|
||||
onPressed: () {
|
||||
Navigator.popAndPushNamed(context, '/settings');
|
||||
|
@ -25,23 +35,17 @@ class WarbandDrawerWidget extends StatelessWidget {
|
|||
style: TextStyle(color: Colors.blue),
|
||||
),
|
||||
),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
// Never fetched any data
|
||||
if (github.activeRoster == null) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 100, left: 30, right: 30),
|
||||
child: Column(children: <Widget>[
|
||||
Text(
|
||||
'The repository is set, but no warbands are found. Try to search for warbands on the settings screen.'),
|
||||
'If you have no clue what this app is all about, open the help screen and read the introduction.'),
|
||||
FlatButton(
|
||||
onPressed: () {
|
||||
Navigator.popAndPushNamed(context, '/settings');
|
||||
onPressed: () async {
|
||||
const url = '';
|
||||
if (await canLaunch(url)) {
|
||||
await launch(url);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
'Open Settings',
|
||||
'Help',
|
||||
style: TextStyle(color: Colors.blue),
|
||||
),
|
||||
),
|
||||
|
@ -57,7 +61,7 @@ class WarbandDrawerWidget extends StatelessWidget {
|
|||
WarbandRoster activeroster = github.activeRoster;
|
||||
List<WarbandRoster> rosters = github.rosters;
|
||||
|
||||
List<Widget> tiles = new List();
|
||||
List<Widget> tiles = [];
|
||||
|
||||
// Show some stats for the own warband
|
||||
tiles.add(UserAccountsDrawerHeader(
|
||||
|
@ -68,8 +72,26 @@ class WarbandDrawerWidget extends StatelessWidget {
|
|||
color: Colors.white,
|
||||
highlightColor: Colors.brown,
|
||||
tooltip: 'Refresh warbands',
|
||||
onPressed: github.update,
|
||||
),
|
||||
onPressed: () async {
|
||||
await github.update();
|
||||
|
||||
if (github.syncErrors.length > 0) {
|
||||
List<Widget> errors = github.syncErrors.map((error) {
|
||||
return Text(error);
|
||||
}).toList();
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('We have some errors while updating.'),
|
||||
content: SettingsScreen.buildSyncErrors(context),
|
||||
actions: <Widget>[
|
||||
FlatButton(child: Text('Ok'), onPressed: () {})
|
||||
]);
|
||||
});
|
||||
}
|
||||
}),
|
||||
IconButton(
|
||||
icon: Icon(Icons.settings),
|
||||
color: Colors.white,
|
||||
|
@ -78,6 +100,14 @@ class WarbandDrawerWidget extends StatelessWidget {
|
|||
Navigator.popAndPushNamed(context, '/settings');
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.help),
|
||||
color: Colors.white,
|
||||
highlightColor: Colors.brown,
|
||||
onPressed: () {
|
||||
Navigator.popAndPushNamed(context, '/help');
|
||||
},
|
||||
),
|
||||
],
|
||||
accountName: Text(activeroster.name),
|
||||
accountEmail: Text(activeroster.race),
|
||||
|
|
|
@ -464,6 +464,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.6"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -27,6 +27,7 @@ dependencies:
|
|||
shared_preferences:
|
||||
preferences:
|
||||
localstorage:
|
||||
url_launcher:
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
|
|
Loading…
Reference in a new issue