TL records are now stored in the database
for tracked players only. Also fixed variety of shit
This commit is contained in:
parent
5d5ac32a8b
commit
8770d5dca8
|
@ -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*
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -430,10 +430,10 @@ class Handling {
|
|||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
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<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) {
|
||||
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<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) {
|
||||
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<double> secondaryTracking;
|
||||
late List secondaryTracking;
|
||||
late double tertiary;
|
||||
late List<double> tertiaryTracking;
|
||||
late List tertiaryTracking;
|
||||
late double extra;
|
||||
late List<double> 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<double>();
|
||||
tertiaryTracking = json['points']['tertiaryAvgTracking'].cast<double>();
|
||||
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<double>();
|
||||
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<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ class DB {
|
|||
_db = db;
|
||||
await db.execute(createTetrioUsersTable);
|
||||
await db.execute(createTetrioUsersToTrack);
|
||||
await db.execute(createTetrioTLRecordsTable);
|
||||
} on MissingPlatformDirectoryException {
|
||||
throw UnableToGetDocuments();
|
||||
}
|
||||
|
|
|
@ -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<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 {
|
||||
try{
|
||||
var cached = _recordsCache.entries.firstWhere((element) => element.value['user'] == userID);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<MainView> with SingleTickerProviderStateMixin {
|
|||
List<TetrioPlayer> states = [];
|
||||
if (isTracking){
|
||||
teto.storeState(me);
|
||||
teto.saveTLMatchesFromStream(await teto.getTLStream(me.userId));
|
||||
states.addAll(await teto.getPlayer(me.userId));
|
||||
}
|
||||
Map<String, dynamic> records = await teto.fetchRecords(me.userId);
|
||||
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() {
|
||||
setState(() {});
|
||||
}
|
||||
|
@ -238,7 +259,7 @@ class _MainState extends State<MainView> 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<NavDrawer> {
|
|||
|
||||
class _TLRecords extends StatelessWidget {
|
||||
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
|
||||
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<TetrioPlayer> 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<FlSpot> trData = [for (var state in states) 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> ppsData = [for (var state in states) 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> 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) if (state.tlSeason1.apm != null) FlSpot(state.state.millisecondsSinceEpoch.toDouble(), state.tlSeason1.apm!)];
|
||||
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) 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(),
|
||||
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())))])];
|
||||
},))
|
||||
)
|
||||
),
|
||||
|
|
|
@ -28,7 +28,7 @@ class StatesState extends State<StatesView> {
|
|||
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: () {
|
||||
|
|
|
@ -31,7 +31,7 @@ class TlMatchResultState extends State<TlMatchResultView> {
|
|||
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<TlMatchResultView> {
|
|||
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<TlMatchResultView> {
|
|||
),
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
],
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 8.0 KiB |
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 |
Loading…
Reference in New Issue