Fetch all data directly from github

This commit is contained in:
Aaron Fischer 2019-07-06 00:44:20 +02:00
parent 35276cf974
commit 27d93e53ff
4 changed files with 240 additions and 107 deletions

View file

@ -0,0 +1,115 @@
import 'dart:collection';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:toolheim/warband_roaster.dart';
import 'package:yaml/yaml.dart';
enum SyncState {
unknown,
running,
success,
error,
}
class GitHubAdapter {
// TODO: Store this in the SharedPreferences
final String repository;
final String path;
static SyncState syncState = SyncState.unknown;
List<String> syncErrors = new List<String>();
static DateTime lastSync;
GitHubAdapter(this.repository, this.path);
/// Search for warband files in the GitHub repository
///
/// This method will search for matching files and check their content in a
/// subfolder (see fields [repository] and [path]). If the file
/// contain errors or can't read, a sync error message will be written into
/// the [syncErrors] list.
Future<List<WarbandRoaster>> search() async {
syncErrors.clear();
Stream<Map<String, String>> roasterStream() async* {
// Get all files which could be potential warband files (end with
// mordheim.yml and contain the word "heros").
http.Response response = await http.get(
"https://api.github.com/search/code?q=heros+repo:" +
repository +
"+filename:mordheim.yml+path:\"" +
path +
"\"");
RegExp fileRegex = new RegExp(r"[a-zA-Z]+\.mordheim\.ya?ml");
var resp = jsonDecode(response.body);
for (var searchResult in resp['items']) {
if (fileRegex.hasMatch(searchResult['name'])) {
String completePath = searchResult['path'];
String playerName =
completePath.substring(path.length + 1).split('/').first;
if (playerName == '') {
playerName = 'Lonely Recluse';
}
// Fetch last change and some metainformation of the file
http.Response response = await http.get(
"https://api.github.com/repos/" +
repository +
"/commits?path=" +
completePath);
var resp = jsonDecode(response.body);
var lastCommit = resp.first;
// TODO: Add some error handling
yield {
'filePath': completePath.toString(),
'shaHash': lastCommit['sha'],
'player': playerName.toString(),
'author': lastCommit['commit']['author']['name'],
'date': lastCommit['commit']['committer']['date'],
'message': lastCommit['commit']['message']
};
}
}
}
List<WarbandRoaster> roasters = new List<WarbandRoaster>();
// TODO: Read params from share preferences
await for (Map<String, String> player in roasterStream()) {
http.Response response = await http.get(
"https://raw.githubusercontent.com/" +
repository +
'/master/' +
player['filePath']);
try {
YamlMap yamlObject = loadYaml(response.body);
WarbandRoaster roaster = WarbandRoaster.fromJson(yamlObject);
roaster.playerName = player['player'];
roaster.author = player['author'];
roaster.commitDate = player['date'];
roaster.commitMessage = player['message'];
roaster.commitHash = player['shaHash']
roasters.add(roaster);
} catch (e) {
syncErrors.add(e.toString());
}
}
return roasters;
}
Future<SyncState> update() async {
// TODO: Search for warband yml files
// TODO: Check if it is in the right format
// TODO: Store it into the database if valid
lastSync = DateTime.now();
return SyncState.success;
}
}

View file

