2024-01-01 17:26:09 +00:00
// ignore_for_file: type_literal_in_constant_pattern
2023-06-26 17:13:53 +00:00
import ' dart:io ' ;
2023-07-20 20:56:00 +00:00
import ' package:flutter/foundation.dart ' ;
2023-06-17 21:50:52 +00:00
import ' package:flutter/material.dart ' ;
2023-08-21 15:39:04 +00:00
import ' package:flutter_svg/flutter_svg.dart ' ;
2023-09-23 19:09:36 +00:00
import ' package:http/http.dart ' ;
2023-06-17 21:50:52 +00:00
import ' package:intl/intl.dart ' ;
2023-06-23 18:38:15 +00:00
import ' dart:math ' ;
2023-06-22 19:02:49 +00:00
import ' package:fl_chart/fl_chart.dart ' ;
2023-06-17 21:50:52 +00:00
import ' package:shared_preferences/shared_preferences.dart ' ;
import ' package:flutter/services.dart ' ;
import ' package:tetra_stats/data_objects/tetrio.dart ' ;
2023-07-10 17:42:20 +00:00
import ' package:tetra_stats/gen/strings.g.dart ' ;
2023-06-17 21:50:52 +00:00
import ' package:tetra_stats/services/tetrio_crud.dart ' ;
2023-10-26 22:38:03 +00:00
import ' package:tetra_stats/main.dart ' show prefs ;
2023-06-17 21:50:52 +00:00
import ' package:tetra_stats/services/crud_exceptions.dart ' ;
2023-10-08 17:20:42 +00:00
import ' package:tetra_stats/views/ranks_averages_view.dart ' show RankAveragesView ;
2023-07-07 20:32:57 +00:00
import ' package:tetra_stats/views/tl_leaderboard_view.dart ' show TLLeaderboardView ;
2023-06-21 19:17:39 +00:00
import ' package:tetra_stats/views/tl_match_view.dart ' show TlMatchResultView ;
2023-06-17 21:50:52 +00:00
import ' package:tetra_stats/widgets/stat_sell_num.dart ' ;
import ' package:tetra_stats/widgets/tl_thingy.dart ' ;
import ' package:tetra_stats/widgets/user_thingy.dart ' ;
2023-10-08 17:20:42 +00:00
import ' package:window_manager/window_manager.dart ' ;
import ' package:flutter_markdown/flutter_markdown.dart ' ;
2023-12-26 22:02:47 +00:00
import ' package:go_router/go_router.dart ' ;
2023-06-17 21:50:52 +00:00
2023-07-20 20:56:00 +00:00
Future < List > me = Future . delayed ( const Duration ( seconds: 60 ) , ( ) = > [ null , null , null , null , null , null ] ) ;
2023-07-17 17:57:24 +00:00
String _searchFor = " 6098518e3d5155e6ec429cdc " ;
2023-06-21 22:05:14 +00:00
String _titleNickname = " dan63047 " ;
2023-06-17 21:50:52 +00:00
final TetrioService teto = TetrioService ( ) ;
2023-06-26 17:13:53 +00:00
var chartsData = < DropdownMenuItem < List < FlSpot > > > [ ] ;
2023-10-26 22:38:03 +00:00
List _historyShortTitles = [ " TR " , " Glicko " , " RD " , " APM " , " PPS " , " VS " , " APP " , " DS/S " , " DS/P " , " APP + DS/P " , " VS/APM " , " Cheese " , " GbE " , " wAPP " , " Area " , " eTR " , " ±eTR " ] ;
int _chartsIndex = 0 ;
final NumberFormat _timeInSec = NumberFormat ( " #,###.###s. " ) ;
final NumberFormat _secs = NumberFormat ( " 00.### " ) ;
final NumberFormat _f2 = NumberFormat . decimalPatternDigits ( locale: LocaleSettings . currentLocale . languageCode , decimalDigits: 2 ) ;
final NumberFormat _f4 = NumberFormat . decimalPatternDigits ( locale: LocaleSettings . currentLocale . languageCode , decimalDigits: 4 ) ;
final DateFormat _dateFormat = DateFormat . yMMMd ( LocaleSettings . currentLocale . languageCode ) . add_Hms ( ) ;
2023-06-17 21:50:52 +00:00
class MainView extends StatefulWidget {
2023-07-09 16:50:17 +00:00
final String ? player ;
const MainView ( { Key ? key , this . player } ) : super ( key: key ) ;
2023-06-17 21:50:52 +00:00
2023-06-21 22:05:14 +00:00
String get title = > " Tetra Stats: $ _titleNickname " ;
2023-06-17 21:50:52 +00:00
@ override
State < MainView > createState ( ) = > _MainState ( ) ;
}
Future < void > copyToClipboard ( String text ) async {
await Clipboard . setData ( ClipboardData ( text: text ) ) ;
}
2023-10-07 16:44:54 +00:00
String get40lTime ( int microseconds ) {
2024-01-01 17:26:09 +00:00
return microseconds > 60000000 ? " ${ ( microseconds / 1000000 / 60 ) . floor ( ) } : ${ ( _secs . format ( microseconds / 1000000 % 60 ) ) } " : _timeInSec . format ( microseconds / 1000000 ) ;
2023-10-07 16:44:54 +00:00
}
2023-06-17 21:50:52 +00:00
class _MainState extends State < MainView > with SingleTickerProviderStateMixin {
final bodyGlobalKey = GlobalKey ( ) ;
bool _searchBoolean = false ;
late TabController _tabController ;
late ScrollController _scrollController ;
late bool fixedScroll ;
Widget _searchTextField ( ) {
return TextField (
maxLength: 25 ,
autocorrect: false ,
enableSuggestions: false ,
decoration: const InputDecoration ( counter: Offstage ( ) ) ,
style: const TextStyle (
shadows: < Shadow > [
Shadow (
offset: Offset ( 0.0 , 0.0 ) ,
blurRadius: 3.0 ,
color: Colors . black ,
) ,
Shadow (
offset: Offset ( 0.0 , 0.0 ) ,
blurRadius: 8.0 ,
color: Colors . black ,
) ,
] ,
) ,
onSubmitted: ( String value ) {
changePlayer ( value ) ;
} ,
) ;
}
@ override
void initState ( ) {
2023-07-22 12:07:57 +00:00
initDB ( ) ;
2023-06-17 21:50:52 +00:00
_scrollController = ScrollController ( ) ;
2023-06-21 22:05:14 +00:00
_tabController = TabController ( length: 6 , vsync: this ) ;
2023-07-09 16:50:17 +00:00
if ( widget . player ! = null ) {
changePlayer ( widget . player ! ) ;
} else {
_getPreferences ( )
2023-06-20 20:53:28 +00:00
. then ( ( value ) = > changePlayer ( prefs . getString ( " player " ) ? ? " dan63047 " ) ) ;
2023-07-09 16:50:17 +00:00
}
2023-06-17 21:50:52 +00:00
super . initState ( ) ;
}
@ override
void dispose ( ) {
_tabController . dispose ( ) ;
_scrollController . dispose ( ) ;
super . dispose ( ) ;
}
Future < void > _getPreferences ( ) async {
prefs = await SharedPreferences . getInstance ( ) ;
}
2023-07-17 17:57:24 +00:00
void changePlayer ( String player , { bool fetchHistory = false } ) {
2023-06-17 21:50:52 +00:00
setState ( ( ) {
_searchFor = player ;
2023-07-17 17:57:24 +00:00
me = fetch ( _searchFor , fetchHistory: fetchHistory ) ;
2023-06-17 21:50:52 +00:00
} ) ;
}
2023-07-22 12:07:57 +00:00
void initDB ( ) async {
await teto . open ( ) ;
}
2023-07-17 17:57:24 +00:00
Future < List > fetch ( String nickOrID , { bool fetchHistory = false } ) async {
2023-07-29 18:01:49 +00:00
TetrioPlayer me ;
if ( nickOrID . startsWith ( " ds: " ) ) {
me = await teto . fetchPlayer ( nickOrID . substring ( 3 ) , isItDiscordID: true ) ;
} else {
me = await teto . fetchPlayer ( nickOrID ) ;
}
2023-07-17 17:57:24 +00:00
_searchFor = me . userId ;
2023-06-21 22:05:14 +00:00
setState ( ( ) { _titleNickname = me . username ; } ) ;
2023-10-10 20:20:27 +00:00
if ( ! kIsWeb & & ! Platform . isAndroid & & ! Platform . isIOS ) await windowManager . setTitle ( widget . title ) ;
2023-10-08 17:20:42 +00:00
late List < dynamic > requests ;
late TetraLeagueAlphaStream tlStream ;
late Map < String , dynamic > records ;
late List < News > news ;
late double ? topTR ;
if ( me . tlSeason1 . gamesPlayed > 9 ) {
requests = await Future . wait ( [ teto . getTLStream ( _searchFor ) , teto . fetchRecords ( _searchFor ) , teto . fetchNews ( _searchFor ) , teto . fetchTopTR ( _searchFor ) ] ) ;
tlStream = requests [ 0 ] as TetraLeagueAlphaStream ;
records = requests [ 1 ] as Map < String , dynamic > ;
news = requests [ 2 ] as List < News > ;
topTR = requests [ 3 ] as double ? ;
} else {
requests = await Future . wait ( [ teto . getTLStream ( _searchFor ) , teto . fetchRecords ( _searchFor ) , teto . fetchNews ( _searchFor ) ] ) ;
tlStream = requests [ 0 ] as TetraLeagueAlphaStream ;
records = requests [ 1 ] as Map < String , dynamic > ;
news = requests [ 2 ] as List < News > ;
topTR = null ;
}
2023-06-26 17:13:53 +00:00
List < TetraLeagueAlphaRecord > tlMatches = [ ] ;
2023-06-22 19:02:49 +00:00
bool isTracking = await teto . isPlayerTracking ( me . userId ) ;
List < TetrioPlayer > states = [ ] ;
2023-06-29 22:41:11 +00:00
TetraLeagueAlpha ? compareWith ;
var uniqueTL = < dynamic > { } ;
2023-10-18 21:50:41 +00:00
tlMatches = tlStream . records ;
2023-07-22 12:07:57 +00:00
if ( isTracking ) {
await teto . storeState ( me ) ;
2023-10-07 16:44:54 +00:00
await teto . saveTLMatchesFromStream ( tlStream ) ;
2023-10-18 21:50:41 +00:00
var storedRecords = await teto . getTLMatchesbyPlayerID ( me . userId ) ;
for ( var match in storedRecords ) {
2023-07-22 12:07:57 +00:00
if ( ! tlMatches . contains ( match ) ) tlMatches . add ( match ) ;
}
tlMatches . sort ( ( a , b ) {
if ( a . timestamp . isBefore ( b . timestamp ) ) return 1 ;
if ( a . timestamp . isAtSameMomentAs ( b . timestamp ) ) return 0 ;
if ( a . timestamp . isAfter ( b . timestamp ) ) return - 1 ;
return 0 ;
} ) ;
}
2023-07-17 17:57:24 +00:00
if ( fetchHistory ) await teto . fetchAndsaveTLHistory ( _searchFor ) ;
2023-07-20 20:56:00 +00:00
states . addAll ( await teto . getPlayer ( me . userId ) ) ;
2023-07-17 17:57:24 +00:00
for ( var element in states ) {
2023-06-28 16:50:40 +00:00
if ( uniqueTL . isNotEmpty & & uniqueTL . last ! = element . tlSeason1 ) uniqueTL . add ( element . tlSeason1 ) ;
if ( uniqueTL . isEmpty ) uniqueTL . add ( element . tlSeason1 ) ;
2023-06-29 22:41:11 +00:00
}
try {
compareWith = uniqueTL . toList ( ) [ uniqueTL . length - 2 ] ;
} on RangeError {
compareWith = null ;
}
2023-06-26 17:13:53 +00:00
chartsData = < DropdownMenuItem < List < FlSpot > > > [
2023-07-15 16:22:25 +00:00
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . gamesPlayed > 9 ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . rating ) ] , child: Text ( t . statCellNum . tr ) ) ,
2023-06-28 16:50:40 +00:00
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . gamesPlayed > 9 ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . glicko ! ) ] , child: const Text ( " Glicko " ) ) ,
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . gamesPlayed > 9 ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . rd ! ) ] , child: const Text ( " Rating Deviation " ) ) ,
2023-07-15 16:22:25 +00:00
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . apm ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . apm ! ) ] , child: Text ( t . statCellNum . apm . replaceAll ( RegExp ( r'\n' ) , " " ) ) ) ,
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . pps ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . pps ! ) ] , child: Text ( t . statCellNum . pps . replaceAll ( RegExp ( r'\n' ) , " " ) ) ) ,
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . vs ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . vs ! ) ] , child: Text ( t . statCellNum . vs . replaceAll ( RegExp ( r'\n' ) , " " ) ) ) ,
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . nerdStats ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . nerdStats ! . app ) ] , child: Text ( t . statCellNum . app . replaceAll ( RegExp ( r'\n' ) , " " ) ) ) ,
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . nerdStats ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . nerdStats ! . dss ) ] , child: Text ( t . statCellNum . dss . replaceAll ( RegExp ( r'\n' ) , " " ) ) ) ,
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . nerdStats ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . nerdStats ! . dsp ) ] , child: Text ( t . statCellNum . dsp . replaceAll ( RegExp ( r'\n' ) , " " ) ) ) ,
2023-06-28 16:50:40 +00:00
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . nerdStats ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . nerdStats ! . appdsp ) ] , child: const Text ( " APP + DS/P " ) ) ,
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . nerdStats ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . nerdStats ! . vsapm ) ] , child: const Text ( " VS/APM " ) ) ,
2023-07-15 16:22:25 +00:00
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . nerdStats ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . nerdStats ! . cheese ) ] , child: Text ( t . statCellNum . cheese . replaceAll ( RegExp ( r'\n' ) , " " ) ) ) ,
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . nerdStats ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . nerdStats ! . gbe ) ] , child: Text ( t . statCellNum . gbe . replaceAll ( RegExp ( r'\n' ) , " " ) ) ) ,
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . nerdStats ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . nerdStats ! . nyaapp ) ] , child: Text ( t . statCellNum . nyaapp . replaceAll ( RegExp ( r'\n' ) , " " ) ) ) ,
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . nerdStats ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . nerdStats ! . area ) ] , child: Text ( t . statCellNum . area . replaceAll ( RegExp ( r'\n' ) , " " ) ) ) ,
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . estTr ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . estTr ! . esttr ) ] , child: Text ( t . statCellNum . estOfTR . replaceAll ( RegExp ( r'\n' ) , " " ) ) ) ,
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . esttracc ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . esttracc ! ) ] , child: Text ( t . statCellNum . accOfEst . replaceAll ( RegExp ( r'\n' ) , " " ) ) ) ,
2023-06-26 17:13:53 +00:00
] ;
2023-10-08 17:20:42 +00:00
return [ me , records , states , tlMatches , compareWith , isTracking , news , topTR ] ;
2023-06-23 18:38:15 +00:00
}
2023-06-17 21:50:52 +00:00
void _justUpdate ( ) {
setState ( ( ) { } ) ;
}
@ override
Widget build ( BuildContext context ) {
2023-07-10 17:42:20 +00:00
final t = Translations . of ( context ) ;
2023-06-17 21:50:52 +00:00
return Scaffold (
2023-07-09 16:50:17 +00:00
drawer: widget . player = = null ? NavDrawer ( changePlayer ) : null ,
2023-06-17 21:50:52 +00:00
appBar: AppBar (
title: ! _searchBoolean
? Text (
widget . title ,
style: const TextStyle (
shadows: < Shadow > [
Shadow (
offset: Offset ( 0.0 , 0.0 ) ,
blurRadius: 3.0 ,
color: Colors . black ,
) ,
Shadow (
offset: Offset ( 0.0 , 0.0 ) ,
blurRadius: 8.0 ,
color: Colors . black ,
) ,
] ,
) ,
)
: _searchTextField ( ) ,
backgroundColor: Colors . black ,
2023-07-11 17:02:35 +00:00
actions: widget . player = = null ? [
2023-06-17 21:50:52 +00:00
! _searchBoolean
? IconButton (
onPressed: ( ) {
setState ( ( ) {
_searchBoolean = true ;
} ) ;
} ,
icon: const Icon ( Icons . search ) ,
2023-07-15 16:22:25 +00:00
tooltip: t . openSearch ,
2023-06-17 21:50:52 +00:00
)
: IconButton (
onPressed: ( ) {
setState ( ( ) {
_searchBoolean = false ;
} ) ;
} ,
icon: const Icon ( Icons . clear ) ,
2023-07-15 16:22:25 +00:00
tooltip: t . closeSearch ,
2023-06-17 21:50:52 +00:00
) ,
PopupMenuButton (
itemBuilder: ( BuildContext context ) = > < PopupMenuEntry > [
2023-07-10 17:42:20 +00:00
PopupMenuItem (
2023-07-09 16:50:17 +00:00
value: " refresh " ,
2023-07-10 17:42:20 +00:00
child: Text ( t . refresh ) ,
2023-07-09 16:50:17 +00:00
) ,
2023-07-20 20:56:00 +00:00
PopupMenuItem (
2023-12-26 22:02:47 +00:00
value: " history " ,
2023-07-20 20:56:00 +00:00
child: Text ( t . fetchAndsaveTLHistory ) ,
) ,
2023-07-10 17:42:20 +00:00
PopupMenuItem (
2023-06-17 21:50:52 +00:00
value: " /states " ,
2023-07-10 17:42:20 +00:00
child: Text ( t . showStoredData ) ,
2023-06-17 21:50:52 +00:00
) ,
2023-07-10 17:42:20 +00:00
PopupMenuItem (
2023-06-17 21:50:52 +00:00
value: " /calc " ,
2023-07-10 17:42:20 +00:00
child: Text ( t . statsCalc ) ,
2023-06-17 21:50:52 +00:00
) ,
2023-07-10 17:42:20 +00:00
PopupMenuItem (
2023-06-17 21:50:52 +00:00
value: " /settings " ,
2023-07-10 17:42:20 +00:00
child: Text ( t . settings ) ,
2023-06-17 21:50:52 +00:00
) ,
] ,
onSelected: ( value ) {
2023-07-09 16:50:17 +00:00
if ( value = = " refresh " ) { changePlayer ( _searchFor ) ;
2023-07-07 20:32:57 +00:00
return ; }
2023-12-26 22:02:47 +00:00
if ( value = = " history " ) { changePlayer ( _searchFor , fetchHistory: true ) ;
2023-07-17 17:57:24 +00:00
return ; }
2023-12-26 22:02:47 +00:00
//Navigator.pushNamed(context, value);
context . go ( value ) ;
2023-06-17 21:50:52 +00:00
} ,
) ,
2023-07-11 17:02:35 +00:00
] : null ,
2023-06-17 21:50:52 +00:00
) ,
body: SafeArea (
2023-06-21 22:05:14 +00:00
child: FutureBuilder < List < dynamic > > (
2023-06-17 21:50:52 +00:00
future: me ,
builder: ( context , snapshot ) {
switch ( snapshot . connectionState ) {
case ConnectionState . none:
case ConnectionState . waiting:
case ConnectionState . active:
2023-07-10 17:42:20 +00:00
return const Center ( child: CircularProgressIndicator ( color: Colors . white ) ) ;
2023-06-17 21:50:52 +00:00
case ConnectionState . done:
//bool bigScreen = MediaQuery.of(context).size.width > 1024;
if ( snapshot . hasData ) {
2023-06-26 17:13:53 +00:00
return RefreshIndicator (
onRefresh: ( ) {
return Future ( ( ) = > changePlayer ( snapshot . data ! [ 0 ] . userId ) ) ;
2023-06-17 21:50:52 +00:00
} ,
2023-10-25 23:00:47 +00:00
// notificationPredicate: (notification) {
// // with NestedScrollView local(depth == 2) OverscrollNotification are not sent
// if (!kIsWeb && (notification is OverscrollNotification || Platform.isIOS)) {
// return notification.depth == 2;
// }
// return notification.depth == 0;
// },
2023-06-26 17:13:53 +00:00
child: NestedScrollView (
controller: _scrollController ,
physics: const AlwaysScrollableScrollPhysics ( ) ,
headerSliverBuilder: ( context , value ) {
return [
SliverToBoxAdapter (
child: UserThingy (
player: snapshot . data ! [ 0 ] ,
showStateTimestamp: false ,
setState: _justUpdate ,
) ) ,
SliverToBoxAdapter (
child: TabBar (
controller: _tabController ,
isScrollable: true ,
2023-07-10 17:42:20 +00:00
tabs: [
Tab ( text: t . tetraLeague ) ,
Tab ( text: t . tlRecords ) ,
Tab ( text: t . history ) ,
Tab ( text: t . sprint ) ,
Tab ( text: t . blitz ) ,
Tab ( text: t . other ) ,
] ,
2023-06-26 17:13:53 +00:00
) ,
) ,
] ;
} ,
body: TabBarView (
controller: _tabController ,
children: [
2024-01-01 17:26:09 +00:00
TLThingy ( tl: snapshot . data ! [ 0 ] . tlSeason1 , userID: snapshot . data ! [ 0 ] . userId , states: snapshot . data ! [ 2 ] , topTR: snapshot . data ! [ 7 ] , bot: snapshot . data ! [ 0 ] . role = = " bot " ) ,
2023-06-26 17:13:53 +00:00
_TLRecords ( userID: snapshot . data ! [ 0 ] . userId , data: snapshot . data ! [ 3 ] ) ,
_History ( states: snapshot . data ! [ 2 ] , update: _justUpdate ) ,
2023-10-08 17:20:42 +00:00
_RecordThingy ( record: ( snapshot . data ! [ 1 ] [ ' sprint ' ] . isNotEmpty ) ? snapshot . data ! [ 1 ] [ ' sprint ' ] [ 0 ] : null ) ,
_RecordThingy ( record: ( snapshot . data ! [ 1 ] [ ' blitz ' ] . isNotEmpty ) ? snapshot . data ! [ 1 ] [ ' blitz ' ] [ 0 ] : null ) ,
_OtherThingy ( zen: snapshot . data ! [ 1 ] [ ' zen ' ] , bio: snapshot . data ! [ 0 ] . bio , distinguishment: snapshot . data ! [ 0 ] . distinguishment , newsletter: snapshot . data ! [ 6 ] , )
2023-06-26 17:13:53 +00:00
] ,
) ,
2023-06-17 21:50:52 +00:00
) ,
) ;
} else if ( snapshot . hasError ) {
2023-07-11 17:02:35 +00:00
String errText = " " ;
switch ( snapshot . error . runtimeType ) {
case TetrioPlayerNotExist:
errText = t . errors . noSuchUser ;
break ;
case ConnectionIssue:
var err = snapshot . error as ConnectionIssue ;
errText = t . errors . connection ( code: err . code , message: err . message ) ;
break ;
2023-09-23 19:09:36 +00:00
case P1nkl0bst3rForbidden:
errText = t . errors . p1nkl0bst3rForbidden ;
break ;
case P1nkl0bst3rTooManyRequests:
errText = t . errors . p1nkl0bst3rTooManyRequests ;
break ;
case P1nkl0bst3rInternalProblem:
errText = kIsWeb ? t . errors . p1nkl0bst3rinternalWebVersion : t . errors . p1nkl0bst3rinternal ;
break ;
case TetrioHistoryNotExist:
errText = t . errors . history ;
break ;
case TetrioForbidden:
errText = t . errors . forbidden ;
break ;
case TetrioTooManyRequests:
errText = t . errors . tooManyRequests ;
break ;
case TetrioOskwareBridgeProblem:
errText = t . errors . oskwareBridge ;
break ;
case TetrioInternalProblem:
errText = kIsWeb ? t . errors . internalWebVersion : t . errors . internal ;
break ;
case ClientException:
errText = t . errors . clientException ;
2023-07-11 17:02:35 +00:00
break ;
default :
errText = snapshot . error . toString ( ) ;
}
2023-09-23 19:09:36 +00:00
return Center ( child: Text ( errText , style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 42 , fontWeight: FontWeight . bold ) , textAlign: TextAlign . center ) ) ;
2023-06-17 21:50:52 +00:00
}
break ;
}
2023-10-09 18:48:50 +00:00
return const Center ( child: Text ( ' default case of FutureBuilder ' , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 42 ) , textAlign: TextAlign . center ) ) ;
2023-06-17 21:50:52 +00:00
} ,
) ,
) ,
) ;
}
}
class NavDrawer extends StatefulWidget {
final Function changePlayer ;
const NavDrawer ( this . changePlayer , { super . key } ) ;
@ override
State < NavDrawer > createState ( ) = > _NavDrawerState ( ) ;
}
class _NavDrawerState extends State < NavDrawer > {
late ScrollController _scrollController ;
String homePlayerNickname = " Checking... " ;
@ override
void initState ( ) {
super . initState ( ) ;
_setHomePlayerNickname ( prefs . getString ( " player " ) ) ;
_scrollController = ScrollController ( ) ;
}
@ override
void dispose ( ) {
_scrollController . dispose ( ) ;
super . dispose ( ) ;
}
Future < void > _setHomePlayerNickname ( String ? n ) async {
if ( n ! = null ) {
try {
homePlayerNickname = await teto . getNicknameByID ( n ) ;
} on TetrioPlayerNotExist {
homePlayerNickname = n ;
}
} else {
homePlayerNickname = " dan63047 " ;
}
setState ( ( ) { } ) ;
}
@ override
Widget build ( BuildContext context ) {
return Drawer (
child: StreamBuilder (
stream: teto . allPlayers ,
builder: ( context , snapshot ) {
switch ( snapshot . connectionState ) {
case ConnectionState . none:
case ConnectionState . waiting:
case ConnectionState . active:
2023-06-20 20:53:28 +00:00
final allPlayers = ( snapshot . data ! = null )
? snapshot . data as Map < String , List < TetrioPlayer > >
: < String , List < TetrioPlayer > > { } ;
2023-10-08 17:20:42 +00:00
List < String > keys = allPlayers . keys . toList ( ) ;
2023-06-17 21:50:52 +00:00
return NestedScrollView (
headerSliverBuilder: ( context , value ) {
return [
2023-07-11 17:02:35 +00:00
SliverToBoxAdapter (
2023-06-17 21:50:52 +00:00
child: DrawerHeader (
2023-07-11 17:02:35 +00:00
child: Text ( t . playersYouTrack , style: const TextStyle ( color: Colors . white , fontSize: 25 ) ,
2023-06-17 21:50:52 +00:00
) ) ) ,
SliverToBoxAdapter (
child: ListTile (
leading: const Icon ( Icons . home ) ,
title: Text ( homePlayerNickname ) ,
onTap: ( ) {
2023-06-20 20:53:28 +00:00
widget . changePlayer (
prefs . getString ( " player " ) ? ? " dan63047 " ) ;
2023-06-17 21:50:52 +00:00
Navigator . of ( context ) . pop ( ) ;
} ,
) ,
2023-07-07 20:32:57 +00:00
) ,
SliverToBoxAdapter (
child: ListTile (
leading: const Icon ( Icons . leaderboard ) ,
2023-07-10 17:42:20 +00:00
title: Text ( t . tlLeaderboard ) ,
2023-07-07 20:32:57 +00:00
onTap: ( ) {
Navigator . push (
context ,
MaterialPageRoute (
builder: ( context ) = > const TLLeaderboardView ( ) ,
) ,
) ;
} ,
) ,
2023-07-11 17:02:35 +00:00
) ,
2023-09-04 18:00:13 +00:00
SliverToBoxAdapter (
child: ListTile (
leading: const Icon ( Icons . compress ) ,
title: Text ( t . rankAveragesViewTitle ) ,
onTap: ( ) {
Navigator . push (
context ,
MaterialPageRoute (
builder: ( context ) = > const RankAveragesView ( ) ,
) ,
) ;
} ,
) ,
) ,
2023-07-11 17:02:35 +00:00
const SliverToBoxAdapter ( child: Divider ( ) )
2023-06-17 21:50:52 +00:00
] ;
} ,
body: ListView . builder (
itemCount: allPlayers . length ,
itemBuilder: ( context , index ) {
return ListTile (
2023-06-20 20:53:28 +00:00
title: Text (
2023-10-08 17:20:42 +00:00
allPlayers [ keys [ allPlayers . length - 1 - index ] ] ? . last . username as String ) ,
2023-06-17 21:50:52 +00:00
onTap: ( ) {
2023-10-08 17:20:42 +00:00
widget . changePlayer ( keys [ allPlayers . length - 1 - index ] ) ;
2023-06-17 21:50:52 +00:00
Navigator . of ( context ) . pop ( ) ;
} ,
) ;
} ) ) ;
case ConnectionState . done:
return const Center ( child: Text ( ' done case of StreamBuilder ' ) ) ;
}
} ,
) ,
) ;
}
}
2023-06-20 20:53:28 +00:00
class _TLRecords extends StatelessWidget {
final String userID ;
2023-06-26 17:13:53 +00:00
final List < TetraLeagueAlphaRecord > data ;
const _TLRecords ( { required this . userID , required this . data } ) ;
2023-06-20 20:53:28 +00:00
@ override
Widget build ( BuildContext context ) {
2023-09-05 20:32:34 +00:00
bool bigScreen = MediaQuery . of ( context ) . size . width > 768 ;
2023-06-26 17:13:53 +00:00
return ListView ( // TODO: Redo using ListView.builder()
physics: const AlwaysScrollableScrollPhysics ( ) ,
children: ( data . isNotEmpty )
? [ for ( var value in data ) ListTile (
leading: Text ( " ${ value . endContext . firstWhere ( ( element ) = > element . userId = = userID ) . points } : ${ value . endContext . firstWhere ( ( element ) = > element . userId ! = userID ) . points } " ,
2023-09-05 20:32:34 +00:00
style: bigScreen ? const TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 28 ) :
const TextStyle ( fontSize: 28 ) ) ,
2023-06-26 17:13:53 +00:00
title: Text ( " vs. ${ value . endContext . firstWhere ( ( element ) = > element . userId ! = userID ) . username } " ) ,
2023-10-26 22:38:03 +00:00
subtitle: Text ( _dateFormat . format ( value . timestamp ) ) ,
2023-09-04 18:00:13 +00:00
trailing: Table ( defaultColumnWidth: const IntrinsicColumnWidth ( ) ,
2023-09-02 22:48:50 +00:00
defaultVerticalAlignment: TableCellVerticalAlignment . baseline ,
textBaseline: TextBaseline . alphabetic ,
2023-09-04 18:00:13 +00:00
columnWidths: const {
2023-09-02 22:48:50 +00:00
0 : FixedColumnWidth ( 50 ) ,
2 : FixedColumnWidth ( 50 ) ,
} ,
2023-06-26 17:13:53 +00:00
children: [
2023-10-26 22:38:03 +00:00
TableRow ( children: [ Text ( _f2 . format ( value . endContext . firstWhere ( ( element ) = > element . userId = = userID ) . secondary ) , textAlign: TextAlign . right , style: const TextStyle ( height: 1.1 ) ) , const Text ( " : " , style: TextStyle ( height: 1.1 ) ) , Text ( _f2 . format ( value . endContext . firstWhere ( ( element ) = > element . userId ! = userID ) . secondary ) , textAlign: TextAlign . right , style: const TextStyle ( height: 1.1 ) ) , const Text ( " APM " , textAlign: TextAlign . right , style: TextStyle ( height: 1.1 ) ) ] ) ,
TableRow ( children: [ Text ( _f2 . format ( value . endContext . firstWhere ( ( element ) = > element . userId = = userID ) . tertiary ) , textAlign: TextAlign . right , style: const TextStyle ( height: 1.1 ) ) , const Text ( " : " , style: TextStyle ( height: 1.1 ) ) , Text ( _f2 . format ( value . endContext . firstWhere ( ( element ) = > element . userId ! = userID ) . tertiary ) , textAlign: TextAlign . right , style: const TextStyle ( height: 1.1 ) ) , const Text ( " PPS " , textAlign: TextAlign . right , style: TextStyle ( height: 1.1 ) ) ] ) ,
TableRow ( children: [ Text ( _f2 . format ( value . endContext . firstWhere ( ( element ) = > element . userId = = userID ) . extra ) , textAlign: TextAlign . right , style: const TextStyle ( height: 1.1 ) ) , const Text ( " : " , style: TextStyle ( height: 1.1 ) ) , Text ( _f2 . format ( value . endContext . firstWhere ( ( element ) = > element . userId ! = userID ) . extra ) , textAlign: TextAlign . right , style: const TextStyle ( height: 1.1 ) ) , const Text ( " VS " , textAlign: TextAlign . right , style: TextStyle ( height: 1.1 ) ) ] ) ,
2023-09-02 22:48:50 +00:00
] , ) ,
2023-06-26 17:13:53 +00:00
onTap: ( ) { Navigator . push (
context ,
MaterialPageRoute (
builder: ( context ) = > TlMatchResultView ( record: value , initPlayerId: userID ) ,
) ,
) ; } ,
) ]
2024-01-01 17:26:09 +00:00
: [ Center ( child: Text ( t . noRecords , style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 28 ) ) ) ] ,
2023-06-26 17:13:53 +00:00
) ;
2023-06-20 20:53:28 +00:00
}
}
2023-06-26 17:13:53 +00:00
class _History extends StatelessWidget {
2023-06-22 19:02:49 +00:00
final List < TetrioPlayer > states ;
2023-06-26 17:13:53 +00:00
final Function update ;
const _History ( { required this . states , required this . update } ) ;
2023-06-22 19:02:49 +00:00
@ override
Widget build ( BuildContext context ) {
bool bigScreen = MediaQuery . of ( context ) . size . width > 768 ;
return ListView ( physics: const ClampingScrollPhysics ( ) ,
children: states . isNotEmpty ? [
Column (
children: [
2023-06-26 17:13:53 +00:00
DropdownButton (
items: chartsData ,
2023-10-26 22:38:03 +00:00
value: chartsData [ _chartsIndex ] . value ,
2023-06-26 17:13:53 +00:00
onChanged: ( value ) {
2023-10-26 22:38:03 +00:00
_chartsIndex = chartsData . indexWhere ( ( element ) = > element . value = = value ) ;
2023-06-26 17:13:53 +00:00
update ( ) ;
}
) ,
2023-10-26 22:38:03 +00:00
if ( chartsData [ _chartsIndex ] . value ! . length > 1 ) _HistoryChartThigy ( data: chartsData [ _chartsIndex ] . value ! , title: " ss " , yAxisTitle: _historyShortTitles [ _chartsIndex ] , bigScreen: bigScreen , leftSpace: bigScreen ? 80 : 45 , yFormat: bigScreen ? _f2 : NumberFormat . compact ( ) , )
2024-01-01 17:26:09 +00:00
else Center ( child: Text ( t . notEnoughData , style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 28 ) ) )
2023-06-22 19:02:49 +00:00
] ,
) ,
2024-01-01 17:26:09 +00:00
] : [ Center ( child: Text ( t . noHistorySaved , textAlign: TextAlign . center , style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 28 ) ) ) ] ) ;
2023-06-22 19:02:49 +00:00
}
}
class _HistoryChartThigy extends StatelessWidget {
final List < FlSpot > data ;
final String title ;
final String yAxisTitle ;
final bool bigScreen ;
2023-06-23 18:38:15 +00:00
final double leftSpace ;
final NumberFormat yFormat ;
const _HistoryChartThigy ( { required this . data , required this . title , required this . yAxisTitle , required this . bigScreen , required this . leftSpace , required this . yFormat } ) ;
2023-06-22 19:02:49 +00:00
@ override
2023-06-23 18:38:15 +00:00
Widget build ( BuildContext context ) {
double xInterval = bigScreen ? max ( 1 , ( data . last . x - data . first . x ) / 6 ) : max ( 1 , ( data . last . x - data . first . x ) / 3 ) ;
2023-06-26 17:13:53 +00:00
return SizedBox (
width: MediaQuery . of ( context ) . size . width ,
height: MediaQuery . of ( context ) . size . height - 100 ,
2023-06-22 19:02:49 +00:00
child: Stack (
children: [
2023-07-09 16:50:17 +00:00
Padding ( padding: bigScreen ? const EdgeInsets . fromLTRB ( 40 , 40 , 40 , 48 ) : const EdgeInsets . fromLTRB ( 0 , 40 , 16 , 48 ) ,
2023-06-22 19:02:49 +00:00
child: LineChart (
LineChartData (
lineBarsData: [ LineChartBarData ( spots: data ) ] ,
borderData: FlBorderData ( show: false ) ,
2023-06-23 18:38:15 +00:00
gridData: FlGridData ( verticalInterval: xInterval ) ,
2023-09-04 18:00:13 +00:00
titlesData: FlTitlesData ( topTitles: const AxisTitles ( sideTitles: SideTitles ( showTitles: false ) ) ,
rightTitles: const AxisTitles ( sideTitles: SideTitles ( showTitles: false ) ) ,
2023-06-23 18:38:15 +00:00
bottomTitles: AxisTitles ( sideTitles: SideTitles ( interval: xInterval , showTitles: true , reservedSize: 30 , getTitlesWidget: ( double value , TitleMeta meta ) {
return value ! = meta . min & & value ! = meta . max ? SideTitleWidget (
2023-06-22 19:02:49 +00:00
axisSide: meta . axisSide ,
2023-08-21 15:39:04 +00:00
child: Text ( DateFormat . yMMMd ( LocaleSettings . currentLocale . languageCode ) . format ( DateTime . fromMillisecondsSinceEpoch ( value . floor ( ) ) ) ) ,
2023-06-23 18:38:15 +00:00
) : Container ( ) ;
2023-06-22 19:02:49 +00:00
} ) ) ,
2023-06-23 18:38:15 +00:00
leftTitles: AxisTitles ( sideTitles: SideTitles ( showTitles: true , reservedSize: leftSpace , getTitlesWidget: ( double value , TitleMeta meta ) {
return value ! = meta . min & & value ! = meta . max ? SideTitleWidget (
2023-06-22 19:02:49 +00:00
axisSide: meta . axisSide ,
2023-06-23 18:38:15 +00:00
child: Text ( yFormat . format ( value ) ) ,
) : Container ( ) ;
2023-06-22 19:02:49 +00:00
} ) ) ) ,
2023-06-23 18:38:15 +00:00
lineTouchData: LineTouchData ( touchTooltipData: LineTouchTooltipData ( fitInsideHorizontally: true , fitInsideVertically: true , getTooltipItems: ( touchedSpots ) {
2023-10-26 22:38:03 +00:00
return [ for ( var v in touchedSpots ) LineTooltipItem ( " ${ _f4 . format ( v . y ) } $ yAxisTitle \n " , const TextStyle ( ) , children: [ TextSpan ( text: _dateFormat . format ( DateTime . fromMillisecondsSinceEpoch ( v . x . floor ( ) ) ) ) ] ) ] ;
2023-06-22 19:02:49 +00:00
} , ) )
)
) ,
) ,
] ,
)
) ;
}
}
2023-06-17 21:50:52 +00:00
class _RecordThingy extends StatelessWidget {
final RecordSingle ? record ;
const _RecordThingy ( { Key ? key , required this . record } ) : super ( key: key ) ;
@ override
Widget build ( BuildContext context ) {
return LayoutBuilder ( builder: ( context , constraints ) {
bool bigScreen = constraints . maxWidth > 768 ;
return ListView . builder (
2023-06-26 17:13:53 +00:00
physics: const AlwaysScrollableScrollPhysics ( ) ,
2023-06-17 21:50:52 +00:00
itemCount: 1 ,
itemBuilder: ( BuildContext context , int index ) {
return Column (
children: ( record ! = null )
? [
if ( record ! . stream . contains ( " 40l " ) )
2023-07-10 17:42:20 +00:00
Text ( t . sprint ,
2023-06-20 20:53:28 +00:00
style: TextStyle (
fontFamily: " Eurostile Round Extended " ,
fontSize: bigScreen ? 42 : 28 ) )
2023-06-17 21:50:52 +00:00
else if ( record ! . stream . contains ( " blitz " ) )
2023-07-10 17:42:20 +00:00
Text ( t . blitz ,
2023-06-20 20:53:28 +00:00
style: TextStyle (
fontFamily: " Eurostile Round Extended " ,
fontSize: bigScreen ? 42 : 28 ) ) ,
2023-06-17 21:50:52 +00:00
if ( record ! . stream . contains ( " 40l " ) )
2023-10-07 16:44:54 +00:00
Text ( get40lTime ( record ! . endContext ! . finalTime . inMicroseconds ) ,
2023-06-20 20:53:28 +00:00
style: TextStyle (
fontFamily: " Eurostile Round Extended " ,
fontSize: bigScreen ? 42 : 28 ) )
2023-06-17 21:50:52 +00:00
else if ( record ! . stream . contains ( " blitz " ) )
2023-06-20 20:53:28 +00:00
Text (
NumberFormat . decimalPattern ( )
. format ( record ! . endContext ! . score ) ,
style: TextStyle (
fontFamily: " Eurostile Round Extended " ,
fontSize: bigScreen ? 42 : 28 ) ) ,
if ( record ! . rank ! = null )
StatCellNum (
playerStat: record ! . rank ! ,
playerStatLabel: " Leaderboard Placement " ,
2023-06-28 16:50:40 +00:00
isScreenBig: bigScreen ,
higherIsBetter: false ) ,
2023-10-26 22:38:03 +00:00
Text ( t . obtainDate ( date: _dateFormat . format ( record ! . timestamp ! ) ) ,
2023-06-17 21:50:52 +00:00
textAlign: TextAlign . center ,
style: const TextStyle (
fontFamily: " Eurostile Round " ,
fontSize: 16 ,
) ) ,
Padding (
padding: const EdgeInsets . fromLTRB ( 0 , 48 , 0 , 48 ) ,
child: Wrap (
direction: Axis . horizontal ,
alignment: WrapAlignment . spaceAround ,
crossAxisAlignment: WrapCrossAlignment . start ,
clipBehavior: Clip . hardEdge ,
spacing: 25 ,
children: [
if ( record ! . stream . contains ( " blitz " ) )
2023-06-20 20:53:28 +00:00
StatCellNum (
playerStat: record ! . endContext ! . level ,
2023-07-10 17:42:20 +00:00
playerStatLabel: t . statCellNum . level ,
2023-06-28 16:50:40 +00:00
isScreenBig: bigScreen ,
higherIsBetter: true , ) ,
2023-06-17 21:50:52 +00:00
if ( record ! . stream . contains ( " blitz " ) )
2023-06-20 20:53:28 +00:00
StatCellNum (
playerStat: record ! . endContext ! . spp ,
2023-07-10 17:42:20 +00:00
playerStatLabel: t . statCellNum . spp ,
2023-06-20 20:53:28 +00:00
fractionDigits: 2 ,
2023-06-28 16:50:40 +00:00
isScreenBig: bigScreen ,
higherIsBetter: true , ) ,
2023-06-20 20:53:28 +00:00
StatCellNum (
playerStat: record ! . endContext ! . piecesPlaced ,
2023-07-10 17:42:20 +00:00
playerStatLabel: t . statCellNum . pieces ,
2023-06-28 16:50:40 +00:00
isScreenBig: bigScreen ,
higherIsBetter: true , ) ,
2023-06-20 20:53:28 +00:00
StatCellNum (
playerStat: record ! . endContext ! . pps ,
2023-07-10 17:42:20 +00:00
playerStatLabel: t . statCellNum . pps ,
2023-06-20 20:53:28 +00:00
fractionDigits: 2 ,
2023-06-28 16:50:40 +00:00
isScreenBig: bigScreen ,
higherIsBetter: true , ) ,
2023-06-20 20:53:28 +00:00
StatCellNum (
playerStat: record ! . endContext ! . finesse . faults ,
2023-07-10 17:42:20 +00:00
playerStatLabel: t . statCellNum . finesseFaults ,
2023-06-28 16:50:40 +00:00
isScreenBig: bigScreen ,
higherIsBetter: false , ) ,
2023-06-17 21:50:52 +00:00
StatCellNum (
2023-06-20 20:53:28 +00:00
playerStat:
record ! . endContext ! . finessePercentage * 100 ,
2023-07-10 17:42:20 +00:00
playerStatLabel: t . statCellNum . finessePercentage ,
2023-06-17 21:50:52 +00:00
fractionDigits: 2 ,
2023-06-28 16:50:40 +00:00
isScreenBig: bigScreen ,
higherIsBetter: true , ) ,
2023-06-20 20:53:28 +00:00
StatCellNum (
playerStat: record ! . endContext ! . inputs ,
2023-07-10 17:42:20 +00:00
playerStatLabel: t . statCellNum . keys ,
2023-06-28 16:50:40 +00:00
isScreenBig: bigScreen ,
higherIsBetter: false , ) ,
2023-06-20 20:53:28 +00:00
StatCellNum (
playerStat: record ! . endContext ! . kpp ,
2023-07-10 17:42:20 +00:00
playerStatLabel: t . statCellNum . kpp ,
2023-06-20 20:53:28 +00:00
fractionDigits: 2 ,
2023-06-28 16:50:40 +00:00
isScreenBig: bigScreen ,
higherIsBetter: false , ) ,
2023-06-20 20:53:28 +00:00
StatCellNum (
playerStat: record ! . endContext ! . kps ,
2023-07-10 17:42:20 +00:00
playerStatLabel: t . statCellNum . kps ,
2023-06-20 20:53:28 +00:00
fractionDigits: 2 ,
2023-06-28 16:50:40 +00:00
isScreenBig: bigScreen ,
higherIsBetter: true , ) ,
2023-06-17 21:50:52 +00:00
] ,
) ,
) ,
Padding (
padding: const EdgeInsets . fromLTRB ( 0 , 16 , 0 , 48 ) ,
child: SizedBox (
2023-06-20 20:53:28 +00:00
width: bigScreen
? MediaQuery . of ( context ) . size . width * 0.4
: MediaQuery . of ( context ) . size . width * 0.85 ,
2023-06-17 21:50:52 +00:00
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Row (
2023-09-23 19:09:36 +00:00
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-09-23 19:09:36 +00:00
Text ( " ${ t . numOfGameActions . pc } : " , style: const TextStyle ( fontSize: 24 ) ) ,
Text ( record ! . endContext ! . clears . allClears . toString ( ) , style: const TextStyle ( fontSize: 24 ) ) ,
2023-06-17 21:50:52 +00:00
] ,
) ,
Row (
2023-09-23 19:09:36 +00:00
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-09-23 19:09:36 +00:00
Text ( " ${ t . numOfGameActions . hold } : " , style: const TextStyle ( fontSize: 24 ) ) ,
Text ( record ! . endContext ! . holds . toString ( ) , style: const TextStyle ( fontSize: 24 ) ) ,
2023-06-17 21:50:52 +00:00
] ,
) ,
Row (
2023-09-23 19:09:36 +00:00
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-09-23 19:09:36 +00:00
Text ( " ${ t . numOfGameActions . tspinsTotal } : " , style: const TextStyle ( fontSize: 24 ) ) ,
Text ( record ! . endContext ! . tSpins . toString ( ) , style: const TextStyle ( fontSize: 24 ) ) ,
2023-06-17 21:50:52 +00:00
] ,
) ,
Row (
2023-09-23 19:09:36 +00:00
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-09-23 19:09:36 +00:00
const Text ( " - T-spin zero: " , style: TextStyle ( fontSize: 18 ) ) ,
Text ( record ! . endContext ! . clears . tSpinZeros . toString ( ) , style: const TextStyle ( fontSize: 18 ) ) ,
2023-06-17 21:50:52 +00:00
] ,
) ,
Row (
2023-09-23 19:09:36 +00:00
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-09-23 19:09:36 +00:00
const Text ( " - T-spin singles: " , style: TextStyle ( fontSize: 18 ) ) ,
Text ( record ! . endContext ! . clears . tSpinSingles . toString ( ) , style: const TextStyle ( fontSize: 18 ) ) ,
2023-06-17 21:50:52 +00:00
] ,
) ,
Row (
2023-09-23 19:09:36 +00:00
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-09-23 19:09:36 +00:00
const Text ( " - T-spin doubles: " , style: TextStyle ( fontSize: 18 ) ) ,
Text ( record ! . endContext ! . clears . tSpinDoubles . toString ( ) , style: const TextStyle ( fontSize: 18 ) ) ,
2023-06-17 21:50:52 +00:00
] ,
) ,
Row (
2023-09-23 19:09:36 +00:00
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-09-23 19:09:36 +00:00
const Text ( " - T-spin triples: " , style: TextStyle ( fontSize: 18 ) ) ,
Text ( record ! . endContext ! . clears . tSpinTriples . toString ( ) , style: const TextStyle ( fontSize: 18 ) ) ,
2023-06-17 21:50:52 +00:00
] ,
) ,
Row (
2023-09-23 19:09:36 +00:00
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-09-23 19:09:36 +00:00
const Text ( " - T-spin mini zero: " , style: TextStyle ( fontSize: 18 ) ) ,
Text ( record ! . endContext ! . clears . tSpinMiniZeros . toString ( ) , style: const TextStyle ( fontSize: 18 ) ) ,
2023-06-17 21:50:52 +00:00
] ,
) ,
Row (
2023-09-23 19:09:36 +00:00
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-09-23 19:09:36 +00:00
const Text ( " - T-spin mini singles: " , style: TextStyle ( fontSize: 18 ) ) ,
Text ( record ! . endContext ! . clears . tSpinMiniSingles . toString ( ) , style: const TextStyle ( fontSize: 18 ) ) ,
2023-06-17 21:50:52 +00:00
] ,
) ,
Row (
2023-09-23 19:09:36 +00:00
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-09-23 19:09:36 +00:00
const Text ( " - T-spin mini doubles: " , style: TextStyle ( fontSize: 18 ) ) ,
Text ( record ! . endContext ! . clears . tSpinMiniDoubles . toString ( ) , style: const TextStyle ( fontSize: 18 ) ) ,
2023-06-17 21:50:52 +00:00
] ,
) ,
2023-09-23 19:09:36 +00:00
Row ( mainAxisAlignment: MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-09-23 19:09:36 +00:00
Text ( " ${ t . numOfGameActions . lineClears } : " , style: const TextStyle ( fontSize: 24 ) ) ,
Text ( record ! . endContext ! . lines . toString ( ) , style: const TextStyle ( fontSize: 24 ) ) ,
2023-06-17 21:50:52 +00:00
] ,
) ,
Row (
2023-06-20 20:53:28 +00:00
mainAxisAlignment:
MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-09-23 19:09:36 +00:00
const Text ( " - Singles: " , style: TextStyle ( fontSize: 18 ) ) ,
Text ( record ! . endContext ! . clears . singles . toString ( ) , style: const TextStyle ( fontSize: 18 ) ) ,
2023-06-17 21:50:52 +00:00
] ,
) ,
Row (
2023-09-23 19:09:36 +00:00
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-09-23 19:09:36 +00:00
const Text ( " - Doubles: " , style: TextStyle ( fontSize: 18 ) ) ,
Text ( record ! . endContext ! . clears . doubles . toString ( ) , style: const TextStyle ( fontSize: 18 ) ) ,
2023-06-17 21:50:52 +00:00
] ,
) ,
Row (
2023-09-23 19:09:36 +00:00
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-09-23 19:09:36 +00:00
const Text ( " - Triples: " , style: TextStyle ( fontSize: 18 ) ) ,
Text ( record ! . endContext ! . clears . triples . toString ( ) , style: const TextStyle ( fontSize: 18 ) ) ,
2023-06-17 21:50:52 +00:00
] ,
) ,
Row (
2023-09-23 19:09:36 +00:00
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-09-23 19:09:36 +00:00
const Text ( " - Quads: " , style: TextStyle ( fontSize: 18 ) ) ,
Text ( record ! . endContext ! . clears . quads . toString ( ) , style: const TextStyle ( fontSize: 18 ) ) ,
2023-06-17 21:50:52 +00:00
] ,
) ,
] ,
) ,
) ,
) ,
]
2023-06-20 20:53:28 +00:00
: [
2024-01-01 17:26:09 +00:00
Text ( t . noRecord , textAlign: TextAlign . center , style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 28 ) )
2023-06-20 20:53:28 +00:00
] ,
2023-06-17 21:50:52 +00:00
) ;
} ) ;
} ) ;
}
}
class _OtherThingy extends StatelessWidget {
final TetrioZen ? zen ;
final String ? bio ;
2023-08-21 15:39:04 +00:00
final Distinguishment ? distinguishment ;
2023-10-07 16:44:54 +00:00
final List < News > ? newsletter ;
const _OtherThingy ( { Key ? key , required this . zen , required this . bio , required this . distinguishment , this . newsletter } )
2023-06-20 20:53:28 +00:00
: super ( key: key ) ;
2023-06-17 21:50:52 +00:00
2024-01-01 17:26:09 +00:00
List < InlineSpan > getDistinguishmentTitle ( String ? text ) {
if ( distinguishment ? . type = = " twc " ) return [ const TextSpan ( text: " TETR.IO World Champion " , style: TextStyle ( fontSize: 28 , fontWeight: FontWeight . bold , color: Colors . yellowAccent ) ) ] ;
if ( text = = null ) return [ const TextSpan ( text: " Header is missing " , style: TextStyle ( fontSize: 28 , fontWeight: FontWeight . bold , color: Colors . redAccent ) ) ] ;
2023-08-21 15:39:04 +00:00
var exploded = text . split ( " " ) ;
List < InlineSpan > result = [ ] ;
for ( String shit in exploded ) {
switch ( shit ) {
case " %osk% " :
result . add ( WidgetSpan ( child: Padding (
padding: const EdgeInsets . only ( left: 8 ) ,
child: SvgPicture . asset ( " res/icons/osk.svg " , height: 28 ) ,
) ) ) ;
break ;
case " %tetrio% " :
result . add ( WidgetSpan ( child: Padding (
padding: const EdgeInsets . only ( left: 8 ) ,
child: SvgPicture . asset ( " res/icons/tetrio-logo.svg " , height: 28 ) ,
) ) ) ;
break ;
default :
result . add ( TextSpan ( text: " $ shit " , style: const TextStyle ( fontSize: 28 , fontWeight: FontWeight . bold ) ) ) ;
}
}
return result ;
}
2024-01-01 17:26:09 +00:00
String getDistinguishmentSubtitle ( String ? text ) {
if ( distinguishment ? . type = = " twc " ) return " ${ distinguishment ? . detail } TETR.IO World Championship " ;
if ( text = = null ) return " Footer is missing " ;
return text ;
}
2023-10-07 16:44:54 +00:00
ListTile getNewsTile ( News news ) {
Map < String , String > gametypes = {
" 40l " : t . sprint ,
" blitz " : t . blitz ,
" 5mblast " : " 5,000,000 Blast "
} ;
switch ( news . type ) {
case " leaderboard " :
return ListTile (
title: RichText (
text: TextSpan (
style: const TextStyle ( fontFamily: ' Eurostile Round ' , fontSize: 16 ) ,
text: t . newsParts . leaderboardStart ,
children: [
TextSpan ( text: " № ${ news . data [ " rank " ] } " , style: const TextStyle ( fontWeight: FontWeight . bold ) ) ,
TextSpan ( text: t . newsParts . leaderboardMiddle ) ,
TextSpan ( text: " № ${ gametypes [ news . data [ " gametype " ] ] } " , style: const TextStyle ( fontWeight: FontWeight . bold ) ) ,
]
)
) ,
2023-10-26 22:38:03 +00:00
subtitle: Text ( _dateFormat . format ( news . timestamp ) ) ,
2023-10-07 16:44:54 +00:00
) ;
case " personalbest " :
return ListTile (
title: RichText (
text: TextSpan (
style: const TextStyle ( fontFamily: ' Eurostile Round ' , fontSize: 16 ) ,
text: t . newsParts . personalbest ,
children: [
TextSpan ( text: " ${ gametypes [ news . data [ " gametype " ] ] } " , style: const TextStyle ( fontWeight: FontWeight . bold ) ) ,
TextSpan ( text: t . newsParts . personalbestMiddle ) ,
TextSpan ( text: news . data [ " gametype " ] = = " blitz " ? NumberFormat . decimalPattern ( ) . format ( news . data [ " result " ] ) : get40lTime ( ( news . data [ " result " ] * 1000 ) . floor ( ) ) , style: const TextStyle ( fontWeight: FontWeight . bold ) ) ,
]
)
) ,
2023-10-26 22:38:03 +00:00
subtitle: Text ( _dateFormat . format ( news . timestamp ) ) ,
2023-10-08 17:20:42 +00:00
leading: Image . asset (
" res/icons/improvement-local.png " ,
height: 48 ,
width: 48 ,
errorBuilder: ( context , error , stackTrace ) {
return Image . asset ( " res/icons/kagari.png " , height: 64 , width: 64 ) ;
} ,
) ,
2023-10-07 16:44:54 +00:00
) ;
case " badge " :
return ListTile (
title: RichText (
text: TextSpan (
style: const TextStyle ( fontFamily: ' Eurostile Round ' , fontSize: 16 ) ,
text: t . newsParts . badgeStart ,
children: [
TextSpan ( text: " ${ news . data [ " label " ] } " , style: const TextStyle ( fontWeight: FontWeight . bold ) ) ,
TextSpan ( text: t . newsParts . badgeEnd )
]
)
) ,
2023-10-26 22:38:03 +00:00
subtitle: Text ( _dateFormat . format ( news . timestamp ) ) ,
2023-10-08 17:20:42 +00:00
leading: Image . asset (
" res/tetrio_badges/ ${ news . data [ " type " ] } .png " ,
height: 48 ,
width: 48 ,
errorBuilder: ( context , error , stackTrace ) {
return Image . asset ( " res/icons/kagari.png " , height: 64 , width: 64 ) ;
} ,
) ,
2023-10-07 16:44:54 +00:00
) ;
case " rankup " :
return ListTile (
title: RichText (
text: TextSpan (
style: const TextStyle ( fontFamily: ' Eurostile Round ' , fontSize: 16 ) ,
text: t . newsParts . rankupStart ,
children: [
TextSpan ( text: t . newsParts . rankupMiddle ( r: news . data [ " rank " ] . toString ( ) . toUpperCase ( ) ) , style: const TextStyle ( fontWeight: FontWeight . bold ) ) ,
TextSpan ( text: t . newsParts . rankupEnd )
]
)
) ,
2023-10-26 22:38:03 +00:00
subtitle: Text ( _dateFormat . format ( news . timestamp ) ) ,
2023-10-08 17:20:42 +00:00
leading: Image . asset (
" res/tetrio_tl_alpha_ranks/ ${ news . data [ " rank " ] } .png " ,
height: 48 ,
width: 48 ,
errorBuilder: ( context , error , stackTrace ) {
return Image . asset ( " res/icons/kagari.png " , height: 64 , width: 64 ) ;
} ,
) ,
2023-10-07 16:44:54 +00:00
) ;
case " supporter " :
return ListTile (
title: RichText (
text: TextSpan (
2023-10-08 17:20:42 +00:00
style: const TextStyle ( fontFamily: ' Eurostile Round ' , fontSize: 16 ) ,
2023-10-07 16:44:54 +00:00
text: t . newsParts . supporterStart ,
children: [
2023-10-08 17:20:42 +00:00
TextSpan ( text: t . newsParts . tetoSupporter , style: const TextStyle ( fontWeight: FontWeight . bold ) )
2023-10-07 16:44:54 +00:00
]
)
) ,
2023-10-26 22:38:03 +00:00
subtitle: Text ( _dateFormat . format ( news . timestamp ) ) ,
2023-10-08 17:20:42 +00:00
leading: Image . asset (
" res/icons/supporter-tag.png " ,
height: 48 ,
width: 48 ,
errorBuilder: ( context , error , stackTrace ) {
return Image . asset ( " res/icons/kagari.png " , height: 64 , width: 64 ) ;
} ,
) ,
2023-10-07 16:44:54 +00:00
) ;
case " supporter_gift " :
return ListTile (
title: RichText (
text: TextSpan (
2023-10-08 17:20:42 +00:00
style: const TextStyle ( fontFamily: ' Eurostile Round ' , fontSize: 16 ) ,
2023-10-07 16:44:54 +00:00
text: t . newsParts . supporterGiftStart ,
children: [
2023-10-08 17:20:42 +00:00
TextSpan ( text: t . newsParts . tetoSupporter , style: const TextStyle ( fontWeight: FontWeight . bold ) )
2023-10-07 16:44:54 +00:00
]
)
) ,
2023-10-26 22:38:03 +00:00
subtitle: Text ( _dateFormat . format ( news . timestamp ) ) ,
2023-10-08 17:20:42 +00:00
leading: Image . asset (
" res/icons/supporter-tag.png " ,
height: 48 ,
width: 48 ,
errorBuilder: ( context , error , stackTrace ) {
return Image . asset ( " res/icons/kagari.png " , height: 64 , width: 64 ) ;
} ,
) ,
2023-10-07 16:44:54 +00:00
) ;
default :
return ListTile (
title: Text ( t . newsParts . unknownNews ( type: news . type ) ) ,
2023-10-26 22:38:03 +00:00
subtitle: Text ( _dateFormat . format ( news . timestamp ) ) ,
2023-10-07 16:44:54 +00:00
) ;
}
}
2023-06-17 21:50:52 +00:00
@ override
Widget build ( BuildContext context ) {
return LayoutBuilder ( builder: ( context , constraints ) {
bool bigScreen = constraints . maxWidth > 768 ;
return ListView . builder (
2023-06-26 17:13:53 +00:00
physics: const AlwaysScrollableScrollPhysics ( ) ,
2023-10-07 16:44:54 +00:00
itemCount: newsletter ! . length + 1 ,
2023-06-17 21:50:52 +00:00
itemBuilder: ( BuildContext context , int index ) {
2023-10-07 16:44:54 +00:00
return index = = 0 ? Column (
2023-06-17 21:50:52 +00:00
children: [
2023-08-21 15:39:04 +00:00
if ( distinguishment ! = null )
2023-06-17 21:50:52 +00:00
Padding (
2023-08-21 15:39:04 +00:00
padding: const EdgeInsets . fromLTRB ( 0 , 0 , 0 , 48 ) ,
2023-06-17 21:50:52 +00:00
child: Column (
children: [
2023-10-09 18:48:50 +00:00
Text ( t . distinguishment , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: bigScreen ? 42 : 28 ) , textAlign: TextAlign . center ) ,
2023-08-21 15:39:04 +00:00
RichText (
2023-10-11 15:32:47 +00:00
textAlign: TextAlign . center ,
2023-08-21 15:39:04 +00:00
text: TextSpan (
style: DefaultTextStyle . of ( context ) . style ,
2024-01-01 17:26:09 +00:00
children: getDistinguishmentTitle ( distinguishment ? . header ) ,
2023-08-21 15:39:04 +00:00
) ,
) ,
2024-01-01 17:26:09 +00:00
Text ( getDistinguishmentSubtitle ( distinguishment ? . footer ) , style: const TextStyle ( fontSize: 18 ) , textAlign: TextAlign . center ) ,
2023-06-17 21:50:52 +00:00
] ,
) ,
) ,
if ( bio ! = null )
Padding (
2023-10-08 17:20:42 +00:00
padding: const EdgeInsets . fromLTRB ( 8 , 0 , 8 , 48 ) ,
2023-06-17 21:50:52 +00:00
child: Column (
children: [
2023-08-21 15:39:04 +00:00
Text ( t . bio , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: bigScreen ? 42 : 28 ) ) ,
2023-10-08 17:20:42 +00:00
MarkdownBody ( data: bio ! , styleSheet: MarkdownStyleSheet ( textScaleFactor: 1.5 , textAlign: WrapAlignment . center ) ) // Text(bio!, style: const TextStyle(fontSize: 18)),
2023-06-17 21:50:52 +00:00
] ,
) ,
) ,
2023-08-21 15:39:04 +00:00
if ( zen ! = null )
Padding (
2023-10-07 16:44:54 +00:00
padding: const EdgeInsets . fromLTRB ( 0 , 0 , 0 , 48 ) ,
2023-08-21 15:39:04 +00:00
child: Column (
children: [
Text ( t . zen , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: bigScreen ? 42 : 28 ) ) ,
Text ( " ${ t . statCellNum . level } ${ NumberFormat . decimalPattern ( ) . format ( zen ! . level ) } " , style: const TextStyle ( fontSize: 28 , fontWeight: FontWeight . bold ) ) ,
Text ( " ${ t . statCellNum . score } ${ NumberFormat . decimalPattern ( ) . format ( zen ! . score ) } " , style: const TextStyle ( fontSize: 18 ) ) ,
] ,
) ,
) ,
2023-10-07 16:44:54 +00:00
if ( newsletter ! = null & & newsletter ! . isNotEmpty )
Text ( t . news , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: bigScreen ? 42 : 28 ) ) ,
2023-06-17 21:50:52 +00:00
] ,
2023-10-07 16:44:54 +00:00
) : getNewsTile ( newsletter ! [ index - 1 ] ) ;
2023-06-17 21:50:52 +00:00
} ,
) ;
} ) ;
}
}