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 { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.dan63.tetra_stats" applicationId "com.dan63.tetra_stats"
testApplicationId "com.dan63.tetra_stats.dev_build"
// You can update the following values to match your application needs. // 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. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion flutter.minSdkVersion minSdkVersion flutter.minSdkVersion

View File

@ -49,7 +49,7 @@ class Summaries {
]; ];
league = league =
TetraLeague.fromJson(json['league'], DateTime.now(), currentSeason, i); 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) { for (var key in json['league']['past'].keys) {
pastLeague[int.parse(key)] = TetraLeague.fromJson( pastLeague[int.parse(key)] = TetraLeague.fromJson(
json['league']['past'][key], json['league']['past'][key],

View File

@ -6,7 +6,7 @@
/// Locales: 3 /// Locales: 3
/// Strings: 1818 (606 per locale) /// 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 // coverage:ignore-file
// ignore_for_file: type=lint // ignore_for_file: type=lint

View File

@ -59,7 +59,17 @@ ThemeData theme = ThemeData(
expandedAlignment: Alignment.bottomCenter, expandedAlignment: Alignment.bottomCenter,
), ),
dropdownMenuTheme: DropdownMenuThemeData(textStyle: TextStyle(fontFamily: "Eurostile Round", fontSize: 18)), 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 { 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( child: Column(
children: [ children: [
Card( 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: "S${currentSeason} TL States"),
Tab(text: "S1 TL States"), Tab(text: "S1 TL States"),
Tab(text: "TL Records") Tab(text: "TL Records")

View File

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.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/cutoff_tetrio.dart';
import 'package:tetra_stats/data_objects/news.dart'; import 'package:tetra_stats/data_objects/news.dart';
import 'package:tetra_stats/data_objects/p1nkl0bst3r.dart'; import 'package:tetra_stats/data_objects/p1nkl0bst3r.dart';
@ -24,6 +25,7 @@ import 'package:tetra_stats/main.dart';
late Future<FetchResults> _data; late Future<FetchResults> _data;
TetrioPlayersLeaderboard? _everyone; TetrioPlayersLeaderboard? _everyone;
int destination = 0;
Future<FetchResults> getData(String searchFor) async { Future<FetchResults> getData(String searchFor) async {
TetrioPlayer player; TetrioPlayer player;
@ -102,15 +104,23 @@ Map<Cards, String> cardsTitles = {
late ScrollController controller; late ScrollController controller;
class _MainState extends State<MainView> with TickerProviderStateMixin { class _MainState extends State<MainView> with TickerProviderStateMixin {
int destination = 0;
String _searchFor = "6098518e3d5155e6ec429cdc"; String _searchFor = "6098518e3d5155e6ec429cdc";
final TextEditingController _searchController = TextEditingController(); final TextEditingController _searchController = TextEditingController();
Timer _backgroundUpdate = Timer(const Duration(days: 365), (){});
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
@override @override
void initState() { void initState() {
teto.open(); teto.open();
controller = ScrollController(); controller = ScrollController();
changePlayer(_searchFor); changePlayer(_searchFor);
if (prefs.getBool("updateInBG") == true) {
_backgroundUpdate = Timer(Duration(minutes: 5), () {
changePlayer(_searchFor);
});
}
super.initState(); super.initState();
} }
@ -139,47 +149,144 @@ 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return LayoutBuilder(
drawer: SearchDrawer(changePlayer: changePlayer, controller: _searchController), builder: (BuildContext context, BoxConstraints constraints){
body: LayoutBuilder( bool screenIsBig = constraints.maxWidth > 768.00;
builder: (BuildContext context, BoxConstraints constraints) { return Scaffold(
return Row( key: _scaffoldKey,
mainAxisAlignment: MainAxisAlignment.center, drawer: SearchDrawer(changePlayer: changePlayer, controller: _searchController),
children: [ endDrawer: DestinationsDrawer(changeDestination: (value) {setState(() {destination = value;});}),
TweenAnimationBuilder( bottomNavigationBar: screenIsBig ? null : BottomAppBar(
child: NavigationRail( shape: const AutomaticNotchedShape(RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(0.0))), RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0)))),
leading: FloatingActionButton( notchMargin: 2.0,
elevation: 0, height: 88,
onPressed: () { child: IconTheme(
Scaffold.of(context).openDrawer(); data: IconThemeData(color: Theme.of(context).colorScheme.primary),
}, child: Row(
child: const Icon(Icons.search), children: <Widget>[
), IconButton(
trailing: IconButton( tooltip: 'Open navigation menu',
onPressed: () { icon: const Icon(Icons.menu),
// Add your onPressed code here! onPressed: () {
}, _scaffoldKey.currentState!.openEndDrawer();
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;
});
},
), ),
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: [
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, duration: Durations.long4,
tween: Tween<double>(begin: 0, end: 1), tween: Tween<double>(begin: 0, end: 1),
curve: Easing.standard, curve: Easing.standard,
@ -189,23 +296,25 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
child: Opacity(opacity: value, child: child), child: Opacity(opacity: value, child: child),
); );
}, },
), ),
Expanded( Expanded(
child: switch (destination){ 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), 1 => DestinationGraphs(searchFor: _searchFor, constraints: constraints),
2 => DestinationLeaderboards(constraints: constraints), 2 => DestinationLeaderboards(constraints: constraints),
3 => DestinationCutoffs(constraints: constraints), 3 => DestinationCutoffs(constraints: constraints),
4 => DestinationCalculator(constraints: constraints), 4 => DestinationCalculator(constraints: constraints),
5 => DestinationInfo(constraints: constraints), 5 => DestinationInfo(constraints: constraints),
6 => DestinationSavedData(constraints: constraints), 6 => DestinationSavedData(constraints: constraints),
7 => DestinationSettings(constraints: constraints), 7 => DestinationSettings(constraints: constraints),
_ => Text("Unknown destination $destination") _ => Text("Unknown destination $destination")
}, },
) )
]); ]
}, ),
)); ));
}
);
} }
} }
@ -222,80 +331,178 @@ class _SearchDrawerState extends State<SearchDrawer> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Drawer( return Drawer(
child: StreamBuilder( child: SafeArea(
stream: teto.allPlayers, child: StreamBuilder(
builder: (context, snapshot) { stream: teto.allPlayers,
switch (snapshot.connectionState) { builder: (context, snapshot) {
case ConnectionState.none: switch (snapshot.connectionState) {
case ConnectionState.waiting: case ConnectionState.none:
case ConnectionState.done: case ConnectionState.waiting:
case ConnectionState.active: case ConnectionState.done:
final allPlayers = (snapshot.data != null) case ConnectionState.active:
? snapshot.data as Map<String, String> final allPlayers = (snapshot.data != null)
: <String, String>{}; ? snapshot.data as Map<String, String>
allPlayers.remove(prefs.getString("playerID") ?? "6098518e3d5155e6ec429cdc"); // player from the home button will be delisted : <String, String>{};
List<String> keys = allPlayers.keys.toList(); allPlayers.remove(prefs.getString("playerID") ?? "6098518e3d5155e6ec429cdc"); // player from the home button will be delisted
return NestedScrollView( List<String> keys = allPlayers.keys.toList();
headerSliverBuilder: (BuildContext context, bool value){ return NestedScrollView(
return [ headerSliverBuilder: (BuildContext context, bool value){
SliverToBoxAdapter( return [
child: SearchBar( SliverToBoxAdapter(
controller: widget.controller, child: SearchBar(
hintText: "Enter the username", controller: widget.controller,
hintStyle: const WidgetStatePropertyAll(TextStyle(color: Colors.grey)), hintText: "Enter the username",
trailing: [ hintStyle: const WidgetStatePropertyAll(TextStyle(color: Colors.grey)),
IconButton(onPressed: (){setState(() { trailing: [
widget.changePlayer(widget.controller.value.text); IconButton(onPressed: (){setState(() {
Navigator.of(context).pop(); widget.changePlayer(widget.controller.value.text);
});}, icon: const Icon(Icons.search)) Navigator.of(context).pop();
], });}, icon: const Icon(Icons.search))
onSubmitted: (value) { ],
setState(() { onSubmitted: (value) {
widget.changePlayer(value); setState(() {
Navigator.of(context).pop(); 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),
), ),
) SliverToBoxAdapter(
]; child: Divider(),
}, ),
body: ListView.builder( // Builds list of tracked players. SliverToBoxAdapter(
itemCount: allPlayers.length, child: Padding(
itemBuilder: (context, index) { padding: const EdgeInsets.only(left: 10.0),
var i = allPlayers.length-1-index; // Last players in this map are most recent ones, they are gonna be shown at the top. child: Text("Tracked Players", style: Theme.of(context).textTheme.headlineLarge),
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)), body: ListView.builder( // Builds list of tracked players.
onTap: () { itemCount: allPlayers.length,
widget.changePlayer(keys[i]); // changes to chosen player itemBuilder: (context, index) {
Navigator.of(context).pop(); // and closes itself. 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<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:flutter/material.dart';
import 'package:tetra_stats/data_objects/record_single.dart'; import 'package:tetra_stats/data_objects/record_single.dart';
import 'package:tetra_stats/gen/strings.g.dart'; import 'package:tetra_stats/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/singleplayer_record.dart';
import 'package:tetra_stats/widgets/text_timestamp.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; //bool bigScreen = MediaQuery.of(context).size.width >= 368;
return Scaffold( return Scaffold(
backgroundColor: Colors.black, backgroundColor: Colors.black,
appBar: AppBar( floatingActionButtonLocation: FloatingActionButtonLocation.startTop,
title: Text("${ floatingActionButton: Padding(
switch (record.gamemode){ padding: const EdgeInsets.fromLTRB(16.0, 8.0, 0.0, 0.0),
"40l" => t.sprint, child: FloatingActionButton(
"blitz" => t.blitz, onPressed: () => Navigator.pop(context),
String() => "5000000 Blast", tooltip: 'Fuck go back',
} child: const Icon(Icons.arrow_back),
} ${timestamp(record.timestamp)}"), ),
), ),
body: SafeArea( body: SafeArea(
child: SingleChildScrollView( child: SingleChildScrollView(
child: Row( child: Center(
crossAxisAlignment: CrossAxisAlignment.center, child: Container(
mainAxisAlignment: MainAxisAlignment.center, constraints: BoxConstraints(
children: [ maxWidth: 768
Column( ),
children: [ child: switch (record.gamemode){
SingleplayerRecord(record: record, hideTitle: true), "zenith" => ZenithCard(record, false),
// TODO: Insert replay link here "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:intl/intl.dart';
import 'package:tetra_stats/data_objects/tetra_league.dart'; import 'package:tetra_stats/data_objects/tetra_league.dart';
import 'package:tetra_stats/gen/strings.g.dart'; import 'package:tetra_stats/gen/strings.g.dart';
import 'package:tetra_stats/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/text_timestamp.dart';
import 'package:tetra_stats/widgets/tl_thingy.dart'; import 'package:tetra_stats/widgets/tl_thingy.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
@ -45,11 +47,19 @@ class StateState extends State<StateView> {
//final t = Translations.of(context); //final t = Translations.of(context);
return Scaffold( return Scaffold(
appBar: AppBar( 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, backgroundColor: Colors.black,
body: SafeArea( 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,8 +13,128 @@ class NerdStatsThingy extends StatelessWidget{
final NerdStats? oldNerdStats; final NerdStats? oldNerdStats;
final CutoffTetrio? averages; final CutoffTetrio? averages;
final PlayerLeaderboardPosition? lbPos; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -23,126 +143,20 @@ class NerdStatsThingy extends StatelessWidget{
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.fromLTRB(12.0, 8.0, 12.0, 8.0), padding: const EdgeInsets.fromLTRB(12.0, 8.0, 12.0, 8.0),
child: Row( child: width > 600 ? Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
SizedBox( big(),
height: 256.0, Expanded(child: manySmalls())
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),
],
),
)
] ]
) : Center(
child: Column(
children: [
big(),
manySmalls()
],
),
), ),
), ),
], ],

View File

@ -1,29 +1,16 @@
import 'package:flutter/material.dart'; 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/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/data_objects/tetrio_constants.dart';
import 'package:tetra_stats/gen/strings.g.dart'; import 'package:tetra_stats/gen/strings.g.dart';
import 'package:tetra_stats/utils/colors_functions.dart'; import 'package:tetra_stats/views/destination_home.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';
class SingleplayerRecord extends StatelessWidget { class SingleplayerRecord extends StatelessWidget {
final RecordSingle? record; final RecordSingle? record;
final SingleplayerStream? stream;
final String? rank; final String? rank;
final bool hideTitle; final bool hideTitle;
/// Widget that displays data from [record] /// 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -42,114 +29,6 @@ class SingleplayerRecord extends StatelessWidget {
blitzBetterThanClosestAverage = record!.stats.score > closestAverageBlitz.value; blitzBetterThanClosestAverage = record!.stats.score > closestAverageBlitz.value;
} }
return LayoutBuilder( return RecordCard(record, [], record!.gamemode == "40l" ? sprintBetterThanRankAverage : blitzBetterThanRankAverage, record!.gamemode == "40l" ? closestAverageSprint : closestAverageBlitz, record!.gamemode == "40l" ? sprintBetterThanClosestAverage : blitzBetterThanClosestAverage, rank);
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)
)
]
),
),
);
}
);
} }
} }

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/icons/kagari.png", height: 128) // Btw why she wearing Kazamatsuri high school uniform?
: Image.asset("res/tetrio_tl_alpha_ranks/${tlData.rank}.png", height: 128), : Image.asset("res/tetrio_tl_alpha_ranks/${tlData.rank}.png", height: 128),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: bigScreen ? CrossAxisAlignment.start : CrossAxisAlignment.center,
children: [ children: [
RichText( RichText(
textAlign: bigScreen ? TextAlign.start : TextAlign.center,
text: TextSpan( text: TextSpan(
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 20, color: Colors.white, height: 0.9), style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 20, color: Colors.white, height: 0.9),
children: (tlData.gamesPlayed > 9) ? switch(prefs.getInt("ratingMode")){ children: (tlData.gamesPlayed > 9) ? switch(prefs.getInt("ratingMode")){
@ -125,7 +126,7 @@ class TLRatingThingy extends StatelessWidget{
], ],
), ),
if (showPositions == true) RichText( if (showPositions == true) RichText(
textAlign: TextAlign.start, textAlign: bigScreen ? TextAlign.start : TextAlign.center,
text: TextSpan( text: TextSpan(
text: "", text: "",
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey), 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 Cutoffs? cutoffs;
final CutoffTetrio? averages; final CutoffTetrio? averages;
final PlayerLeaderboardPosition? lbPos; 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 @override
Widget build(BuildContext context) { 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, 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( Row(
// spacing: 25.0,
// alignment: WrapAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Expanded( 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)), 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 (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 (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), 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),
Expanded( if (width > 400) Expanded(
child: Center( child: Center(
child: Table( child: Table(
defaultVerticalAlignment: TableCellVerticalAlignment.baseline, defaultVerticalAlignment: TableCellVerticalAlignment.baseline,
textBaseline: TextBaseline.alphabetic, textBaseline: TextBaseline.alphabetic,
defaultColumnWidth:const IntrinsicColumnWidth(), defaultColumnWidth:const IntrinsicColumnWidth(),
children: [ children: secondColumn(),
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))
]),
],
), ),
), ),
), ),

View File

@ -9,8 +9,51 @@ import 'package:tetra_stats/widgets/text_timestamp.dart';
class ZenithThingy extends StatelessWidget{ class ZenithThingy extends StatelessWidget{
final RecordSingle? zenith; final RecordSingle? zenith;
final bool old; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -68,30 +111,22 @@ class ZenithThingy extends StatelessWidget{
TableRow(children: [ TableRow(children: [
Text(f2.format(zenith!.aggregateStats.vs), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), Text(f2.format(zenith!.aggregateStats.vs), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)),
const Text(" VS", style: 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), 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),
Expanded( if (width > 400) Expanded(
child: Center( child: Center(
child: Table( child: Table(
defaultColumnWidth:const IntrinsicColumnWidth(), defaultColumnWidth:const IntrinsicColumnWidth(),
children: [ children: secondColumn(),
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))
])
],
), ),
), ),
) )
@ -114,30 +149,21 @@ class ZenithThingy extends StatelessWidget{
const TableRow(children: [ const TableRow(children: [
Text("-.--", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: Colors.grey)), Text("-.--", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: Colors.grey)),
Text(" VS", 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), if (width > 600) GaugetThingy(value: null, min: 0, max: 12, tickInterval: 3, label: "Climb\nSpeed", subString: "Peak: ---", sideSize: 128, fractionDigits: 0, moreIsBetter: true),
Expanded( if (width > 400) Expanded(
child: Center( child: Center(
child: Table( child: Table(
defaultColumnWidth: IntrinsicColumnWidth(), defaultColumnWidth: IntrinsicColumnWidth(),
children: [ children: noRecordSecondColumn(),
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))
])
],
), ),
), ),
) )

View File

@ -278,6 +278,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.13.1" 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: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -645,6 +653,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
quiver:
dependency: transitive
description:
name: quiver
sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2
url: "https://pub.dev"
source: hosted
version: "3.2.2"
screen_retriever: screen_retriever:
dependency: transitive dependency: transitive
description: description:

View File

@ -43,6 +43,7 @@ dependencies:
window_manager: ^0.3.7 window_manager: ^0.3.7
flutter_markdown: ^0.6.18 flutter_markdown: ^0.6.18
flutter_colorpicker: ^1.0.3 flutter_colorpicker: ^1.0.3
flutter_layout_grid: ^2.0.0
go_router: ^13.0.0 go_router: ^13.0.0
syncfusion_flutter_charts: ^24.2.9 syncfusion_flutter_charts: ^24.2.9