diff --git a/android/app/build.gradle b/android/app/build.gradle index 53aeec3..2fa991b 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -51,6 +51,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.dan63.tetra_stats" + testApplicationId "com.dan63.tetra_stats.dev_build" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. minSdkVersion flutter.minSdkVersion diff --git a/lib/data_objects/summaries.dart b/lib/data_objects/summaries.dart index 4c8f105..22e22ab 100644 --- a/lib/data_objects/summaries.dart +++ b/lib/data_objects/summaries.dart @@ -49,7 +49,7 @@ class Summaries { ]; league = TetraLeague.fromJson(json['league'], DateTime.now(), currentSeason, i); - if (json['league']['past'].isNotEmpty) + if (json['league']['past'] != null && json['league']['past'].isNotEmpty) for (var key in json['league']['past'].keys) { pastLeague[int.parse(key)] = TetraLeague.fromJson( json['league']['past'][key], diff --git a/lib/gen/strings.g.dart b/lib/gen/strings.g.dart index c34c1f6..273325a 100644 --- a/lib/gen/strings.g.dart +++ b/lib/gen/strings.g.dart @@ -6,7 +6,7 @@ /// Locales: 3 /// Strings: 1818 (606 per locale) /// -/// Built on 2024-09-30 at 21:23 UTC +/// Built on 2024-11-16 at 13:39 UTC // coverage:ignore-file // ignore_for_file: type=lint diff --git a/lib/main.dart b/lib/main.dart index c21e6f5..32d410e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -59,7 +59,17 @@ ThemeData theme = ThemeData( expandedAlignment: Alignment.bottomCenter, ), dropdownMenuTheme: DropdownMenuThemeData(textStyle: TextStyle(fontFamily: "Eurostile Round", fontSize: 18)), - scaffoldBackgroundColor: Colors.black + scaffoldBackgroundColor: Colors.black, + tooltipTheme: TooltipThemeData( + textStyle: TextStyle(color: Colors.white), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + border: Border.all( + color: Colors.white + ), + color: Colors.black, + ) + ) ); void main() async { diff --git a/lib/views/destination_home.dart b/lib/views/destination_home.dart index 5eff9da..9693644 100644 --- a/lib/views/destination_home.dart +++ b/lib/views/destination_home.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_layout_grid/flutter_layout_grid.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:intl/intl.dart'; @@ -52,6 +53,391 @@ class DestinationHome extends StatefulWidget{ State createState() => _DestinationHomeState(); } +Cards rightCard = Cards.overview; +CardMod cardMod = CardMod.info; +Map>> 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.ex, // yeah i misusing my own Enum shut the fuck up + label: Text('Seasons'), + ), + const ButtonSegment( + value: CardMod.records, + label: Text('Matches'), + ), + ], + Cards.quickPlay: [ + const ButtonSegment( + value: CardMod.info, + label: Text('Normal'), + ), + const ButtonSegment( + value: CardMod.records, + label: Text('Records'), + ), + const ButtonSegment( + value: CardMod.ex, + label: Text('Expert'), + ), + const ButtonSegment( + value: CardMod.exRecords, + label: Text('Ex Records'), + ) + ], + Cards.blitz: [ + const ButtonSegment( + value: CardMod.info, + label: Text('PB'), + ), + const ButtonSegment( + value: CardMod.records, + label: Text('Records'), + ) + ], + Cards.sprint: [ + const ButtonSegment( + value: CardMod.info, + label: Text('PB'), + ), + const ButtonSegment( + value: CardMod.records, + label: Text('Records'), + ) + ] +}; + +class ZenithCard extends StatelessWidget { + final RecordSingle? record; + final bool old; + final double width; + + const ZenithCard(this.record, this.old, {this.width = double.infinity}); + + Widget splitsCard(){ + return Card( + child: SizedBox( + width: 300, + height: 318, + child: 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 + )), + ) + ], + ), + SizedBox( + width: 300.0, + child: 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), + ] + ) + ], + ), + ), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + 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: Theme.of(context).textTheme.titleLarge), + //Text("Leaderboard reset in ${countdown(postSeasonLeft)}", textAlign: TextAlign.center), + ], + ), + ), + ), + ), + ZenithThingy(zenith: record, old: old, width: width), + if (record != null) width > 600 ? Row( + children: [ + Expanded( + child: Card( + child: Column( + children: [ + FinesseThingy(record!.stats.finesse, record!.stats.finessePercentage), + LineclearsThingy(record!.stats.clears, record!.stats.lines, record!.stats.holds, record!.stats.tSpins, showMoreClears: true) + ], + ), + ), + ), + Expanded( + child: splitsCard() + ), + ], + ) : Column( + children: [ + Card( + child: Center( + child: Column( + children: [ + FinesseThingy(record!.stats.finesse, record!.stats.finessePercentage), + LineclearsThingy(record!.stats.clears, record!.stats.lines, record!.stats.holds, record!.stats.tSpins, showMoreClears: true) + ], + ), + ), + ), + splitsCard(), + ], + ), + if (record != null) Card( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Spacer(), + Text(t.nerdStats, style: Theme.of(context).textTheme.titleLarge), + const Spacer() + ], + ), + ), + if (record != null) NerdStatsThingy(nerdStats: record!.aggregateStats.nerdStats, width: width), + if (record != null) Graphs(record!.aggregateStats.apm, record!.aggregateStats.pps, record!.aggregateStats.vs, record!.aggregateStats.nerdStats, record!.aggregateStats.playstyle) + ], + ); + } +} + +class RecordCard extends StatelessWidget { + final RecordSingle? record; + final List achievements; + final bool? betterThanRankAverage; + final MapEntry? closestAverage; + final bool? betterThanClosestAverage; + final String? rank; + + const RecordCard(this.record, this.achievements, this.betterThanRankAverage, this.closestAverage, this.betterThanClosestAverage, this.rank); + + @override + Widget build(BuildContext context) { + if (record == null) { + return const Card( + child: Center(child: Text("No record", style: TextStyle(fontSize: 42))), + ); + } + 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(switch(record!.gamemode){ + "40l" => t.sprint, + "blitz" => t.blitz, + "5mblast" => "5,000,000 Blast", + _ => record!.gamemode + }, style: Theme.of(context).textTheme.titleLarge) + ], + ), + ), + ), + ), + Card( + child: Column( + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (closestAverage != null) Padding(padding: const EdgeInsets.only(right: 8.0), + child: Image.asset("res/tetrio_tl_alpha_ranks/${closestAverage!.key}.png", height: 96) + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + RichText(text: TextSpan( + text: switch(record!.gamemode){ + "40l" => get40lTime(record!.stats.finalTime.inMicroseconds), + "blitz" => NumberFormat.decimalPattern().format(record!.stats.score), + "5mblast" => get40lTime(record!.stats.finalTime.inMicroseconds), + _ => record!.stats.score.toString() + }, + 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 (rank != null && rank != "z") TextSpan(text: "${t.verdictGeneral(n: switch(record!.gamemode){ + "40l" => readableTimeDifference(record!.stats.finalTime, sprintAverages[rank]!), + "blitz" => readableIntDifference(record!.stats.score, blitzAverages[rank]!), + _ => record!.stats.score.toString() + }, verdict: betterThanRankAverage??false ? t.verdictBetter : t.verdictWorse, rank: rank!.toUpperCase())}\n", style: TextStyle( + color: betterThanClosestAverage??false ? Colors.greenAccent : Colors.redAccent + )) + else if ((rank == null || rank == "z" || rank == "x+") && closestAverage != null) TextSpan(text: "${t.verdictGeneral(n: switch(record!.gamemode){ + "40l" => readableTimeDifference(record!.stats.finalTime, closestAverage!.value), + "blitz" => readableIntDifference(record!.stats.score, closestAverage!.value), + _ => record!.stats.score.toString() + }, verdict: betterThanClosestAverage??false ? t.verdictBetter : t.verdictWorse, rank: closestAverage!.key.toUpperCase())}\n", style: TextStyle( + color: betterThanClosestAverage??false ? Colors.greenAccent : Colors.redAccent + )), + 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(record!.timestamp)), + ] + ), + ), + ], + ), + ], + ), + Row( + children: [ + Expanded( + child: Table( + defaultColumnWidth:const IntrinsicColumnWidth(), + children: [ + TableRow(children: [ + Text(switch(record!.gamemode){ + "40l" => record!.stats.piecesPlaced.toString(), + "blitz" => record!.stats.level.toString(), + "5mblast" => NumberFormat.decimalPattern().format(record!.stats.spp), + _ => "What if " + }, textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), + Text(switch(record!.gamemode){ + "40l" => " Pieces", + "blitz" => " Level", + "5mblast" => " SPP", + _ => " i wanted to" + }, textAlign: TextAlign.left, style: const TextStyle(fontSize: 21)), + ]), + TableRow(children: [ + Text(f2.format(record!.stats.pps), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), + const Text(" PPS", textAlign: TextAlign.left, style: TextStyle(fontSize: 21)), + ]), + TableRow(children: [ + Text(switch(record!.gamemode){ + "40l" => f2.format(record!.stats.kpp), + "blitz" => f2.format(record!.stats.spp), + "5mblast" => record!.stats.piecesPlaced.toString(), + _ => "but god said" + }, textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), + Text(switch(record!.gamemode){ + "40l" => " KPP", + "blitz" => " SPP", + "5mblast" => " Pieces", + _ => " no" + }, textAlign: TextAlign.left, style: const TextStyle(fontSize: 21)), + ]) + ], + ), + ), + Expanded( + child: Table( + defaultColumnWidth:const IntrinsicColumnWidth(), + children: [ + TableRow(children: [ + Text(intf.format(record!.stats.inputs), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), + const Text(" Key presses", textAlign: TextAlign.left, style: TextStyle(fontSize: 21)), + ]), + TableRow(children: [ + Text(f2.format(record!.stats.kps), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), + const Text(" KPS", textAlign: TextAlign.left, style: TextStyle(fontSize: 21)), + ]), + TableRow(children: [ + Text(switch(record!.gamemode){ + "40l" => " ", + "blitz" => record!.stats.piecesPlaced.toString(), + "5mblast" => record!.stats.piecesPlaced.toString(), + _ => "but god said" + }, textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), + Text(switch(record!.gamemode){ + "40l" => " ", + "blitz" => " Pieces", + "5mblast" => " Pieces", + _ => " no" + }, textAlign: TextAlign.left, style: const TextStyle(fontSize: 21)), + ]) + ], + ), + ), + ], + ) + ], + ), + ), + Card( + child: Center( + child: Column( + children: [ + FinesseThingy(record!.stats.finesse, record!.stats.finessePercentage), + LineclearsThingy(record!.stats.clears, record!.stats.lines, record!.stats.holds, record!.stats.tSpins), + if (record!.gamemode == 'blitz') Text("${f2.format(record!.stats.kpp)} KPP") + ], + ), + ), + ), + Wrap( + direction: Axis.horizontal, + children: [ + for (Achievement achievement in achievements) FractionallySizedBox(widthFactor: 0.5, child: AchievementSummary(achievement: achievement)), + ], + ), + ] + ); + } +} + class FetchResults{ bool success; TetrioPlayer? player; @@ -155,7 +541,7 @@ class AchievementSummary extends StatelessWidget{ child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text(achievement?.name??"---", style: Theme.of(context).textTheme.titleSmall, textAlign: TextAlign.center), + Text(achievement?.name??"---", style: Theme.of(context).textTheme.titleSmall!.copyWith(color: achievement?.v == null ? Colors.grey : Colors.white), textAlign: TextAlign.center), const Divider(), Row( mainAxisSize: MainAxisSize.min, @@ -175,7 +561,7 @@ class AchievementSummary extends StatelessWidget{ //alignment: Alignment.topLeft.add(Alignment(0.285 * 1, 0)), heightFactor: 0.125, widthFactor: 0.125, - child: Image.asset("res/icons/achievements.png", width: 2048, height: 2048, scale: 1), + child: Image.asset("res/icons/achievements.png", width: 2048, height: 2048, scale: 1, color: achievement?.v == null ? Colors.grey : Colors.white), ), ), ), @@ -198,7 +584,7 @@ class AchievementSummary extends StatelessWidget{ 6 => intf.format(achievement!.v!.abs()), _ => "lol" }, - style: TextStyle(fontFamily: "Eurostile Round", fontSize: 36, fontWeight: FontWeight.w500, color: Colors.white, height: 0.9), + style: TextStyle(fontFamily: "Eurostile Round", fontSize: 36, fontWeight: FontWeight.w500, color: achievement?.v == null ? Colors.grey : Colors.white, height: 0.9), ), ), if (achievement != null) RichText( @@ -206,7 +592,7 @@ class AchievementSummary extends StatelessWidget{ text: TextSpan( style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey), children: [ - TextSpan(text: "${achievement!.object}\n"), + if (achievement!.object.isNotEmpty) TextSpan(text: "${achievement!.object}\n"), if (achievement!.vt == 4) TextSpan(text: "Floor ${achievement?.a != null ? achievement!.a! : "-"}"), if (achievement!.vt == 4) TextSpan(text: " • "), if (achievement!.vt != 5) TextSpan(text: (achievement?.pos != null && !achievement!.pos!.isNegative) ? "№ ${intf.format(achievement!.pos!+1)}" : "№ ---", style: TextStyle(color: achievement?.pos != null ? getColorOfRank(achievement!.pos!+1) : Colors.grey)), @@ -234,8 +620,9 @@ class LeagueCard extends StatelessWidget{ final TetraLeague league; final CutoffTetrio? averages; final bool showSeasonNumber; + final double width; - const LeagueCard({super.key, required this.league, this.averages, this.showSeasonNumber = false}); + const LeagueCard({super.key, required this.league, this.averages, this.showSeasonNumber = false, this.width = double.infinity}); @override Widget build(BuildContext context) { @@ -246,7 +633,7 @@ class LeagueCard extends StatelessWidget{ child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - if (showSeasonNumber) Row( + if (showSeasonNumber) width > 600.0 ? Row( crossAxisAlignment: CrossAxisAlignment.baseline, textBaseline: TextBaseline.alphabetic, children: [ @@ -257,12 +644,23 @@ class LeagueCard extends StatelessWidget{ textAlign: TextAlign.center, style: TextStyle(color: Colors.grey)), ], + ) : Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text("Season ${league.season}", style: Theme.of(context).textTheme.titleSmall), + Text( + "${seasonStarts.elementAtOrNull(league.season - 1) != null ? timestamp(seasonStarts[league.season - 1]) : "---"} — ${seasonEnds.elementAtOrNull(league.season - 1) != null ? timestamp(seasonEnds[league.season - 1]) : "---"}", + textAlign: TextAlign.center, + style: TextStyle(color: Colors.grey)), + ], ) else Text("Tetra League", style: Theme.of(context).textTheme.titleSmall), const Divider(), TLRatingThingy(userID: league.id, tlData: league, showPositions: true), const Divider(), - RichText(text: TextSpan( + RichText( + textAlign: TextAlign.center, + text: TextSpan( style: const TextStyle(fontFamily: "Eurostile Round", color: Colors.grey), children: [ TextSpan(text: "${league.apm != null ? f2.format(league.apm) : "-.--"} APM", style: TextStyle(color: league.apm != null ? getStatColor(league.apm!, averages?.apm, true) : null)), @@ -286,10 +684,7 @@ class LeagueCard extends StatelessWidget{ } class _DestinationHomeState extends State with SingleTickerProviderStateMixin { - Cards rightCard = Cards.overview; - CardMod cardMod = CardMod.info; //Duration postSeasonLeft = seasonStart.difference(DateTime.now()); - late Map>> modeButtons; late MapEntry? closestAverageBlitz; late bool blitzBetterThanClosestAverage; late MapEntry? closestAverageSprint; @@ -299,199 +694,197 @@ class _DestinationHomeState extends State with SingleTickerProv bool? sprintBetterThanRankAverage; bool? blitzBetterThanRankAverage; - Widget getOverviewCard(Summaries summaries, CutoffTetrio? averages){ - return Column( - children: [ - const Card( - child: Padding( - padding: EdgeInsets.only(bottom: 4.0), - child: Center( + Widget getOverviewCard(Summaries summaries, CutoffTetrio? averages, double width){ + return LayoutGrid( + // ASCII-art named areas 🔥 + areas: width > 600 ? ''' + h h + t t + 1 2 + 3 4 + 5 6 + 7 7 + ''' : ''' + t + 1 + 2 + 3 + 4 + 5 + 6 + 7 + ''', + // Concise track sizing extension methods 🔥 + columnSizes: width > 600 ? [auto, auto] : [auto], + rowSizes: width > 600 ? [auto, auto, auto, auto, auto, auto] : [auto, auto, auto, auto, auto, auto, auto, auto], + // Column and row gaps! 🔥 + columnGap: 0, + rowGap: 0, + // Handy grid placement extension methods on Widget 🔥 + children: [ + if (width > 600) 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)), + ], + ), + ), + ), + ).inGridArea('h'), + LeagueCard(league: summaries.league, averages: averages).inGridArea('t'), + Card( + child: Padding( + padding: const EdgeInsets.fromLTRB(20.0, 8.0, 20.0, 12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text("40 Lines", style: Theme.of(context).textTheme.titleSmall), + const Divider(), + RecordSummary(record: summaries.sprint, betterThanClosestAverage: sprintBetterThanClosestAverage, betterThanRankAverage: sprintBetterThanRankAverage, closestAverage: closestAverageSprint, rank: summaries.league.percentileRank), + const Divider(), + Text("${summaries.sprint != null ? intf.format(summaries.sprint!.stats.piecesPlaced) : "---"} P • ${summaries.sprint != null ? f2.format(summaries.sprint!.stats.pps) : "-.--"} PPS • ${summaries.sprint != null ? f2.format(summaries.sprint!.stats.kpp) : "-.--"} KPP", style: const TextStyle(color: Colors.grey), textAlign: TextAlign.center) + ], + ), + ), + ).inGridArea('1'), + Card( + child: Padding( + padding: const EdgeInsets.fromLTRB(20.0, 8.0, 20.0, 12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text("Blitz", style: Theme.of(context).textTheme.titleSmall), + const Divider(), + RecordSummary(record: summaries.blitz, betterThanClosestAverage: blitzBetterThanClosestAverage, betterThanRankAverage: blitzBetterThanRankAverage, closestAverage: closestAverageBlitz, rank: summaries.league.percentileRank), + const Divider(), + Text("Level ${summaries.blitz != null ? intf.format(summaries.blitz!.stats.level): "--"} • ${summaries.blitz != null ? f2.format(summaries.blitz!.stats.spp) : "-.--"} SPP • ${summaries.blitz != null ? f2.format(summaries.blitz!.stats.pps) : "-.--"} PPS", style: const TextStyle(color: Colors.grey)) + ], + ), + ), + ).inGridArea('2'), + Card( + child: Padding( + padding: const EdgeInsets.fromLTRB(20.0, 8.0, 20.0, 14.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text("QP", style: Theme.of(context).textTheme.titleSmall), + const Divider(), + RecordSummary(record: summaries.zenith != null ? summaries.zenith : summaries.zenithCareerBest, hideRank: true, old: summaries.zenith == null), + const Divider(), + Text("Overall PB: ${(summaries.achievements.isNotEmpty && summaries.achievements.firstWhere((e) => e.k == 18).v != null) ? f2.format(summaries.achievements.firstWhere((e) => e.k == 18).v!) : "-.--"} m", style: const TextStyle(color: Colors.grey)) + ], + ), + ), + ).inGridArea('3'), + Card( + child: Padding( + padding: const EdgeInsets.fromLTRB(20.0, 8.0, 20.0, 14.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text("QP Expert", style: Theme.of(context).textTheme.titleSmall), + const Divider(), + RecordSummary(record: summaries.zenithEx != null ? summaries.zenithEx : summaries.zenithExCareerBest, hideRank: true, old: summaries.zenith == null), + const Divider(), + Text("Overall PB: ${(summaries.achievements.isNotEmpty && summaries.achievements.firstWhere((e) => e.k == 19).v != null) ? f2.format(summaries.achievements.firstWhere((e) => e.k == 19).v!) : "-.--"} m", style: const TextStyle(color: Colors.grey)) + ], + ), + ), + ).inGridArea('4'), + Card( + child: Padding( + padding: const EdgeInsets.fromLTRB(20.0, 8.0, 20.0, 14.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Center(child: Text("Zen", style: Theme.of(context).textTheme.titleSmall)), + Text("Level ${intf.format(summaries.zen.level)}", style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 36, fontWeight: FontWeight.w500, color: Colors.white)), + Text("Score ${intf.format(summaries.zen.score)}"), + Text("Level up requirement: ${intf.format(summaries.zen.scoreRequirement)}", style: const TextStyle(color: Colors.grey)) + ], + ), + ), + ).inGridArea('5'), + Card( + child: Padding( + padding: const EdgeInsets.fromLTRB(20.0, 0.0, 20.0, 20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Stack( + alignment: AlignmentDirectional.bottomStart, + children: [ + const Text("f", style: TextStyle( + fontStyle: FontStyle.italic, + fontSize: 65, + height: 1.2, + )), + const Positioned(left: 25, top: 20, child: Text("inesse", style: TextStyle(fontFamily: "Eurostile Round Extended"))), + Padding( + padding: const EdgeInsets.only(left: 10.0), + child: Text("${(summaries.achievements.isNotEmpty && summaries.achievements.firstWhere((e) => e.k == 4).v != null && summaries.achievements.firstWhere((e) => e.k == 1).v != null) ? + f3.format(summaries.achievements.firstWhere((e) => e.k == 4).v!/summaries.achievements.firstWhere((e) => e.k == 1).v! * 100) : "--.---"}%", style: const TextStyle( + //shadows: textShadow, + fontFamily: "Eurostile Round Extended", + fontSize: 36, + fontWeight: FontWeight.w500, + color: Colors.white + )), + ) + ], + ), + Row( + children: [ + const Text("Total pieces placed:"), + const Spacer(), + Text((summaries.achievements.isNotEmpty && summaries.achievements.firstWhere((e) => e.k == 1).v != null) ? intf.format(summaries.achievements.firstWhere((e) => e.k == 1).v!) : "---"), + ], + ), + Row( + children: [ + const Text(" - Placed with perfect finesse:"), + const Spacer(), + Text((summaries.achievements.isNotEmpty && summaries.achievements.firstWhere((e) => e.k == 4).v != null) ? intf.format(summaries.achievements.firstWhere((e) => e.k == 4).v!) : "---"), + ], + ) + ], + ), + ), + ).inGridArea('6'), + Card( + child: Padding( + padding: const EdgeInsets.fromLTRB(20.0, 0.0, 20.0, 0.0), child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text("Overview", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42)), + if (summaries.achievements.firstWhere((e) => e.k == 16).v != null) Row( + children: [ + const Text("Total height climbed in QP"), + const Spacer(), + Text("${f2.format(summaries.achievements.firstWhere((e) => e.k == 16).v!)} m"), + ], + ), + if (summaries.achievements.firstWhere((e) => e.k == 17).v != null) Row( + children: [ + const Text("KO's in QP"), + const Spacer(), + Text(intf.format(summaries.achievements.firstWhere((e) => e.k == 17).v!)), + ], + ) ], ), ), - ), - ), - LeagueCard(league: summaries.league, averages: averages), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Card( - child: Padding( - padding: const EdgeInsets.fromLTRB(20.0, 8.0, 20.0, 12.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text("40 Lines", style: Theme.of(context).textTheme.titleSmall), - const Divider(), - RecordSummary(record: summaries.sprint, betterThanClosestAverage: sprintBetterThanClosestAverage, betterThanRankAverage: sprintBetterThanRankAverage, closestAverage: closestAverageSprint, rank: summaries.league.percentileRank), - const Divider(), - Text("${summaries.sprint != null ? intf.format(summaries.sprint!.stats.piecesPlaced) : "---"} P • ${summaries.sprint != null ? f2.format(summaries.sprint!.stats.pps) : "-.--"} PPS • ${summaries.sprint != null ? f2.format(summaries.sprint!.stats.kpp) : "-.--"} KPP", style: const TextStyle(color: Colors.grey)) - ], - ), - ), - ), - ), - Expanded( - child: Card( - child: Padding( - padding: const EdgeInsets.fromLTRB(20.0, 8.0, 20.0, 12.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text("Blitz", style: Theme.of(context).textTheme.titleSmall), - const Divider(), - RecordSummary(record: summaries.blitz, betterThanClosestAverage: blitzBetterThanClosestAverage, betterThanRankAverage: blitzBetterThanRankAverage, closestAverage: closestAverageBlitz, rank: summaries.league.percentileRank), - const Divider(), - Text("Level ${summaries.blitz != null ? intf.format(summaries.blitz!.stats.level): "--"} • ${summaries.blitz != null ? f2.format(summaries.blitz!.stats.spp) : "-.--"} SPP • ${summaries.blitz != null ? f2.format(summaries.blitz!.stats.pps) : "-.--"} PPS", style: const TextStyle(color: Colors.grey)) - ], - ), - ), - ), - ), - ], - ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Card( - child: Padding( - padding: const EdgeInsets.fromLTRB(20.0, 8.0, 20.0, 14.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text("QP", style: Theme.of(context).textTheme.titleSmall), - const Divider(), - RecordSummary(record: summaries.zenith != null ? summaries.zenith : summaries.zenithCareerBest, hideRank: true, old: summaries.zenith == null), - const Divider(), - Text("Overall PB: ${(summaries.achievements.isNotEmpty && summaries.achievements.firstWhere((e) => e.k == 18).v != null) ? f2.format(summaries.achievements.firstWhere((e) => e.k == 18).v!) : "-.--"} m", style: const TextStyle(color: Colors.grey)) - ], - ), - ), - ), - ), - Expanded( - child: Card( - child: Padding( - padding: const EdgeInsets.fromLTRB(20.0, 8.0, 20.0, 14.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text("QP Expert", style: Theme.of(context).textTheme.titleSmall), - const Divider(), - RecordSummary(record: summaries.zenithEx != null ? summaries.zenithEx : summaries.zenithExCareerBest, hideRank: true, old: summaries.zenith == null), - const Divider(), - Text("Overall PB: ${(summaries.achievements.isNotEmpty && summaries.achievements.firstWhere((e) => e.k == 19).v != null) ? f2.format(summaries.achievements.firstWhere((e) => e.k == 19).v!) : "-.--"} m", style: const TextStyle(color: Colors.grey)) - ], - ), - ), - ), - ), - ], - ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Card( - child: Padding( - padding: const EdgeInsets.fromLTRB(20.0, 8.0, 20.0, 14.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text("Zen", style: Theme.of(context).textTheme.titleSmall), - Text("Level ${intf.format(summaries.zen.level)}", style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 36, fontWeight: FontWeight.w500, color: Colors.white)), - Text("Score ${intf.format(summaries.zen.score)}"), - Text("Level up requirement: ${intf.format(summaries.zen.scoreRequirement)}", style: const TextStyle(color: Colors.grey)) - ], - ), - ), - ), - ), - Expanded( - child: Card( - child: Padding( - padding: const EdgeInsets.fromLTRB(20.0, 0.0, 20.0, 20.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Stack( - alignment: AlignmentDirectional.bottomStart, - children: [ - const Text("f", style: TextStyle( - fontStyle: FontStyle.italic, - fontSize: 65, - height: 1.2, - )), - const Positioned(left: 25, top: 20, child: Text("inesse", style: TextStyle(fontFamily: "Eurostile Round Extended"))), - Padding( - padding: const EdgeInsets.only(left: 10.0), - child: Text("${(summaries.achievements.isNotEmpty && summaries.achievements.firstWhere((e) => e.k == 4).v != null && summaries.achievements.firstWhere((e) => e.k == 1).v != null) ? - f3.format(summaries.achievements.firstWhere((e) => e.k == 4).v!/summaries.achievements.firstWhere((e) => e.k == 1).v! * 100) : "--.---"}%", style: const TextStyle( - //shadows: textShadow, - fontFamily: "Eurostile Round Extended", - fontSize: 36, - fontWeight: FontWeight.w500, - color: Colors.white - )), - ) - ], - ), - Row( - children: [ - const Text("Total pieces placed:"), - const Spacer(), - Text((summaries.achievements.isNotEmpty && summaries.achievements.firstWhere((e) => e.k == 1).v != null) ? intf.format(summaries.achievements.firstWhere((e) => e.k == 1).v!) : "---"), - ], - ), - Row( - children: [ - const Text(" - Placed with perfect finesse:"), - const Spacer(), - Text((summaries.achievements.isNotEmpty && summaries.achievements.firstWhere((e) => e.k == 4).v != null) ? intf.format(summaries.achievements.firstWhere((e) => e.k == 4).v!) : "---"), - ], - ) - ], - ), - ), - ), - ), - ], - ), - if (summaries.achievements.isNotEmpty) Card( - child: Padding( - padding: const EdgeInsets.fromLTRB(20.0, 0.0, 20.0, 0.0), - child: Column( - children: [ - if (summaries.achievements.firstWhere((e) => e.k == 16).v != null) Row( - children: [ - const Text("Total height climbed in QP"), - const Spacer(), - Text("${f2.format(summaries.achievements.firstWhere((e) => e.k == 16).v!)} m"), - ], - ), - if (summaries.achievements.firstWhere((e) => e.k == 17).v != null) Row( - children: [ - const Text("KO's in QP"), - const Spacer(), - Text(intf.format(summaries.achievements.firstWhere((e) => e.k == 17).v!)), - ], - ) - ], - ), - ), - ), - ] - ); + ).inGridArea('7') + ], + ); } - Widget getTetraLeagueCard(TetraLeague data, Cutoffs? cutoffs, CutoffTetrio? averages, List states, PlayerLeaderboardPosition? lbPos){ + Widget getTetraLeagueCard(TetraLeague data, Cutoffs? cutoffs, CutoffTetrio? averages, List states, PlayerLeaderboardPosition? lbPos, double width){ TetraLeague? toCompare = states.length >= 2 ? states.elementAtOrNull(states.length-2) : null; return Column( children: [ @@ -511,7 +904,7 @@ class _DestinationHomeState extends State with SingleTickerProv ), ), ), - TetraLeagueThingy(league: data, toCompare: toCompare, cutoffs: cutoffs, averages: averages, lbPos: lbPos), + TetraLeagueThingy(league: data, toCompare: toCompare, cutoffs: cutoffs, averages: averages, lbPos: lbPos, width: width), if (data.nerdStats != null) Card( //surfaceTintColor: rankColors[data.rank], child: Row( @@ -523,13 +916,13 @@ class _DestinationHomeState extends State with SingleTickerProv ], ), ), - if (data.nerdStats != null) NerdStatsThingy(nerdStats: data.nerdStats!, oldNerdStats: toCompare?.nerdStats, averages: averages, lbPos: lbPos), + if (data.nerdStats != null) NerdStatsThingy(nerdStats: data.nerdStats!, oldNerdStats: toCompare?.nerdStats, averages: averages, lbPos: lbPos, width: width), if (data.nerdStats != null) Graphs(data.apm!, data.pps!, data.vs!, data.nerdStats!, data.playstyle!) ], ); } - Widget getPreviousSeasonsList(Map pastLeague){ + Widget getPreviousSeasonsList(Map pastLeague, double width){ return Column( children: [ Card( @@ -540,7 +933,7 @@ class _DestinationHomeState extends State with SingleTickerProv mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text("Previous Seasons", style: Theme.of(context).textTheme.titleLarge), + Text("Previous Seasons", style: Theme.of(context).textTheme.titleLarge, textAlign: TextAlign.center), //Text("${t.seasonStarts} ${countdown(postSeasonLeft)}", textAlign: TextAlign.center) ], ), @@ -548,7 +941,7 @@ class _DestinationHomeState extends State with SingleTickerProv ), ), for (var key in pastLeague.keys) Card( - child: LeagueCard(league: pastLeague[key]!, showSeasonNumber: true), + child: LeagueCard(league: pastLeague[key]!, showSeasonNumber: true, width: width), ) ], ); @@ -710,353 +1103,8 @@ class _DestinationHomeState extends State with SingleTickerProv ); } - Widget getZenithCard(RecordSingle? record, bool old){ - 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: Theme.of(context).textTheme.titleLarge), - //Text("Leaderboard reset in ${countdown(postSeasonLeft)}", textAlign: TextAlign.center), - ], - ), - ), - ), - ), - ZenithThingy(zenith: record, old: old), - if (record != null) Row( - children: [ - Expanded( - child: Card( - child: Column( - children: [ - FinesseThingy(record.stats.finesse, record.stats.finessePercentage), - LineclearsThingy(record.stats.clears, record.stats.lines, record.stats.holds, record.stats.tSpins, showMoreClears: true), - if (record.gamemode == 'blitz') Text("${f2.format(record.stats.kpp)} KPP") - ], - ), - ), - ), - Expanded( - child: Card( - child: SizedBox( - width: 300, - height: 318, - child: 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 - )), - ) - ], - ), - SizedBox( - width: 300.0, - child: 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 (record != null) Card( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Spacer(), - Text(t.nerdStats, style: Theme.of(context).textTheme.titleLarge), - const Spacer() - ], - ), - ), - if (record != null) NerdStatsThingy(nerdStats: record.aggregateStats.nerdStats), - if (record != null) Graphs(record.aggregateStats.apm, record.aggregateStats.pps, record.aggregateStats.vs, record.aggregateStats.nerdStats, record.aggregateStats.playstyle) - ], - ); - } - - Widget getRecordCard(RecordSingle? record, List achievements, bool? betterThanRankAverage, MapEntry? closestAverage, bool? betterThanClosestAverage, String? rank){ - if (record == null) { - return const Card( - child: Center(child: Text("No record", style: TextStyle(fontSize: 42))), - ); - } - 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(switch(record.gamemode){ - "40l" => t.sprint, - "blitz" => t.blitz, - "5mblast" => "5,000,000 Blast", - _ => record.gamemode - }, style: Theme.of(context).textTheme.titleLarge) - ], - ), - ), - ), - ), - Card( - child: Column( - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (closestAverage != null) Padding(padding: const EdgeInsets.only(right: 8.0), - child: Image.asset("res/tetrio_tl_alpha_ranks/${closestAverage.key}.png", height: 96) - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - RichText(text: TextSpan( - text: switch(record.gamemode){ - "40l" => get40lTime(record.stats.finalTime.inMicroseconds), - "blitz" => NumberFormat.decimalPattern().format(record.stats.score), - "5mblast" => get40lTime(record.stats.finalTime.inMicroseconds), - _ => record.stats.score.toString() - }, - 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 (rank != null && rank != "z") TextSpan(text: "${t.verdictGeneral(n: switch(record.gamemode){ - "40l" => readableTimeDifference(record.stats.finalTime, sprintAverages[rank]!), - "blitz" => readableIntDifference(record.stats.score, blitzAverages[rank]!), - _ => record.stats.score.toString() - }, verdict: betterThanRankAverage??false ? t.verdictBetter : t.verdictWorse, rank: rank.toUpperCase())}\n", style: TextStyle( - color: betterThanClosestAverage??false ? Colors.greenAccent : Colors.redAccent - )) - else if ((rank == null || rank == "z" || rank == "x+") && closestAverage != null) TextSpan(text: "${t.verdictGeneral(n: switch(record.gamemode){ - "40l" => readableTimeDifference(record.stats.finalTime, closestAverage.value), - "blitz" => readableIntDifference(record.stats.score, closestAverage.value), - _ => record.stats.score.toString() - }, verdict: betterThanClosestAverage??false ? t.verdictBetter : t.verdictWorse, rank: closestAverage.key.toUpperCase())}\n", style: TextStyle( - color: betterThanClosestAverage??false ? Colors.greenAccent : Colors.redAccent - )), - 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(record.timestamp)), - ] - ), - ), - ], - ), - ], - ), - Row( - children: [ - Expanded( - child: Table( - defaultColumnWidth:const IntrinsicColumnWidth(), - children: [ - TableRow(children: [ - Text(switch(record.gamemode){ - "40l" => record.stats.piecesPlaced.toString(), - "blitz" => record.stats.level.toString(), - "5mblast" => NumberFormat.decimalPattern().format(record.stats.spp), - _ => "What if " - }, textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), - Text(switch(record.gamemode){ - "40l" => " Pieces", - "blitz" => " Level", - "5mblast" => " SPP", - _ => " i wanted to" - }, textAlign: TextAlign.left, style: const TextStyle(fontSize: 21)), - ]), - TableRow(children: [ - Text(f2.format(record.stats.pps), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), - const Text(" PPS", textAlign: TextAlign.left, style: TextStyle(fontSize: 21)), - ]), - TableRow(children: [ - Text(switch(record.gamemode){ - "40l" => f2.format(record.stats.kpp), - "blitz" => f2.format(record.stats.spp), - "5mblast" => record.stats.piecesPlaced.toString(), - _ => "but god said" - }, textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), - Text(switch(record.gamemode){ - "40l" => " KPP", - "blitz" => " SPP", - "5mblast" => " Pieces", - _ => " no" - }, textAlign: TextAlign.left, style: const TextStyle(fontSize: 21)), - ]) - ], - ), - ), - Expanded( - child: Table( - defaultColumnWidth:const IntrinsicColumnWidth(), - children: [ - TableRow(children: [ - Text(intf.format(record.stats.inputs), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), - const Text(" Key presses", textAlign: TextAlign.left, style: TextStyle(fontSize: 21)), - ]), - TableRow(children: [ - Text(f2.format(record.stats.kps), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), - const Text(" KPS", textAlign: TextAlign.left, style: TextStyle(fontSize: 21)), - ]), - TableRow(children: [ - Text(switch(record.gamemode){ - "40l" => " ", - "blitz" => record.stats.piecesPlaced.toString(), - "5mblast" => record.stats.piecesPlaced.toString(), - _ => "but god said" - }, textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), - Text(switch(record.gamemode){ - "40l" => " ", - "blitz" => " Pieces", - "5mblast" => " Pieces", - _ => " no" - }, textAlign: TextAlign.left, style: const TextStyle(fontSize: 21)), - ]) - ], - ), - ), - ], - ) - ], - ), - ), - Card( - child: Center( - child: Column( - children: [ - FinesseThingy(record.stats.finesse, record.stats.finessePercentage), - LineclearsThingy(record.stats.clears, record.stats.lines, record.stats.holds, record.stats.tSpins), - if (record.gamemode == 'blitz') Text("${f2.format(record.stats.kpp)} KPP") - ], - ), - ), - ), - Wrap( - direction: Axis.horizontal, - children: [ - for (Achievement achievement in achievements) FractionallySizedBox(widthFactor: 0.5, child: AchievementSummary(achievement: achievement)), - ], - ), - ] - ); - } - @override initState(){ - 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.ex, // yeah i misusing my own Enum shut the fuck up - label: Text('Previous Seasons'), - ), - const ButtonSegment( - value: CardMod.records, - label: Text('Recent Matches'), - ), - ], - Cards.quickPlay: [ - const ButtonSegment( - value: CardMod.info, - label: Text('Normal'), - ), - const ButtonSegment( - value: CardMod.records, - label: Text('Records'), - ), - const ButtonSegment( - value: CardMod.ex, - label: Text('Expert'), - ), - const ButtonSegment( - value: CardMod.exRecords, - label: Text('Expert Records'), - ) - ], - Cards.blitz: [ - const ButtonSegment( - value: CardMod.info, - label: Text('PB'), - ), - const ButtonSegment( - value: CardMod.records, - label: Text('Records'), - ) - ], - Cards.sprint: [ - const ButtonSegment( - value: CardMod.info, - label: Text('PB'), - ), - const ButtonSegment( - value: CardMod.records, - label: Text('Records'), - ) - ] - }; - _transition = AnimationController(vsync: this, duration: Durations.long4); _offsetAnimation = Tween( @@ -1070,8 +1118,38 @@ class _DestinationHomeState extends State with SingleTickerProv super.initState(); } + Widget rigthCard(AsyncSnapshot snapshot, List sprintAchievements, List blitzAchievements, double width){ + return switch (rightCard){ + Cards.overview => getOverviewCard(snapshot.data!.summaries!, (snapshot.data!.averages != null && snapshot.data!.summaries!.league.rank != "z") ? snapshot.data!.averages!.data[snapshot.data!.summaries!.league.rank] : (snapshot.data!.averages != null && snapshot.data!.summaries!.league.percentileRank != "z") ? snapshot.data!.averages!.data[snapshot.data!.summaries!.league.percentileRank] : null, width), + Cards.tetraLeague => switch (cardMod){ + CardMod.info => getTetraLeagueCard(snapshot.data!.summaries!.league, snapshot.data!.cutoffs, (snapshot.data!.averages != null && snapshot.data!.summaries!.league.rank != "z") ? snapshot.data!.averages!.data[snapshot.data!.summaries!.league.rank] : (snapshot.data!.averages != null && snapshot.data!.summaries!.league.percentileRank != "z") ? snapshot.data!.averages!.data[snapshot.data!.summaries!.league.percentileRank] : null, snapshot.data!.states, snapshot.data!.playerPos, width), + CardMod.ex => getPreviousSeasonsList(snapshot.data!.summaries!.pastLeague, width), + CardMod.records => getRecentTLrecords(widget.constraints, snapshot.data!.player!.userId), + _ => const Center(child: Text("huh?")) + }, + Cards.quickPlay => switch (cardMod){ + CardMod.info => ZenithCard(snapshot.data?.summaries?.zenith != null ? snapshot.data!.summaries!.zenith : snapshot.data!.summaries?.zenithCareerBest, snapshot.data!.summaries?.zenith == null, width: width), + CardMod.records => getListOfRecords("zenith/recent", "zenith/top", widget.constraints), + CardMod.ex => ZenithCard(snapshot.data?.summaries?.zenithEx != null ? snapshot.data!.summaries!.zenithEx : snapshot.data!.summaries?.zenithExCareerBest, snapshot.data!.summaries?.zenithEx == null, width: width), + CardMod.exRecords => getListOfRecords("zenithex/recent", "zenithex/top", widget.constraints), + }, + Cards.sprint => switch (cardMod){ + CardMod.info => RecordCard(snapshot.data?.summaries!.sprint, sprintAchievements, sprintBetterThanRankAverage, closestAverageSprint, sprintBetterThanClosestAverage, snapshot.data!.summaries!.league.rank), + CardMod.records => getListOfRecords("40l/recent", "40l/top", widget.constraints), + _ => const Center(child: Text("huh?")) + }, + Cards.blitz => switch (cardMod){ + CardMod.info => RecordCard(snapshot.data?.summaries!.blitz, blitzAchievements, blitzBetterThanRankAverage, closestAverageBlitz, blitzBetterThanClosestAverage, snapshot.data!.summaries!.league.rank), + CardMod.records => getListOfRecords("blitz/recent", "blitz/top", widget.constraints), + _ => const Center(child: Text("huh?")) + }, + }; + } + @override Widget build(BuildContext context) { + double width = widget.noSidebar ? widget.constraints.maxWidth : widget.constraints.maxWidth - 80; + bool screenIsBig = width >= 768; return FutureBuilder( future: widget.dataFuture, builder: (context, snapshot) { @@ -1100,7 +1178,7 @@ class _DestinationHomeState extends State with SingleTickerProv closestAverageBlitz = blitzAverages.entries.last; blitzBetterThanClosestAverage = false; } - List sprintAchievements = [ + List sprintAchievements = snapshot.data!.summaries!.achievements.isNotEmpty ? [ snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 5), snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 7), snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 8), @@ -1109,12 +1187,12 @@ class _DestinationHomeState extends State with SingleTickerProv snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 37), snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 38), snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 48), - ]; - List blitzAchievements = [ + ] : []; + List blitzAchievements = snapshot.data!.summaries!.achievements.isNotEmpty ? [ snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 6), snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 39), snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 52), - ]; + ] : []; return TweenAnimationBuilder( duration: Durations.long4, tween: Tween(begin: 0, end: 1), @@ -1125,7 +1203,7 @@ class _DestinationHomeState extends State with SingleTickerProv child: Opacity(opacity: value, child: child), ); }, - child: Row( + child: screenIsBig ? Row( children: [ SizedBox( width: 450, @@ -1161,42 +1239,18 @@ class _DestinationHomeState extends State with SingleTickerProv ), ), SizedBox( - width: widget.noSidebar ? widget.constraints.maxWidth - 450 : widget.constraints.maxWidth - 530, + width: width - 450, child: Column( children: [ SizedBox( height: rightCard != Cards.overview ? widget.constraints.maxHeight - 64 : widget.constraints.maxHeight - 32, - child: SingleChildScrollView( - child: SlideTransition( - position: _offsetAnimation, - child: switch (rightCard){ - Cards.overview => getOverviewCard(snapshot.data!.summaries!, (snapshot.data!.averages != null && snapshot.data!.summaries!.league.rank != "z") ? snapshot.data!.averages!.data[snapshot.data!.summaries!.league.rank] : (snapshot.data!.averages != null && snapshot.data!.summaries!.league.percentileRank != "z") ? snapshot.data!.averages!.data[snapshot.data!.summaries!.league.percentileRank] : null), - Cards.tetraLeague => switch (cardMod){ - CardMod.info => getTetraLeagueCard(snapshot.data!.summaries!.league, snapshot.data!.cutoffs, (snapshot.data!.averages != null && snapshot.data!.summaries!.league.rank != "z") ? snapshot.data!.averages!.data[snapshot.data!.summaries!.league.rank] : (snapshot.data!.averages != null && snapshot.data!.summaries!.league.percentileRank != "z") ? snapshot.data!.averages!.data[snapshot.data!.summaries!.league.percentileRank] : null, snapshot.data!.states, snapshot.data!.playerPos), - CardMod.ex => getPreviousSeasonsList(snapshot.data!.summaries!.pastLeague), - CardMod.records => getRecentTLrecords(widget.constraints, snapshot.data!.player!.userId), - _ => const Center(child: Text("huh?")) - }, - Cards.quickPlay => switch (cardMod){ - CardMod.info => getZenithCard(snapshot.data?.summaries?.zenith != null ? snapshot.data!.summaries!.zenith : snapshot.data!.summaries?.zenithCareerBest, snapshot.data!.summaries?.zenith == null), - CardMod.records => getListOfRecords("zenith/recent", "zenith/top", widget.constraints), - CardMod.ex => getZenithCard(snapshot.data?.summaries?.zenithEx != null ? snapshot.data!.summaries!.zenithEx : snapshot.data!.summaries?.zenithExCareerBest, snapshot.data!.summaries?.zenithEx == null), - CardMod.exRecords => getListOfRecords("zenithex/recent", "zenithex/top", widget.constraints), - }, - Cards.sprint => switch (cardMod){ - CardMod.info => getRecordCard(snapshot.data?.summaries!.sprint, sprintAchievements, sprintBetterThanRankAverage, closestAverageSprint, sprintBetterThanClosestAverage, snapshot.data!.summaries!.league.rank), - CardMod.records => getListOfRecords("40l/recent", "40l/top", widget.constraints), - _ => const Center(child: Text("huh?")) - }, - Cards.blitz => switch (cardMod){ - CardMod.info => getRecordCard(snapshot.data?.summaries!.blitz, blitzAchievements, blitzBetterThanRankAverage, closestAverageBlitz, blitzBetterThanClosestAverage, snapshot.data!.summaries!.league.rank), - CardMod.records => getListOfRecords("blitz/recent", "blitz/top", widget.constraints), - _ => const Center(child: Text("huh?")) - }, - }, - ), + child: SlideTransition( + position: _offsetAnimation, + child: SingleChildScrollView( + child: rigthCard(snapshot, sprintAchievements, blitzAchievements, width - 450), ), ), + ), if (modeButtons[rightCard]!.length > 1) SegmentedButton( showSelectedIcon: false, selected: {cardMod}, @@ -1242,6 +1296,36 @@ class _DestinationHomeState extends State with SingleTickerProv ) ) ], + ) : SingleChildScrollView( + child: Column( + children: [ + 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), + if (snapshot.data!.player!.role == "banned") FakeDistinguishmentThingy(banned: true) + else if (snapshot.data!.player!.badstanding == true) FakeDistinguishmentThingy(badStanding: true), + rigthCard(snapshot, sprintAchievements, blitzAchievements, width), + if (rightCard == Cards.overview) Card( + child: Column( + children: [ + Row( + children: [ + const Spacer(), + Text(t.bio, style: const TextStyle(fontFamily: "Eurostile Round Extended")), + const Spacer() + ], + ), + Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: MarkdownBody(data: snapshot.data!.player!.bio!, styleSheet: MarkdownStyleSheet(textAlign: WrapAlignment.center)), + ) + ], + ), + ), + if (rightCard == Cards.overview) NewsThingy(snapshot.data!.news!) + ], + ) ), ); } diff --git a/lib/views/destination_saved_data.dart b/lib/views/destination_saved_data.dart index a1571ac..eed34c1 100644 --- a/lib/views/destination_saved_data.dart +++ b/lib/views/destination_saved_data.dart @@ -118,7 +118,10 @@ class _DestinationSavedData extends State { child: Column( children: [ Card( - child: TabBar(tabs: [ + child: TabBar( + labelStyle: Theme.of(context).textTheme.titleMedium!.copyWith(fontSize: 28), + labelColor: Theme.of(context).colorScheme.primary, + tabs: [ Tab(text: "S${currentSeason} TL States"), Tab(text: "S1 TL States"), Tab(text: "TL Records") diff --git a/lib/views/main_view.dart b/lib/views/main_view.dart index 26eb932..6efd434 100644 --- a/lib/views/main_view.dart +++ b/lib/views/main_view.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.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'; @@ -24,6 +25,7 @@ import 'package:tetra_stats/main.dart'; late Future _data; TetrioPlayersLeaderboard? _everyone; +int destination = 0; Future getData(String searchFor) async { TetrioPlayer player; @@ -102,15 +104,23 @@ Map cardsTitles = { late ScrollController controller; class _MainState extends State with TickerProviderStateMixin { - int destination = 0; String _searchFor = "6098518e3d5155e6ec429cdc"; final TextEditingController _searchController = TextEditingController(); + Timer _backgroundUpdate = Timer(const Duration(days: 365), (){}); + final GlobalKey _scaffoldKey = GlobalKey(); @override void initState() { teto.open(); controller = ScrollController(); changePlayer(_searchFor); + + if (prefs.getBool("updateInBG") == true) { + _backgroundUpdate = Timer(Duration(minutes: 5), () { + changePlayer(_searchFor); + }); + } + super.initState(); } @@ -139,47 +149,144 @@ class _MainState extends State with TickerProviderStateMixin { ); } + NavigationDestination getMobileDestinationButton(IconData icon, String title){ + return NavigationDestination( + icon: Tooltip( + message: title, + child: Icon(icon) + ), + selectedIcon: Icon(icon), + label: 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.refresh), - ), - 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; - }); - }, + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints){ + bool screenIsBig = constraints.maxWidth > 768.00; + return Scaffold( + key: _scaffoldKey, + drawer: SearchDrawer(changePlayer: changePlayer, controller: _searchController), + endDrawer: DestinationsDrawer(changeDestination: (value) {setState(() {destination = value;});}), + bottomNavigationBar: screenIsBig ? null : BottomAppBar( + shape: const AutomaticNotchedShape(RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(0.0))), RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0)))), + notchMargin: 2.0, + height: 88, + child: IconTheme( + data: IconThemeData(color: Theme.of(context).colorScheme.primary), + child: Row( + children: [ + IconButton( + tooltip: 'Open navigation menu', + icon: const Icon(Icons.menu), + onPressed: () { + _scaffoldKey.currentState!.openEndDrawer(); + }, ), + Expanded( + child: Column( + children: [ + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SegmentedButton( + showSelectedIcon: false, + selected: {cardMod}, + segments: modeButtons[rightCard]!, + onSelectionChanged: (p0) { + setState(() { + cardMod = p0.first; + }); + }, + ), + ), + SegmentedButton( + showSelectedIcon: false, + segments: >[ + const ButtonSegment( + value: Cards.overview, + tooltip: 'Overview', + icon: Icon(Icons.calendar_view_day)), + ButtonSegment( + value: Cards.tetraLeague, + tooltip: 'Tetra League', + icon: SvgPicture.asset("res/icons/league.svg", height: 16, colorFilter: ColorFilter.mode(theme.colorScheme.primary, BlendMode.modulate))), + ButtonSegment( + value: Cards.quickPlay, + tooltip: 'Quick Play', + icon: SvgPicture.asset("res/icons/qp.svg", height: 16, colorFilter: ColorFilter.mode(theme.colorScheme.primary, BlendMode.modulate))), + ButtonSegment( + value: Cards.sprint, + tooltip: '40 Lines', + icon: SvgPicture.asset("res/icons/40l.svg", height: 16, colorFilter: ColorFilter.mode(theme.colorScheme.primary, BlendMode.modulate))), + ButtonSegment( + value: Cards.blitz, + tooltip: 'Blitz', + icon: SvgPicture.asset("res/icons/blitz.svg", height: 16, colorFilter: ColorFilter.mode(theme.colorScheme.primary, BlendMode.modulate))), + ], + selected: {rightCard}, + onSelectionChanged: (Set newSelection) { + setState(() { + cardMod = CardMod.info; + rightCard = newSelection.first; + });}) + ], + ), + ), + IconButton( + tooltip: 'Fake "Open navigation menu" button\nHere only for symmetry', + icon: const Icon(Icons.menu, color: Colors.transparent), + onPressed: () {}, + ), + ], + ), + ), + ), + floatingActionButtonLocation: screenIsBig ? null : FloatingActionButtonLocation.endDocked, + floatingActionButton: screenIsBig ? null : FloatingActionButton( + elevation: 0, + onPressed: () { + _scaffoldKey.currentState!.openDrawer(); + }, + child: const Icon(Icons.search), + ), + body: SafeArea( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (screenIsBig) TweenAnimationBuilder( + child: NavigationRail( + leading: FloatingActionButton( + elevation: 0, + onPressed: () { + Scaffold.of(context).openDrawer(); + }, + child: const Icon(Icons.search), + ), + trailing: IconButton( + tooltip: "Refresh data", + onPressed: () { + changePlayer(_searchFor); + }, + icon: const Icon(Icons.refresh), + ), + 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, @@ -189,23 +296,25 @@ class _MainState extends State with TickerProviderStateMixin { child: Opacity(opacity: value, child: child), ); }, - ), - Expanded( - child: switch (destination){ - 0 => DestinationHome(searchFor: _searchFor, constraints: constraints, dataFuture: _data), - 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") - }, - ) - ]); - }, - )); + ), + Expanded( + child: switch (destination){ + 0 => DestinationHome(searchFor: _searchFor, constraints: constraints, dataFuture: _data, noSidebar: !screenIsBig), + 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") + }, + ) + ] + ), + )); + } + ); } } @@ -222,80 +331,178 @@ 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(); - }); + child: SafeArea( + 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: 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. - }, - ); - }) - ); + 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 DestinationsDrawer extends StatefulWidget{ + final Function changeDestination; + + const DestinationsDrawer({super.key, required this.changeDestination}); + + @override + State createState() => _DestinationsDrawerState(); + +} + +class _DestinationsDrawerState extends State{ + @override + Widget build(BuildContext context) { + return Drawer( + child: NestedScrollView( + headerSliverBuilder: (BuildContext context, bool value){ + return [ + SliverToBoxAdapter( + child: DrawerHeader( + child: Text("Navigation menu", style: const TextStyle(color: Colors.white, fontSize: 25), + ))) + ]; + }, + body: ListView( + children: [ + ListTile( + leading: Icon(Icons.home), + title: Text("Home"), + onTap: (){ + widget.changeDestination(0); + Navigator.of(context).pop(); + }, + ), + ListTile( + leading: Icon(Icons.data_thresholding_outlined), + title: Text("Graphs"), + onTap: (){ + widget.changeDestination(1); + Navigator.of(context).pop(); + }, + ), + ListTile( + leading: Icon(Icons.leaderboard), + title: Text("Leaderboards"), + onTap: (){ + widget.changeDestination(2); + Navigator.of(context).pop(); + }, + ), + ListTile( + leading: Icon(Icons.compress), + title: Text("Cutoffs"), + onTap: (){ + widget.changeDestination(3); + Navigator.of(context).pop(); + }, + ), + ListTile( + leading: Icon(Icons.calculate), + title: Text("Calc"), + onTap: (){ + widget.changeDestination(4); + Navigator.of(context).pop(); + }, + ), + ListTile( + leading: Icon(Icons.info_outline), + title: Text("Information"), + onTap: (){ + widget.changeDestination(5); + Navigator.of(context).pop(); + }, + ), + ListTile( + leading: Icon(Icons.storage), + title: Text("Saved Data"), + onTap: (){ + widget.changeDestination(6); + Navigator.of(context).pop(); + }, + ), + ListTile( + leading: Icon(Icons.settings), + title: Text("Settings"), + onTap: (){ + widget.changeDestination(7); + Navigator.of(context).pop(); + }, + ), + ], + ) ) ); } diff --git a/lib/views/singleplayer_record_view.dart b/lib/views/singleplayer_record_view.dart index 0fe3bf0..0b87728 100644 --- a/lib/views/singleplayer_record_view.dart +++ b/lib/views/singleplayer_record_view.dart @@ -1,6 +1,7 @@ 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/views/destination_home.dart'; import 'package:tetra_stats/widgets/singleplayer_record.dart'; import 'package:tetra_stats/widgets/text_timestamp.dart'; @@ -15,28 +16,28 @@ class SingleplayerRecordView extends StatelessWidget { //bool bigScreen = MediaQuery.of(context).size.width >= 368; return Scaffold( backgroundColor: Colors.black, - appBar: AppBar( - title: Text("${ - switch (record.gamemode){ - "40l" => t.sprint, - "blitz" => t.blitz, - String() => "5000000 Blast", - } - } ${timestamp(record.timestamp)}"), + floatingActionButtonLocation: FloatingActionButtonLocation.startTop, + floatingActionButton: Padding( + padding: const EdgeInsets.fromLTRB(16.0, 8.0, 0.0, 0.0), + child: FloatingActionButton( + onPressed: () => Navigator.pop(context), + tooltip: 'Fuck go back', + child: const Icon(Icons.arrow_back), + ), ), body: SafeArea( child: SingleChildScrollView( - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Column( - children: [ - SingleplayerRecord(record: record, hideTitle: true), - // TODO: Insert replay link here - ] - ) - ], + child: Center( + child: Container( + constraints: BoxConstraints( + maxWidth: 768 + ), + child: switch (record.gamemode){ + "zenith" => ZenithCard(record, false), + "zenithex" => ZenithCard(record, false), + _ => SingleplayerRecord(record: record, hideTitle: true) + }, + ), ) ) ), diff --git a/lib/views/state_view.dart b/lib/views/state_view.dart index 45fab6a..aede46d 100644 --- a/lib/views/state_view.dart +++ b/lib/views/state_view.dart @@ -4,6 +4,8 @@ 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/widgets/graphs.dart'; +import 'package:tetra_stats/widgets/nerd_stats_thingy.dart'; import 'package:tetra_stats/widgets/text_timestamp.dart'; import 'package:tetra_stats/widgets/tl_thingy.dart'; import 'package:window_manager/window_manager.dart'; @@ -45,11 +47,19 @@ class StateState extends State { //final t = Translations.of(context); return Scaffold( appBar: AppBar( - title: Text("State from ${timestamp(widget.state.timestamp)}"), + title: Text("State from ${timestamp(widget.state.timestamp)}", style: Theme.of(context).textTheme.titleMedium!.copyWith(fontSize: 28)), ), backgroundColor: Colors.black, body: SafeArea( - child: TetraLeagueThingy(league: widget.state) + child: SingleChildScrollView( + child: Column( + children: [ + TetraLeagueThingy(league: widget.state), + if (widget.state.nerdStats != null) NerdStatsThingy(nerdStats: widget.state.nerdStats!), + if (widget.state.playstyle != null) Graphs(widget.state.apm!, widget.state.pps!, widget.state.vs!, widget.state.nerdStats!, widget.state.playstyle!) + ], + ), + ) ) ); } diff --git a/lib/widgets/nerd_stats_thingy.dart b/lib/widgets/nerd_stats_thingy.dart index 105b689..bde101d 100644 --- a/lib/widgets/nerd_stats_thingy.dart +++ b/lib/widgets/nerd_stats_thingy.dart @@ -13,8 +13,128 @@ class NerdStatsThingy extends StatelessWidget{ final NerdStats? oldNerdStats; final CutoffTetrio? averages; final PlayerLeaderboardPosition? lbPos; + final double width; - const NerdStatsThingy({super.key, required this.nerdStats, this.oldNerdStats, this.averages, this.lbPos}); + const NerdStatsThingy({super.key, required this.nerdStats, this.oldNerdStats, this.averages, this.lbPos, this.width = double.infinity}); + + Widget big(){ + return 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 + ) + ], + ) + ] + ), + ), + ), + ); + } + + Widget manySmalls(){ + return 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), + ], + ); + } @override Widget build(BuildContext context) { @@ -23,126 +143,20 @@ class NerdStatsThingy extends StatelessWidget{ children: [ Padding( padding: const EdgeInsets.fromLTRB(12.0, 8.0, 12.0, 8.0), - child: Row( + child: width > 600 ? 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), - ], - ), - ) + big(), + Expanded(child: manySmalls()) ] + ) : Center( + child: Column( + children: [ + big(), + manySmalls() + ], + ), ), ), ], diff --git a/lib/widgets/singleplayer_record.dart b/lib/widgets/singleplayer_record.dart index 4a20225..95532d4 100644 --- a/lib/widgets/singleplayer_record.dart +++ b/lib/widgets/singleplayer_record.dart @@ -1,29 +1,16 @@ import 'package:flutter/material.dart'; -import 'package:intl/intl.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/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/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/widgets/finesse_thingy.dart'; -import 'package:tetra_stats/widgets/lineclears_thingy.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/views/destination_home.dart'; class SingleplayerRecord extends StatelessWidget { final RecordSingle? record; - final SingleplayerStream? stream; final String? rank; final bool hideTitle; /// Widget that displays data from [record] - const SingleplayerRecord({super.key, required this.record, this.stream, this.rank, this.hideTitle = false}); + const SingleplayerRecord({super.key, required this.record, this.rank, this.hideTitle = false}); @override Widget build(BuildContext context) { @@ -42,114 +29,6 @@ class SingleplayerRecord extends StatelessWidget { blitzBetterThanClosestAverage = record!.stats.score > closestAverageBlitz.value; } - return LayoutBuilder( - builder: (context, constraints) { - bool bigScreen = constraints.maxWidth > 768; - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - 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: [ - if (record!.gamemode == "40l" && !hideTitle) Text(t.sprint, style: const TextStyle(height: 0.1, fontFamily: "Eurostile Round Extended", fontSize: 18)), - if (record!.gamemode == "blitz" && !hideTitle) Text(t.blitz, style: const TextStyle(height: 0.1, fontFamily: "Eurostile Round Extended", fontSize: 18)), - RichText(text: TextSpan( - text: record!.gamemode == "40l" ? get40lTime(record!.stats.finalTime.inMicroseconds) : NumberFormat.decimalPattern().format(record!.stats.score), - style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 36 : 32, 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" || rank != "x+")) 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" || rank != "x+")) 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)), - ] - ), - ) - ],), - ], - ), - if (record!.gamemode == "40l") Wrap( - alignment: WrapAlignment.spaceBetween, - spacing: 20, - 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), - ], - ), - if (record!.gamemode == "blitz") Wrap( - alignment: WrapAlignment.spaceBetween, - crossAxisAlignment: WrapCrossAlignment.start, - spacing: 20, - children: [ - StatCellNum(playerStat: record!.stats.level, playerStatLabel: t.statCellNum.level, 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.spp, playerStatLabel: t.statCellNum.spp, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true) - ], - ), - FinesseThingy(record?.stats.finesse, record?.stats.finessePercentage), - LineclearsThingy(record!.stats.clears, record!.stats.lines, record!.stats.holds, record!.stats.tSpins), - if (record!.gamemode == "40l") Text("${record!.stats.inputs} KP • ${f2.format(record!.stats.kps)} KPS"), - if (record!.gamemode == "blitz") Text("${record!.stats.piecesPlaced} P • ${record!.stats.inputs} KP • ${f2.format(record!.stats.kpp)} KPP • ${f2.format(record!.stats.kps)} KPS"), - if (record != null) Wrap( - alignment: WrapAlignment.spaceBetween, - crossAxisAlignment: WrapCrossAlignment.start, - spacing: 20, - children: [ - TextButton(onPressed: (){launchInBrowser(Uri.parse("https://tetr.io/#r:${record!.replayId}"));}, child: Text(t.openSPreplay)), - TextButton(onPressed: (){launchInBrowser(Uri.parse("https://inoue.szy.lol/api/replay/${record!.replayId}"));}, child: Text(t.downloadSPreplay)), - ], - ), - if (stream != null && stream!.records.length > 1) for(int i = 1; i < stream!.records.length; i++) ListTile( - onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => SingleplayerRecordView(record: stream!.records[i]))), - leading: Text("#${i+1}", - style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28, shadows: textShadow, height: 0.9) - ), - title: Text( - switch (stream!.records[i].gamemode){ - "40l" => get40lTime(stream!.records[i].stats.finalTime.inMicroseconds), - "blitz" => t.blitzScore(p: NumberFormat.decimalPattern().format(stream!.records[i].stats.score)), - "5mblast" => get40lTime(stream!.records[i].stats.finalTime.inMicroseconds), - String() => "huh", - }, - style: Theme.of(context).textTheme.displayLarge), - subtitle: Text(timestamp(stream!.records[i].timestamp), style: const TextStyle(color: Colors.grey, height: 0.85)), - trailing: SpTrailingStats(stream!.records[i], stream!.records[i].gamemode) - ) - ] - ), - ), - ); - } - ); + return RecordCard(record, [], record!.gamemode == "40l" ? sprintBetterThanRankAverage : blitzBetterThanRankAverage, record!.gamemode == "40l" ? closestAverageSprint : closestAverageBlitz, record!.gamemode == "40l" ? sprintBetterThanClosestAverage : blitzBetterThanClosestAverage, rank); } } \ No newline at end of file diff --git a/lib/widgets/tl_rating_thingy.dart b/lib/widgets/tl_rating_thingy.dart index 41904cb..7caa4ad 100644 --- a/lib/widgets/tl_rating_thingy.dart +++ b/lib/widgets/tl_rating_thingy.dart @@ -40,9 +40,10 @@ 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, + crossAxisAlignment: bigScreen ? CrossAxisAlignment.start : CrossAxisAlignment.center, children: [ RichText( + textAlign: bigScreen ? TextAlign.start : TextAlign.center, text: TextSpan( style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 20, color: Colors.white, height: 0.9), children: (tlData.gamesPlayed > 9) ? switch(prefs.getInt("ratingMode")){ @@ -125,7 +126,7 @@ class TLRatingThingy extends StatelessWidget{ ], ), if (showPositions == true) RichText( - textAlign: TextAlign.start, + textAlign: bigScreen ? TextAlign.start : TextAlign.center, text: TextSpan( text: "", style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey), diff --git a/lib/widgets/tl_thingy.dart b/lib/widgets/tl_thingy.dart index 01e685f..d30afa4 100644 --- a/lib/widgets/tl_thingy.dart +++ b/lib/widgets/tl_thingy.dart @@ -17,8 +17,35 @@ class TetraLeagueThingy extends StatelessWidget{ final Cutoffs? cutoffs; final CutoffTetrio? averages; final PlayerLeaderboardPosition? lbPos; + final double width; - const TetraLeagueThingy({super.key, required this.league, this.toCompare, this.cutoffs, this.averages, this.lbPos}); + const TetraLeagueThingy({super.key, required this.league, this.toCompare, this.cutoffs, this.averages, this.lbPos, this.width = double.infinity}); + + List secondColumn(){ + return [ + 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)) + ]), + ]; + } @override Widget build(BuildContext context) { @@ -37,8 +64,6 @@ class TetraLeagueThingy extends StatelessWidget{ 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( @@ -65,41 +90,26 @@ class TetraLeagueThingy extends StatelessWidget{ 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)) - ]) + ]), + if (width <= 600) TableRow(children: [ + Text(!league.winrate.isNegative ? percentage.format(league.winrate) : "---", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: !league.winrate.isNegative ? Colors.white : Colors.grey)), + Text(" WR", style: TextStyle(fontSize: 21, color: !league.winrate.isNegative ? Colors.white : Colors.grey)), + if (toCompare != null) Text(" (${comparef2.format((league.winrate-toCompare!.winrate)*100)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: getDifferenceColor(league.winrate-toCompare!.winrate))), + if (lbPos != null) Text(lbPos?.winrate != null ? (lbPos!.winrate!.position >= 1000 ? " (${t.top} ${f2.format(lbPos!.winrate!.percentage*100)}%)" : " (№ ${lbPos!.winrate!.position})") : "(№ ---)", style: TextStyle(color: lbPos?.winrate != null ? getColorOfRank(lbPos!.winrate!.position) : null)) + ]), + if (width <= 400) ...secondColumn() ], ), ), ), - 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( + if (width > 600) 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), + if (width > 400) 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)) - ]), - ], + children: secondColumn(), ), ), ), diff --git a/lib/widgets/zenith_thingy.dart b/lib/widgets/zenith_thingy.dart index ee0692a..b280e70 100644 --- a/lib/widgets/zenith_thingy.dart +++ b/lib/widgets/zenith_thingy.dart @@ -9,8 +9,51 @@ import 'package:tetra_stats/widgets/text_timestamp.dart'; class ZenithThingy extends StatelessWidget{ final RecordSingle? zenith; final bool old; + final double width; - const ZenithThingy({super.key, required this.zenith, this.old = false}); + const ZenithThingy({super.key, required this.zenith, this.old = false, this.width = double.infinity}); + + List secondColumn(){ + return [ + 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)) + ]), + if (width <= 600) TableRow(children: [ + Text(f2.format(zenith!.stats.zenith!.peakrank), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), + const Text(" Peak CSP", style: TextStyle(fontSize: 21)), + ]) + ]; + } + + List noRecordSecondColumn(){ + return [ + 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)) + ]), + if (width <= 600) TableRow(children: [ + Text("-.--", textAlign: TextAlign.right, style: const TextStyle(fontSize: 21, color: Colors.grey)), + const Text(" Peak CSP", style: TextStyle(fontSize: 21, color: Colors.grey)), + ]) + ]; + } @override Widget build(BuildContext context) { @@ -68,30 +111,22 @@ class ZenithThingy extends StatelessWidget{ TableRow(children: [ Text(f2.format(zenith!.aggregateStats.vs), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), const Text(" VS", style: TextStyle(fontSize: 21)), - ]) + ]), + if (width <= 600) TableRow(children: [ + Text(f2.format(zenith!.stats.cps), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), + const Text(" CSP", style: TextStyle(fontSize: 21)), + ]), + if (width <= 400) ...secondColumn().reversed ], ), ), ), - 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( + if (width > 600) 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), + if (width > 400) 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)) - ]) - ], + children: secondColumn(), ), ), ) @@ -114,30 +149,21 @@ class ZenithThingy extends StatelessWidget{ const TableRow(children: [ Text("-.--", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: Colors.grey)), Text(" VS", style: TextStyle(fontSize: 21, color: Colors.grey)), + ]), + if (width <= 600) TableRow(children: [ + Text("-.--", textAlign: TextAlign.right, style: const TextStyle(fontSize: 21, color: Colors.grey)), + const Text(" CSP", 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( + if (width > 600) GaugetThingy(value: null, min: 0, max: 12, tickInterval: 3, label: "Climb\nSpeed", subString: "Peak: ---", sideSize: 128, fractionDigits: 0, moreIsBetter: true), + if (width > 400) 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)) - ]) - ], + children: noRecordSecondColumn(), ), ), ) diff --git a/pubspec.lock b/pubspec.lock index 559060d..82d38d7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -278,6 +278,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.13.1" + flutter_layout_grid: + dependency: "direct main" + description: + name: flutter_layout_grid + sha256: "88b4f8484a0874962e27c47733ad256aeb26acc694a9f029edbef771d301885a" + url: "https://pub.dev" + source: hosted + version: "2.0.7" flutter_lints: dependency: "direct dev" description: @@ -645,6 +653,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + quiver: + dependency: transitive + description: + name: quiver + sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 + url: "https://pub.dev" + source: hosted + version: "3.2.2" screen_retriever: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 21e0118..e12e0fa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,6 +43,7 @@ dependencies: window_manager: ^0.3.7 flutter_markdown: ^0.6.18 flutter_colorpicker: ^1.0.3 + flutter_layout_grid: ^2.0.0 go_router: ^13.0.0 syncfusion_flutter_charts: ^24.2.9