TL records are now stored in the database

for tracked players only.
Also fixed variety of shit
This commit is contained in:
dan63047 2023-06-23 21:38:15 +03:00
parent 5d5ac32a8b
commit 8770d5dca8
17 changed files with 200 additions and 108 deletions

View File

@ -12,8 +12,8 @@
- ~~Ability to compare 2 players~~ *v0.1.0, we are here* - ~~Ability to compare 2 players~~ *v0.1.0, we are here*
- ~~Stats Calculator~~ - ~~Stats Calculator~~
- ~~Ability to compare player with himself in past~~ - ~~Ability to compare player with himself in past~~
- ~~Tetra League matches history~~ *dev build are here* - ~~Tetra League matches history~~
- ~~Tetra League historic charts for tracked players~~ (bit mess idk) - ~~Tetra League historic charts for tracked players~~ *dev build are here*
- Better UI with delta and hints for stats *that will be v0.2.0* - Better UI with delta and hints for stats *that will be v0.2.0*
- Ability to compare player with APM-PPS-VS stats - Ability to compare player with APM-PPS-VS stats
- Ability to fetch Tetra League leaderboard - Ability to fetch Tetra League leaderboard
@ -22,7 +22,7 @@
- UI Animations - UI Animations
- i18n, EN and RU locales - i18n, EN and RU locales
- Talk with osk about CORS and EndContext in TL matches - Talk with osk about CORS and EndContext in TL matches
- RELEASE ??? - RELEASE ??? *that will be v1.0.0*
--- ---

View File

@ -26,6 +26,6 @@ subprojects {
project.evaluationDependsOn(':app') project.evaluationDependsOn(':app')
} }
task clean(type: Delete) { tasks.register("clean", Delete) {
delete rootProject.buildDir delete rootProject.buildDir
} }

View File

