Big refactoring, upated design of nerd stats, info center ideas
This commit is contained in:
parent
647d1f87f4
commit
6c8e7b9147
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
const int currentSeason = 2;
|
const int currentSeason = 2;
|
||||||
|
final DateTime sprintAndBlitzRelevance = DateTime(2024, 8, 25);
|
||||||
const double noTrRd = 60.9;
|
const double noTrRd = 60.9;
|
||||||
const double apmWeight = 1;
|
const double apmWeight = 1;
|
||||||
const double ppsWeight = 45;
|
const double ppsWeight = 45;
|
||||||
|
@ -12,6 +13,18 @@ const double appdspWeight = 140;
|
||||||
const double vsapmWeight = 60;
|
const double vsapmWeight = 60;
|
||||||
const double cheeseWeight = 1.25;
|
const double cheeseWeight = 1.25;
|
||||||
const double gbeWeight = 315;
|
const double gbeWeight = 315;
|
||||||
|
|
||||||
|
const Map<int, double> xpTableScuffed = { // level: xp required
|
||||||
|
05000: 67009018.4885772,
|
||||||
|
10000: 763653437.386,
|
||||||
|
15000: 2337651144.54149,
|
||||||
|
20000: 4572735210.50902,
|
||||||
|
25000: 7376166347.04745,
|
||||||
|
30000: 10693620096.2168,
|
||||||
|
40000: 18728882739.482,
|
||||||
|
50000: 28468683855.2853
|
||||||
|
};
|
||||||
|
|
||||||
const List<String> ranks = [
|
const List<String> ranks = [
|
||||||
"d",
|
"d",
|
||||||
"d+",
|
"d+",
|
||||||
|
|
|
@ -7,19 +7,12 @@ import 'dart:developer' as developer;
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:tetra_stats/services/tetrio_crud.dart';
|
import 'package:tetra_stats/services/tetrio_crud.dart';
|
||||||
import 'package:tetra_stats/views/customization_view.dart';
|
|
||||||
import 'package:tetra_stats/views/ranks_averages_view.dart';
|
|
||||||
import 'package:tetra_stats/views/sprint_and_blitz_averages.dart';
|
|
||||||
import 'package:tetra_stats/views/tl_leaderboard_view.dart';
|
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||||
import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';
|
import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:tetra_stats/views/main_view_tiles.dart';
|
import 'package:tetra_stats/views/main_view.dart';
|
||||||
import 'package:tetra_stats/views/settings_view.dart';
|
|
||||||
import 'package:tetra_stats/views/tracked_players_view.dart';
|
|
||||||
import 'package:tetra_stats/views/calc_view.dart';
|
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
late final PackageInfo packageInfo;
|
late final PackageInfo packageInfo;
|
||||||
|
@ -72,44 +65,6 @@ final router = GoRouter(
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: "/",
|
path: "/",
|
||||||
builder: (_, __) => const MainView(),
|
builder: (_, __) => const MainView(),
|
||||||
routes: [
|
|
||||||
GoRoute(
|
|
||||||
path: 'settings',
|
|
||||||
builder: (_, __) => const SettingsView(),
|
|
||||||
routes: [
|
|
||||||
GoRoute(
|
|
||||||
path: 'customization',
|
|
||||||
builder: (_, __) => const CustomizationView(),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
path: "leaderboard",
|
|
||||||
builder: (_, __) => const TLLeaderboardView(),
|
|
||||||
routes: [
|
|
||||||
GoRoute(
|
|
||||||
path: "LBvalues",
|
|
||||||
builder: (_, __) => const RankAveragesView(),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
path: "LBvalues",
|
|
||||||
builder: (_, __) => const RankAveragesView(),
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
path: 'states',
|
|
||||||
builder: (_, __) => const TrackedPlayersView(),
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
path: 'calc',
|
|
||||||
builder: (_, __) => const CalcView(),
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
path: 'sprintAndBlitzAverages',
|
|
||||||
builder: (_, __) => const SprintAndBlitzView(),
|
|
||||||
)
|
|
||||||
]
|
|
||||||
),
|
),
|
||||||
GoRoute( // that one intended for Android users, that can open https://ch.tetr.io/u/ links
|
GoRoute( // that one intended for Android users, that can open https://ch.tetr.io/u/ links
|
||||||
path: "/u/:userId",
|
path: "/u/:userId",
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
Future<void> copyToClipboard(String text) async {
|
||||||
|
await Clipboard.setData(ClipboardData(text: text));
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ import 'package:intl/intl.dart';
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
|
|
||||||
final NumberFormat compareIntf = NumberFormat("+#,###;-#,###")..maximumFractionDigits = 0;
|
final NumberFormat compareIntf = NumberFormat("+#,###;-#,###")..maximumFractionDigits = 0;
|
||||||
|
final NumberFormat fDiff = NumberFormat("+#,###.####;-#,###.####");
|
||||||
final NumberFormat comparef = NumberFormat("+#,###.###;-#,###.###")..maximumFractionDigits = 3;
|
final NumberFormat comparef = NumberFormat("+#,###.###;-#,###.###")..maximumFractionDigits = 3;
|
||||||
final NumberFormat comparef2 = NumberFormat("+#,###.##;-#,###.##")..maximumFractionDigits = 2;
|
final NumberFormat comparef2 = NumberFormat("+#,###.##;-#,###.##")..maximumFractionDigits = 2;
|
||||||
final NumberFormat intf = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0);
|
final NumberFormat intf = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0);
|
||||||
|
|
|
@ -1,146 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:intl/intl.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/gen/strings.g.dart';
|
|
||||||
import 'package:tetra_stats/widgets/graphs.dart';
|
|
||||||
import 'package:window_manager/window_manager.dart';
|
|
||||||
|
|
||||||
double? apm;
|
|
||||||
double? pps;
|
|
||||||
double? vs;
|
|
||||||
NerdStats? nerdStats;
|
|
||||||
EstTr? estTr;
|
|
||||||
Playstyle? playstyle;
|
|
||||||
late String oldWindowTitle;
|
|
||||||
|
|
||||||
class CalcView extends StatefulWidget {
|
|
||||||
const CalcView({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<StatefulWidget> createState() => CalcState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class CalcState extends State<CalcView> {
|
|
||||||
TextEditingController ppsController = TextEditingController();
|
|
||||||
TextEditingController apmController = TextEditingController();
|
|
||||||
TextEditingController vsController = TextEditingController();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS){
|
|
||||||
windowManager.getTitle().then((value) => oldWindowTitle = value);
|
|
||||||
windowManager.setTitle("Tetra Stats: ${t.statsCalc}");
|
|
||||||
}
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) windowManager.setTitle(oldWindowTitle);
|
|
||||||
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")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final t = Translations.of(context);
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text(t.statsCalc),
|
|
||||||
),
|
|
||||||
backgroundColor: Colors.black,
|
|
||||||
body: SingleChildScrollView(
|
|
||||||
child: Center(
|
|
||||||
child: Container(
|
|
||||||
constraints: const BoxConstraints(maxWidth: 768),
|
|
||||||
child: Column(children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(14, 16, 16, 32),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 12),
|
|
||||||
child: TextField(
|
|
||||||
onSubmitted: (value) => calc(),
|
|
||||||
controller: apmController,
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
decoration: const InputDecoration(label: Text("APM"), alignLabelWithHint: true),
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
Expanded(
|
|
||||||
child: TextField(
|
|
||||||
onSubmitted: (value) => calc(),
|
|
||||||
controller: ppsController,
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
decoration: const InputDecoration(label: Text("PPS"), alignLabelWithHint: true),
|
|
||||||
)),
|
|
||||||
Expanded(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 12),
|
|
||||||
child: TextField(
|
|
||||||
onSubmitted: (value) => calc(),
|
|
||||||
controller: vsController,
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
decoration: const InputDecoration(label: Text("VS"), alignLabelWithHint: true),
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => calc(),
|
|
||||||
child: Text(t.calc),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Divider(),
|
|
||||||
if (nerdStats == null) Text(t.calcViewNoValues)
|
|
||||||
else Column(children: [
|
|
||||||
_ListEntry(value: nerdStats!.app, label: t.statCellNum.app.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3),
|
|
||||||
_ListEntry(value: nerdStats!.vsapm, label: "VS/APM", fractionDigits: 3),
|
|
||||||
_ListEntry(value: nerdStats!.dss, label: t.statCellNum.dss.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3),
|
|
||||||
_ListEntry(value: nerdStats!.dsp, label: t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3),
|
|
||||||
_ListEntry(value: nerdStats!.appdsp, label: "APP + DS/P", fractionDigits: 3),
|
|
||||||
_ListEntry(value: nerdStats!.cheese, label: t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3),
|
|
||||||
_ListEntry(value: nerdStats!.gbe, label: t.statCellNum.gbe.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3),
|
|
||||||
_ListEntry(value: nerdStats!.nyaapp, label: t.statCellNum.nyaapp.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3),
|
|
||||||
_ListEntry(value: nerdStats!.area, label: t.statCellNum.area.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3),
|
|
||||||
_ListEntry(value: estTr!.esttr, label: t.statCellNum.estOfTR, fractionDigits: 3),
|
|
||||||
Graphs(apm!, pps!, vs!, nerdStats!, playstyle!)
|
|
||||||
],)
|
|
||||||
],),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ListEntry extends StatelessWidget {
|
|
||||||
final double value;
|
|
||||||
final String label;
|
|
||||||
final int? fractionDigits;
|
|
||||||
const _ListEntry({required this.value, required this.label, this.fractionDigits});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
NumberFormat f = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: fractionDigits ?? 0);
|
|
||||||
return ListTile(title: Text(label), trailing: Text(f.format(value), style: const TextStyle(fontSize: 22)));
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,178 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
|
||||||
import 'package:tetra_stats/views/settings_view.dart' show subtitleStyle;
|
|
||||||
import 'package:tetra_stats/main.dart' show MyAppState, prefs;
|
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
|
||||||
import 'package:window_manager/window_manager.dart';
|
|
||||||
|
|
||||||
late String oldWindowTitle;
|
|
||||||
Color pickerColor = Colors.cyanAccent;
|
|
||||||
Color currentColor = Colors.cyanAccent;
|
|
||||||
|
|
||||||
class CustomizationView extends StatefulWidget {
|
|
||||||
const CustomizationView({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<StatefulWidget> createState() => CustomizationState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class CustomizationState extends State<CustomizationView> {
|
|
||||||
late bool oskKagariGimmick;
|
|
||||||
late bool sheetbotRadarGraphs;
|
|
||||||
late int ratingMode;
|
|
||||||
late int timestampMode;
|
|
||||||
|
|
||||||
void changeColor(Color color) {
|
|
||||||
setState(() => pickerColor = color);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) {
|
|
||||||
windowManager.getTitle().then((value) => oldWindowTitle = value);
|
|
||||||
windowManager.setTitle("Tetra Stats: ${t.settings}");
|
|
||||||
}
|
|
||||||
_getPreferences();
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) windowManager.setTitle(oldWindowTitle);
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _getPreferences() {
|
|
||||||
if (prefs.getBool("oskKagariGimmick") != null) {
|
|
||||||
oskKagariGimmick = prefs.getBool("oskKagariGimmick")!;
|
|
||||||
} else {
|
|
||||||
oskKagariGimmick = true;
|
|
||||||
}
|
|
||||||
if (prefs.getBool("sheetbotRadarGraphs") != null) {
|
|
||||||
sheetbotRadarGraphs = prefs.getBool("sheetbotRadarGraphs")!;
|
|
||||||
} else {
|
|
||||||
sheetbotRadarGraphs = false;
|
|
||||||
}
|
|
||||||
if (prefs.getInt("ratingMode") != null) {
|
|
||||||
ratingMode = prefs.getInt("ratingMode")!;
|
|
||||||
} else {
|
|
||||||
ratingMode = 0;
|
|
||||||
}
|
|
||||||
if (prefs.getInt("timestampMode") != null) {
|
|
||||||
timestampMode = prefs.getInt("timestampMode")!;
|
|
||||||
} else {
|
|
||||||
timestampMode = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ThemeData getTheme(BuildContext context, Color color){
|
|
||||||
return Theme.of(context).copyWith(colorScheme: ColorScheme.dark(primary: color, secondary: Colors.white));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final t = Translations.of(context);
|
|
||||||
List<DropdownMenuItem<AppLocale>>? locales =
|
|
||||||
<DropdownMenuItem<AppLocale>>[];
|
|
||||||
for (var v in AppLocale.values) {
|
|
||||||
locales.add(DropdownMenuItem<AppLocale>(
|
|
||||||
value: v, child: Text(t.locales[v.languageTag]!)));
|
|
||||||
}
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text(t.customization),
|
|
||||||
),
|
|
||||||
backgroundColor: Colors.black,
|
|
||||||
body: SafeArea(
|
|
||||||
child: ListView(
|
|
||||||
children: [
|
|
||||||
ListTile(
|
|
||||||
title: Text(t.AccentColor),
|
|
||||||
subtitle: Text(t.AccentColorDescription, style: subtitleStyle),
|
|
||||||
trailing: ColorIndicator(HSVColor.fromColor(Theme.of(context).colorScheme.primary), width: 25, height: 25),
|
|
||||||
onTap: () {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) => AlertDialog(
|
|
||||||
title: const Text('Pick an accent color'),
|
|
||||||
content: SingleChildScrollView(
|
|
||||||
child: ColorPicker(
|
|
||||||
pickerColor: pickerColor,
|
|
||||||
onColorChanged: changeColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
actions: <Widget>[
|
|
||||||
ElevatedButton(
|
|
||||||
child: const Text('Set'),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
context.findAncestorStateOfType<MyAppState>()?.setAccentColor(pickerColor);
|
|
||||||
prefs.setInt("accentColor", pickerColor.value);
|
|
||||||
});
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
),
|
|
||||||
// const ListTile(
|
|
||||||
// title: Text("Stats Table in TL mathes list"),
|
|
||||||
// subtitle: Text("Not implemented"),
|
|
||||||
// ),
|
|
||||||
ListTile(title: Text(t.timestamps),
|
|
||||||
subtitle: Text(t.timestampsDescription, style: subtitleStyle),
|
|
||||||
trailing: DropdownButton(
|
|
||||||
value: timestampMode,
|
|
||||||
items: <DropdownMenuItem>[
|
|
||||||
DropdownMenuItem(value: 0, child: Text(t.timestampsAbsoluteGMT)),
|
|
||||||
DropdownMenuItem(value: 1, child: Text(t.timestampsAbsoluteLocalTime)),
|
|
||||||
DropdownMenuItem(value: 2, child: Text(t.timestampsRelative))
|
|
||||||
],
|
|
||||||
onChanged: (dynamic value){
|
|
||||||
prefs.setInt("timestampMode", value);
|
|
||||||
setState(() {
|
|
||||||
timestampMode = value;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ListTile(title: Text(t.rating),
|
|
||||||
subtitle: Text(t.ratingDescription, style: subtitleStyle),
|
|
||||||
trailing: DropdownButton(
|
|
||||||
value: ratingMode,
|
|
||||||
items: <DropdownMenuItem>[
|
|
||||||
const DropdownMenuItem(value: 0, child: Text("TR")),
|
|
||||||
const DropdownMenuItem(value: 1, child: Text("Glicko")),
|
|
||||||
DropdownMenuItem(value: 2, child: Text(t.ratingLBposition))
|
|
||||||
],
|
|
||||||
onChanged: (dynamic value){
|
|
||||||
prefs.setInt("ratingMode", value);
|
|
||||||
setState(() {
|
|
||||||
ratingMode = value;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ListTile(title: Text(t.sheetbotGraphs),
|
|
||||||
subtitle: Text(t.sheetbotGraphsDescription, style: subtitleStyle),
|
|
||||||
trailing: Switch(value: sheetbotRadarGraphs, onChanged: (bool value){
|
|
||||||
prefs.setBool("sheetbotRadarGraphs", value);
|
|
||||||
setState(() {
|
|
||||||
sheetbotRadarGraphs = value;
|
|
||||||
});
|
|
||||||
}),),
|
|
||||||
ListTile(title: Text(t.oskKagari),
|
|
||||||
subtitle: Text(t.oskKagariDescription, style: subtitleStyle),
|
|
||||||
trailing: Switch(value: oskKagariGimmick, onChanged: (bool value){
|
|
||||||
prefs.setBool("oskKagariGimmick", value);
|
|
||||||
setState(() {
|
|
||||||
oskKagariGimmick = value;
|
|
||||||
});
|
|
||||||
}),)
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,7 +9,9 @@ import 'package:tetra_stats/data_objects/playstyle.dart';
|
||||||
import 'package:tetra_stats/data_objects/tetrio_constants.dart';
|
import 'package:tetra_stats/data_objects/tetrio_constants.dart';
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
import 'package:tetra_stats/views/main_view_tiles.dart';
|
import 'package:tetra_stats/widgets/graphs.dart';
|
||||||
|
import 'package:tetra_stats/widgets/info_thingy.dart';
|
||||||
|
import 'package:tetra_stats/widgets/nerd_stats_thingy.dart';
|
||||||
|
|
||||||
class DestinationCalculator extends StatefulWidget{
|
class DestinationCalculator extends StatefulWidget{
|
||||||
final BoxConstraints constraints;
|
final BoxConstraints constraints;
|
||||||
|
@ -243,7 +245,7 @@ class _DestinationCalculatorState extends State<DestinationCalculator> {
|
||||||
child: NerdStatsThingy(nerdStats: nerdStats!)
|
child: NerdStatsThingy(nerdStats: nerdStats!)
|
||||||
),
|
),
|
||||||
if (playstyle != null) Card(
|
if (playstyle != null) Card(
|
||||||
child: GraphsThingy(nerdStats: nerdStats!, playstyle: playstyle!, apm: apm!, pps: pps!, vs: vs!)
|
child: Graphs(apm!, pps!, vs!, nerdStats!, playstyle!)
|
||||||
),
|
),
|
||||||
if (nerdStats == null) InfoThingy("Enter values and press \"Calc\" to see Nerd Stats for them")
|
if (nerdStats == null) InfoThingy("Enter values and press \"Calc\" to see Nerd Stats for them")
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
||||||
import 'package:tetra_stats/data_objects/cutoff_tetrio.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_constants.dart';
|
||||||
|
@ -9,8 +8,8 @@ import 'package:tetra_stats/data_objects/tetrio_player_from_leaderboard.dart';
|
||||||
import 'package:tetra_stats/main.dart';
|
import 'package:tetra_stats/main.dart';
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
import 'package:tetra_stats/utils/text_shadow.dart';
|
import 'package:tetra_stats/utils/text_shadow.dart';
|
||||||
import 'package:tetra_stats/views/main_view_tiles.dart';
|
|
||||||
import 'package:tetra_stats/views/rank_view.dart';
|
import 'package:tetra_stats/views/rank_view.dart';
|
||||||
|
import 'package:tetra_stats/widgets/future_error.dart';
|
||||||
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
||||||
import 'package:vector_math/vector_math_64.dart' hide Colors;
|
import 'package:vector_math/vector_math_64.dart' hide Colors;
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,9 @@ import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
import 'package:tetra_stats/main.dart';
|
import 'package:tetra_stats/main.dart';
|
||||||
import 'package:tetra_stats/services/crud_exceptions.dart';
|
import 'package:tetra_stats/services/crud_exceptions.dart';
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
import 'package:tetra_stats/views/main_view_tiles.dart';
|
import 'package:tetra_stats/views/main_view.dart';
|
||||||
|
import 'package:tetra_stats/widgets/error_thingy.dart';
|
||||||
|
import 'package:tetra_stats/widgets/future_error.dart';
|
||||||
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
||||||
|
|
||||||
class DestinationGraphs extends StatefulWidget{
|
class DestinationGraphs extends StatefulWidget{
|
||||||
|
@ -207,7 +209,6 @@ class _DestinationGraphsState extends State<DestinationGraphs> {
|
||||||
if (snapshot.data!.isEmpty || !snapshot.data!.containsKey(_season)) return ErrorThingy(eText: "Not enough data");
|
if (snapshot.data!.isEmpty || !snapshot.data!.containsKey(_season)) return ErrorThingy(eText: "Not enough data");
|
||||||
List<_HistoryChartSpot> selectedGraph = snapshot.data![_season]![_Ychart]!;
|
List<_HistoryChartSpot> selectedGraph = snapshot.data![_season]![_Ychart]!;
|
||||||
yAxisTitle = chartsShortTitles[_Ychart]!;
|
yAxisTitle = chartsShortTitles[_Ychart]!;
|
||||||
// TODO: this graph can Krash
|
|
||||||
return SfCartesianChart(
|
return SfCartesianChart(
|
||||||
tooltipBehavior: _historyTooltipBehavior,
|
tooltipBehavior: _historyTooltipBehavior,
|
||||||
zoomPanBehavior: _zoomPanBehavior,
|
zoomPanBehavior: _zoomPanBehavior,
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
@ -20,12 +19,25 @@ import 'package:tetra_stats/utils/colors_functions.dart';
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
import 'package:tetra_stats/utils/relative_timestamps.dart';
|
import 'package:tetra_stats/utils/relative_timestamps.dart';
|
||||||
import 'package:tetra_stats/utils/text_shadow.dart';
|
import 'package:tetra_stats/utils/text_shadow.dart';
|
||||||
import 'package:tetra_stats/views/main_view_tiles.dart';
|
import 'package:tetra_stats/views/main_view.dart';
|
||||||
import 'package:tetra_stats/views/singleplayer_record_view.dart';
|
import 'package:tetra_stats/views/singleplayer_record_view.dart';
|
||||||
|
import 'package:tetra_stats/widgets/badges_thingy.dart';
|
||||||
|
import 'package:tetra_stats/widgets/distinguishment_thingy.dart';
|
||||||
|
import 'package:tetra_stats/widgets/error_thingy.dart';
|
||||||
|
import 'package:tetra_stats/widgets/fake_distinguishment_thingy.dart';
|
||||||
import 'package:tetra_stats/widgets/finesse_thingy.dart';
|
import 'package:tetra_stats/widgets/finesse_thingy.dart';
|
||||||
|
import 'package:tetra_stats/widgets/future_error.dart';
|
||||||
|
import 'package:tetra_stats/widgets/graphs.dart';
|
||||||
import 'package:tetra_stats/widgets/lineclears_thingy.dart';
|
import 'package:tetra_stats/widgets/lineclears_thingy.dart';
|
||||||
|
import 'package:tetra_stats/widgets/nerd_stats_thingy.dart';
|
||||||
|
import 'package:tetra_stats/widgets/news_thingy.dart';
|
||||||
import 'package:tetra_stats/widgets/sp_trailing_stats.dart';
|
import 'package:tetra_stats/widgets/sp_trailing_stats.dart';
|
||||||
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
||||||
|
import 'package:tetra_stats/widgets/tl_rating_thingy.dart';
|
||||||
|
import 'package:tetra_stats/widgets/tl_records_thingy.dart';
|
||||||
|
import 'package:tetra_stats/widgets/tl_thingy.dart';
|
||||||
|
import 'package:tetra_stats/widgets/user_thingy.dart';
|
||||||
|
import 'package:tetra_stats/widgets/zenith_thingy.dart';
|
||||||
|
|
||||||
class DestinationHome extends StatefulWidget{
|
class DestinationHome extends StatefulWidget{
|
||||||
final String searchFor;
|
final String searchFor;
|
||||||
|
@ -423,8 +435,8 @@ class _DestinationHomeState extends State<DestinationHome> with SingleTickerProv
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (data.nerdStats != null) NerdStatsThingy(nerdStats: data.nerdStats!, oldNerdStats: toCompare?.nerdStats, averages: averages),
|
if (data.nerdStats != null) NerdStatsThingy(nerdStats: data.nerdStats!, oldNerdStats: toCompare?.nerdStats, averages: averages, lbPos: lbPos),
|
||||||
if (data.nerdStats != null) GraphsThingy(nerdStats: data.nerdStats!, playstyle: data.playstyle!, apm: data.apm!, pps: data.pps!, vs: data.vs!)
|
if (data.nerdStats != null) Graphs(data.apm!, data.pps!, data.vs!, data.nerdStats!, data.playstyle!)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -713,7 +725,7 @@ class _DestinationHomeState extends State<DestinationHome> with SingleTickerProv
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (record != null) NerdStatsThingy(nerdStats: record.aggregateStats.nerdStats),
|
if (record != null) NerdStatsThingy(nerdStats: record.aggregateStats.nerdStats),
|
||||||
if (record != null) GraphsThingy(nerdStats: record.aggregateStats.nerdStats, playstyle: record.aggregateStats.playstyle, apm: record.aggregateStats.apm, pps: record.aggregateStats.pps, vs: record.aggregateStats.vs)
|
if (record != null) Graphs(record.aggregateStats.apm, record.aggregateStats.pps, record.aggregateStats.vs, record.aggregateStats.nerdStats, record.aggregateStats.playstyle)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1016,7 +1028,7 @@ class _DestinationHomeState extends State<DestinationHome> with SingleTickerProv
|
||||||
width: 450,
|
width: 450,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
NewUserThingy(player: snapshot.data!.player!, initIsTracking: snapshot.data!.isTracked, showStateTimestamp: false, setState: setState),
|
UserThingy(player: snapshot.data!.player!, initIsTracking: snapshot.data!.isTracked, showStateTimestamp: false, setState: setState),
|
||||||
if (snapshot.data!.player!.badges.isNotEmpty) BadgesThingy(badges: snapshot.data!.player!.badges),
|
if (snapshot.data!.player!.badges.isNotEmpty) BadgesThingy(badges: snapshot.data!.player!.badges),
|
||||||
if (snapshot.data!.player!.distinguishment != null) DistinguishmentThingy(snapshot.data!.player!.distinguishment!),
|
if (snapshot.data!.player!.distinguishment != null) DistinguishmentThingy(snapshot.data!.player!.distinguishment!),
|
||||||
if (snapshot.data!.player!.role == "bot") FakeDistinguishmentThingy(bot: true, botMaintainers: snapshot.data!.player!.botmaster),
|
if (snapshot.data!.player!.role == "bot") FakeDistinguishmentThingy(bot: true, botMaintainers: snapshot.data!.player!.botmaster),
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/tetrio_constants.dart';
|
||||||
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
|
import 'package:tetra_stats/views/sprint_and_blitz_averages.dart';
|
||||||
|
|
||||||
|
class DestinationInfo extends StatefulWidget{
|
||||||
|
final BoxConstraints constraints;
|
||||||
|
|
||||||
|
const DestinationInfo({super.key, required this.constraints});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DestinationInfo> createState() => _DestinationInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
class InfoCard extends StatelessWidget {
|
||||||
|
final double height;
|
||||||
|
final String assetLink;
|
||||||
|
final String? assetLinkOnFocus;
|
||||||
|
final String title;
|
||||||
|
final String description;
|
||||||
|
final void Function() onPressed;
|
||||||
|
|
||||||
|
const InfoCard({required this.height, required this.assetLink, required this.title, required this.description, this.assetLinkOnFocus, required this.onPressed});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
child: SizedBox(
|
||||||
|
width: 450,
|
||||||
|
height: height,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Image.asset(assetLink, fit: BoxFit.cover, height: 300.0),
|
||||||
|
TextButton(child: Text(title, style: Theme.of(context).textTheme.titleLarge!.copyWith(decoration: TextDecoration.underline, decorationColor: Colors.white70, decorationStyle: TextDecorationStyle.dotted), textAlign: TextAlign.center), onPressed: onPressed),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(12.0),
|
||||||
|
child: Text(description),
|
||||||
|
),
|
||||||
|
Spacer()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DestinationInfo extends State<DestinationInfo> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Card(
|
||||||
|
child: Center(child: Text("Information Center", style: Theme.of(context).textTheme.titleLarge)),
|
||||||
|
),
|
||||||
|
SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
InfoCard(
|
||||||
|
height: widget.constraints.maxHeight - 77,
|
||||||
|
assetLink: "res/images/info card 1 focus.png",
|
||||||
|
title: "40 Lines & Blitz Averages",
|
||||||
|
description: "Since calculating 40 Lines & Blitz averages is tedious process, it gets updated only once in a while. Click on the title of this card to see the full 40 Lines & Blitz averages table\n\n${t.sprintAndBlitsRelevance(date: DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).format(sprintAndBlitzRelevance))}",
|
||||||
|
onPressed: (){
|
||||||
|
Navigator.push(context, MaterialPageRoute(
|
||||||
|
builder: (context) => SprintAndBlitzView(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
),
|
||||||
|
InfoCard(
|
||||||
|
height: widget.constraints.maxHeight - 77,
|
||||||
|
assetLink: "res/images/Снимок экрана_2023-11-06_01-00-50.png",
|
||||||
|
title: "Shizuru!",
|
||||||
|
description: "Shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru\nNakatsu Shizuru Nakatsu Shizuru Nakatsu Shizuru Nakatsu Shizuru Nakatsu Shizuru Nakatsu Shizuru Nakatsu Shizuru ",
|
||||||
|
onPressed: (){}
|
||||||
|
),
|
||||||
|
InfoCard(
|
||||||
|
height: widget.constraints.maxHeight - 77,
|
||||||
|
assetLink: "res/images/Снимок экрана_2023-11-06_01-00-50.png",
|
||||||
|
title: "About Tetra Stats",
|
||||||
|
description: "Developed by dan63\n",
|
||||||
|
onPressed: (){},
|
||||||
|
),
|
||||||
|
Card()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,8 +7,8 @@ import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
import 'package:tetra_stats/main.dart';
|
import 'package:tetra_stats/main.dart';
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
import 'package:tetra_stats/utils/relative_timestamps.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';
|
import 'package:tetra_stats/views/user_view.dart';
|
||||||
|
import 'package:tetra_stats/widgets/future_error.dart';
|
||||||
|
|
||||||
class DestinationLeaderboards extends StatefulWidget{
|
class DestinationLeaderboards extends StatefulWidget{
|
||||||
final BoxConstraints constraints;
|
final BoxConstraints constraints;
|
||||||
|
|
|
@ -6,8 +6,10 @@ import 'package:tetra_stats/data_objects/tetrio_constants.dart';
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
import 'package:tetra_stats/main.dart';
|
import 'package:tetra_stats/main.dart';
|
||||||
import 'package:tetra_stats/utils/numers_formats.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/views/state_view.dart';
|
||||||
|
import 'package:tetra_stats/widgets/alpha_league_entry_thingy.dart';
|
||||||
|
import 'package:tetra_stats/widgets/future_error.dart';
|
||||||
|
import 'package:tetra_stats/widgets/info_thingy.dart';
|
||||||
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
||||||
|
|
||||||
class DestinationSavedData extends StatefulWidget{
|
class DestinationSavedData extends StatefulWidget{
|
||||||
|
|
|
@ -13,7 +13,7 @@ import 'package:tetra_stats/main.dart';
|
||||||
import 'package:tetra_stats/utils/filesizes_converter.dart';
|
import 'package:tetra_stats/utils/filesizes_converter.dart';
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
import 'package:tetra_stats/utils/relative_timestamps.dart';
|
import 'package:tetra_stats/utils/relative_timestamps.dart';
|
||||||
import 'package:tetra_stats/views/main_view_tiles.dart';
|
import 'package:tetra_stats/widgets/future_error.dart';
|
||||||
|
|
||||||
class DestinationSettings extends StatefulWidget{
|
class DestinationSettings extends StatefulWidget{
|
||||||
final BoxConstraints constraints;
|
final BoxConstraints constraints;
|
||||||
|
@ -50,7 +50,6 @@ class _DestinationSettings extends State<DestinationSettings> with SingleTickerP
|
||||||
late bool showPositions;
|
late bool showPositions;
|
||||||
late bool showAverages;
|
late bool showAverages;
|
||||||
late bool updateInBG;
|
late bool updateInBG;
|
||||||
final TextEditingController _playertext = TextEditingController();
|
|
||||||
late AnimationController _defaultNicknameAnimController;
|
late AnimationController _defaultNicknameAnimController;
|
||||||
late Animation _goodDefaultNicknameAnim;
|
late Animation _goodDefaultNicknameAnim;
|
||||||
late Animation _badDefaultNicknameAnim;
|
late Animation _badDefaultNicknameAnim;
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,84 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:tetra_stats/main.dart' show teto;
|
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
|
||||||
import 'package:window_manager/window_manager.dart';
|
|
||||||
|
|
||||||
late String oldWindowTitle;
|
|
||||||
|
|
||||||
class MatchesView extends StatefulWidget {
|
|
||||||
final String userID;
|
|
||||||
final String username;
|
|
||||||
const MatchesView({super.key, required this.userID, required this.username});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<StatefulWidget> createState() => MatchesState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class MatchesState extends State<MatchesView> {
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS){
|
|
||||||
windowManager.getTitle().then((value) => oldWindowTitle = value);
|
|
||||||
windowManager.setTitle("Tetra Stats: ${t.matchesViewTitle(nickname: widget.username)}");
|
|
||||||
}
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose(){
|
|
||||||
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) windowManager.setTitle(oldWindowTitle);
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final t = Translations.of(context);
|
|
||||||
bool bigScreen = MediaQuery.of(context).size.width > 768;
|
|
||||||
final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms();
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text(t.matchesViewTitle(nickname: widget.username)),
|
|
||||||
),
|
|
||||||
backgroundColor: Colors.black,
|
|
||||||
body: SafeArea(
|
|
||||||
child: FutureBuilder(
|
|
||||||
future: teto.getTLMatchesbyPlayerID(widget.userID),
|
|
||||||
builder: (context, snapshot){
|
|
||||||
switch (snapshot.connectionState) {
|
|
||||||
case ConnectionState.none:
|
|
||||||
case ConnectionState.waiting:
|
|
||||||
case ConnectionState.active:
|
|
||||||
return const Center(child: CircularProgressIndicator(color: Colors.white));
|
|
||||||
case ConnectionState.done:
|
|
||||||
return ListView(
|
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
|
||||||
children: (snapshot.data!.isNotEmpty)
|
|
||||||
? [for (var value in snapshot.data!) ListTile(
|
|
||||||
leading: Text("${value.endContext.firstWhere((element) => element.userId == widget.userID).points} : ${value.endContext.firstWhere((element) => element.userId != widget.userID).points}",
|
|
||||||
style: bigScreen ? const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28) :
|
|
||||||
const TextStyle(fontSize: 28)),
|
|
||||||
title: Text("vs. ${value.endContext.firstWhere((element) => element.userId != widget.userID).username}"),
|
|
||||||
subtitle: Text(dateFormat.format(value.timestamp)),
|
|
||||||
trailing: IconButton(
|
|
||||||
icon: const Icon(Icons.delete_forever),
|
|
||||||
onPressed: () {
|
|
||||||
DateTime nn = value.timestamp;
|
|
||||||
teto.deleteTLMatch(value.ownId).then((value) => setState(() {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.matchRemoved(date: dateFormat.format(nn)))));
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)]
|
|
||||||
: [Center(child: Text(t.noRecords, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)))],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,554 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:intl/intl.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/views/main_view.dart' show MainView;
|
|
||||||
import 'package:window_manager/window_manager.dart';
|
|
||||||
import 'package:syncfusion_flutter_charts/charts.dart';
|
|
||||||
|
|
||||||
var _chartsShortTitlesDropdowns = <DropdownMenuItem>[for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value))];
|
|
||||||
Stats _chartsX = Stats.tr;
|
|
||||||
Stats _chartsY = Stats.apm;
|
|
||||||
late TooltipBehavior _tooltipBehavior;
|
|
||||||
late ZoomPanBehavior _zoomPanBehavior;
|
|
||||||
List<DropdownMenuItem> _itemStats = [for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value))];
|
|
||||||
List<_MyScatterSpot> _spots = [];
|
|
||||||
Stats _sortBy = Stats.tr;
|
|
||||||
late List<TetrioPlayerFromLeaderboard> they;
|
|
||||||
bool _reversed = false;
|
|
||||||
List<DropdownMenuItem> _itemCountries = [for (MapEntry e in t.countries.entries) DropdownMenuItem(value: e.key, child: Text(e.value))];
|
|
||||||
String _country = "";
|
|
||||||
late String _oldWindowTitle;
|
|
||||||
final NumberFormat _f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2);
|
|
||||||
final NumberFormat _f4 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 4);
|
|
||||||
|
|
||||||
class RankView extends StatefulWidget {
|
|
||||||
final List rank;
|
|
||||||
const RankView({super.key, required this.rank});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<StatefulWidget> createState() => RankState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class RankState extends State<RankView> with SingleTickerProviderStateMixin {
|
|
||||||
late ScrollController _scrollController;
|
|
||||||
late TabController _tabController;
|
|
||||||
late String previousAxisTitles;
|
|
||||||
late double minX;
|
|
||||||
late double actualMinX;
|
|
||||||
late double maxX;
|
|
||||||
late double actualMaxX;
|
|
||||||
late double minY;
|
|
||||||
late double actualMinY;
|
|
||||||
late double maxY;
|
|
||||||
late double actualMaxY;
|
|
||||||
late double xScale;
|
|
||||||
late double yScale;
|
|
||||||
String headerTooltip = t.pseudoTooltipHeaderInit;
|
|
||||||
String footerTooltip = t.pseudoTooltipFooterInit;
|
|
||||||
ValueNotifier<int> hoveredPointId = ValueNotifier<int>(-1);
|
|
||||||
double scaleFactor = 5e2;
|
|
||||||
double dragFactor = 7e2;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
_scrollController = ScrollController();
|
|
||||||
_tabController = TabController(length: 6, vsync: this);
|
|
||||||
_zoomPanBehavior = ZoomPanBehavior(
|
|
||||||
enablePinching: true,
|
|
||||||
enableSelectionZooming: true,
|
|
||||||
enableMouseWheelZooming : true,
|
|
||||||
enablePanning: true,
|
|
||||||
);
|
|
||||||
_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[_chartsX]}\n${_f4.format(data.y)} ${chartsShortTitles[_chartsY]}')
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS){
|
|
||||||
windowManager.getTitle().then((value) => _oldWindowTitle = value);
|
|
||||||
windowManager.setTitle("Tetra Stats: ${widget.rank[1]["everyone"] ? t.everyoneAverages : t.rankAverages(rank: widget.rank[0].rank.toUpperCase())}");
|
|
||||||
}
|
|
||||||
super.initState();
|
|
||||||
previousAxisTitles = _chartsX.toString()+_chartsY.toString();
|
|
||||||
they = TetrioPlayersLeaderboard("lol", []).getStatRanking(widget.rank[1]["entries"]!, _sortBy, reversed: _reversed, country: _country);
|
|
||||||
createSpots();
|
|
||||||
}
|
|
||||||
|
|
||||||
void createSpots(){
|
|
||||||
_spots = [
|
|
||||||
for (TetrioPlayerFromLeaderboard entry in widget.rank[1]["entries"])
|
|
||||||
if (entry.apm != 0.0 && entry.vs != 0.0) // prevents from ScatterChart "Offset argument contained a NaN value." exception
|
|
||||||
_MyScatterSpot(
|
|
||||||
entry.getStatByEnum(_chartsX).toDouble(),
|
|
||||||
entry.getStatByEnum(_chartsY).toDouble(),
|
|
||||||
entry.userId,
|
|
||||||
entry.username,
|
|
||||||
entry.rank,
|
|
||||||
rankColors[entry.rank]??Colors.white
|
|
||||||
)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_tabController.dispose();
|
|
||||||
_scrollController.dispose();
|
|
||||||
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) windowManager.setTitle(_oldWindowTitle);
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _justUpdate() {
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
bool bigScreen = MediaQuery.of(context).size.width > 768;
|
|
||||||
if (previousAxisTitles != _chartsX.toString()+_chartsY.toString()){
|
|
||||||
createSpots();
|
|
||||||
previousAxisTitles = _chartsX.toString()+_chartsY.toString();
|
|
||||||
}
|
|
||||||
final t = Translations.of(context);
|
|
||||||
//they = TetrioPlayersLeaderboard("lol", []).getStatRanking(widget.rank[1]["entries"]!, _sortBy, reversed: _reversed, country: _country);
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text(widget.rank[1]["everyone"] ? t.everyoneAverages : t.rankAverages(rank: widget.rank[0].rank.toUpperCase())),
|
|
||||||
),
|
|
||||||
backgroundColor: Colors.black,
|
|
||||||
body: SafeArea(
|
|
||||||
child: NestedScrollView(
|
|
||||||
controller: _scrollController,
|
|
||||||
headerSliverBuilder: (context, value) {
|
|
||||||
return [ SliverToBoxAdapter(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Flex(
|
|
||||||
direction: Axis.vertical,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Stack(
|
|
||||||
alignment: Alignment.topCenter,
|
|
||||||
children: [Image.asset("res/tetrio_tl_alpha_ranks/${widget.rank[0].rank}.png",fit: BoxFit.fitHeight,height: 128), ],
|
|
||||||
),
|
|
||||||
Flexible(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
widget.rank[1]["everyone"] ? t.everyoneAverages : t.rankAverages(rank: widget.rank[0].rank.toUpperCase()),
|
|
||||||
style: TextStyle(
|
|
||||||
fontFamily: "Eurostile Round Extended",
|
|
||||||
fontSize: bigScreen ? 42 : 28)),
|
|
||||||
Text(
|
|
||||||
t.players(n: widget.rank[1]["entries"].length),
|
|
||||||
style: TextStyle(
|
|
||||||
fontFamily: "Eurostile Round Extended",
|
|
||||||
fontSize: bigScreen ? 42 : 28)),
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: TabBar(
|
|
||||||
controller: _tabController,
|
|
||||||
isScrollable: true,
|
|
||||||
tabs: [
|
|
||||||
Tab(text: t.chart),
|
|
||||||
Tab(text: t.entries),
|
|
||||||
Tab(text: t.minimums),
|
|
||||||
Tab(text: t.averages),
|
|
||||||
Tab(text: t.maximums),
|
|
||||||
Tab(text: t.other),
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
];
|
|
||||||
},
|
|
||||||
body: TabBarView(
|
|
||||||
controller: _tabController,
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Wrap(
|
|
||||||
direction: Axis.horizontal,
|
|
||||||
alignment: WrapAlignment.center,
|
|
||||||
crossAxisAlignment: WrapCrossAlignment.end,
|
|
||||||
spacing: 20,
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Padding(
|
|
||||||
padding: EdgeInsets.all(8.0),
|
|
||||||
child: Text("X:", style: TextStyle(fontSize: 22))),
|
|
||||||
DropdownButton(
|
|
||||||
items: _chartsShortTitlesDropdowns,
|
|
||||||
value: _chartsX,
|
|
||||||
onChanged: (value) {
|
|
||||||
_chartsX = value;
|
|
||||||
_justUpdate();
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Padding(
|
|
||||||
padding: EdgeInsets.all(8.0),
|
|
||||||
child: Text("Y:", style: TextStyle(fontSize: 22)),
|
|
||||||
),
|
|
||||||
DropdownButton(
|
|
||||||
items: _chartsShortTitlesDropdowns,
|
|
||||||
value: _chartsY,
|
|
||||||
onChanged: (value) {
|
|
||||||
_chartsY = value;
|
|
||||||
_justUpdate();
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
IconButton(onPressed: () => _zoomPanBehavior.reset(), icon: const Icon(Icons.refresh), alignment: Alignment.center,)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (widget.rank[1]["entries"].length > 1)
|
|
||||||
SizedBox(
|
|
||||||
width: MediaQuery.of(context).size.width,
|
|
||||||
height: MediaQuery.of(context).size.height - 104,
|
|
||||||
child: Padding(
|
|
||||||
padding: bigScreen ? const EdgeInsets.fromLTRB(40, 10, 40, 20) : const EdgeInsets.fromLTRB(0, 10, 16, 20),
|
|
||||||
child: Listener(
|
|
||||||
behavior: HitTestBehavior.translucent,
|
|
||||||
onPointerSignal: (signal) {
|
|
||||||
if (signal is PointerScrollEvent) {
|
|
||||||
setState(() {
|
|
||||||
_scrollController.jumpTo(_scrollController.position.maxScrollExtent - signal.scrollDelta.dy); // TODO: find a better way to stop scrolling in NestedScrollView
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: SfCartesianChart(
|
|
||||||
tooltipBehavior: _tooltipBehavior,
|
|
||||||
zoomPanBehavior: _zoomPanBehavior,
|
|
||||||
//primaryXAxis: CategoryAxis(),
|
|
||||||
series: [
|
|
||||||
ScatterSeries(
|
|
||||||
enableTooltip: true,
|
|
||||||
dataSource: _spots,
|
|
||||||
animationDuration: 0,
|
|
||||||
pointColorMapper: (data, _) => data.color,
|
|
||||||
xValueMapper: (data, _) => data.x,
|
|
||||||
yValueMapper: (data, _) => data.y,
|
|
||||||
onPointTap: (point) => Navigator.push(context, MaterialPageRoute(builder: (context) => MainView(player: _spots[point.pointIndex!].nickname), maintainState: false)),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
))
|
|
||||||
else Center(child: Text(t.notEnoughData, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)))
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 16),
|
|
||||||
child: Wrap(
|
|
||||||
direction: Axis.horizontal,
|
|
||||||
alignment: WrapAlignment.start,
|
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
|
||||||
spacing: 16,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
|
||||||
textBaseline: TextBaseline.alphabetic,
|
|
||||||
children: [
|
|
||||||
Text("${t.sortBy}: ", style: const TextStyle(color: Colors.white, fontSize: 25)),
|
|
||||||
DropdownButton(
|
|
||||||
items: _itemStats,
|
|
||||||
value: _sortBy,
|
|
||||||
onChanged: ((value) {
|
|
||||||
_sortBy = value;
|
|
||||||
setState(() {
|
|
||||||
they = TetrioPlayersLeaderboard("lol", []).getStatRanking(widget.rank[1]["entries"]!, _sortBy, reversed: _reversed, country: _country);
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
|
||||||
textBaseline: TextBaseline.alphabetic,
|
|
||||||
children: [
|
|
||||||
Text("${t.reversed}: ", style: const TextStyle(color: Colors.white, fontSize: 25)),
|
|
||||||
Padding(padding: const EdgeInsets.fromLTRB(0, 5.5, 0, 7.5),
|
|
||||||
child: Checkbox(
|
|
||||||
value: _reversed,
|
|
||||||
checkColor: Colors.black,
|
|
||||||
onChanged: ((value) {
|
|
||||||
_reversed = value!;
|
|
||||||
setState(() {
|
|
||||||
they = TetrioPlayersLeaderboard("lol", []).getStatRanking(widget.rank[1]["entries"]!, _sortBy, reversed: _reversed, country: _country);
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
|
||||||
textBaseline: TextBaseline.alphabetic,
|
|
||||||
children: [
|
|
||||||
Text("${t.country}: ", style: const TextStyle(color: Colors.white, fontSize: 25)),
|
|
||||||
DropdownButton(
|
|
||||||
items: _itemCountries,
|
|
||||||
value: _country,
|
|
||||||
onChanged: ((value) {
|
|
||||||
_country = value;
|
|
||||||
setState(() {
|
|
||||||
they = TetrioPlayersLeaderboard("lol", []).getStatRanking(widget.rank[1]["entries"]!, _sortBy, reversed: _reversed, country: _country);
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: ListView.builder(
|
|
||||||
itemCount: they.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
bool bigScreen = MediaQuery.of(context).size.width > 768;
|
|
||||||
return ListTile(
|
|
||||||
title: Text(they[index].username, style: const TextStyle(fontFamily: "Eurostile Round Extended")),
|
|
||||||
subtitle: Text(
|
|
||||||
_sortBy == Stats.tr ? "${_f2.format(they[index].apm)} APM, ${_f2.format(they[index].pps)} PPS, ${_f2.format(they[index].vs)} VS, ${_f2.format(they[index].nerdStats.app)} APP, ${_f2.format(they[index].nerdStats.vsapm)} VS/APM" : "${_f4.format(they[index].getStatByEnum(_sortBy))} ${chartsShortTitles[_sortBy]}",
|
|
||||||
style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
|
|
||||||
trailing: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text("${_f2.format(they[index].tr)} TR", style: bigScreen ? const TextStyle(fontSize: 28) : null),
|
|
||||||
Image.asset("res/tetrio_tl_alpha_ranks/${they[index].rank}.png", height: bigScreen ? 48 : 16),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => MainView(player: they[index].username), maintainState: false));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Text(t.lowestValues, style: TextStyle( fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
|
|
||||||
Expanded(
|
|
||||||
child: ListView(
|
|
||||||
children: [
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestTR"], label: t.statCellNum.tr.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestTRid"], username: widget.rank[1]["lowestTRnick"], approximate: false, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestGlixare"], label: "Glixare", id: widget.rank[1]["lowestGlixareID"], username: widget.rank[1]["lowestGlixareNick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestS1tr"], label: "S1 ${t.statCellNum.tr.replaceAll(RegExp(r'\n'), " ")}", id: widget.rank[1]["lowestS1trID"], username: widget.rank[1]["lowestS1trNick"], approximate: false, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestGlicko"], label: "Glicko", id: widget.rank[1]["lowestGlickoID"], username: widget.rank[1]["lowestGlickoNick"], approximate: false, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestRD"], label: t.statCellNum.rd.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestRdID"], username: widget.rank[1]["lowestRdNick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestGamesPlayed"], label: t.statCellNum.gamesPlayed.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestGamesPlayedID"], username: widget.rank[1]["lowestGamesPlayedNick"], approximate: false),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestGamesWon"], label: t.statCellNum.gamesWonTL.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestGamesWonID"], username: widget.rank[1]["lowestGamesWonNick"], approximate: false),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestWinrate"] * 100, label: t.statCellNum.winrate.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestWinrateID"], username: widget.rank[1]["lowestWinrateNick"], approximate: false, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestAPM"], label: t.statCellNum.apm.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestAPMid"], username: widget.rank[1]["lowestAPMnick"], approximate: false, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestPPS"], label: t.statCellNum.pps.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestPPSid"], username: widget.rank[1]["lowestPPSnick"], approximate: false, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestVS"], label: t.statCellNum.vs.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestVSid"], username: widget.rank[1]["lowestVSnick"], approximate: false, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestAPP"], label: t.statCellNum.app.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestAPPid"], username: widget.rank[1]["lowestAPPnick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestVSAPM"], label: "VS / APM", id: widget.rank[1]["lowestVSAPMid"], username: widget.rank[1]["lowestVSAPMnick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestDSS"], label: t.statCellNum.dss.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestDSSid"], username: widget.rank[1]["lowestDSSnick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestDSP"], label: t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestDSPid"], username: widget.rank[1]["lowestDSPnick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestAPPDSP"], label: t.statCellNum.appdsp.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestAPPDSPid"], username: widget.rank[1]["lowestAPPDSPnick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestCheese"], label: t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestCheeseID"], username: widget.rank[1]["lowestCheeseNick"], approximate: false, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestGBE"], label: t.statCellNum.gbe.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestGBEid"], username: widget.rank[1]["lowestGBEnick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestNyaAPP"], label: t.statCellNum.nyaapp.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestNyaAPPid"], username: widget.rank[1]["lowestNyaAPPnick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestArea"], label: t.statCellNum.area.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestAreaID"], username: widget.rank[1]["lowestAreaNick"], approximate: false, fractionDigits: 1),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestEstTR"], label: t.statCellNum.estOfTR.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestEstTRid"], username: widget.rank[1]["lowestEstTRnick"], approximate: false, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestEstAcc"], label: t.statCellNum.accOfEst.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestEstAccID"], username: widget.rank[1]["lowestEstAccNick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestOpener"], label: "Opener", id: widget.rank[1]["lowestOpenerID"], username: widget.rank[1]["lowestOpenerNick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestPlonk"], label: "Plonk", id: widget.rank[1]["lowestPlonkID"], username: widget.rank[1]["lowestPlonkNick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestStride"], label: "Stride", id: widget.rank[1]["lowestStrideID"], username: widget.rank[1]["lowestStrideNick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["lowestInfDS"], label: "Inf. DS", id: widget.rank[1]["lowestInfDSid"], username: widget.rank[1]["lowestInfDSnick"], approximate: false, fractionDigits: 3)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Text(t.averageValues, style: TextStyle( fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
|
|
||||||
Expanded(
|
|
||||||
child: ListView(children: [
|
|
||||||
_ListEntry(value: widget.rank[0].tr, label: t.statCellNum.tr.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[0].gxe, label: "Glixare", id: "", username: "", approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[0].s1tr, label: "S1 ${t.statCellNum.tr.replaceAll(RegExp(r'\n'), " ")}", id: "", username: "", approximate: false, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[0].glicko, label: "Glicko", id: "", username: "", approximate: true, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[0].rd, label: t.statCellNum.rd.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[0].gamesPlayed, label: t.statCellNum.gamesPlayed.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 0),
|
|
||||||
_ListEntry(value: widget.rank[0].gamesWon, label: t.statCellNum.gamesWonTL.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 0),
|
|
||||||
_ListEntry(value: widget.rank[0].winrate * 100, label: t.statCellNum.winrate.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[0].apm, label: t.statCellNum.apm.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[0].pps, label: t.statCellNum.pps.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[0].vs, label: t.statCellNum.vs.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[1]["avgAPP"], label: t.statCellNum.app.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["avgVSAPM"], label: "VS / APM", id: "", username: "", approximate: true, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["avgDSS"], label: t.statCellNum.dss.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["avgDSP"], label: t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["avgAPPDSP"], label: t.statCellNum.appdsp.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["avgCheese"], label: t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[1]["avgGBE"], label: t.statCellNum.gbe.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["avgNyaAPP"], label: t.statCellNum.nyaapp.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["avgArea"], label: t.statCellNum.area.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 1),
|
|
||||||
_ListEntry(value: widget.rank[1]["avgEstTR"], label: t.statCellNum.estOfTR.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[1]["avgEstAcc"], label: t.statCellNum.accOfEst.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["avgOpener"], label: "Opener", id: "", username: "", approximate: true, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["avgPlonk"], label: "Plonk", id: "", username: "", approximate: true, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["avgStride"], label: "Stride", id: "", username: "", approximate: true, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["avgInfDS"], label: "Inf. DS", id: "", username: "", approximate: true, fractionDigits: 3),
|
|
||||||
]))
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Text(t.highestValues, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
|
|
||||||
Expanded(
|
|
||||||
child: ListView(
|
|
||||||
children: [
|
|
||||||
_ListEntry(value: widget.rank[1]["highestTR"], label: t.statCellNum.tr.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestTRid"], username: widget.rank[1]["highestTRnick"], approximate: false, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestGlixare"], label: "Glixare", id: widget.rank[1]["highestGlixareID"], username: widget.rank[1]["highestGlixareNick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestS1tr"], label: "S1 ${t.statCellNum.tr.replaceAll(RegExp(r'\n'), " ")}", id: widget.rank[1]["highestS1trID"], username: widget.rank[1]["highestS1trNick"], approximate: false, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestGlicko"], label: "Glicko", id: widget.rank[1]["highestGlickoID"], username: widget.rank[1]["highestGlickoNick"], approximate: false, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestRD"], label: t.statCellNum.rd.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestRdID"], username: widget.rank[1]["highestRdNick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestGamesPlayed"], label: t.statCellNum.gamesPlayed.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestGamesPlayedID"], username: widget.rank[1]["highestGamesPlayedNick"], approximate: false),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestGamesWon"], label: t.statCellNum.gamesWonTL.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestGamesWonID"], username: widget.rank[1]["highestGamesWonNick"], approximate: false),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestWinrate"] * 100, label: t.statCellNum.winrate.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestWinrateID"], username: widget.rank[1]["highestWinrateNick"], approximate: false, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestAPM"], label: t.statCellNum.apm.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestAPMid"], username: widget.rank[1]["highestAPMnick"], approximate: false, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestPPS"], label: t.statCellNum.pps.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestPPSid"], username: widget.rank[1]["highestPPSnick"], approximate: false, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestVS"], label: t.statCellNum.vs.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestVSid"], username: widget.rank[1]["highestVSnick"], approximate: false, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestAPP"], label: t.statCellNum.app.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestAPPid"], username: widget.rank[1]["highestAPPnick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestVSAPM"], label: "VS / APM", id: widget.rank[1]["highestVSAPMid"], username: widget.rank[1]["highestVSAPMnick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestDSS"], label: t.statCellNum.dss.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestDSSid"], username: widget.rank[1]["highestDSSnick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestDSP"], label: t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestDSPid"], username: widget.rank[1]["highestDSPnick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestAPPDSP"], label: t.statCellNum.appdsp.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestAPPDSPid"], username: widget.rank[1]["highestAPPDSPnick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestCheese"], label: t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestCheeseID"], username: widget.rank[1]["highestCheeseNick"], approximate: false, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestGBE"], label: t.statCellNum.gbe.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestGBEid"], username: widget.rank[1]["highestGBEnick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestNyaAPP"], label: t.statCellNum.nyaapp.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestNyaAPPid"], username: widget.rank[1]["highestNyaAPPnick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestArea"], label: t.statCellNum.area.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestAreaID"], username: widget.rank[1]["highestAreaNick"], approximate: false, fractionDigits: 1),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestEstTR"], label: t.statCellNum.estOfTR.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestEstTRid"], username: widget.rank[1]["highestEstTRnick"], approximate: false, fractionDigits: 2),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestEstAcc"], label: t.statCellNum.accOfEst.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestEstAccID"], username: widget.rank[1]["highestEstAccNick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestOpener"], label: "Opener", id: widget.rank[1]["highestOpenerID"], username: widget.rank[1]["highestOpenerNick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestPlonk"], label: "Plonk", id: widget.rank[1]["highestPlonkID"], username: widget.rank[1]["highestPlonkNick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestStride"], label: "Stride", id: widget.rank[1]["highestStrideID"], username: widget.rank[1]["highestStrideNick"], approximate: false, fractionDigits: 3),
|
|
||||||
_ListEntry(value: widget.rank[1]["highestInfDS"], label: "Inf. DS", id: widget.rank[1]["highestInfDSid"], username: widget.rank[1]["highestInfDSnick"], approximate: false, fractionDigits: 3),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: ListView(children: [
|
|
||||||
_ListEntry(value: widget.rank[1]["totalGamesPlayed"], label: t.statCellNum.totalGames, id: "", username: "", approximate: true, fractionDigits: 0),
|
|
||||||
_ListEntry(value: widget.rank[1]["totalGamesWon"], label: t.statCellNum.totalWon, id: "", username: "", approximate: true, fractionDigits: 0),
|
|
||||||
_ListEntry(value: (widget.rank[1]["totalGamesWon"] / widget.rank[1]["totalGamesPlayed"]) * 100, label: t.statCellNum.winrate.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 3),
|
|
||||||
]))
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
))));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ListEntry extends StatelessWidget {
|
|
||||||
final num value;
|
|
||||||
final String label;
|
|
||||||
final String id;
|
|
||||||
final String username;
|
|
||||||
final bool approximate;
|
|
||||||
final int? fractionDigits;
|
|
||||||
const _ListEntry(
|
|
||||||
{required this.value,
|
|
||||||
required this.label,
|
|
||||||
this.fractionDigits,
|
|
||||||
required this.id,
|
|
||||||
required this.username,
|
|
||||||
required this.approximate});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
NumberFormat f = NumberFormat.decimalPatternDigits(
|
|
||||||
locale: LocaleSettings.currentLocale.languageCode,
|
|
||||||
decimalDigits: fractionDigits ?? 0);
|
|
||||||
return ListTile(
|
|
||||||
title: Text(label),
|
|
||||||
trailing: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(f.format(value),
|
|
||||||
style: const TextStyle(fontSize: 22, height: 0.9)),
|
|
||||||
if (id.isNotEmpty) Text(t.forPlayer(username: username), style: const TextStyle(color: Colors.grey, fontWeight: FontWeight.w100),)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: id.isNotEmpty
|
|
||||||
? () {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => MainView(player: id),
|
|
||||||
maintainState: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:tetra_stats/data_objects/cutoff_tetrio.dart';
|
import 'package:tetra_stats/data_objects/cutoff_tetrio.dart';
|
||||||
import 'package:tetra_stats/main.dart';
|
import 'package:tetra_stats/main.dart';
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
import 'package:tetra_stats/views/main_view_tiles.dart';
|
import 'package:tetra_stats/widgets/future_error.dart';
|
||||||
|
|
||||||
class RankView extends StatefulWidget {
|
class RankView extends StatefulWidget {
|
||||||
final String rank;
|
final String rank;
|
||||||
|
|
|
@ -1,176 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:tetra_stats/data_objects/cutoff_tetrio.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/utils/text_shadow.dart';
|
|
||||||
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
|
||||||
import 'package:window_manager/window_manager.dart';
|
|
||||||
import 'package:tetra_stats/main.dart' show teto;
|
|
||||||
|
|
||||||
class RankAveragesView extends StatefulWidget {
|
|
||||||
const RankAveragesView({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<StatefulWidget> createState() => RanksAverages();
|
|
||||||
}
|
|
||||||
|
|
||||||
late String oldWindowTitle;
|
|
||||||
|
|
||||||
class RanksAverages extends State<RankAveragesView> {
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS){
|
|
||||||
windowManager.getTitle().then((value) => oldWindowTitle = value);
|
|
||||||
windowManager.setTitle("Tetra Stats: ${t.rankAveragesViewTitle}");
|
|
||||||
}
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) windowManager.setTitle(oldWindowTitle);
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text(t.rankAveragesViewTitle),
|
|
||||||
),
|
|
||||||
backgroundColor: Colors.black,
|
|
||||||
body: SafeArea(
|
|
||||||
child: FutureBuilder<CutoffsTetrio?>(future: teto.fetchCutoffsTetrio(), builder: (context, snapshot){
|
|
||||||
switch (snapshot.connectionState) {
|
|
||||||
case ConnectionState.none:
|
|
||||||
case ConnectionState.waiting:
|
|
||||||
case ConnectionState.active:
|
|
||||||
return const Center(child: CircularProgressIndicator(color: Colors.white));
|
|
||||||
case ConnectionState.done:
|
|
||||||
if (snapshot.hasData){
|
|
||||||
return Container(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
child: Container(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
width: 900,
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Table(
|
|
||||||
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
|
|
||||||
border: TableBorder.all(color: Colors.grey.shade900),
|
|
||||||
columnWidths: const {
|
|
||||||
0: FixedColumnWidth(48),
|
|
||||||
1: FixedColumnWidth(155),
|
|
||||||
2: FixedColumnWidth(150),
|
|
||||||
3: FixedColumnWidth(90),
|
|
||||||
4: FixedColumnWidth(130),
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
TableRow(
|
|
||||||
children: [
|
|
||||||
Text(t.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("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("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)),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
for (String rank in snapshot.data!.data.keys) 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(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))
|
|
||||||
]
|
|
||||||
))
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Text(t.sprintAndBlitsRelevance(date: timestamp(snapshot.data!.timestamp)))
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (snapshot.hasError){
|
|
||||||
return Center(child:
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(snapshot.error.toString(), style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 42, fontWeight: FontWeight.bold), textAlign: TextAlign.center),
|
|
||||||
if (snapshot.stackTrace != null) Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
|
||||||
child: Text(snapshot.stackTrace.toString(), style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 18), textAlign: TextAlign.center),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return const Text("end of FutureBuilder");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,302 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
import 'package:tetra_stats/data_objects/tetrio_player.dart';
|
|
||||||
import 'package:tetra_stats/main.dart' show packageInfo, teto, prefs;
|
|
||||||
import 'package:file_selector/file_selector.dart';
|
|
||||||
import 'package:file_picker/file_picker.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
|
||||||
import 'package:tetra_stats/services/crud_exceptions.dart';
|
|
||||||
import 'package:tetra_stats/utils/open_in_browser.dart';
|
|
||||||
import 'package:window_manager/window_manager.dart';
|
|
||||||
|
|
||||||
late String oldWindowTitle;
|
|
||||||
TextStyle subtitleStyle = const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey);
|
|
||||||
|
|
||||||
class SettingsView extends StatefulWidget {
|
|
||||||
const SettingsView({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<StatefulWidget> createState() => SettingsState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class SettingsState extends State<SettingsView> {
|
|
||||||
String defaultNickname = "Checking...";
|
|
||||||
late bool showPositions;
|
|
||||||
late bool updateInBG;
|
|
||||||
final TextEditingController _playertext = TextEditingController();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS){
|
|
||||||
windowManager.getTitle().then((value) => oldWindowTitle = value);
|
|
||||||
windowManager.setTitle("Tetra Stats: ${t.settings}");
|
|
||||||
}
|
|
||||||
_getPreferences();
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose(){
|
|
||||||
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) windowManager.setTitle(oldWindowTitle);
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _getPreferences() {
|
|
||||||
showPositions = prefs.getBool("showPositions") ?? false;
|
|
||||||
updateInBG = prefs.getBool("updateInBG") ?? false;
|
|
||||||
_setDefaultNickname(prefs.getString("player"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _setDefaultNickname(String? n) async {
|
|
||||||
if (n != null) {
|
|
||||||
try {
|
|
||||||
defaultNickname = await teto.getNicknameByID(n);
|
|
||||||
} on TetrioPlayerNotExist {
|
|
||||||
defaultNickname = n;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
defaultNickname = "dan63047";
|
|
||||||
}
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _setPlayer(String player) async {
|
|
||||||
await prefs.setString('player', player);
|
|
||||||
await _setDefaultNickname(player);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _removePlayer() async {
|
|
||||||
await prefs.remove('player');
|
|
||||||
await _setDefaultNickname("6098518e3d5155e6ec429cdc");
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final t = Translations.of(context);
|
|
||||||
List<DropdownMenuItem<AppLocale>>? locales = <DropdownMenuItem<AppLocale>>[];
|
|
||||||
for (var v in AppLocale.values){
|
|
||||||
locales.add(DropdownMenuItem<AppLocale>(
|
|
||||||
value: v, child: Text(t.locales[v.languageTag]!)));
|
|
||||||
}
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text(t.settings),
|
|
||||||
),
|
|
||||||
backgroundColor: Colors.black,
|
|
||||||
body: SafeArea(
|
|
||||||
child: ListView(
|
|
||||||
children: [
|
|
||||||
ListTile(
|
|
||||||
title: Text(t.exportDB),
|
|
||||||
subtitle: Text(t.exportDBDescription, style: subtitleStyle),
|
|
||||||
onTap: () {
|
|
||||||
if (kIsWeb){
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.notForWeb)));
|
|
||||||
} else if (Platform.isAndroid){
|
|
||||||
var downloadFolder = Directory("/storage/emulated/0/Download");
|
|
||||||
File exportedDB = File("${downloadFolder.path}/TetraStats.db");
|
|
||||||
getApplicationDocumentsDirectory().then((value) {
|
|
||||||
exportedDB.writeAsBytes(File("${value.path}/TetraStats.db").readAsBytesSync());
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) => AlertDialog(
|
|
||||||
title: Text(t.androidExportAlertTitle,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontFamily: "Eurostile Round Extended")),
|
|
||||||
content: SingleChildScrollView(
|
|
||||||
child: ListBody(children: [Text(t.androidExportText(exportedDB: exportedDB))]),
|
|
||||||
),
|
|
||||||
actions: <Widget>[
|
|
||||||
TextButton(
|
|
||||||
child: Text(t.popupActions.ok),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
));
|
|
||||||
});
|
|
||||||
} else if (Platform.isLinux || Platform.isWindows) {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) => AlertDialog(
|
|
||||||
title: Text(t.desktopExportAlertTitle,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontFamily: "Eurostile Round Extended")),
|
|
||||||
content: SingleChildScrollView(
|
|
||||||
child: ListBody(children: [
|
|
||||||
Text(t.desktopExportText)
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
actions: <Widget>[
|
|
||||||
TextButton(
|
|
||||||
child: Text(t.popupActions.ok),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: Text(t.importDB),
|
|
||||||
subtitle: Text(t.importDBDescription, style: subtitleStyle),
|
|
||||||
onTap: () {
|
|
||||||
if (kIsWeb){
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.notForWeb)));
|
|
||||||
}else if(Platform.isAndroid){
|
|
||||||
FilePicker.platform.pickFiles(
|
|
||||||
type: FileType.any,
|
|
||||||
).then((value){
|
|
||||||
if (value != null){
|
|
||||||
var newDB = value.paths[0]!;
|
|
||||||
teto.close().then((value){
|
|
||||||
if(!newDB.endsWith("db")){
|
|
||||||
return ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.importWrongFileType)));
|
|
||||||
}
|
|
||||||
getApplicationDocumentsDirectory().then((value){
|
|
||||||
var oldDB = File("${value.path}/TetraStats.db");
|
|
||||||
oldDB.writeAsBytes(File(newDB).readAsBytesSync(), flush: true).then((value){
|
|
||||||
teto.open();
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.importSuccess)));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.importCancelled)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}else{
|
|
||||||
const XTypeGroup typeGroup = XTypeGroup(
|
|
||||||
label: 'Tetra Stats Database',
|
|
||||||
extensions: <String>['db'],
|
|
||||||
);
|
|
||||||
openFile(acceptedTypeGroups: <XTypeGroup>[typeGroup]).then((value){
|
|
||||||
if (value != null){
|
|
||||||
var newDB = value.path;
|
|
||||||
teto.close().then((value){
|
|
||||||
getApplicationDocumentsDirectory().then((value){
|
|
||||||
var oldDB = File("${value.path}/TetraStats.db");
|
|
||||||
oldDB.writeAsBytes(File(newDB).readAsBytesSync()).then((value){
|
|
||||||
teto.open();
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.importSuccess)));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.importCancelled)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: Text(t.yourID),
|
|
||||||
subtitle: Text(t.yourIDText, style: subtitleStyle),
|
|
||||||
trailing: Text(defaultNickname),
|
|
||||||
onTap: () => showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) => AlertDialog(
|
|
||||||
title: Text(t.yourIDAlertTitle,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontFamily: "Eurostile Round Extended")),
|
|
||||||
content: SingleChildScrollView(
|
|
||||||
child: ListBody(children: [
|
|
||||||
Text(t.yourIDText),
|
|
||||||
TextField(controller: _playertext, maxLength: 25)
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
actions: <Widget>[
|
|
||||||
TextButton(
|
|
||||||
child: Text(t.popupActions.cancel),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
child: Text(t.popupActions.submit),
|
|
||||||
onPressed: () async {
|
|
||||||
if (_playertext.text.isEmpty) {
|
|
||||||
_removePlayer();
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
late TetrioPlayer user;
|
|
||||||
try{
|
|
||||||
user = await teto.fetchPlayer(_playertext.text.toLowerCase().trim());
|
|
||||||
}on Exception{
|
|
||||||
if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.noSuchUser)));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_setPlayer(user.userId);
|
|
||||||
if (context.mounted) Navigator.of(context).pop();
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: Text(t.language),
|
|
||||||
subtitle: Text("By default, the system language will be selected (if available among Tetra Stats locales, otherwise English)", style: subtitleStyle),
|
|
||||||
trailing: DropdownButton(
|
|
||||||
items: locales,
|
|
||||||
value: LocaleSettings.currentLocale,
|
|
||||||
onChanged: (value){
|
|
||||||
LocaleSettings.setLocale(value!);
|
|
||||||
if(value.languageCode == Platform.localeName.substring(0, 2)){
|
|
||||||
prefs.remove('locale');
|
|
||||||
}else{
|
|
||||||
prefs.setString('locale', value.languageCode);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ListTile(title: Text(t.customization),
|
|
||||||
subtitle: Text(t.customizationDescription, style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
|
|
||||||
trailing: const Icon(Icons.arrow_right),
|
|
||||||
onTap: () {
|
|
||||||
context.go("/settings/customization");
|
|
||||||
},),
|
|
||||||
ListTile(title: Text(t.updateInBackground),
|
|
||||||
subtitle: Text(t.updateInBackgroundDescription, style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
|
|
||||||
trailing: Switch(value: updateInBG, onChanged: (bool value){
|
|
||||||
prefs.setBool("updateInBG", value);
|
|
||||||
setState(() {
|
|
||||||
updateInBG = value;
|
|
||||||
});
|
|
||||||
}),),
|
|
||||||
ListTile(title: Text(t.lbStats),
|
|
||||||
subtitle: Text(t.lbStatsDescription, style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
|
|
||||||
trailing: Switch(value: showPositions, onChanged: (bool value){
|
|
||||||
prefs.setBool("showPositions", value);
|
|
||||||
setState(() {
|
|
||||||
showPositions = value;
|
|
||||||
});
|
|
||||||
}),),
|
|
||||||
const Divider(),
|
|
||||||
ListTile(
|
|
||||||
onTap: (){
|
|
||||||
launchInBrowser(Uri.https("github.com", "dan63047/TetraStats"));
|
|
||||||
},
|
|
||||||
title: Text(t.aboutApp, style: const TextStyle(fontWeight: FontWeight.w500),),
|
|
||||||
subtitle: Text(t.aboutAppText(appName: packageInfo.appName, packageName: packageInfo.packageName, version: packageInfo.version, buildNumber: packageInfo.buildNumber)),
|
|
||||||
trailing: const Icon(Icons.arrow_right)
|
|
||||||
),
|
|
||||||
// Wrap(
|
|
||||||
// alignment: WrapAlignment.center,
|
|
||||||
// spacing: 8,
|
|
||||||
// children: [
|
|
||||||
// TextButton(child: Text("Donate to me"), onPressed: (){},),TextButton(child: Text("Donate to NOT me"), onPressed: (){},),TextButton(child: Text("Donate to someone else"), onPressed: (){},),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -40,8 +40,14 @@ class SprintAndBlitzState extends State<SprintAndBlitzView> {
|
||||||
final t = Translations.of(context);
|
final t = Translations.of(context);
|
||||||
bool bigScreen = MediaQuery.of(context).size.width >= 368;
|
bool bigScreen = MediaQuery.of(context).size.width >= 368;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
floatingActionButtonLocation: FloatingActionButtonLocation.startTop,
|
||||||
title: Text(t.sprintAndBlitsViewTitle),
|
floatingActionButton: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 0.0),
|
||||||
|
child: FloatingActionButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
tooltip: 'Fuck go back',
|
||||||
|
child: const Icon(Icons.arrow_back),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.black,
|
backgroundColor: Colors.black,
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
|
|
|
@ -49,7 +49,7 @@ class StateState extends State<StateView> {
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.black,
|
backgroundColor: Colors.black,
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: TLThingy(tl: widget.state, userID: widget.state.id, states: [])
|
child: TetraLeagueThingy(league: widget.state)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,119 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:tetra_stats/data_objects/tetra_league.dart';
|
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
|
||||||
import 'package:tetra_stats/main.dart' show teto;
|
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
|
||||||
import 'package:tetra_stats/views/mathes_view.dart';
|
|
||||||
import 'package:tetra_stats/views/state_view.dart';
|
|
||||||
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
|
||||||
import 'package:window_manager/window_manager.dart';
|
|
||||||
|
|
||||||
class StatesView extends StatefulWidget {
|
|
||||||
final String nickname;
|
|
||||||
final String id;
|
|
||||||
const StatesView({required this.nickname, required this.id, super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<StatefulWidget> createState() => StatesState();
|
|
||||||
}
|
|
||||||
|
|
||||||
late String oldWindowTitle;
|
|
||||||
|
|
||||||
class StatesState extends State<StatesView> {
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS){
|
|
||||||
windowManager.getTitle().then((value) => oldWindowTitle = value);
|
|
||||||
//windowManager.setTitle("Tetra Stats: ${t.statesViewTitle(number: widget.states.length, nickname: widget.states.last.id.toUpperCase())}");
|
|
||||||
}
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void 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(t.statesViewTitle(number: "", nickname: widget.nickname)),
|
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: (){
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => MatchesView(userID: widget.id, username: widget.nickname),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}, icon: const Icon(Icons.list), tooltip: t.viewAllMatches)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
backgroundColor: Colors.black,
|
|
||||||
body: SafeArea(
|
|
||||||
child: FutureBuilder<List<TetraLeague>>(future: teto.getStates(widget.id), builder: (context, snapshot) {
|
|
||||||
switch (snapshot.connectionState) {
|
|
||||||
case ConnectionState.none:
|
|
||||||
case ConnectionState.waiting:
|
|
||||||
case ConnectionState.active:
|
|
||||||
return const Center(child: CircularProgressIndicator(color: Colors.white));
|
|
||||||
case ConnectionState.done:
|
|
||||||
if (snapshot.hasData) {
|
|
||||||
return ListView.builder(
|
|
||||||
itemCount: snapshot.data!.length,
|
|
||||||
prototypeItem: ListTile(
|
|
||||||
title: Text(""),
|
|
||||||
subtitle: Text("", style: TextStyle(color: Colors.grey)),
|
|
||||||
trailing: IconButton(icon: const Icon(Icons.delete_forever), onPressed: (){}),
|
|
||||||
),
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
return ListTile(
|
|
||||||
title: Text(timestamp(snapshot.data![index].timestamp)),
|
|
||||||
subtitle: Text(
|
|
||||||
t.statesViewEntry(level: f2.format(snapshot.data![index].tr), games: intf.format(snapshot.data![index].gamesPlayed), glicko: snapshot.data![index].glicko != null ? f2.format(snapshot.data![index].glicko) : "---", rd: snapshot.data![index].rd != null ? f2.format(snapshot.data![index].rd) : "--"),
|
|
||||||
style: TextStyle(color: Colors.grey),
|
|
||||||
),
|
|
||||||
trailing: IconButton(
|
|
||||||
icon: const Icon(Icons.delete_forever),
|
|
||||||
onPressed: () {
|
|
||||||
teto.deleteState(snapshot.data![index].id+snapshot.data![index].timestamp.millisecondsSinceEpoch.toRadixString(16)).then((value) => setState(() {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.stateRemoved(date: timestamp(snapshot.data![index].timestamp)))));
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => StateView(state: snapshot.data![index]),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} else if (snapshot.hasError) {
|
|
||||||
return Center(child:
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(snapshot.error.toString(), style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 42, fontWeight: FontWeight.bold), textAlign: TextAlign.center),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
|
||||||
child: Text(snapshot.stackTrace.toString(), style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 18), textAlign: TextAlign.center),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return const Center(child: Text('default case of FutureBuilder', style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42), textAlign: TextAlign.center));
|
|
||||||
}
|
|
||||||
)));}
|
|
||||||
}
|
|
|
@ -1,218 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:intl/intl.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/views/main_view.dart';
|
|
||||||
import 'package:tetra_stats/views/rank_averages_view.dart';
|
|
||||||
import 'package:tetra_stats/views/ranks_averages_view.dart';
|
|
||||||
import 'package:window_manager/window_manager.dart';
|
|
||||||
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
|
||||||
|
|
||||||
List<DropdownMenuItem> _itemStats = [for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value))];
|
|
||||||
Stats _sortBy = Stats.tr;
|
|
||||||
bool reversed = false;
|
|
||||||
List<DropdownMenuItem> _itemCountries = [for (MapEntry e in t.countries.entries) DropdownMenuItem(value: e.key, child: Text(e.value))];
|
|
||||||
String _country = "";
|
|
||||||
late String _oldWindowTitle;
|
|
||||||
final NumberFormat _f4 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 4);
|
|
||||||
|
|
||||||
class TLLeaderboardView extends StatefulWidget {
|
|
||||||
const TLLeaderboardView({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<StatefulWidget> createState() => TLLeaderboardState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class TLLeaderboardState extends State<TLLeaderboardView> {
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) windowManager.getTitle().then((value) => _oldWindowTitle = value);
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) windowManager.setTitle(_oldWindowTitle);
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final t = Translations.of(context);
|
|
||||||
final NumberFormat f2 = NumberFormat.decimalPattern(LocaleSettings.currentLocale.languageCode)..maximumFractionDigits = 2;
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text(t.tlLeaderboard),
|
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const RankAveragesView(),
|
|
||||||
maintainState: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.compress),
|
|
||||||
tooltip: t.rankAveragesViewTitle,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
backgroundColor: Colors.black,
|
|
||||||
body: SafeArea(
|
|
||||||
child: FutureBuilder(
|
|
||||||
future: teto.fetchTLLeaderboard(),
|
|
||||||
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){
|
|
||||||
final allPlayers = snapshot.data?.getStatRanking(snapshot.data!.leaderboard, _sortBy, reversed: reversed, country: _country);
|
|
||||||
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) windowManager.setTitle("Tetra Stats: ${t.tlLeaderboard} - ${t.players(n: allPlayers != null ? allPlayers.length : 0)}");
|
|
||||||
bool bigScreen = MediaQuery.of(context).size.width > 768;
|
|
||||||
return NestedScrollView(
|
|
||||||
headerSliverBuilder: (context, value) {
|
|
||||||
return [
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 16),
|
|
||||||
child: Wrap(
|
|
||||||
direction: Axis.horizontal,
|
|
||||||
alignment: WrapAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"${t.players(n: allPlayers.length)} • ${t.sprintAndBlitsRelevance(date: timestamp(snapshot.data!.timestamp))}",
|
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 25),
|
|
||||||
),
|
|
||||||
TextButton(onPressed: (){
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => RankView(rank: snapshot.data!.getRankData("")),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}, child: Text(t.everyoneAverages,
|
|
||||||
style: const TextStyle(fontSize: 25)))
|
|
||||||
],)
|
|
||||||
)),
|
|
||||||
SliverToBoxAdapter(child: Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 16),
|
|
||||||
child: Wrap(
|
|
||||||
direction: Axis.horizontal,
|
|
||||||
alignment: WrapAlignment.start,
|
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
|
||||||
spacing: 16,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
|
||||||
textBaseline: TextBaseline.alphabetic,
|
|
||||||
children: [
|
|
||||||
Text("${t.sortBy}: ",
|
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 25)),
|
|
||||||
DropdownButton(items: _itemStats, value: _sortBy, onChanged: ((value) {
|
|
||||||
_sortBy = value;
|
|
||||||
setState(() {});
|
|
||||||
}),),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
|
||||||
textBaseline: TextBaseline.alphabetic,
|
|
||||||
children: [
|
|
||||||
Text("${t.reversed}: ",
|
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 25)),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(0, 5.5, 0, 7.5),
|
|
||||||
child: Checkbox(value: reversed,
|
|
||||||
checkColor: Colors.black,
|
|
||||||
onChanged: ((value) {
|
|
||||||
reversed = value!;
|
|
||||||
setState(() {});
|
|
||||||
}),),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
|
||||||
textBaseline: TextBaseline.alphabetic,
|
|
||||||
children: [
|
|
||||||
Text("${t.country}: ",
|
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 25)),
|
|
||||||
DropdownButton(items: _itemCountries, value: _country, onChanged: ((value) {
|
|
||||||
_country = value;
|
|
||||||
setState(() {});
|
|
||||||
}),),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),),
|
|
||||||
const SliverToBoxAdapter(child: Divider())
|
|
||||||
];
|
|
||||||
},
|
|
||||||
body: ListView.builder(
|
|
||||||
itemCount: allPlayers!.length,
|
|
||||||
prototypeItem: ListTile(
|
|
||||||
leading: Text("0", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 28 : 24, height: 0.9)),
|
|
||||||
title: Text("ehhh...", style: TextStyle(fontFamily: bigScreen ? "Eurostile Round Extended" : "Eurostile Round", height: 0.9)),
|
|
||||||
trailing: SizedBox(height: bigScreen ? 48 : 36, width: 1,),
|
|
||||||
subtitle: const Text("eh..."),
|
|
||||||
),
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
return ListTile(
|
|
||||||
leading: Text(
|
|
||||||
(index+1).toString(),
|
|
||||||
style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 28 : 24, height: 0.9)
|
|
||||||
),
|
|
||||||
title: Text(allPlayers[index].username, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round Extended" : "Eurostile Round", height: 0.9)),
|
|
||||||
subtitle: (bigScreen || _sortBy != Stats.tr) ? Text(_sortBy == Stats.tr ? "${f2.format(allPlayers[index].apm)} APM, ${f2.format(allPlayers[index].pps)} PPS, ${f2.format(allPlayers[index].vs)} VS, ${f2.format(allPlayers[index].nerdStats.app)} APP, ${f2.format(allPlayers[index].nerdStats.vsapm)} VS/APM" : "${_f4.format(allPlayers[index].getStatByEnum(_sortBy))} ${chartsShortTitles[_sortBy]}",
|
|
||||||
style: TextStyle(fontFamily: "Eurostile Round Condensed", fontSize: bigScreen ? null : 13, color: _sortBy == Stats.tr ? Colors.grey : null)) : null,
|
|
||||||
trailing: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text("${f2.format(allPlayers[index].tr)} TR", style: const TextStyle(fontSize: 28)),
|
|
||||||
Image.asset("res/tetrio_tl_alpha_ranks/${allPlayers[index].rank}.png", height: bigScreen ? 48 : 36),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => MainView(player: allPlayers[index].userId),
|
|
||||||
maintainState: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
if (snapshot.hasError){
|
|
||||||
return Center(child:
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(snapshot.error.toString(), style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 42, fontWeight: FontWeight.bold), textAlign: TextAlign.center),
|
|
||||||
if (snapshot.stackTrace != null) Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
|
||||||
child: Text(snapshot.stackTrace.toString(), style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 18), textAlign: TextAlign.center),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return const Text("end of FutureBuilder");
|
|
||||||
}
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,7 +6,7 @@ import 'package:tetra_stats/data_objects/beta_league_stats.dart';
|
||||||
import 'package:tetra_stats/data_objects/beta_record.dart';
|
import 'package:tetra_stats/data_objects/beta_record.dart';
|
||||||
import 'package:tetra_stats/data_objects/tetrio_multiplayer_replay.dart';
|
import 'package:tetra_stats/data_objects/tetrio_multiplayer_replay.dart';
|
||||||
import 'package:tetra_stats/utils/relative_timestamps.dart';
|
import 'package:tetra_stats/utils/relative_timestamps.dart';
|
||||||
import 'package:tetra_stats/views/compare_view.dart' show CompareThingy;
|
import 'package:tetra_stats/widgets/compare_thingy.dart';
|
||||||
import 'package:tetra_stats/widgets/list_tile_trailing_stats.dart';
|
import 'package:tetra_stats/widgets/list_tile_trailing_stats.dart';
|
||||||
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
||||||
import 'package:tetra_stats/widgets/vs_graphs.dart';
|
import 'package:tetra_stats/widgets/vs_graphs.dart';
|
||||||
|
|
|
@ -1,133 +0,0 @@
|
||||||
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' show teto;
|
|
||||||
import 'package:tetra_stats/utils/filesizes_converter.dart';
|
|
||||||
import 'package:tetra_stats/views/states_view.dart';
|
|
||||||
import 'package:window_manager/window_manager.dart';
|
|
||||||
|
|
||||||
late String oldWindowTitle;
|
|
||||||
|
|
||||||
class TrackedPlayersView extends StatefulWidget {
|
|
||||||
const TrackedPlayersView({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<StatefulWidget> createState() => TrackedPlayersState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class TrackedPlayersState extends State<TrackedPlayersView> {
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS){
|
|
||||||
windowManager.getTitle().then((value) => oldWindowTitle = value);
|
|
||||||
windowManager.setTitle("Tetra Stats: ${t.trackedPlayersViewTitle}");
|
|
||||||
}
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void 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(t.trackedPlayersViewTitle),
|
|
||||||
actions: [
|
|
||||||
PopupMenuButton(
|
|
||||||
icon: const Icon(Icons.settings_backup_restore),
|
|
||||||
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
|
||||||
PopupMenuItem(
|
|
||||||
value: 1,
|
|
||||||
child: Text(t.duplicatedFix),
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
|
||||||
value: 2,
|
|
||||||
child: Text(t.compressDB),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
onSelected: (value) {
|
|
||||||
switch (value) {
|
|
||||||
case 1:
|
|
||||||
teto.removeDuplicatesFromTLMatches();
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
teto.compressDB().then((value) => ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.SpaceSaved(size: bytesToSize(value))))));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
})
|
|
||||||
],
|
|
||||||
),
|
|
||||||
backgroundColor: Colors.black,
|
|
||||||
body: SafeArea(
|
|
||||||
child: FutureBuilder(
|
|
||||||
future: teto.getAllPlayers(),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
switch (snapshot.connectionState) {
|
|
||||||
case ConnectionState.none:
|
|
||||||
case ConnectionState.waiting:
|
|
||||||
case ConnectionState.active:
|
|
||||||
return const Center(child: CircularProgressIndicator(color: Colors.white));
|
|
||||||
case ConnectionState.done:
|
|
||||||
final allPlayers = (snapshot.data != null) ? snapshot.data as Map<String, String> : <String, String>{};
|
|
||||||
List<String> keys = allPlayers.keys.toList();
|
|
||||||
return NestedScrollView(
|
|
||||||
headerSliverBuilder: (context, value) {
|
|
||||||
String howManyPlayers(int numberOfPlayers) => Intl.plural(
|
|
||||||
numberOfPlayers,
|
|
||||||
zero: t.trackedPlayersZeroEntrys,
|
|
||||||
one: t.trackedPlayersOneEntry,
|
|
||||||
other: t.trackedPlayersManyEntrys(numberOfPlayers: numberOfPlayers),
|
|
||||||
name: 'howManyPeople',
|
|
||||||
args: [numberOfPlayers],
|
|
||||||
desc: 'Description of how many people are seen in a place.',
|
|
||||||
examples: const {'numberOfPeople': 3},
|
|
||||||
);
|
|
||||||
return [
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 16),
|
|
||||||
child: Text(
|
|
||||||
howManyPlayers(allPlayers.length),
|
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 25),
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
const SliverToBoxAdapter(child: Divider())
|
|
||||||
];
|
|
||||||
},
|
|
||||||
body: ListView.builder(
|
|
||||||
itemCount: allPlayers.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
print(index);
|
|
||||||
return ListTile(
|
|
||||||
title: Text(allPlayers[keys[index]]??"No nickname (huh?)"),
|
|
||||||
subtitle: Text(keys[index], style: TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
|
|
||||||
trailing: IconButton(
|
|
||||||
icon: const Icon(Icons.delete_forever),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {teto.deletePlayer(keys[index]);});
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.trackedPlayersStatesDeleted(nickname: allPlayers[keys[index]]??"No nickname (huh?)"))));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => StatesView(nickname: allPlayers[keys[index]]!, id: keys[index]),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,7 +5,7 @@ import 'package:intl/intl.dart';
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
import 'package:tetra_stats/main.dart';
|
import 'package:tetra_stats/main.dart';
|
||||||
import 'package:tetra_stats/views/destination_home.dart';
|
import 'package:tetra_stats/views/destination_home.dart';
|
||||||
import 'package:tetra_stats/views/main_view_tiles.dart';
|
import 'package:tetra_stats/views/main_view.dart';
|
||||||
|
|
||||||
final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms();
|
final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms();
|
||||||
|
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:tetra_stats/data_objects/record_single.dart';
|
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
|
||||||
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
|
||||||
import 'package:tetra_stats/widgets/zenith_thingy.dart';
|
|
||||||
|
|
||||||
class ZenithRecordView extends StatelessWidget {
|
|
||||||
final RecordSingle record;
|
|
||||||
|
|
||||||
const ZenithRecordView({super.key, required this.record});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final t = Translations.of(context);
|
|
||||||
//bool bigScreen = MediaQuery.of(context).size.width >= 368;
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: Colors.black,
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text("${
|
|
||||||
switch (record.gamemode){
|
|
||||||
"zenith" => t.quickPlay,
|
|
||||||
"zenithex" => "${t.quickPlay} ${t.expert}",
|
|
||||||
String() => "5000000 Blast",
|
|
||||||
}
|
|
||||||
} ${timestamp(record.timestamp)}"),
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: SizedBox(
|
|
||||||
width: MediaQuery.of(context).size.width,
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: ZenithThingy(record: record, switchable: false),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/tetra_league_alpha_record.dart';
|
||||||
|
import 'package:tetra_stats/utils/text_shadow.dart';
|
||||||
|
import 'package:tetra_stats/widgets/list_tile_trailing_stats.dart';
|
||||||
|
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
||||||
|
|
||||||
|
class AlphaLeagueEntryThingy extends StatelessWidget{
|
||||||
|
final TetraLeagueAlphaRecord record;
|
||||||
|
final String userID;
|
||||||
|
|
||||||
|
const AlphaLeagueEntryThingy(this.record, this.userID);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var accentColor = record.endContext.firstWhere((element) => element.userId == userID).success ? Colors.green : Colors.red;
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
stops: const [0, 0.05],
|
||||||
|
colors: [accentColor, Colors.transparent]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
child: ListTile(
|
||||||
|
leading: Text("${record.endContext.firstWhere((element) => element.userId == userID).points} : ${record.endContext.firstWhere((element) => element.userId != userID).points}",
|
||||||
|
style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28, shadows: textShadow)),
|
||||||
|
title: Text("vs. ${record.endContext.firstWhere((element) => element.userId != userID).username}"),
|
||||||
|
subtitle: Text(timestamp(record.timestamp), style: const TextStyle(color: Colors.grey)),
|
||||||
|
trailing: TrailingStats(
|
||||||
|
record.endContext.firstWhere((element) => element.userId == userID).secondary,
|
||||||
|
record.endContext.firstWhere((element) => element.userId == userID).tertiary,
|
||||||
|
record.endContext.firstWhere((element) => element.userId == userID).extra,
|
||||||
|
record.endContext.firstWhere((element) => element.userId != userID).secondary,
|
||||||
|
record.endContext.firstWhere((element) => element.userId != userID).tertiary,
|
||||||
|
record.endContext.firstWhere((element) => element.userId != userID).extra
|
||||||
|
),
|
||||||
|
//onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => TlMatchResultView(record: record, initPlayerId: userID))),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart' hide Badge;
|
||||||
|
import 'package:tetra_stats/data_objects/badge.dart';
|
||||||
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
|
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
||||||
|
|
||||||
|
class BadgesThingy extends StatelessWidget{
|
||||||
|
final List<Badge> badges;
|
||||||
|
|
||||||
|
const BadgesThingy({super.key, required this.badges});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(20.0, 0.0, 20.0, 0.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Text("Badges", style: TextStyle(fontFamily: "Eurostile Round Extended")),
|
||||||
|
const Spacer(),
|
||||||
|
Text(intf.format(badges.length))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
for (var badge in badges)
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(badge.label, style: const TextStyle(fontFamily: "Eurostile Round Extended")),
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: ListBody(
|
||||||
|
children: [
|
||||||
|
Wrap(
|
||||||
|
direction: Axis.horizontal,
|
||||||
|
alignment: WrapAlignment.center,
|
||||||
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
|
spacing: 25,
|
||||||
|
children: [
|
||||||
|
Image.asset("res/tetrio_badges/${badge.badgeId}.png"),
|
||||||
|
Text(badge.ts != null
|
||||||
|
? t.obtainDate(date: timestamp(badge.ts!))
|
||||||
|
: t.assignedManualy),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
child: Text(t.popupActions.ok),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
tooltip: badge.label,
|
||||||
|
icon: Image.asset(
|
||||||
|
"res/tetrio_badges/${badge.badgeId}.png",
|
||||||
|
height: 32,
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
return Image.network(
|
||||||
|
kIsWeb ? "https://ts.dan63.by/oskware_bridge.php?endpoint=TetrioBadge&badge=${badge.badgeId}" : "https://tetr.io/res/badges/${badge.badgeId}.png",
|
||||||
|
height: 32,
|
||||||
|
errorBuilder:(context, error, stackTrace) {
|
||||||
|
return Image.asset("res/icons/kagari.png", height: 32, width: 32);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/beta_record.dart';
|
||||||
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
|
import 'package:tetra_stats/views/tl_match_view.dart';
|
||||||
|
import 'package:tetra_stats/widgets/list_tile_trailing_stats.dart';
|
||||||
|
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
||||||
|
|
||||||
|
class BetaLeagueEntryThingy extends StatelessWidget{
|
||||||
|
final BetaRecord record;
|
||||||
|
final String userID;
|
||||||
|
|
||||||
|
const BetaLeagueEntryThingy(this.record, this.userID);
|
||||||
|
|
||||||
|
TextSpan matchResult(String result){
|
||||||
|
return switch(result){
|
||||||
|
"victory" => TextSpan(
|
||||||
|
text: "Victory",
|
||||||
|
style: TextStyle(color: Colors.greenAccent)
|
||||||
|
),
|
||||||
|
"defeat" => TextSpan(
|
||||||
|
text: "Defeat",
|
||||||
|
style: TextStyle(color: Colors.redAccent)
|
||||||
|
),
|
||||||
|
"tie" => TextSpan(
|
||||||
|
text: "Tie",
|
||||||
|
style: TextStyle(color: Colors.white)
|
||||||
|
),
|
||||||
|
"dqvictory" => TextSpan(
|
||||||
|
text: "Opponent was DQ'ed",
|
||||||
|
style: TextStyle(color: Colors.lightGreenAccent)
|
||||||
|
),
|
||||||
|
"dqdefeat" => TextSpan(
|
||||||
|
text: "Player was DQ'ed",
|
||||||
|
style: TextStyle(color: Colors.red)
|
||||||
|
),
|
||||||
|
"nocontest" => TextSpan(
|
||||||
|
text: "No Contest",
|
||||||
|
style: TextStyle(color: Colors.blueAccent)
|
||||||
|
),
|
||||||
|
"nullified" => TextSpan(
|
||||||
|
text: "Nullified",
|
||||||
|
style: TextStyle(color: Colors.purpleAccent)
|
||||||
|
),
|
||||||
|
_ => TextSpan(
|
||||||
|
text: "${result.toUpperCase()}",
|
||||||
|
style: TextStyle(color: Colors.orangeAccent)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Color deltaColor(double? delta){
|
||||||
|
if (delta == null || delta.isNaN) return Colors.grey;
|
||||||
|
if (delta.isNegative) return Colors.redAccent;
|
||||||
|
else return Colors.greenAccent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
double? deltaTR = (record.extras.league[userID]?[1]?.tr != null && record.extras.league[userID]?[0]?.tr != null) ? record.extras.league[userID]![1]!.tr - record.extras.league[userID]![0]!.tr : null;
|
||||||
|
double? deltaGlicko = (record.extras.league[userID]?[1]?.glicko != null && record.extras.league[userID]?[0]?.glicko != null) ? record.extras.league[userID]![1]!.glicko - record.extras.league[userID]![0]!.glicko : null;
|
||||||
|
double? deltaRD = (record.extras.league[userID]?[1]?.rd != null && record.extras.league[userID]?[0]?.rd != null) ? record.extras.league[userID]![1]!.rd - record.extras.league[userID]![0]!.rd : null;
|
||||||
|
return Card(
|
||||||
|
child: ListTile(
|
||||||
|
title: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"${record.results.leaderboard.firstWhere((element) => element.id != record.enemyID).wins} - ${record.results.leaderboard.firstWhere((element) => element.id == record.enemyID).wins} ",
|
||||||
|
style: TextStyle(fontSize: 26, height: 0.75, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"vs.\n${record.enemyUsername}",
|
||||||
|
style: TextStyle(fontSize: 14, height: 0.8, fontWeight: FontWeight.w100),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
subtitle: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 4.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
style: TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey),
|
||||||
|
children: [
|
||||||
|
matchResult(record.extras.result),
|
||||||
|
TextSpan(
|
||||||
|
text: ", ${timestamp(record.ts)}\n"
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: deltaTR != null ? "${fDiff.format(deltaTR)} TR" : "??? TR",
|
||||||
|
style: TextStyle(
|
||||||
|
color: deltaColor(deltaTR)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: ", "
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: deltaGlicko != null ? "${fDiff.format(deltaGlicko)} Glicko" : "??? Glicko",
|
||||||
|
style: TextStyle(
|
||||||
|
color: deltaColor(deltaGlicko)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: ", "
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: deltaRD != null ? "${fDiff.format(deltaRD)} RD" : "??? RD",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: TrailingStats(
|
||||||
|
record.results.leaderboard.firstWhere((element) => element.id != record.enemyID).stats.apm,
|
||||||
|
record.results.leaderboard.firstWhere((element) => element.id != record.enemyID).stats.pps,
|
||||||
|
record.results.leaderboard.firstWhere((element) => element.id != record.enemyID).stats.vs,
|
||||||
|
record.results.leaderboard.firstWhere((element) => element.id == record.enemyID).stats.apm,
|
||||||
|
record.results.leaderboard.firstWhere((element) => element.id == record.enemyID).stats.pps,
|
||||||
|
record.results.leaderboard.firstWhere((element) => element.id == record.enemyID).stats.vs,
|
||||||
|
),
|
||||||
|
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => TlMatchResultView(record: record, initPlayerId: userID))) //Navigator.push(context, MaterialPageRoute(builder: (context) => TlMatchResultView(record: data[index], initPlayerId: userID))),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
|
|
||||||
|
const TextStyle verdictStyle = TextStyle(fontSize: 14, fontFamily: "Eurostile Round Condensed", color: Colors.grey, height: 1.1);
|
||||||
|
|
||||||
|
class CompareThingy extends StatelessWidget {
|
||||||
|
final num greenSide;
|
||||||
|
final num redSide;
|
||||||
|
final String label;
|
||||||
|
final bool higherIsBetter;
|
||||||
|
final int? fractionDigits;
|
||||||
|
final String? postfix;
|
||||||
|
final String? prefix;
|
||||||
|
const CompareThingy(
|
||||||
|
{super.key,
|
||||||
|
required this.greenSide,
|
||||||
|
required this.redSide,
|
||||||
|
required this.label,
|
||||||
|
required this.higherIsBetter,
|
||||||
|
this.fractionDigits,
|
||||||
|
this.prefix,
|
||||||
|
this.postfix});
|
||||||
|
|
||||||
|
String verdict(num greenSide, num redSide, int fraction) {
|
||||||
|
var f = NumberFormat("+#,###.##;-#,###.##");
|
||||||
|
f.maximumFractionDigits = fraction;
|
||||||
|
return f.format((greenSide - redSide)) + (postfix ?? "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var f = NumberFormat.decimalPattern(LocaleSettings.currentLocale.languageCode);
|
||||||
|
f.maximumFractionDigits = fractionDigits ?? 0;
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 2, 16, 2),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: const [Colors.green, Colors.transparent],
|
||||||
|
begin: Alignment.centerLeft,
|
||||||
|
end: Alignment.centerRight,
|
||||||
|
transform: const GradientRotation(0.6),
|
||||||
|
stops: [
|
||||||
|
0.0,
|
||||||
|
higherIsBetter
|
||||||
|
? greenSide > redSide
|
||||||
|
? 0.6
|
||||||
|
: 0
|
||||||
|
: greenSide < redSide
|
||||||
|
? 0.6
|
||||||
|
: 0
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
(prefix ?? "") + f.format(greenSide) + (postfix ?? ""),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
shadows: <Shadow>[
|
||||||
|
Shadow(
|
||||||
|
offset: Offset(0.0, 0.0),
|
||||||
|
blurRadius: 1.0,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
Shadow(
|
||||||
|
offset: Offset(0.0, 0.0),
|
||||||
|
blurRadius: 2.0,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
Shadow(
|
||||||
|
offset: Offset(0.0, 0.0),
|
||||||
|
blurRadius: 8.0,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(fontSize: 22),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
verdict(greenSide, redSide,
|
||||||
|
fractionDigits != null ? fractionDigits! + 2 : 0),
|
||||||
|
style: verdictStyle,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: const [Colors.red, Colors.transparent],
|
||||||
|
begin: Alignment.centerRight,
|
||||||
|
end: Alignment.centerLeft,
|
||||||
|
transform: const GradientRotation(-0.6),
|
||||||
|
stops: [
|
||||||
|
0.0,
|
||||||
|
higherIsBetter
|
||||||
|
? redSide > greenSide
|
||||||
|
? 0.6
|
||||||
|
: 0
|
||||||
|
: redSide < greenSide
|
||||||
|
? 0.6
|
||||||
|
: 0
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
child: Text(
|
||||||
|
(prefix ?? "") + f.format(redSide) + (postfix ?? ""),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
shadows: <Shadow>[
|
||||||
|
Shadow(
|
||||||
|
offset: Offset(0.0, 0.0),
|
||||||
|
blurRadius: 3.0,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
Shadow(
|
||||||
|
offset: Offset(0.0, 0.0),
|
||||||
|
blurRadius: 8.0,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.end,
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/distinguishment.dart';
|
||||||
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
|
import 'package:tetra_stats/main.dart';
|
||||||
|
|
||||||
|
class DistinguishmentThingy extends StatelessWidget{
|
||||||
|
final Distinguishment distinguishment;
|
||||||
|
|
||||||
|
const DistinguishmentThingy(this.distinguishment, {super.key});
|
||||||
|
|
||||||
|
List<InlineSpan> getDistinguishmentTitle(String? text) {
|
||||||
|
// TWC champions don't have header in their distinguishments
|
||||||
|
if (distinguishment.type == "twc") return [const TextSpan(text: "TETR.IO World Champion", style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Colors.yellowAccent))];
|
||||||
|
// In case if it missing for some other reason, return this
|
||||||
|
if (text == null) return [const TextSpan(text: "Header is missing", style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Colors.redAccent))];
|
||||||
|
|
||||||
|
// Handling placeholders for logos
|
||||||
|
var exploded = text.split(" "); // wtf PHP reference?
|
||||||
|
List<InlineSpan> result = [];
|
||||||
|
for (String shit in exploded){
|
||||||
|
switch (shit) { // if %% thingy was found, insert svg of icon
|
||||||
|
case "%osk%":
|
||||||
|
result.add(WidgetSpan(child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 8),
|
||||||
|
child: SvgPicture.asset("res/icons/osk.svg", height: 28),
|
||||||
|
)));
|
||||||
|
break;
|
||||||
|
case "%tetrio%":
|
||||||
|
result.add(WidgetSpan(child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 8),
|
||||||
|
child: SvgPicture.asset("res/icons/tetrio-logo.svg", height: 28),
|
||||||
|
)));
|
||||||
|
break;
|
||||||
|
default: // if not, insert text span
|
||||||
|
result.add(TextSpan(text: " $shit", style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Colors.white)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Distinguishment title is barely predictable thing.
|
||||||
|
/// Receives [text], which is footer and returns sets of widgets for RichText widget
|
||||||
|
String getDistinguishmentSubtitle(String? text){
|
||||||
|
// TWC champions don't have footer in their distinguishments
|
||||||
|
if (distinguishment.type == "twc") return "${distinguishment.detail} TETR.IO World Championship";
|
||||||
|
// In case if it missing for some other reason, return this
|
||||||
|
if (text == null) return "Footer is missing";
|
||||||
|
// If everything ok, return as it is
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getCardTint(String type, String detail){
|
||||||
|
switch(type){
|
||||||
|
case "staff":
|
||||||
|
switch(detail){
|
||||||
|
case "founder": return const Color(0xAAFD82D4);
|
||||||
|
case "kagarin": return const Color(0xAAFF0060);
|
||||||
|
case "team": return const Color(0xAAFACC2E);
|
||||||
|
case "team-minor": return const Color(0xAAF5BD45);
|
||||||
|
case "administrator": return const Color(0xAAFF4E8A);
|
||||||
|
case "globalmod": return const Color(0xAAE878FF);
|
||||||
|
case "communitymod": return const Color(0xAA4E68FB);
|
||||||
|
case "alumni": return const Color(0xAA6057DB);
|
||||||
|
default: return theme.colorScheme.surface;
|
||||||
|
}
|
||||||
|
case "champion":
|
||||||
|
switch (detail){
|
||||||
|
case "blitz":
|
||||||
|
case "40l": return const Color(0xAACCF5F6);
|
||||||
|
case "league": return const Color(0xAAFFDB31);
|
||||||
|
}
|
||||||
|
case "twc": return const Color(0xAAFFDB31);
|
||||||
|
default: return theme.colorScheme.surface;
|
||||||
|
}
|
||||||
|
return theme.colorScheme.surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
surfaceTintColor: getCardTint(distinguishment.type, distinguishment.detail??"null"),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Spacer(),
|
||||||
|
Text(t.distinguishment, style: const TextStyle(fontFamily: "Eurostile Round Extended")),
|
||||||
|
const Spacer()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
RichText(
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
text: TextSpan(
|
||||||
|
style: DefaultTextStyle.of(context).style,
|
||||||
|
children: getDistinguishmentTitle(distinguishment.header),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(8.0, 4.0, 8.0, 4.0),
|
||||||
|
child: Text(getDistinguishmentSubtitle(distinguishment.footer), style: Theme.of(context).textTheme.displayLarge, textAlign: TextAlign.center),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
|
import 'package:tetra_stats/services/crud_exceptions.dart';
|
||||||
|
import 'package:tetra_stats/views/destination_home.dart';
|
||||||
|
|
||||||
|
class ErrorThingy extends StatelessWidget{
|
||||||
|
final FetchResults? data;
|
||||||
|
final String? eText;
|
||||||
|
|
||||||
|
const ErrorThingy({this.data, this.eText});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
IconData icon = Icons.error_outline;
|
||||||
|
String errText = eText??"";
|
||||||
|
String? subText;
|
||||||
|
if (data?.exception != null) switch (data!.exception!.runtimeType){
|
||||||
|
case TetrioPlayerNotExist:
|
||||||
|
icon = Icons.search_off;
|
||||||
|
errText = t.errors.noSuchUser;
|
||||||
|
subText = t.errors.noSuchUserSub;
|
||||||
|
break;
|
||||||
|
case TetrioDiscordNotExist:
|
||||||
|
icon = Icons.search_off;
|
||||||
|
errText = t.errors.discordNotAssigned;
|
||||||
|
subText = t.errors.discordNotAssignedSub;
|
||||||
|
case ConnectionIssue:
|
||||||
|
var err = data!.exception as ConnectionIssue;
|
||||||
|
errText = t.errors.connection(code: err.code, message: err.message);
|
||||||
|
break;
|
||||||
|
case TetrioForbidden:
|
||||||
|
icon = Icons.remove_circle;
|
||||||
|
errText = t.errors.forbidden;
|
||||||
|
subText = t.errors.forbiddenSub(nickname: 'osk');
|
||||||
|
break;
|
||||||
|
case TetrioTooManyRequests:
|
||||||
|
errText = t.errors.tooManyRequests;
|
||||||
|
subText = t.errors.tooManyRequestsSub;
|
||||||
|
break;
|
||||||
|
case TetrioOskwareBridgeProblem:
|
||||||
|
errText = t.errors.oskwareBridge;
|
||||||
|
subText = t.errors.oskwareBridgeSub;
|
||||||
|
break;
|
||||||
|
case TetrioInternalProblem:
|
||||||
|
errText = kIsWeb ? t.errors.internalWebVersion : t.errors.internal;
|
||||||
|
subText = kIsWeb ? t.errors.internalWebVersionSub : t.errors.internalSub;
|
||||||
|
break;
|
||||||
|
case ClientException:
|
||||||
|
errText = t.errors.clientException;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
errText = data!.exception.toString();
|
||||||
|
}
|
||||||
|
return TweenAnimationBuilder(
|
||||||
|
duration: Durations.medium3,
|
||||||
|
tween: Tween<double>(begin: 0, end: 1),
|
||||||
|
curve: Easing.standard,
|
||||||
|
builder: (context, value, child) {
|
||||||
|
return Container(
|
||||||
|
transform: Matrix4.translationValues(0, 50-value*50, 0),
|
||||||
|
child: Opacity(opacity: value, child: child),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Spacer(),
|
||||||
|
Icon(icon, size: 128.0, color: Colors.red, shadows: [
|
||||||
|
Shadow(offset: Offset(0.0, 0.0), blurRadius: 30.0, color: Colors.red),
|
||||||
|
Shadow(offset: Offset(0.0, 0.0), blurRadius: 80.0, color: Colors.red),
|
||||||
|
]),
|
||||||
|
Text(errText, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 42, fontWeight: FontWeight.bold), textAlign: TextAlign.center),
|
||||||
|
if (subText != null) Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
|
child: Text(subText, textAlign: TextAlign.center),
|
||||||
|
),
|
||||||
|
Spacer()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:tetra_stats/main.dart';
|
||||||
|
|
||||||
|
class FakeDistinguishmentThingy extends StatelessWidget{
|
||||||
|
final bool banned;
|
||||||
|
final bool badStanding;
|
||||||
|
final bool bot;
|
||||||
|
final String? botMaintainers;
|
||||||
|
|
||||||
|
FakeDistinguishmentThingy({super.key, this.banned = false, this.badStanding = false, this.bot = false, this.botMaintainers});
|
||||||
|
|
||||||
|
Color getCardTint(){
|
||||||
|
if (banned) return Colors.red;
|
||||||
|
if (badStanding) return Colors.redAccent;
|
||||||
|
if (bot) return const Color.fromARGB(255, 60, 93, 55);
|
||||||
|
return theme.colorScheme.surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
InlineSpan getDistinguishmentTitle() {
|
||||||
|
String text = "";
|
||||||
|
if (banned) text = "banned";
|
||||||
|
if (badStanding) text = "bad standing";
|
||||||
|
if (bot) text = "bot account";
|
||||||
|
return TextSpan(text: text.toUpperCase(), style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Colors.white));
|
||||||
|
}
|
||||||
|
|
||||||
|
String getDistinguishmentSubtitle(){
|
||||||
|
if (banned) return "Bans are placed when TETR.IO rules or terms of service are broken";
|
||||||
|
if (badStanding) return "One or more recent bans on record";
|
||||||
|
if (bot) return "Operated by $botMaintainers";
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
surfaceTintColor: getCardTint(),
|
||||||
|
child: Container(
|
||||||
|
decoration: banned ? const BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [Colors.transparent, Color.fromARGB(171, 244, 67, 54), Color.fromARGB(171, 244, 67, 54)],
|
||||||
|
stops: [0.1, 0.9, 0.01],
|
||||||
|
tileMode: TileMode.mirror,
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: AlignmentDirectional(-0.95, -0.95)
|
||||||
|
)
|
||||||
|
) : null,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: RichText(
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
text: TextSpan(
|
||||||
|
style: DefaultTextStyle.of(context).style,
|
||||||
|
children: [getDistinguishmentTitle()],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(8.0, 4.0, 8.0, 4.0),
|
||||||
|
child: Text(getDistinguishmentSubtitle(), style: Theme.of(context).textTheme.displayLarge, textAlign: TextAlign.center),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class FutureError extends StatelessWidget{
|
||||||
|
final AsyncSnapshot snapshot;
|
||||||
|
|
||||||
|
FutureError(this.snapshot);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return TweenAnimationBuilder(
|
||||||
|
duration: Durations.medium3,
|
||||||
|
tween: Tween<double>(begin: 0, end: 1),
|
||||||
|
curve: Easing.standard,
|
||||||
|
builder: (context, value, child) {
|
||||||
|
return Container(
|
||||||
|
transform: Matrix4.translationValues(0, 50-value*50, 0),
|
||||||
|
child: Opacity(opacity: value, child: child),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Spacer(),
|
||||||
|
Icon(Icons.error_outline, size: 128.0, color: Colors.red, shadows: [
|
||||||
|
Shadow(offset: Offset(0.0, 0.0), blurRadius: 30.0, color: Colors.red),
|
||||||
|
Shadow(offset: Offset(0.0, 0.0), blurRadius: 80.0, color: Colors.red),
|
||||||
|
]),
|
||||||
|
Text(snapshot.error.toString(), style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 42, fontWeight: FontWeight.bold), textAlign: TextAlign.center),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
|
child: Text(snapshot.stackTrace.toString(), textAlign: TextAlign.left, style: TextStyle(fontFamily: "Monospace")),
|
||||||
|
),
|
||||||
|
Spacer()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,111 +0,0 @@
|
||||||
// ignore_for_file: curly_braces_in_flow_control_structures
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
|
||||||
import 'package:tetra_stats/data_objects/leaderboard_position.dart';
|
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
|
||||||
import 'package:tetra_stats/utils/colors_functions.dart';
|
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
|
||||||
|
|
||||||
class GaugetNum extends StatelessWidget {
|
|
||||||
final num playerStat;
|
|
||||||
final num? oldPlayerStat;
|
|
||||||
final bool higherIsBetter;
|
|
||||||
final List<GaugeRange> ranges;
|
|
||||||
final double minimum;
|
|
||||||
final double maximum;
|
|
||||||
final String playerStatLabel;
|
|
||||||
final String? okText;
|
|
||||||
final String? alertTitle;
|
|
||||||
final List<Widget>? alertWidgets;
|
|
||||||
final LeaderboardPosition? pos;
|
|
||||||
final num? averageStat;
|
|
||||||
|
|
||||||
const GaugetNum(
|
|
||||||
{super.key,
|
|
||||||
required this.playerStat,
|
|
||||||
required this.playerStatLabel,
|
|
||||||
this.alertWidgets,
|
|
||||||
this.oldPlayerStat,
|
|
||||||
required this.higherIsBetter,
|
|
||||||
required this.minimum,
|
|
||||||
required this.maximum,
|
|
||||||
required this.ranges,
|
|
||||||
this.okText, this.alertTitle, this.pos, this.averageStat});
|
|
||||||
|
|
||||||
Color getStatColor(){
|
|
||||||
if (averageStat == null) return Colors.white;
|
|
||||||
num percentile = (higherIsBetter ? playerStat / averageStat! : averageStat! / playerStat).abs();
|
|
||||||
if (percentile > 1.50) return Colors.purpleAccent;
|
|
||||||
else if (percentile > 1.20) return Colors.blueAccent;
|
|
||||||
else if (percentile > 0.90) return Colors.greenAccent;
|
|
||||||
else if (percentile > 0.70) return Colors.yellowAccent;
|
|
||||||
else return Colors.redAccent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SizedBox(
|
|
||||||
width: 200,
|
|
||||||
height: 120,
|
|
||||||
child: SfRadialGauge(
|
|
||||||
title: GaugeTitle(text: playerStatLabel),
|
|
||||||
axes: [RadialAxis(
|
|
||||||
startAngle: 180,
|
|
||||||
endAngle: 360,
|
|
||||||
showLabels: false,
|
|
||||||
showTicks: false,
|
|
||||||
radiusFactor: 2.1,
|
|
||||||
centerY: 0.5,
|
|
||||||
minimum: minimum,
|
|
||||||
maximum: maximum,
|
|
||||||
ranges: ranges,
|
|
||||||
pointers: [
|
|
||||||
NeedlePointer(
|
|
||||||
value: playerStat as double,
|
|
||||||
enableAnimation: true,
|
|
||||||
needleLength: 0.9,
|
|
||||||
needleStartWidth: 2,
|
|
||||||
needleEndWidth: 15,
|
|
||||||
knobStyle: const KnobStyle(color: Colors.transparent),
|
|
||||||
gradient: const LinearGradient(colors: [Colors.transparent, Colors.white], begin: Alignment.bottomCenter, end: Alignment.topCenter, stops: [0.5, 1]),)
|
|
||||||
],
|
|
||||||
annotations: [GaugeAnnotation(
|
|
||||||
widget: TextButton(child: Text(f3.format(playerStat),
|
|
||||||
style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, color: getStatColor())),
|
|
||||||
onPressed: (){
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) => AlertDialog(
|
|
||||||
title: Text(alertTitle??playerStatLabel, style: const TextStyle(fontFamily: "Eurostile Round Extended")),
|
|
||||||
content: SingleChildScrollView(child: ListBody(children: alertWidgets!)),
|
|
||||||
actions: <Widget>[
|
|
||||||
TextButton(
|
|
||||||
child: Text(okText??t.popupActions.ok),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
));
|
|
||||||
},), verticalAlignment: GaugeAlignment.far, positionFactor: 0.05),
|
|
||||||
if (oldPlayerStat != null || pos != null) GaugeAnnotation(
|
|
||||||
widget: RichText(text: TextSpan(
|
|
||||||
text: "",
|
|
||||||
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey),
|
|
||||||
children: [
|
|
||||||
if (oldPlayerStat != null) TextSpan(text: comparef.format(playerStat - oldPlayerStat!), style: TextStyle(
|
|
||||||
color: higherIsBetter ?
|
|
||||||
oldPlayerStat! > playerStat ? Colors.redAccent : Colors.greenAccent :
|
|
||||||
oldPlayerStat! < playerStat ? Colors.redAccent : Colors.greenAccent
|
|
||||||
),),
|
|
||||||
if (oldPlayerStat != null && pos != null) const TextSpan(text: " • "),
|
|
||||||
if (pos != null) TextSpan(text: pos!.position >= 1000 ? "${t.top} ${f2.format(pos!.percentage*100)}%" : "№${pos!.position}", style: TextStyle(color: getColorOfRank(pos!.position)))
|
|
||||||
]
|
|
||||||
),
|
|
||||||
),
|
|
||||||
positionFactor: 0.05)],
|
|
||||||
)],),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/leaderboard_position.dart';
|
||||||
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
|
import 'package:tetra_stats/main.dart';
|
||||||
|
import 'package:tetra_stats/utils/colors_functions.dart';
|
||||||
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
|
|
||||||
|
class GaugetThingy extends StatelessWidget{
|
||||||
|
final double? value;
|
||||||
|
final String? subString;
|
||||||
|
final double min;
|
||||||
|
final double max;
|
||||||
|
final double? oldValue;
|
||||||
|
final double? avgValue;
|
||||||
|
final bool moreIsBetter;
|
||||||
|
final double tickInterval;
|
||||||
|
final String label;
|
||||||
|
final double sideSize;
|
||||||
|
final bool percentileFormat;
|
||||||
|
final int fractionDigits;
|
||||||
|
final LeaderboardPosition? lbPos;
|
||||||
|
|
||||||
|
const GaugetThingy({super.key, required this.value, this.subString, required this.min, required this.max, this.oldValue, this.avgValue, required this.tickInterval, required this.label, required this.sideSize, required this.fractionDigits, required this.moreIsBetter, this.percentileFormat = false, this.lbPos});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
NumberFormat f = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: fractionDigits);
|
||||||
|
return ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(1000),
|
||||||
|
child: SizedBox(
|
||||||
|
height: sideSize,
|
||||||
|
width: sideSize,
|
||||||
|
child: SfRadialGauge(
|
||||||
|
backgroundColor: Colors.black,
|
||||||
|
axes: [
|
||||||
|
RadialAxis(
|
||||||
|
radiusFactor: 1.01,
|
||||||
|
minimum: min,
|
||||||
|
maximum: max,
|
||||||
|
showTicks: true,
|
||||||
|
showLabels: false,
|
||||||
|
interval: tickInterval,
|
||||||
|
minorTicksPerInterval: 0,
|
||||||
|
ranges:[
|
||||||
|
GaugeRange(startValue: 0, endValue: (value != null && !value!.isNaN) ? value! : 0, color: theme.colorScheme.primary)
|
||||||
|
],
|
||||||
|
annotations: [
|
||||||
|
GaugeAnnotation(widget: Container(child:
|
||||||
|
Text((value != null && !value!.isNaN) ? percentileFormat ? percentage.format(value) : f.format(value) : "---", textAlign: TextAlign.center, style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold, color: (value != null && !value!.isNaN) ? getStatColor(value!, avgValue, moreIsBetter) : Colors.grey))),
|
||||||
|
angle: 90,positionFactor: 0.10
|
||||||
|
),
|
||||||
|
GaugeAnnotation(widget: Container(child:
|
||||||
|
Text(label, textAlign: TextAlign.center, style: TextStyle(height: .9, color: (value != null && !value!.isNaN) ? null : Colors.grey))),
|
||||||
|
angle: 270,positionFactor: 0.3, verticalAlignment: GaugeAlignment.far,
|
||||||
|
),
|
||||||
|
if (oldValue != null && (value != null && !value!.isNaN)) GaugeAnnotation(widget: Container(child:
|
||||||
|
Text(comparef2.format(percentileFormat ? (value!-oldValue!) * 100 : value!-oldValue!), textAlign: TextAlign.center, style: TextStyle(color: getDifferenceColor(moreIsBetter ? value!-oldValue! : oldValue!-value!)))),
|
||||||
|
angle: 90,positionFactor: lbPos != null ? 0.7 : 0.45
|
||||||
|
),
|
||||||
|
if (subString != null) GaugeAnnotation(widget: Container(child:
|
||||||
|
Text(subString!, textAlign: TextAlign.center, style: TextStyle(color: (value != null && !value!.isNaN) ? null : Colors.grey))),
|
||||||
|
angle: 90,positionFactor: lbPos != null ? 0.7 : 0.45
|
||||||
|
),
|
||||||
|
if (lbPos != null) GaugeAnnotation(widget: Container(child:
|
||||||
|
Text(lbPos!.position >= 1000 ? "${t.top} ${f2.format(lbPos!.percentage*100)}%" : "№ ${lbPos!.position}", textAlign: TextAlign.center, style: TextStyle(color: (lbPos != null) ? getColorOfRank(lbPos!.position) : Colors.grey))),
|
||||||
|
angle: 90,positionFactor: 0.45
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -287,205 +287,212 @@ class Graphs extends StatelessWidget{
|
||||||
double speed = pps / 3.75;
|
double speed = pps / 3.75;
|
||||||
double defense = nerdStats.dss * 1.15;
|
double defense = nerdStats.dss * 1.15;
|
||||||
double cheese = nerdStats.cheese / 110;
|
double cheese = nerdStats.cheese / 110;
|
||||||
return Wrap(
|
return Card(
|
||||||
direction: Axis.horizontal,
|
child: Padding(
|
||||||
alignment: WrapAlignment.center,
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
spacing: 25,
|
child: Center(
|
||||||
crossAxisAlignment: WrapCrossAlignment.start,
|
child: Wrap(
|
||||||
clipBehavior: Clip.hardEdge,
|
direction: Axis.horizontal,
|
||||||
children: [
|
alignment: WrapAlignment.center,
|
||||||
if (true) Padding( // vs graph
|
spacing: 25,
|
||||||
padding: const EdgeInsets.fromLTRB(18, 0, 18, 44),
|
crossAxisAlignment: WrapCrossAlignment.start,
|
||||||
child: SizedBox(
|
clipBehavior: Clip.hardEdge,
|
||||||
height: 310,
|
children: [
|
||||||
width: 310,
|
if (true) Padding( // vs graph
|
||||||
child: MyRadarChart(
|
padding: const EdgeInsets.fromLTRB(18, 0, 18, 22),
|
||||||
RadarChartData(
|
child: SizedBox(
|
||||||
radarShape: RadarShape.circle,
|
height: 310,
|
||||||
tickCount: 4,
|
width: 310,
|
||||||
radarBackgroundColor: Colors.black.withAlpha(170),
|
child: MyRadarChart(
|
||||||
radarBorderData: const BorderSide(color: Colors.white24, width: 1),
|
RadarChartData(
|
||||||
gridBorderData: const BorderSide(color: Colors.white24, width: 1),
|
radarShape: RadarShape.circle,
|
||||||
tickBorderData: const BorderSide(color: Colors.white24, width: 1),
|
tickCount: 4,
|
||||||
getTitle: (index, angle) {
|
radarBackgroundColor: Colors.black.withAlpha(170),
|
||||||
switch (index) {
|
radarBorderData: const BorderSide(color: Colors.white24, width: 1),
|
||||||
case 0:
|
gridBorderData: const BorderSide(color: Colors.white24, width: 1),
|
||||||
return RadarChartTitle(text: 'APM', angle: angle, positionPercentageOffset: 0.05);
|
tickBorderData: const BorderSide(color: Colors.white24, width: 1),
|
||||||
case 1:
|
getTitle: (index, angle) {
|
||||||
return RadarChartTitle(text: 'PPS', angle: angle, positionPercentageOffset: 0.05);
|
switch (index) {
|
||||||
case 2:
|
case 0:
|
||||||
return RadarChartTitle(text: 'VS', angle: angle, positionPercentageOffset: 0.05);
|
return RadarChartTitle(text: 'APM', angle: angle, positionPercentageOffset: 0.05);
|
||||||
case 3:
|
case 1:
|
||||||
return RadarChartTitle(text: 'APP', angle: angle + 180, positionPercentageOffset: 0.05);
|
return RadarChartTitle(text: 'PPS', angle: angle, positionPercentageOffset: 0.05);
|
||||||
case 4:
|
case 2:
|
||||||
return RadarChartTitle(text: 'DS/S', angle: angle + 180, positionPercentageOffset: 0.05);
|
return RadarChartTitle(text: 'VS', angle: angle, positionPercentageOffset: 0.05);
|
||||||
case 5:
|
case 3:
|
||||||
return RadarChartTitle(text: 'DS/P', angle: angle + 180, positionPercentageOffset: 0.05);
|
return RadarChartTitle(text: 'APP', angle: angle + 180, positionPercentageOffset: 0.05);
|
||||||
case 6:
|
case 4:
|
||||||
return RadarChartTitle(text: 'APP+DS/P', angle: angle + 180, positionPercentageOffset: 0.05);
|
return RadarChartTitle(text: 'DS/S', angle: angle + 180, positionPercentageOffset: 0.05);
|
||||||
case 7:
|
case 5:
|
||||||
return RadarChartTitle(text: 'VS/APM', angle: angle + 180, positionPercentageOffset: 0.05);
|
return RadarChartTitle(text: 'DS/P', angle: angle + 180, positionPercentageOffset: 0.05);
|
||||||
case 8:
|
case 6:
|
||||||
return RadarChartTitle(text: 'Cheese', angle: angle, positionPercentageOffset: 0.05);
|
return RadarChartTitle(text: 'APP+DS/P', angle: angle + 180, positionPercentageOffset: 0.05);
|
||||||
case 9:
|
case 7:
|
||||||
return RadarChartTitle(text: 'Gb Eff.', angle: angle, positionPercentageOffset: 0.05);
|
return RadarChartTitle(text: 'VS/APM', angle: angle + 180, positionPercentageOffset: 0.05);
|
||||||
default:
|
case 8:
|
||||||
return const RadarChartTitle(text: '');
|
return RadarChartTitle(text: 'Cheese', angle: angle, positionPercentageOffset: 0.05);
|
||||||
}
|
case 9:
|
||||||
},
|
return RadarChartTitle(text: 'Gb Eff.', angle: angle, positionPercentageOffset: 0.05);
|
||||||
dataSets: [
|
default:
|
||||||
RadarDataSet(
|
return const RadarChartTitle(text: '');
|
||||||
fillColor: Theme.of(context).colorScheme.primary.withAlpha(170),
|
}
|
||||||
borderColor: Theme.of(context).colorScheme.primary,
|
},
|
||||||
dataEntries: [
|
dataSets: [
|
||||||
RadarEntry(value: apm * apmWeight),
|
RadarDataSet(
|
||||||
RadarEntry(value: pps * ppsWeight),
|
fillColor: Theme.of(context).colorScheme.primary.withAlpha(170),
|
||||||
RadarEntry(value: vs * vsWeight),
|
borderColor: Theme.of(context).colorScheme.primary,
|
||||||
RadarEntry(value: nerdStats.app * appWeight),
|
dataEntries: [
|
||||||
RadarEntry(value: nerdStats.dss * dssWeight),
|
RadarEntry(value: apm * apmWeight),
|
||||||
RadarEntry(value: nerdStats.dsp * dspWeight),
|
RadarEntry(value: pps * ppsWeight),
|
||||||
RadarEntry(value: nerdStats.appdsp * appdspWeight),
|
RadarEntry(value: vs * vsWeight),
|
||||||
RadarEntry(value: nerdStats.vsapm * vsapmWeight),
|
RadarEntry(value: nerdStats.app * appWeight),
|
||||||
RadarEntry(value: nerdStats.cheese * cheeseWeight),
|
RadarEntry(value: nerdStats.dss * dssWeight),
|
||||||
RadarEntry(value: nerdStats.gbe * gbeWeight),
|
RadarEntry(value: nerdStats.dsp * dspWeight),
|
||||||
],
|
RadarEntry(value: nerdStats.appdsp * appdspWeight),
|
||||||
|
RadarEntry(value: nerdStats.vsapm * vsapmWeight),
|
||||||
|
RadarEntry(value: nerdStats.cheese * cheeseWeight),
|
||||||
|
RadarEntry(value: nerdStats.gbe * gbeWeight),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
RadarDataSet(
|
||||||
|
fillColor: Colors.transparent,
|
||||||
|
borderColor: Colors.transparent,
|
||||||
|
dataEntries: [
|
||||||
|
const RadarEntry(value: 0),
|
||||||
|
const RadarEntry(value: 180),
|
||||||
|
const RadarEntry(value: 0),
|
||||||
|
const RadarEntry(value: 0),
|
||||||
|
const RadarEntry(value: 0),
|
||||||
|
const RadarEntry(value: 0),
|
||||||
|
const RadarEntry(value: 0),
|
||||||
|
const RadarEntry(value: 0),
|
||||||
|
const RadarEntry(value: 0),
|
||||||
|
const RadarEntry(value: 0),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
swapAnimationDuration: const Duration(milliseconds: 150), // Optional
|
||||||
|
swapAnimationCurve: Curves.linear, // Optional
|
||||||
),
|
),
|
||||||
RadarDataSet(
|
),
|
||||||
fillColor: Colors.transparent,
|
|
||||||
borderColor: Colors.transparent,
|
|
||||||
dataEntries: [
|
|
||||||
const RadarEntry(value: 0),
|
|
||||||
const RadarEntry(value: 180),
|
|
||||||
const RadarEntry(value: 0),
|
|
||||||
const RadarEntry(value: 0),
|
|
||||||
const RadarEntry(value: 0),
|
|
||||||
const RadarEntry(value: 0),
|
|
||||||
const RadarEntry(value: 0),
|
|
||||||
const RadarEntry(value: 0),
|
|
||||||
const RadarEntry(value: 0),
|
|
||||||
const RadarEntry(value: 0),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
swapAnimationDuration: const Duration(milliseconds: 150), // Optional
|
Padding( // psq graph
|
||||||
swapAnimationCurve: Curves.linear, // Optional
|
padding: const EdgeInsets.fromLTRB(18, 0, 18, 22),
|
||||||
),
|
child: SizedBox(
|
||||||
),
|
height: 310,
|
||||||
),
|
width: 310,
|
||||||
Padding( // psq graph
|
child: MyRadarChart(
|
||||||
padding: const EdgeInsets.fromLTRB(18, 0, 18, 44),
|
RadarChartData(
|
||||||
child: SizedBox(
|
radarShape: RadarShape.circle,
|
||||||
height: 310,
|
tickCount: 4,
|
||||||
width: 310,
|
radarBackgroundColor: Colors.black.withAlpha(170),
|
||||||
child: MyRadarChart(
|
radarBorderData: const BorderSide(color: Colors.white24, width: 1),
|
||||||
RadarChartData(
|
gridBorderData: const BorderSide(color: Colors.white24, width: 1),
|
||||||
radarShape: RadarShape.circle,
|
tickBorderData: const BorderSide(color: Colors.white24, width: 1),
|
||||||
tickCount: 4,
|
titleTextStyle: const TextStyle(height: 1.1),
|
||||||
radarBackgroundColor: Colors.black.withAlpha(170),
|
radarTouchData: RadarTouchData(),
|
||||||
radarBorderData: const BorderSide(color: Colors.white24, width: 1),
|
getTitle: (index, angle) {
|
||||||
gridBorderData: const BorderSide(color: Colors.white24, width: 1),
|
switch (index) {
|
||||||
tickBorderData: const BorderSide(color: Colors.white24, width: 1),
|
case 0:
|
||||||
titleTextStyle: const TextStyle(height: 1.1),
|
return RadarChartTitle(text: 'Opener\n${percentage.format(playstyle.opener)}', angle: 0, positionPercentageOffset: 0.05);
|
||||||
radarTouchData: RadarTouchData(),
|
case 1:
|
||||||
getTitle: (index, angle) {
|
return RadarChartTitle(text: 'Stride\n${percentage.format(playstyle.stride)}', angle: 0, positionPercentageOffset: 0.05);
|
||||||
switch (index) {
|
case 2:
|
||||||
case 0:
|
return RadarChartTitle(text: 'Inf DS\n${percentage.format(playstyle.infds)}', angle: angle + 180, positionPercentageOffset: 0.05);
|
||||||
return RadarChartTitle(text: 'Opener\n${percentage.format(playstyle.opener)}', angle: 0, positionPercentageOffset: 0.05);
|
case 3:
|
||||||
case 1:
|
return RadarChartTitle(text: 'Plonk\n${percentage.format(playstyle.plonk)}', angle: 0, positionPercentageOffset: 0.05);
|
||||||
return RadarChartTitle(text: 'Stride\n${percentage.format(playstyle.stride)}', angle: 0, positionPercentageOffset: 0.05);
|
default:
|
||||||
case 2:
|
return const RadarChartTitle(text: '');
|
||||||
return RadarChartTitle(text: 'Inf DS\n${percentage.format(playstyle.infds)}', angle: angle + 180, positionPercentageOffset: 0.05);
|
}
|
||||||
case 3:
|
},
|
||||||
return RadarChartTitle(text: 'Plonk\n${percentage.format(playstyle.plonk)}', angle: 0, positionPercentageOffset: 0.05);
|
dataSets: [
|
||||||
default:
|
RadarDataSet(
|
||||||
return const RadarChartTitle(text: '');
|
fillColor: Theme.of(context).colorScheme.primary.withAlpha(170),
|
||||||
}
|
borderColor: Theme.of(context).colorScheme.primary,
|
||||||
},
|
dataEntries: [
|
||||||
dataSets: [
|
RadarEntry(value: playstyle.opener),
|
||||||
RadarDataSet(
|
RadarEntry(value: playstyle.stride),
|
||||||
fillColor: Theme.of(context).colorScheme.primary.withAlpha(170),
|
RadarEntry(value: playstyle.infds),
|
||||||
borderColor: Theme.of(context).colorScheme.primary,
|
RadarEntry(value: playstyle.plonk),
|
||||||
dataEntries: [
|
],
|
||||||
RadarEntry(value: playstyle.opener),
|
),
|
||||||
RadarEntry(value: playstyle.stride),
|
RadarDataSet(
|
||||||
RadarEntry(value: playstyle.infds),
|
fillColor: Colors.transparent,
|
||||||
RadarEntry(value: playstyle.plonk),
|
borderColor: Colors.transparent,
|
||||||
],
|
dataEntries: [
|
||||||
|
const RadarEntry(value: 0),
|
||||||
|
const RadarEntry(value: 1),
|
||||||
|
const RadarEntry(value: 0),
|
||||||
|
const RadarEntry(value: 0),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
swapAnimationDuration: const Duration(milliseconds: 150), // Optional
|
||||||
|
swapAnimationCurve: Curves.linear, // Optional
|
||||||
),
|
),
|
||||||
RadarDataSet(
|
),
|
||||||
fillColor: Colors.transparent,
|
|
||||||
borderColor: Colors.transparent,
|
|
||||||
dataEntries: [
|
|
||||||
const RadarEntry(value: 0),
|
|
||||||
const RadarEntry(value: 1),
|
|
||||||
const RadarEntry(value: 0),
|
|
||||||
const RadarEntry(value: 0),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
swapAnimationDuration: const Duration(milliseconds: 150), // Optional
|
Padding( // sq graph
|
||||||
swapAnimationCurve: Curves.linear, // Optional
|
padding: const EdgeInsets.fromLTRB(18, 0, 18, 22),
|
||||||
),
|
child: SizedBox(
|
||||||
),
|
height: 310,
|
||||||
),
|
width: 310,
|
||||||
Padding( // sq graph
|
child: MyRadarChart(
|
||||||
padding: const EdgeInsets.fromLTRB(18, 0, 18, 44),
|
RadarChartData(
|
||||||
child: SizedBox(
|
radarShape: RadarShape.circle,
|
||||||
height: 310,
|
tickCount: 4,
|
||||||
width: 310,
|
radarBackgroundColor: Colors.black.withAlpha(170),
|
||||||
child: MyRadarChart(
|
radarBorderData: const BorderSide(color: Colors.white24, width: 1),
|
||||||
RadarChartData(
|
gridBorderData: const BorderSide(color: Colors.white24, width: 1),
|
||||||
radarShape: RadarShape.circle,
|
tickBorderData: const BorderSide(color: Colors.white24, width: 1),
|
||||||
tickCount: 4,
|
titleTextStyle: const TextStyle(height: 1.1),
|
||||||
radarBackgroundColor: Colors.black.withAlpha(170),
|
radarTouchData: RadarTouchData(),
|
||||||
radarBorderData: const BorderSide(color: Colors.white24, width: 1),
|
getTitle: (index, angle) {
|
||||||
gridBorderData: const BorderSide(color: Colors.white24, width: 1),
|
switch (index) {
|
||||||
tickBorderData: const BorderSide(color: Colors.white24, width: 1),
|
case 0:
|
||||||
titleTextStyle: const TextStyle(height: 1.1),
|
return RadarChartTitle(text: '${t.graphs.attack}\n${f2.format(apm)} APM', angle: 0, positionPercentageOffset: 0.05);
|
||||||
radarTouchData: RadarTouchData(),
|
case 1:
|
||||||
getTitle: (index, angle) {
|
return RadarChartTitle(text: '${t.graphs.speed}\n${f2.format(pps)} PPS', angle: 0, positionPercentageOffset: 0.05);
|
||||||
switch (index) {
|
case 2:
|
||||||
case 0:
|
return RadarChartTitle(text: '${t.graphs.defense}\n${f2.format(nerdStats.dss)} DS/S', angle: angle + 180, positionPercentageOffset: 0.05);
|
||||||
return RadarChartTitle(text: '${t.graphs.attack}\n${f2.format(apm)} APM', angle: 0, positionPercentageOffset: 0.05);
|
case 3:
|
||||||
case 1:
|
return RadarChartTitle(text: '${t.graphs.cheese}\n${f3.format(nerdStats.cheese)}', angle: 0, positionPercentageOffset: 0.05);
|
||||||
return RadarChartTitle(text: '${t.graphs.speed}\n${f2.format(pps)} PPS', angle: 0, positionPercentageOffset: 0.05);
|
default:
|
||||||
case 2:
|
return const RadarChartTitle(text: '');
|
||||||
return RadarChartTitle(text: '${t.graphs.defense}\n${f2.format(nerdStats.dss)} DS/S', angle: angle + 180, positionPercentageOffset: 0.05);
|
}
|
||||||
case 3:
|
},
|
||||||
return RadarChartTitle(text: '${t.graphs.cheese}\n${f3.format(nerdStats.cheese)}', angle: 0, positionPercentageOffset: 0.05);
|
dataSets: [
|
||||||
default:
|
RadarDataSet(
|
||||||
return const RadarChartTitle(text: '');
|
fillColor: Theme.of(context).colorScheme.primary.withAlpha(170),
|
||||||
}
|
borderColor: Theme.of(context).colorScheme.primary,
|
||||||
},
|
dataEntries: [
|
||||||
dataSets: [
|
RadarEntry(value: attack),
|
||||||
RadarDataSet(
|
RadarEntry(value: speed),
|
||||||
fillColor: Theme.of(context).colorScheme.primary.withAlpha(170),
|
RadarEntry(value: defense),
|
||||||
borderColor: Theme.of(context).colorScheme.primary,
|
RadarEntry(value: cheese),
|
||||||
dataEntries: [
|
],
|
||||||
RadarEntry(value: attack),
|
),
|
||||||
RadarEntry(value: speed),
|
RadarDataSet(
|
||||||
RadarEntry(value: defense),
|
fillColor: Colors.transparent,
|
||||||
RadarEntry(value: cheese),
|
borderColor: Colors.transparent,
|
||||||
],
|
dataEntries: [
|
||||||
),
|
const RadarEntry(value: 0),
|
||||||
RadarDataSet(
|
const RadarEntry(value: 1.2),
|
||||||
fillColor: Colors.transparent,
|
const RadarEntry(value: 0),
|
||||||
borderColor: Colors.transparent,
|
const RadarEntry(value: 0),
|
||||||
dataEntries: [
|
],
|
||||||
const RadarEntry(value: 0),
|
)
|
||||||
const RadarEntry(value: 1.2),
|
],
|
||||||
const RadarEntry(value: 0),
|
)
|
||||||
const RadarEntry(value: 0),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
],
|
)
|
||||||
)
|
)
|
||||||
)
|
],
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
],
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class InfoThingy extends StatelessWidget{
|
||||||
|
final String info;
|
||||||
|
|
||||||
|
const InfoThingy(this.info);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Center(child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.info_outline, size: 128.0, color: Colors.grey.shade800),
|
||||||
|
SizedBox(height: 5.0),
|
||||||
|
Text(info, textAlign: TextAlign.center),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/cutoff_tetrio.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/nerd_stats.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/player_leaderboard_position.dart';
|
||||||
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
|
import 'package:tetra_stats/utils/colors_functions.dart';
|
||||||
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
|
import 'package:tetra_stats/widgets/gauget_thingy.dart';
|
||||||
|
|
||||||
|
class NerdStatsThingy extends StatelessWidget{
|
||||||
|
final NerdStats nerdStats;
|
||||||
|
final NerdStats? oldNerdStats;
|
||||||
|
final CutoffTetrio? averages;
|
||||||
|
final PlayerLeaderboardPosition? lbPos;
|
||||||
|
|
||||||
|
const NerdStatsThingy({super.key, required this.nerdStats, this.oldNerdStats, this.averages, this.lbPos});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(12.0, 8.0, 12.0, 8.0),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 256.0,
|
||||||
|
width: 256.0,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(1000),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(gradient: RadialGradient(colors: [Colors.black12.withAlpha(100), Colors.black], radius: 0.6)),
|
||||||
|
child: SfRadialGauge(
|
||||||
|
axes: [
|
||||||
|
RadialAxis(
|
||||||
|
startAngle: 190,
|
||||||
|
endAngle: 350,
|
||||||
|
showLabels: false,
|
||||||
|
showTicks: true,
|
||||||
|
radiusFactor: 1,
|
||||||
|
centerY: 0.5,
|
||||||
|
minimum: 0,
|
||||||
|
maximum: 1,
|
||||||
|
ranges: [
|
||||||
|
GaugeRange(startValue: 0, endValue: 0.2, color: Colors.red),
|
||||||
|
GaugeRange(startValue: 0.2, endValue: 0.4, color: Colors.yellow),
|
||||||
|
GaugeRange(startValue: 0.4, endValue: 0.6, color: Colors.green),
|
||||||
|
GaugeRange(startValue: 0.6, endValue: 0.8, color: Colors.blue),
|
||||||
|
GaugeRange(startValue: 0.8, endValue: 1, color: Colors.purple),
|
||||||
|
],
|
||||||
|
pointers: [
|
||||||
|
NeedlePointer(
|
||||||
|
value: nerdStats.app,
|
||||||
|
enableAnimation: true,
|
||||||
|
needleLength: 0.9,
|
||||||
|
needleStartWidth: 2,
|
||||||
|
needleEndWidth: 15,
|
||||||
|
knobStyle: const KnobStyle(color: Colors.transparent),
|
||||||
|
gradient: const LinearGradient(colors: [Colors.transparent, Colors.white], begin: Alignment.bottomCenter, end: Alignment.topCenter, stops: [0.5, 1]),)
|
||||||
|
],
|
||||||
|
annotations: [
|
||||||
|
GaugeAnnotation(widget: Container(child:
|
||||||
|
RichText(
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
text: TextSpan(
|
||||||
|
style: const TextStyle(fontFamily: "Eurostile Round"),
|
||||||
|
children: [
|
||||||
|
const TextSpan(text: "APP\n"),
|
||||||
|
TextSpan(text: f3.format(nerdStats.app), style: TextStyle(fontSize: 25, fontFamily: "Eurostile Round Extended", fontWeight: FontWeight.w100, color: getStatColor(nerdStats.app, averages?.nerdStats?.app, true))),
|
||||||
|
if (lbPos != null) TextSpan(text: lbPos!.app!.position >= 1000 ? "\n${t.top} ${f2.format(lbPos!.app!.percentage*100)}%" : "\n№${lbPos!.app!.position}", style: TextStyle(color: getColorOfRank(lbPos!.app!.position))),
|
||||||
|
if (oldNerdStats != null) TextSpan(text: "\n${comparef.format(nerdStats.app - oldNerdStats!.app)}", style: TextStyle(color: getDifferenceColor(nerdStats.app - oldNerdStats!.app)))
|
||||||
|
]
|
||||||
|
))),
|
||||||
|
angle: 270,positionFactor: 0.5
|
||||||
|
)],
|
||||||
|
),
|
||||||
|
RadialAxis(
|
||||||
|
startAngle: 20,
|
||||||
|
endAngle: 160,
|
||||||
|
isInversed: true,
|
||||||
|
showLabels: false,
|
||||||
|
showTicks: true,
|
||||||
|
radiusFactor: 1,
|
||||||
|
centerY: 0.5,
|
||||||
|
minimum: 1.8,
|
||||||
|
maximum: 2.4,
|
||||||
|
ranges: [
|
||||||
|
GaugeRange(startValue: 1.8, endValue: 2.0, color: Colors.green),
|
||||||
|
GaugeRange(startValue: 2.0, endValue: 2.2, color: Colors.blue),
|
||||||
|
GaugeRange(startValue: 2.2, endValue: 2.4, color: Colors.purple),
|
||||||
|
],
|
||||||
|
pointers: [
|
||||||
|
NeedlePointer(
|
||||||
|
value: nerdStats.vsapm,
|
||||||
|
enableAnimation: true,
|
||||||
|
needleLength: 0.9,
|
||||||
|
needleStartWidth: 2,
|
||||||
|
needleEndWidth: 15,
|
||||||
|
knobStyle: const KnobStyle(color: Colors.transparent),
|
||||||
|
gradient: const LinearGradient(colors: [Colors.transparent, Colors.white], begin: Alignment.bottomCenter, end: Alignment.topCenter, stops: [0.5, 1]),)
|
||||||
|
],
|
||||||
|
annotations: [
|
||||||
|
GaugeAnnotation(widget: Container(child:
|
||||||
|
RichText(
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
text: TextSpan(
|
||||||
|
style: const TextStyle(fontFamily: "Eurostile Round"),
|
||||||
|
children: [
|
||||||
|
const TextSpan(text: "VS/APM\n"),
|
||||||
|
TextSpan(text: f3.format(nerdStats.vsapm), style: TextStyle(fontSize: 25, fontFamily: "Eurostile Round Extended", fontWeight: FontWeight.w100, color: getStatColor(nerdStats.vsapm, averages?.nerdStats?.vsapm, true))),
|
||||||
|
if (lbPos != null) TextSpan(text: lbPos!.vsapm!.position >= 1000 ? "\n${t.top} ${f2.format(lbPos!.vsapm!.percentage*100)}%" : "\n№${lbPos!.vsapm!.position}", style: TextStyle(color: getColorOfRank(lbPos!.vsapm!.position))),
|
||||||
|
if (oldNerdStats != null) TextSpan(text: "\n${comparef.format(nerdStats.vsapm - oldNerdStats!.vsapm)}", style: TextStyle(color: getDifferenceColor(nerdStats.vsapm - oldNerdStats!.vsapm))),
|
||||||
|
]
|
||||||
|
))),
|
||||||
|
angle: 90,positionFactor: 0.5
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Wrap(
|
||||||
|
alignment: WrapAlignment.center,
|
||||||
|
spacing: 10.0,
|
||||||
|
runSpacing: 10.0,
|
||||||
|
runAlignment: WrapAlignment.start,
|
||||||
|
children: [
|
||||||
|
GaugetThingy(value: nerdStats.dss, oldValue: oldNerdStats?.dss, min: 0, max: 1.0, tickInterval: .2, label: "DS/S", sideSize: 128.0, fractionDigits: 3, moreIsBetter: true, avgValue: averages?.nerdStats?.dss, lbPos: lbPos?.dss),
|
||||||
|
GaugetThingy(value: nerdStats.dsp, oldValue: oldNerdStats?.dsp, min: 0, max: 1.0, tickInterval: .2, label: "DS/P", sideSize: 128.0, fractionDigits: 3, moreIsBetter: true, avgValue: averages?.nerdStats?.dsp, lbPos: lbPos?.dsp),
|
||||||
|
GaugetThingy(value: nerdStats.appdsp, oldValue: oldNerdStats?.appdsp, min: 0, max: 1.2, tickInterval: .2, label: "APP+DS/P", sideSize: 128.0, fractionDigits: 3, moreIsBetter: true, avgValue: averages?.nerdStats?.appdsp, lbPos: lbPos?.appdsp),
|
||||||
|
GaugetThingy(value: nerdStats.cheese, oldValue: oldNerdStats?.cheese, min: -80, max: 80, tickInterval: 40, label: "Cheese", sideSize: 128.0, fractionDigits: 2, moreIsBetter: false, lbPos: lbPos?.cheese),
|
||||||
|
GaugetThingy(value: nerdStats.gbe, oldValue: oldNerdStats?.gbe, min: 0, max: 1.0, tickInterval: .2, label: "GbE", sideSize: 128.0, fractionDigits: 3, moreIsBetter: true, avgValue: averages?.nerdStats?.gbe, lbPos: lbPos?.gbe),
|
||||||
|
GaugetThingy(value: nerdStats.nyaapp, oldValue: oldNerdStats?.nyaapp, min: 0, max: 1.2, tickInterval: .2, label: "wAPP", sideSize: 128.0, fractionDigits: 3, moreIsBetter: true, avgValue: averages?.nerdStats?.nyaapp, lbPos: lbPos?.nyaapp),
|
||||||
|
GaugetThingy(value: nerdStats.area, oldValue: oldNerdStats?.area, min: 0, max: 1000, tickInterval: 100, label: "Area", sideSize: 128.0, fractionDigits: 1, moreIsBetter: true, avgValue: averages?.nerdStats?.area, lbPos: lbPos?.area),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,188 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/news.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/news_entry.dart';
|
||||||
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
|
import 'package:tetra_stats/utils/relative_timestamps.dart';
|
||||||
|
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
||||||
|
|
||||||
|
class NewsThingy extends StatelessWidget{
|
||||||
|
final News news;
|
||||||
|
|
||||||
|
const NewsThingy(this.news, {super.key});
|
||||||
|
|
||||||
|
ListTile getNewsTile(NewsEntry news){
|
||||||
|
Map<String, String> gametypes = {
|
||||||
|
"40l": t.sprint,
|
||||||
|
"blitz": t.blitz,
|
||||||
|
"5mblast": "5,000,000 Blast",
|
||||||
|
"zenith": "Quick Play",
|
||||||
|
"zenithex": "Quick Play Expert",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Individuly handle each entry type
|
||||||
|
switch (news.type) {
|
||||||
|
case "leaderboard":
|
||||||
|
return ListTile(
|
||||||
|
title: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16, color: Colors.white),
|
||||||
|
text: t.newsParts.leaderboardStart,
|
||||||
|
children: [
|
||||||
|
TextSpan(text: "№${news.data["rank"]} ", style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
TextSpan(text: t.newsParts.leaderboardMiddle),
|
||||||
|
TextSpan(text: "№${gametypes[news.data["gametype"]]}", style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
subtitle: Text(timestamp(news.timestamp)),
|
||||||
|
);
|
||||||
|
case "personalbest":
|
||||||
|
return ListTile(
|
||||||
|
title: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16, color: Colors.white),
|
||||||
|
text: t.newsParts.personalbest,
|
||||||
|
children: [
|
||||||
|
TextSpan(text: "${gametypes[news.data["gametype"]]} ", style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
TextSpan(text: t.newsParts.personalbestMiddle),
|
||||||
|
TextSpan(text: switch (news.data["gametype"]){
|
||||||
|
"blitz" => NumberFormat.decimalPattern().format(news.data["result"]),
|
||||||
|
"40l" => get40lTime((news.data["result"]*1000).floor()),
|
||||||
|
"5mblast" => get40lTime((news.data["result"]*1000).floor()),
|
||||||
|
"zenith" => "${f2.format(news.data["result"])} m.",
|
||||||
|
"zenithex" => "${f2.format(news.data["result"])} m.",
|
||||||
|
_ => "unknown"
|
||||||
|
},
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
subtitle: Text(timestamp(news.timestamp)),
|
||||||
|
leading: Image.asset(
|
||||||
|
"res/icons/improvement-local.png",
|
||||||
|
height: 48,
|
||||||
|
width: 48,
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
return Image.asset("res/icons/kagari.png", height: 64, width: 64);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
case "badge":
|
||||||
|
return ListTile(
|
||||||
|
title: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16, color: Colors.white),
|
||||||
|
text: t.newsParts.badgeStart,
|
||||||
|
children: [
|
||||||
|
TextSpan(text: "${news.data["label"]} ", style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
TextSpan(text: t.newsParts.badgeEnd)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
subtitle: Text(timestamp(news.timestamp)),
|
||||||
|
leading: Image.asset(
|
||||||
|
"res/tetrio_badges/${news.data["type"]}.png",
|
||||||
|
height: 48,
|
||||||
|
width: 48,
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
return Image.asset("res/icons/kagari.png", height: 64, width: 64);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
case "rankup":
|
||||||
|
return ListTile(
|
||||||
|
title: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16, color: Colors.white),
|
||||||
|
text: t.newsParts.rankupStart,
|
||||||
|
children: [
|
||||||
|
TextSpan(text: t.newsParts.rankupMiddle(r: news.data["rank"].toString().toUpperCase()), style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
TextSpan(text: t.newsParts.rankupEnd)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
subtitle: Text(timestamp(news.timestamp)),
|
||||||
|
leading: Image.asset(
|
||||||
|
"res/tetrio_tl_alpha_ranks/${news.data["rank"]}.png",
|
||||||
|
height: 48,
|
||||||
|
width: 48,
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
return Image.asset("res/icons/kagari.png", height: 64, width: 64);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
case "supporter":
|
||||||
|
return ListTile(
|
||||||
|
title: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16, color: Colors.white),
|
||||||
|
text: t.newsParts.supporterStart,
|
||||||
|
children: [
|
||||||
|
TextSpan(text: t.newsParts.tetoSupporter, style: const TextStyle(fontWeight: FontWeight.bold))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
subtitle: Text(timestamp(news.timestamp)),
|
||||||
|
leading: Image.asset(
|
||||||
|
"res/icons/supporter-tag.png",
|
||||||
|
height: 48,
|
||||||
|
width: 48,
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
return Image.asset("res/icons/kagari.png", height: 64, width: 64);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
case "supporter_gift":
|
||||||
|
return ListTile(
|
||||||
|
title: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16, color: Colors.white),
|
||||||
|
text: t.newsParts.supporterGiftStart,
|
||||||
|
children: [
|
||||||
|
TextSpan(text: t.newsParts.tetoSupporter, style: const TextStyle(fontWeight: FontWeight.bold))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
subtitle: Text(timestamp(news.timestamp)),
|
||||||
|
leading: Image.asset(
|
||||||
|
"res/icons/supporter-tag.png",
|
||||||
|
height: 48,
|
||||||
|
width: 48,
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
return Image.asset("res/icons/kagari.png", height: 64, width: 64);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
default: // if type is unknown
|
||||||
|
return ListTile(
|
||||||
|
title: Text(t.newsParts.unknownNews(type: news.type)),
|
||||||
|
subtitle: Text(timestamp(news.timestamp)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Spacer(),
|
||||||
|
Text(t.news, style: const TextStyle(fontFamily: "Eurostile Round Extended")),
|
||||||
|
const Spacer()
|
||||||
|
]
|
||||||
|
),
|
||||||
|
if (news.news.isEmpty) const Center(child: Text("Empty list"))
|
||||||
|
else for (NewsEntry entry in news.news) getNewsTile(entry)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -100,7 +100,7 @@ class SingleplayerRecord extends StatelessWidget {
|
||||||
if (record!.gamemode == "40l") Wrap(
|
if (record!.gamemode == "40l") Wrap(
|
||||||
alignment: WrapAlignment.spaceBetween,
|
alignment: WrapAlignment.spaceBetween,
|
||||||
spacing: 20,
|
spacing: 20,
|
||||||
children: [
|
children: [ // TODO: replace
|
||||||
StatCellNum(playerStat: record!.stats.piecesPlaced, playerStatLabel: t.statCellNum.pieces, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: false),
|
StatCellNum(playerStat: record!.stats.piecesPlaced, playerStatLabel: t.statCellNum.pieces, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: false),
|
||||||
StatCellNum(playerStat: record!.stats.pps, playerStatLabel: t.statCellNum.pps, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: false),
|
StatCellNum(playerStat: record!.stats.pps, playerStatLabel: t.statCellNum.pps, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: false),
|
||||||
StatCellNum(playerStat: record!.stats.kpp, playerStatLabel: t.statCellNum.kpp, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: false),
|
StatCellNum(playerStat: record!.stats.kpp, playerStatLabel: t.statCellNum.kpp, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: false),
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:tetra_stats/data_objects/tetra_league.dart';
|
import 'package:tetra_stats/data_objects/tetra_league.dart';
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
import 'package:tetra_stats/main.dart' show prefs;
|
import 'package:tetra_stats/main.dart' show prefs;
|
||||||
|
import 'package:tetra_stats/utils/colors_functions.dart';
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
|
|
||||||
var fDiff = NumberFormat("+#,###.####;-#,###.####");
|
import 'text_timestamp.dart';
|
||||||
|
|
||||||
class TLRatingThingy extends StatelessWidget{
|
class TLRatingThingy extends StatelessWidget{
|
||||||
final String userID;
|
final String userID;
|
||||||
final TetraLeague tlData;
|
final TetraLeague tlData;
|
||||||
final TetraLeague? oldTl;
|
final TetraLeague? oldTl;
|
||||||
final double? topTR;
|
final double? topTR;
|
||||||
|
final bool? showPositions;
|
||||||
final DateTime? lastMatchPlayed;
|
final DateTime? lastMatchPlayed;
|
||||||
|
|
||||||
const TLRatingThingy({super.key, required this.userID, required this.tlData, this.oldTl, this.topTR, this.lastMatchPlayed});
|
const TLRatingThingy({super.key, required this.userID, required this.tlData, this.oldTl, this.topTR, this.lastMatchPlayed, this.showPositions});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -39,10 +40,11 @@ class TLRatingThingy extends StatelessWidget{
|
||||||
? Image.asset("res/icons/kagari.png", height: 128) // Btw why she wearing Kazamatsuri high school uniform?
|
? Image.asset("res/icons/kagari.png", height: 128) // Btw why she wearing Kazamatsuri high school uniform?
|
||||||
: Image.asset("res/tetrio_tl_alpha_ranks/${tlData.rank}.png", height: 128),
|
: Image.asset("res/tetrio_tl_alpha_ranks/${tlData.rank}.png", height: 128),
|
||||||
Column(
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
RichText(
|
RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 20, color: Colors.white),
|
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 20, color: Colors.white, height: 0.9),
|
||||||
children: (tlData.gamesPlayed > 9) ? switch(prefs.getInt("ratingMode")){
|
children: (tlData.gamesPlayed > 9) ? switch(prefs.getInt("ratingMode")){
|
||||||
1 => [
|
1 => [
|
||||||
TextSpan(text: formatedGlicko[0], style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
|
TextSpan(text: formatedGlicko[0], style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
|
||||||
|
@ -62,17 +64,43 @@ class TLRatingThingy extends StatelessWidget{
|
||||||
} : [TextSpan(text: "---\n", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28, color: Colors.grey)), TextSpan(text: t.gamesUntilRanked(left: 10-tlData.gamesPlayed), style: const TextStyle(color: Colors.grey, fontSize: 14)),]
|
} : [TextSpan(text: "---\n", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28, color: Colors.grey)), TextSpan(text: t.gamesUntilRanked(left: 10-tlData.gamesPlayed), style: const TextStyle(color: Colors.grey, fontSize: 14)),]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
if (oldTl != null) Text(
|
if (oldTl != null) RichText(
|
||||||
switch(prefs.getInt("ratingMode")){
|
|
||||||
1 => "${fDiff.format(tlData.glicko! - oldTl!.glicko!)} Glicko",
|
|
||||||
2 => "${fDiff.format(tlData.percentile * 100 - oldTl!.percentile * 100)} %",
|
|
||||||
_ => "${fDiff.format(tlData.tr - oldTl!.tr)} TR"
|
|
||||||
},
|
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
softWrap: true,
|
||||||
color: tlData.tr - oldTl!.tr < 0 ?
|
text: TextSpan(
|
||||||
Colors.red :
|
style: DefaultTextStyle.of(context).style,
|
||||||
Colors.green
|
children: [
|
||||||
|
TextSpan(text: switch(prefs.getInt("ratingMode")){
|
||||||
|
1 => "${fDiff.format(tlData.glicko! - oldTl!.glicko!)} Glicko",
|
||||||
|
2 => "${fDiff.format(tlData.percentile * 100 - oldTl!.percentile * 100)} %",
|
||||||
|
_ => "${fDiff.format(tlData.tr - oldTl!.tr)} TR"
|
||||||
|
},
|
||||||
|
style: TextStyle(
|
||||||
|
color: getDifferenceColor(switch(prefs.getInt("ratingMode")){
|
||||||
|
1 => tlData.glicko! - oldTl!.glicko!,
|
||||||
|
2 => tlData.percentile - oldTl!.percentile,
|
||||||
|
_ => tlData.tr - oldTl!.tr
|
||||||
|
})
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const TextSpan(text: " • ", style: TextStyle(color: Colors.grey)),
|
||||||
|
TextSpan(text: switch(prefs.getInt("ratingMode")){
|
||||||
|
1 => "${fDiff.format(tlData.tr - oldTl!.tr)} TR",
|
||||||
|
_ => "${fDiff.format(tlData.glicko! - oldTl!.glicko!)} Glicko"
|
||||||
|
},
|
||||||
|
style: TextStyle(
|
||||||
|
color: getDifferenceColor(switch(prefs.getInt("ratingMode")){
|
||||||
|
1 => tlData.tr - oldTl!.tr,
|
||||||
|
_ => tlData.glicko! - oldTl!.glicko!
|
||||||
|
})
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const TextSpan(text: " • ", style: TextStyle(color: Colors.grey)),
|
||||||
|
TextSpan(
|
||||||
|
text: "${fDiff.format(tlData.rd! - oldTl!.rd!)} RD",
|
||||||
|
style: TextStyle(color: getDifferenceColor(oldTl!.rd! - tlData.rd!))
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (tlData.gamesPlayed > 9) Column(
|
if (tlData.gamesPlayed > 9) Column(
|
||||||
|
@ -96,6 +124,20 @@ class TLRatingThingy extends StatelessWidget{
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
if (showPositions == true) RichText(
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
text: TextSpan(
|
||||||
|
text: "",
|
||||||
|
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey),
|
||||||
|
children: [
|
||||||
|
if (tlData.standing != -1) TextSpan(text: "№ ${intf.format(tlData.standing)}", style: TextStyle(color: getColorOfRank(tlData.standing))),
|
||||||
|
if (tlData.standing != -1 || tlData.standingLocal != -1) const TextSpan(text: " • "),
|
||||||
|
if (tlData.standingLocal != -1) TextSpan(text: "№ ${intf.format(tlData.standingLocal)} local", style: TextStyle(color: getColorOfRank(tlData.standingLocal))),
|
||||||
|
if (tlData.standing != -1 && tlData.standingLocal != -1) const TextSpan(text: " • "),
|
||||||
|
TextSpan(text: timestamp(tlData.timestamp)),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/beta_record.dart';
|
||||||
|
import 'package:tetra_stats/main.dart';
|
||||||
|
import 'package:tetra_stats/widgets/beta_league_entry_thingy.dart';
|
||||||
|
import 'package:tetra_stats/widgets/future_error.dart';
|
||||||
|
|
||||||
|
class TLRecords extends StatelessWidget {
|
||||||
|
final String userID;
|
||||||
|
|
||||||
|
/// Widget, that displays Tetra League records.
|
||||||
|
/// Accepts list of TL records ([data]) and [userID] of player from the view
|
||||||
|
const TLRecords(this.userID);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FutureBuilder(
|
||||||
|
future: teto.fetchTLStream(userID),
|
||||||
|
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 Column(
|
||||||
|
children: [
|
||||||
|
for (BetaRecord record in snapshot.data!.records) BetaLeagueEntryThingy(record, userID)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (snapshot.hasError){ return FutureError(snapshot); }
|
||||||
|
}
|
||||||
|
return const Text("what?");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,319 +1,112 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:tetra_stats/data_objects/cutoff_tetrio.dart';
|
||||||
import 'package:tetra_stats/data_objects/player_leaderboard_position.dart';
|
import 'package:tetra_stats/data_objects/p1nkl0bst3r.dart';
|
||||||
import 'package:tetra_stats/data_objects/tetra_league.dart';
|
import 'package:tetra_stats/data_objects/player_leaderboard_position.dart';
|
||||||
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
import 'package:tetra_stats/data_objects/tetra_league.dart';
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
import 'package:tetra_stats/data_objects/tetrio_constants.dart';
|
||||||
import 'package:tetra_stats/utils/colors_functions.dart';
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
import 'package:tetra_stats/utils/colors_functions.dart';
|
||||||
import 'package:tetra_stats/widgets/gauget_num.dart';
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
import 'package:tetra_stats/widgets/graphs.dart';
|
import 'package:tetra_stats/widgets/gauget_thingy.dart';
|
||||||
import 'package:tetra_stats/widgets/stat_sell_num.dart';
|
import 'package:tetra_stats/widgets/tl_progress_bar.dart';
|
||||||
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
import 'package:tetra_stats/widgets/tl_rating_thingy.dart';
|
||||||
import 'package:tetra_stats/widgets/tl_progress_bar.dart';
|
|
||||||
import 'package:tetra_stats/widgets/tl_rating_thingy.dart';
|
class TetraLeagueThingy extends StatelessWidget{
|
||||||
|
final TetraLeague league;
|
||||||
|
final TetraLeague? toCompare;
|
||||||
var intFDiff = NumberFormat("+#,###.000;-#,###.000");
|
final Cutoffs? cutoffs;
|
||||||
|
final CutoffTetrio? averages;
|
||||||
class TLThingy extends StatefulWidget {
|
final PlayerLeaderboardPosition? lbPos;
|
||||||
final TetraLeague tl;
|
|
||||||
final String userID;
|
const TetraLeagueThingy({super.key, required this.league, this.toCompare, this.cutoffs, this.averages, this.lbPos});
|
||||||
final List<TetraLeague> states;
|
|
||||||
final bool showTitle;
|
@override
|
||||||
final bool bot;
|
Widget build(BuildContext context) {
|
||||||
final bool guest;
|
return Card(
|
||||||
final double? topTR;
|
//surfaceTintColor: rankColors[league.rank],
|
||||||
final PlayerLeaderboardPosition? lbPositions;
|
child: Column(
|
||||||
final TetraLeague? averages;
|
children: [
|
||||||
final double? thatRankCutoff;
|
TLRatingThingy(userID: league.id, tlData: league, oldTl: toCompare, showPositions: true),
|
||||||
final double? thatRankCutoffGlicko;
|
if (league.gamesPlayed > 9) TLProgress(
|
||||||
final double? thatRankTarget;
|
tlData: league,
|
||||||
final double? nextRankCutoff;
|
previousRankTRcutoff: cutoffs != null ? cutoffs!.tr[league.rank != "z" ? league.rank : league.percentileRank] : null,
|
||||||
final double? nextRankCutoffGlicko;
|
nextRankTRcutoff: cutoffs != null ? (league.rank != "z" ? league.rank == "x+" : league.percentileRank == "x+") ? 25000 : cutoffs!.tr[ranks.elementAtOrNull(ranks.indexOf(league.rank != "z" ? league.rank : league.percentileRank)+1)] : null,
|
||||||
final double? nextRankTarget;
|
previousRankTRcutoffTarget: league.rank != "z" ? rankTargets[league.rank] : null,
|
||||||
final DateTime? lastMatchPlayed;
|
nextRankTRcutoffTarget: (league.rank != "z" && league.rank != "x+") ? rankTargets[ranks.elementAtOrNull(ranks.indexOf(league.rank)+1)] : null,
|
||||||
const TLThingy({super.key, required this.tl, required this.userID, required this.states, this.showTitle = true, this.bot=false, this.guest=false, this.topTR, this.lbPositions, this.averages, this.nextRankCutoff, this.thatRankCutoff, this.thatRankCutoffGlicko, this.nextRankCutoffGlicko, this.nextRankTarget, this.thatRankTarget, this.lastMatchPlayed});
|
previousGlickoCutoff: cutoffs != null ? cutoffs!.glicko[league.rank != "z" ? league.rank : league.percentileRank] : null,
|
||||||
|
nextRankGlickoCutoff: cutoffs != null ? (league.rank != "z" ? league.rank == "x+" : league.percentileRank == "x+") ? 25000 : cutoffs!.glicko[ranks.elementAtOrNull(ranks.indexOf(league.rank != "z" ? league.rank : league.percentileRank)+1)] : null,
|
||||||
@override
|
),
|
||||||
State<TLThingy> createState() => _TLThingyState();
|
Row(
|
||||||
}
|
// spacing: 25.0,
|
||||||
|
// alignment: WrapAlignment.spaceAround,
|
||||||
class _TLThingyState extends State<TLThingy> with TickerProviderStateMixin {
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
late bool oskKagariGimmick;
|
children: [
|
||||||
late TetraLeague? oldTl;
|
Expanded(
|
||||||
late TetraLeague currentTl;
|
child: Center(
|
||||||
late RangeValues _currentRangeValues;
|
child: Table(
|
||||||
late List<TetraLeague> sortedStates;
|
defaultVerticalAlignment: TableCellVerticalAlignment.baseline,
|
||||||
|
textBaseline: TextBaseline.alphabetic,
|
||||||
@override
|
defaultColumnWidth:const IntrinsicColumnWidth(),
|
||||||
void initState() {
|
children: [
|
||||||
_currentRangeValues = const RangeValues(0, 1);
|
TableRow(children: [
|
||||||
sortedStates = widget.states.reversed.toList();
|
Text(league.apm != null ? f2.format(league.apm) : "-.--", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: league.apm != null ? getStatColor(league.apm!, averages?.apm, true) : Colors.grey)),
|
||||||
oldTl = sortedStates.elementAtOrNull(1);
|
Text(" APM", style: TextStyle(fontSize: 21, color: league.apm != null ? getStatColor(league.apm!, averages?.apm, true) : Colors.grey)),
|
||||||
currentTl = widget.tl;
|
if (toCompare != null) Text(" (${comparef2.format(league.apm!-toCompare!.apm!)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: getDifferenceColor(league.apm!-toCompare!.apm!))),
|
||||||
super.initState();
|
if (lbPos != null) Text(lbPos?.apm != null ? (lbPos!.apm!.position >= 1000 ? " (${t.top} ${f2.format(lbPos!.apm!.percentage*100)}%)" : " (№ ${lbPos!.apm!.position})") : "(№ ---)", style: TextStyle(color: lbPos?.apm != null ? getColorOfRank(lbPos!.apm!.position) : null))
|
||||||
}
|
]),
|
||||||
|
TableRow(children: [
|
||||||
@override
|
Text(league.pps != null ? f2.format(league.pps) : "-.--", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: league.pps != null ? getStatColor(league.pps!, averages?.pps, true) : Colors.grey)),
|
||||||
void dispose() {
|
Text(" PPS", style: TextStyle(fontSize: 21, color: league.pps != null ? getStatColor(league.pps!, averages?.pps, true) : Colors.grey)),
|
||||||
super.dispose();
|
if (toCompare != null) Text(" (${comparef2.format(league.pps!-toCompare!.pps!)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: getDifferenceColor(league.pps!-toCompare!.pps!))),
|
||||||
}
|
if (lbPos != null) Text(lbPos?.pps != null ? (lbPos!.pps!.position >= 1000 ? " (${t.top} ${f2.format(lbPos!.pps!.percentage*100)}%)" : " (№ ${lbPos!.pps!.position})") : "(№ ---)", style: TextStyle(color: lbPos?.pps != null ? getColorOfRank(lbPos!.pps!.position) : null))
|
||||||
|
]),
|
||||||
|
TableRow(children: [
|
||||||
@override
|
Text(league.vs != null ? f2.format(league.vs) : "-.--", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: league.vs != null ? getStatColor(league.vs!, averages?.vs, true) : Colors.grey)),
|
||||||
Widget build(BuildContext context) {
|
Text(" VS", style: TextStyle(fontSize: 21, color: league.vs != null ? getStatColor(league.vs!, averages?.vs, true) : Colors.grey)),
|
||||||
final t = Translations.of(context);
|
if (toCompare != null) Text(" (${comparef2.format(league.vs!-toCompare!.vs!)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: getDifferenceColor(league.vs!-toCompare!.vs!))),
|
||||||
String decimalSeparator = f2.symbols.DECIMAL_SEP;
|
if (lbPos != null) Text(lbPos?.vs != null ? (lbPos!.vs!.position >= 1000 ? " (${t.top} ${f2.format(lbPos!.vs!.percentage*100)}%)" : " (№ ${lbPos!.vs!.position})") : "(№ ---)", style: TextStyle(color: lbPos?.vs != null ? getColorOfRank(lbPos!.vs!.position) : null))
|
||||||
List<String> estTRformated = currentTl.estTr != null ? f2.format(currentTl.estTr!.esttr).split(decimalSeparator) : [];
|
])
|
||||||
List<String> estTRaccFormated = currentTl.esttracc != null ? intFDiff.format(currentTl.esttracc!).split(".") : [];
|
],
|
||||||
if (currentTl.gamesPlayed == 0) return Center(child: Text(widget.guest ? t.anonTL : widget.bot ? t.botTL : t.neverPlayedTL, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28), textAlign: TextAlign.center,));
|
),
|
||||||
return LayoutBuilder(builder: (context, constraints) {
|
),
|
||||||
bool bigScreen = constraints.maxWidth >= 768;
|
),
|
||||||
return ListView.builder(
|
GaugetThingy(value: league.winrate, min: 0, max: 1, tickInterval: 0.25, label: "Winrate", sideSize: 128, fractionDigits: 2, moreIsBetter: true, oldValue: toCompare?.winrate, percentileFormat: true, lbPos: lbPos?.winrate),
|
||||||
physics: const ClampingScrollPhysics(),
|
Expanded(
|
||||||
itemCount: 1,
|
child: Center(
|
||||||
itemBuilder: (BuildContext context, int index) {
|
child: Table(
|
||||||
return Column(
|
defaultVerticalAlignment: TableCellVerticalAlignment.baseline,
|
||||||
children: [
|
textBaseline: TextBaseline.alphabetic,
|
||||||
if (widget.showTitle) Text(t.tetraLeague, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
|
defaultColumnWidth:const IntrinsicColumnWidth(),
|
||||||
if (oldTl != null) Text(t.comparingWith(newDate: timestamp(currentTl.timestamp), oldDate: timestamp(oldTl!.timestamp)),
|
children: [
|
||||||
textAlign: TextAlign.center,),
|
TableRow(children: [
|
||||||
if (oldTl != null) RangeSlider(values: _currentRangeValues, max: widget.states.length.toDouble(),
|
//Text("APM: ", style: TextStyle(fontSize: 21)),
|
||||||
labels: RangeLabels(
|
Text(intf.format(league.gamesPlayed), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)),
|
||||||
_currentRangeValues.start.round().toString(),
|
const Text(" Games", style: TextStyle(fontSize: 21)),
|
||||||
_currentRangeValues.end.round().toString(),
|
if (toCompare != null) Text(" (${comparef2.format(league.gamesPlayed-toCompare!.gamesPlayed)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: Colors.grey)),
|
||||||
),
|
if (lbPos != null) Text(lbPos?.gamesPlayed != null ? (lbPos!.gamesPlayed!.position >= 1000 ? " (${t.top} ${f2.format(lbPos!.gamesPlayed!.percentage*100)}%)" : " (№ ${lbPos!.gamesPlayed!.position})") : "(№ ---)", style: TextStyle(color: lbPos?.gamesPlayed != null ? getColorOfRank(lbPos!.gamesPlayed!.position) : null))
|
||||||
onChanged: (RangeValues values) {
|
]),
|
||||||
setState(() {
|
TableRow(children: [
|
||||||
_currentRangeValues = values;
|
//Text("PPS: ", style: TextStyle(fontSize: 21)),
|
||||||
if (values.start.round() == 0){
|
Text(intf.format(league.gamesWon), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)),
|
||||||
currentTl = widget.tl;
|
const Text(" Won", style: TextStyle(fontSize: 21)),
|
||||||
}else{
|
if (toCompare != null) Text(" (${comparef2.format(league.gamesWon-toCompare!.gamesWon)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: Colors.grey)),
|
||||||
currentTl = sortedStates[values.start.round()-1];
|
if (lbPos != null) Text(lbPos?.gamesWon != null ? (lbPos!.gamesWon!.position >= 1000 ? " (${t.top} ${f2.format(lbPos!.gamesWon!.percentage*100)}%)" : " (№ ${lbPos!.gamesWon!.position})") : "(№ ---)", style: TextStyle(color: lbPos?.gamesWon != null ? getColorOfRank(lbPos!.gamesWon!.position) : null))
|
||||||
}
|
]),
|
||||||
if (values.end.round() == 0){
|
TableRow(children: [
|
||||||
oldTl = widget.tl;
|
//Text("VS: ", style: TextStyle(fontSize: 21)),
|
||||||
}else{
|
Tooltip(child: Text("${league.gxe.isNegative ? "---" : f3.format(league.gxe)}", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: league.gxe.isNegative ? Colors.grey : Colors.white)), message: "${f2.format(league.s1tr)} S1 TR"),
|
||||||
oldTl = sortedStates[values.end.round()-1];
|
Tooltip(child: Text(" GXE", style: TextStyle(fontSize: 21, color: league.gxe.isNegative ? Colors.grey : Colors.white)), message: "Glixare"),
|
||||||
}
|
if (toCompare != null) Text(" (${comparef.format(league.gxe-toCompare!.gxe)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: getDifferenceColor(league.gxe-toCompare!.gxe))),
|
||||||
});
|
if (lbPos != null) Text(lbPos?.glixare != null ? (lbPos!.glixare!.position >= 1000 ? " (${t.top} ${f2.format(lbPos!.glixare!.percentage*100)}%)" : " (№ ${lbPos!.glixare!.position})") : "(№ ---)", style: TextStyle(color: lbPos?.glixare != null ? getColorOfRank(lbPos!.glixare!.position) : null))
|
||||||
},
|
]),
|
||||||
),
|
],
|
||||||
TLRatingThingy(userID: widget.userID, tlData: currentTl, oldTl: oldTl, topTR: widget.topTR, lastMatchPlayed: widget.lastMatchPlayed),
|
),
|
||||||
if (currentTl.gamesPlayed > 9) TLProgress(
|
),
|
||||||
tlData: currentTl,
|
),
|
||||||
previousRankTRcutoff: widget.thatRankCutoff,
|
],
|
||||||
previousGlickoCutoff: widget.thatRankCutoffGlicko,
|
),
|
||||||
previousRankTRcutoffTarget: widget.thatRankTarget,
|
],
|
||||||
nextRankTRcutoff: widget.nextRankCutoff,
|
),
|
||||||
nextRankGlickoCutoff: widget.nextRankCutoffGlicko,
|
);
|
||||||
nextRankTRcutoffTarget: widget.nextRankTarget,
|
}
|
||||||
),
|
}
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(8, 16, 8, 48),
|
|
||||||
child: Wrap(
|
|
||||||
direction: Axis.horizontal,
|
|
||||||
alignment: WrapAlignment.center,
|
|
||||||
spacing: 25,
|
|
||||||
crossAxisAlignment: WrapCrossAlignment.start,
|
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
children: [
|
|
||||||
if (currentTl.apm != null) StatCellNum(playerStat: currentTl.apm!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.apm, higherIsBetter: true, oldPlayerStat: oldTl?.apm, pos: widget.lbPositions?.apm, averageStat: widget.averages?.apm),
|
|
||||||
if (currentTl.pps != null) StatCellNum(playerStat: currentTl.pps!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.pps, higherIsBetter: true, oldPlayerStat: oldTl?.pps, pos: widget.lbPositions?.pps, averageStat: widget.averages?.pps, smallDecimal: false),
|
|
||||||
if (currentTl.vs != null) StatCellNum(playerStat: currentTl.vs!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.vs, higherIsBetter: true, oldPlayerStat: oldTl?.vs, pos: widget.lbPositions?.vs, averageStat: widget.averages?.vs),
|
|
||||||
if (currentTl.standingLocal > 0) StatCellNum(playerStat: currentTl.standingLocal, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.lbpc, higherIsBetter: false, oldPlayerStat: oldTl?.standingLocal),
|
|
||||||
StatCellNum(playerStat: currentTl.gamesPlayed, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.gamesPlayed, higherIsBetter: true, oldPlayerStat: oldTl?.gamesPlayed, pos: widget.lbPositions?.gamesPlayed),
|
|
||||||
StatCellNum(playerStat: currentTl.gamesWon, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.gamesWonTL, higherIsBetter: true, oldPlayerStat: oldTl?.gamesWon, pos: widget.lbPositions?.gamesWon),
|
|
||||||
StatCellNum(playerStat: currentTl.winrate * 100, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.winrate, higherIsBetter: true, oldPlayerStat: oldTl != null ? oldTl!.winrate*100 : null, pos: widget.lbPositions?.winrate, averageStat: widget.averages != null ? widget.averages!.winrate * 100 : null),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (currentTl.nerdStats != null)
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Text(t.nerdStats, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(0, 40, 0, 0),
|
|
||||||
child: Wrap(
|
|
||||||
direction: Axis.horizontal,
|
|
||||||
alignment: WrapAlignment.center,
|
|
||||||
spacing: 35,
|
|
||||||
crossAxisAlignment: WrapCrossAlignment.start,
|
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
children: [
|
|
||||||
GaugetNum(playerStat: currentTl.nerdStats!.app, playerStatLabel: t.statCellNum.app, higherIsBetter: true, minimum: 0, maximum: 1, ranges: [
|
|
||||||
GaugeRange(startValue: 0, endValue: 0.2, color: Colors.red),
|
|
||||||
GaugeRange(startValue: 0.2, endValue: 0.4, color: Colors.yellow),
|
|
||||||
GaugeRange(startValue: 0.4, endValue: 0.6, color: Colors.green),
|
|
||||||
GaugeRange(startValue: 0.6, endValue: 0.8, color: Colors.blue),
|
|
||||||
GaugeRange(startValue: 0.8, endValue: 1, color: Colors.purple),
|
|
||||||
], alertWidgets: [
|
|
||||||
Text(t.statCellNum.appDescription),
|
|
||||||
Text("${t.exactValue}: ${currentTl.nerdStats!.app}")
|
|
||||||
], oldPlayerStat: oldTl?.nerdStats?.app, pos: widget.lbPositions?.app,
|
|
||||||
averageStat: widget.averages?.nerdStats?.app),
|
|
||||||
GaugetNum(playerStat: currentTl.nerdStats!.vsapm, playerStatLabel: "VS / APM", higherIsBetter: true, minimum: 1.8, maximum: 2.4, ranges: [
|
|
||||||
GaugeRange(startValue: 1.8, endValue: 2.0, color: Colors.green),
|
|
||||||
GaugeRange(startValue: 2.0, endValue: 2.2, color: Colors.blue),
|
|
||||||
GaugeRange(startValue: 2.2, endValue: 2.4, color: Colors.purple),
|
|
||||||
], alertWidgets: [
|
|
||||||
Text(t.statCellNum.vsapmDescription),
|
|
||||||
Text("${t.exactValue}: ${currentTl.nerdStats!.vsapm}")
|
|
||||||
], oldPlayerStat: oldTl?.nerdStats?.vsapm, pos: widget.lbPositions?.vsapm,
|
|
||||||
averageStat: widget.averages?.nerdStats?.vsapm)
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
|
|
||||||
child: Wrap(
|
|
||||||
direction: Axis.horizontal,
|
|
||||||
alignment: WrapAlignment.center,
|
|
||||||
spacing: 25,
|
|
||||||
crossAxisAlignment: WrapCrossAlignment.start,
|
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
children: [
|
|
||||||
StatCellNum(playerStat: currentTl.nerdStats!.dss, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.dss,
|
|
||||||
pos: widget.lbPositions?.dss,
|
|
||||||
averageStat: widget.averages?.nerdStats?.dss, smallDecimal: false,
|
|
||||||
alertWidgets: [Text(t.statCellNum.dssDescription),
|
|
||||||
Text("${t.formula}: (VS / 100) - (APM / 60)"),
|
|
||||||
Text("${t.exactValue}: ${currentTl.nerdStats!.dss}"),],
|
|
||||||
okText: t.popupActions.ok,
|
|
||||||
higherIsBetter: true,
|
|
||||||
oldPlayerStat: oldTl?.nerdStats?.dss,),
|
|
||||||
StatCellNum(playerStat: currentTl.nerdStats!.dsp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.dsp,
|
|
||||||
pos: widget.lbPositions?.dsp,
|
|
||||||
averageStat: widget.averages?.nerdStats?.dsp, smallDecimal: false,
|
|
||||||
alertWidgets: [Text(t.statCellNum.dspDescription),
|
|
||||||
Text("${t.formula}: DS/S / PPS"),
|
|
||||||
Text("${t.exactValue}: ${currentTl.nerdStats!.dsp}"),],
|
|
||||||
okText: t.popupActions.ok,
|
|
||||||
higherIsBetter: true,
|
|
||||||
oldPlayerStat: oldTl?.nerdStats?.dsp,),
|
|
||||||
StatCellNum(playerStat: currentTl.nerdStats!.appdsp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.appdsp,
|
|
||||||
pos: widget.lbPositions?.appdsp,
|
|
||||||
averageStat: widget.averages?.nerdStats?.appdsp, smallDecimal: false,
|
|
||||||
alertWidgets: [Text(t.statCellNum.appdspDescription),
|
|
||||||
Text("${t.formula}: APP + DS/P"),
|
|
||||||
Text("${t.exactValue}: ${currentTl.nerdStats!.appdsp}"),],
|
|
||||||
okText: t.popupActions.ok,
|
|
||||||
higherIsBetter: true,
|
|
||||||
oldPlayerStat: oldTl?.nerdStats?.appdsp,),
|
|
||||||
StatCellNum(playerStat: currentTl.nerdStats!.cheese, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.cheese,
|
|
||||||
pos: widget.lbPositions?.cheese,
|
|
||||||
alertWidgets: [Text(t.statCellNum.cheeseDescription),
|
|
||||||
Text("${t.formula}: (DS/P * 150) + ((VS/APM - 2) * 50) + (0.6 - APP) * 125"),
|
|
||||||
Text("${t.exactValue}: ${currentTl.nerdStats!.cheese}"),],
|
|
||||||
okText: t.popupActions.ok,
|
|
||||||
higherIsBetter: false,
|
|
||||||
oldPlayerStat: oldTl?.nerdStats?.cheese,),
|
|
||||||
StatCellNum(playerStat: currentTl.nerdStats!.gbe, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.gbe,
|
|
||||||
pos: widget.lbPositions?.gbe,
|
|
||||||
averageStat: widget.averages?.nerdStats?.gbe, smallDecimal: false,
|
|
||||||
alertWidgets: [Text(t.statCellNum.gbeDescription),
|
|
||||||
Text("${t.formula}: APP * DS/P * 2"),
|
|
||||||
Text("${t.exactValue}: ${currentTl.nerdStats!.gbe}"),],
|
|
||||||
okText: t.popupActions.ok,
|
|
||||||
higherIsBetter: true,
|
|
||||||
oldPlayerStat: oldTl?.nerdStats?.gbe,),
|
|
||||||
StatCellNum(playerStat: currentTl.nerdStats!.nyaapp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.nyaapp,
|
|
||||||
pos: widget.lbPositions?.nyaapp,
|
|
||||||
averageStat: widget.averages?.nerdStats?.nyaapp, smallDecimal: false,
|
|
||||||
alertWidgets: [Text(t.statCellNum.nyaappDescription),
|
|
||||||
Text("${t.formula}: APP - 5 * tan(radians((Cheese Index / -30) + 1))"),
|
|
||||||
Text("${t.exactValue}: ${currentTl.nerdStats!.nyaapp}"),],
|
|
||||||
okText: t.popupActions.ok,
|
|
||||||
higherIsBetter: true,
|
|
||||||
oldPlayerStat: oldTl?.nerdStats?.nyaapp,),
|
|
||||||
StatCellNum(playerStat: currentTl.nerdStats!.area, isScreenBig: bigScreen, fractionDigits: 1, playerStatLabel: t.statCellNum.area,
|
|
||||||
pos: widget.lbPositions?.area,
|
|
||||||
averageStat: widget.averages?.nerdStats?.area,
|
|
||||||
alertWidgets: [Text(t.statCellNum.areaDescription),
|
|
||||||
Text("${t.formula}: APM * 1 + PPS * 45 + VS * 0.444 + APP * 185 + DS/S * 175 + DS/P * 450 + Garbage Effi * 315"),
|
|
||||||
Text("${t.exactValue}: ${currentTl.nerdStats!.area}"),],
|
|
||||||
okText: t.popupActions.ok,
|
|
||||||
higherIsBetter: true,
|
|
||||||
oldPlayerStat: oldTl?.nerdStats?.area,)
|
|
||||||
]),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (currentTl.estTr != null)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(8, 20, 8, 20),
|
|
||||||
child: Container(
|
|
||||||
height: 70,
|
|
||||||
constraints: const BoxConstraints(maxWidth: 500),
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
Positioned(
|
|
||||||
left: 0,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(t.statCellNum.estOfTR, style: const TextStyle(height: 0.1),),
|
|
||||||
RichText(
|
|
||||||
text: TextSpan(
|
|
||||||
text: estTRformated[0],
|
|
||||||
style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 36 : 30, fontWeight: FontWeight.w500, color: Colors.white),
|
|
||||||
children: [TextSpan(text: decimalSeparator+estTRformated[1], style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100))]
|
|
||||||
),
|
|
||||||
),
|
|
||||||
RichText(text: TextSpan(
|
|
||||||
text: "",
|
|
||||||
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey, height: 0.5),
|
|
||||||
children: [
|
|
||||||
if (oldTl?.estTr?.esttr != null) TextSpan(text: comparef.format(currentTl.estTr!.esttr - oldTl!.estTr!.esttr), style: TextStyle(
|
|
||||||
color: oldTl!.estTr!.esttr > currentTl.estTr!.esttr ? Colors.redAccent : Colors.greenAccent
|
|
||||||
),),
|
|
||||||
if (oldTl?.estTr?.esttr != null && widget.lbPositions?.estTr != null) const TextSpan(text: " • "),
|
|
||||||
if (widget.lbPositions?.estTr != null) TextSpan(text: widget.lbPositions!.estTr!.position >= 1000 ? "${t.top} ${f2.format(widget.lbPositions!.estTr!.percentage*100)}%" : "№${widget.lbPositions!.estTr!.position}", style: TextStyle(color: getColorOfRank(widget.lbPositions!.estTr!.position))),
|
|
||||||
if (widget.lbPositions?.estTr != null || oldTl?.estTr?.esttr != null) const TextSpan(text: " • "),
|
|
||||||
TextSpan(text: "Glicko: ${f2.format(currentTl.estTr!.estglicko)}")
|
|
||||||
]
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
right: 0,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
Text(t.statCellNum.accOfEst, style: const TextStyle(height: 0.1),),
|
|
||||||
RichText(
|
|
||||||
text: TextSpan(
|
|
||||||
text: (currentTl.esttracc != null && currentTl.bestRank != "z") ? estTRaccFormated[0] : "---",
|
|
||||||
style: TextStyle(fontFamily: "Eurostile Round", fontSize: bigScreen ? 36 : 30, fontWeight: FontWeight.w500, color: Colors.white),
|
|
||||||
children: [
|
|
||||||
TextSpan(text: (currentTl.esttracc != null && currentTl.bestRank != "z") ? decimalSeparator+estTRaccFormated[1] : ".---", style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100))
|
|
||||||
]
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if ((oldTl?.esttracc != null || widget.lbPositions != null) && currentTl.bestRank != "z") RichText(text: TextSpan(
|
|
||||||
text: "",
|
|
||||||
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey, height: 0.5),
|
|
||||||
children: [
|
|
||||||
if (oldTl?.esttracc != null) TextSpan(text: comparef.format(currentTl.esttracc! - oldTl!.esttracc!), style: TextStyle(
|
|
||||||
color: oldTl!.esttracc! > currentTl.esttracc! ? Colors.redAccent : Colors.greenAccent
|
|
||||||
),),
|
|
||||||
if (oldTl?.esttracc != null && widget.lbPositions?.accOfEst != null) const TextSpan(text: " • "),
|
|
||||||
if (widget.lbPositions?.accOfEst != null) TextSpan(text: widget.lbPositions!.accOfEst!.position >= 1000 ? "${t.top} ${f2.format(widget.lbPositions!.accOfEst!.percentage*100)}%" : "№${widget.lbPositions!.accOfEst!.position}", style: TextStyle(color: getColorOfRank(widget.lbPositions!.accOfEst!.position)))
|
|
||||||
]
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
if (currentTl.nerdStats != null) Graphs(currentTl.apm!, currentTl.pps!, currentTl.vs!, currentTl.nerdStats!, currentTl.playstyle!)
|
|
||||||
]
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,429 +1,354 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'dart:math';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:tetra_stats/data_objects/tetrio_player.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
||||||
import 'package:tetra_stats/main.dart' show teto;
|
import 'package:tetra_stats/data_objects/tetrio_constants.dart';
|
||||||
import 'package:tetra_stats/views/compare_view.dart';
|
import 'package:tetra_stats/data_objects/tetrio_player.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
import 'package:tetra_stats/utils/text_shadow.dart';
|
import 'package:tetra_stats/main.dart';
|
||||||
import 'dart:developer' as developer;
|
import 'package:tetra_stats/utils/copy_to_clipboard.dart';
|
||||||
import 'package:tetra_stats/widgets/stat_sell_num.dart';
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
import 'package:tetra_stats/utils/relative_timestamps.dart';
|
||||||
|
import 'package:tetra_stats/utils/text_shadow.dart';
|
||||||
const Map<int, double> xpTableScuffed = { // level: xp required
|
import 'package:tetra_stats/views/compare_view_tiles.dart';
|
||||||
05000: 67009018.4885772,
|
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
||||||
10000: 763653437.386,
|
import 'package:transparent_image/transparent_image.dart';
|
||||||
15000: 2337651144.54149,
|
|
||||||
20000: 4572735210.50902,
|
class UserThingy extends StatefulWidget {
|
||||||
25000: 7376166347.04745,
|
final TetrioPlayer player;
|
||||||
30000: 10693620096.2168,
|
final bool showStateTimestamp;
|
||||||
40000: 18728882739.482,
|
final bool initIsTracking;
|
||||||
50000: 28468683855.2853
|
final Function setState;
|
||||||
};
|
|
||||||
|
const UserThingy({super.key, required this.player, required this.initIsTracking, required this.showStateTimestamp, required this.setState});
|
||||||
Future<void> copyToClipboard(String text) async {
|
|
||||||
await Clipboard.setData(ClipboardData(text: text));
|
@override
|
||||||
}
|
State<UserThingy> createState() => _UserThingyState();
|
||||||
|
}
|
||||||
class UserThingy extends StatelessWidget {
|
|
||||||
final TetrioPlayer player;
|
class _UserThingyState extends State<UserThingy> with SingleTickerProviderStateMixin {
|
||||||
final bool showStateTimestamp;
|
late AnimationController _addToTrackAnimController;
|
||||||
final Function setState;
|
late Animation _addToTrackAnim;
|
||||||
|
|
||||||
const UserThingy({super.key, required this.player, required this.showStateTimestamp, required this.setState});
|
@override
|
||||||
|
void initState(){
|
||||||
@override
|
_addToTrackAnimController = AnimationController(
|
||||||
Widget build(BuildContext context) {
|
value: widget.initIsTracking ? 1.0 : 0.0,
|
||||||
final t = Translations.of(context);
|
duration: Durations.extralong4,
|
||||||
return LayoutBuilder(builder: (context, constraints) {
|
vsync: this,
|
||||||
bool bigScreen = constraints.maxWidth > 768;
|
);
|
||||||
double bannerHeight = bigScreen ? 240 : 120;
|
_addToTrackAnim = new Tween(
|
||||||
double pfpHeight = 128;
|
begin: 0.0,
|
||||||
int xpTableID = 0;
|
end: 1.0,
|
||||||
|
).animate(new CurvedAnimation(
|
||||||
while (player.xp > xpTableScuffed.values.toList()[xpTableID]) {
|
parent: _addToTrackAnimController,
|
||||||
xpTableID++;
|
curve: Cubic(.15,-0.40,.86,-0.39),
|
||||||
}
|
reverseCurve: Cubic(0,.99,.99,1.01)
|
||||||
|
));
|
||||||
return Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
super.initState();
|
||||||
children: [
|
}
|
||||||
Stack(
|
|
||||||
alignment: Alignment.topCenter,
|
@override
|
||||||
children: [
|
void dispose() {
|
||||||
if (player.bannerRevision != null)
|
_addToTrackAnimController.dispose();
|
||||||
Image.network(kIsWeb ? "https://ts.dan63.by/oskware_bridge.php?endpoint=TetrioBanner&user=${player.userId}&rv=${player.bannerRevision}" : "https://tetr.io/user-content/banners/${player.userId}.jpg?rv=${player.bannerRevision}",
|
super.dispose();
|
||||||
fit: BoxFit.cover,
|
}
|
||||||
height: bannerHeight,
|
|
||||||
errorBuilder: (context, error, stackTrace) {
|
Color roleColor(String role){
|
||||||
developer.log("Error with building banner image", name: "main_view", error: error, stackTrace: stackTrace);
|
switch (role){
|
||||||
return Container();
|
case "sysop":
|
||||||
},
|
return const Color.fromARGB(255, 23, 165, 133);
|
||||||
),
|
case "admin":
|
||||||
Padding(
|
return const Color.fromARGB(255, 255, 78, 138);
|
||||||
padding: EdgeInsets.fromLTRB(8, player.bannerRevision != null ? bannerHeight / 1.4 : 0, 8, bigScreen ? 16 : 0),
|
case "mod":
|
||||||
child: Row(
|
return const Color.fromARGB(255, 204, 128, 242);
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
case "halfmod":
|
||||||
children: [
|
return const Color.fromARGB(255, 95, 118, 254);
|
||||||
Column(
|
case "bot":
|
||||||
children: [
|
return const Color.fromARGB(255, 60, 93, 55);
|
||||||
Wrap(
|
case "banned":
|
||||||
direction: bigScreen ? Axis.horizontal : Axis.vertical,
|
return const Color.fromARGB(255, 248, 28, 28);
|
||||||
alignment: WrapAlignment.spaceBetween,
|
default:
|
||||||
spacing: bigScreen ? 25 : 0,
|
return Colors.white10;
|
||||||
//mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
}
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
}
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
children: [
|
String fontStyle(int length){
|
||||||
Wrap(
|
if (length < 10) return "Eurostile Round Extended";
|
||||||
direction: bigScreen ? Axis.horizontal : Axis.vertical,
|
else if (length < 13) return "Eurostile Round";
|
||||||
alignment: WrapAlignment.start,
|
else return "Eurostile Round Condensed";
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
}
|
||||||
spacing: bigScreen ? 20 : 0,
|
|
||||||
clipBehavior: Clip.hardEdge,
|
@override
|
||||||
children: [
|
Widget build(BuildContext context) {
|
||||||
Stack(
|
final t = Translations.of(context);
|
||||||
alignment: Alignment.topCenter,
|
return LayoutBuilder(builder: (context, constraints) {
|
||||||
children: [
|
double pfpHeight = 128;
|
||||||
ClipRRect(
|
int xpTableID = 0;
|
||||||
borderRadius: BorderRadius.circular(1000),
|
|
||||||
child: player.role == "banned"
|
while (widget.player.xp > xpTableScuffed.values.toList()[xpTableID]) {
|
||||||
? Image.asset("res/avatars/tetrio_banned.png", fit: BoxFit.fitHeight, height: pfpHeight,)
|
xpTableID++;
|
||||||
: player.avatarRevision != null
|
}
|
||||||
? Image.network(kIsWeb ? "https://ts.dan63.by/oskware_bridge.php?endpoint=TetrioProfilePicture&user=${player.userId}&rv=${player.avatarRevision}" : "https://tetr.io/user-content/avatars/${player.userId}.jpg?rv=${player.avatarRevision}",
|
|
||||||
// TODO: osk banner can cause memory leak
|
return Card(
|
||||||
fit: BoxFit.fitHeight, height: 128, errorBuilder: (context, error, stackTrace) {
|
clipBehavior: Clip.antiAlias,
|
||||||
developer.log("Error with building profile picture", name: "main_view", error: error, stackTrace: stackTrace);
|
child: Column(
|
||||||
return Image.asset("res/avatars/tetrio_anon.png", fit: BoxFit.fitHeight, height: pfpHeight);
|
children: [
|
||||||
})
|
Padding(
|
||||||
: Image.asset("res/avatars/tetrio_anon.png", fit: BoxFit.fitHeight, height: pfpHeight),
|
padding: const EdgeInsets.only(bottom: 4.0),
|
||||||
),
|
child: Container(
|
||||||
if (player.verified)
|
constraints: const BoxConstraints(maxWidth: 960),
|
||||||
Padding(
|
height: widget.player.bannerRevision != null ? 218.0 : 138.0,
|
||||||
padding: EdgeInsets.fromLTRB(pfpHeight - 22, pfpHeight - 32, 0, 0),
|
child: Stack(
|
||||||
child: const Icon(Icons.verified),
|
//clipBehavior: Clip.none,
|
||||||
)
|
children: [
|
||||||
],
|
// TODO: osk banner can cause memory leak
|
||||||
),
|
if (widget.player.bannerRevision != null) FadeInImage.memoryNetwork(image: kIsWeb ? "https://ts.dan63.by/oskware_bridge.php?endpoint=TetrioBanner&user=${widget.player.userId}&rv=${widget.player.bannerRevision}" : "https://tetr.io/user-content/banners/${widget.player.userId}.jpg?rv=${widget.player.bannerRevision}",
|
||||||
Column(
|
placeholder: kTransparentImage,
|
||||||
children: [
|
fit: BoxFit.cover,
|
||||||
Text(player.username,
|
height: 120,
|
||||||
style: TextStyle(
|
fadeInCurve: Easing.standard, fadeInDuration: Durations.long4
|
||||||
fontFamily: "Eurostile Round Extended",
|
),
|
||||||
fontSize: bigScreen ? 42 : 28,
|
Positioned(
|
||||||
shadows: textShadow,
|
top: widget.player.bannerRevision != null ? 90.0 : 10.0,
|
||||||
)),
|
left: 16.0,
|
||||||
TextButton(
|
child: ClipRRect(
|
||||||
child: Text(player.userId, style: const TextStyle(fontFamily: "Eurostile Round Condensed", fontSize: 14)),
|
borderRadius: BorderRadius.circular(1000),
|
||||||
onPressed: () {
|
child: widget.player.role == "banned"
|
||||||
copyToClipboard(player.userId);
|
? Image.asset("res/avatars/tetrio_banned.png", fit: BoxFit.fitHeight, height: pfpHeight,)
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.copiedToClipboard)));
|
: widget.player.avatarRevision != null
|
||||||
}),
|
? FadeInImage.memoryNetwork(image: kIsWeb ? "https://ts.dan63.by/oskware_bridge.php?endpoint=TetrioProfilePicture&user=${widget.player.userId}&rv=${widget.player.avatarRevision}" : "https://tetr.io/user-content/avatars/${widget.player.userId}.jpg?rv=${widget.player.avatarRevision}",
|
||||||
],
|
fit: BoxFit.fitHeight, height: 128, placeholder: kTransparentImage, fadeInCurve: Easing.emphasizedDecelerate, fadeInDuration: Durations.long4)
|
||||||
),
|
: Image.asset("res/avatars/tetrio_anon.png", fit: BoxFit.fitHeight, height: pfpHeight),
|
||||||
],
|
)
|
||||||
),
|
),
|
||||||
showStateTimestamp
|
Positioned(
|
||||||
? Text(t.fetchDate(date: timestamp(player.state)))
|
top: widget.player.bannerRevision != null ? 120.0 : 40.0,
|
||||||
: Wrap(direction: Axis.horizontal, alignment: WrapAlignment.center, spacing: 25, crossAxisAlignment: WrapCrossAlignment.start, children: [
|
left: 160.0,
|
||||||
FutureBuilder(
|
child: Tooltip(
|
||||||
future: teto.isPlayerTracking(player.userId),
|
message: "${widget.player.userId}\n(Click to copy user ID)",
|
||||||
builder: (context, snapshot) {
|
child: RichText(text: TextSpan(text: widget.player.username, style: TextStyle(
|
||||||
switch (snapshot.connectionState) {
|
fontFamily: fontStyle(widget.player.username.length),
|
||||||
case ConnectionState.none:
|
fontSize: 28,
|
||||||
case ConnectionState.waiting:
|
),
|
||||||
case ConnectionState.active:
|
recognizer: TapGestureRecognizer()..onTap = (){
|
||||||
case ConnectionState.done:
|
copyToClipboard(widget.player.userId);
|
||||||
if (snapshot.data != null && snapshot.data!) {
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.copiedToClipboard)));
|
||||||
return Column(
|
}
|
||||||
children: [
|
)
|
||||||
IconButton(
|
)
|
||||||
icon: const Icon(
|
),
|
||||||
Icons.person_remove,
|
),
|
||||||
shadows: <Shadow>[
|
Positioned(
|
||||||
Shadow(
|
top: widget.player.bannerRevision != null ? 160.0 : 80.0,
|
||||||
offset: Offset(0.0, 0.0),
|
left: 160.0,
|
||||||
blurRadius: 3.0,
|
child: Row(
|
||||||
color: Colors.black,
|
children: [
|
||||||
),
|
Padding(
|
||||||
Shadow(
|
padding: const EdgeInsets.only(right: 4.0),
|
||||||
offset: Offset(0.0, 0.0),
|
child: Chip(label: Text(widget.player.role.toUpperCase(), style: const TextStyle(shadows: textShadow),), padding: const EdgeInsets.all(0.0), color: WidgetStatePropertyAll(roleColor(widget.player.role))),
|
||||||
blurRadius: 8.0,
|
),
|
||||||
color: Colors.black,
|
RichText(
|
||||||
),
|
text: TextSpan(
|
||||||
],),
|
style: const TextStyle(fontFamily: "Eurostile Round"),
|
||||||
onPressed: () {
|
children:
|
||||||
teto.deletePlayerToTrack(player.userId).then((value) => setState());
|
[
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.stoppedBeingTracked)));
|
if (widget.player.friendCount > 0) const WidgetSpan(child: Icon(Icons.person), alignment: PlaceholderAlignment.middle, baseline: TextBaseline.alphabetic),
|
||||||
},
|
if (widget.player.friendCount > 0) TextSpan(text: "${intf.format(widget.player.friendCount)} "),
|
||||||
),
|
if (widget.player.supporterTier > 0) WidgetSpan(child: Icon(widget.player.supporterTier > 1 ? Icons.star : Icons.star_border, color: widget.player.supporterTier > 1 ? Colors.yellowAccent : Colors.white), alignment: PlaceholderAlignment.middle, baseline: TextBaseline.alphabetic),
|
||||||
Text(t.stopTracking, textAlign: TextAlign.center)
|
if (widget.player.supporterTier > 0) TextSpan(text: widget.player.supporterTier.toString(), style: TextStyle(color: widget.player.supporterTier > 1 ? Colors.yellowAccent : Colors.white)),
|
||||||
],
|
]
|
||||||
);
|
)
|
||||||
} else {
|
)
|
||||||
return Column(
|
],
|
||||||
children: [
|
),
|
||||||
IconButton(
|
),
|
||||||
icon: const Icon(
|
Positioned(
|
||||||
Icons.person_add,
|
top: widget.player.bannerRevision != null ? 193.0 : 113.0,
|
||||||
shadows: <Shadow>[
|
left: 160.0,
|
||||||
Shadow(
|
child: SizedBox(
|
||||||
offset: Offset(0.0, 0.0),
|
width: 270,
|
||||||
blurRadius: 3.0,
|
child: RichText(
|
||||||
color: Colors.black,
|
text: TextSpan(
|
||||||
),
|
style: const TextStyle(fontFamily: "Eurostile Round"),
|
||||||
Shadow(
|
children: [
|
||||||
offset: Offset(0.0, 0.0),
|
TextSpan(text: timestamp(widget.player.registrationTime), style: const TextStyle(color: Colors.grey)),
|
||||||
blurRadius: 8.0,
|
if (widget.player.country != null) TextSpan(text: " • ${t.countries[widget.player.country]}")
|
||||||
color: Colors.black,
|
]
|
||||||
),
|
)
|
||||||
],),
|
),
|
||||||
onPressed: () {
|
)
|
||||||
teto.addPlayerToTrack(player).then((value) => setState());
|
),
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.becameTracked)));
|
Positioned(
|
||||||
},
|
top: widget.player.bannerRevision != null ? 126.0 : 46.0,
|
||||||
),
|
right: 16.0,
|
||||||
Text(t.track, textAlign: TextAlign.center)
|
child: RichText(
|
||||||
],
|
textAlign: TextAlign.end,
|
||||||
);
|
text: TextSpan(
|
||||||
}
|
style: const TextStyle(fontFamily: "Eurostile Round"),
|
||||||
}
|
children: [
|
||||||
}),
|
TextSpan(text: "Level ${(widget.player.level.isNegative || widget.player.level.isNaN) ? "---" : intf.format(widget.player.level.floor())}", style: TextStyle(decoration: (widget.player.level.isNegative || widget.player.level.isNaN) ? null : TextDecoration.underline, decorationColor: Colors.white70, decorationStyle: TextDecorationStyle.dotted, color: (widget.player.level.isNegative || widget.player.level.isNaN) ? Colors.grey : Colors.white), recognizer: (widget.player.level.isNegative || widget.player.level.isNaN) ? null : TapGestureRecognizer()?..onTap = (){
|
||||||
Column(
|
showDialog(
|
||||||
children: [
|
context: context,
|
||||||
IconButton(
|
builder: (BuildContext context) => AlertDialog(
|
||||||
icon: const Icon(
|
title: Text("Level ${intf.format(widget.player.level.floor())}", textAlign: TextAlign.center),
|
||||||
Icons.balance,
|
content: SingleChildScrollView(
|
||||||
shadows: <Shadow>[
|
child: ListBody(children: [
|
||||||
Shadow(
|
Text(
|
||||||
offset: Offset(0.0, 0.0),
|
"${NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2).format(widget.player.xp)} XP",
|
||||||
blurRadius: 3.0,
|
style: const TextStyle(fontFamily: "Eurostile Round", fontWeight: FontWeight.bold)
|
||||||
color: Colors.black,
|
),
|
||||||
),
|
Padding(
|
||||||
Shadow(
|
padding: const EdgeInsets.fromLTRB(0, 8, 0, 8),
|
||||||
offset: Offset(0.0, 0.0),
|
child: SfLinearGauge(
|
||||||
blurRadius: 8.0,
|
minimum: 0,
|
||||||
color: Colors.black,
|
maximum: 1,
|
||||||
),
|
interval: 1,
|
||||||
],),
|
ranges: [
|
||||||
onPressed: () {
|
LinearGaugeRange(startValue: 0, endValue: widget.player.level - widget.player.level.floor(), color: Colors.cyanAccent),
|
||||||
Navigator.push(
|
LinearGaugeRange(startValue: 0, endValue: (widget.player.xp / xpTableScuffed.values.toList()[xpTableID]), color: Colors.redAccent, position: LinearElementPosition.cross)
|
||||||
context,
|
],
|
||||||
MaterialPageRoute(
|
showTicks: true,
|
||||||
builder: (context) => CompareView(greenSide: [player, null, null], redSide: const [null, null, null], greenMode: Mode.player, redMode: Mode.player),
|
showLabels: false
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
Text("${t.statCellNum.xpProgress}: ${((widget.player.level - widget.player.level.floor()) * 100).toStringAsFixed(2)} %"),
|
||||||
),
|
Text("${t.statCellNum.xpFrom0ToLevel(n: xpTableScuffed.keys.toList()[xpTableID])}: ${((widget.player.xp / xpTableScuffed.values.toList()[xpTableID]) * 100).toStringAsFixed(2)} % (${NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0).format(xpTableScuffed.values.toList()[xpTableID] - widget.player.xp)} ${t.statCellNum.xpLeft})")
|
||||||
Text(t.compare, textAlign: TextAlign.center)
|
]
|
||||||
],
|
),
|
||||||
)
|
),
|
||||||
])
|
actions: <Widget>[
|
||||||
]),
|
TextButton(
|
||||||
],
|
child: const Text("OK"),
|
||||||
),
|
onPressed: () {Navigator.of(context).pop();}
|
||||||
],
|
)
|
||||||
),
|
]
|
||||||
),
|
)
|
||||||
],
|
);
|
||||||
),
|
}),
|
||||||
if (!["banned", "p1nkl0bst3r"].contains(player.role))
|
const TextSpan(text:"\n"),
|
||||||
Wrap(
|
TextSpan(text: widget.player.gameTime.isNegative ? "-h --m" : playtime(widget.player.gameTime), style: TextStyle(color: widget.player.gameTime.isNegative ? Colors.grey : Colors.white, decoration: widget.player.gameTime.isNegative ? null : TextDecoration.underline, decorationColor: Colors.white70, decorationStyle: TextDecorationStyle.dotted), recognizer: !widget.player.gameTime.isNegative ? (TapGestureRecognizer()..onTap = (){
|
||||||
// mainAxisSize: MainAxisSize.min,
|
Duration accountAge = DateTime.timestamp().difference(widget.player.registrationTime);
|
||||||
direction: Axis.horizontal,
|
Duration avgGametimeADay = Duration(microseconds: (widget.player.gameTime.inMicroseconds / accountAge.inDays).floor());
|
||||||
alignment: WrapAlignment.center,
|
showDialog(
|
||||||
spacing: 25,
|
context: context,
|
||||||
crossAxisAlignment: WrapCrossAlignment.start,
|
builder: (BuildContext context) => AlertDialog(
|
||||||
clipBehavior: Clip.hardEdge, // hard WHAT???
|
title: Text(t.exactGametime, textAlign: TextAlign.center),
|
||||||
children: [
|
content: SingleChildScrollView(
|
||||||
if (!player.level.isNegative && !player.level.isNaN) StatCellNum(
|
child: Column(
|
||||||
playerStat: player.level,
|
children: [
|
||||||
playerStatLabel: t.statCellNum.xpLevel,
|
RichText(text: TextSpan(
|
||||||
isScreenBig: bigScreen,
|
style: TextStyle(fontFamily: "Eurostile Round", color: Colors.white, fontSize: 28),
|
||||||
alertWidgets: [
|
children: [
|
||||||
Text(
|
TextSpan(text: "${intf.format(widget.player.gameTime.inHours)}"),
|
||||||
"${NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2).format(player.xp)} XP",
|
TextSpan(text: ":${nonsecs.format(widget.player.gameTime.inMinutes%60)}:${nonsecs.format(widget.player.gameTime.inSeconds%60)}"),
|
||||||
style: const TextStyle(fontFamily: "Eurostile Round", fontWeight: FontWeight.bold)
|
TextSpan(text: ".${nonsecs3.format(widget.player.gameTime.inMicroseconds%1000000)}", style: TextStyle(fontSize: 14))
|
||||||
),
|
]
|
||||||
Padding(
|
)),
|
||||||
padding: const EdgeInsets.fromLTRB(0, 8, 0, 8),
|
Text("${playtime(avgGametimeADay)} a day in average"),
|
||||||
child: SfLinearGauge(
|
Padding(
|
||||||
minimum: 0,
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
maximum: 1,
|
child: Text("It's ${f4.format(widget.player.gameTime.inSeconds/31536000)} years,"),
|
||||||
interval: 1,
|
),
|
||||||
ranges: [
|
Text("or ${f4.format(widget.player.gameTime.inSeconds/2628000)} months,"),
|
||||||
LinearGaugeRange(startValue: 0, endValue: player.level - player.level.floor(), color: Colors.cyanAccent),
|
Text("or ${f4.format(widget.player.gameTime.inSeconds/86400)} days,"),
|
||||||
LinearGaugeRange(startValue: 0, endValue: (player.xp / xpTableScuffed.values.toList()[xpTableID]), color: Colors.redAccent, position: LinearElementPosition.cross)
|
Text("or ${f2.format(widget.player.gameTime.inMilliseconds/60000)} minutes,"),
|
||||||
],
|
Text("or ${intf.format(widget.player.gameTime.inSeconds)} seconds"),
|
||||||
// markerPointers: [LinearShapePointer(value: player.level - player.level.floor(), position: LinearElementPosition.inside, shapeType: LinearShapePointerType.triangle, color: Colors.white, height: 20)],
|
]
|
||||||
showTicks: true,
|
),
|
||||||
showLabels: false
|
),
|
||||||
),
|
actions: <Widget>[
|
||||||
),
|
TextButton(
|
||||||
Text("${t.statCellNum.xpProgress}: ${((player.level - player.level.floor()) * 100).toStringAsFixed(2)} %"),
|
child: const Text("OK"),
|
||||||
Text("${t.statCellNum.xpFrom0ToLevel(n: xpTableScuffed.keys.toList()[xpTableID])}: ${((player.xp / xpTableScuffed.values.toList()[xpTableID]) * 100).toStringAsFixed(2)} % (${NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0).format(xpTableScuffed.values.toList()[xpTableID] - player.xp)} ${t.statCellNum.xpLeft})")],
|
onPressed: () {Navigator.of(context).pop();}
|
||||||
okText: t.popupActions.ok,
|
)
|
||||||
higherIsBetter: true,
|
]
|
||||||
),
|
)
|
||||||
if (player.gameTime >= Duration.zero)
|
);
|
||||||
StatCellNum(
|
}) : null),
|
||||||
playerStat: player.gameTime.inHours,
|
const TextSpan(text:"\n"),
|
||||||
playerStatLabel: t.statCellNum.hoursPlayed,
|
TextSpan(text: widget.player.gamesWon > -1 ? intf.format(widget.player.gamesWon) : "---", style: TextStyle(color: widget.player.gamesWon > -1 ? Colors.white : Colors.grey)),
|
||||||
isScreenBig: bigScreen,
|
TextSpan(text: "/${widget.player.gamesPlayed > -1 ? intf.format(widget.player.gamesPlayed) : "---"}", style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
|
||||||
alertTitle: t.exactGametime,
|
]
|
||||||
alertWidgets: [Text(player.gameTime.toString(), style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 24),)],
|
)
|
||||||
higherIsBetter: true,),
|
)
|
||||||
if (player.gamesPlayed >= 0)
|
)
|
||||||
StatCellNum(
|
],
|
||||||
playerStat: player.gamesPlayed,
|
),
|
||||||
isScreenBig: bigScreen,
|
),
|
||||||
playerStatLabel: t.statCellNum.onlineGames,
|
),
|
||||||
higherIsBetter: true,),
|
Row(
|
||||||
if (player.gamesWon >= 0)
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
StatCellNum(
|
children: [
|
||||||
playerStat: player.gamesWon,
|
Expanded(
|
||||||
isScreenBig: bigScreen,
|
child: AnimatedBuilder(
|
||||||
playerStatLabel: t.statCellNum.gamesWon,
|
animation: _addToTrackAnim,
|
||||||
higherIsBetter: true,),
|
builder: (context, child) {
|
||||||
if (player.friendCount > 0)
|
double firstButtonPosition = 0+(_addToTrackAnim.value as double)*25;
|
||||||
StatCellNum(
|
double secondButtonPosition = -25+(_addToTrackAnim.value as double)*25;
|
||||||
playerStat: player.friendCount,
|
double firstButtonOpacity = 1-(_addToTrackAnim.value as double)*2;
|
||||||
isScreenBig: bigScreen,
|
double secondButtonOpacity = _addToTrackAnim.value*2-1;
|
||||||
playerStatLabel: t.statCellNum.friends,
|
return ElevatedButton.icon(
|
||||||
higherIsBetter: true,),
|
onPressed: (){
|
||||||
],
|
_addToTrackAnimController.value == 1 ? teto.deletePlayerToTrack(widget.player.userId) : teto.addPlayerToTrack(widget.player);
|
||||||
),
|
_addToTrackAnim.isCompleted ? _addToTrackAnimController.reverse() : _addToTrackAnimController.forward();
|
||||||
if (player.role == "banned") Text(
|
},
|
||||||
t.bigRedBanned,
|
icon: _addToTrackAnim.value < 0.5 ? Opacity(
|
||||||
textAlign: TextAlign.center,
|
opacity: min(1, firstButtonOpacity),
|
||||||
style: TextStyle(
|
child: Transform.translate(
|
||||||
fontFamily: "Eurostile Round Extended",
|
offset: Offset(0, _addToTrackAnim.status == AnimationStatus.forward ? firstButtonPosition*4 : firstButtonPosition),
|
||||||
fontWeight: FontWeight.w900,
|
child: Transform.rotate(
|
||||||
color: Colors.red,
|
angle:_addToTrackAnim.status == AnimationStatus.forward ? (_addToTrackAnim.value as double)*2 : 0,
|
||||||
fontSize: bigScreen ? 60 : 45,
|
child: const Icon(Icons.person_add),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (player.role == "p1nkl0bst3r") Text(
|
) : Container(
|
||||||
t.p1nkl0bst3rAlert,
|
transform: Matrix4.translationValues(secondButtonPosition*5, -secondButtonPosition*25, 0),
|
||||||
textAlign: TextAlign.center,
|
child: Opacity(
|
||||||
style: const TextStyle(
|
opacity: max(0, min(1, secondButtonOpacity)),
|
||||||
fontFamily: "Eurostile Round",
|
child: Transform.rotate(
|
||||||
fontSize: 16,
|
angle:_addToTrackAnim.status == AnimationStatus.reverse ? (1-_addToTrackAnim.value as double)*-20 : 0,
|
||||||
)
|
child: const Icon(Icons.person_remove)
|
||||||
),
|
)
|
||||||
if (player.badstanding != null && player.badstanding!)
|
)
|
||||||
Text(
|
),
|
||||||
t.bigRedBadStanding,
|
label: _addToTrackAnim.value < 0.5 ? Container(
|
||||||
textAlign: TextAlign.center,
|
transform: Matrix4.translationValues(0, firstButtonPosition, 0),
|
||||||
style: TextStyle(
|
child: Opacity(
|
||||||
fontFamily: "Eurostile Round Extended",
|
opacity: max(min(1, firstButtonOpacity), 0),
|
||||||
fontWeight: FontWeight.w900,
|
child: Text(_addToTrackAnimController.isAnimating && _addToTrackAnim.status == AnimationStatus.forward ? "Done!" : "Track")
|
||||||
color: Colors.red,
|
)
|
||||||
fontSize: bigScreen ? 60 : 45,
|
) : Container(
|
||||||
),
|
transform: Matrix4.translationValues(0, secondButtonPosition, 0),
|
||||||
),
|
child: Opacity(
|
||||||
if (player.role != "p1nkl0bst3r") Padding(
|
opacity: max(0, min(1, secondButtonOpacity)),
|
||||||
padding: EdgeInsets.only(top: bigScreen ? 8 : 0),
|
child: Text(_addToTrackAnimController.isAnimating && _addToTrackAnim.status == AnimationStatus.reverse ? "Done! " : "Stop tracking")
|
||||||
child: Row(
|
)
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
),
|
||||||
children: [
|
style: const ButtonStyle(shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.only(bottomLeft: Radius.circular(12.0))))));
|
||||||
Expanded(
|
},
|
||||||
child: RichText(
|
)),
|
||||||
textAlign: TextAlign.center,
|
Expanded(
|
||||||
text: TextSpan(text: "", style: const TextStyle(
|
child: ElevatedButton.icon(
|
||||||
fontFamily: "Eurostile Round",
|
onPressed: (){
|
||||||
fontSize: 16,
|
Navigator.push(context, MaterialPageRoute(
|
||||||
color: Colors.white,
|
builder: (context) => CompareView(widget.player),
|
||||||
),
|
),
|
||||||
children: [
|
);
|
||||||
if (player.country != null) TextSpan(text: "${t.countries[player.country]} • "),
|
},
|
||||||
TextSpan(text: "${t.playerRole[player.role]}${t.playerRoleAccount}${t.created} ${timestamp(player.registrationTime)}"),
|
icon: const Icon(Icons.balance),
|
||||||
if (player.supporterTier > 0) const TextSpan(text: " • "),
|
label: Text(t.compare),
|
||||||
if (player.supporterTier > 0) WidgetSpan(child: Icon(player.supporterTier > 1 ? Icons.star : Icons.star_border, color: player.supporterTier > 1 ? Colors.yellowAccent : Colors.white), alignment: PlaceholderAlignment.middle, baseline: TextBaseline.alphabetic),
|
style: const ButtonStyle(shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.only(bottomRight: Radius.circular(12.0)))))
|
||||||
if (player.supporterTier > 0) TextSpan(text: player.supporterTier.toString(), style: TextStyle(color: player.supporterTier > 1 ? Colors.yellowAccent : Colors.white))
|
)
|
||||||
]
|
)
|
||||||
)
|
],
|
||||||
),
|
)
|
||||||
// Text(
|
],
|
||||||
// "${player.country != null ? "${t.countries[player.country]} • " : ""}${t.playerRole[player.role]}${t.playerRoleAccount}${player.registrationTime == null ? t.wasFromBeginning : '${t.created} ${dateFormat.format(player.registrationTime!)}'}${player.botmaster != null ? " ${t.botCreatedBy} ${player.botmaster}" : ""} • ${player.supporterTier == 0 ? t.notSupporter : t.supporter(tier: player.supporterTier)}",
|
),
|
||||||
// textAlign: TextAlign.center,
|
);
|
||||||
// style: const TextStyle(
|
});
|
||||||
// fontFamily: "Eurostile Round",
|
}
|
||||||
// fontSize: 16,
|
}
|
||||||
// )),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Wrap(
|
|
||||||
direction: Axis.horizontal,
|
|
||||||
alignment: WrapAlignment.center,
|
|
||||||
spacing: 25,
|
|
||||||
crossAxisAlignment: WrapCrossAlignment.start,
|
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
children: [
|
|
||||||
for (var badge in player.badges)
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: Text(
|
|
||||||
badge.label,
|
|
||||||
style: const TextStyle(fontFamily: "Eurostile Round Extended"),
|
|
||||||
),
|
|
||||||
content: SingleChildScrollView(
|
|
||||||
child: ListBody(
|
|
||||||
children: [
|
|
||||||
Wrap(
|
|
||||||
direction: Axis.horizontal,
|
|
||||||
alignment: WrapAlignment.center,
|
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
|
||||||
spacing: 25,
|
|
||||||
children: [
|
|
||||||
Image.asset("res/tetrio_badges/${badge.badgeId}.png"),
|
|
||||||
Text(badge.ts != null
|
|
||||||
? t.obtainDate(date: timestamp(badge.ts!))
|
|
||||||
: t.assignedManualy),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
actions: <Widget>[
|
|
||||||
TextButton(
|
|
||||||
child: Text(t.popupActions.ok),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
tooltip: badge.label,
|
|
||||||
icon: Image.asset(
|
|
||||||
"res/tetrio_badges/${badge.badgeId}.png",
|
|
||||||
height: 32,
|
|
||||||
width: 32,
|
|
||||||
errorBuilder: (context, error, stackTrace) {
|
|
||||||
developer.log("Error with building $badge", name: "main_view", error: error, stackTrace: stackTrace);
|
|
||||||
return Image.network(
|
|
||||||
kIsWeb ? "https://ts.dan63.by/oskware_bridge.php?endpoint=TetrioBadge&badge=${badge.badgeId}" : "https://tetr.io/res/badges/${badge.badgeId}.png",
|
|
||||||
height: 32,
|
|
||||||
width: 32,
|
|
||||||
errorBuilder:(context, error, stackTrace) {
|
|
||||||
return Image.asset("res/icons/kagari.png", height: 32, width: 32);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
))
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,284 +1,151 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
|
||||||
import 'package:tetra_stats/data_objects/record_extras.dart';
|
import 'package:tetra_stats/data_objects/record_extras.dart';
|
||||||
import 'package:tetra_stats/data_objects/record_single.dart';
|
import 'package:tetra_stats/data_objects/record_single.dart';
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
|
||||||
import 'package:tetra_stats/utils/colors_functions.dart';
|
import 'package:tetra_stats/utils/colors_functions.dart';
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
import 'package:tetra_stats/utils/relative_timestamps.dart';
|
import 'package:tetra_stats/widgets/gauget_thingy.dart';
|
||||||
import 'package:tetra_stats/utils/text_shadow.dart';
|
|
||||||
import 'package:tetra_stats/widgets/finesse_thingy.dart';
|
|
||||||
import 'package:tetra_stats/widgets/gauget_num.dart';
|
|
||||||
import 'package:tetra_stats/widgets/graphs.dart';
|
|
||||||
import 'package:tetra_stats/widgets/lineclears_thingy.dart';
|
|
||||||
import 'package:tetra_stats/widgets/stat_sell_num.dart';
|
|
||||||
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
||||||
|
|
||||||
class ZenithThingy extends StatefulWidget{
|
class ZenithThingy extends StatelessWidget{
|
||||||
final RecordSingle? record;
|
final RecordSingle? zenith;
|
||||||
final bool switchable;
|
final bool old;
|
||||||
final bool initEXvalue;
|
|
||||||
final RecordSingle? recordEX;
|
|
||||||
final Function? parentZenithToggle;
|
|
||||||
|
|
||||||
const ZenithThingy({super.key, this.record, this.recordEX, this.switchable = true, this.parentZenithToggle, this.initEXvalue = false});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<ZenithThingy> createState() => _ZenithThingyState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ZenithThingyState extends State<ZenithThingy> {
|
|
||||||
late RecordSingle? record;
|
|
||||||
bool ex = false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState(){
|
|
||||||
ex = widget.initEXvalue;
|
|
||||||
|
|
||||||
super.initState();
|
|
||||||
if (widget.switchable){
|
|
||||||
record = (ex ? widget.recordEX : widget.record);
|
|
||||||
}else{
|
|
||||||
record = widget.record;
|
|
||||||
ex = widget.record!.gamemode == "zenithex";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const ZenithThingy({super.key, required this.zenith, this.old = false});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return LayoutBuilder(builder: (context, constraints){
|
return Card(
|
||||||
bool bigScreen = constraints.maxWidth > 768;
|
child: Padding(
|
||||||
if (record == null) {
|
padding: const EdgeInsets.fromLTRB(8.0, 0.0, 8.0, 0.0),
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Text("${t.quickPlay}${ex ? " ${t.expert}" : ""}", style: const TextStyle(height: 0.1, fontFamily: "Eurostile Round Extended", fontSize: 18)),
|
|
||||||
RichText(text: TextSpan(
|
|
||||||
text: "--- m",
|
|
||||||
style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 36 : 32, fontWeight: FontWeight.w500, color: Colors.grey),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(onPressed: (){
|
|
||||||
if (ex){
|
|
||||||
ex = false;
|
|
||||||
}else{
|
|
||||||
ex = true;
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
if (widget.parentZenithToggle != null) widget.parentZenithToggle!();
|
|
||||||
record = ex ? widget.recordEX : widget.record;
|
|
||||||
});
|
|
||||||
}, child: Text(ex ? "Switch to normal" : "Switch to Expert")),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Padding(padding: const EdgeInsets.only(top: 8.0),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text("${t.quickPlay}${ex ? " ${t.expert}" : ""}", style: const TextStyle(height: 0.1, fontFamily: "Eurostile Round Extended", fontSize: 18)),
|
Row(
|
||||||
RichText(text: TextSpan(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
text: "${f2.format(record!.stats.zenith!.altitude)} m",
|
|
||||||
style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 36 : 32, fontWeight: FontWeight.w500, color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if ((record!.extras as ZenithExtras).mods.isNotEmpty) RichText(
|
|
||||||
text: TextSpan(
|
|
||||||
text: "",
|
|
||||||
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.white),
|
|
||||||
children: [
|
|
||||||
TextSpan(text: "${t.withMods}: "),
|
|
||||||
for (String mod in (record!.extras as ZenithExtras).mods) TextSpan(text: "${mod.toUpperCase()} "),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
),
|
|
||||||
RichText(
|
|
||||||
text: TextSpan(
|
|
||||||
text: "",
|
|
||||||
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey),
|
|
||||||
children: [
|
|
||||||
if (record!.rank != -1) TextSpan(text: "№ ${intf.format(record!.rank)}", style: TextStyle(color: getColorOfRank(record!.rank))),
|
|
||||||
if (record!.rank != -1) const TextSpan(text: " • "),
|
|
||||||
if (record!.countryRank != -1) TextSpan(text: "№ ${intf.format(record!.countryRank)} local", style: TextStyle(color: getColorOfRank(record!.countryRank))),
|
|
||||||
if (record!.countryRank != -1) const TextSpan(text: " • "),
|
|
||||||
TextSpan(text: timestamp(widget.record!.timestamp)),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (widget.switchable) TextButton(onPressed: (){
|
|
||||||
if (ex){
|
|
||||||
ex = false;
|
|
||||||
}else{
|
|
||||||
ex = true;
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
if (widget.parentZenithToggle != null) widget.parentZenithToggle!();
|
|
||||||
record = ex ? widget.recordEX : widget.record;
|
|
||||||
});
|
|
||||||
}, child: Text(ex ? "Switch to normal" : "Switch to Expert")),
|
|
||||||
Wrap(
|
|
||||||
alignment: WrapAlignment.center,
|
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
|
||||||
spacing: 20,
|
|
||||||
children: [
|
children: [
|
||||||
StatCellNum(playerStat: record!.aggregateStats.apm, playerStatLabel: t.statCellNum.apm, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: true),
|
Column(
|
||||||
StatCellNum(playerStat: record!.aggregateStats.pps, playerStatLabel: t.statCellNum.pps, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: false),
|
|
||||||
StatCellNum(playerStat: record!.aggregateStats.vs, playerStatLabel: t.statCellNum.vs, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: true),
|
|
||||||
StatCellNum(playerStat: record!.stats.kills, playerStatLabel: "KO's", isScreenBig: bigScreen, higherIsBetter: true),
|
|
||||||
StatCellNum(playerStat: record!.stats.cps, playerStatLabel: "Climb speed\n(Peak: ${f2.format(record!.stats.zenith!.peakrank)})", fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true),
|
|
||||||
StatCellNum(playerStat: record!.stats.topBtB, playerStatLabel: "Top B2B\nchain", isScreenBig: bigScreen, higherIsBetter: true)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
FinesseThingy(record?.stats.finesse, record?.stats.finessePercentage),
|
|
||||||
LineclearsThingy(record!.stats.clears, record!.stats.lines, record!.stats.holds, record!.stats.tSpins),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
|
||||||
child: SizedBox(
|
|
||||||
width: 300,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Stack(
|
RichText(
|
||||||
alignment: AlignmentDirectional.bottomStart,
|
text: TextSpan(
|
||||||
children: [
|
text: zenith != null ? "${f2.format(zenith!.stats.zenith!.altitude)} m" : "--- m",
|
||||||
const Text("T", style: TextStyle(
|
style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, fontWeight: FontWeight.w500, color: (zenith != null && !old) ? Colors.white : Colors.grey),
|
||||||
fontStyle: FontStyle.italic,
|
),
|
||||||
fontSize: 65,
|
|
||||||
height: 1.2,
|
|
||||||
)),
|
|
||||||
const Positioned(left: 25, top: 20, child: Text("otal time", style: TextStyle(fontFamily: "Eurostile Round Extended"))),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 10.0),
|
|
||||||
child: Text(getMoreNormalTime(record!.stats.finalTime), style: const TextStyle(
|
|
||||||
shadows: textShadow,
|
|
||||||
fontFamily: "Eurostile Round Extended",
|
|
||||||
fontSize: 36,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Colors.white
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
Table(
|
if (zenith != null) RichText(
|
||||||
columnWidths: const {
|
text: TextSpan(
|
||||||
0: FixedColumnWidth(36)
|
text: "",
|
||||||
},
|
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey),
|
||||||
children: [
|
children: [
|
||||||
const TableRow(
|
if (zenith!.rank != -1) TextSpan(text: "№ ${intf.format(zenith!.rank)}", style: TextStyle(color: getColorOfRank(zenith!.rank))),
|
||||||
children: [
|
if (zenith!.rank != -1) const TextSpan(text: " • "),
|
||||||
Text("Floor"),
|
if (zenith!.countryRank != -1) TextSpan(text: "№ ${intf.format(zenith!.countryRank)} local", style: TextStyle(color: getColorOfRank(zenith!.countryRank))),
|
||||||
Text("Split", textAlign: TextAlign.right),
|
if (zenith!.countryRank != -1) const TextSpan(text: " • "),
|
||||||
Text("Total", textAlign: TextAlign.right),
|
TextSpan(text: timestamp(zenith!.timestamp)),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
for (int i = 0; i < record!.stats.zenith!.splits.length; i++) TableRow(
|
|
||||||
children: [
|
|
||||||
Text((i+1).toString()),
|
|
||||||
Text(record!.stats.zenith!.splits[i] != Duration.zero ? getMoreNormalTime(record!.stats.zenith!.splits[i]-(i-1 != -1 ? record!.stats.zenith!.splits[i-1] : Duration.zero)) : "--:--.---", textAlign: TextAlign.right),
|
|
||||||
Text(record!.stats.zenith!.splits[i] != Duration.zero ? getMoreNormalTime(record!.stats.zenith!.splits[i]) : "--:--.---", textAlign: TextAlign.right),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
if (zenith != null && (zenith!.extras as ZenithExtras).mods.isNotEmpty) Container(width: 16.0),
|
||||||
),
|
if (zenith != null && (zenith!.extras as ZenithExtras).mods.isNotEmpty) for (String mod in (zenith!.extras as ZenithExtras).mods) Image.asset("res/icons/${mod}.png", height: 64.0)
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Text(t.nerdStats, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(0, 40, 0, 0),
|
|
||||||
child: Wrap(
|
|
||||||
direction: Axis.horizontal,
|
|
||||||
alignment: WrapAlignment.center,
|
|
||||||
spacing: 35,
|
|
||||||
crossAxisAlignment: WrapCrossAlignment.start,
|
|
||||||
//clipBehavior: Clip.hardEdge,
|
|
||||||
children: [
|
|
||||||
GaugetNum(playerStat: record!.aggregateStats.nerdStats.app, playerStatLabel: t.statCellNum.app, higherIsBetter: true, minimum: 0, maximum: 1, ranges: [
|
|
||||||
GaugeRange(startValue: 0, endValue: 0.2, color: Colors.red),
|
|
||||||
GaugeRange(startValue: 0.2, endValue: 0.4, color: Colors.yellow),
|
|
||||||
GaugeRange(startValue: 0.4, endValue: 0.6, color: Colors.green),
|
|
||||||
GaugeRange(startValue: 0.6, endValue: 0.8, color: Colors.blue),
|
|
||||||
GaugeRange(startValue: 0.8, endValue: 1, color: Colors.purple),
|
|
||||||
], alertWidgets: [
|
|
||||||
Text(t.statCellNum.appDescription),
|
|
||||||
Text("${t.exactValue}: ${record!.aggregateStats.nerdStats.app}")
|
|
||||||
]),
|
|
||||||
GaugetNum(playerStat: record!.aggregateStats.nerdStats.vsapm, playerStatLabel: "VS / APM", higherIsBetter: true, minimum: 1.8, maximum: 2.4, ranges: [
|
|
||||||
GaugeRange(startValue: 1.8, endValue: 2.0, color: Colors.green),
|
|
||||||
GaugeRange(startValue: 2.0, endValue: 2.2, color: Colors.blue),
|
|
||||||
GaugeRange(startValue: 2.2, endValue: 2.4, color: Colors.purple),
|
|
||||||
], alertWidgets: [
|
|
||||||
Text(t.statCellNum.vsapmDescription),
|
|
||||||
Text("${t.exactValue}: ${record!.aggregateStats.nerdStats.vsapm}")
|
|
||||||
])
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
|
|
||||||
child: Wrap(
|
|
||||||
direction: Axis.horizontal,
|
|
||||||
alignment: WrapAlignment.center,
|
|
||||||
spacing: 25,
|
|
||||||
crossAxisAlignment: WrapCrossAlignment.start,
|
|
||||||
//clipBehavior: Clip.hardEdge,
|
|
||||||
children: [
|
|
||||||
StatCellNum(playerStat: record!.aggregateStats.nerdStats.dss, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.dss,
|
|
||||||
alertWidgets: [Text(t.statCellNum.dssDescription),
|
|
||||||
Text("${t.formula}: (VS / 100) - (APM / 60)"),
|
|
||||||
Text("${t.exactValue}: ${record!.aggregateStats.nerdStats.dss}"),],
|
|
||||||
okText: t.popupActions.ok,
|
|
||||||
higherIsBetter: true,),
|
|
||||||
StatCellNum(playerStat: record!.aggregateStats.nerdStats.dsp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.dsp,
|
|
||||||
alertWidgets: [Text(t.statCellNum.dspDescription),
|
|
||||||
Text("${t.formula}: DS/S / PPS"),
|
|
||||||
Text("${t.exactValue}: ${record!.aggregateStats.nerdStats.dsp}"),],
|
|
||||||
okText: t.popupActions.ok,
|
|
||||||
higherIsBetter: true),
|
|
||||||
StatCellNum(playerStat: record!.aggregateStats.nerdStats.appdsp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.appdsp,
|
|
||||||
alertWidgets: [Text(t.statCellNum.appdspDescription),
|
|
||||||
Text("${t.formula}: APP + DS/P"),
|
|
||||||
Text("${t.exactValue}: ${record!.aggregateStats.nerdStats.appdsp}"),],
|
|
||||||
okText: t.popupActions.ok,
|
|
||||||
higherIsBetter: true),
|
|
||||||
StatCellNum(playerStat: record!.aggregateStats.nerdStats.cheese, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.cheese,
|
|
||||||
alertWidgets: [Text(t.statCellNum.cheeseDescription),
|
|
||||||
Text("${t.formula}: (DS/P * 150) + ((VS/APM - 2) * 50) + (0.6 - APP) * 125"),
|
|
||||||
Text("${t.exactValue}: ${record!.aggregateStats.nerdStats.cheese}"),],
|
|
||||||
okText: t.popupActions.ok,
|
|
||||||
higherIsBetter: false),
|
|
||||||
StatCellNum(playerStat: record!.aggregateStats.nerdStats.gbe, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.gbe,
|
|
||||||
alertWidgets: [Text(t.statCellNum.gbeDescription),
|
|
||||||
Text("${t.formula}: APP * DS/P * 2"),
|
|
||||||
Text("${t.exactValue}: ${record!.aggregateStats.nerdStats.gbe}"),],
|
|
||||||
okText: t.popupActions.ok,
|
|
||||||
higherIsBetter: true),
|
|
||||||
StatCellNum(playerStat: record!.aggregateStats.nerdStats.nyaapp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.nyaapp,
|
|
||||||
alertWidgets: [Text(t.statCellNum.nyaappDescription),
|
|
||||||
Text("${t.formula}: APP - 5 * tan(radians((Cheese Index / -30) + 1))"),
|
|
||||||
Text("${t.exactValue}: ${record!.aggregateStats.nerdStats.nyaapp}")],
|
|
||||||
okText: t.popupActions.ok,
|
|
||||||
higherIsBetter: true),
|
|
||||||
StatCellNum(playerStat: record!.aggregateStats.nerdStats.area, isScreenBig: bigScreen, fractionDigits: 1, playerStatLabel: t.statCellNum.area,
|
|
||||||
alertWidgets: [Text(t.statCellNum.areaDescription),
|
|
||||||
Text("${t.formula}: APM * 1 + PPS * 45 + VS * 0.444 + APP * 185 + DS/S * 175 + DS/P * 450 + Garbage Effi * 315"),
|
|
||||||
Text("${t.exactValue}: ${record!.aggregateStats.nerdStats.area}"),],
|
|
||||||
okText: t.popupActions.ok,
|
|
||||||
higherIsBetter: true)
|
|
||||||
]),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Padding(
|
if (zenith != null) Row(
|
||||||
padding: const EdgeInsets.only(top: 16.0),
|
children: [
|
||||||
child: Graphs(record!.aggregateStats.apm, record!.aggregateStats.pps, record!.aggregateStats.vs, record!.aggregateStats.nerdStats, record!.aggregateStats.playstyle),
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Table(
|
||||||
|
defaultColumnWidth:const IntrinsicColumnWidth(),
|
||||||
|
children: [
|
||||||
|
TableRow(children: [
|
||||||
|
Text(f2.format(zenith!.aggregateStats.apm), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)),
|
||||||
|
const Text(" APM", style: TextStyle(fontSize: 21)),
|
||||||
|
]),
|
||||||
|
TableRow(children: [
|
||||||
|
Text(f2.format(zenith!.aggregateStats.pps), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)),
|
||||||
|
const Text(" PPS", style: TextStyle(fontSize: 21)),
|
||||||
|
]),
|
||||||
|
TableRow(children: [
|
||||||
|
Text(f2.format(zenith!.aggregateStats.vs), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)),
|
||||||
|
const Text(" VS", style: TextStyle(fontSize: 21)),
|
||||||
|
])
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GaugetThingy(value: zenith!.stats.cps, min: 0, max: 12, tickInterval: 3, label: "Climb\nSpeed", subString: "Peak: ${f2.format(zenith!.stats.zenith!.peakrank)}", sideSize: 128, fractionDigits: 2, moreIsBetter: true),
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Table(
|
||||||
|
defaultColumnWidth:const IntrinsicColumnWidth(),
|
||||||
|
children: [
|
||||||
|
TableRow(children: [
|
||||||
|
Text(intf.format(zenith!.stats.kills), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)),
|
||||||
|
const Text(" KO's", style: TextStyle(fontSize: 21))
|
||||||
|
]),
|
||||||
|
TableRow(children: [
|
||||||
|
Text(zenith!.stats.topBtB.toString(), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)),
|
||||||
|
const Text(" B2B", style: TextStyle(fontSize: 21))
|
||||||
|
]),
|
||||||
|
TableRow(children: [
|
||||||
|
Text(zenith!.stats.garbage.maxspike_nomult.toString(), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)),
|
||||||
|
const Text(" Top spike", style: TextStyle(fontSize: 21))
|
||||||
|
])
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
) else Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Table(
|
||||||
|
defaultColumnWidth: IntrinsicColumnWidth(),
|
||||||
|
children: [
|
||||||
|
const TableRow(children: [
|
||||||
|
Text("-.--", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: Colors.grey)),
|
||||||
|
Text(" APM", style: TextStyle(fontSize: 21, color: Colors.grey)),
|
||||||
|
]),
|
||||||
|
const TableRow(children: [
|
||||||
|
Text("-.--", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: Colors.grey)),
|
||||||
|
Text(" PPS", style: TextStyle(fontSize: 21, color: Colors.grey)),
|
||||||
|
]),
|
||||||
|
const TableRow(children: [
|
||||||
|
Text("-.--", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: Colors.grey)),
|
||||||
|
Text(" VS", style: TextStyle(fontSize: 21, color: Colors.grey)),
|
||||||
|
])
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GaugetThingy(value: null, min: 0, max: 12, tickInterval: 3, label: "Climb\nSpeed", subString: "Peak: ---", sideSize: 128, fractionDigits: 0, moreIsBetter: true),
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Table(
|
||||||
|
defaultColumnWidth: IntrinsicColumnWidth(),
|
||||||
|
children: [
|
||||||
|
const TableRow(children: [
|
||||||
|
Text("---", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: Colors.grey)),
|
||||||
|
Text(" KO's", style: TextStyle(fontSize: 21, color: Colors.grey))
|
||||||
|
]),
|
||||||
|
const TableRow(children: [
|
||||||
|
Text("---", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: Colors.grey)),
|
||||||
|
Text(" B2B", style: TextStyle(fontSize: 21, color: Colors.grey))
|
||||||
|
]),
|
||||||
|
const TableRow(children: [
|
||||||
|
Text("---", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: Colors.grey)),
|
||||||
|
Text(" Top spike", style: TextStyle(fontSize: 21, color: Colors.grey))
|
||||||
|
])
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
)
|
)
|
||||||
],
|
]
|
||||||
)
|
),
|
||||||
);
|
)
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
Binary file not shown.
After Width: | Height: | Size: 81 KiB |
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
Loading…
Reference in New Issue