diff --git a/lib/data_objects/tetrio_constants.dart b/lib/data_objects/tetrio_constants.dart index 3d9d127..0588125 100644 --- a/lib/data_objects/tetrio_constants.dart +++ b/lib/data_objects/tetrio_constants.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; const int currentSeason = 2; +final DateTime sprintAndBlitzRelevance = DateTime(2024, 8, 25); const double noTrRd = 60.9; const double apmWeight = 1; const double ppsWeight = 45; @@ -12,6 +13,18 @@ const double appdspWeight = 140; const double vsapmWeight = 60; const double cheeseWeight = 1.25; const double gbeWeight = 315; + +const Map 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 ranks = [ "d", "d+", diff --git a/lib/main.dart b/lib/main.dart index 9ff7a57..d05dfac 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,19 +7,12 @@ import 'dart:developer' as developer; import 'package:package_info_plus/package_info_plus.dart'; import 'package:shared_preferences/shared_preferences.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:sqflite_common_ffi/sqflite_ffi.dart'; import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart'; import 'package:tetra_stats/gen/strings.g.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; -import 'package:tetra_stats/views/main_view_tiles.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:tetra_stats/views/main_view.dart'; import 'package:go_router/go_router.dart'; late final PackageInfo packageInfo; @@ -72,44 +65,6 @@ final router = GoRouter( GoRoute( path: "/", 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 path: "/u/:userId", diff --git a/lib/utils/copy_to_clipboard.dart b/lib/utils/copy_to_clipboard.dart new file mode 100644 index 0000000..727a6c8 --- /dev/null +++ b/lib/utils/copy_to_clipboard.dart @@ -0,0 +1,5 @@ +import 'package:flutter/services.dart'; + +Future copyToClipboard(String text) async { + await Clipboard.setData(ClipboardData(text: text)); +} \ No newline at end of file diff --git a/lib/utils/numers_formats.dart b/lib/utils/numers_formats.dart index f359edb..3e72b3d 100644 --- a/lib/utils/numers_formats.dart +++ b/lib/utils/numers_formats.dart @@ -2,6 +2,7 @@ import 'package:intl/intl.dart'; import 'package:tetra_stats/gen/strings.g.dart'; final NumberFormat compareIntf = NumberFormat("+#,###;-#,###")..maximumFractionDigits = 0; +final NumberFormat fDiff = NumberFormat("+#,###.####;-#,###.####"); final NumberFormat comparef = NumberFormat("+#,###.###;-#,###.###")..maximumFractionDigits = 3; final NumberFormat comparef2 = NumberFormat("+#,###.##;-#,###.##")..maximumFractionDigits = 2; final NumberFormat intf = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0); diff --git a/lib/views/calc_view.dart b/lib/views/calc_view.dart deleted file mode 100644 index 603e4d6..0000000 --- a/lib/views/calc_view.dart +++ /dev/null @@ -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 createState() => CalcState(); -} - -class CalcState extends State { - 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))); - } -} diff --git a/lib/views/compare_view.dart b/lib/views/compare_view.dart deleted file mode 100644 index 8e78493..0000000 --- a/lib/views/compare_view.dart +++ /dev/null @@ -1,1871 +0,0 @@ -// ignore_for_file: use_build_context_synchronously - -import 'dart:io'; -import 'dart:math'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'package:tetra_stats/data_objects/summaries.dart'; -import 'package:tetra_stats/data_objects/tetra_league.dart'; -import 'package:tetra_stats/data_objects/tetrio_constants.dart'; -import 'package:tetra_stats/data_objects/tetrio_zen.dart'; -import 'package:tetra_stats/gen/strings.g.dart'; -import 'package:tetra_stats/main.dart' show teto; -import 'package:tetra_stats/utils/relative_timestamps.dart'; -import 'package:tetra_stats/widgets/vs_graphs.dart'; -import 'package:window_manager/window_manager.dart'; - -enum Mode{ - player, - stats, - averages -} -Mode greenSideMode = Mode.player; -List theGreenSide = [null, null, null]; // TetrioPlayer?, List>?, Summary -Mode redSideMode = Mode.player; -List theRedSide = [null, null, null]; -final DateFormat dateFormat = DateFormat.yMd(LocaleSettings.currentLocale.languageCode).add_Hm(); -var numbersReg = RegExp(r'\d+(\.\d*)*'); -late String oldWindowTitle; - -class CompareView extends StatefulWidget { - final List greenSide; - final List redSide; - final Mode greenMode; - final Mode redMode; - const CompareView({super.key, required this.greenSide, required this.redSide, required this.greenMode, required this.redMode}); - - @override - State createState() => CompareState(); -} - -class CompareState extends State { - late ScrollController _scrollController; - - @override - void initState() { - theGreenSide = widget.greenSide; - fetchGreenSide(widget.greenSide[0].userId); - if (widget.redSide[0] != null) fetchRedSide(widget.redSide[0].userId); - _scrollController = ScrollController(); - if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS){ - windowManager.getTitle().then((value) => oldWindowTitle = value); - } - super.initState(); - } - - @override - void dispose(){ - theGreenSide = [null, null, null]; - greenSideMode = Mode.player; - theRedSide = [null, null, null]; - redSideMode = Mode.player; - if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) windowManager.setTitle(oldWindowTitle); - super.dispose(); - } - - void fetchRedSide(String user) async { - try { - if (user.startsWith("\$avg")){ - try{ - //var average = (await teto.fetchTLLeaderboard()).getAverageOfRank(user.substring(4).toLowerCase())[0]; - //Summaries summary = Summaries("avg${user.substring(4).toLowerCase()}", average, TetrioZen(level: 0, score: 0)); - redSideMode = Mode.averages; - //theRedSide = [null, null, summary]; - return setState(() {}); - }on Exception { - if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.compareViewWrongValue(value: user)))); - return; - } - } - var tearDownToNumbers = numbersReg.allMatches(user); - if (tearDownToNumbers.length == 3) { - redSideMode = Mode.stats; - var threeNumbers = tearDownToNumbers.toList(); - double apm = double.parse(threeNumbers[0][0]!); - double pps = double.parse(threeNumbers[1][0]!); - double vs = double.parse(threeNumbers[2][0]!); - theRedSide = [null, - null, - Summaries(user, TetraLeague( - id: "", - timestamp: DateTime.now(), - apm: apm, - pps: pps, - vs: vs, - rd: noTrRd, - gamesPlayed: -1, - gamesWon: -1, - bestRank: "z", - decaying: true, - tr: -1, - gxe: -1, - rank: "z", - percentileRank: "z", - percentile: 1, - standing: -1, - standingLocal: -1, - nextAt: -1, - prevAt: -1, season: currentSeason), TetrioZen(level: 0, score: 0))]; - return setState(() {}); - } - var player = await teto.fetchPlayer(user); - Summaries summary = await teto.fetchSummaries(player.userId); - redSideMode = Mode.player; - //late List states; - // List>? dStates = >[]; - // try{ - // states = await teto.getPlayer(player.userId); - // for (final TetrioPlayer state in states) { - // dStates.add(DropdownMenuItem( - // value: state, child: Text(dateFormat.format(state.state)))); - // } - // dStates.firstWhere((element) => element.value == player, orElse: () { - // dStates?.add(DropdownMenuItem( - // value: player, child: Text(t.mostRecentOne))); - // return DropdownMenuItem( - // value: player, child: Text(t.mostRecentOne)); - // },); - // }on Exception { - // dStates = null; - // } - theRedSide = [player, null, summary]; - } on Exception { - if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.compareViewWrongValue(value: user)))); - } - _justUpdate(); - } - - void changeRedSide(TetraLeague user) { - setState(() { - //theRedSide[0] = user; - theRedSide[2].league = user; - }); - } - - void fetchGreenSide(String user) async { - try { - if (user.startsWith("\$avg")){ - try{ - //var average = (await teto.fetchTLLeaderboard()).getAverageOfRank(user.substring(4).toLowerCase())[0]; - //Summaries summary = Summaries("avg${user.substring(4).toLowerCase()}", average, TetrioZen(level: 0, score: 0)); - greenSideMode = Mode.averages; - //theGreenSide = [null, null, summary]; - return setState(() {}); - }on Exception { - if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Falied to assign $user"))); - return; - } - } - var tearDownToNumbers = numbersReg.allMatches(user); - if (tearDownToNumbers.length == 3) { - greenSideMode = Mode.stats; - var threeNumbers = tearDownToNumbers.toList(); - double apm = double.parse(threeNumbers[0][0]!); - double pps = double.parse(threeNumbers[1][0]!); - double vs = double.parse(threeNumbers[2][0]!); - theGreenSide = [null, - null, - Summaries(user, TetraLeague( - id: "", - timestamp: DateTime.now(), - apm: apm, - pps: pps, - vs: vs, - rd: noTrRd, - gamesPlayed: -1, - gamesWon: -1, - bestRank: "z", - decaying: true, - tr: -1, - gxe: -1, - rank: "z", - percentileRank: "z", - percentile: 1, - standing: -1, - standingLocal: -1, - nextAt: -1, - prevAt: -1, season: currentSeason), TetrioZen(level: 0, score: 0))]; - return setState(() {}); - } - var player = await teto.fetchPlayer(user); - Summaries summary = await teto.fetchSummaries(player.userId); - greenSideMode = Mode.player; - // late List states; - // List>? dStates = >[]; - // try{ - // states = await teto.getPlayer(player.userId); - // for (final TetrioPlayer state in states) { - // dStates.add(DropdownMenuItem( - // value: state, child: Text(dateFormat.format(state.state)))); - // } - // dStates.firstWhere((element) => element.value == player, orElse: () { - // dStates?.add(DropdownMenuItem( - // value: player, child: Text(t.mostRecentOne))); - // return DropdownMenuItem( - // value: player, child: Text(t.mostRecentOne)); - // },); - // }on Exception { - // dStates = null; - // } - theGreenSide = [player, null, summary]; - } on Exception { - if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Falied to assign $user"))); - } - _justUpdate(); - } - - void changeGreenSide(TetraLeague user) { - setState(() { - //theGreenSide[0] = user; - theGreenSide[2].league = user; - }); - } - - double getWinrateByTR(double yourGlicko, double yourRD, double notyourGlicko,double notyourRD) { - return ((1 / - (1 + pow(10, - (notyourGlicko - yourGlicko) / - (400 * sqrt(1 + (3 * pow(0.0057564273, 2) * - (pow(yourRD, 2) + pow(notyourRD, 2)) / pow(pi, 2) - ))) - ) - ) - )); - } - - void _justUpdate() { - setState(() {}); - } - - @override - Widget build(BuildContext context) { - final t = Translations.of(context); - bool bigScreen = MediaQuery.of(context).size.width > 768; - String titleGreenSide; - String titleRedSide; - switch (greenSideMode){ - case Mode.player: - titleGreenSide = theGreenSide[0] != null ? theGreenSide[0].username.toUpperCase() : "???"; - break; - case Mode.stats: - titleGreenSide = "${theGreenSide[2].league.apm} APM, ${theGreenSide[2].league.pps} PPS, ${theGreenSide[2].league.vs} VS"; - break; - case Mode.averages: - titleGreenSide = t.averageXrank(rankLetter: theGreenSide[2].league.rank.toUpperCase()); - break; - } - switch (redSideMode){ - case Mode.player: - titleRedSide = theRedSide[0] != null ? theRedSide[0].username.toUpperCase() : "???"; - break; - case Mode.stats: - titleRedSide = "${theRedSide[2].league.apm} APM, ${theRedSide[2].league.pps} PPS, ${theRedSide[2].league.vs} VS"; - break; - case Mode.averages: - titleRedSide = t.averageXrank(rankLetter: theRedSide[2].league.rank.toUpperCase()); - break; - } - windowManager.setTitle("Tetra Stats: $titleGreenSide ${t.vs} $titleRedSide"); - return Scaffold( - appBar: AppBar(title: Text("$titleGreenSide ${t.vs} $titleRedSide")), - backgroundColor: Colors.black, - body: SingleChildScrollView( - controller: _scrollController, - physics: const AlwaysScrollableScrollPhysics(), - child: Center( - child: Container( - constraints: const BoxConstraints(maxWidth: 768), - child: Column(children: [ - Padding( - padding: const EdgeInsets.fromLTRB(16, 16, 16, 32), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Container( - decoration: const BoxDecoration( - gradient: LinearGradient( - colors: [Colors.green, Colors.transparent], - begin: Alignment.bottomCenter, - end: Alignment.topCenter, - stops: [0.0, 0.4], - )), - child: Padding( - padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), - child: PlayerSelector( - data: theGreenSide, - mode: greenSideMode, - fetch: fetchGreenSide, - change: changeGreenSide, - updateState: _justUpdate, - ), - ), - ), - ), - const Padding( - padding: EdgeInsets.only(top: 16), - child: Text("VS"), - ), - Expanded( - child: Container( - decoration: const BoxDecoration( - gradient: LinearGradient( - colors: [Colors.red, Colors.transparent], - begin: Alignment.bottomCenter, - end: Alignment.topCenter, - stops: [0.0, 0.4], - )), - child: Padding( - padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), - child: PlayerSelector( - data: theRedSide, - mode: redSideMode, - fetch: fetchRedSide, - change: changeRedSide, - updateState: _justUpdate, - ), - ), - ), - ), - ], - ), - ), - const Divider(), - if (!listEquals(theGreenSide, [null, null, null]) && !listEquals(theRedSide, [null, null, null])) Column( - children: [ - if (theGreenSide[0] != null && theRedSide[0] != null && theGreenSide[0]!.role != "banned" && theRedSide[0]!.role != "banned") - Column( - children: [ - CompareRegTimeThingy( - greenSide: theGreenSide[0].registrationTime, - redSide: theRedSide[0].registrationTime, - label: t.registred), - CompareThingy( - label: t.statCellNum.level, - greenSide: theGreenSide[0].level, - redSide: theRedSide[0].level, - higherIsBetter: true, - fractionDigits: 2, - ), - if (!theGreenSide[0].gameTime.isNegative && !theRedSide[0].gameTime.isNegative) - CompareThingy( - greenSide: theGreenSide[0].gameTime.inMicroseconds / - 1000000 / - 60 / - 60, - redSide: theRedSide[0].gameTime.inMicroseconds / - 1000000 / - 60 / - 60, - label: t.statCellNum.hoursPlayed.replaceAll(RegExp(r'\n'), " "), - higherIsBetter: true, - fractionDigits: 2, - ), - if (theGreenSide[0].gamesPlayed >= 0 && theRedSide[0].gamesPlayed >= 0) - CompareThingy( - label: t.statCellNum.onlineGames.replaceAll(RegExp(r'\n'), " "), - greenSide: theGreenSide[0].gamesPlayed, - redSide: theRedSide[0].gamesPlayed, - higherIsBetter: true, - ), - if (theGreenSide[0].gamesWon >= 0 && theRedSide[0].gamesWon >= 0) - CompareThingy( - label: t.statCellNum.gamesWon.replaceAll(RegExp(r'\n'), " "), - greenSide: theGreenSide[0].gamesWon, - redSide: theRedSide[0].gamesWon, - higherIsBetter: true, - ), - CompareThingy( - label: t.statCellNum.friends, - greenSide: theGreenSide[0].friendCount, - redSide: theRedSide[0].friendCount, - higherIsBetter: true, - ), - const Divider(), - ], - ), - if (theGreenSide[0] != null && theRedSide[0] != null && (theGreenSide[0]!.role == "banned" || theRedSide[0]!.role == "banned")) - CompareBoolThingy( - greenSide: theGreenSide[0].role == "banned", - redSide: theRedSide[0].role == "banned", - label: t.normalBanned, - trueIsBetter: false - ), - (theGreenSide[2].league.gamesPlayed > 0 || greenSideMode == Mode.stats) && (theRedSide[2].league.gamesPlayed > 0 || redSideMode == Mode.stats) - ? Column( - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Text(t.tetraLeague, - style: TextStyle( - fontFamily: "Eurostile Round Extended", - fontSize: bigScreen ? 42 : 28)), - ), - if (theGreenSide[2].league.gamesPlayed > 9 && - theRedSide[2].league.gamesPlayed > 9 && - greenSideMode != Mode.stats && - redSideMode != Mode.stats) - CompareThingy( - label: "TR", - greenSide: theGreenSide[2].league.tr, - redSide: theRedSide[2].league.tr, - fractionDigits: 2, - higherIsBetter: true, - ), - if (greenSideMode != Mode.stats && - redSideMode != Mode.stats) - CompareThingy( - label: t.statCellNum.gamesPlayed.replaceAll(RegExp(r'\n'), " "), - greenSide: theGreenSide[2].league.gamesPlayed, - redSide: theRedSide[2].league.gamesPlayed, - higherIsBetter: true, - ), - if (greenSideMode != Mode.stats && - redSideMode != Mode.stats) - CompareThingy( - label: t.statCellNum.gamesWonTL.replaceAll(RegExp(r'\n'), " "), - greenSide: theGreenSide[2].league.gamesWon, - redSide: theRedSide[2].league.gamesWon, - higherIsBetter: true, - ), - if (greenSideMode != Mode.stats && - redSideMode != Mode.stats) - CompareThingy( - label: "WR %", - greenSide: - theGreenSide[2].league.winrate * 100, - redSide: theRedSide[2].league.winrate * 100, - fractionDigits: 2, - higherIsBetter: true, - ), - if (theGreenSide[2].league.gamesPlayed > 9 && - theRedSide[2].league.gamesPlayed > 9 && - greenSideMode != Mode.stats && - redSideMode != Mode.stats) - CompareThingy( - label: "Glicko", - greenSide: theGreenSide[2].league.glicko!, - redSide: theRedSide[2].league.glicko!, - fractionDigits: 2, - higherIsBetter: true, - ), - if (theGreenSide[2].league.gamesPlayed > 9 && - theRedSide[2].league.gamesPlayed > 9 && - greenSideMode != Mode.stats && - redSideMode != Mode.stats) - CompareThingy( - label: "RD", - greenSide: theGreenSide[2].league.rd!, - redSide: theRedSide[2].league.rd!, - fractionDigits: 3, - higherIsBetter: false, - ), - if (theGreenSide[2].league.standing > 0 && - theRedSide[2].league.standing > 0 && - greenSideMode == Mode.player && - redSideMode == Mode.player) - CompareThingy( - label: t.statCellNum.lbpShort, - greenSide: theGreenSide[2].league.standing, - redSide: theRedSide[2].league.standing, - higherIsBetter: false, - ), - if (theGreenSide[2].league.standingLocal > 0 && - theRedSide[2].league.standingLocal > 0 && - greenSideMode == Mode.player && - redSideMode == Mode.player) - CompareThingy( - label: t.statCellNum.lbpcShort, - greenSide: - theGreenSide[2].league.standingLocal, - redSide: theRedSide[2].league.standingLocal, - higherIsBetter: false, - ), - if (theGreenSide[2].league.apm != null && - theRedSide[2].league.apm != null) - CompareThingy( - label: "APM", - greenSide: theGreenSide[2].league.apm!, - redSide: theRedSide[2].league.apm!, - fractionDigits: 2, - higherIsBetter: true, - ), - if (theGreenSide[2].league.pps != null && - theRedSide[2].league.pps != null) - CompareThingy( - label: "PPS", - greenSide: theGreenSide[2].league.pps!, - redSide: theRedSide[2].league.pps!, - fractionDigits: 2, - higherIsBetter: true, - ), - if (theGreenSide[2].league.vs != null && - theRedSide[2].league.vs != null) - CompareThingy( - label: "VS", - greenSide: theGreenSide[2].league.vs!, - redSide: theRedSide[2].league.vs!, - fractionDigits: 2, - higherIsBetter: true, - ), - ], - ) - : CompareBoolThingy( - greenSide: theGreenSide[2].league.gamesPlayed > 0, - redSide: theRedSide[2].league.gamesPlayed > 0, - label: t.playedTL, - trueIsBetter: false), - if (theGreenSide[2].league.nerdStats != null && - theRedSide[2].league.nerdStats != null) - Column( - children: [ - const Divider(), - Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Text(t.nerdStats, - style: TextStyle( - fontFamily: "Eurostile Round Extended", - fontSize: bigScreen ? 42 : 28)), - ), - CompareThingy( - label: "APP", - greenSide: theGreenSide[2].league.nerdStats!.app, - redSide: theRedSide[2].league.nerdStats!.app, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "VS/APM", - greenSide: theGreenSide[2].league.nerdStats!.vsapm, - redSide: theRedSide[2].league.nerdStats!.vsapm, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "DS/S", - greenSide: theGreenSide[2].league.nerdStats!.dss, - redSide: theRedSide[2].league.nerdStats!.dss, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "DS/P", - greenSide: theGreenSide[2].league.nerdStats!.dsp, - redSide: theRedSide[2].league.nerdStats!.dsp, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "APP + DS/P", - greenSide: - theGreenSide[2].league.nerdStats!.appdsp, - redSide: theRedSide[2].league.nerdStats!.appdsp, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "), - greenSide: - theGreenSide[2].league.nerdStats!.cheese, - redSide: theRedSide[2].league.nerdStats!.cheese, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareThingy( - label: "Gb Eff.", - greenSide: theGreenSide[2].league.nerdStats!.gbe, - redSide: theRedSide[2].league.nerdStats!.gbe, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "wAPP", - greenSide: - theGreenSide[2].league.nerdStats!.nyaapp, - redSide: theRedSide[2].league.nerdStats!.nyaapp, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "Area", - greenSide: theGreenSide[2].league.nerdStats!.area, - redSide: theRedSide[2].league.nerdStats!.area, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareThingy( - label: t.statCellNum.estOfTRShort, - greenSide: theGreenSide[2].league.estTr!.esttr, - redSide: theRedSide[2].league.estTr!.esttr, - fractionDigits: 2, - higherIsBetter: true, - ), - if (theGreenSide[2].league.gamesPlayed > 9 && - theGreenSide[2].league.gamesPlayed > 9 && - greenSideMode != Mode.stats && - redSideMode != Mode.stats) - CompareThingy( - label: t.statCellNum.accOfEstShort, - greenSide: theGreenSide[2].league.esttracc!, - redSide: theRedSide[2].league.esttracc!, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareThingy( - label: "Opener", - greenSide: theGreenSide[2].league.playstyle!.opener, - redSide: theRedSide[2].league.playstyle!.opener, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "Plonk", - greenSide: theGreenSide[2].league.playstyle!.plonk, - redSide: theRedSide[2].league.playstyle!.plonk, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "Stride", - greenSide: theGreenSide[2].league.playstyle!.stride, - redSide: theRedSide[2].league.playstyle!.stride, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "Inf. DS", - greenSide: theGreenSide[2].league.playstyle!.infds, - redSide: theRedSide[2].league.playstyle!.infds, - fractionDigits: 3, - higherIsBetter: true, - ), - VsGraphs(theGreenSide[2].league.apm!, theGreenSide[2].league.pps!, theGreenSide[2].league.vs!, theGreenSide[2].league.nerdStats!, theGreenSide[2].league.playstyle!, theRedSide[2].league.apm!, theRedSide[2].league.pps!, theRedSide[2].league.vs!, theRedSide[2].league.nerdStats!, theRedSide[2].league.playstyle!), - const Divider(), - Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Text(t.winChance, - style: TextStyle( - fontFamily: "Eurostile Round Extended", - fontSize: bigScreen ? 42 : 28)), - ), - if (greenSideMode != Mode.stats && redSideMode != Mode.stats && - theGreenSide[2].league.gamesPlayed > 9 && theRedSide[2].league.gamesPlayed > 9) - CompareThingy( - label: t.byGlicko, - greenSide: getWinrateByTR( - theGreenSide[2].league.glicko!, - theGreenSide[2].league.rd!, - theRedSide[2].league.glicko!, - theRedSide[2].league.rd!) * - 100, - redSide: getWinrateByTR( - theRedSide[2].league.glicko!, - theRedSide[2].league.rd!, - theGreenSide[2].league.glicko!, - theGreenSide[2].league.rd!) * - 100, - fractionDigits: 2, - higherIsBetter: true, - postfix: "%", - ), - CompareThingy( - label: t.byEstTR, - greenSide: getWinrateByTR( - theGreenSide[2].league.estTr!.estglicko, - theGreenSide[2].league.rd ?? noTrRd, - theRedSide[2].league.estTr!.estglicko, - theRedSide[2].league.rd ?? noTrRd) * - 100, - redSide: getWinrateByTR( - theRedSide[2].league.estTr!.estglicko, - theRedSide[2].league.rd ?? noTrRd, - theGreenSide[2].league.estTr!.estglicko, - theGreenSide[2].league.rd ?? noTrRd) * - 100, - fractionDigits: 2, - higherIsBetter: true, - postfix: "%", - ), - ], - ), - if (theGreenSide[2].zenith != null && theRedSide[2].zenith != null && greenSideMode == Mode.player && redSideMode == Mode.player) Column( - children: [ - const Divider(), - Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Text(t.quickPlay, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), - ), - CompareThingy( - label: "Height", - greenSide: theGreenSide[2].zenith.stats.zenith!.altitude, - redSide: theRedSide[2].zenith.stats.zenith!.altitude, - fractionDigits: 2, - higherIsBetter: true, - postfix: "m", - ), - CompareThingy( - label: "Position", - greenSide: theGreenSide[2].zenith.rank, - redSide: theRedSide[2].zenith.rank, - higherIsBetter: false, - prefix: "№ ", - ), - CompareThingy( - label: "Position (Country)", - greenSide: theGreenSide[2].zenith.countryRank, - redSide: theRedSide[2].zenith.countryRank, - higherIsBetter: false, - prefix: "№ ", - ), - CompareThingy( - label: "APM", - greenSide: theGreenSide[2].zenith.aggregateStats.apm, - redSide: theRedSide[2].zenith.aggregateStats.apm, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareThingy( - label: "PPS", - greenSide: theGreenSide[2].zenith.aggregateStats.pps, - redSide: theRedSide[2].zenith.aggregateStats.pps, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareThingy( - label: "VS", - greenSide: theGreenSide[2].zenith.aggregateStats.vs, - redSide: theRedSide[2].zenith.aggregateStats.vs, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareThingy( - label: "KO's", - greenSide: theGreenSide[2].zenith.stats.kills, - redSide: theRedSide[2].zenith.stats.kills, - higherIsBetter: true, - ), - CompareThingy( - label: "CPS", - greenSide: theGreenSide[2].zenith.stats.cps, - redSide: theRedSide[2].zenith.stats.cps, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareThingy( - label: "Peak CPS", - greenSide: theGreenSide[2].zenith.stats.zenith!.peakrank, - redSide: theRedSide[2].zenith.stats.zenith!.peakrank, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareDurationThingy( - label: "Time", - greenSide: theGreenSide[2].zenith.stats.finalTime, - redSide: theRedSide[2].zenith.stats.finalTime, - higherIsBetter: false, - ), - CompareThingy( - label: "Finesse", - greenSide: theGreenSide[2].zenith.stats.finessePercentage * 100, - redSide: theRedSide[2].zenith.stats.finessePercentage * 100, - fractionDigits: 2, - postfix: "%", - higherIsBetter: true, - ), - const Divider(), - Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Text("${t.quickPlay} ${t.nerdStats}", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)), - ), - CompareThingy( - label: "APP", - greenSide: theGreenSide[2].zenith.aggregateStats.nerdStats.app, - redSide: theRedSide[2].zenith.aggregateStats.nerdStats.app, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "VS/APM", - greenSide: theGreenSide[2].zenith.aggregateStats.nerdStats.vsapm, - redSide: theRedSide[2].zenith.aggregateStats.nerdStats.vsapm, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "DS/S", - greenSide: theGreenSide[2].zenith.aggregateStats.nerdStats.dss, - redSide: theRedSide[2].zenith.aggregateStats.nerdStats.dss, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "DS/P", - greenSide: theGreenSide[2].zenith.aggregateStats.nerdStats.dsp, - redSide: theRedSide[2].zenith.aggregateStats.nerdStats.dsp, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "APP + DS/P", - greenSide: - theGreenSide[2].zenith.aggregateStats.nerdStats.appdsp, - redSide: theRedSide[2].zenith.aggregateStats.nerdStats.appdsp, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "), - greenSide: - theGreenSide[2].zenith.aggregateStats.nerdStats.cheese, - redSide: theRedSide[2].zenith.aggregateStats.nerdStats.cheese, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareThingy( - label: "Gb Eff.", - greenSide: theGreenSide[2].zenith.aggregateStats.nerdStats.gbe, - redSide: theRedSide[2].zenith.aggregateStats.nerdStats.gbe, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "wAPP", - greenSide: - theGreenSide[2].zenith.aggregateStats.nerdStats.nyaapp, - redSide: theRedSide[2].zenith.aggregateStats.nerdStats.nyaapp, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "Area", - greenSide: theGreenSide[2].zenith.aggregateStats.nerdStats.area, - redSide: theRedSide[2].zenith.aggregateStats.nerdStats.area, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareThingy( - label: "Opener", - greenSide: theGreenSide[2].zenith.aggregateStats.playstyle.opener, - redSide: theRedSide[2].zenith.aggregateStats.playstyle.opener, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "Plonk", - greenSide: theGreenSide[2].zenith.aggregateStats.playstyle.plonk, - redSide: theRedSide[2].zenith.aggregateStats.playstyle.plonk, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "Stride", - greenSide: theGreenSide[2].zenith.aggregateStats.playstyle.stride, - redSide: theRedSide[2].zenith.aggregateStats.playstyle.stride, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "Inf. DS", - greenSide: theGreenSide[2].zenith.aggregateStats.playstyle.infds, - redSide: theRedSide[2].zenith.aggregateStats.playstyle.infds, - fractionDigits: 3, - higherIsBetter: true, - ), - VsGraphs(theGreenSide[2].zenith.aggregateStats.apm, theGreenSide[2].zenith.aggregateStats.pps, theGreenSide[2].zenith.aggregateStats.vs, theGreenSide[2].zenith.aggregateStats.nerdStats, theGreenSide[2].zenith.aggregateStats.playstyle, theRedSide[2].zenith.aggregateStats.apm, theRedSide[2].zenith.aggregateStats.pps, theRedSide[2].zenith.aggregateStats.vs, theRedSide[2].zenith.aggregateStats.nerdStats, theRedSide[2].zenith.aggregateStats.playstyle), - ], - ) - else if (greenSideMode == Mode.player && redSideMode == Mode.player) CompareBoolThingy(greenSide: theGreenSide[2].zenith != null, redSide: theRedSide[2].zenith != null, label: "Played QP", trueIsBetter: true), - if (theGreenSide[2].zenithEx != null && theRedSide[2].zenithEx != null && greenSideMode == Mode.player && redSideMode == Mode.player) Column( - children: [ - const Divider(), - Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Text("${t.quickPlay} ${t.expert}", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), - ), - CompareThingy( - label: "Height", - greenSide: theGreenSide[2].zenithEx.stats.zenith!.altitude, - redSide: theRedSide[2].zenithEx.stats.zenith!.altitude, - fractionDigits: 2, - higherIsBetter: true, - postfix: "m", - ), - CompareThingy( - label: "Position", - greenSide: theGreenSide[2].zenithEx.rank, - redSide: theRedSide[2].zenithEx.rank, - higherIsBetter: false, - prefix: "№ ", - ), - CompareThingy( - label: "Position (Country)", - greenSide: theGreenSide[2].zenithEx.countryRank, - redSide: theRedSide[2].zenithEx.countryRank, - higherIsBetter: false, - prefix: "№ ", - ), - CompareThingy( - label: "APM", - greenSide: theGreenSide[2].zenithEx.aggregateStats.apm, - redSide: theRedSide[2].zenithEx.aggregateStats.apm, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareThingy( - label: "PPS", - greenSide: theGreenSide[2].zenithEx.aggregateStats.pps, - redSide: theRedSide[2].zenithEx.aggregateStats.pps, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareThingy( - label: "VS", - greenSide: theGreenSide[2].zenithEx.aggregateStats.vs, - redSide: theRedSide[2].zenithEx.aggregateStats.vs, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareThingy( - label: "KO's", - greenSide: theGreenSide[2].zenithEx.stats.kills, - redSide: theRedSide[2].zenithEx.stats.kills, - higherIsBetter: true, - ), - CompareThingy( - label: "CPS", - greenSide: theGreenSide[2].zenithEx.stats.cps, - redSide: theRedSide[2].zenithEx.stats.cps, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareThingy( - label: "Peak CPS", - greenSide: theGreenSide[2].zenithEx.stats.zenith!.peakrank, - redSide: theRedSide[2].zenithEx.stats.zenith!.peakrank, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareDurationThingy( - label: "Time", - greenSide: theGreenSide[2].zenithEx.stats.finalTime, - redSide: theRedSide[2].zenithEx.stats.finalTime, - higherIsBetter: false, - ), - CompareThingy( - label: "Finesse", - greenSide: theGreenSide[2].zenithEx.stats.finessePercentage * 100, - redSide: theRedSide[2].zenithEx.stats.finessePercentage * 100, - fractionDigits: 2, - postfix: "%", - higherIsBetter: true, - ), - const Divider(), - Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Text("${t.quickPlay} ${t.expert} ${t.nerdStats}", style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)), - ), - CompareThingy( - label: "APP", - greenSide: theGreenSide[2].zenithEx.aggregateStats.nerdStats.app, - redSide: theRedSide[2].zenithEx.aggregateStats.nerdStats.app, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "VS/APM", - greenSide: theGreenSide[2].zenithEx.aggregateStats.nerdStats.vsapm, - redSide: theRedSide[2].zenithEx.aggregateStats.nerdStats.vsapm, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "DS/S", - greenSide: theGreenSide[2].zenithEx.aggregateStats.nerdStats.dss, - redSide: theRedSide[2].zenithEx.aggregateStats.nerdStats.dss, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "DS/P", - greenSide: theGreenSide[2].zenithEx.aggregateStats.nerdStats.dsp, - redSide: theRedSide[2].zenithEx.aggregateStats.nerdStats.dsp, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "APP + DS/P", - greenSide: - theGreenSide[2].zenithEx.aggregateStats.nerdStats.appdsp, - redSide: theRedSide[2].zenithEx.aggregateStats.nerdStats.appdsp, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "), - greenSide: - theGreenSide[2].zenithEx.aggregateStats.nerdStats.cheese, - redSide: theRedSide[2].zenithEx.aggregateStats.nerdStats.cheese, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareThingy( - label: "Gb Eff.", - greenSide: theGreenSide[2].zenithEx.aggregateStats.nerdStats.gbe, - redSide: theRedSide[2].zenithEx.aggregateStats.nerdStats.gbe, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "wAPP", - greenSide: - theGreenSide[2].zenithEx.aggregateStats.nerdStats.nyaapp, - redSide: theRedSide[2].zenithEx.aggregateStats.nerdStats.nyaapp, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "Area", - greenSide: theGreenSide[2].zenithEx.aggregateStats.nerdStats.area, - redSide: theRedSide[2].zenithEx.aggregateStats.nerdStats.area, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareThingy( - label: "Opener", - greenSide: theGreenSide[2].zenithEx.aggregateStats.playstyle.opener, - redSide: theRedSide[2].zenithEx.aggregateStats.playstyle.opener, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "Plonk", - greenSide: theGreenSide[2].zenithEx.aggregateStats.playstyle.plonk, - redSide: theRedSide[2].zenithEx.aggregateStats.playstyle.plonk, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "Stride", - greenSide: theGreenSide[2].zenithEx.aggregateStats.playstyle.stride, - redSide: theRedSide[2].zenithEx.aggregateStats.playstyle.stride, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "Inf. DS", - greenSide: theGreenSide[2].zenithEx.aggregateStats.playstyle.infds, - redSide: theRedSide[2].zenithEx.aggregateStats.playstyle.infds, - fractionDigits: 3, - higherIsBetter: true, - ), - VsGraphs(theGreenSide[2].zenithEx.aggregateStats.apm, theGreenSide[2].zenithEx.aggregateStats.pps, theGreenSide[2].zenithEx.aggregateStats.vs, theGreenSide[2].zenithEx.aggregateStats.nerdStats, theGreenSide[2].zenithEx.aggregateStats.playstyle, theRedSide[2].zenithEx.aggregateStats.apm, theRedSide[2].zenithEx.aggregateStats.pps, theRedSide[2].zenithEx.aggregateStats.vs, theRedSide[2].zenithEx.aggregateStats.nerdStats, theRedSide[2].zenithEx.aggregateStats.playstyle), - ], - ) - else if (greenSideMode == Mode.player && redSideMode == Mode.player) CompareBoolThingy(greenSide: theGreenSide[2].zenithEx != null, redSide: theRedSide[2].zenithEx != null, label: "Played QP Expert", trueIsBetter: true), - if (theGreenSide[2].sprint != null && theRedSide[2].sprint != null && greenSideMode == Mode.player && redSideMode == Mode.player) Column( - children: [ - const Divider(), - Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Text(t.sprint, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), - ), - CompareDurationThingy( - label: "Time", - greenSide: theGreenSide[2].sprint.stats.finalTime, - redSide: theRedSide[2].sprint.stats.finalTime, - higherIsBetter: false, - ), - CompareThingy( - label: "Lines", - greenSide: theGreenSide[2].sprint.stats.lines, - redSide: theRedSide[2].sprint.stats.lines, - higherIsBetter: false, - ), - CompareThingy( - label: t.statCellNum.pieces.replaceAll(RegExp(r'\n'), " "), - greenSide: theGreenSide[2].sprint.stats.piecesPlaced, - redSide: theRedSide[2].sprint.stats.piecesPlaced, - higherIsBetter: false, - ), - CompareThingy( - label: t.statCellNum.keys.replaceAll(RegExp(r'\n'), " "), - greenSide: theGreenSide[2].sprint.stats.inputs, - redSide: theRedSide[2].sprint.stats.inputs, - higherIsBetter: false, - ), - CompareThingy( - label: "PPS", - greenSide: theGreenSide[2].sprint.stats.pps, - redSide: theRedSide[2].sprint.stats.pps, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareThingy( - label: "KPP", - greenSide: theGreenSide[2].sprint.stats.kpp, - redSide: theRedSide[2].sprint.stats.kpp, - fractionDigits: 2, - higherIsBetter: false, - ), - CompareThingy( - label: "KPS", - greenSide: theGreenSide[2].sprint.stats.kps, - redSide: theRedSide[2].sprint.stats.kps, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareThingy( - label: "Finesse", - greenSide: theGreenSide[2].sprint.stats.finessePercentage * 100, - redSide: theRedSide[2].sprint.stats.finessePercentage * 100, - fractionDigits: 2, - postfix: "%", - higherIsBetter: true, - ), - CompareThingy( - label: "Holds", - greenSide: theGreenSide[2].sprint.stats.holds, - redSide: theRedSide[2].sprint.stats.holds, - higherIsBetter: false, - ), - CompareThingy( - label: "T-spins", - greenSide: theGreenSide[2].sprint.stats.tSpins, - redSide: theRedSide[2].sprint.stats.tSpins, - higherIsBetter: false, - ), - CompareThingy( - label: "Quads", - greenSide: theGreenSide[2].sprint.stats.clears.quads, - redSide: theRedSide[2].sprint.stats.clears.quads, - higherIsBetter: true, - ), - CompareThingy( - label: "PC's", - greenSide: theGreenSide[2].sprint.stats.clears.allClears, - redSide: theRedSide[2].sprint.stats.clears.allClears, - higherIsBetter: true, - ), - ], - ) - else if (greenSideMode == Mode.player && redSideMode == Mode.player) CompareBoolThingy(greenSide: theGreenSide[2].sprint != null, redSide: theRedSide[2].sprint != null, label: "Played 40 Lines", trueIsBetter: true), - if (theGreenSide[2].blitz != null && theRedSide[2].blitz != null && greenSideMode == Mode.player && redSideMode == Mode.player) Column( - children: [ - const Divider(), - Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Text(t.blitz, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), - ), - CompareThingy( - label: "Score", - greenSide: theGreenSide[2].blitz.stats.score, - redSide: theRedSide[2].blitz.stats.score, - higherIsBetter: true, - ), - CompareThingy( - label: "SPP", - greenSide: theGreenSide[2].blitz.stats.spp, - redSide: theRedSide[2].blitz.stats.spp, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareThingy( - label: "Level", - greenSide: theGreenSide[2].blitz.stats.level, - redSide: theRedSide[2].blitz.stats.level, - higherIsBetter: true, - ), - CompareThingy( - label: "Lines", - greenSide: theGreenSide[2].blitz.stats.lines, - redSide: theRedSide[2].blitz.stats.lines, - higherIsBetter: true, - ), - CompareThingy( - label: t.statCellNum.pieces.replaceAll(RegExp(r'\n'), " "), - greenSide: theGreenSide[2].blitz.stats.piecesPlaced, - redSide: theRedSide[2].blitz.stats.piecesPlaced, - higherIsBetter: true, - ), - CompareThingy( - label: t.statCellNum.keys.replaceAll(RegExp(r'\n'), " "), - greenSide: theGreenSide[2].blitz.stats.inputs, - redSide: theRedSide[2].blitz.stats.inputs, - higherIsBetter: true, - ), - CompareThingy( - label: "PPS", - greenSide: theGreenSide[2].blitz.stats.pps, - redSide: theRedSide[2].blitz.stats.pps, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareThingy( - label: "KPP", - greenSide: theGreenSide[2].blitz.stats.kpp, - redSide: theRedSide[2].blitz.stats.kpp, - fractionDigits: 2, - higherIsBetter: false, - ), - CompareThingy( - label: "KPS", - greenSide: theGreenSide[2].blitz.stats.kps, - redSide: theRedSide[2].blitz.stats.kps, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareThingy( - label: "Finesse", - greenSide: theGreenSide[2].blitz.stats.finessePercentage * 100, - redSide: theRedSide[2].blitz.stats.finessePercentage * 100, - fractionDigits: 2, - postfix: "%", - higherIsBetter: true, - ), - CompareThingy( - label: "Holds", - greenSide: theGreenSide[2].blitz.stats.holds, - redSide: theRedSide[2].blitz.stats.holds, - higherIsBetter: false, - ), - CompareThingy( - label: "T-spins", - greenSide: theGreenSide[2].blitz.stats.tSpins, - redSide: theRedSide[2].blitz.stats.tSpins, - higherIsBetter: false, - ), - CompareThingy( - label: "Quads", - greenSide: theGreenSide[2].blitz.stats.clears.quads, - redSide: theRedSide[2].blitz.stats.clears.quads, - higherIsBetter: true, - ), - CompareThingy( - label: "PC's", - greenSide: theGreenSide[2].blitz.stats.clears.allClears, - redSide: theRedSide[2].blitz.stats.clears.allClears, - higherIsBetter: true, - ), - ], - ) - else if (greenSideMode == Mode.player && redSideMode == Mode.player) CompareBoolThingy(greenSide: theGreenSide[2].blitz != null, redSide: theRedSide[2].blitz != null, label: "Played Blitz", trueIsBetter: true), - if (greenSideMode == Mode.player && redSideMode == Mode.player) Column( - children: [ - const Divider(), - Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Text(t.zen, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), - ), - CompareThingy( - label: "Level", - greenSide: theGreenSide[2].zen.level, - redSide: theRedSide[2].zen.level, - higherIsBetter: true, - ), - CompareThingy( - label: "Score", - greenSide: theGreenSide[2].zen.score, - redSide: theRedSide[2].zen.score, - higherIsBetter: true, - ), - ], - ) - ], - ) - else Padding( - padding: const EdgeInsets.all(8.0), - child: Text(t.compareViewNoValues(avgR: "\$avgR"), textAlign: TextAlign.center), - ) - ], - ), - ), - ), - ), - ); - } -} - -class PlayerSelector extends StatelessWidget { - final List data; - final Mode mode; - final Function fetch; - final Function change; - final Function updateState; - const PlayerSelector( - {super.key, - required this.data, - required this.mode, - required this.updateState, - required this.fetch, - required this.change}); - - @override - Widget build(BuildContext context) { - final TextEditingController playerController = TextEditingController(); - String underFieldString = ""; - if (!listEquals(data, [null, null, null])){ - switch (mode){ - case Mode.player: - playerController.text = data[0] != null ? data[0].username : ""; - break; - case Mode.stats: - playerController.text = "${data[2].league.apm} ${data[2].league.pps} ${data[2].league.vs}"; - break; - case Mode.averages: - playerController.text = "\$avg${data[2].league.rank.toUpperCase()}"; - break; - } - } - if (!listEquals(data, [null, null, null])){ - switch (mode){ - case Mode.player: - underFieldString = data[0] != null ? data[0].toString() : "???"; - break; - case Mode.stats: - underFieldString = "${data[2].league.apm} APM, ${data[2].league.pps} PPS, ${data[2].league.vs} VS"; - break; - case Mode.averages: - underFieldString = t.averageXrank(rankLetter: data[2].league.rank.toUpperCase()); - break; - } - } - return Column( - children: [ - TextField( - autocorrect: false, - enableSuggestions: false, - maxLength: 25, - controller: playerController, - decoration: const InputDecoration(counter: Offstage()), - onSubmitted: (String value) { - underFieldString = "Fetching..."; - fetch(value); - }), - if (data[0] != null && data[1] != null) - Padding( - padding: const EdgeInsets.only(bottom: 8.0), - child: DropdownButton( - items: data[1], - value: data[0], - onChanged: (value) => change(value!), - ), - ) - else Text( - underFieldString, - style: const TextStyle( - shadows: [ - 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, - ), - ], - ), - ), - ], - ); - } -} - -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( - 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( - 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, - ), - )), - ], - ), - ); - } -} - -class CompareBoolThingy extends StatelessWidget { - final bool greenSide; - final bool redSide; - final String label; - final bool trueIsBetter; - const CompareBoolThingy( - {super.key, - required this.greenSide, - required this.redSide, - required this.label, - required this.trueIsBetter}); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.fromLTRB(16, 2, 16, 2), - child: Row(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, - stops: [ - 0.0, - trueIsBetter - ? greenSide - ? 0.6 - : 0 - : !greenSide - ? 0.6 - : 0 - ], - )), - child: Text( - greenSide ? t.yes : t.no, - style: const TextStyle( - fontSize: 22, - shadows: [ - 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.start, - ), - )), - Column( - children: [ - Text( - label, - style: const TextStyle(fontSize: 22), - textAlign: TextAlign.center, - ), - const Text("---", 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, - stops: [ - 0.0, - trueIsBetter - ? redSide - ? 0.6 - : 0 - : !redSide - ? 0.6 - : 0 - ], - )), - child: Text( - redSide ? t.yes : t.no, - style: const TextStyle( - fontSize: 22, - shadows: [ - 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, - ), - )), - ]), - ); - } -} - -class CompareDurationThingy extends StatelessWidget { - final Duration greenSide; - final Duration redSide; - final String label; - final bool higherIsBetter; - const CompareDurationThingy( - {super.key, - required this.greenSide, - required this.redSide, - required this.label, - required this.higherIsBetter}); - - Duration verdict(Duration greenSide, Duration redSide) { - return greenSide - redSide; - } - - @override - Widget build(BuildContext context) { - 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(get40lTime(greenSide.inMicroseconds), style: const TextStyle( - fontSize: 22, - shadows: [ - 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.start) - )), - Column( - children: [ - Text( - label, - style: const TextStyle( - fontSize: 22, - shadows: [ - 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.center, - ), - Text( - verdict(greenSide, redSide).toString(), 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(get40lTime(redSide.inMicroseconds), style: const TextStyle( - fontSize: 22, - shadows: [ - 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) - )), - ], - ), - ); - } -} - -class CompareRegTimeThingy extends StatelessWidget { - final DateTime? greenSide; - final DateTime? redSide; - final String label; - final int? fractionDigits; - const CompareRegTimeThingy( - {super.key, - required this.greenSide, - required this.redSide, - required this.label, - this.fractionDigits}); - - String verdict(DateTime? greenSide, DateTime? redSide) { - var f = NumberFormat("#,### ${t.daysLater};#,### ${t.dayseBefore}"); - String result = "---"; - if (greenSide != null && redSide != null) { - result = f.format(greenSide.difference(redSide).inDays); - } - return result; - } - - @override - Widget build(BuildContext context) { - DateFormat f = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode); - 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, - stops: [ - 0.0, - greenSide == null - ? 0.6 - : redSide != null && greenSide!.isBefore(redSide!) - ? 0.6 - : 0 - ], - )), - child: Text( - greenSide != null ? f.format(greenSide!) : t.fromBeginning, - style: const TextStyle( - fontSize: 22, - shadows: [ - 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.start, - ), - )), - Column( - children: [ - Text( - label, - style: const TextStyle(fontSize: 22), - textAlign: TextAlign.center, - ), - Text(verdict(greenSide, redSide), 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, - stops: [ - 0.0, - redSide == null - ? 0.6 - : greenSide != null && redSide!.isBefore(greenSide!) - ? 0.6 - : 0 - ], - )), - child: Text( - redSide != null ? f.format(redSide!) : t.fromBeginning, - style: const TextStyle( - fontSize: 22, - shadows: [ - 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, - ), - )), - ], - ), - ); - } -} diff --git a/lib/views/customization_view.dart b/lib/views/customization_view.dart deleted file mode 100644 index f518c8a..0000000 --- a/lib/views/customization_view.dart +++ /dev/null @@ -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 createState() => CustomizationState(); -} - -class CustomizationState extends State { - 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>? locales = - >[]; - for (var v in AppLocale.values) { - locales.add(DropdownMenuItem( - 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: [ - ElevatedButton( - child: const Text('Set'), - onPressed: () { - setState(() { - context.findAncestorStateOfType()?.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(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: [ - 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; - }); - }),) - ], - )), - ); - } -} diff --git a/lib/views/destination_calculator.dart b/lib/views/destination_calculator.dart index c5c98cd..fce3747 100644 --- a/lib/views/destination_calculator.dart +++ b/lib/views/destination_calculator.dart @@ -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/gen/strings.g.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{ final BoxConstraints constraints; @@ -243,7 +245,7 @@ class _DestinationCalculatorState extends State { child: NerdStatsThingy(nerdStats: nerdStats!) ), 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") ], diff --git a/lib/views/destination_cutoffs.dart b/lib/views/destination_cutoffs.dart index 8321033..7feb500 100644 --- a/lib/views/destination_cutoffs.dart +++ b/lib/views/destination_cutoffs.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; import 'package:syncfusion_flutter_gauges/gauges.dart'; import 'package:tetra_stats/data_objects/cutoff_tetrio.dart'; import 'package:tetra_stats/data_objects/tetrio_constants.dart'; @@ -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/utils/numers_formats.dart'; import 'package:tetra_stats/utils/text_shadow.dart'; -import 'package:tetra_stats/views/main_view_tiles.dart'; import 'package:tetra_stats/views/rank_view.dart'; +import 'package:tetra_stats/widgets/future_error.dart'; import 'package:tetra_stats/widgets/text_timestamp.dart'; import 'package:vector_math/vector_math_64.dart' hide Colors; diff --git a/lib/views/destination_graphs.dart b/lib/views/destination_graphs.dart index 64221d6..7bb5ad7 100644 --- a/lib/views/destination_graphs.dart +++ b/lib/views/destination_graphs.dart @@ -10,7 +10,9 @@ import 'package:tetra_stats/gen/strings.g.dart'; import 'package:tetra_stats/main.dart'; import 'package:tetra_stats/services/crud_exceptions.dart'; import 'package:tetra_stats/utils/numers_formats.dart'; -import 'package:tetra_stats/views/main_view_tiles.dart'; +import 'package:tetra_stats/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'; class DestinationGraphs extends StatefulWidget{ @@ -207,7 +209,6 @@ class _DestinationGraphsState extends State { if (snapshot.data!.isEmpty || !snapshot.data!.containsKey(_season)) return ErrorThingy(eText: "Not enough data"); List<_HistoryChartSpot> selectedGraph = snapshot.data![_season]![_Ychart]!; yAxisTitle = chartsShortTitles[_Ychart]!; - // TODO: this graph can Krash return SfCartesianChart( tooltipBehavior: _historyTooltipBehavior, zoomPanBehavior: _zoomPanBehavior, diff --git a/lib/views/destination_home.dart b/lib/views/destination_home.dart index 47cbd7b..6339792 100644 --- a/lib/views/destination_home.dart +++ b/lib/views/destination_home.dart @@ -1,4 +1,3 @@ -import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.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/relative_timestamps.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/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/future_error.dart'; +import 'package:tetra_stats/widgets/graphs.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/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{ final String searchFor; @@ -423,8 +435,8 @@ class _DestinationHomeState extends State with SingleTickerProv ], ), ), - if (data.nerdStats != null) NerdStatsThingy(nerdStats: data.nerdStats!, oldNerdStats: toCompare?.nerdStats, averages: averages), - if (data.nerdStats != null) GraphsThingy(nerdStats: data.nerdStats!, playstyle: data.playstyle!, apm: data.apm!, pps: data.pps!, vs: data.vs!) + if (data.nerdStats != null) NerdStatsThingy(nerdStats: data.nerdStats!, oldNerdStats: toCompare?.nerdStats, averages: averages, lbPos: lbPos), + if (data.nerdStats != null) Graphs(data.apm!, data.pps!, data.vs!, data.nerdStats!, data.playstyle!) ], ); } @@ -713,7 +725,7 @@ class _DestinationHomeState extends State with SingleTickerProv ), ), 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 with SingleTickerProv width: 450, child: Column( 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!.distinguishment != null) DistinguishmentThingy(snapshot.data!.player!.distinguishment!), if (snapshot.data!.player!.role == "bot") FakeDistinguishmentThingy(bot: true, botMaintainers: snapshot.data!.player!.botmaster), diff --git a/lib/views/destination_info.dart b/lib/views/destination_info.dart new file mode 100644 index 0000000..75d89df --- /dev/null +++ b/lib/views/destination_info.dart @@ -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 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 { + @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() + ], + ), + ) + ], + ); + } +} diff --git a/lib/views/destination_leaderboards.dart b/lib/views/destination_leaderboards.dart index e95b987..d354ba8 100644 --- a/lib/views/destination_leaderboards.dart +++ b/lib/views/destination_leaderboards.dart @@ -7,8 +7,8 @@ import 'package:tetra_stats/gen/strings.g.dart'; import 'package:tetra_stats/main.dart'; import 'package:tetra_stats/utils/numers_formats.dart'; import 'package:tetra_stats/utils/relative_timestamps.dart'; -import 'package:tetra_stats/views/main_view_tiles.dart'; import 'package:tetra_stats/views/user_view.dart'; +import 'package:tetra_stats/widgets/future_error.dart'; class DestinationLeaderboards extends StatefulWidget{ final BoxConstraints constraints; diff --git a/lib/views/destination_saved_data.dart b/lib/views/destination_saved_data.dart index 6d09395..a1571ac 100644 --- a/lib/views/destination_saved_data.dart +++ b/lib/views/destination_saved_data.dart @@ -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/main.dart'; import 'package:tetra_stats/utils/numers_formats.dart'; -import 'package:tetra_stats/views/main_view_tiles.dart'; import 'package:tetra_stats/views/state_view.dart'; +import 'package:tetra_stats/widgets/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'; class DestinationSavedData extends StatefulWidget{ diff --git a/lib/views/destination_settings.dart b/lib/views/destination_settings.dart index 30dfc28..935bfac 100644 --- a/lib/views/destination_settings.dart +++ b/lib/views/destination_settings.dart @@ -13,7 +13,7 @@ import 'package:tetra_stats/main.dart'; import 'package:tetra_stats/utils/filesizes_converter.dart'; import 'package:tetra_stats/utils/numers_formats.dart'; import 'package:tetra_stats/utils/relative_timestamps.dart'; -import 'package:tetra_stats/views/main_view_tiles.dart'; +import 'package:tetra_stats/widgets/future_error.dart'; class DestinationSettings extends StatefulWidget{ final BoxConstraints constraints; @@ -50,7 +50,6 @@ class _DestinationSettings extends State with SingleTickerP late bool showPositions; late bool showAverages; late bool updateInBG; - final TextEditingController _playertext = TextEditingController(); late AnimationController _defaultNicknameAnimController; late Animation _goodDefaultNicknameAnim; late Animation _badDefaultNicknameAnim; diff --git a/lib/views/main_view.dart b/lib/views/main_view.dart index 4ef9c32..06a4807 100644 --- a/lib/views/main_view.dart +++ b/lib/views/main_view.dart @@ -1,1629 +1,314 @@ -// ignore_for_file: type_literal_in_constant_pattern, use_build_context_synchronously - -import 'dart:async'; -import 'dart:io'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:http/http.dart'; -import 'package:intl/intl.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:flutter/services.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:tetra_stats/data_objects/beta_record.dart'; -import 'package:tetra_stats/data_objects/distinguishment.dart'; -import 'package:tetra_stats/data_objects/news.dart'; -import 'package:tetra_stats/data_objects/news_entry.dart'; -import 'package:tetra_stats/data_objects/player_leaderboard_position.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/singleplayer_stream.dart'; -import 'package:tetra_stats/data_objects/summaries.dart'; -import 'package:tetra_stats/data_objects/tetra_league.dart'; -import 'package:tetra_stats/data_objects/tetra_league_alpha_record.dart'; -import 'package:tetra_stats/data_objects/tetra_league_alpha_stream.dart'; -import 'package:tetra_stats/data_objects/tetra_league_beta_stream.dart'; -import 'package:tetra_stats/data_objects/p1nkl0bst3r.dart'; -import 'package:tetra_stats/data_objects/tetrio_constants.dart'; -import 'package:tetra_stats/data_objects/tetrio_player.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/data_objects/tetrio_zen.dart'; -import 'package:tetra_stats/gen/strings.g.dart'; -import 'package:tetra_stats/main.dart' show prefs, teto; -import 'package:tetra_stats/services/crud_exceptions.dart'; -import 'package:tetra_stats/utils/numers_formats.dart'; -import 'package:tetra_stats/utils/open_in_browser.dart'; -import 'package:tetra_stats/utils/relative_timestamps.dart'; -import 'package:tetra_stats/utils/text_shadow.dart'; -import 'package:tetra_stats/views/singleplayer_record_view.dart'; -import 'package:tetra_stats/views/tl_match_view.dart' show TlMatchResultView; -import 'package:tetra_stats/views/zenith_record_view.dart'; -import 'package:tetra_stats/widgets/finesse_thingy.dart'; -import 'package:tetra_stats/widgets/lineclears_thingy.dart'; -import 'package:tetra_stats/widgets/list_tile_trailing_stats.dart'; -import 'package:tetra_stats/widgets/recent_sp_games.dart'; -import 'package:tetra_stats/widgets/search_box.dart'; -import 'package:tetra_stats/widgets/singleplayer_record.dart'; -import 'package:tetra_stats/widgets/sp_trailing_stats.dart'; -import 'package:tetra_stats/widgets/stat_sell_num.dart'; -import 'package:tetra_stats/widgets/text_timestamp.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'; -import 'package:window_manager/window_manager.dart'; -import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:go_router/go_router.dart'; - -int _chartsIndex = 0; -int _season = currentSeason-1; -bool _gamesPlayedInsteadOfDateAndTime = false; -late ZoomPanBehavior _zoomPanBehavior; -bool _smooth = false; -List _historyShortTitles = ["TR", "Glicko", "RD", "APM", "PPS", "VS", "APP", "DS/S", "DS/P", "APP + DS/P", "VS/APM", "Cheese", "GbE", "wAPP", "Area", "eTR", "±eTR", "Opener", "Plonk", "Inf. DS", "Stride"]; -late ScrollController _scrollController; - -class MainView extends StatefulWidget { - final String? player; - /// The very first view, that user see when he launch this programm. - /// By default it loads my or defined in preferences user stats, but - /// if [player] username or id provided, it loads his stats. Also it hides menu drawer and three dots menu. - const MainView({super.key, this.player}); - - @override - State createState() => _MainState(); -} - -Future copyToClipboard(String text) async { - await Clipboard.setData(ClipboardData(text: text)); -} - -class _MainState extends State with TickerProviderStateMixin { - Future me = Future.delayed(const Duration(seconds: 60), () => [null, null, null, null, null, null]); // I love lists shut up - TetrioPlayersLeaderboard? everyone; - PlayerLeaderboardPosition? meAmongEveryone; - TetraLeague? rankAverages; - double? thatRankCutoff; - double? nextRankCutoff; - double? thatRankGlickoCutoff; - double? nextRankGlickoCutoff; - String _searchFor = "6098518e3d5155e6ec429cdc"; // who we looking for - String _titleNickname = ""; - /// Each dropdown menu item contains list of dots for the graph - /// chartsData[season-1][chart] - List>>> chartsData = []; - //var tableData = []; - final bodyGlobalKey = GlobalKey(); - bool _showSearchBar = false; - Timer backgroundUpdate = Timer(const Duration(days: 365), (){}); - bool _TLHistoryWasFetched = false; - late TabController _tabController; - late TabController _wideScreenTabController; - bool zenithEX = false; - - String get title => "Tetra Stats: $_titleNickname"; - - @override - void initState() { - initDB(); - _scrollController = ScrollController(); - _tabController = TabController(length: 9, vsync: this); - _wideScreenTabController = TabController(length: 5, vsync: this); - _zoomPanBehavior = ZoomPanBehavior( - enablePinching: true, - enableSelectionZooming: true, - enableMouseWheelZooming : true, - enablePanning: true, - ); - // We need to show something - if (widget.player != null){ // if we have user input, - changePlayer(widget.player!); // it's gonna be user input - }else{ - _getPreferences() // otherwise, checking for preferences - .then((value) => changePlayer(prefs.getString("player") ?? "6098518e3d5155e6ec429cdc")); // no preferences - loading me - } - super.initState(); - } - - @override - void dispose() { - _tabController.dispose(); - _scrollController.dispose(); - super.dispose(); - } - - Future _getPreferences() async { - prefs = await SharedPreferences.getInstance(); - } - - /// That function initiate search of data about [player]. If [fetchHistory] is true, - /// also attempting to retrieve players history. Can trow an Exception if fails - void changePlayer(String player, {bool fetchHistory = false, bool fetchTLmatches = false}) { - setState(() { - _searchFor = player; - me = fetch(_searchFor, fetchHistory: fetchHistory, fetchTLmatches: fetchTLmatches); - }); - } - - void initDB() async{ - await teto.open(); - } - - /// Retrieves data from 3 different Tetra Channel API endpoints + 1 endpoint from p1nkl0bst3r's API - /// using [nickOrID] of player. - /// - /// If [fetchHistory] is true, also retrieves players history from p1nkl0bst3r's API. If [fetchTLmatches] is true, also retrieves players old Tetra League - /// matches from p1nkl0bst3r's API. Returns list which contains [TetrioPlayer], his records, previous states, TL matches, previous TL state, - /// if player tracked (bool), news entries and topTR. - /// - /// If at least one request to Tetra Channel API fails, whole function will throw an exception. - Future fetch(String nickOrID, {bool fetchHistory = false, bool fetchTLmatches = false}) async { - TetrioPlayer me; - _TLHistoryWasFetched = false; - backgroundUpdate.cancel(); - - // If user trying to search with discord id - if (nickOrID.startsWith("ds:")){ - me = await teto.fetchPlayer(nickOrID.substring(3), isItDiscordID: true); // we trying to get him with that - }else{ - me = await teto.fetchPlayer(nickOrID); // Otherwise it's probably a user id or username - } - _searchFor = me.userId; // gonna use user id for next requests - - // Change view title and window title if avaliable - setState((){_titleNickname = me.username;}); - if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) await windowManager.setTitle(title); - - // Requesting Tetra League (alpha), records, news and top TR of player - List requests; - Summaries summaries = await teto.fetchSummaries(_searchFor); - late TetraLeagueBetaStream tlStream; - late News news; - // late SingleplayerStream recentSprint; - // late SingleplayerStream recentBlitz; - // late SingleplayerStream sprint; - // late SingleplayerStream blitz; - late SingleplayerStream recentZenith; - late SingleplayerStream recentZenithEX; - late TetrioPlayerFromLeaderboard? topOne; - // late TopTr? topTR; - requests = await Future.wait([ - teto.fetchSummaries(_searchFor), - teto.fetchTLStream(_searchFor), - teto.fetchNews(_searchFor), - teto.fetchStream(_searchFor, "zenith/recent"), - teto.fetchStream(_searchFor, "zenithex/recent"), - teto.fetchCutoffsBeanserver(), - (summaries.league.rank != "z" ? summaries.league.rank == "x+" : summaries.league.percentileRank == "x+") ? teto.fetchTopOneFromTheLeaderboard() : Future.delayed(Duration.zero, ()=>null), - ]); - //prefs.getBool("showPositions") != true ? teto.fetchCutoffsBeanserver() : Future.delayed(Duration.zero, ()=>>[]), - - //(summaries.league.gamesPlayed > 9) ? teto.fetchTopTR(_searchFor) : Future.delayed(Duration.zero, () => null) // can retrieve this only if player has TR - summaries = requests[0] as Summaries; - tlStream = requests[1] as TetraLeagueBetaStream; - // records = requests[1] as UserRecords; - news = requests[2] as News; - recentZenith = requests[3] as SingleplayerStream; - recentZenithEX = requests[4] as SingleplayerStream; - // recent = requests[3] as SingleplayerStream; - // sprint = requests[4] as SingleplayerStream; - // blitz = requests[5] as SingleplayerStream; - topOne = requests[6] as TetrioPlayerFromLeaderboard?; - // topTR = requests[8] as TopTr?; // No TR - no Top TR - - meAmongEveryone = teto.getCachedLeaderboardPositions(me.userId); - if (prefs.getBool("showPositions") == true){ - // Get tetra League leaderboard - everyone = teto.getCachedLeaderboard(); - everyone ??= await teto.fetchTLLeaderboard(); - if (meAmongEveryone == null && everyone!.leaderboard.isNotEmpty){ - meAmongEveryone = await compute(everyone!.getLeaderboardPosition, {me.userId: summaries.league}); - if (meAmongEveryone != null) teto.cacheLeaderboardPositions(me.userId, meAmongEveryone!); - } - } - Map? cutoffs = (requests[5] as Cutoffs?)?.tr; - Map? cutoffsGlicko = (requests[5] as Cutoffs?)?.glicko; - - if (summaries.league.gamesPlayed > 9) { - thatRankCutoff = cutoffs?[summaries.league.rank != "z" ? summaries.league.rank : summaries.league.percentileRank]; - thatRankGlickoCutoff = cutoffsGlicko?[summaries.league.rank != "z" ? summaries.league.rank : summaries.league.percentileRank]; - nextRankCutoff = (summaries.league.rank != "z" ? summaries.league.rank == "x+" : summaries.league.percentileRank == "x+") ? topOne?.tr??25000 : cutoffs?[ranks.elementAtOrNull(ranks.indexOf(summaries.league.rank != "z" ? summaries.league.rank : summaries.league.percentileRank)+1)]; - nextRankGlickoCutoff = (summaries.league.rank != "z" ? summaries.league.rank == "x+" : summaries.league.percentileRank == "x+") ? topOne?.glicko??double.infinity : cutoffsGlicko?[ranks.elementAtOrNull(ranks.indexOf(summaries.league.rank != "z" ? summaries.league.rank : summaries.league.percentileRank)+1)]; - } - - if (everyone != null && summaries.league.gamesPlayed > 9) rankAverages = everyone?.averages[summaries.league.percentileRank]?[0]; - - // Making list of Tetra League matches - bool isTracking = await teto.isPlayerTracking(me.userId); - List> states = await Future.wait>([ - teto.getStates(me.userId, season: 1), teto.getStates(me.userId, season: 2), - ]); - List storedRecords = await teto.getTLMatchesbyPlayerID(me.userId); // get old matches - if (isTracking){ // if tracked - save data to local DB - await teto.storeState(summaries.league); - //await teto.saveTLMatchesFromStream(tlStream); - } - TetraLeagueAlphaStream? oldMatches; - // building list of TL matches - if(fetchTLmatches) { - try{ - oldMatches = await teto.fetchAndSaveOldTLmatches(_searchFor); - if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.fetchAndSaveOldTLmatchesResult(number: oldMatches.records.length)))); - }on TetrioHistoryNotExist{ - if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.p1nkl0bst3rTLmatches))); - }on P1nkl0bst3rForbidden { - if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.p1nkl0bst3rForbidden))); - }on P1nkl0bst3rInternalProblem { - if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.p1nkl0bst3rinternal))); - }on P1nkl0bst3rTooManyRequests{ - if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.p1nkl0bst3rTooManyRequests))); - }finally{ - _TLHistoryWasFetched = true; - } - } - if (storedRecords.isNotEmpty) { - _TLHistoryWasFetched = true; - tlStream.addFromAlphaStream(storedRecords); - } - - // tlMatches.sort((a, b) { // Newest matches gonna be shown at the top of the list - // if(a.ts.isBefore(b.ts)) return 1; - // if(a.ts.isAtSameMomentAs(b.ts)) return 0; - // if(a.ts.isAfter(b.ts)) return -1; - // return 0; - // }); - - // Handling history - if(fetchHistory){ - try{ - var history = await teto.fetchAndsaveTLHistory(_searchFor); // Retrieve if needed - if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.fetchAndsaveTLHistoryResult(number: history.length)))); - }on TetrioHistoryNotExist{ - if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.noHistorySaved))); - }on P1nkl0bst3rForbidden { - if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.p1nkl0bst3rForbidden))); - }on P1nkl0bst3rInternalProblem { - if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.p1nkl0bst3rinternal))); - }on P1nkl0bst3rTooManyRequests{ - if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.p1nkl0bst3rTooManyRequests))); - } - } - - //states.addAll(await teto.getPlayer(me.userId)); - // for (var element in states) { // For graphs I need only unique entries - // if (element.tlSeason1 != null && uniqueTL.isNotEmpty && uniqueTL.last != element.tlSeason1) uniqueTL.add(element.tlSeason1!); - // if (uniqueTL.isEmpty) uniqueTL.add(summaries.league); - // } - if (states[1].length >= 2 || states[0].length >= 2){ - chartsData = [for (List s in states) >>[ // Dumping charts data into dropdown menu items, while cheking if every entry is valid - DropdownMenuItem(value: [for (var tl in s) if (tl.gamesPlayed > 9) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.tr)], child: Text(t.statCellNum.tr)), - DropdownMenuItem(value: [for (var tl in s) if (tl.gamesPlayed > 9) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.glicko!)], child: const Text("Glicko")), - DropdownMenuItem(value: [for (var tl in s) if (tl.gamesPlayed > 9) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.rd!)], child: const Text("Rating Deviation")), - DropdownMenuItem(value: [for (var tl in s) if (tl.apm != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.apm!)], child: Text(t.statCellNum.apm.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in s) if (tl.pps != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.pps!)], child: Text(t.statCellNum.pps.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in s) if (tl.vs != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.vs!)], child: Text(t.statCellNum.vs.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.app)], child: Text(t.statCellNum.app.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.dss)], child: Text(t.statCellNum.dss.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.dsp)], child: Text(t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.appdsp)], child: const Text("APP + DS/P")), - DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.vsapm)], child: const Text("VS/APM")), - DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.cheese)], child: Text(t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.gbe)], child: Text(t.statCellNum.gbe.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.nyaapp)], child: Text(t.statCellNum.nyaapp.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.area)], child: Text(t.statCellNum.area.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in s) if (tl.estTr != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.estTr!.esttr)], child: Text(t.statCellNum.estOfTR.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in s) if (tl.esttracc != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.esttracc!)], child: Text(t.statCellNum.accOfEst.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in s) if (tl.playstyle != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.playstyle!.opener)], child: const Text("Opener")), - DropdownMenuItem(value: [for (var tl in s) if (tl.playstyle != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.playstyle!.plonk)], child: const Text("Plonk")), - DropdownMenuItem(value: [for (var tl in s) if (tl.playstyle != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.playstyle!.infds)], child: const Text("Inf. DS")), - DropdownMenuItem(value: [for (var tl in s) if (tl.playstyle != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.playstyle!.stride)], child: const Text("Stride")), - ]]; - }else{ - chartsData = []; - } - - if (prefs.getBool("updateInBG") == true) { - backgroundUpdate = Timer(me.cachedUntil!.difference(DateTime.now()), () { - changePlayer(me.userId); - }); - } - return [me, summaries, news, tlStream, recentZenith, recentZenithEX, states[currentSeason-1]]; - //return [me, records, states, tlMatches, compareWith, isTracking, news, topTR, recent, sprint, blitz, tlMatches.elementAtOrNull(0)?.timestamp]; - } - - /// Triggers widgets rebuild - void _justUpdate() { - setState(() {}); - } - - void toggleZenith(){ - setState(() {zenithEX = !zenithEX;}); - } - - @override - Widget build(BuildContext context) { - final t = Translations.of(context); - bool bigScreen = MediaQuery.of(context).size.width > 1024; - return Scaffold( - drawer: widget.player == null ? NavDrawer(changePlayer) : null, // Side menu hidden if player provided - drawerEdgeDragWidth: MediaQuery.of(context).size.width * 0.2, // 20% of left side of the screen used of Drawer gesture - appBar: AppBar( - title: _showSearchBar ? SearchBox(onSubmit: changePlayer, bigScreen: MediaQuery.of(context).size.width > 768) : Text(title, style: const TextStyle(shadows: textShadow)), - backgroundColor: Colors.black, - actions: widget.player == null ? [ // search bar and PopupMenuButton hidden if player provided - _showSearchBar - ? IconButton( - onPressed: () { - setState(() { - _showSearchBar = false; - }); - }, - icon: const Icon(Icons.clear), - tooltip: t.closeSearch, - ) - : IconButton( - onPressed: () { - setState(() { - _showSearchBar = true; - }); - }, - icon: const Icon(Icons.search), - tooltip: t.openSearch, - ), - PopupMenuButton( - itemBuilder: (BuildContext context) => [ - PopupMenuItem( - value: "refresh", - child: Text(t.refresh), - ), - PopupMenuItem( - value: "history", - child: Text(t.fetchAndsaveTLHistory), - ), - PopupMenuItem( - value: "TLmatches", - child: Text(t.fetchAndSaveOldTLmatches), - ), - PopupMenuItem( - value: "/states", - child: Text(t.showStoredData), - ), - PopupMenuItem( - value: "/calc", - child: Text(t.statsCalc), - ), - PopupMenuItem( - value: "/settings", - child: Text(t.settings), - ), - ], - onSelected: (value) { - switch (value){ - case "refresh": - changePlayer(_searchFor); - break; - case "history": - changePlayer(_searchFor, fetchHistory: true); - break; - case "TLmatches": - changePlayer(_searchFor, fetchTLmatches: true); - break; - default: - context.go(value); - } - }, - ), - ] : null, - ), - body: SafeArea( - child: FutureBuilder>( - future: me, - 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 RefreshIndicator( - onRefresh: () { - return Future(() => changePlayer(snapshot.data![0].userId)); - }, - notificationPredicate: (notification) { - // with NestedScrollView local(depth == 2) OverscrollNotification are not sent - if (!kIsWeb && (notification is OverscrollNotification || Platform.isIOS)) { - return notification.depth == 2; - } - return notification.depth == 0; - }, - child: NestedScrollView( - scrollBehavior: ScrollConfiguration.of(context).copyWith(scrollbars: false, physics: const AlwaysScrollableScrollPhysics()), - controller: _scrollController, - headerSliverBuilder: (context, value) { - return [ - SliverToBoxAdapter( - child: UserThingy( - player: snapshot.data![0], - showStateTimestamp: false, - setState: _justUpdate, - )), - SliverToBoxAdapter( - child: TabBar( - controller: bigScreen ? _wideScreenTabController : _tabController, - padding: const EdgeInsets.all(0.0), - isScrollable: true, - tabs: bigScreen ? [ - Tab(text: t.tetraLeague,), - Tab(text: t.history), - Tab(text: t.quickPlay), - Tab(text: "${t.sprint} & ${t.blitz}"), - Tab(text: t.other), - ] : [ - Tab(text: t.tetraLeague), - Tab(text: t.tlRecords), - Tab(text: t.history), - Tab(text: t.quickPlay), - Tab(text: "${t.quickPlay} ${t.recent}"), - Tab(text: t.sprint), - Tab(text: t.blitz), - Tab(text: t.recentRuns), - Tab(text: t.other), - ], - ), - ), - ]; - }, - body: TabBarView( - controller: bigScreen ? _wideScreenTabController : _tabController, - children: bigScreen ? [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - width: MediaQuery.of(context).size.width-450, - constraints: const BoxConstraints(maxWidth: 1024), - child: TLThingy( - tl: snapshot.data![1].league, - userID: snapshot.data![0].userId, - states: snapshot.data![6], - //topTR: snapshot.data![7]?.tr, - //lastMatchPlayed: snapshot.data![11], - bot: snapshot.data![0].role == "bot", - guest: snapshot.data![0].role == "anon", - thatRankCutoff: thatRankCutoff, - thatRankCutoffGlicko: thatRankGlickoCutoff, - thatRankTarget: snapshot.data![1].league.rank != "z" ? rankTargets[snapshot.data![1].league.rank] : null, - nextRankCutoff: nextRankCutoff, - nextRankCutoffGlicko: nextRankGlickoCutoff, - nextRankTarget: (snapshot.data![1].league.rank != "z" && snapshot.data![1].league.rank != "x") ? rankTargets[ranks.elementAtOrNull(ranks.indexOf(snapshot.data![1].league.rank)+1)] : null, - averages: rankAverages, - lbPositions: meAmongEveryone - ), - ), - SizedBox( - width: 450, - child: _TLRecords(userID: snapshot.data![0].userId, changePlayer: changePlayer, data: snapshot.data![3].records, wasActiveInTL: true, oldMathcesHere: _TLHistoryWasFetched, separateScrollController: true) - ), - ],), - _History(chartsData: chartsData, changePlayer: changePlayer, userID: _searchFor, update: _justUpdate, wasActiveInTL: snapshot.data![1].league.gamesPlayed > 0), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - width: MediaQuery.of(context).size.width-450, - constraints: const BoxConstraints(maxWidth: 1024), - child: SingleChildScrollView(child: ZenithThingy(record: snapshot.data![1].zenith, recordEX: snapshot.data![1].zenithEx, parentZenithToggle: toggleZenith, initEXvalue: zenithEX)) - ), - SizedBox( - width: 450.0, - child: _ZenithRecords(userID: snapshot.data![0].userId, data: snapshot.data![zenithEX ? 5 : 4], separateScrollController: true), - ) - ], - ), - _TwoRecordsThingy(sprint: snapshot.data![1].sprint, blitz: snapshot.data![1].blitz, rank: snapshot.data![1].league.percentileRank, recent: SingleplayerStream(userId: "userId", records: [], type: "recent"), sprintStream: SingleplayerStream(userId: "userId", records: [], type: "40l"), blitzStream: SingleplayerStream(userId: "userId", records: [], type: "blitz")), - _OtherThingy(zen: snapshot.data![1].zen, bio: snapshot.data![0].bio, distinguishment: snapshot.data![0].distinguishment, newsletter: snapshot.data![2]) - ] : [ - TLThingy( - tl: snapshot.data![1].league, - userID: snapshot.data![0].userId, - states: const [], //snapshot.data![2], - //topTR: snapshot.data![7]?.tr, - //lastMatchPlayed: snapshot.data![11], - bot: snapshot.data![0].role == "bot", - guest: snapshot.data![0].role == "anon", - thatRankCutoff: thatRankCutoff, - thatRankCutoffGlicko: thatRankGlickoCutoff, - thatRankTarget: snapshot.data![1].league.rank != "z" ? rankTargets[snapshot.data![1].league.rank] : null, - nextRankCutoff: nextRankCutoff, - nextRankCutoffGlicko: nextRankGlickoCutoff, - nextRankTarget: (snapshot.data![1].league.rank != "z" && snapshot.data![1].league.rank != "x") ? rankTargets[ranks.elementAtOrNull(ranks.indexOf(snapshot.data![1].league.rank)+1)] : null, - averages: rankAverages, - lbPositions: meAmongEveryone - ), - _TLRecords(userID: snapshot.data![0].userId, changePlayer: changePlayer, data: snapshot.data![3].records, wasActiveInTL: true, oldMathcesHere: _TLHistoryWasFetched, separateScrollController: true), - _History(chartsData: chartsData, changePlayer: changePlayer, userID: _searchFor, update: _justUpdate, wasActiveInTL: snapshot.data![1].league.gamesPlayed > 0), - SingleChildScrollView(child: ZenithThingy(record: snapshot.data![1].zenith, recordEX: snapshot.data![1].zenithEx, parentZenithToggle: toggleZenith, initEXvalue: zenithEX)), - _ZenithRecords(userID: snapshot.data![0].userId, data: snapshot.data![zenithEX ? 5 : 4], separateScrollController: true), - SingleplayerRecord(record: snapshot.data![1].sprint, rank: snapshot.data![1].league.percentileRank, stream: SingleplayerStream(userId: "userId", records: [], type: "40l")), - SingleplayerRecord(record: snapshot.data![1].blitz, rank: snapshot.data![1].league.percentileRank, stream: SingleplayerStream(userId: "userId", records: [], type: "Blitz")), - _RecentSingleplayersThingy(SingleplayerStream(userId: "userId", records: [], type: "recent")), - _OtherThingy(zen: snapshot.data![1].zen, bio: snapshot.data![0].bio, distinguishment: snapshot.data![0].distinguishment, newsletter: snapshot.data![2]) - ], - ), - ), - ); - } else if (snapshot.hasError) { - String errText = ""; - String? subText; - switch (snapshot.error.runtimeType){ - case TetrioPlayerNotExist: - errText = t.errors.noSuchUser; - subText = t.errors.noSuchUserSub; - break; - case TetrioDiscordNotExist: - errText = t.errors.discordNotAssigned; - subText = t.errors.discordNotAssignedSub; - case ConnectionIssue: - var err = snapshot.error as ConnectionIssue; - errText = t.errors.connection(code: err.code, message: err.message); - break; - case TetrioForbidden: - 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 = snapshot.error.toString(); - subText = snapshot.stackTrace.toString(); - } - return Center(child: - Column( - mainAxisSize: MainAxisSize.min, - children: [ - 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, 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)); - }, - ), - ), - ); - } -} - -class NavDrawer extends StatefulWidget { - final Function changePlayer; - - /// Thing, that shows from the left side of the view. - /// Requires [changePlayer] function in order to be able to change players on main view - const NavDrawer(this.changePlayer, {super.key}); - - @override - State createState() => _NavDrawerState(); -} - -class _NavDrawerState extends State { - String homePlayerNickname = "Checking..."; - @override - void initState() { - super.initState(); - _setHomePlayerNickname(prefs.getString("player")); - } - - @override - void dispose() { - super.dispose(); - } - - /// Sets username for home button in NavDrawer. - /// Accepts [id] or username. If it's not provided, sets my nickname. - /// Otherwise, sets username or [id] if failed to find - Future _setHomePlayerNickname(String? id) async { - if (id != null) { - try { - homePlayerNickname = await teto.getNicknameByID(id); - } on TetrioPlayerNotExist { - homePlayerNickname = id; - } - } else { - homePlayerNickname = "dan63047"; - } - setState(() {}); - } - - @override - Widget build(BuildContext context) { - return Drawer( - child: StreamBuilder( - stream: teto.allPlayers, - builder: (context, snapshot) { - switch (snapshot.connectionState) { - case ConnectionState.none: - case ConnectionState.waiting: - case ConnectionState.active: - final allPlayers = (snapshot.data != null) - ? snapshot.data as Map - : {}; - allPlayers.remove(prefs.getString("player") ?? "6098518e3d5155e6ec429cdc"); // player from the home button will be delisted - List keys = allPlayers.keys.toList(); - return NestedScrollView( - headerSliverBuilder: (context, value) { - return [ - SliverToBoxAdapter( - child: DrawerHeader( - child: Text(t.playersYouTrack, style: const TextStyle(color: Colors.white, fontSize: 25), - ))), - SliverToBoxAdapter( - child: ListTile( // Home button - leading: const Icon(Icons.home), - title: Text(homePlayerNickname), - onTap: () { - widget.changePlayer(prefs.getString("player") ?? "6098518e3d5155e6ec429cdc"); // changes player on main view to the one from preferences - Navigator.of(context).pop(); // and then NavDrawer closes itself. - }, - ), - ), - SliverToBoxAdapter( - child: ListTile( // Leaderboard button - leading: const Icon(Icons.leaderboard), - title: Text(t.tlLeaderboard), - onTap: () { - context.go("/leaderboard"); - }, - ), - ), - SliverToBoxAdapter( - child: ListTile( // Rank averages button - leading: const Icon(Icons.compress), - title: Text(t.rankAveragesViewTitle), - onTap: () { - context.go("/LBvalues"); - }, - ), - ), - SliverToBoxAdapter( - child: ListTile( // Rank averages button - leading: const Icon(Icons.bar_chart), - title: Text(t.sprintAndBlitsViewTitle), - onTap: () { - context.go("/sprintAndBlitzAverages"); - }, - ), - ), - const SliverToBoxAdapter(child: Divider()) - ]; - }, - body: ListView.builder( // Builds list of tracked players. - itemCount: allPlayers.length, - itemBuilder: (context, index) { - var i = allPlayers.length-1-index; // Last players in this map are most recent ones, they are gonna be shown at the top. - return ListTile( - title: Text(allPlayers[keys[i]]??keys[i]), // Takes last known username from list of states - onTap: () { - widget.changePlayer(keys[i]); // changes to chosen player - Navigator.of(context).pop(); // and closes itself. - }, - ); - })); - case ConnectionState.done: - return const Center(child: Text('done case of StreamBuilder')); // what if that thing breaks? - } - }, - ), - ); - } -} - -class _TLRecords extends StatelessWidget { - final String userID; - final Function changePlayer; - final List data; - final bool wasActiveInTL; - final bool oldMathcesHere; - final bool separateScrollController; - - /// Widget, that displays Tetra League records. - /// Accepts list of TL records ([data]) and [userID] of player from the view - const _TLRecords({required this.userID, required this.changePlayer, required this.data, required this.wasActiveInTL, required this.oldMathcesHere, this.separateScrollController = false}); - - @override - Widget build(BuildContext context) { - if (data.isEmpty) { - return Center(child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(t.noRecords, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28)), - if (wasActiveInTL) Text(t.errors.actionSuggestion), - if (wasActiveInTL) TextButton(onPressed: (){changePlayer(userID, fetchTLmatches: true);}, child: Text(t.fetchAndSaveOldTLmatches)) - ], - )); - } - bool bigScreen = MediaQuery.of(context).size.width >= 768; - int length = data.length; - return ListView.builder( - physics: const AlwaysScrollableScrollPhysics(), - controller: separateScrollController ? ScrollController() : null, - itemCount: oldMathcesHere ? length : length + 1, - itemBuilder: (BuildContext context, int index) { - if (index == length) { - return Center(child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(t.noOldRecords(n: length), style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28)), - if (wasActiveInTL) Text(t.errors.actionSuggestion), - if (wasActiveInTL) TextButton(onPressed: (){changePlayer(userID, fetchTLmatches: true);}, child: Text(t.fetchAndSaveOldTLmatches)) - ], - )); - } - - var accentColor = data[index].results.leaderboard.firstWhere((element) => element.id == userID).wins > data[index].results.leaderboard.firstWhere((element) => element.id != userID).wins ? Colors.green : Colors.red; - return Container( - decoration: BoxDecoration( - gradient: LinearGradient( - stops: const [0, 0.05], - colors: [accentColor, Colors.transparent] - ) - ), - child: ListTile( - leading: Text("${data[index].results.leaderboard.firstWhere((element) => element.id == userID).wins} : ${data[index].results.leaderboard.firstWhere((element) => element.id != userID).wins}", - style: bigScreen ? const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28, shadows: textShadow) : const TextStyle(fontSize: 28, shadows: textShadow)), - title: Text("vs. ${data[index].results.leaderboard.firstWhere((element) => element.id != userID).username}"), - subtitle: Text(timestamp(data[index].ts), style: const TextStyle(color: Colors.grey)), - trailing: TrailingStats( - data[index].results.leaderboard.firstWhere((element) => element.id == userID).stats.apm, - data[index].results.leaderboard.firstWhere((element) => element.id == userID).stats.pps, - data[index].results.leaderboard.firstWhere((element) => element.id == userID).stats.vs, - data[index].results.leaderboard.firstWhere((element) => element.id != userID).stats.apm, - data[index].results.leaderboard.firstWhere((element) => element.id != userID).stats.pps, - data[index].results.leaderboard.firstWhere((element) => element.id != userID).stats.vs, - ), - onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => TlMatchResultView(record: data[index], initPlayerId: userID))) //Navigator.push(context, MaterialPageRoute(builder: (context) => TlMatchResultView(record: data[index], initPlayerId: userID))), - ), - ); - }); - } -} - -class _ZenithRecords extends StatelessWidget { - final String userID; - final SingleplayerStream data; - final bool separateScrollController; - - /// Widget, that displays Quick Play records. - /// Accepts list of TL records ([data]) and [userID] of player from the view - const _ZenithRecords({required this.userID, required this.data, this.separateScrollController = false}); - - @override - Widget build(BuildContext context) { - if (data.records.isEmpty) { - return Center(child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(t.noRecords, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28)), - ], - )); - } - bool bigScreen = MediaQuery.of(context).size.width >= 768; - int length = data.records.length; - return ListView.builder( - physics: const AlwaysScrollableScrollPhysics(), - controller: separateScrollController ? ScrollController() : null, - itemCount: length + 1, - itemBuilder: (BuildContext context, int index) { - if (index == length) { - return Center(child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(t.noOldRecords(n: length), style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28)), - ], - )); - } - const TextStyle style = TextStyle(height: 1.1, fontWeight: FontWeight.w100, fontSize: 13); - return Container( - child: ListTile( - leading: Text("QP", - style: bigScreen ? const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28, shadows: textShadow) : const TextStyle(fontSize: 28, shadows: textShadow)), - title: Text("${f2.format(data.records[index].stats.zenith!.altitude)} m${(data.records[index].extras as ZenithExtras).mods.isNotEmpty ? " (${t.withModsPlural(n: (data.records[index].extras as ZenithExtras).mods.length)})" : ""}"), - subtitle: Text(timestamp(data.records[index].timestamp), style: const TextStyle(color: Colors.grey)), - trailing: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text("${f2.format(data.records[index].aggregateStats.apm)} APM, ${f2.format(data.records[index].aggregateStats.pps)} PPS", style: style, textAlign: TextAlign.right), - Text("${f2.format(data.records[index].stats.cps)} CSP (${f2.format(data.records[index].stats.zenith!.peakrank)} peak)", style: style, textAlign: TextAlign.right), - Text("${data.records[index].stats.kills} KO's, ${getMoreNormalTime(data.records[index].stats.finalTime)}", style: style, textAlign: TextAlign.right) - ], - ), - onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => ZenithRecordView(record: data.records[index]))) //Navigator.push(context, MaterialPageRoute(builder: (context) => TlMatchResultView(record: data[index], initPlayerId: userID))), - ), - ); - }); - } -} - -class _History extends StatelessWidget{ - final List>>> chartsData; - final String userID; - final Function update; - final Function changePlayer; - final bool wasActiveInTL; - - /// Widget, that can show history of some stat of the player on the graph. - /// Requires player [states], which is list of states and function [update], which rebuild widgets - const _History({required this.chartsData, required this.userID, required this.changePlayer, required this.update, required this.wasActiveInTL}); - - @override - Widget build(BuildContext context) { - if (chartsData.isEmpty) { - return Center(child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(t.noHistorySaved, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28)), - if (wasActiveInTL) Text(t.errors.actionSuggestion), - if (wasActiveInTL) TextButton(onPressed: (){changePlayer(userID, fetchHistory: true);}, child: Text(t.fetchAndsaveTLHistory)) - ], - )); - } - bool bigScreen = MediaQuery.of(context).size.width > 768; - List<_HistoryChartSpot> selectedGraph = chartsData[_season][_chartsIndex].value!; - return SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Wrap( - spacing: 20, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Padding(padding: EdgeInsets.all(8.0), child: Text("Season:", style: TextStyle(fontSize: 22))), - DropdownButton( - items: [for (int i = 1; i <= currentSeason; i++) DropdownMenuItem(value: i-1, child: Text("$i"))], - value: _season, - onChanged: (value) { - _season = value!; - update(); - } - ), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Padding(padding: EdgeInsets.all(8.0), child: Text("X:", style: TextStyle(fontSize: 22))), - DropdownButton( - items: const [DropdownMenuItem(value: false, child: Text("Date & Time")), DropdownMenuItem(value: true, child: Text("Games Played"))], - value: _gamesPlayedInsteadOfDateAndTime, - onChanged: (value) { - _gamesPlayedInsteadOfDateAndTime = value!; - update(); - } - ), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Padding(padding: EdgeInsets.all(8.0), child: Text("Y:", style: TextStyle(fontSize: 22))), - DropdownButton( - items: chartsData[_season], - value: chartsData[_season][_chartsIndex].value, - onChanged: (value) { - _chartsIndex = chartsData[_season].indexWhere((element) => element.value == value); - update(); - } - ), - ], - ), - if (selectedGraph.length > 300) Row( - mainAxisSize: MainAxisSize.min, - children: [ - Checkbox(value: _smooth, - checkColor: Colors.black, - onChanged: ((value) { - _smooth = value!; - update(); - })), - Text(t.smooth, style: const TextStyle(color: Colors.white, fontSize: 22)) - ], - ), - IconButton(onPressed: () => _zoomPanBehavior.reset(), icon: const Icon(Icons.refresh), alignment: Alignment.center,) - ], - ), - if(chartsData[_season][_chartsIndex].value!.length > 1) _HistoryChartThigy(data: selectedGraph, smooth: _smooth, yAxisTitle: _historyShortTitles[_chartsIndex], bigScreen: bigScreen, leftSpace: bigScreen? 80 : 45, yFormat: bigScreen? f2 : NumberFormat.compact(), xFormat: NumberFormat.compact()) - else if (chartsData[_season][_chartsIndex].value!.length <= 1) Center(child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(t.notEnoughData, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28)), - if (wasActiveInTL && _season == 0) Text(t.errors.actionSuggestion), - if (wasActiveInTL && _season == 0) TextButton(onPressed: (){changePlayer(userID, fetchHistory: true);}, child: Text(t.fetchAndsaveTLHistory)) - ], - )) - ], - ), - ); - } -} - -class _HistoryChartSpot{ - final DateTime timestamp; - final int gamesPlayed; - final String rank; - final double stat; - const _HistoryChartSpot(this.timestamp, this.gamesPlayed, this.rank, this.stat); -} - -class _HistoryChartThigy extends StatefulWidget{ - final List<_HistoryChartSpot> data; - final bool smooth; - final String yAxisTitle; - final bool bigScreen; - final double leftSpace; - final NumberFormat yFormat; - final NumberFormat? xFormat; - - /// Implements graph for the _History widget. Requires [data] which is a list of dots for the graph. [yAxisTitle] used to keep track of changes. - /// [bigScreen] tells if screen wide enough, [leftSpace] sets size, reserved for titles on the left from the graph and [yFormat] sets number format - /// for left titles - const _HistoryChartThigy({required this.data, required this.smooth, required this.yAxisTitle, required this.bigScreen, required this.leftSpace, required this.yFormat, this.xFormat}); - - @override - State<_HistoryChartThigy> createState() => _HistoryChartThigyState(); -} - -class _HistoryChartThigyState extends State<_HistoryChartThigy> { - late String previousAxisTitle; - late bool previousGamesPlayedInsteadOfDateAndTime; - late TooltipBehavior _tooltipBehavior; - - - @override - void initState(){ - super.initState(); - _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( - "${f4.format(data.stat)} ${widget.yAxisTitle}", - style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 20), - ), - ), - Text(_gamesPlayedInsteadOfDateAndTime ? t.gamesPlayed(games: t.games(n: data.gamesPlayed)) : timestamp(data.timestamp)) - ], - ), - ); - } - ); - previousAxisTitle = widget.yAxisTitle; - previousGamesPlayedInsteadOfDateAndTime = _gamesPlayedInsteadOfDateAndTime; - } - - @override - void dispose(){ - super.dispose(); -} - - @override - Widget build(BuildContext context) { - if ((previousAxisTitle != widget.yAxisTitle) || (previousGamesPlayedInsteadOfDateAndTime != _gamesPlayedInsteadOfDateAndTime)) { - previousAxisTitle = widget.yAxisTitle; - previousGamesPlayedInsteadOfDateAndTime = _gamesPlayedInsteadOfDateAndTime; - setState((){}); - } - EdgeInsets padding = widget.bigScreen ? const EdgeInsets.fromLTRB(40, 30, 40, 30) : const EdgeInsets.fromLTRB(0, 40, 16, 48); - return SizedBox( - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height - 104, - child: Padding( padding: padding, - 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: _gamesPlayedInsteadOfDateAndTime ? const NumericAxis() : const DateTimeAxis(), - primaryYAxis: const NumericAxis( - rangePadding: ChartRangePadding.additional, - ), - margin: const EdgeInsets.all(0), - series: [ - if (_gamesPlayedInsteadOfDateAndTime) StepLineSeries<_HistoryChartSpot, int>( - enableTooltip: true, - dataSource: widget.data, - animationDuration: 0, - opacity: _smooth ? 0 : 1, - xValueMapper: (_HistoryChartSpot data, _) => data.gamesPlayed, - yValueMapper: (_HistoryChartSpot data, _) => data.stat, - color: Theme.of(context).colorScheme.primary, - trendlines:[ - Trendline( - isVisible: _smooth, - period: (widget.data.length/175).floor(), - type: TrendlineType.movingAverage, - color: Theme.of(context).colorScheme.primary) - ], - ) - else StepLineSeries<_HistoryChartSpot, DateTime>( - enableTooltip: true, - dataSource: widget.data, - animationDuration: 0, - opacity: _smooth ? 0 : 1, - xValueMapper: (_HistoryChartSpot data, _) => data.timestamp, - yValueMapper: (_HistoryChartSpot data, _) => data.stat, - color: Theme.of(context).colorScheme.primary, - trendlines:[ - Trendline( - isVisible: _smooth, - period: (widget.data.length/175).floor(), - type: TrendlineType.movingAverage, - color: Theme.of(context).colorScheme.primary) - ], - ), - ], - ), - ), - ) - ); - } -} - -class _TwoRecordsThingy extends StatelessWidget { - final RecordSingle? sprint; - final RecordSingle? blitz; - final SingleplayerStream recent; - final SingleplayerStream sprintStream; - final SingleplayerStream blitzStream; - final String? rank; - - const _TwoRecordsThingy({required this.sprint, required this.blitz, this.rank, required this.recent, required this.sprintStream, required this.blitzStream}); - - Color getColorOfRank(int rank){ - if (rank == 1) return Colors.yellowAccent; - if (rank == 2) return Colors.blueGrey; - if (rank == 3) return Colors.brown[400]!; - if (rank <= 9) return Colors.blueAccent; - if (rank <= 99) return Colors.greenAccent; - return Colors.grey; - } - - @override - Widget build(BuildContext context) { - late MapEntry closestAverageBlitz; - late bool blitzBetterThanClosestAverage; - bool? blitzBetterThanRankAverage = (rank != null && rank != "z" && blitz != null) ? blitz!.stats.score > blitzAverages[rank]! : null; - late MapEntry closestAverageSprint; - late bool sprintBetterThanClosestAverage; - bool? sprintBetterThanRankAverage = (rank != null && rank != "z" && sprint != null) ? sprint!.stats.finalTime < sprintAverages[rank]! : null; - if (sprint != null) { - closestAverageSprint = sprintAverages.entries.singleWhere((element) => element.value == sprintAverages.values.reduce((a, b) => (a-sprint!.stats.finalTime).abs() < (b -sprint!.stats.finalTime).abs() ? a : b)); - sprintBetterThanClosestAverage = sprint!.stats.finalTime < closestAverageSprint.value; - } - if (blitz != null){ - closestAverageBlitz = blitzAverages.entries.singleWhere((element) => element.value == blitzAverages.values.reduce((a, b) => (a-blitz!.stats.score).abs() < (b -blitz!.stats.score).abs() ? a : b)); - blitzBetterThanClosestAverage = blitz!.stats.score > closestAverageBlitz.value; - } - return SingleChildScrollView(child: Padding( - padding: const EdgeInsets.only(top: 20.0), - child: Wrap( - alignment: WrapAlignment.spaceEvenly, - crossAxisAlignment: WrapCrossAlignment.start, - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Padding(padding: const EdgeInsets.only(right: 8.0), - child: sprint != null ? Image.asset("res/tetrio_tl_alpha_ranks/${closestAverageSprint.key}.png", height: 96) : Image.asset("res/tetrio_tl_alpha_ranks/z.png", height: 96) - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(t.sprint, style: const TextStyle(height: 0.1, fontFamily: "Eurostile Round Extended", fontSize: 18)), - RichText(text: TextSpan( - text: sprint != null ? get40lTime(sprint!.stats.finalTime.inMicroseconds) : "---", - style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, fontWeight: FontWeight.w500, color: sprint != null ? Colors.white : Colors.grey), - //children: [TextSpan(text: get40lTime(record!.stats.finalTime.inMicroseconds), style: TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100))] - ), - ), - if (sprint != null) RichText(text: TextSpan( - text: "", - style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey), - children: [ - if (rank != null && rank != "z") TextSpan(text: "${t.verdictGeneral(n: readableTimeDifference(sprint!.stats.finalTime, sprintAverages[rank]!), verdict: sprintBetterThanRankAverage??false ? t.verdictBetter : t.verdictWorse, rank: rank!.toUpperCase())}\n", style: TextStyle( - color: sprintBetterThanRankAverage??false ? Colors.greenAccent : Colors.redAccent - )) - else TextSpan(text: "${t.verdictGeneral(n: readableTimeDifference(sprint!.stats.finalTime, closestAverageSprint.value), verdict: sprintBetterThanClosestAverage ? t.verdictBetter : t.verdictWorse, rank: closestAverageSprint.key.toUpperCase())}\n", style: TextStyle( - color: sprintBetterThanClosestAverage ? Colors.greenAccent : Colors.redAccent - )), - TextSpan(text: "№${sprint!.rank}", style: TextStyle(color: getColorOfRank(sprint!.rank))), - const TextSpan(text: " • "), - TextSpan(text: timestamp(sprint!.timestamp)), - ] - ), - ), - ],), - ], - ), - if (sprint != null) Wrap( - //mainAxisSize: MainAxisSize.max, - alignment: WrapAlignment.spaceBetween, - spacing: 20, - children: [ - StatCellNum(playerStat: sprint!.stats.piecesPlaced, playerStatLabel: t.statCellNum.pieces, isScreenBig: true, higherIsBetter: true, smallDecimal: false), - StatCellNum(playerStat: sprint!.stats.pps, playerStatLabel: t.statCellNum.pps, fractionDigits: 2, isScreenBig: true, higherIsBetter: true, smallDecimal: false), - StatCellNum(playerStat: sprint!.stats.kpp, playerStatLabel: t.statCellNum.kpp, fractionDigits: 2, isScreenBig: true, higherIsBetter: true, smallDecimal: false), - ], - ), - if (sprint != null) FinesseThingy(sprint?.stats.finesse, sprint?.stats.finessePercentage), - if (sprint != null) LineclearsThingy(sprint!.stats.clears, sprint!.stats.lines, sprint!.stats.holds, sprint!.stats.tSpins), - if (sprint != null) Text("${sprint!.stats.inputs} KP • ${f2.format(sprint!.stats.kps)} KPS"), - if (sprint != null) Wrap( - alignment: WrapAlignment.spaceBetween, - crossAxisAlignment: WrapCrossAlignment.start, - spacing: 20, - children: [ - TextButton(onPressed: (){launchInBrowser(Uri.parse("https://tetr.io/#r:${sprint!.replayId}"));}, child: Text(t.openSPreplay)), - TextButton(onPressed: (){launchInBrowser(Uri.parse("https://inoue.szy.lol/api/replay/${sprint!.replayId}"));}, child: Text(t.downloadSPreplay)), - ], - ), - if (sprintStream.records.length > 1) SizedBox( - width: 400, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - for (int i = 1; i < sprintStream.records.length; i++) ListTile( - onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => SingleplayerRecordView(record: sprintStream.records[i]))), - leading: Text("#${i+1}", style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28, shadows: textShadow, height: 0.9) ), - title: Text(get40lTime(sprintStream.records[i].stats.finalTime.inMicroseconds), - style: Theme.of(context).textTheme.displayLarge), - subtitle: Text(timestamp(sprintStream.records[i].timestamp), style: const TextStyle(color: Colors.grey, height: 0.85)), - trailing: SpTrailingStats(sprintStream.records[i], sprintStream.records[i].gamemode) - ) - ], - ), - ) - ] - ), - Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text(t.blitz, style: const TextStyle(height: 0.1, fontFamily: "Eurostile Round Extended", fontSize: 18)), - RichText( - text: TextSpan( - text: "", - style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, fontWeight: FontWeight.w500, color: Colors.white), - children: [ - TextSpan(text: blitz != null ? NumberFormat.decimalPattern().format(blitz!.stats.score) : "---"), - //WidgetSpan(child: Image.asset("res/icons/kagari.png", height: 48)) - ] - ), - ), - if (blitz != null) RichText( - textAlign: TextAlign.end, - text: TextSpan( - text: "", - style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey), - children: [ - if (rank != null && rank != "z") TextSpan(text: "${t.verdictGeneral(n: readableIntDifference(blitz!.stats.score, blitzAverages[rank]!), verdict: blitzBetterThanRankAverage??false ? t.verdictBetter : t.verdictWorse, rank: rank!.toUpperCase())}\n", style: TextStyle( - color: blitzBetterThanRankAverage??false ? Colors.greenAccent : Colors.redAccent - )) - else TextSpan(text: "${t.verdictGeneral(n: readableIntDifference(blitz!.stats.score, closestAverageBlitz.value), verdict: blitzBetterThanClosestAverage ? t.verdictBetter : t.verdictWorse, rank: closestAverageBlitz.key.toUpperCase())}\n", style: TextStyle( - color: blitzBetterThanClosestAverage ? Colors.greenAccent : Colors.redAccent - )), - TextSpan(text: timestamp(blitz!.timestamp)), - const TextSpan(text: " • "), - TextSpan(text: "№${blitz!.rank}", style: TextStyle(color: getColorOfRank(blitz!.rank))), - ] - ), - ), - ],), - Padding(padding: const EdgeInsets.only(left: 8.0), - child: blitz != null ? Image.asset("res/tetrio_tl_alpha_ranks/${closestAverageBlitz.key}.png", height: 96) : Image.asset("res/tetrio_tl_alpha_ranks/z.png", height: 96)), - ], - ), - if (blitz != null) Wrap( - //mainAxisSize: MainAxisSize.max, - alignment: WrapAlignment.spaceBetween, - crossAxisAlignment: WrapCrossAlignment.start, - spacing: 20, - children: [ - StatCellNum(playerStat: blitz!.stats.level, playerStatLabel: t.statCellNum.level, isScreenBig: true, higherIsBetter: true, smallDecimal: false), - StatCellNum(playerStat: blitz!.stats.pps, playerStatLabel: t.statCellNum.pps, fractionDigits: 2, isScreenBig: true, higherIsBetter: true, smallDecimal: false), - StatCellNum(playerStat: blitz!.stats.spp, playerStatLabel: t.statCellNum.spp, fractionDigits: 2, isScreenBig: true, higherIsBetter: true) - ], - ), - if (blitz != null) FinesseThingy(blitz?.stats.finesse, blitz?.stats.finessePercentage), - if (blitz != null) LineclearsThingy(blitz!.stats.clears, blitz!.stats.lines, blitz!.stats.holds, blitz!.stats.tSpins), - if (blitz != null) Text("${blitz!.stats.piecesPlaced} P • ${blitz!.stats.inputs} KP • ${f2.format(blitz!.stats.kpp)} KPP • ${f2.format(blitz!.stats.kps)} KPS"), - if (blitz != null) Wrap( - alignment: WrapAlignment.spaceBetween, - crossAxisAlignment: WrapCrossAlignment.start, - spacing: 20, - children: [ - TextButton(onPressed: (){launchInBrowser(Uri.parse("https://tetr.io/#r:${blitz!.replayId}"));}, child: Text(t.openSPreplay)), - TextButton(onPressed: (){launchInBrowser(Uri.parse("https://inoue.szy.lol/api/replay/${blitz!.replayId}"));}, child: Text(t.downloadSPreplay)), - ], - ), - if (blitzStream.records.length > 1) SizedBox( - width: 400, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - for (int i = 1; i < blitzStream.records.length; i++) ListTile( - onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => SingleplayerRecordView(record: blitzStream.records[i]))), - leading: Text("#${i+1}", style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28, shadows: textShadow, height: 0.9) ), - title: Text("${NumberFormat.decimalPattern().format(blitzStream.records[i].stats.score)} points", - style: Theme.of(context).textTheme.displayLarge), - subtitle: Text(timestamp(blitzStream.records[i].timestamp), style: const TextStyle(color: Colors.grey, height: 0.85)), - trailing: SpTrailingStats(blitzStream.records[i], blitzStream.records[i].gamemode) - ) - ], - ), - ) - ], - ), - SizedBox( - width: 400, - child: RecentSingleplayerGames(recent: recent), - ) - ]), - )); - } -} - -class _RecentSingleplayersThingy extends StatelessWidget { - final SingleplayerStream recent; - - const _RecentSingleplayersThingy(this.recent); - - @override - Widget build(BuildContext context) { - return SingleChildScrollView( - child: RecentSingleplayerGames(recent: recent, hideTitle: true) - ); - } -} - -class _OtherThingy extends StatelessWidget { - final TetrioZen? zen; - final String? bio; - final Distinguishment? distinguishment; - final News? newsletter; - - /// Widget, that shows players [distinguishment], [bio], [zen] and [newsletter] - const _OtherThingy({required this.zen, required this.bio, required this.distinguishment, this.newsletter}); - - /// Distinguishment title is not very predictable thing. - /// Receives [text], which is header and returns sets of widgets for RichText widget - List 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 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; - } - - /// Handles [news] entry and returns widget that contains this entry - ListTile getNewsTile(NewsEntry news){ - Map 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)), - ); - } - } - - Widget getShit(BuildContext context, bool bigScreen, bool showNewsTitle){ - return Column( - children: [ - if (distinguishment != null) - Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 0, 48), - child: Column( - children: [ - Text(t.distinguishment, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28), textAlign: TextAlign.center), - RichText( - textAlign: TextAlign.center, - text: TextSpan( - style: DefaultTextStyle.of(context).style, - children: getDistinguishmentTitle(distinguishment?.header), - ), - ), - Text(getDistinguishmentSubtitle(distinguishment?.footer), style: Theme.of(context).textTheme.displayLarge, textAlign: TextAlign.center), - ], - ), - ), - if (bio != null) - Padding( - padding: const EdgeInsets.fromLTRB(8, 0, 8, 48), - child: Column( - children: [ - Text(t.bio, style: TextStyle(fontFamily: "Eurostile Round Extended",fontSize: bigScreen ? 42 : 28)), - MarkdownBody(data: bio!, styleSheet: MarkdownStyleSheet(textScaler: TextScaler.linear(1.5), textAlign: WrapAlignment.center)) // Text(bio!, style: const Theme.of(context).textTheme.displayLarge), - ], - ), - ), - if (zen != null) - Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 0, 48), - child: Column( - children: [ - Text(t.zen, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), - Text("${t.statCellNum.level} ${NumberFormat.decimalPattern().format(zen!.level)}", style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold)), - Text("${t.statCellNum.score} ${NumberFormat.decimalPattern().format(zen!.score)}", style: Theme.of(context).textTheme.displayLarge), - Container( - constraints: const BoxConstraints(maxWidth: 300.0), - child: Row(children: [ - const Text("Score requirement to level up:"), - const Spacer(), - Text(intf.format(zen!.scoreRequirement)) - ],), - ) - ], - ), - ), - if (newsletter != null && newsletter!.news.isNotEmpty && showNewsTitle) - Text(t.news, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), - ], - ); - } - - @override - Widget build(BuildContext context) { - return LayoutBuilder(builder: (context, constraints) { - bool bigScreen = constraints.maxWidth > 768; - if (constraints.maxWidth >= 1024){ - return Row( - children: [ - SizedBox(width: 450, child: getShit(context, true, false)), - SizedBox(width: constraints.maxWidth - 450, child: ListView.builder( - physics: const AlwaysScrollableScrollPhysics(), - itemCount: newsletter!.news.length+1, - itemBuilder: (BuildContext context, int index) { - return index == 0 ? Center(child: Text(t.news, style: Theme.of(context).textTheme.titleLarge)) : getNewsTile(newsletter!.news[index-1]); - } - )) - ] - ); - } - else { - return ListView.builder( - physics: const AlwaysScrollableScrollPhysics(), - itemCount: newsletter!.news.length+1, - itemBuilder: (BuildContext context, int index) { - return index == 0 ? getShit(context, bigScreen, true) : getNewsTile(newsletter!.news[index-1]); - }, - ); - } - }); - } -} +import 'dart:async'; +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/news.dart'; +import 'package:tetra_stats/data_objects/p1nkl0bst3r.dart'; +import 'package:tetra_stats/data_objects/player_leaderboard_position.dart'; +import 'package:tetra_stats/data_objects/summaries.dart'; +import 'package:tetra_stats/data_objects/tetra_league.dart'; +import 'package:tetra_stats/data_objects/tetrio_constants.dart'; +import 'package:tetra_stats/data_objects/tetrio_player.dart'; +import 'package:tetra_stats/data_objects/tetrio_players_leaderboard.dart'; +import 'package:tetra_stats/gen/strings.g.dart'; +import 'package:tetra_stats/services/crud_exceptions.dart'; +import 'package:tetra_stats/views/destination_calculator.dart'; +import 'package:tetra_stats/views/destination_cutoffs.dart'; +import 'package:tetra_stats/views/destination_graphs.dart'; +import 'package:tetra_stats/views/destination_home.dart'; +import 'package:tetra_stats/views/destination_info.dart'; +import 'package:tetra_stats/views/destination_leaderboards.dart'; +import 'package:tetra_stats/views/destination_saved_data.dart'; +import 'package:tetra_stats/views/destination_settings.dart'; +import 'package:tetra_stats/main.dart'; + +late Future _data; +late Future _newsData; +TetrioPlayersLeaderboard? _everyone; + +Future getData(String searchFor) async { + TetrioPlayer player; + try{ + if (searchFor.startsWith("ds:")){ + player = await teto.fetchPlayer(searchFor.substring(3), isItDiscordID: true); // we trying to get him with that + }else{ + player = await teto.fetchPlayer(searchFor); // Otherwise it's probably a user id or username + } + + }on TetrioPlayerNotExist{ + return FetchResults(false, null, [], null, null, null, null, false, TetrioPlayerNotExist()); + } + late Summaries summaries; + late Cutoffs? cutoffs; + late CutoffsTetrio? averages; + try { + List requests = await Future.wait([ + teto.fetchSummaries(player.userId), + teto.fetchCutoffsBeanserver(), + if (prefs.getBool("showAverages") == true) teto.fetchCutoffsTetrio() + ]); + + summaries = requests[0]; + cutoffs = requests.elementAtOrNull(1); + averages = requests.elementAtOrNull(2); + } on Exception catch (e) { + return FetchResults(false, null, [], null, null, null, null, false, e); + } + PlayerLeaderboardPosition? _meAmongEveryone; + if (prefs.getBool("showPositions") == true){ + // Get tetra League leaderboard + _everyone = teto.getCachedLeaderboard(); + _everyone ??= await teto.fetchTLLeaderboard(); + if (_everyone!.leaderboard.isNotEmpty){ + _meAmongEveryone = await compute(_everyone!.getLeaderboardPosition, {player.userId: summaries.league}); + if (_meAmongEveryone != null) teto.cacheLeaderboardPositions(player.userId, _meAmongEveryone); + } + } + List states = await teto.getStates(player.userId, season: currentSeason); + + bool isTracking = await teto.isPlayerTracking(player.userId); + if (isTracking){ // if tracked - save data to local DB + await teto.storeState(summaries.league); + } + + return FetchResults(true, player, states, summaries, cutoffs, averages, _meAmongEveryone, isTracking, null); + } + +class MainView extends StatefulWidget { + final String? player; + /// The very first view, that user see when he launch this programm. + /// By default it loads my or defined in preferences user stats, but + /// if [player] username or id provided, it loads his stats. Also it hides menu drawer and three dots menu. + const MainView({super.key, this.player}); + + @override + State createState() => _MainState(); +} + +enum Cards {overview, tetraLeague, quickPlay, sprint, blitz} +enum CardMod {info, records, ex, exRecords} +Map cardsTitles = { + Cards.overview: "Overview", + Cards.tetraLeague: t.tetraLeague, + Cards.quickPlay: t.quickPlay, + //Cards.quickPlayExpert: "${t.quickPlay} ${t.expert}", + Cards.sprint: t.sprint, + Cards.blitz: t.blitz, + //Cards.other: t.other +}; + +late ScrollController controller; + +class _MainState extends State with TickerProviderStateMixin { + int destination = 0; + String _searchFor = "6098518e3d5155e6ec429cdc"; + final TextEditingController _searchController = TextEditingController(); + + @override + void initState() { + teto.open(); + controller = ScrollController(); + changePlayer(_searchFor); + super.initState(); + } + + void changePlayer(String player) { + setState(() { + _searchFor = player; + _data = getData(_searchFor); + _newsData = teto.fetchNews(_searchFor); + }); + } + + @override + void dispose() { + controller.dispose(); + _searchController.dispose(); + super.dispose(); + } + + NavigationRailDestination getDestinationButton(IconData icon, String title){ + return NavigationRailDestination( + icon: Tooltip( + message: title, + child: Icon(icon) + ), + selectedIcon: Icon(icon), + label: Text(title), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + drawer: SearchDrawer(changePlayer: changePlayer, controller: _searchController), + body: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TweenAnimationBuilder( + child: NavigationRail( + leading: FloatingActionButton( + elevation: 0, + onPressed: () { + Scaffold.of(context).openDrawer(); + }, + child: const Icon(Icons.search), + ), + trailing: IconButton( + onPressed: () { + // Add your onPressed code here! + }, + icon: const Icon(Icons.more_horiz_rounded), + ), + destinations: [ + getDestinationButton(Icons.home, "Home"), + getDestinationButton(Icons.data_thresholding_outlined, "Graphs"), + getDestinationButton(Icons.leaderboard, "Leaderboards"), + getDestinationButton(Icons.compress, "Cutoffs"), + getDestinationButton(Icons.calculate, "Calc"), + getDestinationButton(Icons.info_outline, "Information"), + getDestinationButton(Icons.storage, "Saved Data"), + getDestinationButton(Icons.settings, "Settings"), + ], + selectedIndex: destination, + onDestinationSelected: (value) { + setState(() { + destination = value; + }); + }, + ), + duration: Durations.long4, + tween: Tween(begin: 0, end: 1), + curve: Easing.standard, + builder: (context, value, child) { + return Container( + transform: Matrix4.translationValues(-80+value*80, 0, 0), + child: Opacity(opacity: value, child: child), + ); + }, + ), + Expanded( + child: switch (destination){ + 0 => DestinationHome(searchFor: _searchFor, constraints: constraints, dataFuture: _data, newsFuture: _newsData), + 1 => DestinationGraphs(searchFor: _searchFor, constraints: constraints), + 2 => DestinationLeaderboards(constraints: constraints), + 3 => DestinationCutoffs(constraints: constraints), + 4 => DestinationCalculator(constraints: constraints), + 5 => DestinationInfo(constraints: constraints), + 6 => DestinationSavedData(constraints: constraints), + 7 => DestinationSettings(constraints: constraints), + _ => Text("Unknown destination $destination") + }, + ) + ]); + }, + )); + } +} + +class SearchDrawer extends StatefulWidget{ + final Function changePlayer; + final TextEditingController controller; + const SearchDrawer({super.key, required this.changePlayer, required this.controller}); + + @override + State createState() => _SearchDrawerState(); +} + +class _SearchDrawerState extends State { + @override + Widget build(BuildContext context) { + return Drawer( + child: StreamBuilder( + stream: teto.allPlayers, + builder: (context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.none: + case ConnectionState.waiting: + case ConnectionState.done: + case ConnectionState.active: + final allPlayers = (snapshot.data != null) + ? snapshot.data as Map + : {}; + allPlayers.remove(prefs.getString("playerID") ?? "6098518e3d5155e6ec429cdc"); // player from the home button will be delisted + List keys = allPlayers.keys.toList(); + return NestedScrollView( + headerSliverBuilder: (BuildContext context, bool value){ + return [ + SliverToBoxAdapter( + child: SearchBar( + controller: widget.controller, + hintText: "Enter the username", + hintStyle: const WidgetStatePropertyAll(TextStyle(color: Colors.grey)), + trailing: [ + IconButton(onPressed: (){setState(() { + widget.changePlayer(widget.controller.value.text); + Navigator.of(context).pop(); + });}, icon: const Icon(Icons.search)) + ], + onSubmitted: (value) { + setState(() { + widget.changePlayer(value); + Navigator.of(context).pop(); + }); + }, + ), + ), + SliverToBoxAdapter( + child: ListTile( + leading: Icon(Icons.home), + title: Text(prefs.getString("player") ?? "dan63"), + onTap: () { + widget.changePlayer(prefs.getString("playerID") ?? "6098518e3d5155e6ec429cdc"); + Navigator.of(context).pop(); + }, + ), + ), + SliverToBoxAdapter( + child: Divider(), + ), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.only(left: 10.0), + child: Text("Tracked Players", style: Theme.of(context).textTheme.headlineLarge), + ), + ) + ]; + }, + body: ListView.builder( // Builds list of tracked players. + itemCount: allPlayers.length, + itemBuilder: (context, index) { + var i = allPlayers.length-1-index; // Last players in this map are most recent ones, they are gonna be shown at the top. + return ListTile( + title: Text(allPlayers[keys[i]]??keys[i]), // Takes last known username from list of states + trailing: IconButton(onPressed: (){ + teto.deletePlayerToTrack(keys[i]); + }, icon: Icon(Icons.delete, color: Colors.grey)), + onTap: () { + widget.changePlayer(keys[i]); // changes to chosen player + Navigator.of(context).pop(); // and closes itself. + }, + ); + }) + ); + } + } + ) + ); + } +} + +// class EstTrThingy extends StatelessWidget{ +// final EstTr estTr; + +// const EstTrThingy({super.key, required this.estTr}); + +// @override +// Widget build(BuildContext context) { +// return const Card( +// //child: , +// ); +// } +// } \ No newline at end of file diff --git a/lib/views/main_view_tiles.dart b/lib/views/main_view_tiles.dart deleted file mode 100644 index 4367135..0000000 --- a/lib/views/main_view_tiles.dart +++ /dev/null @@ -1,2106 +0,0 @@ -import 'dart:async'; -import 'dart:math'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart' hide Badge; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:http/http.dart'; -import 'package:intl/intl.dart'; -import 'package:syncfusion_flutter_gauges/gauges.dart'; -import 'package:tetra_stats/data_objects/badge.dart'; -import 'package:tetra_stats/data_objects/beta_record.dart'; -import 'package:tetra_stats/data_objects/cutoff_tetrio.dart'; -import 'package:tetra_stats/data_objects/distinguishment.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/news.dart'; -import 'package:tetra_stats/data_objects/news_entry.dart'; -import 'package:tetra_stats/data_objects/p1nkl0bst3r.dart'; -import 'package:tetra_stats/data_objects/player_leaderboard_position.dart'; -import 'package:tetra_stats/data_objects/playstyle.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/summaries.dart'; -import 'package:tetra_stats/data_objects/tetra_league.dart'; -import 'package:tetra_stats/data_objects/tetra_league_alpha_record.dart'; -import 'package:tetra_stats/data_objects/tetrio_constants.dart'; -import 'package:tetra_stats/data_objects/tetrio_player.dart'; -import 'package:tetra_stats/data_objects/tetrio_players_leaderboard.dart'; -import 'package:tetra_stats/gen/strings.g.dart'; -import 'package:tetra_stats/services/crud_exceptions.dart'; -import 'package:tetra_stats/utils/colors_functions.dart'; -import 'package:tetra_stats/utils/numers_formats.dart'; -import 'package:tetra_stats/utils/relative_timestamps.dart'; -import 'package:tetra_stats/utils/text_shadow.dart'; -import 'package:tetra_stats/views/destination_calculator.dart'; -import 'package:tetra_stats/views/destination_cutoffs.dart'; -import 'package:tetra_stats/views/destination_graphs.dart'; -import 'package:tetra_stats/views/destination_home.dart'; -import 'package:tetra_stats/views/destination_leaderboards.dart'; -import 'package:tetra_stats/views/destination_saved_data.dart'; -import 'package:tetra_stats/views/destination_settings.dart'; -import 'package:tetra_stats/views/tl_match_view.dart'; -import 'package:tetra_stats/views/compare_view_tiles.dart'; -import 'package:tetra_stats/widgets/graphs.dart'; -import 'package:tetra_stats/widgets/list_tile_trailing_stats.dart'; -import 'package:tetra_stats/widgets/text_timestamp.dart'; -import 'package:tetra_stats/main.dart'; -import 'package:tetra_stats/widgets/tl_progress_bar.dart'; -import 'package:tetra_stats/widgets/user_thingy.dart'; -import 'package:transparent_image/transparent_image.dart'; -import 'package:vector_math/vector_math_64.dart' hide Colors; - -// TODO: Refactor it - -var fDiff = NumberFormat("+#,###.####;-#,###.####"); -late Future _data; -late Future _newsData; -TetrioPlayersLeaderboard? _everyone; - -Future getData(String searchFor) async { - TetrioPlayer player; - try{ - if (searchFor.startsWith("ds:")){ - player = await teto.fetchPlayer(searchFor.substring(3), isItDiscordID: true); // we trying to get him with that - }else{ - player = await teto.fetchPlayer(searchFor); // Otherwise it's probably a user id or username - } - - }on TetrioPlayerNotExist{ - return FetchResults(false, null, [], null, null, null, null, false, TetrioPlayerNotExist()); - } - late Summaries summaries; - late Cutoffs? cutoffs; - late CutoffsTetrio? averages; - try { - List requests = await Future.wait([ - teto.fetchSummaries(player.userId), - teto.fetchCutoffsBeanserver(), - if (prefs.getBool("showAverages") == true) teto.fetchCutoffsTetrio() - ]); - - summaries = requests[0]; - cutoffs = requests.elementAtOrNull(1); - averages = requests.elementAtOrNull(2); - } on Exception catch (e) { - return FetchResults(false, null, [], null, null, null, null, false, e); - } - PlayerLeaderboardPosition? _meAmongEveryone; - if (prefs.getBool("showPositions") == true){ - // Get tetra League leaderboard - _everyone = teto.getCachedLeaderboard(); - _everyone ??= await teto.fetchTLLeaderboard(); - if (_everyone!.leaderboard.isNotEmpty){ - _meAmongEveryone = await compute(_everyone!.getLeaderboardPosition, {player.userId: summaries.league}); - if (_meAmongEveryone != null) teto.cacheLeaderboardPositions(player.userId, _meAmongEveryone); - } - } - List states = await teto.getStates(player.userId, season: currentSeason); - - bool isTracking = await teto.isPlayerTracking(player.userId); - if (isTracking){ // if tracked - save data to local DB - await teto.storeState(summaries.league); - } - - return FetchResults(true, player, states, summaries, cutoffs, averages, _meAmongEveryone, isTracking, null); - } - -class MainView extends StatefulWidget { - final String? player; - /// The very first view, that user see when he launch this programm. - /// By default it loads my or defined in preferences user stats, but - /// if [player] username or id provided, it loads his stats. Also it hides menu drawer and three dots menu. - const MainView({super.key, this.player}); - - @override - State createState() => _MainState(); -} - -enum Cards {overview, tetraLeague, quickPlay, sprint, blitz} -enum CardMod {info, records, ex, exRecords} -Map cardsTitles = { - Cards.overview: "Overview", - Cards.tetraLeague: t.tetraLeague, - Cards.quickPlay: t.quickPlay, - //Cards.quickPlayExpert: "${t.quickPlay} ${t.expert}", - Cards.sprint: t.sprint, - Cards.blitz: t.blitz, - //Cards.other: t.other -}; - -late ScrollController controller; - -class _MainState extends State with TickerProviderStateMixin { - int destination = 0; - String _searchFor = "6098518e3d5155e6ec429cdc"; - final TextEditingController _searchController = TextEditingController(); - - @override - void initState() { - teto.open(); - controller = ScrollController(); - changePlayer(_searchFor); - super.initState(); - } - - void changePlayer(String player) { - setState(() { - _searchFor = player; - _data = getData(_searchFor); - _newsData = teto.fetchNews(_searchFor); - }); - } - - @override - void dispose() { - controller.dispose(); - _searchController.dispose(); - super.dispose(); - } - - NavigationRailDestination getDestinationButton(IconData icon, String title){ - return NavigationRailDestination( - icon: Tooltip( - message: title, - child: Icon(icon) - ), - selectedIcon: Icon(icon), - label: Text(title), - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - drawer: SearchDrawer(changePlayer: changePlayer, controller: _searchController), - body: LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - TweenAnimationBuilder( - child: NavigationRail( - leading: FloatingActionButton( - elevation: 0, - onPressed: () { - Scaffold.of(context).openDrawer(); - }, - child: const Icon(Icons.search), - ), - trailing: IconButton( - onPressed: () { - // Add your onPressed code here! - }, - icon: const Icon(Icons.more_horiz_rounded), - ), - destinations: [ - getDestinationButton(Icons.home, "Home"), - getDestinationButton(Icons.data_thresholding_outlined, "Graphs"), - getDestinationButton(Icons.leaderboard, "Leaderboards"), - getDestinationButton(Icons.compress, "Cutoffs"), - getDestinationButton(Icons.calculate, "Calc"), - getDestinationButton(Icons.info_outline, "Information"), - getDestinationButton(Icons.storage, "Saved Data"), - getDestinationButton(Icons.settings, "Settings"), - ], - selectedIndex: destination, - onDestinationSelected: (value) { - setState(() { - destination = value; - }); - }, - ), - duration: Durations.long4, - tween: Tween(begin: 0, end: 1), - curve: Easing.standard, - builder: (context, value, child) { - return Container( - transform: Matrix4.translationValues(-80+value*80, 0, 0), - child: Opacity(opacity: value, child: child), - ); - }, - ), - Expanded( - child: switch (destination){ - 0 => DestinationHome(searchFor: _searchFor, constraints: constraints, dataFuture: _data, newsFuture: _newsData), - 1 => DestinationGraphs(searchFor: _searchFor, constraints: constraints), - 2 => DestinationLeaderboards(constraints: constraints), - 3 => DestinationCutoffs(constraints: constraints), - 4 => DestinationCalculator(constraints: constraints), - 5 => DestinationInfo(constraints: constraints), - 6 => DestinationSavedData(constraints: constraints), - 7 => DestinationSettings(constraints: constraints), - _ => Text("Unknown destination $destination") - }, - ) - ]); - }, - )); - } -} - -class DestinationInfo extends StatefulWidget{ - final BoxConstraints constraints; - - const DestinationInfo({super.key, required this.constraints}); - - @override - State createState() => _DestinationInfo(); -} - -class InfoCard extends StatelessWidget { - final double height; - final String assetLink; - final String title; - final String description; - - const InfoCard({required this.height, required this.assetLink, required this.title, required this.description}); - - @override - Widget build(BuildContext context) { - return Card( - clipBehavior: Clip.hardEdge, - child: SizedBox( - width: 450, - height: height, - child: Column( - children: [ - Image.asset("res/images/Снимок экрана_2023-11-06_01-00-50.png", fit: BoxFit.cover, height: 300.0), - Text(title, style: Theme.of(context).textTheme.titleLarge), - Padding( - padding: const EdgeInsets.all(12.0), - child: Text(description), - ), - Spacer() - ], - ), - ), - ); - } - -} - -class _DestinationInfo extends State { - @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/Снимок экрана_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 " - ), - 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 " - ), - 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 " - ), - Card() - ], - ), - ) - ], - ); - } -} - -class NewsThingy extends StatelessWidget{ - final News news; - - const NewsThingy(this.news, {super.key}); - - ListTile getNewsTile(NewsEntry news){ - Map 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) - ], - ), - ), - ); - } - -} - -class DistinguishmentThingy extends StatelessWidget{ - final Distinguishment distinguishment; - - const DistinguishmentThingy(this.distinguishment, {super.key}); - - List 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 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), - ), - ], - ), - ); - } -} - -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), - ), - ], - ), - ), - ); - } - -} - -class BadgesThingy extends StatelessWidget{ - final List 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( - 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: [ - 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); - } - ); - }, - ) - ) - ], - ), - ) - ], - ), - ); - } -} - -class NewUserThingy extends StatefulWidget { - final TetrioPlayer player; - final bool showStateTimestamp; - final bool initIsTracking; - final Function setState; - - const NewUserThingy({super.key, required this.player, required this.initIsTracking, required this.showStateTimestamp, required this.setState}); - - @override - State createState() => _NewUserThingyState(); -} - -class _NewUserThingyState extends State with SingleTickerProviderStateMixin { - late AnimationController _addToTrackAnimController; - late Animation _addToTrackAnim; - - @override - void initState(){ - _addToTrackAnimController = AnimationController( - value: widget.initIsTracking ? 1.0 : 0.0, - duration: Durations.extralong4, - vsync: this, - ); - _addToTrackAnim = new Tween( - begin: 0.0, - end: 1.0, - ).animate(new CurvedAnimation( - parent: _addToTrackAnimController, - curve: Cubic(.15,-0.40,.86,-0.39), - reverseCurve: Cubic(0,.99,.99,1.01) - )); - - super.initState(); - } - - @override - void dispose() { - _addToTrackAnimController.dispose(); - super.dispose(); - } - - Color roleColor(String role){ - switch (role){ - case "sysop": - return const Color.fromARGB(255, 23, 165, 133); - case "admin": - return const Color.fromARGB(255, 255, 78, 138); - case "mod": - return const Color.fromARGB(255, 204, 128, 242); - case "halfmod": - return const Color.fromARGB(255, 95, 118, 254); - case "bot": - return const Color.fromARGB(255, 60, 93, 55); - case "banned": - return const Color.fromARGB(255, 248, 28, 28); - default: - return Colors.white10; - } - } - - String fontStyle(int length){ - if (length < 10) return "Eurostile Round Extended"; - else if (length < 13) return "Eurostile Round"; - else return "Eurostile Round Condensed"; - } - - @override - Widget build(BuildContext context) { - final t = Translations.of(context); - return LayoutBuilder(builder: (context, constraints) { - double pfpHeight = 128; - int xpTableID = 0; - - while (widget.player.xp > xpTableScuffed.values.toList()[xpTableID]) { - xpTableID++; - } - - return Card( - clipBehavior: Clip.antiAlias, - child: Column( - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 4.0), - child: Container( - constraints: const BoxConstraints(maxWidth: 960), - height: widget.player.bannerRevision != null ? 218.0 : 138.0, - child: Stack( - //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}", - placeholder: kTransparentImage, - fit: BoxFit.cover, - height: 120, - fadeInCurve: Easing.standard, fadeInDuration: Durations.long4 - ), - Positioned( - top: widget.player.bannerRevision != null ? 90.0 : 10.0, - left: 16.0, - child: ClipRRect( - borderRadius: BorderRadius.circular(1000), - child: widget.player.role == "banned" - ? Image.asset("res/avatars/tetrio_banned.png", fit: BoxFit.fitHeight, height: pfpHeight,) - : 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), - ) - ), - Positioned( - top: widget.player.bannerRevision != null ? 120.0 : 40.0, - left: 160.0, - child: Tooltip( - message: "${widget.player.userId}\n(Click to copy user ID)", - child: RichText(text: TextSpan(text: widget.player.username, style: TextStyle( - fontFamily: fontStyle(widget.player.username.length), - fontSize: 28, - ), - recognizer: TapGestureRecognizer()..onTap = (){ - copyToClipboard(widget.player.userId); - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.copiedToClipboard))); - } - ) - ) - ), - ), - Positioned( - top: widget.player.bannerRevision != null ? 160.0 : 80.0, - left: 160.0, - child: Row( - children: [ - Padding( - padding: const EdgeInsets.only(right: 4.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))), - ), - RichText( - text: TextSpan( - style: const TextStyle(fontFamily: "Eurostile Round"), - children: - [ - 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), - if (widget.player.supporterTier > 0) TextSpan(text: widget.player.supporterTier.toString(), style: TextStyle(color: widget.player.supporterTier > 1 ? Colors.yellowAccent : Colors.white)), - ] - ) - ) - ], - ), - ), - Positioned( - top: widget.player.bannerRevision != null ? 193.0 : 113.0, - left: 160.0, - child: SizedBox( - width: 270, - child: RichText( - text: TextSpan( - style: const TextStyle(fontFamily: "Eurostile Round"), - children: [ - if (widget.player.country != null) TextSpan(text: "${t.countries[widget.player.country]} • "), - TextSpan(text: timestamp(widget.player.registrationTime), style: const TextStyle(color: Colors.grey)) - ] - ) - ), - ) - ), - Positioned( - top: widget.player.bannerRevision != null ? 126.0 : 46.0, - right: 16.0, - 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 = (){ - showDialog( - context: context, - builder: (BuildContext context) => AlertDialog( - title: Text("Level ${intf.format(widget.player.level.floor())}", textAlign: TextAlign.center), - content: SingleChildScrollView( - child: ListBody(children: [ - Text( - "${NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2).format(widget.player.xp)} XP", - style: const TextStyle(fontFamily: "Eurostile Round", fontWeight: FontWeight.bold) - ), - Padding( - padding: const EdgeInsets.fromLTRB(0, 8, 0, 8), - child: SfLinearGauge( - minimum: 0, - maximum: 1, - interval: 1, - ranges: [ - LinearGaugeRange(startValue: 0, endValue: widget.player.level - widget.player.level.floor(), color: Colors.cyanAccent), - LinearGaugeRange(startValue: 0, endValue: (widget.player.xp / xpTableScuffed.values.toList()[xpTableID]), color: Colors.redAccent, position: LinearElementPosition.cross) - ], - showTicks: true, - 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})") - ] - ), - ), - actions: [ - TextButton( - child: const Text("OK"), - onPressed: () {Navigator.of(context).pop();} - ) - ] - ) - ); - }), - const TextSpan(text:"\n"), - 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 = (){ - Duration accountAge = DateTime.timestamp().difference(widget.player.registrationTime); - Duration avgGametimeADay = Duration(microseconds: (widget.player.gameTime.inMicroseconds / accountAge.inDays).floor()); - showDialog( - context: context, - builder: (BuildContext context) => AlertDialog( - title: Text(t.exactGametime, textAlign: TextAlign.center), - content: SingleChildScrollView( - child: Column( - children: [ - RichText(text: TextSpan( - style: TextStyle(fontFamily: "Eurostile Round", color: Colors.white, fontSize: 28), - children: [ - TextSpan(text: "${intf.format(widget.player.gameTime.inHours)}"), - TextSpan(text: ":${nonsecs.format(widget.player.gameTime.inMinutes%60)}:${nonsecs.format(widget.player.gameTime.inSeconds%60)}"), - TextSpan(text: ".${nonsecs3.format(widget.player.gameTime.inMicroseconds%1000000)}", style: TextStyle(fontSize: 14)) - ] - )), - Text("${playtime(avgGametimeADay)} a day in average"), - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text("It's ${f4.format(widget.player.gameTime.inSeconds/31536000)} years,"), - ), - Text("or ${f4.format(widget.player.gameTime.inSeconds/2628000)} months,"), - Text("or ${f4.format(widget.player.gameTime.inSeconds/86400)} days,"), - Text("or ${f2.format(widget.player.gameTime.inMilliseconds/60000)} minutes,"), - Text("or ${intf.format(widget.player.gameTime.inSeconds)} seconds"), - ] - ), - ), - actions: [ - TextButton( - child: const Text("OK"), - onPressed: () {Navigator.of(context).pop();} - ) - ] - ) - ); - }) : null), - const TextSpan(text:"\n"), - TextSpan(text: widget.player.gamesWon > -1 ? intf.format(widget.player.gamesWon) : "---", style: TextStyle(color: widget.player.gamesWon > -1 ? Colors.white : Colors.grey)), - TextSpan(text: "/${widget.player.gamesPlayed > -1 ? intf.format(widget.player.gamesPlayed) : "---"}", style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)), - ] - ) - ) - ) - ], - ), - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: AnimatedBuilder( - animation: _addToTrackAnim, - builder: (context, child) { - double firstButtonPosition = 0+(_addToTrackAnim.value as double)*25; - double secondButtonPosition = -25+(_addToTrackAnim.value as double)*25; - double firstButtonOpacity = 1-(_addToTrackAnim.value as double)*2; - double secondButtonOpacity = _addToTrackAnim.value*2-1; - return ElevatedButton.icon( - onPressed: (){ - _addToTrackAnimController.value == 1 ? teto.deletePlayerToTrack(widget.player.userId) : teto.addPlayerToTrack(widget.player); - _addToTrackAnim.isCompleted ? _addToTrackAnimController.reverse() : _addToTrackAnimController.forward(); - }, - icon: _addToTrackAnim.value < 0.5 ? Opacity( - opacity: min(1, firstButtonOpacity), - child: Transform.translate( - offset: Offset(0, _addToTrackAnim.status == AnimationStatus.forward ? firstButtonPosition*4 : firstButtonPosition), - child: Transform.rotate( - angle:_addToTrackAnim.status == AnimationStatus.forward ? (_addToTrackAnim.value as double)*2 : 0, - child: const Icon(Icons.person_add), - ), - ), - ) : Container( - transform: Matrix4.translationValues(secondButtonPosition*5, -secondButtonPosition*25, 0), - child: Opacity( - opacity: max(0, min(1, secondButtonOpacity)), - child: Transform.rotate( - angle:_addToTrackAnim.status == AnimationStatus.reverse ? (1-_addToTrackAnim.value as double)*-20 : 0, - child: const Icon(Icons.person_remove) - ) - ) - ), - label: _addToTrackAnim.value < 0.5 ? Container( - transform: Matrix4.translationValues(0, firstButtonPosition, 0), - child: Opacity( - opacity: max(min(1, firstButtonOpacity), 0), - child: Text(_addToTrackAnimController.isAnimating && _addToTrackAnim.status == AnimationStatus.forward ? "Done!" : "Track") - ) - ) : Container( - transform: Matrix4.translationValues(0, secondButtonPosition, 0), - child: Opacity( - opacity: max(0, min(1, secondButtonOpacity)), - child: Text(_addToTrackAnimController.isAnimating && _addToTrackAnim.status == AnimationStatus.reverse ? "Done! " : "Stop tracking") - ) - ), - style: const ButtonStyle(shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.only(bottomLeft: Radius.circular(12.0)))))); - }, - )), - Expanded( - child: ElevatedButton.icon( - onPressed: (){ - Navigator.push(context, MaterialPageRoute( - builder: (context) => CompareView(widget.player), - ), - ); - }, - icon: const Icon(Icons.balance), - label: Text(t.compare), - style: const ButtonStyle(shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.only(bottomRight: Radius.circular(12.0))))) - ) - ) - ], - ) - ], - ), - ); - }); - } -} - -class SearchDrawer extends StatefulWidget{ - final Function changePlayer; - final TextEditingController controller; - const SearchDrawer({super.key, required this.changePlayer, required this.controller}); - - @override - State createState() => _SearchDrawerState(); -} - -class _SearchDrawerState extends State { - @override - Widget build(BuildContext context) { - return Drawer( - child: StreamBuilder( - stream: teto.allPlayers, - builder: (context, snapshot) { - switch (snapshot.connectionState) { - case ConnectionState.none: - case ConnectionState.waiting: - case ConnectionState.done: - case ConnectionState.active: - final allPlayers = (snapshot.data != null) - ? snapshot.data as Map - : {}; - allPlayers.remove(prefs.getString("playerID") ?? "6098518e3d5155e6ec429cdc"); // player from the home button will be delisted - List keys = allPlayers.keys.toList(); - return NestedScrollView( - headerSliverBuilder: (BuildContext context, bool value){ - return [ - SliverToBoxAdapter( - child: SearchBar( - controller: widget.controller, - hintText: "Enter the username", - hintStyle: const WidgetStatePropertyAll(TextStyle(color: Colors.grey)), - trailing: [ - IconButton(onPressed: (){setState(() { - widget.changePlayer(widget.controller.value.text); - Navigator.of(context).pop(); - });}, icon: const Icon(Icons.search)) - ], - onSubmitted: (value) { - setState(() { - widget.changePlayer(value); - Navigator.of(context).pop(); - }); - }, - ), - ), - SliverToBoxAdapter( - child: ListTile( - leading: Icon(Icons.home), - title: Text(prefs.getString("player") ?? "dan63"), - onTap: () { - widget.changePlayer(prefs.getString("playerID") ?? "6098518e3d5155e6ec429cdc"); - Navigator.of(context).pop(); - }, - ), - ), - SliverToBoxAdapter( - child: Divider(), - ), - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.only(left: 10.0), - child: Text("Tracked Players", style: Theme.of(context).textTheme.headlineLarge), - ), - ) - ]; - }, - body: ListView.builder( // Builds list of tracked players. - itemCount: allPlayers.length, - itemBuilder: (context, index) { - var i = allPlayers.length-1-index; // Last players in this map are most recent ones, they are gonna be shown at the top. - return ListTile( - title: Text(allPlayers[keys[i]]??keys[i]), // Takes last known username from list of states - trailing: IconButton(onPressed: (){ - teto.deletePlayerToTrack(keys[i]); - }, icon: Icon(Icons.delete, color: Colors.grey)), - onTap: () { - widget.changePlayer(keys[i]); // changes to chosen player - Navigator.of(context).pop(); // and closes itself. - }, - ); - }) - ); - } - } - ) - ); - } -} - -class TetraLeagueThingy extends StatelessWidget{ - final TetraLeague league; - final TetraLeague? toCompare; - final Cutoffs? cutoffs; - final CutoffTetrio? averages; - final PlayerLeaderboardPosition? lbPos; - - const TetraLeagueThingy({super.key, required this.league, this.toCompare, this.cutoffs, this.averages, this.lbPos}); - - @override - Widget build(BuildContext context) { - return Card( - //surfaceTintColor: rankColors[league.rank], - child: Column( - children: [ - TLRatingThingy(userID: league.id, tlData: league, oldTl: toCompare, showPositions: true), - if (league.gamesPlayed > 9) TLProgress( - tlData: league, - previousRankTRcutoff: cutoffs != null ? cutoffs!.tr[league.rank != "z" ? league.rank : league.percentileRank] : null, - 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, - previousRankTRcutoffTarget: league.rank != "z" ? rankTargets[league.rank] : null, - nextRankTRcutoffTarget: (league.rank != "z" && league.rank != "x+") ? rankTargets[ranks.elementAtOrNull(ranks.indexOf(league.rank)+1)] : null, - 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, - ), - Row( - // spacing: 25.0, - // alignment: WrapAlignment.spaceAround, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: Center( - child: Table( - defaultVerticalAlignment: TableCellVerticalAlignment.baseline, - textBaseline: TextBaseline.alphabetic, - defaultColumnWidth:const IntrinsicColumnWidth(), - children: [ - TableRow(children: [ - 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)), - Text(" APM", style: TextStyle(fontSize: 21, color: league.apm != null ? getStatColor(league.apm!, averages?.apm, true) : Colors.grey)), - if (toCompare != null) Text(" (${comparef2.format(league.apm!-toCompare!.apm!)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: getDifferenceColor(league.apm!-toCompare!.apm!))), - 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: [ - 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)), - Text(" PPS", style: TextStyle(fontSize: 21, color: league.pps != null ? getStatColor(league.pps!, averages?.pps, true) : Colors.grey)), - 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: [ - 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)), - Text(" VS", style: TextStyle(fontSize: 21, color: league.vs != null ? getStatColor(league.vs!, averages?.vs, true) : Colors.grey)), - if (toCompare != null) Text(" (${comparef2.format(league.vs!-toCompare!.vs!)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: getDifferenceColor(league.vs!-toCompare!.vs!))), - 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)) - ]) - ], - ), - ), - ), - GaugetThingy(value: league.winrate, min: 0, max: 1, tickInterval: 0.25, label: "Winrate", sideSize: 128, fractionDigits: 2, moreIsBetter: true, oldValue: toCompare?.winrate, percentileFormat: true), - Expanded( - child: Center( - child: Table( - defaultVerticalAlignment: TableCellVerticalAlignment.baseline, - textBaseline: TextBaseline.alphabetic, - defaultColumnWidth:const IntrinsicColumnWidth(), - children: [ - TableRow(children: [ - //Text("APM: ", style: TextStyle(fontSize: 21)), - Text(intf.format(league.gamesPlayed), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), - const Text(" Games", style: TextStyle(fontSize: 21)), - 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)) - ]), - TableRow(children: [ - //Text("PPS: ", style: TextStyle(fontSize: 21)), - Text(intf.format(league.gamesWon), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), - const Text(" Won", style: TextStyle(fontSize: 21)), - if (toCompare != null) Text(" (${comparef2.format(league.gamesWon-toCompare!.gamesWon)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: Colors.grey)), - 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)) - ]), - TableRow(children: [ - //Text("VS: ", style: TextStyle(fontSize: 21)), - 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)}",), - Text(" GLIXARE", style: TextStyle(fontSize: 21, color: league.gxe.isNegative ? Colors.grey : Colors.white)), - 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)) - ]), - ], - ), - ), - ), - ], - ), - ], - ), - ); - } -} - -class NerdStatsThingy extends StatelessWidget{ - final NerdStats nerdStats; - final NerdStats? oldNerdStats; - final CutoffTetrio? averages; - - const NerdStatsThingy({super.key, required this.nerdStats, this.oldNerdStats, this.averages}); - - @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: SfRadialGauge( - backgroundColor: Colors.black, - axes: [ - RadialAxis( - startAngle: 200, - endAngle: 340, - minimum: 0.0, - maximum: 1.0, - radiusFactor: 1.01, - showTicks: true, - showLabels: false, - interval: 0.1, - //labelsPosition: ElementsPosition.outside, - ranges:[ - GaugeRange(startValue: 0, endValue: nerdStats.app, color: theme.colorScheme.primary) - ], - 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 (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, - minimum: 1.8, - maximum: 2.4, - radiusFactor: 1.01, - showTicks: true, - showLabels: false, - interval: 0.1, - //labelsPosition: ElementsPosition.outside, - ranges:[ - GaugeRange(startValue: 0, endValue: nerdStats.vsapm, color: theme.colorScheme.primary) - ], - 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 (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), - 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), - 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), - GaugetThingy(value: nerdStats.cheese, oldValue: oldNerdStats?.cheese, min: -80, max: 80, tickInterval: 40, label: "Cheese", sideSize: 128.0, fractionDigits: 2, moreIsBetter: false), - 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), - 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), - 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), - ], - ), - ) - ] - ), - ), - ], - ) - ); - } -} - -class EstTrThingy extends StatelessWidget{ - final EstTr estTr; - - const EstTrThingy({super.key, required this.estTr}); - - @override - Widget build(BuildContext context) { - return const Card( - //child: , - ); - } -} - -class GraphsThingy extends StatelessWidget{ - final double apm; - final double pps; - final double vs; - final NerdStats nerdStats; - final Playstyle playstyle; - - const GraphsThingy({super.key, required this.nerdStats, required this.playstyle, required this.apm, required this.pps, required this.vs}); - - @override - Widget build(BuildContext context) { - return Card( - child: Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Center(child: Graphs(apm, pps, vs, nerdStats, playstyle)), - ), - ); - } - -} - -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; - - 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}); - - @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: 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: 0.45 - ) - ], - ) - ] - ), - ), - ); - } -} - -class ZenithThingy extends StatelessWidget{ - final RecordSingle? zenith; - final bool old; - - const ZenithThingy({super.key, required this.zenith, this.old = false}); - - @override - Widget build(BuildContext context) { - return Card( - child: Padding( - padding: const EdgeInsets.fromLTRB(8.0, 0.0, 8.0, 0.0), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Column( - mainAxisSize: MainAxisSize.min, - children: [ - RichText( - text: TextSpan( - text: zenith != null ? "${f2.format(zenith!.stats.zenith!.altitude)} m" : "--- m", - style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, fontWeight: FontWeight.w500, color: (zenith != null && !old) ? Colors.white : Colors.grey), - ), - ), - if (zenith != null) RichText( - text: TextSpan( - text: "", - style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey), - children: [ - if (zenith!.rank != -1) TextSpan(text: "№ ${intf.format(zenith!.rank)}", style: TextStyle(color: getColorOfRank(zenith!.rank))), - if (zenith!.rank != -1) const TextSpan(text: " • "), - if (zenith!.countryRank != -1) TextSpan(text: "№ ${intf.format(zenith!.countryRank)} local", style: TextStyle(color: getColorOfRank(zenith!.countryRank))), - if (zenith!.countryRank != -1) const TextSpan(text: " • "), - TextSpan(text: timestamp(zenith!.timestamp)), - ] - ), - ), - ], - ), - 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) - ], - ), - if (zenith != null) Row( - children: [ - 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)) - ]) - ], - ), - ), - ) - ], - ) - ] - ), - ) - ); - } - -} - -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))), - ), - ); - } -} - -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))), - ), - ); - } - -} - -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?"); - }, - ); - } -} - -class TLRatingThingy extends StatelessWidget{ - final String userID; - final TetraLeague tlData; - final TetraLeague? oldTl; - final double? topTR; - final bool? showPositions; - final DateTime? lastMatchPlayed; - - const TLRatingThingy({super.key, required this.userID, required this.tlData, this.oldTl, this.topTR, this.lastMatchPlayed, this.showPositions}); - - @override - Widget build(BuildContext context) { - bool oskKagariGimmick = prefs.getBool("oskKagariGimmick")??true; - bool bigScreen = MediaQuery.of(context).size.width >= 768; - String decimalSeparator = f4.symbols.DECIMAL_SEP; - List formatedTR = f4.format(tlData.tr).split(decimalSeparator); - List formatedGlicko = tlData.glicko != null ? f4.format(tlData.glicko).split(decimalSeparator) : ["---","--"]; - List formatedPercentile = f4.format(tlData.percentile * 100).split(decimalSeparator); - //DateTime now = DateTime.now(); - //bool beforeS1end = now.isBefore(seasonEnd); - //int daysLeft = seasonEnd.difference(now).inDays; - //int safeRD = min(100, (100 + ((tlData.rd! >= 100 && tlData.decaying) ? 7 : max(0, 7 - (lastMatchPlayed != null ? now.difference(lastMatchPlayed!).inDays : 7))) - daysLeft).toInt()); - return Wrap( - direction: Axis.horizontal, - alignment: WrapAlignment.spaceAround, - crossAxisAlignment: WrapCrossAlignment.center, - clipBehavior: Clip.hardEdge, - children: [ - (userID == "5e32fc85ab319c2ab1beb07c" && oskKagariGimmick) // he love her so much, you can't even imagine - ? 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), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - RichText( - text: TextSpan( - style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 20, color: Colors.white, height: 0.9), - children: (tlData.gamesPlayed > 9) ? switch(prefs.getInt("ratingMode")){ - 1 => [ - TextSpan(text: formatedGlicko[0], style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), - if (formatedGlicko.elementAtOrNull(1) != null) TextSpan(text: decimalSeparator + formatedGlicko[1]), - TextSpan(text: " Glicko", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)) - ], - 2 => [ - TextSpan(text: "${t.top} ${formatedPercentile[0]}", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), - if (formatedPercentile.elementAtOrNull(1) != null) TextSpan(text: decimalSeparator + formatedPercentile[1]), - TextSpan(text: " %", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)) - ], - _ => [ - TextSpan(text: formatedTR[0], style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), - if (formatedTR.elementAtOrNull(1) != null) TextSpan(text: decimalSeparator + formatedTR[1]), - TextSpan(text: " TR", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)) - ], - } : [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) RichText( - textAlign: TextAlign.center, - softWrap: true, - text: TextSpan( - style: DefaultTextStyle.of(context).style, - 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( - children: [ - RichText( - textAlign: TextAlign.center, - softWrap: true, - text: TextSpan( - style: DefaultTextStyle.of(context).style, - children: [ - TextSpan(text: prefs.getInt("ratingMode") == 2 ? "${f2.format(tlData.tr)} TR • % ${t.rank}: ${tlData.percentileRank.toUpperCase()}" : "${t.top} ${f2.format(tlData.percentile * 100)}% (${tlData.percentileRank.toUpperCase()})"), - if (tlData.bestRank != "z") const TextSpan(text: " • "), - if (tlData.bestRank != "z") TextSpan(text: "${t.topRank}: ${tlData.bestRank.toUpperCase()}"), - if (topTR != null) TextSpan(text: " (${f2.format(topTR)} TR)"), - TextSpan(text: " • ${prefs.getInt("ratingMode") == 1 ? "${f2.format(tlData.tr)} TR • RD: " : "Glicko: ${tlData.glicko != null ? f2.format(tlData.glicko) : "---"}±"}"), - TextSpan(text: f2.format(tlData.rd!), style: tlData.decaying ? TextStyle(color: tlData.rd! > 98 ? Colors.red : Colors.yellow) : null), - if (tlData.decaying) WidgetSpan(child: Icon(Icons.trending_up, color: tlData.rd! > 98 ? Colors.red : Colors.yellow,), alignment: PlaceholderAlignment.middle, baseline: TextBaseline.alphabetic), - //if (beforeS1end) tlData.rd! <= safeRD ? TextSpan(text: " (Safe)", style: TextStyle(color: Colors.greenAccent)) : TextSpan(text: " (> ${safeRD} RD !!!)", style: TextStyle(color: Colors.redAccent)) - ], - ), - ), - ], - ), - 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)), - ] - ), - ), - ], - ), - ], - ); - } -} - -class FutureError extends StatelessWidget{ - final AsyncSnapshot snapshot; - - FutureError(this.snapshot); - - @override - Widget build(BuildContext context) { - return TweenAnimationBuilder( - duration: Durations.medium3, - tween: Tween(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() - ], - ), - ); - } -} - -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(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() - ], - ), - ); - } -} - -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), - ], - )); - } - -} \ No newline at end of file diff --git a/lib/views/mathes_view.dart b/lib/views/mathes_view.dart deleted file mode 100644 index fa709c8..0000000 --- a/lib/views/mathes_view.dart +++ /dev/null @@ -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 createState() => MatchesState(); -} - -class MatchesState extends State { - - @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)))], - ); - } - } - ) - ) - ); - } -} diff --git a/lib/views/rank_averages_view.dart b/lib/views/rank_averages_view.dart deleted file mode 100644 index a0422f4..0000000 --- a/lib/views/rank_averages_view.dart +++ /dev/null @@ -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 = [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 _itemStats = [for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value))]; -List<_MyScatterSpot> _spots = []; -Stats _sortBy = Stats.tr; -late List they; -bool _reversed = false; -List _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 createState() => RankState(); -} - -class RankState extends State 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 hoveredPointId = ValueNotifier(-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); -} diff --git a/lib/views/rank_view.dart b/lib/views/rank_view.dart index 1249376..826d0fd 100644 --- a/lib/views/rank_view.dart +++ b/lib/views/rank_view.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:tetra_stats/data_objects/cutoff_tetrio.dart'; import 'package:tetra_stats/main.dart'; import 'package:tetra_stats/utils/numers_formats.dart'; -import 'package:tetra_stats/views/main_view_tiles.dart'; +import 'package:tetra_stats/widgets/future_error.dart'; class RankView extends StatefulWidget { final String rank; diff --git a/lib/views/ranks_averages_view.dart b/lib/views/ranks_averages_view.dart deleted file mode 100644 index a368d35..0000000 --- a/lib/views/ranks_averages_view.dart +++ /dev/null @@ -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 createState() => RanksAverages(); -} - -late String oldWindowTitle; - -class RanksAverages extends State { - - @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(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"); - } - }) - ), - ); - } -} diff --git a/lib/views/settings_view.dart b/lib/views/settings_view.dart deleted file mode 100644 index 6734b14..0000000 --- a/lib/views/settings_view.dart +++ /dev/null @@ -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 createState() => SettingsState(); -} - -class SettingsState extends State { - 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 _setDefaultNickname(String? n) async { - if (n != null) { - try { - defaultNickname = await teto.getNicknameByID(n); - } on TetrioPlayerNotExist { - defaultNickname = n; - } - } else { - defaultNickname = "dan63047"; - } - setState(() {}); - } - - Future _setPlayer(String player) async { - await prefs.setString('player', player); - await _setDefaultNickname(player); - } - - Future _removePlayer() async { - await prefs.remove('player'); - await _setDefaultNickname("6098518e3d5155e6ec429cdc"); - } - - @override - Widget build(BuildContext context) { - final t = Translations.of(context); - List>? locales = >[]; - for (var v in AppLocale.values){ - locales.add(DropdownMenuItem( - 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: [ - 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: [ - 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: ['db'], - ); - openFile(acceptedTypeGroups: [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: [ - 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: (){},), - // ], - // ), - ], - )), - ); - } -} diff --git a/lib/views/sprint_and_blitz_averages.dart b/lib/views/sprint_and_blitz_averages.dart index 0fd8630..4ed9a99 100644 --- a/lib/views/sprint_and_blitz_averages.dart +++ b/lib/views/sprint_and_blitz_averages.dart @@ -40,8 +40,14 @@ class SprintAndBlitzState extends State { final t = Translations.of(context); bool bigScreen = MediaQuery.of(context).size.width >= 368; return Scaffold( - appBar: AppBar( - title: Text(t.sprintAndBlitsViewTitle), + floatingActionButtonLocation: FloatingActionButtonLocation.startTop, + 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, body: SafeArea( diff --git a/lib/views/state_view.dart b/lib/views/state_view.dart index 2e70d2f..45fab6a 100644 --- a/lib/views/state_view.dart +++ b/lib/views/state_view.dart @@ -49,7 +49,7 @@ class StateState extends State { ), backgroundColor: Colors.black, body: SafeArea( - child: TLThingy(tl: widget.state, userID: widget.state.id, states: []) + child: TetraLeagueThingy(league: widget.state) ) ); } diff --git a/lib/views/states_view.dart b/lib/views/states_view.dart deleted file mode 100644 index 021adf9..0000000 --- a/lib/views/states_view.dart +++ /dev/null @@ -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 createState() => StatesState(); -} - -late String oldWindowTitle; - -class StatesState extends State { - @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>(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)); - } - )));} - } diff --git a/lib/views/tl_leaderboard_view.dart b/lib/views/tl_leaderboard_view.dart deleted file mode 100644 index 3c6f41d..0000000 --- a/lib/views/tl_leaderboard_view.dart +++ /dev/null @@ -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 _itemStats = [for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value))]; -Stats _sortBy = Stats.tr; -bool reversed = false; -List _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 createState() => TLLeaderboardState(); -} - -class TLLeaderboardState extends State { - @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"); - } - })), - ); - } -} diff --git a/lib/views/tl_match_view.dart b/lib/views/tl_match_view.dart index c3229b8..d2bbfda 100644 --- a/lib/views/tl_match_view.dart +++ b/lib/views/tl_match_view.dart @@ -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/tetrio_multiplayer_replay.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/text_timestamp.dart'; import 'package:tetra_stats/widgets/vs_graphs.dart'; diff --git a/lib/views/tracked_players_view.dart b/lib/views/tracked_players_view.dart deleted file mode 100644 index b7816e5..0000000 --- a/lib/views/tracked_players_view.dart +++ /dev/null @@ -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 createState() => TrackedPlayersState(); -} - -class TrackedPlayersState extends State { - @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) => [ - 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 : {}; - List 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]), - ), - ); - }, - ); - })); - } - })), - ); - } -} diff --git a/lib/views/user_view.dart b/lib/views/user_view.dart index acb1851..c2395ad 100644 --- a/lib/views/user_view.dart +++ b/lib/views/user_view.dart @@ -5,7 +5,7 @@ import 'package:intl/intl.dart'; import 'package:tetra_stats/gen/strings.g.dart'; import 'package:tetra_stats/main.dart'; import 'package:tetra_stats/views/destination_home.dart'; -import 'package:tetra_stats/views/main_view_tiles.dart'; +import 'package:tetra_stats/views/main_view.dart'; final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms(); diff --git a/lib/views/zenith_record_view.dart b/lib/views/zenith_record_view.dart deleted file mode 100644 index 7169bf7..0000000 --- a/lib/views/zenith_record_view.dart +++ /dev/null @@ -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), - ), - ) - ), - ); - } - -} \ No newline at end of file diff --git a/lib/widgets/alpha_league_entry_thingy.dart b/lib/widgets/alpha_league_entry_thingy.dart new file mode 100644 index 0000000..2a3fd9a --- /dev/null +++ b/lib/widgets/alpha_league_entry_thingy.dart @@ -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))), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/badges_thingy.dart b/lib/widgets/badges_thingy.dart new file mode 100644 index 0000000..b223198 --- /dev/null +++ b/lib/widgets/badges_thingy.dart @@ -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 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( + 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: [ + 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); + } + ); + }, + ) + ) + ], + ), + ) + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/beta_league_entry_thingy.dart b/lib/widgets/beta_league_entry_thingy.dart new file mode 100644 index 0000000..61d854c --- /dev/null +++ b/lib/widgets/beta_league_entry_thingy.dart @@ -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))), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/compare_thingy.dart b/lib/widgets/compare_thingy.dart new file mode 100644 index 0000000..a5324e1 --- /dev/null +++ b/lib/widgets/compare_thingy.dart @@ -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( + 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( + 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, + ), + )), + ], + ), + ); + } +} diff --git a/lib/widgets/distinguishment_thingy.dart b/lib/widgets/distinguishment_thingy.dart new file mode 100644 index 0000000..6d6952c --- /dev/null +++ b/lib/widgets/distinguishment_thingy.dart @@ -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 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 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), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/error_thingy.dart b/lib/widgets/error_thingy.dart new file mode 100644 index 0000000..dc317a3 --- /dev/null +++ b/lib/widgets/error_thingy.dart @@ -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(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() + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/fake_distinguishment_thingy.dart b/lib/widgets/fake_distinguishment_thingy.dart new file mode 100644 index 0000000..26a9e21 --- /dev/null +++ b/lib/widgets/fake_distinguishment_thingy.dart @@ -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), + ), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/future_error.dart b/lib/widgets/future_error.dart new file mode 100644 index 0000000..ccab8c8 --- /dev/null +++ b/lib/widgets/future_error.dart @@ -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(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() + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/gauget_num.dart b/lib/widgets/gauget_num.dart deleted file mode 100644 index edfad86..0000000 --- a/lib/widgets/gauget_num.dart +++ /dev/null @@ -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 ranges; - final double minimum; - final double maximum; - final String playerStatLabel; - final String? okText; - final String? alertTitle; - final List? 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: [ - 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)], - )],), - ); - } -} \ No newline at end of file diff --git a/lib/widgets/gauget_thingy.dart b/lib/widgets/gauget_thingy.dart new file mode 100644 index 0000000..fa5afe7 --- /dev/null +++ b/lib/widgets/gauget_thingy.dart @@ -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 + ) + ], + ) + ] + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/graphs.dart b/lib/widgets/graphs.dart index 3049eeb..7815b0c 100644 --- a/lib/widgets/graphs.dart +++ b/lib/widgets/graphs.dart @@ -287,205 +287,212 @@ class Graphs extends StatelessWidget{ double speed = pps / 3.75; double defense = nerdStats.dss * 1.15; double cheese = nerdStats.cheese / 110; - return Wrap( - direction: Axis.horizontal, - alignment: WrapAlignment.center, - spacing: 25, - crossAxisAlignment: WrapCrossAlignment.start, - clipBehavior: Clip.hardEdge, - children: [ - if (true) Padding( // vs graph - padding: const EdgeInsets.fromLTRB(18, 0, 18, 44), - child: SizedBox( - height: 310, - width: 310, - child: MyRadarChart( - RadarChartData( - radarShape: RadarShape.circle, - tickCount: 4, - radarBackgroundColor: Colors.black.withAlpha(170), - radarBorderData: const BorderSide(color: Colors.white24, width: 1), - gridBorderData: const BorderSide(color: Colors.white24, width: 1), - tickBorderData: const BorderSide(color: Colors.white24, width: 1), - getTitle: (index, angle) { - switch (index) { - case 0: - return RadarChartTitle(text: 'APM', angle: angle, positionPercentageOffset: 0.05); - case 1: - return RadarChartTitle(text: 'PPS', angle: angle, positionPercentageOffset: 0.05); - case 2: - return RadarChartTitle(text: 'VS', angle: angle, positionPercentageOffset: 0.05); - case 3: - return RadarChartTitle(text: 'APP', angle: angle + 180, positionPercentageOffset: 0.05); - case 4: - return RadarChartTitle(text: 'DS/S', angle: angle + 180, positionPercentageOffset: 0.05); - case 5: - return RadarChartTitle(text: 'DS/P', angle: angle + 180, positionPercentageOffset: 0.05); - case 6: - return RadarChartTitle(text: 'APP+DS/P', angle: angle + 180, positionPercentageOffset: 0.05); - case 7: - return RadarChartTitle(text: 'VS/APM', angle: angle + 180, positionPercentageOffset: 0.05); - case 8: - return RadarChartTitle(text: 'Cheese', angle: angle, positionPercentageOffset: 0.05); - case 9: - return RadarChartTitle(text: 'Gb Eff.', angle: angle, positionPercentageOffset: 0.05); - default: - return const RadarChartTitle(text: ''); - } - }, - dataSets: [ - RadarDataSet( - fillColor: Theme.of(context).colorScheme.primary.withAlpha(170), - borderColor: Theme.of(context).colorScheme.primary, - dataEntries: [ - RadarEntry(value: apm * apmWeight), - RadarEntry(value: pps * ppsWeight), - RadarEntry(value: vs * vsWeight), - RadarEntry(value: nerdStats.app * appWeight), - RadarEntry(value: nerdStats.dss * dssWeight), - 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), - ], + return Card( + child: Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Center( + child: Wrap( + direction: Axis.horizontal, + alignment: WrapAlignment.center, + spacing: 25, + crossAxisAlignment: WrapCrossAlignment.start, + clipBehavior: Clip.hardEdge, + children: [ + if (true) Padding( // vs graph + padding: const EdgeInsets.fromLTRB(18, 0, 18, 22), + child: SizedBox( + height: 310, + width: 310, + child: MyRadarChart( + RadarChartData( + radarShape: RadarShape.circle, + tickCount: 4, + radarBackgroundColor: Colors.black.withAlpha(170), + radarBorderData: const BorderSide(color: Colors.white24, width: 1), + gridBorderData: const BorderSide(color: Colors.white24, width: 1), + tickBorderData: const BorderSide(color: Colors.white24, width: 1), + getTitle: (index, angle) { + switch (index) { + case 0: + return RadarChartTitle(text: 'APM', angle: angle, positionPercentageOffset: 0.05); + case 1: + return RadarChartTitle(text: 'PPS', angle: angle, positionPercentageOffset: 0.05); + case 2: + return RadarChartTitle(text: 'VS', angle: angle, positionPercentageOffset: 0.05); + case 3: + return RadarChartTitle(text: 'APP', angle: angle + 180, positionPercentageOffset: 0.05); + case 4: + return RadarChartTitle(text: 'DS/S', angle: angle + 180, positionPercentageOffset: 0.05); + case 5: + return RadarChartTitle(text: 'DS/P', angle: angle + 180, positionPercentageOffset: 0.05); + case 6: + return RadarChartTitle(text: 'APP+DS/P', angle: angle + 180, positionPercentageOffset: 0.05); + case 7: + return RadarChartTitle(text: 'VS/APM', angle: angle + 180, positionPercentageOffset: 0.05); + case 8: + return RadarChartTitle(text: 'Cheese', angle: angle, positionPercentageOffset: 0.05); + case 9: + return RadarChartTitle(text: 'Gb Eff.', angle: angle, positionPercentageOffset: 0.05); + default: + return const RadarChartTitle(text: ''); + } + }, + dataSets: [ + RadarDataSet( + fillColor: Theme.of(context).colorScheme.primary.withAlpha(170), + borderColor: Theme.of(context).colorScheme.primary, + dataEntries: [ + RadarEntry(value: apm * apmWeight), + RadarEntry(value: pps * ppsWeight), + RadarEntry(value: vs * vsWeight), + RadarEntry(value: nerdStats.app * appWeight), + RadarEntry(value: nerdStats.dss * dssWeight), + 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 - swapAnimationCurve: Curves.linear, // Optional - ), - ), - ), - Padding( // psq graph - padding: const EdgeInsets.fromLTRB(18, 0, 18, 44), - child: SizedBox( - height: 310, - width: 310, - child: MyRadarChart( - RadarChartData( - radarShape: RadarShape.circle, - tickCount: 4, - radarBackgroundColor: Colors.black.withAlpha(170), - radarBorderData: const BorderSide(color: Colors.white24, width: 1), - gridBorderData: const BorderSide(color: Colors.white24, width: 1), - tickBorderData: const BorderSide(color: Colors.white24, width: 1), - titleTextStyle: const TextStyle(height: 1.1), - radarTouchData: RadarTouchData(), - getTitle: (index, angle) { - switch (index) { - case 0: - return RadarChartTitle(text: 'Opener\n${percentage.format(playstyle.opener)}', angle: 0, positionPercentageOffset: 0.05); - case 1: - return RadarChartTitle(text: 'Stride\n${percentage.format(playstyle.stride)}', angle: 0, positionPercentageOffset: 0.05); - case 2: - 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); - default: - return const RadarChartTitle(text: ''); - } - }, - dataSets: [ - RadarDataSet( - fillColor: Theme.of(context).colorScheme.primary.withAlpha(170), - borderColor: Theme.of(context).colorScheme.primary, - dataEntries: [ - RadarEntry(value: playstyle.opener), - RadarEntry(value: playstyle.stride), - RadarEntry(value: playstyle.infds), - RadarEntry(value: playstyle.plonk), - ], + Padding( // psq graph + padding: const EdgeInsets.fromLTRB(18, 0, 18, 22), + child: SizedBox( + height: 310, + width: 310, + child: MyRadarChart( + RadarChartData( + radarShape: RadarShape.circle, + tickCount: 4, + radarBackgroundColor: Colors.black.withAlpha(170), + radarBorderData: const BorderSide(color: Colors.white24, width: 1), + gridBorderData: const BorderSide(color: Colors.white24, width: 1), + tickBorderData: const BorderSide(color: Colors.white24, width: 1), + titleTextStyle: const TextStyle(height: 1.1), + radarTouchData: RadarTouchData(), + getTitle: (index, angle) { + switch (index) { + case 0: + return RadarChartTitle(text: 'Opener\n${percentage.format(playstyle.opener)}', angle: 0, positionPercentageOffset: 0.05); + case 1: + return RadarChartTitle(text: 'Stride\n${percentage.format(playstyle.stride)}', angle: 0, positionPercentageOffset: 0.05); + case 2: + 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); + default: + return const RadarChartTitle(text: ''); + } + }, + dataSets: [ + RadarDataSet( + fillColor: Theme.of(context).colorScheme.primary.withAlpha(170), + borderColor: Theme.of(context).colorScheme.primary, + dataEntries: [ + RadarEntry(value: playstyle.opener), + RadarEntry(value: playstyle.stride), + RadarEntry(value: playstyle.infds), + RadarEntry(value: playstyle.plonk), + ], + ), + 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 + 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 - swapAnimationCurve: Curves.linear, // Optional - ), - ), - ), - Padding( // sq graph - padding: const EdgeInsets.fromLTRB(18, 0, 18, 44), - child: SizedBox( - height: 310, - width: 310, - child: MyRadarChart( - RadarChartData( - radarShape: RadarShape.circle, - tickCount: 4, - radarBackgroundColor: Colors.black.withAlpha(170), - radarBorderData: const BorderSide(color: Colors.white24, width: 1), - gridBorderData: const BorderSide(color: Colors.white24, width: 1), - tickBorderData: const BorderSide(color: Colors.white24, width: 1), - titleTextStyle: const TextStyle(height: 1.1), - radarTouchData: RadarTouchData(), - getTitle: (index, angle) { - switch (index) { - case 0: - return RadarChartTitle(text: '${t.graphs.attack}\n${f2.format(apm)} APM', angle: 0, positionPercentageOffset: 0.05); - case 1: - return RadarChartTitle(text: '${t.graphs.speed}\n${f2.format(pps)} PPS', angle: 0, positionPercentageOffset: 0.05); - case 2: - 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); - default: - return const RadarChartTitle(text: ''); - } - }, - dataSets: [ - RadarDataSet( - fillColor: Theme.of(context).colorScheme.primary.withAlpha(170), - borderColor: Theme.of(context).colorScheme.primary, - dataEntries: [ - RadarEntry(value: attack), - RadarEntry(value: speed), - RadarEntry(value: defense), - RadarEntry(value: cheese), - ], - ), - RadarDataSet( - fillColor: Colors.transparent, - borderColor: Colors.transparent, - dataEntries: [ - const RadarEntry(value: 0), - const RadarEntry(value: 1.2), - const RadarEntry(value: 0), - const RadarEntry(value: 0), - ], + Padding( // sq graph + padding: const EdgeInsets.fromLTRB(18, 0, 18, 22), + child: SizedBox( + height: 310, + width: 310, + child: MyRadarChart( + RadarChartData( + radarShape: RadarShape.circle, + tickCount: 4, + radarBackgroundColor: Colors.black.withAlpha(170), + radarBorderData: const BorderSide(color: Colors.white24, width: 1), + gridBorderData: const BorderSide(color: Colors.white24, width: 1), + tickBorderData: const BorderSide(color: Colors.white24, width: 1), + titleTextStyle: const TextStyle(height: 1.1), + radarTouchData: RadarTouchData(), + getTitle: (index, angle) { + switch (index) { + case 0: + return RadarChartTitle(text: '${t.graphs.attack}\n${f2.format(apm)} APM', angle: 0, positionPercentageOffset: 0.05); + case 1: + return RadarChartTitle(text: '${t.graphs.speed}\n${f2.format(pps)} PPS', angle: 0, positionPercentageOffset: 0.05); + case 2: + 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); + default: + return const RadarChartTitle(text: ''); + } + }, + dataSets: [ + RadarDataSet( + fillColor: Theme.of(context).colorScheme.primary.withAlpha(170), + borderColor: Theme.of(context).colorScheme.primary, + dataEntries: [ + RadarEntry(value: attack), + RadarEntry(value: speed), + RadarEntry(value: defense), + RadarEntry(value: cheese), + ], + ), + RadarDataSet( + fillColor: Colors.transparent, + borderColor: Colors.transparent, + dataEntries: [ + const RadarEntry(value: 0), + const RadarEntry(value: 1.2), + const RadarEntry(value: 0), + const RadarEntry(value: 0), + ], + ) + ], + ) ) - ], + ) ) - ) - ) - ) - ], + ], + ), + ), + ), ); } diff --git a/lib/widgets/info_thingy.dart b/lib/widgets/info_thingy.dart new file mode 100644 index 0000000..dfd90c3 --- /dev/null +++ b/lib/widgets/info_thingy.dart @@ -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), + ], + )); + } + +} \ No newline at end of file diff --git a/lib/widgets/nerd_stats_thingy.dart b/lib/widgets/nerd_stats_thingy.dart new file mode 100644 index 0000000..105b689 --- /dev/null +++ b/lib/widgets/nerd_stats_thingy.dart @@ -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), + ], + ), + ) + ] + ), + ), + ], + ) + ); + } +} \ No newline at end of file diff --git a/lib/widgets/news_thingy.dart b/lib/widgets/news_thingy.dart new file mode 100644 index 0000000..57c9905 --- /dev/null +++ b/lib/widgets/news_thingy.dart @@ -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 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) + ], + ), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/widgets/singleplayer_record.dart b/lib/widgets/singleplayer_record.dart index 2276d8d..4a20225 100644 --- a/lib/widgets/singleplayer_record.dart +++ b/lib/widgets/singleplayer_record.dart @@ -100,7 +100,7 @@ class SingleplayerRecord extends StatelessWidget { if (record!.gamemode == "40l") Wrap( alignment: WrapAlignment.spaceBetween, 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.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), diff --git a/lib/widgets/tl_rating_thingy.dart b/lib/widgets/tl_rating_thingy.dart index 1a3fbd0..41904cb 100644 --- a/lib/widgets/tl_rating_thingy.dart +++ b/lib/widgets/tl_rating_thingy.dart @@ -1,21 +1,22 @@ import 'package:flutter/material.dart'; -import 'package:intl/intl.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 prefs; +import 'package:tetra_stats/utils/colors_functions.dart'; import 'package:tetra_stats/utils/numers_formats.dart'; -var fDiff = NumberFormat("+#,###.####;-#,###.####"); +import 'text_timestamp.dart'; class TLRatingThingy extends StatelessWidget{ final String userID; final TetraLeague tlData; final TetraLeague? oldTl; final double? topTR; + final bool? showPositions; 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 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/tetrio_tl_alpha_ranks/${tlData.rank}.png", height: 128), Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ RichText( 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")){ 1 => [ 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)),] ) ), - if (oldTl != null) 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" - }, + if (oldTl != null) RichText( textAlign: TextAlign.center, - style: TextStyle( - color: tlData.tr - oldTl!.tr < 0 ? - Colors.red : - Colors.green + softWrap: true, + text: TextSpan( + style: DefaultTextStyle.of(context).style, + 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( @@ -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)), + ] + ), + ), ], ), ], diff --git a/lib/widgets/tl_records_thingy.dart b/lib/widgets/tl_records_thingy.dart new file mode 100644 index 0000000..7217e8d --- /dev/null +++ b/lib/widgets/tl_records_thingy.dart @@ -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?"); + }, + ); + } +} \ No newline at end of file diff --git a/lib/widgets/tl_thingy.dart b/lib/widgets/tl_thingy.dart index be40b7c..01e685f 100644 --- a/lib/widgets/tl_thingy.dart +++ b/lib/widgets/tl_thingy.dart @@ -1,319 +1,112 @@ -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'package:tetra_stats/data_objects/player_leaderboard_position.dart'; -import 'package:tetra_stats/data_objects/tetra_league.dart'; -import 'package:syncfusion_flutter_gauges/gauges.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_num.dart'; -import 'package:tetra_stats/widgets/graphs.dart'; -import 'package:tetra_stats/widgets/stat_sell_num.dart'; -import 'package:tetra_stats/widgets/text_timestamp.dart'; -import 'package:tetra_stats/widgets/tl_progress_bar.dart'; -import 'package:tetra_stats/widgets/tl_rating_thingy.dart'; - - -var intFDiff = NumberFormat("+#,###.000;-#,###.000"); - -class TLThingy extends StatefulWidget { - final TetraLeague tl; - final String userID; - final List states; - final bool showTitle; - final bool bot; - final bool guest; - final double? topTR; - final PlayerLeaderboardPosition? lbPositions; - final TetraLeague? averages; - final double? thatRankCutoff; - final double? thatRankCutoffGlicko; - final double? thatRankTarget; - final double? nextRankCutoff; - final double? nextRankCutoffGlicko; - final double? nextRankTarget; - final DateTime? lastMatchPlayed; - 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}); - - @override - State createState() => _TLThingyState(); -} - -class _TLThingyState extends State with TickerProviderStateMixin { - late bool oskKagariGimmick; - late TetraLeague? oldTl; - late TetraLeague currentTl; - late RangeValues _currentRangeValues; - late List sortedStates; - -@override - void initState() { - _currentRangeValues = const RangeValues(0, 1); - sortedStates = widget.states.reversed.toList(); - oldTl = sortedStates.elementAtOrNull(1); - currentTl = widget.tl; - super.initState(); - } - - @override - void dispose() { - super.dispose(); - } - - - @override - Widget build(BuildContext context) { - final t = Translations.of(context); - String decimalSeparator = f2.symbols.DECIMAL_SEP; - List estTRformated = currentTl.estTr != null ? f2.format(currentTl.estTr!.esttr).split(decimalSeparator) : []; - List 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( - physics: const ClampingScrollPhysics(), - itemCount: 1, - itemBuilder: (BuildContext context, int index) { - return Column( - children: [ - if (widget.showTitle) Text(t.tetraLeague, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), - if (oldTl != null) Text(t.comparingWith(newDate: timestamp(currentTl.timestamp), oldDate: timestamp(oldTl!.timestamp)), - textAlign: TextAlign.center,), - if (oldTl != null) RangeSlider(values: _currentRangeValues, max: widget.states.length.toDouble(), - labels: RangeLabels( - _currentRangeValues.start.round().toString(), - _currentRangeValues.end.round().toString(), - ), - onChanged: (RangeValues values) { - setState(() { - _currentRangeValues = values; - if (values.start.round() == 0){ - currentTl = widget.tl; - }else{ - currentTl = sortedStates[values.start.round()-1]; - } - if (values.end.round() == 0){ - oldTl = widget.tl; - }else{ - oldTl = sortedStates[values.end.round()-1]; - } - }); - }, - ), - 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!) - ] - ); - }, - ); - }); - } -} +import 'package:flutter/material.dart'; +import 'package:tetra_stats/data_objects/cutoff_tetrio.dart'; +import 'package:tetra_stats/data_objects/p1nkl0bst3r.dart'; +import 'package:tetra_stats/data_objects/player_leaderboard_position.dart'; +import 'package:tetra_stats/data_objects/tetra_league.dart'; +import 'package:tetra_stats/data_objects/tetrio_constants.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'; +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; + final Cutoffs? cutoffs; + final CutoffTetrio? averages; + final PlayerLeaderboardPosition? lbPos; + + const TetraLeagueThingy({super.key, required this.league, this.toCompare, this.cutoffs, this.averages, this.lbPos}); + + @override + Widget build(BuildContext context) { + return Card( + //surfaceTintColor: rankColors[league.rank], + child: Column( + children: [ + TLRatingThingy(userID: league.id, tlData: league, oldTl: toCompare, showPositions: true), + if (league.gamesPlayed > 9) TLProgress( + tlData: league, + previousRankTRcutoff: cutoffs != null ? cutoffs!.tr[league.rank != "z" ? league.rank : league.percentileRank] : null, + 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, + previousRankTRcutoffTarget: league.rank != "z" ? rankTargets[league.rank] : null, + nextRankTRcutoffTarget: (league.rank != "z" && league.rank != "x+") ? rankTargets[ranks.elementAtOrNull(ranks.indexOf(league.rank)+1)] : null, + 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, + ), + Row( + // spacing: 25.0, + // alignment: WrapAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Center( + child: Table( + defaultVerticalAlignment: TableCellVerticalAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + defaultColumnWidth:const IntrinsicColumnWidth(), + children: [ + TableRow(children: [ + 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)), + Text(" APM", style: TextStyle(fontSize: 21, color: league.apm != null ? getStatColor(league.apm!, averages?.apm, true) : Colors.grey)), + if (toCompare != null) Text(" (${comparef2.format(league.apm!-toCompare!.apm!)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: getDifferenceColor(league.apm!-toCompare!.apm!))), + 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: [ + 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)), + Text(" PPS", style: TextStyle(fontSize: 21, color: league.pps != null ? getStatColor(league.pps!, averages?.pps, true) : Colors.grey)), + 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: [ + 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)), + Text(" VS", style: TextStyle(fontSize: 21, color: league.vs != null ? getStatColor(league.vs!, averages?.vs, true) : Colors.grey)), + if (toCompare != null) Text(" (${comparef2.format(league.vs!-toCompare!.vs!)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: getDifferenceColor(league.vs!-toCompare!.vs!))), + 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)) + ]) + ], + ), + ), + ), + 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), + Expanded( + child: Center( + child: Table( + defaultVerticalAlignment: TableCellVerticalAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + defaultColumnWidth:const IntrinsicColumnWidth(), + children: [ + TableRow(children: [ + //Text("APM: ", style: TextStyle(fontSize: 21)), + Text(intf.format(league.gamesPlayed), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), + const Text(" Games", style: TextStyle(fontSize: 21)), + 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)) + ]), + TableRow(children: [ + //Text("PPS: ", style: TextStyle(fontSize: 21)), + Text(intf.format(league.gamesWon), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), + const Text(" Won", style: TextStyle(fontSize: 21)), + if (toCompare != null) Text(" (${comparef2.format(league.gamesWon-toCompare!.gamesWon)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: Colors.grey)), + 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)) + ]), + TableRow(children: [ + //Text("VS: ", style: TextStyle(fontSize: 21)), + 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"), + 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)) + ]), + ], + ), + ), + ), + ], + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/user_thingy.dart b/lib/widgets/user_thingy.dart index 957f4d5..3d56a30 100644 --- a/lib/widgets/user_thingy.dart +++ b/lib/widgets/user_thingy.dart @@ -1,429 +1,354 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:syncfusion_flutter_gauges/gauges.dart'; -import 'package:tetra_stats/data_objects/tetrio_player.dart'; -import 'package:tetra_stats/gen/strings.g.dart'; -import 'package:tetra_stats/main.dart' show teto; -import 'package:tetra_stats/views/compare_view.dart'; -import 'package:intl/intl.dart'; -import 'package:tetra_stats/utils/text_shadow.dart'; -import 'dart:developer' as developer; -import 'package:tetra_stats/widgets/stat_sell_num.dart'; -import 'package:tetra_stats/widgets/text_timestamp.dart'; - -const Map 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 -}; - -Future copyToClipboard(String text) async { - await Clipboard.setData(ClipboardData(text: text)); -} - -class UserThingy extends StatelessWidget { - final TetrioPlayer player; - final bool showStateTimestamp; - final Function setState; - - const UserThingy({super.key, required this.player, required this.showStateTimestamp, required this.setState}); - - @override - Widget build(BuildContext context) { - final t = Translations.of(context); - return LayoutBuilder(builder: (context, constraints) { - bool bigScreen = constraints.maxWidth > 768; - double bannerHeight = bigScreen ? 240 : 120; - double pfpHeight = 128; - int xpTableID = 0; - - while (player.xp > xpTableScuffed.values.toList()[xpTableID]) { - xpTableID++; - } - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Stack( - alignment: Alignment.topCenter, - children: [ - if (player.bannerRevision != null) - 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}", - fit: BoxFit.cover, - height: bannerHeight, - errorBuilder: (context, error, stackTrace) { - developer.log("Error with building banner image", name: "main_view", error: error, stackTrace: stackTrace); - return Container(); - }, - ), - Padding( - padding: EdgeInsets.fromLTRB(8, player.bannerRevision != null ? bannerHeight / 1.4 : 0, 8, bigScreen ? 16 : 0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Column( - children: [ - Wrap( - direction: bigScreen ? Axis.horizontal : Axis.vertical, - alignment: WrapAlignment.spaceBetween, - spacing: bigScreen ? 25 : 0, - //mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: WrapCrossAlignment.center, - clipBehavior: Clip.hardEdge, - children: [ - Wrap( - direction: bigScreen ? Axis.horizontal : Axis.vertical, - alignment: WrapAlignment.start, - crossAxisAlignment: WrapCrossAlignment.center, - spacing: bigScreen ? 20 : 0, - clipBehavior: Clip.hardEdge, - children: [ - Stack( - alignment: Alignment.topCenter, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(1000), - child: player.role == "banned" - ? Image.asset("res/avatars/tetrio_banned.png", fit: BoxFit.fitHeight, height: pfpHeight,) - : 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 - fit: BoxFit.fitHeight, height: 128, errorBuilder: (context, error, stackTrace) { - developer.log("Error with building profile picture", name: "main_view", error: error, stackTrace: stackTrace); - return Image.asset("res/avatars/tetrio_anon.png", fit: BoxFit.fitHeight, height: pfpHeight); - }) - : Image.asset("res/avatars/tetrio_anon.png", fit: BoxFit.fitHeight, height: pfpHeight), - ), - if (player.verified) - Padding( - padding: EdgeInsets.fromLTRB(pfpHeight - 22, pfpHeight - 32, 0, 0), - child: const Icon(Icons.verified), - ) - ], - ), - Column( - children: [ - Text(player.username, - style: TextStyle( - fontFamily: "Eurostile Round Extended", - fontSize: bigScreen ? 42 : 28, - shadows: textShadow, - )), - TextButton( - child: Text(player.userId, style: const TextStyle(fontFamily: "Eurostile Round Condensed", fontSize: 14)), - onPressed: () { - copyToClipboard(player.userId); - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.copiedToClipboard))); - }), - ], - ), - ], - ), - showStateTimestamp - ? Text(t.fetchDate(date: timestamp(player.state))) - : Wrap(direction: Axis.horizontal, alignment: WrapAlignment.center, spacing: 25, crossAxisAlignment: WrapCrossAlignment.start, children: [ - FutureBuilder( - future: teto.isPlayerTracking(player.userId), - builder: (context, snapshot) { - switch (snapshot.connectionState) { - case ConnectionState.none: - case ConnectionState.waiting: - case ConnectionState.active: - case ConnectionState.done: - if (snapshot.data != null && snapshot.data!) { - return Column( - children: [ - IconButton( - icon: const Icon( - Icons.person_remove, - shadows: [ - 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, - ), - ],), - onPressed: () { - teto.deletePlayerToTrack(player.userId).then((value) => setState()); - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.stoppedBeingTracked))); - }, - ), - Text(t.stopTracking, textAlign: TextAlign.center) - ], - ); - } else { - return Column( - children: [ - IconButton( - icon: const Icon( - Icons.person_add, - shadows: [ - 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, - ), - ],), - onPressed: () { - teto.addPlayerToTrack(player).then((value) => setState()); - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.becameTracked))); - }, - ), - Text(t.track, textAlign: TextAlign.center) - ], - ); - } - } - }), - Column( - children: [ - IconButton( - icon: const Icon( - Icons.balance, - shadows: [ - 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, - ), - ],), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => CompareView(greenSide: [player, null, null], redSide: const [null, null, null], greenMode: Mode.player, redMode: Mode.player), - ), - ); - }, - ), - Text(t.compare, textAlign: TextAlign.center) - ], - ) - ]) - ]), - ], - ), - ], - ), - ), - ], - ), - if (!["banned", "p1nkl0bst3r"].contains(player.role)) - Wrap( - // mainAxisSize: MainAxisSize.min, - direction: Axis.horizontal, - alignment: WrapAlignment.center, - spacing: 25, - crossAxisAlignment: WrapCrossAlignment.start, - clipBehavior: Clip.hardEdge, // hard WHAT??? - children: [ - if (!player.level.isNegative && !player.level.isNaN) StatCellNum( - playerStat: player.level, - playerStatLabel: t.statCellNum.xpLevel, - isScreenBig: bigScreen, - alertWidgets: [ - Text( - "${NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2).format(player.xp)} XP", - style: const TextStyle(fontFamily: "Eurostile Round", fontWeight: FontWeight.bold) - ), - Padding( - padding: const EdgeInsets.fromLTRB(0, 8, 0, 8), - child: SfLinearGauge( - minimum: 0, - maximum: 1, - interval: 1, - ranges: [ - LinearGaugeRange(startValue: 0, endValue: player.level - player.level.floor(), color: Colors.cyanAccent), - LinearGaugeRange(startValue: 0, endValue: (player.xp / xpTableScuffed.values.toList()[xpTableID]), color: Colors.redAccent, position: LinearElementPosition.cross) - ], - // markerPointers: [LinearShapePointer(value: player.level - player.level.floor(), position: LinearElementPosition.inside, shapeType: LinearShapePointerType.triangle, color: Colors.white, height: 20)], - showTicks: true, - showLabels: false - ), - ), - Text("${t.statCellNum.xpProgress}: ${((player.level - player.level.floor()) * 100).toStringAsFixed(2)} %"), - 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})")], - okText: t.popupActions.ok, - higherIsBetter: true, - ), - if (player.gameTime >= Duration.zero) - StatCellNum( - playerStat: player.gameTime.inHours, - playerStatLabel: t.statCellNum.hoursPlayed, - isScreenBig: bigScreen, - 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,), - if (player.gamesWon >= 0) - StatCellNum( - playerStat: player.gamesWon, - isScreenBig: bigScreen, - playerStatLabel: t.statCellNum.gamesWon, - higherIsBetter: true,), - if (player.friendCount > 0) - StatCellNum( - playerStat: player.friendCount, - isScreenBig: bigScreen, - playerStatLabel: t.statCellNum.friends, - higherIsBetter: true,), - ], - ), - if (player.role == "banned") Text( - t.bigRedBanned, - textAlign: TextAlign.center, - style: TextStyle( - fontFamily: "Eurostile Round Extended", - fontWeight: FontWeight.w900, - color: Colors.red, - fontSize: bigScreen ? 60 : 45, - ), - ), - if (player.role == "p1nkl0bst3r") Text( - t.p1nkl0bst3rAlert, - textAlign: TextAlign.center, - style: const TextStyle( - fontFamily: "Eurostile Round", - fontSize: 16, - ) - ), - if (player.badstanding != null && player.badstanding!) - Text( - t.bigRedBadStanding, - textAlign: TextAlign.center, - style: TextStyle( - fontFamily: "Eurostile Round Extended", - fontWeight: FontWeight.w900, - color: Colors.red, - fontSize: bigScreen ? 60 : 45, - ), - ), - if (player.role != "p1nkl0bst3r") Padding( - padding: EdgeInsets.only(top: bigScreen ? 8 : 0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: RichText( - textAlign: TextAlign.center, - text: TextSpan(text: "", style: const TextStyle( - fontFamily: "Eurostile Round", - fontSize: 16, - color: Colors.white, - ), - children: [ - if (player.country != null) TextSpan(text: "${t.countries[player.country]} • "), - TextSpan(text: "${t.playerRole[player.role]}${t.playerRoleAccount}${t.created} ${timestamp(player.registrationTime)}"), - if (player.supporterTier > 0) const TextSpan(text: " • "), - 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), - 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( - 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: [ - 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); - } - ); - }, - )) - ], - ), - ], - ); - }); - } -} +import 'dart:math'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:syncfusion_flutter_gauges/gauges.dart'; +import 'package:tetra_stats/data_objects/tetrio_constants.dart'; +import 'package:tetra_stats/data_objects/tetrio_player.dart'; +import 'package:tetra_stats/gen/strings.g.dart'; +import 'package:tetra_stats/main.dart'; +import 'package:tetra_stats/utils/copy_to_clipboard.dart'; +import 'package:tetra_stats/utils/numers_formats.dart'; +import 'package:tetra_stats/utils/relative_timestamps.dart'; +import 'package:tetra_stats/utils/text_shadow.dart'; +import 'package:tetra_stats/views/compare_view_tiles.dart'; +import 'package:tetra_stats/widgets/text_timestamp.dart'; +import 'package:transparent_image/transparent_image.dart'; + +class UserThingy extends StatefulWidget { + final TetrioPlayer player; + final bool showStateTimestamp; + final bool initIsTracking; + final Function setState; + + const UserThingy({super.key, required this.player, required this.initIsTracking, required this.showStateTimestamp, required this.setState}); + + @override + State createState() => _UserThingyState(); +} + +class _UserThingyState extends State with SingleTickerProviderStateMixin { + late AnimationController _addToTrackAnimController; + late Animation _addToTrackAnim; + + @override + void initState(){ + _addToTrackAnimController = AnimationController( + value: widget.initIsTracking ? 1.0 : 0.0, + duration: Durations.extralong4, + vsync: this, + ); + _addToTrackAnim = new Tween( + begin: 0.0, + end: 1.0, + ).animate(new CurvedAnimation( + parent: _addToTrackAnimController, + curve: Cubic(.15,-0.40,.86,-0.39), + reverseCurve: Cubic(0,.99,.99,1.01) + )); + + super.initState(); + } + + @override + void dispose() { + _addToTrackAnimController.dispose(); + super.dispose(); + } + + Color roleColor(String role){ + switch (role){ + case "sysop": + return const Color.fromARGB(255, 23, 165, 133); + case "admin": + return const Color.fromARGB(255, 255, 78, 138); + case "mod": + return const Color.fromARGB(255, 204, 128, 242); + case "halfmod": + return const Color.fromARGB(255, 95, 118, 254); + case "bot": + return const Color.fromARGB(255, 60, 93, 55); + case "banned": + return const Color.fromARGB(255, 248, 28, 28); + default: + return Colors.white10; + } + } + + String fontStyle(int length){ + if (length < 10) return "Eurostile Round Extended"; + else if (length < 13) return "Eurostile Round"; + else return "Eurostile Round Condensed"; + } + + @override + Widget build(BuildContext context) { + final t = Translations.of(context); + return LayoutBuilder(builder: (context, constraints) { + double pfpHeight = 128; + int xpTableID = 0; + + while (widget.player.xp > xpTableScuffed.values.toList()[xpTableID]) { + xpTableID++; + } + + return Card( + clipBehavior: Clip.antiAlias, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 4.0), + child: Container( + constraints: const BoxConstraints(maxWidth: 960), + height: widget.player.bannerRevision != null ? 218.0 : 138.0, + child: Stack( + //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}", + placeholder: kTransparentImage, + fit: BoxFit.cover, + height: 120, + fadeInCurve: Easing.standard, fadeInDuration: Durations.long4 + ), + Positioned( + top: widget.player.bannerRevision != null ? 90.0 : 10.0, + left: 16.0, + child: ClipRRect( + borderRadius: BorderRadius.circular(1000), + child: widget.player.role == "banned" + ? Image.asset("res/avatars/tetrio_banned.png", fit: BoxFit.fitHeight, height: pfpHeight,) + : 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), + ) + ), + Positioned( + top: widget.player.bannerRevision != null ? 120.0 : 40.0, + left: 160.0, + child: Tooltip( + message: "${widget.player.userId}\n(Click to copy user ID)", + child: RichText(text: TextSpan(text: widget.player.username, style: TextStyle( + fontFamily: fontStyle(widget.player.username.length), + fontSize: 28, + ), + recognizer: TapGestureRecognizer()..onTap = (){ + copyToClipboard(widget.player.userId); + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.copiedToClipboard))); + } + ) + ) + ), + ), + Positioned( + top: widget.player.bannerRevision != null ? 160.0 : 80.0, + left: 160.0, + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(right: 4.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))), + ), + RichText( + text: TextSpan( + style: const TextStyle(fontFamily: "Eurostile Round"), + children: + [ + 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), + if (widget.player.supporterTier > 0) TextSpan(text: widget.player.supporterTier.toString(), style: TextStyle(color: widget.player.supporterTier > 1 ? Colors.yellowAccent : Colors.white)), + ] + ) + ) + ], + ), + ), + Positioned( + top: widget.player.bannerRevision != null ? 193.0 : 113.0, + left: 160.0, + child: SizedBox( + width: 270, + child: RichText( + text: TextSpan( + style: const TextStyle(fontFamily: "Eurostile Round"), + children: [ + TextSpan(text: timestamp(widget.player.registrationTime), style: const TextStyle(color: Colors.grey)), + if (widget.player.country != null) TextSpan(text: " • ${t.countries[widget.player.country]}") + ] + ) + ), + ) + ), + Positioned( + top: widget.player.bannerRevision != null ? 126.0 : 46.0, + right: 16.0, + 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 = (){ + showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + title: Text("Level ${intf.format(widget.player.level.floor())}", textAlign: TextAlign.center), + content: SingleChildScrollView( + child: ListBody(children: [ + Text( + "${NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2).format(widget.player.xp)} XP", + style: const TextStyle(fontFamily: "Eurostile Round", fontWeight: FontWeight.bold) + ), + Padding( + padding: const EdgeInsets.fromLTRB(0, 8, 0, 8), + child: SfLinearGauge( + minimum: 0, + maximum: 1, + interval: 1, + ranges: [ + LinearGaugeRange(startValue: 0, endValue: widget.player.level - widget.player.level.floor(), color: Colors.cyanAccent), + LinearGaugeRange(startValue: 0, endValue: (widget.player.xp / xpTableScuffed.values.toList()[xpTableID]), color: Colors.redAccent, position: LinearElementPosition.cross) + ], + showTicks: true, + 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})") + ] + ), + ), + actions: [ + TextButton( + child: const Text("OK"), + onPressed: () {Navigator.of(context).pop();} + ) + ] + ) + ); + }), + const TextSpan(text:"\n"), + 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 = (){ + Duration accountAge = DateTime.timestamp().difference(widget.player.registrationTime); + Duration avgGametimeADay = Duration(microseconds: (widget.player.gameTime.inMicroseconds / accountAge.inDays).floor()); + showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + title: Text(t.exactGametime, textAlign: TextAlign.center), + content: SingleChildScrollView( + child: Column( + children: [ + RichText(text: TextSpan( + style: TextStyle(fontFamily: "Eurostile Round", color: Colors.white, fontSize: 28), + children: [ + TextSpan(text: "${intf.format(widget.player.gameTime.inHours)}"), + TextSpan(text: ":${nonsecs.format(widget.player.gameTime.inMinutes%60)}:${nonsecs.format(widget.player.gameTime.inSeconds%60)}"), + TextSpan(text: ".${nonsecs3.format(widget.player.gameTime.inMicroseconds%1000000)}", style: TextStyle(fontSize: 14)) + ] + )), + Text("${playtime(avgGametimeADay)} a day in average"), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text("It's ${f4.format(widget.player.gameTime.inSeconds/31536000)} years,"), + ), + Text("or ${f4.format(widget.player.gameTime.inSeconds/2628000)} months,"), + Text("or ${f4.format(widget.player.gameTime.inSeconds/86400)} days,"), + Text("or ${f2.format(widget.player.gameTime.inMilliseconds/60000)} minutes,"), + Text("or ${intf.format(widget.player.gameTime.inSeconds)} seconds"), + ] + ), + ), + actions: [ + TextButton( + child: const Text("OK"), + onPressed: () {Navigator.of(context).pop();} + ) + ] + ) + ); + }) : null), + const TextSpan(text:"\n"), + TextSpan(text: widget.player.gamesWon > -1 ? intf.format(widget.player.gamesWon) : "---", style: TextStyle(color: widget.player.gamesWon > -1 ? Colors.white : Colors.grey)), + TextSpan(text: "/${widget.player.gamesPlayed > -1 ? intf.format(widget.player.gamesPlayed) : "---"}", style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)), + ] + ) + ) + ) + ], + ), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: AnimatedBuilder( + animation: _addToTrackAnim, + builder: (context, child) { + double firstButtonPosition = 0+(_addToTrackAnim.value as double)*25; + double secondButtonPosition = -25+(_addToTrackAnim.value as double)*25; + double firstButtonOpacity = 1-(_addToTrackAnim.value as double)*2; + double secondButtonOpacity = _addToTrackAnim.value*2-1; + return ElevatedButton.icon( + onPressed: (){ + _addToTrackAnimController.value == 1 ? teto.deletePlayerToTrack(widget.player.userId) : teto.addPlayerToTrack(widget.player); + _addToTrackAnim.isCompleted ? _addToTrackAnimController.reverse() : _addToTrackAnimController.forward(); + }, + icon: _addToTrackAnim.value < 0.5 ? Opacity( + opacity: min(1, firstButtonOpacity), + child: Transform.translate( + offset: Offset(0, _addToTrackAnim.status == AnimationStatus.forward ? firstButtonPosition*4 : firstButtonPosition), + child: Transform.rotate( + angle:_addToTrackAnim.status == AnimationStatus.forward ? (_addToTrackAnim.value as double)*2 : 0, + child: const Icon(Icons.person_add), + ), + ), + ) : Container( + transform: Matrix4.translationValues(secondButtonPosition*5, -secondButtonPosition*25, 0), + child: Opacity( + opacity: max(0, min(1, secondButtonOpacity)), + child: Transform.rotate( + angle:_addToTrackAnim.status == AnimationStatus.reverse ? (1-_addToTrackAnim.value as double)*-20 : 0, + child: const Icon(Icons.person_remove) + ) + ) + ), + label: _addToTrackAnim.value < 0.5 ? Container( + transform: Matrix4.translationValues(0, firstButtonPosition, 0), + child: Opacity( + opacity: max(min(1, firstButtonOpacity), 0), + child: Text(_addToTrackAnimController.isAnimating && _addToTrackAnim.status == AnimationStatus.forward ? "Done!" : "Track") + ) + ) : Container( + transform: Matrix4.translationValues(0, secondButtonPosition, 0), + child: Opacity( + opacity: max(0, min(1, secondButtonOpacity)), + child: Text(_addToTrackAnimController.isAnimating && _addToTrackAnim.status == AnimationStatus.reverse ? "Done! " : "Stop tracking") + ) + ), + style: const ButtonStyle(shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.only(bottomLeft: Radius.circular(12.0)))))); + }, + )), + Expanded( + child: ElevatedButton.icon( + onPressed: (){ + Navigator.push(context, MaterialPageRoute( + builder: (context) => CompareView(widget.player), + ), + ); + }, + icon: const Icon(Icons.balance), + label: Text(t.compare), + style: const ButtonStyle(shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.only(bottomRight: Radius.circular(12.0))))) + ) + ) + ], + ) + ], + ), + ); + }); + } +} diff --git a/lib/widgets/zenith_thingy.dart b/lib/widgets/zenith_thingy.dart index d1954d0..ee0692a 100644 --- a/lib/widgets/zenith_thingy.dart +++ b/lib/widgets/zenith_thingy.dart @@ -1,284 +1,151 @@ 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_single.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/utils/relative_timestamps.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/gauget_thingy.dart'; import 'package:tetra_stats/widgets/text_timestamp.dart'; -class ZenithThingy extends StatefulWidget{ - final RecordSingle? record; - final bool switchable; - 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 createState() => _ZenithThingyState(); -} - -class _ZenithThingyState extends State { - 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"; - } - } +class ZenithThingy extends StatelessWidget{ + final RecordSingle? zenith; + final bool old; + const ZenithThingy({super.key, required this.zenith, this.old = false}); + @override Widget build(BuildContext context) { - return LayoutBuilder(builder: (context, constraints){ - bool bigScreen = constraints.maxWidth > 768; - if (record == null) { - 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), + return Card( + child: Padding( + padding: const EdgeInsets.fromLTRB(8.0, 0.0, 8.0, 0.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: "${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, + Row( + mainAxisAlignment: MainAxisAlignment.center, children: [ - StatCellNum(playerStat: record!.aggregateStats.apm, playerStatLabel: t.statCellNum.apm, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: true), - 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( + Column( mainAxisSize: MainAxisSize.min, children: [ - Stack( - alignment: AlignmentDirectional.bottomStart, - children: [ - const Text("T", style: TextStyle( - 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 - )), - ) - ], + RichText( + text: TextSpan( + text: zenith != null ? "${f2.format(zenith!.stats.zenith!.altitude)} m" : "--- m", + style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, fontWeight: FontWeight.w500, color: (zenith != null && !old) ? Colors.white : Colors.grey), + ), ), - Table( - columnWidths: const { - 0: FixedColumnWidth(36) - }, - children: [ - const TableRow( - children: [ - Text("Floor"), - Text("Split", textAlign: TextAlign.right), - Text("Total", textAlign: TextAlign.right), - ] - ), - 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) RichText( + text: TextSpan( + text: "", + style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey), + children: [ + if (zenith!.rank != -1) TextSpan(text: "№ ${intf.format(zenith!.rank)}", style: TextStyle(color: getColorOfRank(zenith!.rank))), + if (zenith!.rank != -1) const TextSpan(text: " • "), + if (zenith!.countryRank != -1) TextSpan(text: "№ ${intf.format(zenith!.countryRank)} local", style: TextStyle(color: getColorOfRank(zenith!.countryRank))), + if (zenith!.countryRank != -1) const TextSpan(text: " • "), + TextSpan(text: timestamp(zenith!.timestamp)), + ] + ), ), ], ), - ), - ), - 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) - ]), - ) + 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) ], ), - Padding( - padding: const EdgeInsets.only(top: 16.0), - child: Graphs(record!.aggregateStats.apm, record!.aggregateStats.pps, record!.aggregateStats.vs, record!.aggregateStats.nerdStats, record!.aggregateStats.playstyle), + if (zenith != null) Row( + children: [ + 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)) + ]) + ], + ), + ), + ) + ], ) - ], - ) - ); - }); + ] + ), + ) + ); } } \ No newline at end of file diff --git a/res/images/info card 1 focus.png b/res/images/info card 1 focus.png new file mode 100644 index 0000000..658da52 Binary files /dev/null and b/res/images/info card 1 focus.png differ diff --git a/res/images/info card 1.png b/res/images/info card 1.png new file mode 100644 index 0000000..1629628 Binary files /dev/null and b/res/images/info card 1.png differ