Thinking about adaptivity

This commit is contained in:
dan63047 2024-11-20 02:21:03 +03:00
parent 1b9249f36b
commit 874d5a2e5c
16 changed files with 1301 additions and 1038 deletions

View File

@ -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

View File

@ -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],

View File

@ -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

View File

@ -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 {

File diff suppressed because it is too large Load Diff

View File

@ -118,7 +118,10 @@ class _DestinationSavedData extends State<DestinationSavedData> {
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")

View File

@ -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<FetchResults> _data;
TetrioPlayersLeaderboard? _everyone;
int destination = 0;
Future<FetchResults> getData(String searchFor) async {
TetrioPlayer player;
@ -102,15 +104,23 @@ Map<Cards, String> cardsTitles = {
late ScrollController controller;
class _MainState extends State<MainView> with TickerProviderStateMixin {
int destination = 0;
String _searchFor = "6098518e3d5155e6ec429cdc";
final TextEditingController _searchController = TextEditingController();
Timer _backgroundUpdate = Timer(const Duration(days: 365), (){});
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
@override
void initState() {
teto.open();
controller = ScrollController();
changePlayer(_searchFor);
if (prefs.getBool("updateInBG") == true) {
_backgroundUpdate = Timer(Duration(minutes: 5), () {
changePlayer(_searchFor);
});
}
super.initState();
}
@ -139,16 +149,112 @@ class _MainState extends State<MainView> 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 LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints){
bool screenIsBig = constraints.maxWidth > 768.00;
return Scaffold(
key: _scaffoldKey,
drawer: SearchDrawer(changePlayer: changePlayer, controller: _searchController),
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Row(
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: <Widget>[
IconButton(
tooltip: 'Open navigation menu',
icon: const Icon(Icons.menu),
onPressed: () {
_scaffoldKey.currentState!.openEndDrawer();
},
),
Expanded(
child: Column(
children: [
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SegmentedButton<CardMod>(
showSelectedIcon: false,
selected: <CardMod>{cardMod},
segments: modeButtons[rightCard]!,
onSelectionChanged: (p0) {
setState(() {
cardMod = p0.first;
});
},
),
),
SegmentedButton<Cards>(
showSelectedIcon: false,
segments: <ButtonSegment<Cards>>[
const ButtonSegment<Cards>(
value: Cards.overview,
tooltip: 'Overview',
icon: Icon(Icons.calendar_view_day)),
ButtonSegment<Cards>(
value: Cards.tetraLeague,
tooltip: 'Tetra League',
icon: SvgPicture.asset("res/icons/league.svg", height: 16, colorFilter: ColorFilter.mode(theme.colorScheme.primary, BlendMode.modulate))),
ButtonSegment<Cards>(
value: Cards.quickPlay,
tooltip: 'Quick Play',
icon: SvgPicture.asset("res/icons/qp.svg", height: 16, colorFilter: ColorFilter.mode(theme.colorScheme.primary, BlendMode.modulate))),
ButtonSegment<Cards>(
value: Cards.sprint,
tooltip: '40 Lines',
icon: SvgPicture.asset("res/icons/40l.svg", height: 16, colorFilter: ColorFilter.mode(theme.colorScheme.primary, BlendMode.modulate))),
ButtonSegment<Cards>(
value: Cards.blitz,
tooltip: 'Blitz',
icon: SvgPicture.asset("res/icons/blitz.svg", height: 16, colorFilter: ColorFilter.mode(theme.colorScheme.primary, BlendMode.modulate))),
],
selected: <Cards>{rightCard},
onSelectionChanged: (Set<Cards> 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: [
TweenAnimationBuilder(
if (screenIsBig) TweenAnimationBuilder(
child: NavigationRail(
leading: FloatingActionButton(
elevation: 0,
@ -158,8 +264,9 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
child: const Icon(Icons.search),
),
trailing: IconButton(
tooltip: "Refresh data",
onPressed: () {
// Add your onPressed code here!
changePlayer(_searchFor);
},
icon: const Icon(Icons.refresh),
),
@ -192,7 +299,7 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
),
Expanded(
child: switch (destination){
0 => DestinationHome(searchFor: _searchFor, constraints: constraints, dataFuture: _data),
0 => DestinationHome(searchFor: _searchFor, constraints: constraints, dataFuture: _data, noSidebar: !screenIsBig),
1 => DestinationGraphs(searchFor: _searchFor, constraints: constraints),
2 => DestinationLeaderboards(constraints: constraints),
3 => DestinationCutoffs(constraints: constraints),
@ -203,10 +310,12 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
_ => Text("Unknown destination $destination")
},
)
]);
},
]
),
));
}
);
}
}
class SearchDrawer extends StatefulWidget{
@ -222,6 +331,7 @@ class _SearchDrawerState extends State<SearchDrawer> {
@override
Widget build(BuildContext context) {
return Drawer(
child: SafeArea(
child: StreamBuilder(
stream: teto.allPlayers,
builder: (context, snapshot) {
@ -296,6 +406,103 @@ class _SearchDrawerState extends State<SearchDrawer> {
);
}
}
),
)
);
}
}
class DestinationsDrawer extends StatefulWidget{
final Function changeDestination;
const DestinationsDrawer({super.key, required this.changeDestination});
@override
State<StatefulWidget> createState() => _DestinationsDrawerState();
}
class _DestinationsDrawerState extends State<DestinationsDrawer>{
@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();
},
),
],
)
)
);
}

View File

@ -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)
},
),
)
)
),

View File

@ -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<StateView> {
//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!)
],
),
)
)
);
}

View File

@ -13,21 +13,12 @@ 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});
@override
Widget build(BuildContext context) {
return Card(
child: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(12.0, 8.0, 12.0, 8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
Widget big(){
return SizedBox(
height: 256.0,
width: 256.0,
child: ClipRRect(
@ -124,9 +115,11 @@ class NerdStatsThingy extends StatelessWidget{
),
),
),
),
Expanded(
child: Wrap(
);
}
Widget manySmalls(){
return Wrap(
alignment: WrapAlignment.center,
spacing: 10.0,
runSpacing: 10.0,
@ -140,9 +133,30 @@ class NerdStatsThingy extends StatelessWidget{
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) {
return Card(
child: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(12.0, 8.0, 12.0, 8.0),
child: width > 600 ? Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
big(),
Expanded(child: manySmalls())
]
) : Center(
child: Column(
children: [
big(),
manySmalls()
],
),
),
),
],

View File

@ -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);
}
}

View File

@ -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),

View File

@ -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<TableRow> 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(),
),
),
),

View File

@ -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<TableRow> 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<TableRow> 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(),
),
),
)

View File

@ -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:

View File

@ -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