@ -430,10 +430,10 @@ class Handling {
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{}; final Map<String, dynamic> data = <String, dynamic>{};
data['arr'] = arr; data['arr'] = arr.toDouble();
data['das'] = das; data['das'] = das.toDouble();
data['dcd'] = dcd; data['dcd'] = dcd.toDouble();
data['sdf'] = sdf; data['sdf'] = sdf.toDouble();
data['safelock'] = safeLock; data['safelock'] = safeLock;
data['cancel'] = cancel; data['cancel'] = cancel;
return data; return data;
@ -526,24 +526,24 @@ class Playstyle {
class TetraLeagueAlphaStream{ class TetraLeagueAlphaStream{
late String userId; late String userId;
List<TetraLeagueAlphaRecord>? records; late List<TetraLeagueAlphaRecord> records;
TetraLeagueAlphaStream({required this.userId, this.records}); TetraLeagueAlphaStream({required this.userId, required this.records});
TetraLeagueAlphaStream.fromJson(List<dynamic> json, String userID) { TetraLeagueAlphaStream.fromJson(List<dynamic> json, String userID) {
userId = userID; userId = userID;
records = []; records = [];
for (var value in json) {records!.add(TetraLeagueAlphaRecord.fromJson(value));} for (var value in json) {records.add(TetraLeagueAlphaRecord.fromJson(value));}
} }
} }
class TetraLeagueAlphaRecord{ class TetraLeagueAlphaRecord{
late String replayId; late String replayId;
late String ownId; late String ownId;
DateTime? timestamp; late DateTime timestamp;
late List<EndContextMulti> endContext; late List<EndContextMulti> endContext;
TetraLeagueAlphaRecord({required this.replayId, required this.ownId, this.timestamp, required this.endContext}); TetraLeagueAlphaRecord({required this.replayId, required this.ownId, required this.timestamp, required this.endContext});
TetraLeagueAlphaRecord.fromJson(Map<String, dynamic> json) { TetraLeagueAlphaRecord.fromJson(Map<String, dynamic> json) {
ownId = json['_id']; ownId = json['_id'];
@ -561,6 +561,10 @@ class TetraLeagueAlphaRecord{
data['ts'] = timestamp; data['ts'] = timestamp;
return data; return data;
} }
@override
bool operator ==(covariant TetraLeagueAlphaRecord other) => ownId == other.ownId;
@override @override
String toString() { String toString() {
return "TetraLeagueAlphaRecord: ${endContext.first.userId} vs ${endContext.last.userId}"; return "TetraLeagueAlphaRecord: ${endContext.first.userId} vs ${endContext.last.userId}";
@ -577,11 +581,11 @@ class EndContextMulti {
late int points; late int points;
late int wins; late int wins;
late double secondary; late double secondary;
late List<double> secondaryTracking; late List secondaryTracking;
late double tertiary; late double tertiary;
late List<double> tertiaryTracking; late List tertiaryTracking;
late double extra; late double extra;
late List<double> extraTracking; late List extraTracking;
late bool success; late bool success;
late NerdStats nerdStats; late NerdStats nerdStats;
late EstTr estTr; late EstTr estTr;
@ -616,10 +620,10 @@ class EndContextMulti {
points = json['points']['primary']; points = json['points']['primary'];
secondary = json['points']['secondary'].toDouble(); secondary = json['points']['secondary'].toDouble();
tertiary = json['points']['tertiary'].toDouble(); tertiary = json['points']['tertiary'].toDouble();
secondaryTracking = json['points']['secondaryAvgTracking'].cast<double>(); secondaryTracking = json['points']['secondaryAvgTracking'].map((e) => e.toDouble()).toList();
tertiaryTracking = json['points']['tertiaryAvgTracking'].cast<double>(); tertiaryTracking = json['points']['tertiaryAvgTracking'].map((e) => e.toDouble()).toList();
extra = json['points']['extra']['vs'].toDouble(); extra = json['points']['extra']['vs'].toDouble();
extraTracking = json['points']['extraAvgTracking']['aggregatestats___vsscore'].cast<double>(); extraTracking = json['points']['extraAvgTracking']['aggregatestats___vsscore'].map((e) => e.toDouble()).toList();
nerdStats = NerdStats(secondary, tertiary, extra); nerdStats = NerdStats(secondary, tertiary, extra);
estTr = EstTr(secondary, tertiary, extra, noTrRd, nerdStats.app, nerdStats.dss, nerdStats.dsp, nerdStats.gbe); estTr = EstTr(secondary, tertiary, extra, noTrRd, nerdStats.app, nerdStats.dss, nerdStats.dsp, nerdStats.gbe);
playstyle = Playstyle(secondary, tertiary, nerdStats.app, nerdStats.vsapm, nerdStats.dsp, nerdStats.gbe, estTr.srarea, estTr.statrank); playstyle = Playstyle(secondary, tertiary, nerdStats.app, nerdStats.vsapm, nerdStats.dsp, nerdStats.gbe, estTr.srarea, estTr.statrank);
@ -627,19 +631,14 @@ class EndContextMulti {
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{}; final Map<String, dynamic> data = <String, dynamic>{};
data['user']['_id'] = userId; data['user'] = {'_id': userId, 'username': username};
data['user']['username'] = username;
data['handling'] = handling.toJson(); data['handling'] = handling.toJson();
data['success'] = success; data['success'] = success;
data['inputs'] = inputs; data['inputs'] = inputs;
data['piecesplaced'] = piecesPlaced; data['piecesplaced'] = piecesPlaced;
data['naturalorder'] = naturalOrder; data['naturalorder'] = naturalOrder;
data['wins'] = wins; data['wins'] = wins;
data['points']['primary'] = points; data['points'] = {'primary': points, 'secondary': secondary, 'tertiary':tertiary, 'extra': {'vs': extra}, 'secondaryAvgTracking': secondaryTracking, 'tertiaryAvgTracking': tertiaryTracking, 'extraAvgTracking': {'aggregatestats___vsscore': extraTracking}};
data['points']['secondary'] = secondary;
data['points']['tertiary'] = tertiary;
data['points']['extra']['vs'] = extra;
data['points']['extraAvgTracking']['aggregatestats___vsscore'] = extraTracking;
return data; return data;
} }
} }

View File

@ -20,6 +20,7 @@ class DB {
_db = db; _db = db;
await db.execute(createTetrioUsersTable); await db.execute(createTetrioUsersTable);
await db.execute(createTetrioUsersToTrack); await db.execute(createTetrioUsersToTrack);
await db.execute(createTetrioTLRecordsTable);
} on MissingPlatformDirectoryException { } on MissingPlatformDirectoryException {
throw UnableToGetDocuments(); throw UnableToGetDocuments();
} }

View File

@ -9,10 +9,16 @@ import 'package:tetra_stats/data_objects/tetrio.dart';
const String dbName = "TetraStats.db"; const String dbName = "TetraStats.db";
const String tetrioUsersTable = "tetrioUsers"; const String tetrioUsersTable = "tetrioUsers";
const String tetrioUsersToTrackTable = "tetrioUsersToTrack"; const String tetrioUsersToTrackTable = "tetrioUsersToTrack";
const String tetraLeagueMatchesTable = "tetraLeagueMatches"; const String tetraLeagueMatchesTable = "tetrioAlphaLeagueMathces";
const String idCol = "id"; const String idCol = "id";
const String replayID = "replayId";
const String nickCol = "nickname"; const String nickCol = "nickname";
const String timestamp = "timestamp";
const String endContext1 = "endContext1";
const String endContext2 = "endContext2";
const String statesCol = "jsonStates"; const String statesCol = "jsonStates";
const String player1id = "player1id";
const String player2id = "player2id";
const String createTetrioUsersTable = ''' const String createTetrioUsersTable = '''
CREATE TABLE IF NOT EXISTS "tetrioUsers" ( CREATE TABLE IF NOT EXISTS "tetrioUsers" (
"id" TEXT UNIQUE, "id" TEXT UNIQUE,
@ -26,6 +32,17 @@ const String createTetrioUsersToTrack = '''
PRIMARY KEY("ID") PRIMARY KEY("ID")
) )
'''; ''';
const String createTetrioTLRecordsTable = '''
CREATE TABLE IF NOT EXISTS "tetrioAlphaLeagueMathces" (
"id" TEXT,
"replayId" TEXT,
"player1id" TEXT,
"player2id" TEXT,
"timestamp" TEXT,
"endContext1" TEXT,
"endContext2" TEXT
)
''';
class TetrioService extends DB { class TetrioService extends DB {
Map<String, List<TetrioPlayer>> _players = {}; Map<String, List<TetrioPlayer>> _players = {};
@ -108,6 +125,27 @@ class TetrioService extends DB {
} }
} }
Future<void> saveTLMatchesFromStream(TetraLeagueAlphaStream stream) async {
ensureDbIsOpen();
final db = getDatabaseOrThrow();
for (TetraLeagueAlphaRecord match in stream.records) {
final results = await db.query(tetraLeagueMatchesTable, where: '$idCol = ?', whereArgs: [match.ownId]);
if (results.isNotEmpty) continue;
db.insert(tetraLeagueMatchesTable, {idCol: match.ownId, replayID: match.replayId, timestamp: match.timestamp.toString(), player1id: match.endContext.first.userId, player2id: match.endContext.last.userId, endContext1: jsonEncode(match.endContext.first.toJson()), endContext2: jsonEncode(match.endContext.last.toJson())});
}
}
Future<List<TetraLeagueAlphaRecord>> getTLMatchesbyPlayerID(String playerID) async {
ensureDbIsOpen();
final db = getDatabaseOrThrow();
List<TetraLeagueAlphaRecord> matches = [];
final results = await db.query(tetraLeagueMatchesTable, where: '($player1id = ?) OR ($player2id = ?)', whereArgs: [playerID, playerID]);
for (var match in results){
matches.add(TetraLeagueAlphaRecord(ownId: match[idCol].toString(), replayId: match[replayID].toString(), timestamp: DateTime.parse(match[timestamp].toString()), endContext:[EndContextMulti.fromJson(jsonDecode(match[endContext1].toString())), EndContextMulti.fromJson(jsonDecode(match[endContext2].toString()))]));
}
return matches;
}
Future<Map<String, dynamic>> fetchRecords(String userID) async { Future<Map<String, dynamic>> fetchRecords(String userID) async {
try{ try{
var cached = _recordsCache.entries.firstWhere((element) => element.value['user'] == userID); var cached = _recordsCache.entries.firstWhere((element) => element.value['user'] == userID);

View File

@ -1194,8 +1194,9 @@ class CompareRegTimeThingy extends StatelessWidget {
String verdict(DateTime? greenSide, DateTime? redSide) { String verdict(DateTime? greenSide, DateTime? redSide) {
var f = NumberFormat("#,### days later;#,### days before"); var f = NumberFormat("#,### days later;#,### days before");
String result = "---"; String result = "---";
if (greenSide != null && redSide != null) if (greenSide != null && redSide != null) {
result = f.format(greenSide.difference(redSide).inDays); result = f.format(greenSide.difference(redSide).inDays);
}
return result; return result;
} }

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'dart:math';
import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/fl_chart.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -21,6 +22,7 @@ const allowedHeightForPlayerBioInPixels = 30.0;
const givenTextHeightByScreenPercentage = 0.3; const givenTextHeightByScreenPercentage = 0.3;
final NumberFormat timeInSec = NumberFormat("#,###.###s."); final NumberFormat timeInSec = NumberFormat("#,###.###s.");
final NumberFormat f2 = NumberFormat.decimalPatternDigits(decimalDigits: 2); final NumberFormat f2 = NumberFormat.decimalPatternDigits(decimalDigits: 2);
final NumberFormat f4 = NumberFormat.decimalPatternDigits(decimalDigits: 4);
final DateFormat dateFormat = DateFormat.yMMMd().add_Hms(); final DateFormat dateFormat = DateFormat.yMMMd().add_Hms();
class MainView extends StatefulWidget { class MainView extends StatefulWidget {
@ -112,12 +114,31 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
List<TetrioPlayer> states = []; List<TetrioPlayer> states = [];
if (isTracking){ if (isTracking){
teto.storeState(me); teto.storeState(me);
teto.saveTLMatchesFromStream(await teto.getTLStream(me.userId));
states.addAll(await teto.getPlayer(me.userId)); states.addAll(await teto.getPlayer(me.userId));
} }
Map<String, dynamic> records = await teto.fetchRecords(me.userId); Map<String, dynamic> records = await teto.fetchRecords(me.userId);
return [me, records, states, isTracking]; return [me, records, states, isTracking];
} }
Future<List<TetraLeagueAlphaRecord>> getTLMatches(String userID) async {
var fetched = await teto.getTLStream(userID);
bool isTracked = await teto.isPlayerTracking(userID);
if (!isTracked) return fetched.records;
teto.saveTLMatchesFromStream(fetched);
var fromdb = await teto.getTLMatchesbyPlayerID(userID);
for (var match in fetched.records) {
if (!fromdb.contains(match)) fromdb.add(match);
}
fromdb.sort((a, b) {
if(a.timestamp.isBefore(b.timestamp)) return 1;
if(a.timestamp.isAtSameMomentAs(b.timestamp)) return 0;
if(a.timestamp.isAfter(b.timestamp)) return -1;
return 0;
});
return fromdb;
}
void _justUpdate() { void _justUpdate() {
setState(() {}); setState(() {});
} }
@ -238,7 +259,7 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
TLThingy( TLThingy(
tl: snapshot.data![0].tlSeason1, tl: snapshot.data![0].tlSeason1,
userID: snapshot.data![0].userId), userID: snapshot.data![0].userId),
_TLRecords(userID: snapshot.data![0].userId), _TLRecords(userID: snapshot.data![0].userId, get: getTLMatches,),
_TLHistory(states: snapshot.data![2]), _TLHistory(states: snapshot.data![2]),
_RecordThingy( _RecordThingy(
record: (snapshot.data![1]['sprint'].isNotEmpty) record: (snapshot.data![1]['sprint'].isNotEmpty)
@ -380,13 +401,14 @@ class _NavDrawerState extends State<NavDrawer> {
class _TLRecords extends StatelessWidget { class _TLRecords extends StatelessWidget {
final String userID; final String userID;
final Future<List<TetraLeagueAlphaRecord>> Function(String user) get;
const _TLRecords({required this.userID}); const _TLRecords({required this.userID, required this.get});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FutureBuilder( return FutureBuilder(
future: teto.getTLStream(userID), future: get(userID),
builder: (context, snapshot) { builder: (context, snapshot) {
switch (snapshot.connectionState) { switch (snapshot.connectionState) {
case ConnectionState.none: case ConnectionState.none:
@ -400,19 +422,19 @@ class _TLRecords extends StatelessWidget {
} else { } else {
return ListView( return ListView(
physics: const ClampingScrollPhysics(), physics: const ClampingScrollPhysics(),
children: (snapshot.data!.records!.isNotEmpty) children: (snapshot.data!.isNotEmpty)
? [for (var value in snapshot.data!.records!) ListTile( ? [for (var value in snapshot.data!) ListTile(
leading: Text("${value.endContext.firstWhere((element) => element.userId == userID).points} : ${value.endContext.firstWhere((element) => element.userId != userID).points}", leading: Text("${value.endContext.firstWhere((element) => element.userId == userID).points} : ${value.endContext.firstWhere((element) => element.userId != userID).points}",
style: const TextStyle( style: const TextStyle(
fontFamily: "Eurostile Round Extended", fontFamily: "Eurostile Round Extended",
fontSize: 28,)), fontSize: 28,)),
title: Text("vs. ${value.endContext.firstWhere((element) => element.userId != userID).username}"), title: Text("vs. ${value.endContext.firstWhere((element) => element.userId != userID).username}"),
subtitle: Text(dateFormat.format(value.timestamp!)), subtitle: Text(dateFormat.format(value.timestamp)),
trailing: Column(mainAxisAlignment: MainAxisAlignment.end, trailing: Column(mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text("${f2.format(value.endContext.firstWhere((element) => element.userId == userID).secondary)} : ${f2.format(value.endContext.firstWhere((element) => element.userId != userID).secondary)} APM", style: TextStyle(height: 1.1)), Text("${f2.format(value.endContext.firstWhere((element) => element.userId == userID).secondary)} : ${f2.format(value.endContext.firstWhere((element) => element.userId != userID).secondary)} APM", style: const TextStyle(height: 1.1)),
Text("${f2.format(value.endContext.firstWhere((element) => element.userId == userID).tertiary)} : ${f2.format(value.endContext.firstWhere((element) => element.userId != userID).tertiary)} PPS", style: TextStyle(height: 1.1)), Text("${f2.format(value.endContext.firstWhere((element) => element.userId == userID).tertiary)} : ${f2.format(value.endContext.firstWhere((element) => element.userId != userID).tertiary)} PPS", style: const TextStyle(height: 1.1)),
Text("${f2.format(value.endContext.firstWhere((element) => element.userId == userID).extra)} : ${f2.format(value.endContext.firstWhere((element) => element.userId != userID).extra)} VS", style: TextStyle(height: 1.1)), Text("${f2.format(value.endContext.firstWhere((element) => element.userId == userID).extra)} : ${f2.format(value.endContext.firstWhere((element) => element.userId != userID).extra)} VS", style: const TextStyle(height: 1.1)),
]), ]),
onTap: (){Navigator.push( onTap: (){Navigator.push(
context, context,
@ -431,23 +453,47 @@ class _TLRecords extends StatelessWidget {
class _TLHistory extends StatelessWidget{ class _TLHistory extends StatelessWidget{
final List<TetrioPlayer> states; final List<TetrioPlayer> states;
const _TLHistory({super.key, required this.states}); const _TLHistory({required this.states});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool bigScreen = MediaQuery.of(context).size.width > 768; bool bigScreen = MediaQuery.of(context).size.width > 768;
List<FlSpot> trData = [for (var state in states) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.rating)]; List<FlSpot> trData = [for (var state in states) if (state.tlSeason1.gamesPlayed > 9) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.rating)];
List<FlSpot> apmData = [for (var state in states) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.apm!)]; List<FlSpot> apmData = [for (var state in states) if (state.tlSeason1.apm != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.apm!)];
List<FlSpot> ppsData = [for (var state in states) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.pps!)]; List<FlSpot> ppsData = [for (var state in states) if (state.tlSeason1.pps != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.pps!)];
List<FlSpot> vsData = [for (var state in states) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.vs!)]; List<FlSpot> vsData = [for (var state in states) if (state.tlSeason1.vs != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.vs!)];
List<FlSpot> appData = [for (var state in states) if (state.tlSeason1.nerdStats != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.nerdStats!.app)];
List<FlSpot> dssData = [for (var state in states) if (state.tlSeason1.nerdStats != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.nerdStats!.dss)];
List<FlSpot> dspData = [for (var state in states) if (state.tlSeason1.nerdStats != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.nerdStats!.dsp)];
List<FlSpot> appdspData = [for (var state in states) if (state.tlSeason1.nerdStats != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.nerdStats!.appdsp)];
List<FlSpot> vsapmData = [for (var state in states) if (state.tlSeason1.nerdStats != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.nerdStats!.vsapm)];
List<FlSpot> cheeseData = [for (var state in states) if (state.tlSeason1.nerdStats != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.nerdStats!.cheese)];
List<FlSpot> gbeData = [for (var state in states) if (state.tlSeason1.nerdStats != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.nerdStats!.gbe)];
List<FlSpot> nyaappData = [for (var state in states) if (state.tlSeason1.nerdStats != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.nerdStats!.nyaapp)];
List<FlSpot> areaData = [for (var state in states) if (state.tlSeason1.nerdStats != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.nerdStats!.area)];
List<FlSpot> estTrData = [for (var state in states) if (state.tlSeason1.estTr != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.estTr!.esttr)];
List<FlSpot> estaccData = [for (var state in states) if (state.tlSeason1.esttracc != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.esttracc!)];
return ListView(physics: const ClampingScrollPhysics(), return ListView(physics: const ClampingScrollPhysics(),
children: states.isNotEmpty ? [ children: states.isNotEmpty ? [
Column( Column(
children: [ children: [
_HistoryChartThigy(data: trData, title: "Tetra Rating", yAxisTitle: "TR", bigScreen: bigScreen), if(trData.length > 1) _HistoryChartThigy(data: trData, title: "Tetra Rating", yAxisTitle: "TR", bigScreen: bigScreen, leftSpace: bigScreen? 80 : 45, yFormat: bigScreen? f2 : NumberFormat.compact(),),
_HistoryChartThigy(data: apmData, title: "Attack Per Minute", yAxisTitle: "APM", bigScreen: bigScreen), if(apmData.length > 1) _HistoryChartThigy(data: apmData, title: "Attack Per Minute", yAxisTitle: "APM", bigScreen: bigScreen, leftSpace: 40, yFormat: NumberFormat.compact(),),
_HistoryChartThigy(data: ppsData, title: "Pieces Per Second", yAxisTitle: "PPS", bigScreen: bigScreen), if(ppsData.length > 1) _HistoryChartThigy(data: ppsData, title: "Pieces Per Second", yAxisTitle: "PPS", bigScreen: bigScreen, leftSpace: 40, yFormat: NumberFormat.compact(),),
_HistoryChartThigy(data: vsData, title: "Versus Score", yAxisTitle: "VS", bigScreen: bigScreen), if(vsData.length > 1) _HistoryChartThigy(data: vsData, title: "Versus Score", yAxisTitle: "VS", bigScreen: bigScreen, leftSpace: 40, yFormat: NumberFormat.compact(),),
if(appData.length > 1) _HistoryChartThigy(data: appData, title: "Attack Per Piece", yAxisTitle: "APP", bigScreen: bigScreen, leftSpace: 48, yFormat: NumberFormat.compact(),),
if(dssData.length > 1) _HistoryChartThigy(data: dssData, title: bigScreen ? "Downstack Per Second" : "Downstack\nPer Second", yAxisTitle: "DS/S", bigScreen: bigScreen, leftSpace: 48, yFormat: NumberFormat.compact(),),
if(dspData.length > 1) _HistoryChartThigy(data: dspData, title: bigScreen ? "Downstack Per Piece" : "Downstack\nPer Piece", yAxisTitle: "DS/P", bigScreen: bigScreen, leftSpace: 48, yFormat: NumberFormat.compact(),),
if(appdspData.length > 1) _HistoryChartThigy(data: appdspData, title: "APP + DS/P", yAxisTitle: "APP + DS/P", bigScreen: bigScreen, leftSpace: 48, yFormat: NumberFormat.compact(),),
if(vsapmData.length > 1) _HistoryChartThigy(data: vsapmData, title: "VS/APM", yAxisTitle: "VS/APM", bigScreen: bigScreen, leftSpace: 48, yFormat: NumberFormat.compact(),),
if(cheeseData.length > 1) _HistoryChartThigy(data: cheeseData, title: "Cheese Index", yAxisTitle: "Cheese", bigScreen: bigScreen, leftSpace: 40, yFormat: NumberFormat.compact(),),
if(gbeData.length > 1) _HistoryChartThigy(data: gbeData, title: "Garbage Efficiency", yAxisTitle: "GbE", bigScreen: bigScreen, leftSpace: 48, yFormat: NumberFormat.compact(),),
if(nyaappData.length > 1) _HistoryChartThigy(data: nyaappData, title: "Weighted APP", yAxisTitle: "wAPP", bigScreen: bigScreen, leftSpace: 48, yFormat: NumberFormat.compact(),),
if(areaData.length > 1) _HistoryChartThigy(data: areaData, title: "Area", yAxisTitle: "Area", bigScreen: bigScreen, leftSpace: 40, yFormat: NumberFormat.compact(),),
if(estTrData.length > 1) _HistoryChartThigy(data: estTrData, title: "Est. of TR", yAxisTitle: "eTR", bigScreen: bigScreen, leftSpace: bigScreen? 80 : 45, yFormat: bigScreen? f2 : NumberFormat.compact(),),
if(estaccData.length > 1) _HistoryChartThigy(data: estaccData, title: "Accuracy of Est.", yAxisTitle: "±eTR", bigScreen: bigScreen, leftSpace: 60, yFormat: NumberFormat.compact(explicitSign: true),),
if(trData.length <= 1 || apmData.length <= 1 || ppsData.length <= 1 || vsData.length <= 1 || appData.length <= 1 || dssData.length <= 1 || dspData.length <= 1 || appdspData.length <= 1 || vsapmData.length <= 1 || cheeseData.length <= 1 || gbeData.length <= 1 || nyaappData.length <= 1 || areaData.length <= 1 || estTrData.length <= 1 || estaccData.length <= 1) const Center(child: Text("Some charts aren't shown due to lack of data...", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)))
// Why it's look like a garbage solution???
], ],
), ),
] : [const Center(child: Text("No history saved", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)))]); ] : [const Center(child: Text("No history saved", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)))]);
@ -459,37 +505,40 @@ class _HistoryChartThigy extends StatelessWidget{
final String title; final String title;
final String yAxisTitle; final String yAxisTitle;
final bool bigScreen; final bool bigScreen;
const _HistoryChartThigy({super.key, required this.data, required this.title, required this.yAxisTitle, required this.bigScreen}); final double leftSpace;
final NumberFormat yFormat;
const _HistoryChartThigy({required this.data, required this.title, required this.yAxisTitle, required this.bigScreen, required this.leftSpace, required this.yFormat});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
double xInterval = bigScreen ? max(1, (data.last.x - data.first.x) / 6) : max(1, (data.last.x - data.first.x) / 3);
return AspectRatio( return AspectRatio(
aspectRatio: bigScreen ? 1.9 : 1.1, aspectRatio: bigScreen ? 1.9 : 1.1,
child: Stack( child: Stack(
children: [ children: [
Row(mainAxisAlignment: MainAxisAlignment.center, children: [Text(title, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28))]), Row(mainAxisAlignment: MainAxisAlignment.center, children: [Text(title, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28))]),
Padding( padding: bigScreen ? const EdgeInsets.fromLTRB(40, 80, 40, 48) : const EdgeInsets.fromLTRB(0, 80, 0, 48) , Padding( padding: bigScreen ? const EdgeInsets.fromLTRB(40, 75, 40, 48) : const EdgeInsets.fromLTRB(0, 80, 0, 48) ,
child: LineChart( child: LineChart(
LineChartData( LineChartData(
lineBarsData: [LineChartBarData(spots: data)], lineBarsData: [LineChartBarData(spots: data)],
borderData: FlBorderData(show: false), borderData: FlBorderData(show: false),
gridData: FlGridData(verticalInterval: xInterval),
titlesData: FlTitlesData(topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)), titlesData: FlTitlesData(topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
rightTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)), rightTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
bottomTitles: AxisTitles(sideTitles: SideTitles(showTitles: true, reservedSize: 30, getTitlesWidget: (double value, TitleMeta meta){ bottomTitles: AxisTitles(sideTitles: SideTitles(interval: xInterval, showTitles: true, reservedSize: 30, getTitlesWidget: (double value, TitleMeta meta){
return SideTitleWidget( return value != meta.min && value != meta.max ? SideTitleWidget(
axisSide: meta.axisSide, axisSide: meta.axisSide,
angle: 0.3,
child: Text(DateFormat(DateFormat.YEAR_ABBR_MONTH_DAY).format(DateTime.fromMillisecondsSinceEpoch(value.floor()))), child: Text(DateFormat(DateFormat.YEAR_ABBR_MONTH_DAY).format(DateTime.fromMillisecondsSinceEpoch(value.floor()))),
); ) : Container();
})), })),
leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: true, reservedSize: 80, getTitlesWidget: (double value, TitleMeta meta){ leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: true, reservedSize: leftSpace, getTitlesWidget: (double value, TitleMeta meta){
return SideTitleWidget( return value != meta.min && value != meta.max ? SideTitleWidget(
axisSide: meta.axisSide, axisSide: meta.axisSide,
child: Text(f2.format(value)), child: Text(yFormat.format(value)),
); ) : Container();
}))), }))),
lineTouchData: LineTouchData(touchTooltipData: LineTouchTooltipData(getTooltipItems: (touchedSpots) { lineTouchData: LineTouchData(touchTooltipData: LineTouchTooltipData( fitInsideHorizontally: true, fitInsideVertically: true, getTooltipItems: (touchedSpots) {
return [for (var v in touchedSpots) LineTooltipItem("${f2.format(v.y)} $yAxisTitle \n", TextStyle(), children: [TextSpan(text: "${dateFormat.format(DateTime.fromMillisecondsSinceEpoch(v.x.floor()))}")])]; return [for (var v in touchedSpots) LineTooltipItem("${f4.format(v.y)} $yAxisTitle \n", const TextStyle(), children: [TextSpan(text: dateFormat.format(DateTime.fromMillisecondsSinceEpoch(v.x.floor())))])];
},)) },))
) )
), ),

View File

@ -28,7 +28,7 @@ class StatesState extends State<StatesView> {
itemBuilder: (context, index) { itemBuilder: (context, index) {
return ListTile( return ListTile(
title: Text("On ${dateFormat.format(widget.states[index].state)}"), title: Text("On ${dateFormat.format(widget.states[index].state)}"),
subtitle: Text("Level ${widget.states[index].level.toStringAsFixed(2)} level, ${widget.states[index].gameTime} of gametime"), subtitle: Text("Level ${widget.states[index].level.toStringAsFixed(2)}, ${widget.states[index].gameTime} of gametime, ${widget.states[index].friendCount} friends, ${NumberFormat.compact().format(widget.states[index].tlSeason1.rd)} RD"),
trailing: IconButton( trailing: IconButton(
icon: const Icon(Icons.delete_forever), icon: const Icon(Icons.delete_forever),
onPressed: () { onPressed: () {

View File

@ -31,7 +31,7 @@ class TlMatchResultState extends State<TlMatchResultView> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text( title: Text(
"${widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).username.toUpperCase()} vs. ${widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).username.toUpperCase()} in TL match ${dateFormat.format(widget.record.timestamp!)}"), "${widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).username.toUpperCase()} vs. ${widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).username.toUpperCase()} in TL match ${dateFormat.format(widget.record.timestamp)}"),
), ),
backgroundColor: Colors.black, backgroundColor: Colors.black,
body: SafeArea( body: SafeArea(
@ -48,19 +48,19 @@ class TlMatchResultState extends State<TlMatchResultView> {
children: [ children: [
Expanded( Expanded(
child: Container( child: Container(
decoration: const BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [Colors.green, Colors.transparent], colors: const [Colors.green, Colors.transparent],
begin: Alignment.bottomCenter, begin: Alignment.bottomCenter,
end: Alignment.topCenter, end: Alignment.topCenter,
stops: [0.0, 0.4], stops: [0.0, widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).success ? 0.4 : 0.0],
)), )),
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
child: Column(children: [ child: Column(children: [
Text(widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).username, style: const TextStyle( Text(widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).username, style: bigScreen ? const TextStyle(
fontFamily: "Eurostile Round Extended", fontFamily: "Eurostile Round Extended",
fontSize: 28)), fontSize: 28) : const TextStyle()),
Text(widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).points.toString(), style: const TextStyle( Text(widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).points.toString(), style: const TextStyle(
fontFamily: "Eurostile Round Extended", fontFamily: "Eurostile Round Extended",
fontSize: 42)) fontSize: 42))
@ -74,19 +74,19 @@ class TlMatchResultState extends State<TlMatchResultView> {
), ),
Expanded( Expanded(
child: Container( child: Container(
decoration: const BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [Colors.red, Colors.transparent], colors: const [Colors.red, Colors.transparent],
begin: Alignment.bottomCenter, begin: Alignment.bottomCenter,
end: Alignment.topCenter, end: Alignment.topCenter,
stops: [0.0, 0.4], stops: [0.0, widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).success ? 0.4 : 0.0],
)), )),
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
child: Column(children: [ child: Column(children: [
Text(widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).username, style: const TextStyle( Text(widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).username, style: bigScreen ? const TextStyle(
fontFamily: "Eurostile Round Extended", fontFamily: "Eurostile Round Extended",
fontSize: 28)), fontSize: 28) : const TextStyle()),
Text(widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).points.toString(), style: const TextStyle( Text(widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).points.toString(), style: const TextStyle(
fontFamily: "Eurostile Round Extended", fontFamily: "Eurostile Round Extended",
fontSize: 42)) fontSize: 42))
@ -779,8 +779,9 @@ class CompareRegTimeThingy extends StatelessWidget {
String verdict(DateTime? greenSide, DateTime? redSide) { String verdict(DateTime? greenSide, DateTime? redSide) {
var f = NumberFormat("#,### days later;#,### days before"); var f = NumberFormat("#,### days later;#,### days before");
String result = "---"; String result = "---";
if (greenSide != null && redSide != null) if (greenSide != null && redSide != null) {
result = f.format(greenSide.difference(redSide).inDays); result = f.format(greenSide.difference(redSide).inDays);
}
return result; return result;
} }

View File

@ -206,7 +206,7 @@ class TLThingy extends StatelessWidget {
RadarEntry(value: tl.nerdStats!.dss * dssWeight), RadarEntry(value: tl.nerdStats!.dss * dssWeight),
RadarEntry(value: tl.nerdStats!.dsp * dspWeight), RadarEntry(value: tl.nerdStats!.dsp * dspWeight),
RadarEntry(value: tl.nerdStats!.appdsp * appdspWeight), RadarEntry(value: tl.nerdStats!.appdsp * appdspWeight),
RadarEntry(value: tl.nerdStats!.vsapm * vsWeight), RadarEntry(value: tl.nerdStats!.vsapm * vsapmWeight),
RadarEntry(value: tl.nerdStats!.cheese * cheeseWeight), RadarEntry(value: tl.nerdStats!.cheese * cheeseWeight),
RadarEntry(value: tl.nerdStats!.gbe * gbeWeight), RadarEntry(value: tl.nerdStats!.gbe * gbeWeight),
], ],

View File

@ -48,9 +48,7 @@ class UserThingy extends StatelessWidget {
height: bannerHeight, height: bannerHeight,
errorBuilder: (context, error, stackTrace) { errorBuilder: (context, error, stackTrace) {
developer.log("Error with building banner image", name: "main_view", error: error, stackTrace: stackTrace); developer.log("Error with building banner image", name: "main_view", error: error, stackTrace: stackTrace);
return const Placeholder( return Container();
color: Colors.black,
);
}, },
), ),
Container( Container(

View File

@ -85,7 +85,12 @@ flutter:
- res/tetrio_badges/early-supporter.png - res/tetrio_badges/early-supporter.png
- res/tetrio_badges/founder.png - res/tetrio_badges/founder.png
- res/tetrio_badges/galactic2x2_1.png - res/tetrio_badges/galactic2x2_1.png
- res/tetrio_badges/ggc_1.png
- res/tetrio_badges/ggc_2.png - res/tetrio_badges/ggc_2.png
- res/tetrio_badges/ggc_3.png
- res/tetrio_badges/hdoxii_1.png
- res/tetrio_badges/hdoxii_2.png
- res/tetrio_badges/hdoxii_3.png
- res/tetrio_badges/heart.png - res/tetrio_badges/heart.png
- res/tetrio_badges/hnprism_1.png - res/tetrio_badges/hnprism_1.png
- res/tetrio_badges/hnprism_2.png - res/tetrio_badges/hnprism_2.png

BIN
res/tetrio_badges/ggc_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

BIN
res/tetrio_badges/ggc_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB