`UserView` doesn't work as well, as i wanted, need different approach
This commit is contained in:
parent
d27a57f0f8
commit
7eb55f9638
|
@ -0,0 +1,602 @@
|
||||||
|
import 'dart:math';
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/est_tr.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/nerd_stats.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/playstyle.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/tetrio_constants.dart';
|
||||||
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
|
import 'package:tetra_stats/views/main_view_tiles.dart';
|
||||||
|
|
||||||
|
class DestinationCalculator extends StatefulWidget{
|
||||||
|
final BoxConstraints constraints;
|
||||||
|
|
||||||
|
const DestinationCalculator({super.key, required this.constraints});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DestinationCalculator> createState() => _DestinationCalculatorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CalcCards{
|
||||||
|
calc,
|
||||||
|
damage
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClearData{
|
||||||
|
final String title;
|
||||||
|
final Lineclears lineclear;
|
||||||
|
final int lines;
|
||||||
|
final bool miniSpin;
|
||||||
|
final bool spin;
|
||||||
|
bool perfectClear = false;
|
||||||
|
int id = -1;
|
||||||
|
|
||||||
|
ClearData(this.title, this.lineclear, this.lines, this.miniSpin, this.spin);
|
||||||
|
|
||||||
|
ClearData cloneWith(int i){
|
||||||
|
ClearData newOne = ClearData(title, lineclear, lines, miniSpin, spin)..id = i;
|
||||||
|
return newOne;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get difficultClear {
|
||||||
|
if (lines == 0) return false;
|
||||||
|
if (lines >= 4 || miniSpin || spin) return true;
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void togglePC(){
|
||||||
|
perfectClear = !perfectClear;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dealsDamage(int combo, int b2b, int previousB2B, Rules rules){
|
||||||
|
if (lines == 0) return 0;
|
||||||
|
double damage = 0;
|
||||||
|
|
||||||
|
if (spin){
|
||||||
|
if (lines <= 5) damage += garbage[lineclear]!;
|
||||||
|
else damage += garbage[Lineclears.TSPIN_PENTA]! + 2 * (lines - 5);
|
||||||
|
} else if (miniSpin){
|
||||||
|
damage += garbage[lineclear]!;
|
||||||
|
} else {
|
||||||
|
if (lines <= 5) damage += garbage[lineclear]!;
|
||||||
|
else damage += garbage[Lineclears.PENTA]! + (lines - 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (difficultClear && b2b >= 1 && rules.b2b){
|
||||||
|
if (rules.b2bChaining) damage += BACKTOBACK_BONUS * ((1 + log(1 + (b2b) * BACKTOBACK_BONUS_LOG)).floor() + (b2b == 1 ? 0 : (1 + log(1 +(b2b) * BACKTOBACK_BONUS_LOG) % 1) / 3)); // but it should be b2b-1 ???
|
||||||
|
else damage += 1; // if b2b chaining off
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rules.combo && rules.comboTable != ComboTables.none) {
|
||||||
|
if (combo >= 1){
|
||||||
|
if (lines == 1 && rules.comboTable != ComboTables.multiplier) damage += combotable[rules.comboTable]![max(0, min(combo - 1, combotable[rules.comboTable]!.length - 1))];
|
||||||
|
else damage *= (1 + COMBO_BONUS * (combo));
|
||||||
|
}
|
||||||
|
if (combo >= 2) {
|
||||||
|
damage = max(log(1 + COMBO_MINIFIER * (combo) * COMBO_MINIFIER_LOG), damage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!difficultClear && rules.surge && previousB2B >= rules.surgeInitAtB2b && b2b == -1){
|
||||||
|
damage += rules.surgeInitAmount + (previousB2B - rules.surgeInitAtB2b);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (perfectClear) damage += rules.pcDamage;
|
||||||
|
|
||||||
|
return (damage * rules.multiplier).floor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, List<ClearData>> clearsExisting = {
|
||||||
|
"No Spin Clears": [
|
||||||
|
ClearData("No lineclear (Break Combo)", Lineclears.ZERO, 0, false, false),
|
||||||
|
ClearData("Single", Lineclears.SINGLE, 1, false, false),
|
||||||
|
ClearData("Double", Lineclears.DOUBLE, 2, false, false),
|
||||||
|
ClearData("Triple", Lineclears.TRIPLE, 3, false, false),
|
||||||
|
ClearData("Quad", Lineclears.QUAD, 4, false, false)
|
||||||
|
],
|
||||||
|
"Spins": [
|
||||||
|
ClearData("Spin Zero", Lineclears.TSPIN, 0, false, true),
|
||||||
|
ClearData("Spin Single", Lineclears.TSPIN_SINGLE, 1, false, true),
|
||||||
|
ClearData("Spin Double", Lineclears.TSPIN_DOUBLE, 2, false, true),
|
||||||
|
ClearData("Spin Triple", Lineclears.TSPIN_TRIPLE, 3, false, true),
|
||||||
|
ClearData("Spin Quad", Lineclears.TSPIN_QUAD, 4, false, true),
|
||||||
|
],
|
||||||
|
"Mini spins": [
|
||||||
|
ClearData("Mini Spin Zero", Lineclears.TSPIN_MINI, 0, true, false),
|
||||||
|
ClearData("Mini Spin Single", Lineclears.TSPIN_MINI_SINGLE, 1, true, false),
|
||||||
|
ClearData("Mini Spin Double", Lineclears.TSPIN_MINI_DOUBLE, 2, true, false),
|
||||||
|
ClearData("Mini Spin Triple", Lineclears.TSPIN_MINI_TRIPLE, 3, true, false),
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
class Rules{
|
||||||
|
bool combo = true;
|
||||||
|
bool b2b = true;
|
||||||
|
bool b2bChaining = false;
|
||||||
|
bool surge = true;
|
||||||
|
int surgeInitAmount = 4;
|
||||||
|
int surgeInitAtB2b = 4;
|
||||||
|
ComboTables comboTable = ComboTables.multiplier;
|
||||||
|
int pcDamage = 5;
|
||||||
|
int pcB2B = 1;
|
||||||
|
double multiplier = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TextStyle mainToggleInRules = TextStyle(fontSize: 18, fontWeight: ui.FontWeight.w800);
|
||||||
|
|
||||||
|
class _DestinationCalculatorState extends State<DestinationCalculator> {
|
||||||
|
double? apm;
|
||||||
|
double? pps;
|
||||||
|
double? vs;
|
||||||
|
NerdStats? nerdStats;
|
||||||
|
EstTr? estTr;
|
||||||
|
Playstyle? playstyle;
|
||||||
|
TextEditingController ppsController = TextEditingController();
|
||||||
|
TextEditingController apmController = TextEditingController();
|
||||||
|
TextEditingController vsController = TextEditingController();
|
||||||
|
|
||||||
|
List<ClearData> clears = [];
|
||||||
|
Map<String, int> customClearsChoice = {
|
||||||
|
"No Spin Clears": 5,
|
||||||
|
"Spins": 5
|
||||||
|
};
|
||||||
|
int idCounter = 0;
|
||||||
|
Rules rules = Rules();
|
||||||
|
|
||||||
|
CalcCards card = CalcCards.calc;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void calc() {
|
||||||
|
apm = double.tryParse(apmController.text);
|
||||||
|
pps = double.tryParse(ppsController.text);
|
||||||
|
vs = double.tryParse(vsController.text);
|
||||||
|
if (apm != null && pps != null && vs != null) {
|
||||||
|
nerdStats = NerdStats(apm!, pps!, vs!);
|
||||||
|
estTr = EstTr(apm!, pps!, vs!, nerdStats!.app, nerdStats!.dss, nerdStats!.dsp, nerdStats!.gbe);
|
||||||
|
playstyle = Playstyle(apm!, pps!, nerdStats!.app, nerdStats!.vsapm, nerdStats!.dsp, nerdStats!.gbe, estTr!.srarea, estTr!.statrank);
|
||||||
|
setState(() {});
|
||||||
|
} else {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Please, enter valid values")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// void calcDamage(){
|
||||||
|
// for (ClearData lineclear in clears){
|
||||||
|
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
Widget getCalculator(){
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Card(
|
||||||
|
child: Center(child: Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text("Stats Calucator", style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 8.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 0.0),
|
||||||
|
child: TextField(
|
||||||
|
onSubmitted: (value) => calc(),
|
||||||
|
controller: apmController,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
decoration: const InputDecoration(suffix: Text("APM"), alignLabelWithHint: true, hintText: "Enter your APM"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 0.0),
|
||||||
|
child: TextField(
|
||||||
|
onSubmitted: (value) => calc(),
|
||||||
|
controller: ppsController,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
decoration: const InputDecoration(suffix: Text("PPS"), alignLabelWithHint: true, hintText: "Enter your PPS"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 0.0),
|
||||||
|
child: TextField(
|
||||||
|
onSubmitted: (value) => calc(),
|
||||||
|
controller: vsController,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
decoration: const InputDecoration(suffix: Text("VS"), alignLabelWithHint: true, hintText: "Enter your VS"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => calc(),
|
||||||
|
child: Text(t.calc),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (nerdStats != null) Card(
|
||||||
|
child: NerdStatsThingy(nerdStats: nerdStats!)
|
||||||
|
),
|
||||||
|
if (playstyle != null) Card(
|
||||||
|
child: GraphsThingy(nerdStats: nerdStats!, playstyle: playstyle!, apm: apm!, pps: pps!, vs: vs!)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget getDamageCalculator(){
|
||||||
|
List<Widget> rSideWidgets = [];
|
||||||
|
List<Widget> lSideWidgets = [];
|
||||||
|
|
||||||
|
for (var key in clearsExisting.keys){
|
||||||
|
rSideWidgets.add(Text(key));
|
||||||
|
for (ClearData data in clearsExisting[key]!) rSideWidgets.add(Card(
|
||||||
|
child: ListTile(
|
||||||
|
title: Text(data.title),
|
||||||
|
subtitle: Text("${data.dealsDamage(0, 0, 0, rules)} damage${data.difficultClear ? ", difficult" : ""}", style: TextStyle(color: Colors.grey)),
|
||||||
|
trailing: Icon(Icons.arrow_forward_ios),
|
||||||
|
onTap: (){
|
||||||
|
setState((){
|
||||||
|
clears.add(data.cloneWith(idCounter));
|
||||||
|
});
|
||||||
|
idCounter++;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
));
|
||||||
|
if (key != "Mini spins") rSideWidgets.add(Card(
|
||||||
|
child: ListTile(
|
||||||
|
title: Text("Custom"),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SizedBox(width: 30.0, child: TextField(
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||||
|
decoration: InputDecoration(hintText: "5"),
|
||||||
|
onChanged: (value) => customClearsChoice[key] = int.parse(value),
|
||||||
|
)),
|
||||||
|
Text(" Lines", style: TextStyle(fontSize: 18)),
|
||||||
|
Icon(Icons.arrow_forward_ios)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: (){
|
||||||
|
setState((){
|
||||||
|
clears.add(ClearData("${key == "Spins" ? "Spin " : ""}${clearNames[min(customClearsChoice[key]!, clearNames.length-1)]} (${customClearsChoice[key]!} Lines)", key == "Spins" ? Lineclears.TSPIN_PENTA : Lineclears.PENTA, customClearsChoice[key]!, false, key == "Spins").cloneWith(idCounter));
|
||||||
|
});
|
||||||
|
idCounter++;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
));
|
||||||
|
rSideWidgets.add(const Divider());
|
||||||
|
}
|
||||||
|
|
||||||
|
int combo = -1;
|
||||||
|
int b2b = -1;
|
||||||
|
int previousB2B = -1;
|
||||||
|
int totalDamage = 0;
|
||||||
|
int normalDamage = 0;
|
||||||
|
int comboDamage = 0;
|
||||||
|
int b2bDamage = 0;
|
||||||
|
int surgeDamage = 0;
|
||||||
|
int pcDamage = 0;
|
||||||
|
|
||||||
|
for (ClearData lineclear in clears){
|
||||||
|
previousB2B = b2b;
|
||||||
|
if (lineclear.difficultClear) b2b++; else if (lineclear.lines > 0) b2b = -1;
|
||||||
|
if (lineclear.lines > 0) combo++; else combo = -1;
|
||||||
|
int pcDmg = lineclear.perfectClear ? (rules.pcDamage * rules.multiplier).floor() : 0;
|
||||||
|
int normalDmg = lineclear.dealsDamage(0, 0, 0, rules) - pcDmg;
|
||||||
|
int surgeDmg = (!lineclear.difficultClear && rules.surge && previousB2B >= rules.surgeInitAtB2b && b2b == -1) ? rules.surgeInitAmount + (previousB2B - rules.surgeInitAtB2b) : 0;
|
||||||
|
int b2bDmg = lineclear.dealsDamage(0, b2b, b2b-1, rules) - normalDmg - pcDmg;
|
||||||
|
int dmg = lineclear.dealsDamage(combo, b2b, previousB2B, rules);
|
||||||
|
int comboDmg = dmg - normalDmg - b2bDmg - surgeDmg - pcDmg;
|
||||||
|
lSideWidgets.add(
|
||||||
|
ListTile(
|
||||||
|
key: ValueKey(lineclear.id),
|
||||||
|
leading: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(onPressed: (){ setState((){clears.removeWhere((element) => element.id == lineclear.id,);}); }, icon: Icon(Icons.clear)),
|
||||||
|
if (lineclear.lines > 0) IconButton(onPressed: (){ setState((){lineclear.togglePC();}); }, icon: Icon(Icons.local_parking_outlined, color: lineclear.perfectClear ? Colors.white : Colors.grey.shade800)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
title: Text("${lineclear.title}${lineclear.perfectClear ? " PC" : ""}${combo > 0 ? ", ${combo} combo" : ""}${b2b > 0 ? ", B2Bx${b2b}" : ""}"),
|
||||||
|
subtitle: lineclear.lines > 0 ? Text("${dmg == normalDmg ? "No bonuses" : ""}${b2bDmg > 0 ? "+${intf.format(b2bDmg)} for B2B" : ""}${(b2bDmg > 0 && comboDmg > 0) ? ", " : ""}${comboDmg > 0 ? "+${intf.format(comboDmg)} for combo" : ""}${(comboDmg > 0 && lineclear.perfectClear) ? ", " : ""}${lineclear.perfectClear ? "+${intf.format(pcDmg)} for PC" : ""}${(surgeDmg > 0 && (lineclear.perfectClear || comboDmg > 0)) ? ", " : ""}${surgeDmg > 0 ? "Surge released: +${intf.format(surgeDmg)}" : ""}", style: TextStyle(color: Colors.grey)) : null,
|
||||||
|
trailing: lineclear.lines > 0 ? Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 10.0),
|
||||||
|
child: Text(dmg.toString(), style: TextStyle(fontSize: 36, fontWeight: ui.FontWeight.w100)),
|
||||||
|
) : null,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
totalDamage += dmg;
|
||||||
|
normalDamage += normalDmg;
|
||||||
|
comboDamage += comboDmg;
|
||||||
|
b2bDamage += b2bDmg;
|
||||||
|
surgeDamage += surgeDmg;
|
||||||
|
pcDamage += pcDmg;
|
||||||
|
}
|
||||||
|
// values for "the bar"
|
||||||
|
double sec2end = normalDamage.toDouble()+comboDamage.toDouble();
|
||||||
|
double sec3end = normalDamage.toDouble()+comboDamage.toDouble()+b2bDamage.toDouble();
|
||||||
|
double sec4end = normalDamage.toDouble()+comboDamage.toDouble()+b2bDamage.toDouble()+surgeDamage.toDouble();
|
||||||
|
double sec5end = normalDamage.toDouble()+comboDamage.toDouble()+b2bDamage.toDouble()+surgeDamage.toDouble()+pcDamage.toDouble();
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Card(
|
||||||
|
child: Center(child: Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text("Damage Calucator", style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 350.0,
|
||||||
|
child: DefaultTabController(length: 2,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Card(
|
||||||
|
child: TabBar(tabs: [
|
||||||
|
Tab(text: "Actions"),
|
||||||
|
Tab(text: "Rules"),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: widget.constraints.maxHeight - 164,
|
||||||
|
child: TabBarView(children: [
|
||||||
|
SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: rSideWidgets,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Card(
|
||||||
|
child: ListTile(
|
||||||
|
title: Text("Multiplier", style: mainToggleInRules),
|
||||||
|
trailing: SizedBox(width: 90.0, child: TextField(
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'[0-9.]'))],
|
||||||
|
decoration: InputDecoration(hintText: rules.multiplier.toString()),
|
||||||
|
onChanged: (value) => setState((){rules.multiplier = double.parse(value);}),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Card(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
title: Text("Combo", style: mainToggleInRules),
|
||||||
|
trailing: Switch(value: rules.combo, onChanged: (v) => setState((){rules.combo = v;})),
|
||||||
|
),
|
||||||
|
if (rules.combo) ListTile(
|
||||||
|
title: Text("Combo Table"),
|
||||||
|
trailing: DropdownButton(
|
||||||
|
items: [for (var v in ComboTables.values) DropdownMenuItem(value: v.index, child: Text(v.name))],
|
||||||
|
value: rules.comboTable.index,
|
||||||
|
onChanged: (v) => setState((){rules.comboTable = ComboTables.values[v!];}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Card(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
title: Text("Back-To-Back (B2B)", style: mainToggleInRules),
|
||||||
|
trailing: Switch(value: rules.b2b, onChanged: (v) => setState((){rules.b2b = v;})),
|
||||||
|
),
|
||||||
|
if (rules.b2b) ListTile(
|
||||||
|
title: Text("Back-To-Back Chaining"),
|
||||||
|
trailing: Switch(value: rules.b2bChaining, onChanged: (v) => setState((){rules.b2bChaining = v;})),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Card(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
title: Text("Surge", style: mainToggleInRules),
|
||||||
|
trailing: Switch(value: rules.surge, onChanged: (v) => setState((){rules.surge = v;})),
|
||||||
|
),
|
||||||
|
if (rules.surge) ListTile(
|
||||||
|
title: Text("Starts at B2B"),
|
||||||
|
trailing: SizedBox(width: 90.0, child: TextField(
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||||
|
decoration: InputDecoration(hintText: rules.surgeInitAtB2b.toString()),
|
||||||
|
onChanged: (value) => setState((){rules.surgeInitAtB2b = int.parse(value);}),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
if (rules.surge) ListTile(
|
||||||
|
title: Text("Start amount"),
|
||||||
|
trailing: SizedBox(width: 90.0, child: TextField(
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||||
|
decoration: InputDecoration(hintText: rules.surgeInitAmount.toString()),
|
||||||
|
onChanged: (value) => setState((){rules.surgeInitAmount = int.parse(value);}),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: widget.constraints.maxWidth - 350 - 80,
|
||||||
|
height: widget.constraints.maxHeight - 108,
|
||||||
|
child: clears.isEmpty ? Center(child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.info_outline, size: 128.0, color: Colors.grey.shade800),
|
||||||
|
SizedBox(height: 5.0),
|
||||||
|
Text("Click on the actions on the left to add them here", textAlign: ui.TextAlign.center),
|
||||||
|
],
|
||||||
|
)) :
|
||||||
|
Card(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: ReorderableListView(
|
||||||
|
onReorder: (oldIndex, newIndex) {
|
||||||
|
setState((){
|
||||||
|
if (oldIndex < newIndex) {
|
||||||
|
newIndex -= 1;
|
||||||
|
}
|
||||||
|
final ClearData item = clears.removeAt(oldIndex);
|
||||||
|
clears.insert(newIndex, item);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
children: lSideWidgets,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Divider(),
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16.0, 0.0, 34.0, 0.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text("Total damage:", style: TextStyle(fontSize: 36, fontWeight: ui.FontWeight.w100)),
|
||||||
|
Spacer(),
|
||||||
|
Text(intf.format(totalDamage), style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, fontWeight: ui.FontWeight.w100))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
Text("Lineclears: ${intf.format(normalDamage)}"),
|
||||||
|
Text("Combo: ${intf.format(comboDamage)}"),
|
||||||
|
Text("B2B: ${intf.format(b2bDamage)}"),
|
||||||
|
Text("Surge: ${intf.format(surgeDamage)}"),
|
||||||
|
Text("PC's: ${intf.format(pcDamage)}")
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SfLinearGauge(
|
||||||
|
minimum: 0,
|
||||||
|
maximum: totalDamage.toDouble(),
|
||||||
|
showLabels: false,
|
||||||
|
showTicks: false,
|
||||||
|
ranges: [
|
||||||
|
LinearGaugeRange(
|
||||||
|
color: Colors.green,
|
||||||
|
startValue: 0,
|
||||||
|
endValue: normalDamage.toDouble(),
|
||||||
|
position: LinearElementPosition.cross,
|
||||||
|
),
|
||||||
|
LinearGaugeRange(
|
||||||
|
color: Colors.yellow,
|
||||||
|
startValue: normalDamage.toDouble(),
|
||||||
|
endValue: sec2end,
|
||||||
|
position: LinearElementPosition.cross,
|
||||||
|
),
|
||||||
|
LinearGaugeRange(
|
||||||
|
color: Colors.blue,
|
||||||
|
startValue: sec2end,
|
||||||
|
endValue: sec3end,
|
||||||
|
position: LinearElementPosition.cross,
|
||||||
|
),
|
||||||
|
LinearGaugeRange(
|
||||||
|
color: Colors.red,
|
||||||
|
startValue: sec3end,
|
||||||
|
endValue: sec4end,
|
||||||
|
position: LinearElementPosition.cross,
|
||||||
|
),
|
||||||
|
LinearGaugeRange(
|
||||||
|
color: Colors.orange,
|
||||||
|
startValue: sec4end,
|
||||||
|
endValue: sec5end,
|
||||||
|
position: LinearElementPosition.cross,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
ElevatedButton.icon(onPressed: (){setState((){clears.clear();});}, icon: const Icon(Icons.clear), label: Text("Clear all"), style: const ButtonStyle(shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12.0))))))
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: widget.constraints.maxHeight -32,
|
||||||
|
child: switch (card){
|
||||||
|
CalcCards.calc => getCalculator(),
|
||||||
|
CalcCards.damage => getDamageCalculator()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
SegmentedButton<CalcCards>(
|
||||||
|
showSelectedIcon: false,
|
||||||
|
segments: <ButtonSegment<CalcCards>>[
|
||||||
|
const ButtonSegment<CalcCards>(
|
||||||
|
value: CalcCards.calc,
|
||||||
|
label: Text('Stats Calculator'),
|
||||||
|
),
|
||||||
|
ButtonSegment<CalcCards>(
|
||||||
|
value: CalcCards.damage,
|
||||||
|
label: Text('Damage Calculator'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
selected: <CalcCards>{card},
|
||||||
|
onSelectionChanged: (Set<CalcCards> newSelection) {
|
||||||
|
setState(() {
|
||||||
|
card = newSelection.first;
|
||||||
|
});})
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,293 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/cutoff_tetrio.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/tetrio_constants.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/tetrio_player_from_leaderboard.dart';
|
||||||
|
import 'package:tetra_stats/main.dart';
|
||||||
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
|
import 'package:tetra_stats/utils/text_shadow.dart';
|
||||||
|
import 'package:tetra_stats/views/main_view_tiles.dart';
|
||||||
|
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
||||||
|
import 'package:vector_math/vector_math_64.dart' hide Colors;
|
||||||
|
|
||||||
|
class FetchCutoffsResults{
|
||||||
|
late bool success;
|
||||||
|
CutoffsTetrio? cutoffs;
|
||||||
|
Exception? exception;
|
||||||
|
|
||||||
|
FetchCutoffsResults(this.success, this.cutoffs, this.exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
class DestinationCutoffs extends StatefulWidget{
|
||||||
|
final BoxConstraints constraints;
|
||||||
|
|
||||||
|
const DestinationCutoffs({super.key, required this.constraints});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DestinationCutoffs> createState() => _DestinationCutoffsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DestinationCutoffsState extends State<DestinationCutoffs> {
|
||||||
|
|
||||||
|
Future<CutoffsTetrio> fetch() async {
|
||||||
|
TetrioPlayerFromLeaderboard top1;
|
||||||
|
CutoffsTetrio cutoffs;
|
||||||
|
List<dynamic> requests = await Future.wait([
|
||||||
|
teto.fetchCutoffsTetrio(),
|
||||||
|
teto.fetchTopOneFromTheLeaderboard(),
|
||||||
|
]);
|
||||||
|
cutoffs = requests[0];
|
||||||
|
top1 = requests[1];
|
||||||
|
cutoffs.data["top1"] = CutoffTetrio(
|
||||||
|
pos: 1,
|
||||||
|
percentile: 0.00,
|
||||||
|
tr: top1.tr,
|
||||||
|
targetTr: 25000,
|
||||||
|
apm: top1.apm,
|
||||||
|
pps: top1.pps,
|
||||||
|
vs: top1.vs,
|
||||||
|
count: 1,
|
||||||
|
countPercentile: 0.0
|
||||||
|
);
|
||||||
|
return cutoffs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FutureBuilder<CutoffsTetrio>(
|
||||||
|
future: fetch(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
switch (snapshot.connectionState){
|
||||||
|
case ConnectionState.none:
|
||||||
|
case ConnectionState.waiting:
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
case ConnectionState.active:
|
||||||
|
case ConnectionState.done:
|
||||||
|
if (snapshot.hasData){
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Card(
|
||||||
|
child: Center(child: Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text("Tetra League State", style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42)),
|
||||||
|
Text("as of ${timestamp(snapshot.data!.timestamp)}"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom:4.0),
|
||||||
|
child: Card(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 8.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 12.0),
|
||||||
|
child: Text("Actual"),
|
||||||
|
),
|
||||||
|
Text("Target")
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(12.0, 8.0, 12.0, 8.0),
|
||||||
|
child: SfLinearGauge(
|
||||||
|
minimum: 0.00000000,
|
||||||
|
maximum: 25000.0000,
|
||||||
|
showTicks: false,
|
||||||
|
showLabels: false,
|
||||||
|
ranges: [
|
||||||
|
for (var cutoff in snapshot.data!.data.keys) LinearGaugeRange(
|
||||||
|
position: LinearElementPosition.outside,
|
||||||
|
startValue: snapshot.data!.data[cutoff]!.tr,
|
||||||
|
startWidth: 20.0,
|
||||||
|
endWidth: 20.0,
|
||||||
|
endValue: switch (cutoff){
|
||||||
|
"top1" => 25000.00,
|
||||||
|
"x+" => snapshot.data!.data["top1"]!.tr,
|
||||||
|
_ => snapshot.data!.data[ranks[ranks.indexOf(cutoff)+1]]!.tr
|
||||||
|
},
|
||||||
|
color: cutoff != "top1" ? rankColors[cutoff] : Colors.grey.shade800,
|
||||||
|
),
|
||||||
|
for (var cutoff in snapshot.data!.data.keys) LinearGaugeRange(
|
||||||
|
position: LinearElementPosition.inside,
|
||||||
|
startValue: snapshot.data!.data[cutoff]!.targetTr,
|
||||||
|
endValue: switch (cutoff){
|
||||||
|
"top1" => 25000.00,
|
||||||
|
"x+" => snapshot.data!.data["top1"]!.targetTr,
|
||||||
|
_ => snapshot.data!.data[ranks[ranks.indexOf(cutoff)+1]]!.targetTr
|
||||||
|
},
|
||||||
|
color: cutoff != "top1" ? rankColors[cutoff] : null,
|
||||||
|
),
|
||||||
|
for (var cutoff in snapshot.data!.data.keys.skip(1)) if (snapshot.data!.data[cutoff]!.tr < snapshot.data!.data[cutoff]!.targetTr) LinearGaugeRange(
|
||||||
|
position: LinearElementPosition.cross,
|
||||||
|
startValue: snapshot.data!.data[cutoff]!.tr,
|
||||||
|
endValue: snapshot.data!.data[cutoff]!.targetTr,
|
||||||
|
color: Colors.green,
|
||||||
|
),
|
||||||
|
for (var cutoff in snapshot.data!.data.keys.skip(1)) if (snapshot.data!.data[ranks[ranks.indexOf(cutoff)+1]]!.tr > snapshot.data!.data[ranks[ranks.indexOf(cutoff)+1]]!.targetTr)LinearGaugeRange(
|
||||||
|
position: LinearElementPosition.cross,
|
||||||
|
startValue: snapshot.data!.data[ranks[ranks.indexOf(cutoff)+1]]!.targetTr,
|
||||||
|
endValue: snapshot.data!.data[ranks[ranks.indexOf(cutoff)+1]]!.tr,
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
markerPointers: [
|
||||||
|
for (var cutoff in snapshot.data!.data.keys) LinearWidgetPointer(child: Container(child: Text(intf.format(snapshot.data!.data[cutoff]!.tr), style: TextStyle(fontSize: 12)), transform: Matrix4.compose(Vector3(0, 35, 0), Quaternion.axisAngle(Vector3(0, 0, 1), -1), Vector3(1, 1, 1)), height: 45.0), value: snapshot.data!.data[cutoff]!.tr, position: LinearElementPosition.outside, offset: 20),
|
||||||
|
for (var cutoff in snapshot.data!.data.keys) LinearWidgetPointer(child: Container(child: Text(intf.format(snapshot.data!.data[cutoff]!.targetTr), textAlign: ui.TextAlign.right, style: TextStyle(fontSize: 12)), transform: Matrix4.compose(Vector3(-15, 0, 0), Quaternion.axisAngle(Vector3(0, 0, 1), -1), Vector3(1, 1, 1)), height: 45.0, transformAlignment: Alignment.topRight), value: snapshot.data!.data[cutoff]!.targetTr, position: LinearElementPosition.inside, offset: 6)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Table(
|
||||||
|
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
|
||||||
|
border: TableBorder.all(color: Colors.grey.shade900),
|
||||||
|
columnWidths: const {
|
||||||
|
0: FixedColumnWidth(48),
|
||||||
|
1: FixedColumnWidth(155),
|
||||||
|
2: FixedColumnWidth(140),
|
||||||
|
3: FixedColumnWidth(160),
|
||||||
|
4: FixedColumnWidth(150),
|
||||||
|
5: FixedColumnWidth(90),
|
||||||
|
6: FixedColumnWidth(130),
|
||||||
|
7: FixedColumnWidth(120),
|
||||||
|
8: FixedColumnWidth(125),
|
||||||
|
9: FixedColumnWidth(70),
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
TableRow(
|
||||||
|
children: [
|
||||||
|
Text("Rank", textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white)),
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.only(right: 8.0),
|
||||||
|
child: Text("Cutoff TR", textAlign: TextAlign.right, style: TextStyle(fontFamily: "Eurostile Round", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white)),
|
||||||
|
),
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.only(right: 8.0),
|
||||||
|
child: Text("Target TR", textAlign: TextAlign.right, style: TextStyle(fontFamily: "Eurostile Round", fontSize: 24, fontWeight: FontWeight.w100, color: Colors.white)),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: Text("State", textAlign: TextAlign.right, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white)),
|
||||||
|
),
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.only(right: 8.0),
|
||||||
|
child: Text("APM", textAlign: TextAlign.right, style: TextStyle(fontFamily: "Eurostile Round", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white)),
|
||||||
|
),
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.only(right: 8.0),
|
||||||
|
child: Text("PPS", textAlign: TextAlign.right, style: TextStyle(fontFamily: "Eurostile Round", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white)),
|
||||||
|
),
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.only(right: 8.0),
|
||||||
|
child: Text("VS", textAlign: TextAlign.right, style: TextStyle(fontFamily: "Eurostile Round", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white)),
|
||||||
|
),
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.only(right: 8.0),
|
||||||
|
child: Text("Advanced", textAlign: TextAlign.right, style: TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white)),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: Text("Players (${intf.format(snapshot.data!.total)})", textAlign: TextAlign.right, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white)),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: Text("More info", textAlign: TextAlign.right, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white)),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
for (String rank in snapshot.data!.data.keys) if (rank != "top1") TableRow(
|
||||||
|
decoration: BoxDecoration(gradient: LinearGradient(colors: [rankColors[rank]!.withAlpha(200), rankColors[rank]!.withAlpha(100)])),
|
||||||
|
children: [
|
||||||
|
Container(decoration: BoxDecoration(boxShadow: [BoxShadow(color: Colors.black.withAlpha(132), blurRadius: 32.0, blurStyle: BlurStyle.inner)]), child: Image.asset("res/tetrio_tl_alpha_ranks/$rank.png", height: 48)),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: Text(f2.format(snapshot.data!.data[rank]!.tr), textAlign: TextAlign.right, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white, shadows: textShadow)),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: Text(f2.format(snapshot.data!.data[rank]!.targetTr), textAlign: TextAlign.right, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 24, fontWeight: FontWeight.w100, color: Colors.white, shadows: textShadow)),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: RichText(
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
text: TextSpan(
|
||||||
|
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100, color: Colors.white, shadows: textShadow),
|
||||||
|
children: [
|
||||||
|
if (rank == "x+") TextSpan(text: "№ 1 is ${f2.format(snapshot.data!.data["top1"]!.tr)} TR", style: const TextStyle(color: Colors.white60, shadows: null))
|
||||||
|
else TextSpan(text: snapshot.data!.data[ranks[ranks.indexOf(rank)+1]]!.tr > snapshot.data!.data[ranks[ranks.indexOf(rank)+1]]!.targetTr ? "Inflated from ${NumberFormat.compact().format(snapshot.data!.data[rank]!.targetTr)} TR" : "Not inflated", style: TextStyle(color: snapshot.data!.data[ranks[ranks.indexOf(rank)+1]]!.tr > snapshot.data!.data[ranks[ranks.indexOf(rank)+1]]!.targetTr ? Colors.white :Colors.white60, shadows: null)),
|
||||||
|
TextSpan(text: "\n", style: const TextStyle(color: Colors.white60, shadows: null)),
|
||||||
|
if (rank == "d") TextSpan(text: "Well...", style: const TextStyle(color: Colors.white60, shadows: null))
|
||||||
|
else TextSpan(text: snapshot.data!.data[rank]!.tr < snapshot.data!.data[rank]!.targetTr ? "Deflated untill ${NumberFormat.compact().format(snapshot.data!.data[rank]!.targetTr)} TR" : "Not deflated", style: TextStyle(color: snapshot.data!.data[rank]!.tr < snapshot.data!.data[rank]!.targetTr ? Colors.white : Colors.white60, shadows: null))
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: Text(snapshot.data?.data[rank]?.apm != null ? f2.format(snapshot.data!.data[rank]!.apm) : "-.--", textAlign: TextAlign.right, style: TextStyle(fontFamily: "Eurostile Round", fontSize: 28, fontWeight: FontWeight.w100, color: snapshot.data?.data[rank]?.apm != null ? Colors.white : Colors.grey, shadows: textShadow)),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: Text(snapshot.data?.data[rank]?.pps != null ? f2.format(snapshot.data!.data[rank]!.pps) : "-.--", textAlign: TextAlign.right, style: TextStyle(fontFamily: "Eurostile Round", fontSize: 28, fontWeight: FontWeight.w100, color: snapshot.data?.data[rank]?.pps != null ? Colors.white : Colors.grey, shadows: textShadow)),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: Text(snapshot.data?.data[rank]?.vs != null ? f2.format(snapshot.data!.data[rank]!.vs) : "-.--", textAlign: TextAlign.right, style: TextStyle(fontFamily: "Eurostile Round", fontSize: 28, fontWeight: FontWeight.w100, color: snapshot.data?.data[rank]?.vs != null ? Colors.white : Colors.grey, shadows: textShadow)),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: Text("${snapshot.data?.data[rank]?.apm != null && snapshot.data?.data[rank]?.pps != null ? f3.format(snapshot.data!.data[rank]!.apm! / (snapshot.data!.data[rank]!.pps! * 60)) : "-.---"} APP\n${snapshot.data?.data[rank]?.apm != null && snapshot.data?.data[rank]?.vs != null ? f3.format(snapshot.data!.data[rank]!.vs! / snapshot.data!.data[rank]!.apm!) : "-.---"} VS/APM", textAlign: TextAlign.right, style: TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100, color: snapshot.data?.data[rank]?.apm != null && snapshot.data?.data[rank]?.pps != null && snapshot.data?.data[rank]?.vs != null ? Colors.white : Colors.grey, shadows: textShadow)),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: RichText(
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
text: TextSpan(
|
||||||
|
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100, color: Colors.white, shadows: textShadow),
|
||||||
|
children: [
|
||||||
|
TextSpan(text: intf.format(snapshot.data!.data[rank]!.count)),
|
||||||
|
TextSpan(text: " (${f2.format(snapshot.data!.data[rank]!.countPercentile * 100)}%)", style: const TextStyle(color: Colors.white60, shadows: null)),
|
||||||
|
TextSpan(text: "\n(from № ${intf.format(snapshot.data!.data[rank]!.pos)})", style: const TextStyle(color: Colors.white60, shadows: null))
|
||||||
|
]
|
||||||
|
))
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: TextButton(child: Text("View", textAlign: TextAlign.right, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white)), onPressed: () {
|
||||||
|
|
||||||
|
},),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (snapshot.hasError){ return FutureError(snapshot); }
|
||||||
|
}
|
||||||
|
return Text("huh?");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,475 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncfusion_flutter_charts/charts.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/p1nkl0bst3r.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/tetra_league.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/tetrio_constants.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/tetrio_player_from_leaderboard.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/tetrio_players_leaderboard.dart';
|
||||||
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
|
import 'package:tetra_stats/main.dart';
|
||||||
|
import 'package:tetra_stats/services/crud_exceptions.dart';
|
||||||
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
|
import 'package:tetra_stats/views/main_view_tiles.dart';
|
||||||
|
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
||||||
|
|
||||||
|
class DestinationGraphs extends StatefulWidget{
|
||||||
|
final String searchFor;
|
||||||
|
//final Function setState;
|
||||||
|
final BoxConstraints constraints;
|
||||||
|
|
||||||
|
const DestinationGraphs({super.key, required this.searchFor, required this.constraints});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DestinationGraphs> createState() => _DestinationGraphsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Graph{
|
||||||
|
history,
|
||||||
|
leagueState,
|
||||||
|
leagueCutoffs
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DestinationGraphsState extends State<DestinationGraphs> {
|
||||||
|
bool fetchData = false;
|
||||||
|
bool _gamesPlayedInsteadOfDateAndTime = false;
|
||||||
|
late ZoomPanBehavior _zoomPanBehavior;
|
||||||
|
late TooltipBehavior _historyTooltipBehavior;
|
||||||
|
late TooltipBehavior _tooltipBehavior;
|
||||||
|
late TooltipBehavior _leagueTooltipBehavior;
|
||||||
|
String yAxisTitle = "";
|
||||||
|
bool _smooth = false;
|
||||||
|
final List<DropdownMenuItem<Stats>> _yAxis = [for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value))];
|
||||||
|
Graph _graph = Graph.history;
|
||||||
|
Stats _Ychart = Stats.tr;
|
||||||
|
Stats _Xchart = Stats.tr;
|
||||||
|
int _season = currentSeason-1;
|
||||||
|
//Duration postSeasonLeft = seasonStart.difference(DateTime.now());
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState(){
|
||||||
|
_historyTooltipBehavior = TooltipBehavior(
|
||||||
|
color: Colors.black,
|
||||||
|
borderColor: Colors.white,
|
||||||
|
enable: true,
|
||||||
|
animationDuration: 0,
|
||||||
|
builder: (dynamic data, dynamic point, dynamic series,
|
||||||
|
int pointIndex, int seriesIndex) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8.0),
|
||||||
|
child: Text(
|
||||||
|
"${f4.format(data.stat)} $yAxisTitle",
|
||||||
|
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(_gamesPlayedInsteadOfDateAndTime ? t.gamesPlayed(games: t.games(n: data.gamesPlayed)) : timestamp(data.timestamp))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
_tooltipBehavior = TooltipBehavior(
|
||||||
|
color: Colors.black,
|
||||||
|
borderColor: Colors.white,
|
||||||
|
enable: true,
|
||||||
|
animationDuration: 0,
|
||||||
|
builder: (dynamic data, dynamic point, dynamic series,
|
||||||
|
int pointIndex, int seriesIndex) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8.0),
|
||||||
|
child: Text(
|
||||||
|
"${data.nickname} (${data.rank.toUpperCase()})",
|
||||||
|
style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text('${f4.format(data.x)} ${chartsShortTitles[_Xchart]}\n${f4.format(data.y)} ${chartsShortTitles[_Ychart]}')
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
_leagueTooltipBehavior = TooltipBehavior(
|
||||||
|
color: Colors.black,
|
||||||
|
borderColor: Colors.white,
|
||||||
|
enable: true,
|
||||||
|
animationDuration: 0,
|
||||||
|
builder: (dynamic data, dynamic point, dynamic series,
|
||||||
|
int pointIndex, int seriesIndex) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8.0),
|
||||||
|
child: Text(
|
||||||
|
"${f4.format(point.y)} $yAxisTitle",
|
||||||
|
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(timestamp(data.ts))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
_zoomPanBehavior = ZoomPanBehavior(
|
||||||
|
enablePinching: true,
|
||||||
|
enableSelectionZooming: true,
|
||||||
|
enableMouseWheelZooming : true,
|
||||||
|
enablePanning: true,
|
||||||
|
);
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Map<Stats, List<_HistoryChartSpot>>>> getHistoryData(bool fetchHistory) async {
|
||||||
|
if(fetchHistory){
|
||||||
|
try{
|
||||||
|
var history = await teto.fetchAndsaveTLHistory(widget.searchFor);
|
||||||
|
if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.fetchAndsaveTLHistoryResult(number: history.length))));
|
||||||
|
}on TetrioHistoryNotExist{
|
||||||
|
if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.noHistorySaved)));
|
||||||
|
}on P1nkl0bst3rForbidden {
|
||||||
|
if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.p1nkl0bst3rForbidden)));
|
||||||
|
}on P1nkl0bst3rInternalProblem {
|
||||||
|
if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.p1nkl0bst3rinternal)));
|
||||||
|
}on P1nkl0bst3rTooManyRequests{
|
||||||
|
if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.p1nkl0bst3rTooManyRequests)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<List<TetraLeague>> states = await Future.wait<List<TetraLeague>>([
|
||||||
|
teto.getStates(widget.searchFor, season: 1), teto.getStates(widget.searchFor, season: 2),
|
||||||
|
]);
|
||||||
|
List<Map<Stats, List<_HistoryChartSpot>>> historyData = []; // [season][metric][spot]
|
||||||
|
for (int season = 0; season < currentSeason; season++){
|
||||||
|
if (states[season].length >= 2){
|
||||||
|
Map<Stats, List<_HistoryChartSpot>> statsMap = {};
|
||||||
|
for (var stat in Stats.values) statsMap[stat] = [for (var tl in states[season]) if (tl.getStatByEnum(stat) != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.getStatByEnum(stat)!.toDouble())];
|
||||||
|
historyData.add(statsMap);
|
||||||
|
}else{
|
||||||
|
historyData.add({});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchData = false;
|
||||||
|
|
||||||
|
return historyData;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<_MyScatterSpot>> getTetraLeagueData(Stats x, Stats y) async {
|
||||||
|
TetrioPlayersLeaderboard leaderboard = await teto.fetchTLLeaderboard();
|
||||||
|
List<_MyScatterSpot> _spots = [
|
||||||
|
for (TetrioPlayerFromLeaderboard entry in leaderboard.leaderboard)
|
||||||
|
_MyScatterSpot(
|
||||||
|
entry.getStatByEnum(x).toDouble(),
|
||||||
|
entry.getStatByEnum(y).toDouble(),
|
||||||
|
entry.userId,
|
||||||
|
entry.username,
|
||||||
|
entry.rank,
|
||||||
|
rankColors[entry.rank]??Colors.white
|
||||||
|
)
|
||||||
|
];
|
||||||
|
return _spots;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget getHistoryGraph(){
|
||||||
|
return FutureBuilder<List<Map<Stats, List<_HistoryChartSpot>>>>(
|
||||||
|
future: getHistoryData(fetchData),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
switch (snapshot.connectionState){
|
||||||
|
case ConnectionState.none:
|
||||||
|
case ConnectionState.waiting:
|
||||||
|
case ConnectionState.active:
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
case ConnectionState.done:
|
||||||
|
if (snapshot.hasData){
|
||||||
|
List<_HistoryChartSpot> selectedGraph = snapshot.data![_season][_Ychart]!;
|
||||||
|
yAxisTitle = chartsShortTitles[_Ychart]!;
|
||||||
|
// TODO: this graph can Krash
|
||||||
|
return SfCartesianChart(
|
||||||
|
tooltipBehavior: _historyTooltipBehavior,
|
||||||
|
zoomPanBehavior: _zoomPanBehavior,
|
||||||
|
primaryXAxis: _gamesPlayedInsteadOfDateAndTime ? const NumericAxis() : const DateTimeAxis(),
|
||||||
|
primaryYAxis: const NumericAxis(
|
||||||
|
rangePadding: ChartRangePadding.additional,
|
||||||
|
),
|
||||||
|
margin: const EdgeInsets.all(0),
|
||||||
|
series: <CartesianSeries>[
|
||||||
|
if (_gamesPlayedInsteadOfDateAndTime) StepLineSeries<_HistoryChartSpot, int>(
|
||||||
|
enableTooltip: true,
|
||||||
|
dataSource: selectedGraph,
|
||||||
|
animationDuration: 0,
|
||||||
|
opacity: _smooth ? 0 : 1,
|
||||||
|
xValueMapper: (_HistoryChartSpot data, _) => data.gamesPlayed,
|
||||||
|
yValueMapper: (_HistoryChartSpot data, _) => data.stat,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
trendlines:<Trendline>[
|
||||||
|
Trendline(
|
||||||
|
isVisible: _smooth,
|
||||||
|
period: (selectedGraph.length/175).floor(),
|
||||||
|
type: TrendlineType.movingAverage,
|
||||||
|
color: Theme.of(context).colorScheme.primary)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
else StepLineSeries<_HistoryChartSpot, DateTime>(
|
||||||
|
enableTooltip: true,
|
||||||
|
dataSource: selectedGraph,
|
||||||
|
animationDuration: 0,
|
||||||
|
opacity: _smooth ? 0 : 1,
|
||||||
|
xValueMapper: (_HistoryChartSpot data, _) => data.timestamp,
|
||||||
|
yValueMapper: (_HistoryChartSpot data, _) => data.stat,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
trendlines:<Trendline>[
|
||||||
|
Trendline(
|
||||||
|
isVisible: _smooth,
|
||||||
|
period: (selectedGraph.length/175).floor(),
|
||||||
|
type: TrendlineType.movingAverage,
|
||||||
|
color: Theme.of(context).colorScheme.primary)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}else{ return FutureError(snapshot); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget getLeagueState (){
|
||||||
|
return FutureBuilder<List<_MyScatterSpot>>(
|
||||||
|
future: getTetraLeagueData(_Xchart, _Ychart),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
switch (snapshot.connectionState){
|
||||||
|
case ConnectionState.none:
|
||||||
|
case ConnectionState.waiting:
|
||||||
|
case ConnectionState.active:
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
case ConnectionState.done:
|
||||||
|
if (snapshot.hasData){
|
||||||
|
return SfCartesianChart(
|
||||||
|
tooltipBehavior: _tooltipBehavior,
|
||||||
|
zoomPanBehavior: _zoomPanBehavior,
|
||||||
|
//primaryXAxis: CategoryAxis(),
|
||||||
|
series: [
|
||||||
|
ScatterSeries(
|
||||||
|
enableTooltip: true,
|
||||||
|
dataSource: snapshot.data,
|
||||||
|
animationDuration: 0,
|
||||||
|
pointColorMapper: (data, _) => data.color,
|
||||||
|
xValueMapper: (data, _) => data.x,
|
||||||
|
yValueMapper: (data, _) => data.y,
|
||||||
|
onPointTap: (point) => Navigator.push(context, MaterialPageRoute(builder: (context) => MainView(player: snapshot.data![point.pointIndex!].nickname), maintainState: false)),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}else{ return FutureError(snapshot); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget getCutoffsHistory(){
|
||||||
|
return FutureBuilder<List<Cutoffs>>(
|
||||||
|
future: teto.fetchCutoffsHistory(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
switch (snapshot.connectionState){
|
||||||
|
case ConnectionState.none:
|
||||||
|
case ConnectionState.waiting:
|
||||||
|
case ConnectionState.active:
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
case ConnectionState.done:
|
||||||
|
if (snapshot.hasData){
|
||||||
|
yAxisTitle = chartsShortTitles[_Ychart]!;
|
||||||
|
return SfCartesianChart(
|
||||||
|
tooltipBehavior: _leagueTooltipBehavior,
|
||||||
|
zoomPanBehavior: _zoomPanBehavior,
|
||||||
|
primaryXAxis: const DateTimeAxis(),
|
||||||
|
primaryYAxis: NumericAxis(
|
||||||
|
// isInversed: true,
|
||||||
|
maximum: switch (_Ychart){
|
||||||
|
Stats.tr => 25000.0,
|
||||||
|
Stats.gxe => 100.00,
|
||||||
|
_ => null
|
||||||
|
},
|
||||||
|
),
|
||||||
|
margin: const EdgeInsets.all(0),
|
||||||
|
series: <CartesianSeries>[
|
||||||
|
for (String rank in ranks) StepLineSeries<Cutoffs, DateTime>(
|
||||||
|
enableTooltip: true,
|
||||||
|
dataSource: snapshot.data,
|
||||||
|
animationDuration: 0,
|
||||||
|
//opacity: 0.5,
|
||||||
|
xValueMapper: (Cutoffs data, _) => data.ts,
|
||||||
|
yValueMapper: (Cutoffs data, _) => switch (_Ychart){
|
||||||
|
Stats.glicko => data.glicko[rank],
|
||||||
|
Stats.gxe => data.gxe[rank],
|
||||||
|
_ => data.tr[rank]
|
||||||
|
},
|
||||||
|
color: rankColors[rank]!
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}else{ return FutureError(snapshot); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Card(
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 20,
|
||||||
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
|
children: [
|
||||||
|
if (_graph == Graph.history) Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Padding(padding: EdgeInsets.all(8.0), child: Text("Season:", style: TextStyle(fontSize: 22))),
|
||||||
|
DropdownButton(
|
||||||
|
items: [for (int i = 1; i <= currentSeason; i++) DropdownMenuItem(value: i-1, child: Text("$i"))],
|
||||||
|
value: _season,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_season = value!;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (_graph != Graph.leagueCutoffs) Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Padding(padding: EdgeInsets.all(8.0), child: Text("X:", style: TextStyle(fontSize: 22))),
|
||||||
|
DropdownButton(
|
||||||
|
items: switch (_graph){
|
||||||
|
Graph.history => [DropdownMenuItem(value: false, child: Text("Date & Time")), DropdownMenuItem(value: true, child: Text("Games Played"))],
|
||||||
|
Graph.leagueState => _yAxis,
|
||||||
|
Graph.leagueCutoffs => [],
|
||||||
|
},
|
||||||
|
value: _graph == Graph.history ? _gamesPlayedInsteadOfDateAndTime : _Xchart,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
if (_graph == Graph.history)
|
||||||
|
_gamesPlayedInsteadOfDateAndTime = value! as bool;
|
||||||
|
else _Xchart = value! as Stats;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Padding(padding: EdgeInsets.all(8.0), child: Text("Y:", style: TextStyle(fontSize: 22))),
|
||||||
|
DropdownButton<Stats>(
|
||||||
|
items: _graph == Graph.leagueCutoffs ? [DropdownMenuItem(value: Stats.tr, child: Text(chartsShortTitles[Stats.tr]!)), DropdownMenuItem(value: Stats.glicko, child: Text(chartsShortTitles[Stats.glicko]!)), DropdownMenuItem(value: Stats.gxe, child: Text(chartsShortTitles[Stats.gxe]!))] : _yAxis,
|
||||||
|
value: _Ychart,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_Ychart = value!;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (_graph != Graph.leagueState) Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Checkbox(value: _smooth,
|
||||||
|
checkColor: Colors.black,
|
||||||
|
onChanged: ((value) {
|
||||||
|
setState(() {
|
||||||
|
_smooth = value!;
|
||||||
|
});
|
||||||
|
})),
|
||||||
|
Text(t.smooth, style: const TextStyle(color: Colors.white, fontSize: 22))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
IconButton(onPressed: () => _zoomPanBehavior.reset(), icon: const Icon(Icons.refresh), alignment: Alignment.center,)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Card(
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width - 88,
|
||||||
|
height: MediaQuery.of(context).size.height - 96,
|
||||||
|
child: Padding( padding: const EdgeInsets.fromLTRB(40, 30, 40, 30),
|
||||||
|
child: switch (_graph){
|
||||||
|
Graph.history => getHistoryGraph(),
|
||||||
|
Graph.leagueState => getLeagueState(),
|
||||||
|
Graph.leagueCutoffs => getCutoffsHistory()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SegmentedButton<Graph>(
|
||||||
|
showSelectedIcon: false,
|
||||||
|
segments: <ButtonSegment<Graph>>[
|
||||||
|
const ButtonSegment<Graph>(
|
||||||
|
value: Graph.history,
|
||||||
|
label: Text('Player History')),
|
||||||
|
ButtonSegment<Graph>(
|
||||||
|
value: Graph.leagueState,
|
||||||
|
label: Text('League State')),
|
||||||
|
ButtonSegment<Graph>(
|
||||||
|
value: Graph.leagueCutoffs,
|
||||||
|
label: Text('League Cutoffs'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
selected: <Graph>{_graph},
|
||||||
|
onSelectionChanged: (Set<Graph> newSelection) {
|
||||||
|
setState(() {
|
||||||
|
_graph = newSelection.first;
|
||||||
|
switch (newSelection.first){
|
||||||
|
case Graph.leagueCutoffs:
|
||||||
|
case Graph.history:
|
||||||
|
_Ychart = Stats.tr;
|
||||||
|
case Graph.leagueState:
|
||||||
|
_Ychart = Stats.apm;
|
||||||
|
}
|
||||||
|
});})
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HistoryChartSpot{
|
||||||
|
final DateTime timestamp;
|
||||||
|
final int gamesPlayed;
|
||||||
|
final String rank;
|
||||||
|
final double stat;
|
||||||
|
const _HistoryChartSpot(this.timestamp, this.gamesPlayed, this.rank, this.stat);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MyScatterSpot{
|
||||||
|
num x;
|
||||||
|
num y;
|
||||||
|
String id;
|
||||||
|
String nickname;
|
||||||
|
String rank;
|
||||||
|
Color color;
|
||||||
|
_MyScatterSpot(this.x, this.y, this.id, this.nickname, this.rank, this.color);
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,277 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/tetrio_constants.dart';
|
||||||
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
|
import 'package:tetra_stats/main.dart';
|
||||||
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
|
import 'package:tetra_stats/utils/relative_timestamps.dart';
|
||||||
|
import 'package:tetra_stats/views/main_view_tiles.dart';
|
||||||
|
import 'package:tetra_stats/views/user_view.dart';
|
||||||
|
|
||||||
|
class DestinationLeaderboards extends StatefulWidget{
|
||||||
|
final BoxConstraints constraints;
|
||||||
|
|
||||||
|
const DestinationLeaderboards({super.key, required this.constraints});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DestinationLeaderboards> createState() => _DestinationLeaderboardsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Leaderboards{
|
||||||
|
tl,
|
||||||
|
fullTL,
|
||||||
|
xp,
|
||||||
|
ar,
|
||||||
|
sprint,
|
||||||
|
blitz,
|
||||||
|
zenith,
|
||||||
|
zenithex,
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
|
||||||
|
//Duration postSeasonLeft = seasonStart.difference(DateTime.now());
|
||||||
|
final Map<Leaderboards, String> leaderboards = {
|
||||||
|
Leaderboards.tl: "Tetra League (Current Season)",
|
||||||
|
Leaderboards.fullTL: "Tetra League (Current Season, full one)",
|
||||||
|
Leaderboards.xp: "XP",
|
||||||
|
Leaderboards.ar: "Acievement Points",
|
||||||
|
Leaderboards.sprint: "40 Lines",
|
||||||
|
Leaderboards.blitz: "Blitz",
|
||||||
|
Leaderboards.zenith: "Quick Play",
|
||||||
|
Leaderboards.zenithex: "Quick Play Expert",
|
||||||
|
};
|
||||||
|
Leaderboards _currentLb = Leaderboards.tl;
|
||||||
|
final StreamController<List<dynamic>> _dataStreamController = StreamController<List<dynamic>>.broadcast();
|
||||||
|
late final ScrollController _scrollController;
|
||||||
|
Stream<List<dynamic>> get dataStream => _dataStreamController.stream;
|
||||||
|
List<dynamic> list = [];
|
||||||
|
bool _isFetchingData = false;
|
||||||
|
String? prisecter;
|
||||||
|
List<DropdownMenuEntry> _countries = [for (MapEntry e in t.countries.entries) DropdownMenuEntry(value: e.key, label: e.value)];
|
||||||
|
List<DropdownMenuEntry> _stats = [for (MapEntry e in chartsShortTitles.entries) DropdownMenuEntry(value: e.key, label: e.value)];
|
||||||
|
String? _country;
|
||||||
|
Stats stat = Stats.tr;
|
||||||
|
|
||||||
|
Future<void> _fetchData() async {
|
||||||
|
if (_isFetchingData) {
|
||||||
|
// Avoid fetching new data while already fetching
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
_isFetchingData = true;
|
||||||
|
setState(() {});
|
||||||
|
|
||||||
|
final items = switch(_currentLb){
|
||||||
|
Leaderboards.tl => await teto.fetchTetrioLeaderboard(prisecter: prisecter, country: _country),
|
||||||
|
Leaderboards.fullTL => (await teto.fetchTLLeaderboard()).getStatRankingFromLB(stat, country: _country??""),
|
||||||
|
Leaderboards.xp => await teto.fetchTetrioLeaderboard(prisecter: prisecter, lb: "xp", country: _country),
|
||||||
|
Leaderboards.ar => await teto.fetchTetrioLeaderboard(prisecter: prisecter, lb: "ar", country: _country),
|
||||||
|
Leaderboards.sprint => await teto.fetchTetrioRecordsLeaderboard(prisecter: prisecter, country: _country),
|
||||||
|
Leaderboards.blitz => await teto.fetchTetrioRecordsLeaderboard(prisecter: prisecter, lb: "blitz_global", country: _country),
|
||||||
|
Leaderboards.zenith => await teto.fetchTetrioRecordsLeaderboard(prisecter: prisecter, lb: "zenith_global", country: _country),
|
||||||
|
Leaderboards.zenithex => await teto.fetchTetrioRecordsLeaderboard(prisecter: prisecter, lb: "zenithex_global", country: _country),
|
||||||
|
};
|
||||||
|
|
||||||
|
list.addAll(items);
|
||||||
|
|
||||||
|
_dataStreamController.add(list);
|
||||||
|
prisecter = list.last.prisecter.toString();
|
||||||
|
} catch (e) {
|
||||||
|
_dataStreamController.addError(e);
|
||||||
|
} finally {
|
||||||
|
// Set to false when data fetching is complete
|
||||||
|
_isFetchingData = false;
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_scrollController = ScrollController();
|
||||||
|
_fetchData();
|
||||||
|
_scrollController.addListener(() {
|
||||||
|
_scrollController.addListener(() {
|
||||||
|
final maxScroll = _scrollController.position.maxScrollExtent;
|
||||||
|
final currentScroll = _scrollController.position.pixels;
|
||||||
|
|
||||||
|
if (currentScroll == maxScroll && _currentLb != Leaderboards.fullTL) {
|
||||||
|
// When the last item is fully visible, load the next page.
|
||||||
|
_fetchData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static TextStyle trailingStyle = TextStyle(fontSize: 28);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 350.0,
|
||||||
|
height: widget.constraints.maxHeight,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Card(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Spacer(),
|
||||||
|
Text("Leaderboards", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36)),
|
||||||
|
Spacer()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: leaderboards.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return Card(
|
||||||
|
surfaceTintColor: index == 1 ? Colors.redAccent : theme.colorScheme.primary,
|
||||||
|
child: ListTile(
|
||||||
|
title: Text(leaderboards.values.elementAt(index)),
|
||||||
|
subtitle: index == 1 ? Text("Heavy, but allows you to sort players by their stats", style: TextStyle(color: Colors.grey, fontSize: 12)) : null,
|
||||||
|
onTap: () {
|
||||||
|
_currentLb = leaderboards.keys.elementAt(index);
|
||||||
|
list.clear();
|
||||||
|
prisecter = null;
|
||||||
|
_fetchData();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: widget.constraints.maxWidth - 350 - 88,
|
||||||
|
child: Card(
|
||||||
|
child: StreamBuilder<List<dynamic>>(
|
||||||
|
stream: dataStream,
|
||||||
|
builder:(context, snapshot) {
|
||||||
|
switch (snapshot.connectionState){
|
||||||
|
case ConnectionState.none:
|
||||||
|
case ConnectionState.waiting:
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
case ConnectionState.active:
|
||||||
|
case ConnectionState.done:
|
||||||
|
if (snapshot.hasData){
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Text(leaderboards[_currentLb]!, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28, height: 0.9)),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
DropdownMenu(
|
||||||
|
leadingIcon: Icon(Icons.public),
|
||||||
|
inputDecorationTheme: InputDecorationTheme(
|
||||||
|
isDense: true,
|
||||||
|
),
|
||||||
|
textStyle: TextStyle(fontSize: 14, height: 0.9),
|
||||||
|
dropdownMenuEntries: _countries,
|
||||||
|
initialSelection: "",
|
||||||
|
onSelected: ((value) {
|
||||||
|
_country = value as String?;
|
||||||
|
list.clear();
|
||||||
|
prisecter = null;
|
||||||
|
_isFetchingData = false;
|
||||||
|
setState((){_fetchData();});
|
||||||
|
})
|
||||||
|
),
|
||||||
|
if (_currentLb == Leaderboards.fullTL) SizedBox(width: 5.0),
|
||||||
|
if (_currentLb == Leaderboards.fullTL) DropdownMenu(
|
||||||
|
leadingIcon: Icon(Icons.sort),
|
||||||
|
inputDecorationTheme: InputDecorationTheme(
|
||||||
|
isDense: true,
|
||||||
|
),
|
||||||
|
textStyle: TextStyle(fontSize: 14, height: 0.9),
|
||||||
|
dropdownMenuEntries: _stats,
|
||||||
|
initialSelection: stat,
|
||||||
|
onSelected: ((value) {
|
||||||
|
stat = value;
|
||||||
|
list.clear();
|
||||||
|
prisecter = null;
|
||||||
|
_isFetchingData = false;
|
||||||
|
setState((){_fetchData();});
|
||||||
|
})
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
controller: _scrollController,
|
||||||
|
itemCount: list.length,
|
||||||
|
prototypeItem: ListTile(
|
||||||
|
leading: Text("0"),
|
||||||
|
title: Text("ehhh...", style: TextStyle(fontSize: 22)),
|
||||||
|
trailing: SizedBox(height: 36, width: 1),
|
||||||
|
subtitle: const Text("eh...", style: TextStyle(color: Colors.grey, fontSize: 12)),
|
||||||
|
),
|
||||||
|
itemBuilder: (BuildContext context, int index){
|
||||||
|
return ListTile(
|
||||||
|
leading: Text(intf.format(index+1)),
|
||||||
|
title: Text(snapshot.data![index].username, style: TextStyle(fontSize: 22)),
|
||||||
|
trailing: switch (_currentLb){
|
||||||
|
Leaderboards.tl => Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text("${f2.format(snapshot.data![index].tr)} TR", style: trailingStyle),
|
||||||
|
Image.asset("res/tetrio_tl_alpha_ranks/${snapshot.data![index].rank}.png", height: 36)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Leaderboards.fullTL => Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text("${f2.format(snapshot.data![index].tr)} TR", style: trailingStyle),
|
||||||
|
Image.asset("res/tetrio_tl_alpha_ranks/${snapshot.data![index].rank}.png", height: 36)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Leaderboards.xp => Text("LVL ${f2.format(snapshot.data![index].level)}", style: trailingStyle),
|
||||||
|
Leaderboards.ar => Text("${intf.format(snapshot.data![index].ar)} AR", style: trailingStyle),
|
||||||
|
Leaderboards.sprint => Text(get40lTime(snapshot.data![index].stats.finalTime.inMicroseconds), style: trailingStyle),
|
||||||
|
Leaderboards.blitz => Text(intf.format(snapshot.data![index].stats.score), style: trailingStyle),
|
||||||
|
Leaderboards.zenith => Text("${f2.format(snapshot.data![index].stats.zenith!.altitude)} m", style: trailingStyle),
|
||||||
|
Leaderboards.zenithex => Text("${f2.format(snapshot.data![index].stats.zenith!.altitude)} m", style: trailingStyle)
|
||||||
|
},
|
||||||
|
subtitle: Text(switch (_currentLb){
|
||||||
|
Leaderboards.tl => "${f2.format(snapshot.data![index].apm)} APM, ${f2.format(snapshot.data![index].pps)} PPS, ${f2.format(snapshot.data![index].vs)} VS, ${f2.format(snapshot.data![index].nerdStats.app)} APP, ${f2.format(snapshot.data![index].nerdStats.vsapm)} VS/APM",
|
||||||
|
Leaderboards.fullTL => "${f2.format(snapshot.data![index].apm)} APM, ${f2.format(snapshot.data![index].pps)} PPS, ${f2.format(snapshot.data![index].vs)} VS, ${f2.format(snapshot.data![index].nerdStats.app)} APP, ${f2.format(snapshot.data![index].nerdStats.vsapm)} VS/APM",
|
||||||
|
Leaderboards.xp => "${f2.format(snapshot.data![index].xp)} XP${snapshot.data![index].playtime.isNegative ? "" : ", ${playtime(snapshot.data![index].playtime)} of gametime"}",
|
||||||
|
Leaderboards.ar => "${snapshot.data![index].ar_counts}",
|
||||||
|
Leaderboards.sprint => "${intf.format(snapshot.data![index].stats.finesse.faults)} FF, ${f2.format(snapshot.data![index].stats.kpp)} KPP, ${f2.format(snapshot.data![index].stats.kps)} KPS, ${f2.format(snapshot.data![index].stats.pps)} PPS, ${intf.format(snapshot.data![index].stats.piecesPlaced)} P",
|
||||||
|
Leaderboards.blitz => "lvl ${snapshot.data![index].stats.level}, ${f2.format(snapshot.data![index].stats.pps)} PPS, ${f2.format(snapshot.data![index].stats.spp)} SPP",
|
||||||
|
Leaderboards.zenith => "${f2.format(snapshot.data![index].aggregateStats.apm)} APM, ${f2.format(snapshot.data![index].aggregateStats.pps)} PPS, ${intf.format(snapshot.data![index].stats.kills)} KO's, ${f2.format(snapshot.data![index].stats.cps)} climb speed (${f2.format(snapshot.data![index].stats.zenith!.peakrank)} peak), ${intf.format(snapshot.data![index].stats.topBtB)} B2B",
|
||||||
|
Leaderboards.zenithex => "${f2.format(snapshot.data![index].aggregateStats.apm)} APM, ${f2.format(snapshot.data![index].aggregateStats.pps)} PPS, ${intf.format(snapshot.data![index].stats.kills)} KO's, ${f2.format(snapshot.data![index].stats.cps)} climb speed (${f2.format(snapshot.data![index].stats.zenith!.peakrank)} peak), ${intf.format(snapshot.data![index].stats.topBtB)} B2B"
|
||||||
|
}, style: TextStyle(color: Colors.grey, fontSize: 12)),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => UserView(searchFor: snapshot.data![index].userId),
|
||||||
|
maintainState: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (snapshot.hasError){ return FutureError(snapshot); }
|
||||||
|
}
|
||||||
|
return Text("huh?");
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,167 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/tetra_league.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/tetra_league_alpha_record.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/tetrio_constants.dart';
|
||||||
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
|
import 'package:tetra_stats/main.dart';
|
||||||
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
|
import 'package:tetra_stats/views/main_view_tiles.dart';
|
||||||
|
import 'package:tetra_stats/views/state_view.dart';
|
||||||
|
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
||||||
|
|
||||||
|
class DestinationSavedData extends StatefulWidget{
|
||||||
|
final BoxConstraints constraints;
|
||||||
|
|
||||||
|
const DestinationSavedData({super.key, required this.constraints});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DestinationSavedData> createState() => _DestinationSavedData();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DestinationSavedData extends State<DestinationSavedData> {
|
||||||
|
String? selectedID;
|
||||||
|
|
||||||
|
Future<(List<TetraLeague>, List<TetraLeague>, List<TetraLeagueAlphaRecord>)> getDataAbout(String id) async {
|
||||||
|
return (await teto.getStates(id, season: currentSeason), await teto.getStates(id, season: 1), await teto.getTLMatchesbyPlayerID(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget getTetraLeagueListTile(TetraLeague data){
|
||||||
|
return ListTile(
|
||||||
|
title: Text("${timestamp(data.timestamp)}"),
|
||||||
|
subtitle: Text("${f2.format(data.apm)} APM, ${f2.format(data.pps)} PPS, ${f2.format(data.vs)} VS, ${intf.format(data.gamesPlayed)} games", style: TextStyle(color: Colors.grey)),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text("${f2.format(data.tr)} TR", style: TextStyle(fontSize: 28)),
|
||||||
|
Image.asset("res/tetrio_tl_alpha_ranks/${data.rank}.png", height: 36)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
leading: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
teto.deleteState(data.id+data.timestamp.millisecondsSinceEpoch.toRadixString(16)).then((value) => setState(() {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.stateRemoved(date: timestamp(data.timestamp)))));
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.delete_forever)
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => StateView(state: data),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FutureBuilder<Map<String, String>>(
|
||||||
|
future: teto.getAllPlayers(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
switch (snapshot.connectionState){
|
||||||
|
case ConnectionState.none:
|
||||||
|
case ConnectionState.waiting:
|
||||||
|
case ConnectionState.active:
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
case ConnectionState.done:
|
||||||
|
if (snapshot.hasError){ return FutureError(snapshot); }
|
||||||
|
if (snapshot.hasData){
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 450,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Card(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Spacer(),
|
||||||
|
Text("Saved Data", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36)),
|
||||||
|
Spacer()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
for (String id in snapshot.data!.keys) Card(
|
||||||
|
child: ListTile(
|
||||||
|
title: Text(snapshot.data![id]!),
|
||||||
|
//subtitle: Text("NaN states, NaN TL records", style: TextStyle(color: Colors.grey)),
|
||||||
|
onTap: () => setState(() {
|
||||||
|
selectedID = id;
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: widget.constraints.maxWidth - 450 - 80,
|
||||||
|
child: selectedID != null ? FutureBuilder<(List<TetraLeague>, List<TetraLeague>, List<TetraLeagueAlphaRecord>)>(
|
||||||
|
future: getDataAbout(selectedID!),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
switch(snapshot.connectionState){
|
||||||
|
case ConnectionState.none:
|
||||||
|
case ConnectionState.waiting:
|
||||||
|
case ConnectionState.active:
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
case ConnectionState.done:
|
||||||
|
if (snapshot.hasError){ return FutureError(snapshot); }
|
||||||
|
if (snapshot.hasData){
|
||||||
|
return DefaultTabController(
|
||||||
|
length: 3,
|
||||||
|
child: Card(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Card(
|
||||||
|
child: TabBar(tabs: [
|
||||||
|
Tab(text: "S${currentSeason} TL States"),
|
||||||
|
Tab(text: "S1 TL States"),
|
||||||
|
Tab(text: "TL Records")
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: widget.constraints.maxHeight - 64,
|
||||||
|
child: TabBarView(children: [
|
||||||
|
ListView.builder(
|
||||||
|
itemCount: snapshot.data!.$1.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return getTetraLeagueListTile(snapshot.data!.$1[index]);
|
||||||
|
},),
|
||||||
|
ListView.builder(
|
||||||
|
itemCount: snapshot.data!.$2.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return getTetraLeagueListTile(snapshot.data!.$2[index]);
|
||||||
|
},),
|
||||||
|
ListView.builder(
|
||||||
|
itemCount: snapshot.data!.$3.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return ListTile(
|
||||||
|
title: Text(snapshot.data!.$3[index].toString()),
|
||||||
|
);
|
||||||
|
},),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Text("what?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) :
|
||||||
|
Text("Select nickname on the left to see data assosiated with it")
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return const Text("End of FutureBuilder<FetchResults>");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,59 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
|
import 'package:tetra_stats/main.dart';
|
||||||
|
import 'package:tetra_stats/views/destination_home.dart';
|
||||||
|
import 'package:tetra_stats/views/main_view_tiles.dart';
|
||||||
|
|
||||||
|
final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms();
|
||||||
|
|
||||||
|
class UserView extends StatefulWidget {
|
||||||
|
final String searchFor;
|
||||||
|
const UserView({super.key, required this.searchFor});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => UserState();
|
||||||
|
}
|
||||||
|
|
||||||
|
late String oldWindowTitle;
|
||||||
|
|
||||||
|
class UserState extends State<UserView> {
|
||||||
|
late ScrollController _scrollController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_scrollController = ScrollController();
|
||||||
|
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS){
|
||||||
|
// windowManager.getTitle().then((value) => oldWindowTitle = value);
|
||||||
|
// windowManager.setTitle("State from ${timestamp(widget.state.timestamp)}");
|
||||||
|
}
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_scrollController.dispose();
|
||||||
|
// if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) windowManager.setTitle(oldWindowTitle);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
//final t = Translations.of(context);
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text("Search For"),
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.black,
|
||||||
|
body: SafeArea(
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
|
return DestinationHome(searchFor: widget.searchFor, dataFuture: getData(widget.searchFor), newsFuture: teto.fetchNews(widget.searchFor), constraints: constraints);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue