More error handling

This commit is contained in:
Aaron Fischer 2019-08-01 00:05:42 +02:00
parent 82682b3d16
commit d34a0d4b32
7 changed files with 149 additions and 64 deletions

View file

@ -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);
roster.playerName = getPlayerNameFromFilePath(filePath);
roster.version = latestVersion;
roster.filePath = filePath;
if (roster != null && latestVersion != null) {
roster.playerName = getPlayerNameFromFilePath(filePath);
roster.version = latestVersion;
roster.filePath = filePath;
_rosters.add(roster);
_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);
}
YamlMap yamlObject = loadYaml(response.body);
return WarbandRoster.fromJson(yamlObject);
} catch (e) {
_syncErrors.add(e.toString());
_syncErrors.add(filePath + ': ' + e.message);
}
return null;

View file

@ -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;
}

View file

@ -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,

View file

@ -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);

View file

@ -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,19 +61,37 @@ 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(
margin: const EdgeInsets.all(0),
otherAccountsPictures: <Widget>[
IconButton(
icon: Icon(Icons.refresh),
color: Colors.white,
highlightColor: Colors.brown,
tooltip: 'Refresh warbands',
onPressed: github.update,
),
icon: Icon(Icons.refresh),
color: Colors.white,
highlightColor: Colors.brown,
tooltip: 'Refresh warbands',
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),

View file

@ -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:

View file

@ -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.