diff --git a/README.md b/README.md index 5a85a5a..4186b90 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@ - ~~Ability to compare 2 players~~ *v0.1.0, we are here* - ~~Stats Calculator~~ - ~~Ability to compare player with himself in past~~ -- ~~Tetra League matches history~~ *dev build are here* -- ~~Tetra League historic charts for tracked players~~ (bit mess idk) +- ~~Tetra League matches history~~ +- ~~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* - Ability to compare player with APM-PPS-VS stats - Ability to fetch Tetra League leaderboard @@ -22,7 +22,7 @@ - UI Animations - i18n, EN and RU locales - Talk with osk about CORS and EndContext in TL matches -- RELEASE ??? +- RELEASE ??? *that will be v1.0.0* --- diff --git a/android/build.gradle b/android/build.gradle index 6b815dd..713d7f6 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,31 +1,31 @@ -buildscript { - ext.kotlin_version = '1.7.10' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.2.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -allprojects { - repositories { - google() - mavenCentral() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -task clean(type: Delete) { - delete rootProject.buildDir -} +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.2.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/lib/data_objects/tetrio.dart b/lib/data_objects/tetrio.dart index e9eacf9..ba3d586 100644 --- a/lib/data_objects/tetrio.dart +++ b/lib/data_objects/tetrio.dart @@ -430,10 +430,10 @@ class Handling { Map toJson() { final Map data = {}; - data['arr'] = arr; - data['das'] = das; - data['dcd'] = dcd; - data['sdf'] = sdf; + data['arr'] = arr.toDouble(); + data['das'] = das.toDouble(); + data['dcd'] = dcd.toDouble(); + data['sdf'] = sdf.toDouble(); data['safelock'] = safeLock; data['cancel'] = cancel; return data; @@ -526,24 +526,24 @@ class Playstyle { class TetraLeagueAlphaStream{ late String userId; - List? records; + late List records; - TetraLeagueAlphaStream({required this.userId, this.records}); + TetraLeagueAlphaStream({required this.userId, required this.records}); TetraLeagueAlphaStream.fromJson(List json, String userID) { userId = userID; records = []; - for (var value in json) {records!.add(TetraLeagueAlphaRecord.fromJson(value));} + for (var value in json) {records.add(TetraLeagueAlphaRecord.fromJson(value));} } } class TetraLeagueAlphaRecord{ late String replayId; late String ownId; - DateTime? timestamp; + late DateTime timestamp; late List 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 json) { ownId = json['_id']; @@ -561,6 +561,10 @@ class TetraLeagueAlphaRecord{ data['ts'] = timestamp; return data; } + + @override + bool operator ==(covariant TetraLeagueAlphaRecord other) => ownId == other.ownId; + @override String toString() { return "TetraLeagueAlphaRecord: ${endContext.first.userId} vs ${endContext.last.userId}"; @@ -577,11 +581,11 @@ class EndContextMulti { late int points; late int wins; late double secondary; - late List secondaryTracking; + late List secondaryTracking; late double tertiary; - late List tertiaryTracking; + late List tertiaryTracking; late double extra; - late List extraTracking; + late List extraTracking; late bool success; late NerdStats nerdStats; late EstTr estTr; @@ -616,10 +620,10 @@ class EndContextMulti { points = json['points']['primary']; secondary = json['points']['secondary'].toDouble(); tertiary = json['points']['tertiary'].toDouble(); - secondaryTracking = json['points']['secondaryAvgTracking'].cast(); - tertiaryTracking = json['points']['tertiaryAvgTracking'].cast(); + secondaryTracking = json['points']['secondaryAvgTracking'].map((e) => e.toDouble()).toList(); + tertiaryTracking = json['points']['tertiaryAvgTracking'].map((e) => e.toDouble()).toList(); extra = json['points']['extra']['vs'].toDouble(); - extraTracking = json['points']['extraAvgTracking']['aggregatestats___vsscore'].cast(); + extraTracking = json['points']['extraAvgTracking']['aggregatestats___vsscore'].map((e) => e.toDouble()).toList(); nerdStats = NerdStats(secondary, tertiary, extra); 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); @@ -627,19 +631,14 @@ class EndContextMulti { Map toJson() { final Map data = {}; - data['user']['_id'] = userId; - data['user']['username'] = username; + data['user'] = {'_id': userId, 'username': username}; data['handling'] = handling.toJson(); data['success'] = success; data['inputs'] = inputs; data['piecesplaced'] = piecesPlaced; data['naturalorder'] = naturalOrder; data['wins'] = wins; - data['points']['primary'] = points; - data['points']['secondary'] = secondary; - data['points']['tertiary'] = tertiary; - data['points']['extra']['vs'] = extra; - data['points']['extraAvgTracking']['aggregatestats___vsscore'] = extraTracking; + data['points'] = {'primary': points, 'secondary': secondary, 'tertiary':tertiary, 'extra': {'vs': extra}, 'secondaryAvgTracking': secondaryTracking, 'tertiaryAvgTracking': tertiaryTracking, 'extraAvgTracking': {'aggregatestats___vsscore': extraTracking}}; return data; } } diff --git a/lib/services/sqlite_db_controller.dart b/lib/services/sqlite_db_controller.dart index 0c878b0..2daa851 100644 --- a/lib/services/sqlite_db_controller.dart +++ b/lib/services/sqlite_db_controller.dart @@ -20,6 +20,7 @@ class DB { _db = db; await db.execute(createTetrioUsersTable); await db.execute(createTetrioUsersToTrack); + await db.execute(createTetrioTLRecordsTable); } on MissingPlatformDirectoryException { throw UnableToGetDocuments(); } diff --git a/lib/services/tetrio_crud.dart b/lib/services/tetrio_crud.dart index 69c9e3c..577bfbf 100644 --- a/lib/services/tetrio_crud.dart +++ b/lib/services/tetrio_crud.dart @@ -9,10 +9,16 @@ import 'package:tetra_stats/data_objects/tetrio.dart'; const String dbName = "TetraStats.db"; const String tetrioUsersTable = "tetrioUsers"; const String tetrioUsersToTrackTable = "tetrioUsersToTrack"; -const String tetraLeagueMatchesTable = "tetraLeagueMatches"; +const String tetraLeagueMatchesTable = "tetrioAlphaLeagueMathces"; const String idCol = "id"; +const String replayID = "replayId"; const String nickCol = "nickname"; +const String timestamp = "timestamp"; +const String endContext1 = "endContext1"; +const String endContext2 = "endContext2"; const String statesCol = "jsonStates"; +const String player1id = "player1id"; +const String player2id = "player2id"; const String createTetrioUsersTable = ''' CREATE TABLE IF NOT EXISTS "tetrioUsers" ( "id" TEXT UNIQUE, @@ -26,6 +32,17 @@ const String createTetrioUsersToTrack = ''' 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 { Map> _players = {}; @@ -108,6 +125,27 @@ class TetrioService extends DB { } } + Future 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> getTLMatchesbyPlayerID(String playerID) async { + ensureDbIsOpen(); + final db = getDatabaseOrThrow(); + List 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> fetchRecords(String userID) async { try{ var cached = _recordsCache.entries.firstWhere((element) => element.value['user'] == userID); diff --git a/lib/views/compare_view.dart b/lib/views/compare_view.dart index 40448fc..9fc6cb9 100644 --- a/lib/views/compare_view.dart +++ b/lib/views/compare_view.dart @@ -1194,8 +1194,9 @@ class CompareRegTimeThingy extends StatelessWidget { String verdict(DateTime? greenSide, DateTime? redSide) { var f = NumberFormat("#,### days later;#,### days before"); String result = "---"; - if (greenSide != null && redSide != null) + if (greenSide != null && redSide != null) { result = f.format(greenSide.difference(redSide).inDays); + } return result; } diff --git a/lib/views/main_view.dart b/lib/views/main_view.dart index 4bdb67e..ccaa900 100644 --- a/lib/views/main_view.dart +++ b/lib/views/main_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import 'dart:math'; import 'package:fl_chart/fl_chart.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter/services.dart'; @@ -21,6 +22,7 @@ const allowedHeightForPlayerBioInPixels = 30.0; const givenTextHeightByScreenPercentage = 0.3; final NumberFormat timeInSec = NumberFormat("#,###.###s."); final NumberFormat f2 = NumberFormat.decimalPatternDigits(decimalDigits: 2); +final NumberFormat f4 = NumberFormat.decimalPatternDigits(decimalDigits: 4); final DateFormat dateFormat = DateFormat.yMMMd().add_Hms(); class MainView extends StatefulWidget { @@ -112,12 +114,31 @@ class _MainState extends State with SingleTickerProviderStateMixin { List states = []; if (isTracking){ teto.storeState(me); + teto.saveTLMatchesFromStream(await teto.getTLStream(me.userId)); states.addAll(await teto.getPlayer(me.userId)); } Map records = await teto.fetchRecords(me.userId); return [me, records, states, isTracking]; } + Future> 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() { setState(() {}); } @@ -238,7 +259,7 @@ class _MainState extends State with SingleTickerProviderStateMixin { TLThingy( tl: snapshot.data![0].tlSeason1, userID: snapshot.data![0].userId), - _TLRecords(userID: snapshot.data![0].userId), + _TLRecords(userID: snapshot.data![0].userId, get: getTLMatches,), _TLHistory(states: snapshot.data![2]), _RecordThingy( record: (snapshot.data![1]['sprint'].isNotEmpty) @@ -380,13 +401,14 @@ class _NavDrawerState extends State { class _TLRecords extends StatelessWidget { final String userID; + final Future> Function(String user) get; - const _TLRecords({required this.userID}); + const _TLRecords({required this.userID, required this.get}); @override Widget build(BuildContext context) { return FutureBuilder( - future: teto.getTLStream(userID), + future: get(userID), builder: (context, snapshot) { switch (snapshot.connectionState) { case ConnectionState.none: @@ -400,19 +422,19 @@ class _TLRecords extends StatelessWidget { } else { return ListView( physics: const ClampingScrollPhysics(), - children: (snapshot.data!.records!.isNotEmpty) - ? [for (var value in snapshot.data!.records!) ListTile( + children: (snapshot.data!.isNotEmpty) + ? [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}", style: const TextStyle( fontFamily: "Eurostile Round Extended", fontSize: 28,)), title: Text("vs. ${value.endContext.firstWhere((element) => element.userId != userID).username}"), - subtitle: Text(dateFormat.format(value.timestamp!)), - trailing: Column(mainAxisAlignment: MainAxisAlignment.end, + subtitle: Text(dateFormat.format(value.timestamp)), + trailing: Column(mainAxisAlignment: MainAxisAlignment.center, 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).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).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).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: 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: const TextStyle(height: 1.1)), ]), onTap: (){Navigator.push( context, @@ -431,23 +453,47 @@ class _TLRecords extends StatelessWidget { class _TLHistory extends StatelessWidget{ final List states; - const _TLHistory({super.key, required this.states}); + const _TLHistory({required this.states}); @override Widget build(BuildContext context) { bool bigScreen = MediaQuery.of(context).size.width > 768; - List trData = [for (var state in states) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.rating)]; - List apmData = [for (var state in states) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.apm!)]; - List ppsData = [for (var state in states) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.pps!)]; - List vsData = [for (var state in states) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.vs!)]; + List trData = [for (var state in states) if (state.tlSeason1.gamesPlayed > 9) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.rating)]; + List apmData = [for (var state in states) if (state.tlSeason1.apm != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.apm!)]; + List ppsData = [for (var state in states) if (state.tlSeason1.pps != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.pps!)]; + List vsData = [for (var state in states) if (state.tlSeason1.vs != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.vs!)]; + List appData = [for (var state in states) if (state.tlSeason1.nerdStats != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.nerdStats!.app)]; + List dssData = [for (var state in states) if (state.tlSeason1.nerdStats != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.nerdStats!.dss)]; + List dspData = [for (var state in states) if (state.tlSeason1.nerdStats != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.nerdStats!.dsp)]; + List appdspData = [for (var state in states) if (state.tlSeason1.nerdStats != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.nerdStats!.appdsp)]; + List vsapmData = [for (var state in states) if (state.tlSeason1.nerdStats != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.nerdStats!.vsapm)]; + List cheeseData = [for (var state in states) if (state.tlSeason1.nerdStats != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.nerdStats!.cheese)]; + List gbeData = [for (var state in states) if (state.tlSeason1.nerdStats != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.nerdStats!.gbe)]; + List nyaappData = [for (var state in states) if (state.tlSeason1.nerdStats != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.nerdStats!.nyaapp)]; + List areaData = [for (var state in states) if (state.tlSeason1.nerdStats != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.nerdStats!.area)]; + List estTrData = [for (var state in states) if (state.tlSeason1.estTr != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.estTr!.esttr)]; + List estaccData = [for (var state in states) if (state.tlSeason1.esttracc != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.esttracc!)]; return ListView(physics: const ClampingScrollPhysics(), children: states.isNotEmpty ? [ Column( children: [ - _HistoryChartThigy(data: trData, title: "Tetra Rating", yAxisTitle: "TR", bigScreen: bigScreen), - _HistoryChartThigy(data: apmData, title: "Attack Per Minute", yAxisTitle: "APM", bigScreen: bigScreen), - _HistoryChartThigy(data: ppsData, title: "Pieces Per Second", yAxisTitle: "PPS", bigScreen: bigScreen), - _HistoryChartThigy(data: vsData, title: "Versus Score", yAxisTitle: "VS", 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(),), + if(apmData.length > 1) _HistoryChartThigy(data: apmData, title: "Attack Per Minute", yAxisTitle: "APM", bigScreen: bigScreen, leftSpace: 40, yFormat: NumberFormat.compact(),), + if(ppsData.length > 1) _HistoryChartThigy(data: ppsData, title: "Pieces Per Second", yAxisTitle: "PPS", bigScreen: bigScreen, leftSpace: 40, yFormat: NumberFormat.compact(),), + 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)))]); @@ -459,37 +505,40 @@ class _HistoryChartThigy extends StatelessWidget{ final String title; final String yAxisTitle; 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 - 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( aspectRatio: bigScreen ? 1.9 : 1.1, child: Stack( children: [ - Row(mainAxisAlignment: MainAxisAlignment.center, children: [Text(title, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28))]), - Padding( padding: bigScreen ? const EdgeInsets.fromLTRB(40, 80, 40, 48) : const EdgeInsets.fromLTRB(0, 80, 0, 48) , + Row(mainAxisAlignment: MainAxisAlignment.center, children: [Text(title, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28))]), + Padding( padding: bigScreen ? const EdgeInsets.fromLTRB(40, 75, 40, 48) : const EdgeInsets.fromLTRB(0, 80, 0, 48) , child: LineChart( LineChartData( lineBarsData: [LineChartBarData(spots: data)], borderData: FlBorderData(show: false), + gridData: FlGridData(verticalInterval: xInterval), titlesData: FlTitlesData(topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)), rightTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)), - bottomTitles: AxisTitles(sideTitles: SideTitles(showTitles: true, reservedSize: 30, getTitlesWidget: (double value, TitleMeta meta){ - return SideTitleWidget( + bottomTitles: AxisTitles(sideTitles: SideTitles(interval: xInterval, showTitles: true, reservedSize: 30, getTitlesWidget: (double value, TitleMeta meta){ + return value != meta.min && value != meta.max ? SideTitleWidget( axisSide: meta.axisSide, - angle: 0.3, 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){ - return SideTitleWidget( + leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: true, reservedSize: leftSpace, getTitlesWidget: (double value, TitleMeta meta){ + return value != meta.min && value != meta.max ? SideTitleWidget( axisSide: meta.axisSide, - child: Text(f2.format(value)), - ); + child: Text(yFormat.format(value)), + ) : Container(); }))), - lineTouchData: LineTouchData(touchTooltipData: LineTouchTooltipData(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()))}")])]; + lineTouchData: LineTouchData(touchTooltipData: LineTouchTooltipData( fitInsideHorizontally: true, fitInsideVertically: true, getTooltipItems: (touchedSpots) { + 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())))])]; },)) ) ), diff --git a/lib/views/states_view.dart b/lib/views/states_view.dart index 73abd02..c769895 100644 --- a/lib/views/states_view.dart +++ b/lib/views/states_view.dart @@ -28,7 +28,7 @@ class StatesState extends State { itemBuilder: (context, index) { return ListTile( 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( icon: const Icon(Icons.delete_forever), onPressed: () { diff --git a/lib/views/tl_match_view.dart b/lib/views/tl_match_view.dart index 46ffd64..5d69c87 100644 --- a/lib/views/tl_match_view.dart +++ b/lib/views/tl_match_view.dart @@ -31,7 +31,7 @@ class TlMatchResultState extends State { return Scaffold( appBar: AppBar( 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, body: SafeArea( @@ -48,19 +48,19 @@ class TlMatchResultState extends State { children: [ Expanded( child: Container( - decoration: const BoxDecoration( + decoration: BoxDecoration( gradient: LinearGradient( - colors: [Colors.green, Colors.transparent], + colors: const [Colors.green, Colors.transparent], begin: Alignment.bottomCenter, 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( padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), 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", - fontSize: 28)), + fontSize: 28) : const TextStyle()), Text(widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).points.toString(), style: const TextStyle( fontFamily: "Eurostile Round Extended", fontSize: 42)) @@ -74,19 +74,19 @@ class TlMatchResultState extends State { ), Expanded( child: Container( - decoration: const BoxDecoration( + decoration: BoxDecoration( gradient: LinearGradient( - colors: [Colors.red, Colors.transparent], + colors: const [Colors.red, Colors.transparent], begin: Alignment.bottomCenter, 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( padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), 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", - fontSize: 28)), + fontSize: 28) : const TextStyle()), Text(widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).points.toString(), style: const TextStyle( fontFamily: "Eurostile Round Extended", fontSize: 42)) @@ -779,8 +779,9 @@ class CompareRegTimeThingy extends StatelessWidget { String verdict(DateTime? greenSide, DateTime? redSide) { var f = NumberFormat("#,### days later;#,### days before"); String result = "---"; - if (greenSide != null && redSide != null) + if (greenSide != null && redSide != null) { result = f.format(greenSide.difference(redSide).inDays); + } return result; } diff --git a/lib/widgets/tl_thingy.dart b/lib/widgets/tl_thingy.dart index 907151a..00e0b26 100644 --- a/lib/widgets/tl_thingy.dart +++ b/lib/widgets/tl_thingy.dart @@ -206,7 +206,7 @@ class TLThingy extends StatelessWidget { RadarEntry(value: tl.nerdStats!.dss * dssWeight), RadarEntry(value: tl.nerdStats!.dsp * dspWeight), 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!.gbe * gbeWeight), ], diff --git a/lib/widgets/user_thingy.dart b/lib/widgets/user_thingy.dart index 0c7de58..0f2f808 100644 --- a/lib/widgets/user_thingy.dart +++ b/lib/widgets/user_thingy.dart @@ -48,9 +48,7 @@ class UserThingy extends StatelessWidget { height: bannerHeight, errorBuilder: (context, error, stackTrace) { developer.log("Error with building banner image", name: "main_view", error: error, stackTrace: stackTrace); - return const Placeholder( - color: Colors.black, - ); + return Container(); }, ), Container( diff --git a/pubspec.yaml b/pubspec.yaml index 2514616..dd896dc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -85,7 +85,12 @@ flutter: - res/tetrio_badges/early-supporter.png - res/tetrio_badges/founder.png - res/tetrio_badges/galactic2x2_1.png + - res/tetrio_badges/ggc_1.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/hnprism_1.png - res/tetrio_badges/hnprism_2.png diff --git a/res/tetrio_badges/ggc_1.png b/res/tetrio_badges/ggc_1.png new file mode 100644 index 0000000..26f25a5 Binary files /dev/null and b/res/tetrio_badges/ggc_1.png differ diff --git a/res/tetrio_badges/ggc_3.png b/res/tetrio_badges/ggc_3.png new file mode 100644 index 0000000..3b29b97 Binary files /dev/null and b/res/tetrio_badges/ggc_3.png differ diff --git a/res/tetrio_badges/hdoxii_1.png b/res/tetrio_badges/hdoxii_1.png new file mode 100644 index 0000000..0379f17 Binary files /dev/null and b/res/tetrio_badges/hdoxii_1.png differ diff --git a/res/tetrio_badges/hdoxii_2.png b/res/tetrio_badges/hdoxii_2.png new file mode 100644 index 0000000..7511e5a Binary files /dev/null and b/res/tetrio_badges/hdoxii_2.png differ diff --git a/res/tetrio_badges/hdoxii_3.png b/res/tetrio_badges/hdoxii_3.png new file mode 100644 index 0000000..5b692ab Binary files /dev/null and b/res/tetrio_badges/hdoxii_3.png differ