@ -1,8 +1,8 @@
import 'dart:collection'; import 'dart:collection';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; import 'package:toolheim/github_reader.dart';
import 'package:toolheim/WarbandRoaster.dart'; import 'package:toolheim/warband_roaster.dart';
import 'package:yaml/yaml.dart'; import 'package:yaml/yaml.dart';
void main() => runApp(Toolheim()); void main() => runApp(Toolheim());
@ -27,37 +27,26 @@ class RoasterWidget extends StatefulWidget {
} }
class _RoasterWidgetState extends State<RoasterWidget> { class _RoasterWidgetState extends State<RoasterWidget> {
Future<WarbandRoaster> roaster; // TODO: Read this from SharedPreferences
String activePlayer = 'Aaron';
Future<List<WarbandRoaster>> roasters;
GitHubAdapter github = GitHubAdapter(
'Labernator/Mordheim', 'Mordheim-BorderTownBurning/Warband Rosters');
static Future<WarbandRoaster> fetchWarband(urlPath) async { Future initState() {
http.Response response = await http.get(urlPath);
YamlMap yamlObject = loadYaml(response.body);
return WarbandRoaster.fromJson(yamlObject);
}
void initState() {
super.initState(); super.initState();
roaster = fetchWarband(players()['Aaron']); roasters = github.search();
} }
HashMap players() { List<Widget> playerList(List<WarbandRoaster> roasters) {
HashMap players = new HashMap();
players['Aaron'] = 'https://raw.githubusercontent.com/Labernator/Mordheim/master/Mordheim-BorderTownBurning/Warband%20Rosters/Aaron/aaron.mordheim.yml';
players['Kai'] = 'https://raw.githubusercontent.com/Labernator/Mordheim/master/Mordheim-BorderTownBurning/Warband%20Rosters/kai/kai.mordheim_post5.yml';
players['Fabian'] = 'https://raw.githubusercontent.com/Labernator/Mordheim/master/Mordheim-BorderTownBurning/Warband%20Rosters/Fabian/fabian.mordheim.yml';
players['Marius'] = 'https://raw.githubusercontent.com/Labernator/Mordheim/master/Mordheim-BorderTownBurning/Warband%20Rosters/Marius/Marius_Post_5.mordheim.yml';
players['Stefan'] = 'https://raw.githubusercontent.com/Labernator/Mordheim/master/Mordheim-BorderTownBurning/Warband%20Rosters/Stefan/Pit%20Fighter.yml';
return players;
}
List<Widget> playerList() {
List<Widget> tiles = new List(); List<Widget> tiles = new List();
WarbandRoaster myWarband = roasters.first;
// Show some stats for the own warband // Show some stats for the own warband
tiles.add(UserAccountsDrawerHeader( tiles.add(UserAccountsDrawerHeader(
accountName: Text('The Revolting Dwarfs'), accountName: Text(myWarband.name),
accountEmail: Text('Dwarf Rangers'), accountEmail: Text(myWarband.race),
currentAccountPicture: CircleAvatar( currentAccountPicture: CircleAvatar(
child: Text('Aa'), child: Text('Aa'),
), ),
@ -65,16 +54,15 @@ class _RoasterWidgetState extends State<RoasterWidget> {
// TODO: Order Players on CP or rating // TODO: Order Players on CP or rating
players().forEach((player, url) { roasters.forEach((roaster) {
tiles.add(ListTile( tiles.add(ListTile(
title: Text(player.toString()), title: Text(roaster.name + '(' + roaster.playerName + ')'),
leading: CircleAvatar( subtitle: Text(roaster.commitMessage),
child: Text('WB'), isThreeLine: true,
), trailing: Chip(
trailing: Chip( label: Text(
label: Text('326 XP') '326 XP',
) ))));
));
}); });
tiles.add(Divider()); tiles.add(Divider());
@ -85,7 +73,7 @@ class _RoasterWidgetState extends State<RoasterWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FutureBuilder( return FutureBuilder(
future: roaster, future: github.search(),
builder: (BuildContext context, AsyncSnapshot snapshot) { builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) { switch (snapshot.connectionState) {
case ConnectionState.none: case ConnectionState.none:
@ -93,53 +81,54 @@ class _RoasterWidgetState extends State<RoasterWidget> {
case ConnectionState.active: case ConnectionState.active:
return Center(child: CircularProgressIndicator()); return Center(child: CircularProgressIndicator());
case ConnectionState.done: case ConnectionState.done:
WarbandRoaster roaster = snapshot.data; List<WarbandRoaster> roasters = snapshot.data;
// TODO: Replace with router
WarbandRoaster warband = roasters.first;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(roaster.name), title: Text(warband.name),
),
drawer: Drawer(
child: Column(
children: this.playerList()
), ),
), drawer: Drawer(
body: ListView.builder( child: Column(children: playerList(roasters)),
itemCount: roaster.heros.length + roaster.henchmenGroups.length, ),
itemBuilder: (BuildContext context, int index) { body: ListView.builder(
// TODO: Sort by initiative itemCount:
if (index < roaster.heros.length) { warband.heros.length + warband.henchmenGroups.length,
var hero = roaster.heros[index]; itemBuilder: (BuildContext context, int index) {
// TODO: Sort by initiative
if (index < warband.heros.length) {
var hero = warband.heros[index];
return ListTile( return ListTile(
title: Text(hero.name), title: Text(hero.name),
leading: CircleAvatar( leading: CircleAvatar(
child: Text(hero.experience.toString()), child: Text(hero.experience.toString()),
backgroundColor: Colors.green, backgroundColor: Colors.green,
foregroundColor: Colors.greenAccent, foregroundColor: Colors.greenAccent,
), ),
subtitle: Text(hero.type), subtitle: Text(hero.type),
); );
} else { } else {
var henchmenGroup = roaster.henchmenGroups[index-roaster.heros.length]; var henchmenGroup = warband
.henchmenGroups[index - warband.heros.length];
return ListTile( return ListTile(
title: Text(henchmenGroup.name), title: Text(henchmenGroup.name),
trailing: Chip( trailing: Chip(
label: Text(henchmenGroup.number.toString() + 'x') label: Text(
), henchmenGroup.number.toString() + 'x')),
leading: CircleAvatar( leading: CircleAvatar(
child: Text(henchmenGroup.experience.toString()), child: Text(henchmenGroup.experience.toString()),
backgroundColor: Colors.orange, backgroundColor: Colors.orange,
foregroundColor: Colors.white, foregroundColor: Colors.white,
), ),
subtitle: Text(henchmenGroup.type), subtitle: Text(henchmenGroup.type),
); );
} }
} }));
)
);
} }
} });
);
} }
} }

View file

@ -2,7 +2,7 @@ import 'dart:collection';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
part 'WarbandRoaster.g.dart'; part 'warband_roaster.g.dart';
// flutter packages pub run build_runner build --delete-conflicting-outputs // flutter packages pub run build_runner build --delete-conflicting-outputs
@ -15,20 +15,20 @@ abstract class Unit {
} }
static Stats _statsFromJson(String stats) { 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*"); 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 matches = re.allMatches(stats).toList().first;
return Stats( return Stats(
int.tryParse(matches.group(1)) ?? 0, int.tryParse(matches.group(1)) ?? 0,
int.tryParse(matches.group(2)) ?? 0, int.tryParse(matches.group(2)) ?? 0,
int.tryParse(matches.group(3)) ?? 0, int.tryParse(matches.group(3)) ?? 0,
int.tryParse(matches.group(4)) ?? 0, int.tryParse(matches.group(4)) ?? 0,
int.tryParse(matches.group(5)) ?? 0, int.tryParse(matches.group(5)) ?? 0,
int.tryParse(matches.group(6)) ?? 0, int.tryParse(matches.group(6)) ?? 0,
int.tryParse(matches.group(7)) ?? 0, int.tryParse(matches.group(7)) ?? 0,
int.tryParse(matches.group(8)) ?? 0, int.tryParse(matches.group(8)) ?? 0,
int.tryParse(matches.group(9)) ?? 0, int.tryParse(matches.group(9)) ?? 0,
int.tryParse(matches.group(10)) ?? 0 int.tryParse(matches.group(10)) ?? 0);
);
} }
} }
@ -63,7 +63,8 @@ class HenchmenGroup extends Unit {
static HashMap<String, String> _henchmenHeaderFromJson(String header) { static HashMap<String, String> _henchmenHeaderFromJson(String header) {
HashMap<String, String> h = new HashMap(); HashMap<String, String> h = new HashMap();
RegExp re = new RegExp(r"([^\(]+)\(([0-9]+)x?\s+([^\)]+)\)\s*\[([0-9]+)XP\]\s*"); RegExp re =
new RegExp(r"([^\(]+)\(([0-9]+)x?\s+([^\)]+)\)\s*\[([0-9]+)XP\]\s*");
var matches = re.allMatches(header).toList().first; var matches = re.allMatches(header).toList().first;
h['name'] = matches.group(1); h['name'] = matches.group(1);
@ -74,9 +75,7 @@ class HenchmenGroup extends Unit {
return h; return h;
} }
factory HenchmenGroup.fromJson(yaml) => factory HenchmenGroup.fromJson(yaml) => _$HenchmenGroupFromJson(yaml);
_$HenchmenGroupFromJson(yaml);
} }
@JsonSerializable(nullable: true, anyMap: true, createToJson: false) @JsonSerializable(nullable: true, anyMap: true, createToJson: false)
@ -107,18 +106,18 @@ class Hero extends Unit {
final int warbandaddition; final int warbandaddition;
Hero(this.stats, this.skilllists, this.weapons, this.amour, this.rules, this.warbandaddition, this.header) { Hero(this.stats, this.skilllists, this.weapons, this.amour, this.rules,
this.warbandaddition, this.header) {
this.name = this.header['name']; this.name = this.header['name'];
this.type = this.header['type']; this.type = this.header['type'];
this.experience = int.tryParse(this.header['experience']) ?? 0; this.experience = int.tryParse(this.header['experience']) ?? 0;
} }
factory Hero.fromJson(yaml) => factory Hero.fromJson(yaml) => _$HeroFromJson(yaml);
_$HeroFromJson(yaml);
static HashMap<String, String> _heroHeaderFromJson(String header) { static HashMap<String, String> _heroHeaderFromJson(String header) {
HashMap<String, String> h = new HashMap(); HashMap<String, String> h = new HashMap();
RegExp re = new RegExp(r"([^\(]+)\(([^\)]+)\)\s*\[([0-9]+)XP\]\s*"); RegExp re = new RegExp(r"([^\(]+)\(([^\)]+)\)\s*\[([0-9]+)XP\]\s*");
var matches = re.allMatches(header).toList().first; var matches = re.allMatches(header).toList().first;
h['name'] = matches.group(1); h['name'] = matches.group(1);
@ -141,11 +140,23 @@ class Stats {
final int leadership; final int leadership;
final int save; final int save;
Stats(this.movement, this.weaponSkill, this.ballisticSkill, this.strength, this.toughtness, this.wounds, this.initiative, this.attacks, this.leadership, this.save); Stats(
this.movement,
this.weaponSkill,
this.ballisticSkill,
this.strength,
this.toughtness,
this.wounds,
this.initiative,
this.attacks,
this.leadership,
this.save);
} }
@JsonSerializable(nullable: true, anyMap: true, createToJson: false) @JsonSerializable(nullable: true, anyMap: true, createToJson: false)
class WarbandRoaster { class WarbandRoaster {
/// 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)
final HashMap<String, String> nameAndRace; final HashMap<String, String> nameAndRace;
@JsonKey(ignore: true) @JsonKey(ignore: true)
@ -167,7 +178,25 @@ class WarbandRoaster {
@JsonKey(name: 'henchmen') @JsonKey(name: 'henchmen')
final List<HenchmenGroup> henchmenGroups; final List<HenchmenGroup> henchmenGroups;
WarbandRoaster(this.nameAndRace, this.campaignPoints, this.objective, this.alignment, this.gc, this.shards, this.equipment, this.achievments, this.heros, this.henchmenGroups) { /// The players name is not defined in the yml file. This will be added later
/// from the GitHubAdapter.
String playerName = '';
String author = '';
String commitDate = '';
String commitMessage = '';
String commitHash = '';
WarbandRoaster(
this.nameAndRace,
this.campaignPoints,
this.objective,
this.alignment,
this.gc,
this.shards,
this.equipment,
this.achievments,
this.heros,
this.henchmenGroups) {
this.name = this.nameAndRace['name']; this.name = this.nameAndRace['name'];
this.race = this.nameAndRace['race']; this.race = this.nameAndRace['race'];
} }
@ -183,6 +212,5 @@ class WarbandRoaster {
return nr; return nr;
} }
factory WarbandRoaster.fromJson(yaml) => factory WarbandRoaster.fromJson(yaml) => _$WarbandRoasterFromJson(yaml);
_$WarbandRoasterFromJson(yaml); }
}

View file

@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'WarbandRoaster.dart'; part of 'warband_roaster.dart';
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator
@ -40,5 +40,6 @@ WarbandRoaster _$WarbandRoasterFromJson(Map json) {
?.toList(), ?.toList(),
(json['henchmen'] as List) (json['henchmen'] as List)
?.map((e) => e == null ? null : HenchmenGroup.fromJson(e)) ?.map((e) => e == null ? null : HenchmenGroup.fromJson(e))
?.toList()); ?.toList())
..playerName = json['playerName'] as String;
} }