From cda6dc790c3edea9084514cca15c364fce68e669 Mon Sep 17 00:00:00 2001 From: dan63047 Date: Sat, 10 Aug 2024 01:52:50 +0300 Subject: [PATCH] Redesign still WIP --- lib/views/main_view_tiles.dart | 645 +++++++++++++++++++++++++++------ res/icons/allspin.png | Bin 0 -> 1820 bytes res/icons/doublehole.png | Bin 0 -> 2133 bytes res/icons/expert.png | Bin 0 -> 1608 bytes res/icons/gravity.png | Bin 0 -> 1133 bytes res/icons/invisible.png | Bin 0 -> 1166 bytes res/icons/messy.png | Bin 0 -> 1008 bytes res/icons/nohold.png | Bin 0 -> 1446 bytes res/icons/volatile.png | Bin 0 -> 1441 bytes 9 files changed, 542 insertions(+), 103 deletions(-) create mode 100644 res/icons/allspin.png create mode 100644 res/icons/doublehole.png create mode 100644 res/icons/expert.png create mode 100644 res/icons/gravity.png create mode 100644 res/icons/invisible.png create mode 100644 res/icons/messy.png create mode 100644 res/icons/nohold.png create mode 100644 res/icons/volatile.png diff --git a/lib/views/main_view_tiles.dart b/lib/views/main_view_tiles.dart index 41edc67..43bb7d7 100644 --- a/lib/views/main_view_tiles.dart +++ b/lib/views/main_view_tiles.dart @@ -8,10 +8,12 @@ import 'package:syncfusion_flutter_charts/charts.dart'; import 'package:syncfusion_flutter_gauges/gauges.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/tl_match_view.dart'; +import 'package:tetra_stats/widgets/graphs.dart'; import 'package:tetra_stats/widgets/list_tile_trailing_stats.dart'; import 'package:tetra_stats/widgets/stat_sell_num.dart'; import 'package:tetra_stats/widgets/text_timestamp.dart'; @@ -33,16 +35,16 @@ class MainView extends StatefulWidget { } enum Page {home, leaderboards, leagueAverages, calculator, settings} -enum Cards {overview, tetraLeague, quickPlay, quickPlayExpert, sprint, blitz, other} -enum CardMod {info, recent} +enum Cards {overview, tetraLeague, quickPlay, sprint, blitz} +enum CardMod {info, recent, top, ex, exRecent, exTop} Map cardsTitles = { Cards.overview: "Overview", Cards.tetraLeague: t.tetraLeague, Cards.quickPlay: t.quickPlay, - Cards.quickPlayExpert: "${t.quickPlay} ${t.expert}", + //Cards.quickPlayExpert: "${t.quickPlay} ${t.expert}", Cards.sprint: t.sprint, Cards.blitz: t.blitz, - Cards.other: t.other + //Cards.other: t.other }; TetrioPlayer testPlayer = TetrioPlayer( @@ -177,6 +179,11 @@ class _MainState extends State with TickerProviderStateMixin { selectedIcon: Icon(Icons.calculate), label: Text('Calc'), ), + NavigationRailDestination( + icon: Icon(Icons.storage), + selectedIcon: Icon(Icons.storage), + label: Text('Saved Data'), + ), NavigationRailDestination( icon: Icon(Icons.settings), selectedIcon: Icon(Icons.settings), @@ -225,13 +232,13 @@ class _DestinationLeaderboardsState extends State { height: widget.constraints.maxHeight, child: Column( children: [ - Card( + const Card( child: Row( mainAxisSize: MainAxisSize.min, children: [ - const Spacer(), - Text("Leaderboards", style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36)), - const Spacer() + Spacer(), + Text("Leaderboards", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36)), + Spacer() ], ), ), @@ -253,10 +260,12 @@ class _DestinationLeaderboardsState extends State { ), SizedBox( width: widget.constraints.maxWidth - 350 - 88, - child: Column( - children: [ - - ], + child: const Card( + child: Column( + children: [ + + ], + ), ), ), ], @@ -533,10 +542,10 @@ class _DestinationGraphsState extends State { ); } } - return Center(child: Column( + return const Center(child: Column( mainAxisSize: MainAxisSize.min, children: [ - Text("lol", style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28)), + Text("lol", style: TextStyle(fontFamily: "Eurostile Round", fontSize: 28)), ], )); }, @@ -565,7 +574,258 @@ class DestinationHome extends StatefulWidget{ class _DestinationHomeState extends State { Cards rightCard = Cards.tetraLeague; + CardMod cardMod = CardMod.info; Duration postSeasonLeft = seasonStart.difference(DateTime.now()); + late Map>> modeButtons; + late MapEntry closestAverageBlitz; + late bool blitzBetterThanClosestAverage; + late MapEntry closestAverageSprint; + late bool sprintBetterThanClosestAverage; + bool? sprintBetterThanRankAverage; + bool? blitzBetterThanRankAverage; + + Widget getOverviewCard(Summaries summaries){ + return const Column( + children: [ + Card( + child: Padding( + padding: EdgeInsets.only(bottom: 4.0), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text("Overview", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42)), + ], + ), + ), + ), + ), + Card( + child: Padding( + padding: EdgeInsets.fromLTRB(8.0, 0.0, 8.0, 0.0), + child: Column( + children: [ + Row( + children: [ + Text("Title"), + Spacer(), + Text("Value"), + ], + ) + ], + ), + ), + ), + ] + ); + } + + Widget getTetraLeagueCard(TetraLeagueAlpha data){ + return Column( + children: [ + Card( + child: Padding( + padding: const EdgeInsets.only(bottom: 4.0), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text(t.tetraLeague, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42)), + Text("${t.seasonStarts} ${countdown(postSeasonLeft)}", textAlign: TextAlign.center) + ], + ), + ), + ), + ), + TetraLeagueThingy(league: testPlayer.tlSeason1!), + Card( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Spacer(), + Text(t.nerdStats, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42)), + const Spacer() + ], + ), + ), + NerdStatsThingy(nerdStats: testPlayer.tlSeason1!.nerdStats!), + GraphsThingy(nerdStats: testPlayer.tlSeason1!.nerdStats!, playstyle: testPlayer.tlSeason1!.playstyle!, apm: testPlayer.tlSeason1!.apm!, pps: testPlayer.tlSeason1!.pps!, vs: testPlayer.tlSeason1!.vs!) + ], + ); + } + + Widget getZenithCard(RecordSingle? record){ + return Column( + children: [ + Card( + child: Padding( + padding: const EdgeInsets.only(bottom: 4.0), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text(t.quickPlay, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42)), + Text("Leaderboard reset in ${countdown(postSeasonLeft)}", textAlign: TextAlign.center), + ], + ), + ), + ), + ), + ZenithThingy(zenith: record), + if (record != null) Card( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Spacer(), + Text(t.nerdStats, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42)), + const Spacer() + ], + ), + ), + 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) + ], + ); + } + + Widget getRecordCard(RecordSingle? record){ + return Column( + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + // if (record!.gamemode == "40l") Padding(padding: const EdgeInsets.only(right: 8.0), + // child: Image.asset("res/tetrio_tl_alpha_ranks/${closestAverageSprint.key}.png", height: 96) + // ), + // if (record!.gamemode == "blitz") Padding(padding: const EdgeInsets.only(right: 8.0), + // child: Image.asset("res/tetrio_tl_alpha_ranks/${closestAverageBlitz.key}.png", height: 96) + // ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + RichText(text: TextSpan( + text: record!.gamemode == "40l" ? get40lTime(record.stats.finalTime.inMicroseconds) : NumberFormat.decimalPattern().format(record.stats.score), + style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, fontWeight: FontWeight.w500, color: Colors.white), + ), + ), + RichText(text: TextSpan( + text: "", + style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey), + children: [ + // if (record!.gamemode == "40l" && (rank != null && rank != "z")) TextSpan(text: "${t.verdictGeneral(n: readableTimeDifference(record!.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 if (record!.gamemode == "40l" && (rank == null || rank == "z")) TextSpan(text: "${t.verdictGeneral(n: readableTimeDifference(record!.stats.finalTime, closestAverageSprint.value), verdict: sprintBetterThanClosestAverage ? t.verdictBetter : t.verdictWorse, rank: closestAverageSprint.key.toUpperCase())}\n", style: TextStyle( + // color: sprintBetterThanClosestAverage ? Colors.greenAccent : Colors.redAccent + // )) + // else if (record!.gamemode == "blitz" && (rank != null && rank != "z")) TextSpan(text: "${t.verdictGeneral(n: readableIntDifference(record!.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 if (record!.gamemode == "blitz" && (rank == null || rank == "z")) TextSpan(text: "${t.verdictGeneral(n: readableIntDifference(record!.stats.score, closestAverageBlitz.value), verdict: blitzBetterThanClosestAverage ? t.verdictBetter : t.verdictWorse, rank: closestAverageBlitz.key.toUpperCase())}\n", style: TextStyle( + // color: blitzBetterThanClosestAverage ? Colors.greenAccent : Colors.redAccent + // )), + if (record.rank != -1) TextSpan(text: "№${record.rank}", style: TextStyle(color: getColorOfRank(record.rank))), + if (record.rank != -1) const TextSpan(text: " • "), + TextSpan(text: timestamp(record.timestamp)), + ] + ), + ) + ],), + ], + ), + ] + ); + } + + @override + initState(){ + // bool? blitzBetterThanRankAverage = (rank != null && rank != "z") ? record!.stats.score > blitzAverages[rank]! : null; + // bool? sprintBetterThanRankAverage = (rank != null && rank != "z") ? record!.stats.finalTime < sprintAverages[rank]! : null; + // if (record!.gamemode == "40l") { + // closestAverageSprint = sprintAverages.entries.singleWhere((element) => element.value == sprintAverages.values.reduce((a, b) => (a-record!.stats.finalTime).abs() < (b -record!.stats.finalTime).abs() ? a : b)); + // sprintBetterThanClosestAverage = record!.stats.finalTime < closestAverageSprint.value; + // }else if (record!.gamemode == "blitz"){ + // closestAverageBlitz = blitzAverages.entries.singleWhere((element) => element.value == blitzAverages.values.reduce((a, b) => (a-record!.stats.score).abs() < (b -record!.stats.score).abs() ? a : b)); + // blitzBetterThanClosestAverage = record!.stats.score > closestAverageBlitz.value; + // } + modeButtons = { + Cards.overview: [ + const ButtonSegment( + value: CardMod.info, + label: Text('General'), + ), + ], + Cards.tetraLeague: [ + const ButtonSegment( + value: CardMod.info, + label: Text('Standing'), + ), + const ButtonSegment( + value: CardMod.recent, + label: Text('Recent Matches'), + ), + ], + Cards.quickPlay: [ + const ButtonSegment( + value: CardMod.info, + label: Text('Normal'), + ), + const ButtonSegment( + value: CardMod.recent, + label: Text('Recent Normal'), + ), + const ButtonSegment( + value: CardMod.top, + label: Text('Top Normal'), + ), + const ButtonSegment( + value: CardMod.ex, + label: Text('Expert'), + ), + const ButtonSegment( + value: CardMod.exRecent, + label: Text('Recent Expert'), + ), + const ButtonSegment( + value: CardMod.exTop, + label: Text('Top Expert'), + ), + ], + Cards.blitz: [ + const ButtonSegment( + value: CardMod.info, + label: Text('PB'), + ), + const ButtonSegment( + value: CardMod.recent, + label: Text('Recent'), + ), + const ButtonSegment( + value: CardMod.top, + label: Text('Top'), + ), + ], + Cards.sprint: [ + const ButtonSegment( + value: CardMod.info, + label: Text('PB'), + ), + const ButtonSegment( + value: CardMod.recent, + label: Text('Recent'), + ), + const ButtonSegment( + value: CardMod.top, + label: Text('Top'), + ), + ] + }; + super.initState(); + } @override Widget build(BuildContext context) { @@ -586,6 +846,9 @@ class _DestinationHomeState extends State { NewUserThingy(player: snapshot.data!, showStateTimestamp: false, setState: setState), if (snapshot.data!.badges.isNotEmpty) BadgesThingy(badges: snapshot.data!.badges), if (snapshot.data!.distinguishment != null) DistinguishmentThingy(snapshot.data!.distinguishment!), + if (snapshot.data!.role == "bot") FakeDistinguishmentThingy(bot: true, botMaintainers: snapshot.data!.botmaster), + if (snapshot.data!.role == "banned") FakeDistinguishmentThingy(banned: true) + else if (snapshot.data!.badstanding == true) FakeDistinguishmentThingy(badStanding: true), if (snapshot.data!.bio != null) Card( child: Column( children: [ @@ -612,7 +875,7 @@ class _DestinationHomeState extends State { case ConnectionState.none: case ConnectionState.waiting: case ConnectionState.active: - return Card(child: Center(child: CircularProgressIndicator())); + return const Card(child: Center(child: CircularProgressIndicator())); case ConnectionState.done: if (snapshot.hasData){ return NewsThingy(snapshot.data!); @@ -624,13 +887,30 @@ class _DestinationHomeState extends State { )); } } - return Text("what?"); + return const Text("what?"); } ), ) ], ); - }else{ + } + if (snapshot.hasError){ + if (snapshot.error.runtimeType == TetrioPlayerNotExist) { + return Card( + child: Center(child: + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(t.errors.noSuchUser, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 42, fontWeight: FontWeight.bold), textAlign: TextAlign.center), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text(t.errors.noSuchUserSub, textAlign: TextAlign.center), + ), + ], + ) + ), + ); + } return Center(child: Column( mainAxisSize: MainAxisSize.min, @@ -644,6 +924,7 @@ class _DestinationHomeState extends State { ) ); } + return Text("huh?"); } }, )), @@ -655,64 +936,58 @@ class _DestinationHomeState extends State { SizedBox( height: widget.constraints.maxHeight - 64, child: SingleChildScrollView( - child: Column( - //mainAxisSize: MainAxisSize.min, - children: [ - Card( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Spacer(), - Text(t.tetraLeague, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42)), - const Spacer() - ], - ), - ), - Card( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(t.seasonStarts), - Center(child: Text(countdown(postSeasonLeft), textAlign: TextAlign.center, style: const TextStyle(fontSize: 32.0))), - ], - ), - ), - TetraLeagueThingy(league: testPlayer.tlSeason1!), - Card( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Spacer(), - Text(t.nerdStats, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42)), - const Spacer() - ], - ), - ), - NerdStatsThingy(nerdStats: testPlayer.tlSeason1!.nerdStats!) - ], + child: FutureBuilder( + future: teto.fetchSummaries(widget.searchFor), + 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 switch (rightCard){ + Cards.overview => getOverviewCard(snapshot.data!), + Cards.tetraLeague => getTetraLeagueCard(snapshot.data!.league), + Cards.quickPlay => getZenithCard(cardMod == CardMod.ex ? snapshot.data?.zenithEx : snapshot.data?.zenith), + Cards.sprint => getRecordCard(snapshot.data?.sprint), + Cards.blitz => getRecordCard(snapshot.data?.blitz), + }; + } + if (snapshot.hasError){ + return Center(child: + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(snapshot.error != null ? snapshot.error.toString() : "lol", 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 != null ? snapshot.stackTrace.toString() : "lol", textAlign: TextAlign.center), + ), + ], + ) + ); + } + return const Text("lol"); + } + } ), ), ), - SegmentedButton( + if (modeButtons[rightCard]!.length > 1) SegmentedButton( showSelectedIcon: false, - selected: {CardMod.info}, - segments: >[ - ButtonSegment( - value: CardMod.info, - label: Text('PB'), - //icon: Icon(Icons.calendar_view_day) - ), - ButtonSegment( - value: CardMod.recent, - label: Text('Recent'), - //icon: Icon(Icons.calendar_view_day) - ), - ] + selected: {cardMod}, + segments: modeButtons[rightCard]!, + onSelectionChanged: (p0) { + setState(() { + cardMod = p0.first; + }); + }, ), SegmentedButton( showSelectedIcon: false, segments: >[ - ButtonSegment( + const ButtonSegment( value: Cards.overview, //label: Text('Overview'), icon: Icon(Icons.calendar_view_day)), @@ -724,10 +999,6 @@ class _DestinationHomeState extends State { value: Cards.quickPlay, //label: Text('Quick Play'), icon: SvgPicture.asset("res/icons/qp.svg", height: 16, colorFilter: ColorFilter.mode(theme.colorScheme.primary, BlendMode.modulate))), - // ButtonSegment( - // value: Cards.quickPlayExpert, - // label: Text('QP Expert'), - // icon: Icon(Icons.calendar_today)), ButtonSegment( value: Cards.sprint, //label: Text('40 Lines'), @@ -736,17 +1007,14 @@ class _DestinationHomeState extends State { value: Cards.blitz, //label: Text('Blitz'), icon: SvgPicture.asset("res/icons/blitz.svg", height: 16, colorFilter: ColorFilter.mode(theme.colorScheme.primary, BlendMode.modulate))), - // ButtonSegment( - // value: Cards.other, - // label: Text('Other'), - // icon: Icon(Icons.calendar_today)), ], selected: {rightCard}, onSelectionChanged: (Set newSelection) { setState(() { + cardMod = CardMod.info; rightCard = newSelection.first; });}) - ], + ] ), ), // SizedBox( @@ -928,7 +1196,7 @@ class NewsThingy extends StatelessWidget{ const Spacer() ] ), - if (news.news.isEmpty) Center(child: Text("Empty list")) + if (news.news.isEmpty) const Center(child: Text("Empty list")) else for (NewsEntry entry in news.news) getNewsTile(entry) ], ), @@ -1037,6 +1305,59 @@ class DistinguishmentThingy extends StatelessWidget{ } } +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 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: Column( + children: [ + Center( + child: RichText( + textAlign: TextAlign.center, + text: TextSpan( + style: DefaultTextStyle.of(context).style, + children: [getDistinguishmentTitle()], + ), + ), + ), + Text(getDistinguishmentSubtitle(), style: const TextStyle(fontSize: 18), textAlign: TextAlign.center), + ], + ), + ); + } + +} + class BadgesThingy extends StatelessWidget{ final List badges; @@ -1258,7 +1579,7 @@ class NewUserThingy extends StatelessWidget { text: TextSpan( style: const TextStyle(fontFamily: "Eurostile Round"), children: [ - TextSpan(text: "Level ${intf.format(player.level.floor())}", style: TextStyle(decoration: TextDecoration.underline, decorationColor: Colors.white70, decorationStyle: TextDecorationStyle.dotted), recognizer: TapGestureRecognizer()..onTap = (){ + TextSpan(text: "Level ${intf.format(player.level.floor())}", style: const TextStyle(decoration: TextDecoration.underline, decorationColor: Colors.white70, decorationStyle: TextDecorationStyle.dotted), recognizer: TapGestureRecognizer()..onTap = (){ showDialog( context: context, builder: (BuildContext context) => AlertDialog( @@ -1453,21 +1774,21 @@ class TetraLeagueThingy extends StatelessWidget{ Expanded( child: Center( child: Table( - defaultColumnWidth:IntrinsicColumnWidth(), + defaultColumnWidth:const IntrinsicColumnWidth(), children: [ TableRow(children: [ - Text("APM: ", style: TextStyle(fontSize: 21)), - Text(league.apm != null ? f2.format(league.apm) : "---", textAlign: TextAlign.right, style: TextStyle(fontSize: 21)), + const Text("APM: ", style: TextStyle(fontSize: 21)), + Text(league.apm != null ? f2.format(league.apm) : "---", textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), //Text(" APM", style: TextStyle(fontSize: 21)) ]), TableRow(children: [ - Text("PPS: ", style: TextStyle(fontSize: 21)), - Text(league.apm != null ? f2.format(league.pps) : "---", textAlign: TextAlign.right, style: TextStyle(fontSize: 21)), + const Text("PPS: ", style: TextStyle(fontSize: 21)), + Text(league.apm != null ? f2.format(league.pps) : "---", textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), //Text(" PPS", style: TextStyle(fontSize: 21)) ]), TableRow(children: [ - Text("VS: ", style: TextStyle(fontSize: 21)), - Text(league.apm != null ? f2.format(league.vs) : "---", textAlign: TextAlign.right, style: TextStyle(fontSize: 21)), + const Text("VS: ", style: TextStyle(fontSize: 21)), + Text(league.apm != null ? f2.format(league.vs) : "---", textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), // Text(" VS", style: TextStyle(fontSize: 21)) ]) ], @@ -1494,7 +1815,7 @@ class TetraLeagueThingy extends StatelessWidget{ ], annotations: [ GaugeAnnotation(widget: Container(child: - Text(percentage.format(league.winrate), textAlign: TextAlign.center, style: TextStyle(fontSize: 25,fontWeight: FontWeight.bold))), + Text(percentage.format(league.winrate), textAlign: TextAlign.center, style: const TextStyle(fontSize: 25,fontWeight: FontWeight.bold))), angle: 90,positionFactor: 0.1 ), GaugeAnnotation(widget: Container(child: @@ -1509,22 +1830,22 @@ class TetraLeagueThingy extends StatelessWidget{ Expanded( child: Center( child: Table( - defaultColumnWidth:IntrinsicColumnWidth(), + defaultColumnWidth:const IntrinsicColumnWidth(), children: [ TableRow(children: [ //Text("VS: ", style: TextStyle(fontSize: 21)), - Text("№ ${intf.format(league.standingLocal)}", textAlign: TextAlign.right, style: TextStyle(fontSize: 21)), - Text(" in BY", style: TextStyle(fontSize: 21)) + Text("№ ${intf.format(league.standingLocal)}", textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), + const Text(" in BY", style: TextStyle(fontSize: 21)) ]), TableRow(children: [ //Text("APM: ", style: TextStyle(fontSize: 21)), - Text(intf.format(league.gamesPlayed), textAlign: TextAlign.right, style: TextStyle(fontSize: 21)), - Text(" Games", style: TextStyle(fontSize: 21)) + Text(intf.format(league.gamesPlayed), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), + const Text(" Games", style: TextStyle(fontSize: 21)) ]), TableRow(children: [ //Text("PPS: ", style: TextStyle(fontSize: 21)), - Text(intf.format(league.gamesWon), textAlign: TextAlign.right, style: TextStyle(fontSize: 21)), - Text(" Won", style: TextStyle(fontSize: 21)) + Text(intf.format(league.gamesWon), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), + const Text(" Won", style: TextStyle(fontSize: 21)) ]) ], ), @@ -1574,10 +1895,10 @@ class NerdStatsThingy extends StatelessWidget{ RichText( textAlign: TextAlign.center, text: TextSpan( - style: TextStyle(fontFamily: "Eurostile Round"), + style: const TextStyle(fontFamily: "Eurostile Round"), children: [ - TextSpan(text: "APP\n"), - TextSpan(text: f3.format(nerdStats.app), style: TextStyle(fontSize: 25,fontWeight: FontWeight.bold)), + const TextSpan(text: "APP\n"), + TextSpan(text: f3.format(nerdStats.app), style: const TextStyle(fontSize: 25,fontWeight: FontWeight.bold)), //TextSpan(text: "\nAPP"), ] ))), @@ -1604,10 +1925,10 @@ class NerdStatsThingy extends StatelessWidget{ RichText( textAlign: TextAlign.center, text: TextSpan( - style: TextStyle(fontFamily: "Eurostile Round"), + style: const TextStyle(fontFamily: "Eurostile Round"), children: [ - TextSpan(text: "VS/APM\n"), - TextSpan(text: f3.format(nerdStats.vsapm), style: TextStyle(fontSize: 25,fontWeight: FontWeight.bold)), + const TextSpan(text: "VS/APM\n"), + TextSpan(text: f3.format(nerdStats.vsapm), style: const TextStyle(fontSize: 25,fontWeight: FontWeight.bold)), ] ))), angle: 0,positionFactor: 0.5 @@ -1645,11 +1966,33 @@ class EstTrThingy extends StatelessWidget{ @override Widget build(BuildContext context) { - // TODO: implement build - throw UnimplementedError(); + 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 double min; @@ -1684,11 +2027,11 @@ class GaugetThingy extends StatelessWidget{ ], annotations: [ GaugeAnnotation(widget: Container(child: - Text(f.format(value), textAlign: TextAlign.center, style: TextStyle(fontSize: 25,fontWeight: FontWeight.bold))), + Text(f.format(value), textAlign: TextAlign.center, style: const TextStyle(fontSize: 25,fontWeight: FontWeight.bold))), angle: 90,positionFactor: 0.25 ), GaugeAnnotation(widget: Container(child: - Text(label, textAlign: TextAlign.center, style: TextStyle(height: .9))), + Text(label, textAlign: TextAlign.center, style: const TextStyle(height: .9))), angle: 270,positionFactor: 0.4 ) ], @@ -1697,6 +2040,102 @@ class GaugetThingy extends StatelessWidget{ ), ); } +} + +class ZenithThingy extends StatelessWidget{ + final RecordSingle? zenith; + + const ZenithThingy({super.key, required this.zenith}); + + @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 ? 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: "№${zenith!.rank}", style: TextStyle(color: getColorOfRank(zenith!.rank))), + if (zenith!.rank != -1) const TextSpan(text: " • "), + if (zenith!.countryRank != -1) TextSpan(text: "№${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: [ + const Text("APM: ", style: TextStyle(fontSize: 21)), + Text(f2.format(zenith!.aggregateStats.apm), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), + ]), + TableRow(children: [ + const Text("PPS: ", style: TextStyle(fontSize: 21)), + Text(f2.format(zenith!.aggregateStats.pps), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), + ]), + TableRow(children: [ + const Text("VS: ", style: TextStyle(fontSize: 21)), + Text(f2.format(zenith!.aggregateStats.vs), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), + ]) + ], + ), + ), + ), + 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(f2.format(zenith!.stats.cps), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), + const Text(" CPS", style: TextStyle(fontSize: 21)) + ]), + TableRow(children: [ + Text(f2.format(zenith!.stats.zenith!.peakrank), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), + const Text(" Peak CPS", style: TextStyle(fontSize: 21)) + ]) + ], + ), + ), + ), + ], + ) + ] + ), + ) + ); + } } diff --git a/res/icons/allspin.png b/res/icons/allspin.png new file mode 100644 index 0000000000000000000000000000000000000000..f40c752dfe21fc7f297745d45acc44f2eb3a495d GIT binary patch literal 1820 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 literal 0 HcmV?d00001 diff --git a/res/icons/expert.png b/res/icons/expert.png new file mode 100644 index 0000000000000000000000000000000000000000..678ce4fba9a83c190a8abf7cc1fad5c169d7a61a GIT binary patch literal 1608 zcmeAS@N?(olHy`uVBq!ia0vp^+d-Iv8AzsYuwM(L7>k44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O`1&4)6(a1=1VeZX1^9P$cg;p!YmVg8YIRto%%w*f*VA_d&3C1()t;E-apNP;gJh$?cvljv*Dd-rju~B;_m7_Rw8qs!GOZ%#o`{7d~S~0a-^zA^1wp0_%CzRCP3e)-eJ+AQ&{2T)O%8NX`g;u*Sk)3%j*Mx}4N9b{PJ zJV{QdarR9G&sWB=;aY9`ub2KlnzQnBNv$VPLz$VgXOMb=__?{OcV(I;h##_^v_#Qx z$6VfB?`9MppY?i5;jFI0_F1oke-sI{-oCkqOCo(%wt-~4@mqte1v}3**?$o&+%fm8 z_uY~sE3{eH`X39wa4cm7Tdd(L5A(e$eA_oay#FD0@2T|frs4MTb)x^2`xdg~rGIvN z%(v{J;_k(Bta^5xNS4pIBYg_!l#EN8D}T5e#Bj4cIBN5Irdp${TzdO?-Nomew{b}y zb}lpEJM|!}tm9Sh!^>}!pNjN6_l+&*W3YWay;Qr*Z1${IdJH+9m(tF;gj@LL`E7ik zwThdkfLZ#}vwLefe>4j0F(Kq$^{G58PbhP z*gm*8@^3iUnW<#M8h?^q%Tj+Fm&)Oed3v|` zRe|tswwi#}SXsLb8?q1b=ImHJN#{+~hkc9wGBEGSPq^M1edB}oMxHmfKKu{8Q@i27 zed!xt9|msY%uBr9{$HckL(A^*cb_-E3Pe->R!Z--s|o%2S82QV)9-grtm6+CymM&U z)L8j_(H!O*%GUzZxUFcZ`X?XOw>NKo)YJ48tZ@A%^)&WWh+AZT$c+tB(f!l5FsROZ z^r2|mt;i3bE&24*>+>=jr=EIvUv%YK*4+1tJ_LQ+eN9Y2>)QR);D~B&v+W|Qi&sTF z*VUU4+IuTz!k;ki@7u1jK8V^;CVr=UOTye~<`x+TZ`}*{lz1a@!Nc}tW*?UD1}}ZH zW&OFMc5dvy-PV5Ca&Sk(+;0obInH0QOM- z?cvKiM}S1k1rX~ti1i%A`UGPA0}}T-fkfO?5NiR5wWfqWQq$({=IQTDby}BKTVJ+a zx%5K`f5EQuog(wziTZ!vG5yd}0|&j=nxfyNzj|fWGR_L}1Qv@7p00i_>zopr0E>J- A)Bpeg literal 0 HcmV?d00001 diff --git a/res/icons/gravity.png b/res/icons/gravity.png new file mode 100644 index 0000000000000000000000000000000000000000..54fe2ffe3ab7ad08aab4aa0d73e2fcaeab263d8f GIT binary patch literal 1133 zcmeAS@N?(olHy`uVBq!ia0vp^+d-Iv8AzsYuwM(L7>k44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O`252Ka=y0_lx!w+#}MbSLXK&_VhoL4Lsu|5+;5P2T?O>#^(e?@wq^x%w;C z^V5bD3#k`X#RVVylClkD`z}4?d}2CPV%KY9t3@xx9T*sxojqL~Ln>~)y~~~@<0#^K zu{>L3PVu>$UctZP=N)8IR@wbH_#*$SJO}oJ20oYn*WXQeFDg0VWM7N3ij2}j0nT<0 z3ztKRLVOboI;bx+fkXdJit1!uBGJ)vbt={UY!CFz{!H09|3S-ixBb2h>U$i7Eg~MW z&N^_AOaB53Usy$>TET`x-!HJ7%cwrP!b#XdcvYU+mIUY0%{v!|-aW{*-sfG2wiS!| z9;3y}qwWM39MayL^`&FWteY2Fc(0y$=Fn|*fOGws_nXDONcMaSY>9WKI;nSv{h&S#!miq?%KbHv!?XsHt)aK zQl?k-*nin%-K!eqQ`dZ$>vp^HM%<6&-fuS!l^vF@J%0OPsPf*DKb-b|Vw7bgZ`AE@ z`?TTj2ebVWe?AEFZu<^o2lJM_O8os$T_NojLY% z_@nNyFWdb0Lv_@Ry`nknbJU|Q2#0O({lIi^G?Hz~jxt_b`S~$dO7i-m?KlF8h{*xUs z_ir09yyvd_cjdc7FH3D=#C`4SLifI_ZEN|ycY*H1+V{EM7587?0SZmq4ix&M`|$5O z7w?Mwt1k)Nd(XDD<@@f4zkD|}_T0bux}4Yj;orRU{gbV@-j_H0zWX$=V!!si_dEIz iiApRuac_0`*7(3xejC2{=ZOGwB7>)^pUXO@geCx>+dv5b literal 0 HcmV?d00001 diff --git a/res/icons/invisible.png b/res/icons/invisible.png new file mode 100644 index 0000000000000000000000000000000000000000..13409410870b77848e5578c8ccda06f1cea53780 GIT binary patch literal 1166 zcmeAS@N?(olHy`uVBq!ia0vp^+d-Iv8AzsYuwM(L7>k44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O`0F1o(uw0_lx!x6vQ{{yQlEXt#PvkY6x^m6L68!3 z$NBN!e|sJMw;odq{BqJnyzi}-?EEUg$9d_7(T3h7JRYaomgG>IurT{}^X19n<>~Hh z@n;^cc;Is9^Fux!cNXwew|O@V&)rx-)nXlo$d8Yzy0oKLFy`L*|T>2JHq`c=F}?5|IK@CTB7pX z&E?8#v5$uz=Lmk?>8kX>BHG0^Pf*_G+`g^nZ@g2rXOKOZmOXFN-AlI(8P-YE8lStF z_p_ml;-UGBzXmr7E122$Y}VazuLz));T#DmNzdHZCdy=#rgEfwrP`j zb=9S#{LHohCAe=Um7i^l?Y?VNq5NXsqXX;yyyMGvxjS9vM(Pb;@7L3&owJmKTMUGvg<}!Ki`h7BM%DAa(F8y+`TOD``y2XJAdz%h&ej(*Ui7G zN(G|fuhPrEpZ-wJxNYvPn|I&V_pja&tiOBr-GZqOuTsjli?0*%s(tfr+r#-XZv_5$ zrzh`zyMGQ}8PC6CDapI9IRb&wM`T=H6M+ zYTv!`X3jf2H{rqK%ilikd@G!0DqfPajmzLS>SdycB c_5WYS5bsAnU2ON)1M@F~r>mdKI;Vst0H+>jWB>pF literal 0 HcmV?d00001 diff --git a/res/icons/messy.png b/res/icons/messy.png new file mode 100644 index 0000000000000000000000000000000000000000..7fc79640a95620d6e98a626012a7219c35d68403 GIT binary patch literal 1008 zcmeAS@N?(olHy`uVBq!ia0vp^+d-Iv8AzsYuwM(L7>k44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O`1=1o(uw0_lx!x6udPHmy(x+NWF+sLR;dLE3wBb#S& zdXH)8S9PiG85P2_-pX6W@K0OBz`*p`)5S5Q;?~={;d#=I0xb{aq7GW7M^Cx>|8M`2 z-9H@7cwQXj7CWjbYEat5&$7|F`(=RonMzkSj#Ot2X{Vb4r`iI{S~e+8;a!+Rg3zBg z9u_W#6ovRE7Id^YtH>xl6yR*fDx`9VefgOaDTm=`#-Y0Z50yXY$M26h*Zex?Ug*^y z2V?DO*R4vDib>zRdiS2H+Q9llCOgihZU2~LbeO&AHorVao_&qNj=vuoAAG|hppj=? zqp{r{l}}g{A%aJLzZC)+y%|MSF`N6dw1)^Z9C@s?kF($bMF=F`H%0!pYQ)S zol`t(*6o7E%#zpQ$Lh+pOIho_N6JnyJeaHDAb96ollRw~1(ulxR`{Ws2jaOSpo=+{r2Iq7Fr zZ#ArE4BPN*&e@jrmUYGtv;Cg>{FrsvN_gKhp7TJVTOgr7K0j_X8wl_FR&%Chy{#Eg z$p7-K?ya2h)%UN>+w1$|mh#STWqF6L$KBe>UIA1VvwrWR*&nu=*Q@PODvs0s{#iQL X>F@{Zk5LA|+`{1L>gTe~DWM4f!O9U0 literal 0 HcmV?d00001 diff --git a/res/icons/nohold.png b/res/icons/nohold.png new file mode 100644 index 0000000000000000000000000000000000000000..38811ec4986fd1ad8e953cb37c5e230d3ad1205c GIT binary patch literal 1446 zcmeAS@N?(olHy`uVBq!ia0vp^+d-Iv8AzsYuwM(L7>k44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O`1Y4e$wZ1=1VeZW|#eDPjT((8GQuL4LsuR#nE|Kk%q9vCkJ2d!}g{{55uE zT1BH_z%3Vhi{jGw*Gg^KGcL7sbMj4|B`g0&@9nR359@sjmfeXsI3X(3y;Dj;c;U$; z9aB%I%+q}=Qo94}7#NuUdb&7F-;pR?4r^ov>X{hxk& zyn;k%`CQ{v`NWm?v@1+5tyr2jeeHp5Q;iffr6+Atqz5r?t`MK(!`2gCer8kVM*4a|=mwQv+vu?zA7icrtedB ze}QuPkFJ;R$~$tFN=oMUO21C1-tqXMTZ6S+9t)6h?B$(m@v!x~eAY(qU#lf6v-3c2 z@UHLC@*VOk?(McX(0h5;|LbW#bg%vv{`IhJa`c=R`d4>z{ViC0Kem1U@~Z!i_ICi@7cZ_<`p;JZn||pP?^Qf zol7!f+v|2beD??*kxxcfku_OBnffwt7# zd7dEr=fG^;wKjLGH%Qh^E?d39vd@3T?(MIp%FV{#yDzx{&11CR{(-sueTfH1r?t+X z1y63rf`tF?JIL1nw1nr!%pdH*!gmeW_g!G#x^4E#(hYYXZa@0P>c+hf)@NSyGq0NZ z(eHcdkBSSit9#1-t2b{{cy#ud+(grZ5f+~}ZJfS>QJ#BE!W+wIA-iusbUEd#wWhc9 z+ph6GbbQ`J)rZOb-zI(7s9xv!V@LA_m6|(0BZTbU-O%BbFQ0WRc8yxiom%PgZ?irW zx)im literal 0 HcmV?d00001 diff --git a/res/icons/volatile.png b/res/icons/volatile.png new file mode 100644 index 0000000000000000000000000000000000000000..aef5dcd2a9282f3a651d517466aadfbe001f2407 GIT binary patch literal 1441 zcmeAS@N?(olHy`uVBq!ia0vp^+d-Iv8AzsYuwM(L7>k44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O@EW3h)VW1&VKcyKN++|5nGMfuRsq666=m@ZZXKnfjd5?h(QdPpSmuY1;3f za7m)={`~6|+pDgYE-AJ!{cadPOG*C1Eipk`zLv9{jVpgmJ@(b*#f-^%v3`FxbZ=s+ zf3nBtAm^@v4^gr_ub*Xpa$ZI#oYL)+?x;F|M`t-=RyJ?rra6jGJb=V^5*e;zq`}uD}254}aObd;N*HnekEA zrdu6hIDcZ_-<;PiYAp*qa z?w8#g*u64480R&gE%$mU#rNj|bLU)})F6H1DPO+* zp2bbK`QM*-UgyZRqqjDallKIJ`Np5FoIq}8uh1-I;XTT$tEbs~=-ZQbf?@l{_^HL0 zef!TYRLk$=DUhDMebyf19S_U4{r~Xo@ZTJp>0j7q`^(!+Si;PF=eXU{lh(Wp_G{yA z{CFN;d!^Li0B_Fi%$+;7Sod~MJ9oa#c4sv&+m8O;J9Al;_bgr}?L2q--hfu#x&3Fq zm)^MW^Umo{Uwd!He*G}dbo1(eb822E?0!&x;_`)q-wv)naWR_xHs}A?KA9Z-Jwms6 zr|)$<%Kf3+XnVltw&#=ja^Ef4cctd4^qKu%JhRtt&)ZjJdD5^X;(J@2e^m6HUF9jV zxxvT37jI+N_$T-3$D22QK0LR)!TP}R2H%eVs<$0`*&qCyo|FG-Pr-+Mzw5HnrZ>L+ z{5IfW>#X~>>1pCk{L5-S$8p(3x9gsL@ao)`8^&I%=Kp%Qw`pnIrvrcArH7R{{@7=7 z%T#-}N=@A2wb3_SAKGUuiPhe%6SFw-X6wVy8+AXtM7PP;#m_IfctVe_KA|GwP5SSL zzjk)_u>?t~b7}y`8sBKCeLk#*L{D(tY^5 zU)O)AI{Ib9j(_aOc3ZtVE|v2?+~a)io2~-c)THO#8`nnNoRoFQTqV=~owT7d-#gQu&X%Q~loCIGI$21)<` literal 0 HcmV?d00001