2023-06-26 17:13:53 +00:00
import ' dart:io ' ;
2023-06-17 21:50:52 +00:00
import ' package:flutter/material.dart ' ;
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 ' ;
import ' package:tetra_stats/services/tetrio_crud.dart ' ;
import ' package:tetra_stats/services/crud_exceptions.dart ' ;
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-06-21 22:05:14 +00:00
late Future < List > me ;
2023-06-17 21:50:52 +00:00
String _searchFor = " dan63047 " ;
2023-06-21 22:05:14 +00:00
String _titleNickname = " dan63047 " ;
2023-06-17 21:50:52 +00:00
final TetrioService teto = TetrioService ( ) ;
late SharedPreferences prefs ;
2023-06-26 17:13:53 +00:00
var chartsData = < DropdownMenuItem < List < FlSpot > > > [ ] ;
List chartsShortTitles = [ " 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 ;
2023-06-17 21:50:52 +00:00
const allowedHeightForPlayerIdInPixels = 40.0 ;
const allowedHeightForPlayerBioInPixels = 30.0 ;
const givenTextHeightByScreenPercentage = 0.3 ;
final NumberFormat timeInSec = NumberFormat ( " #,###.###s. " ) ;
2023-06-20 20:53:28 +00:00
final NumberFormat f2 = NumberFormat . decimalPatternDigits ( decimalDigits: 2 ) ;
2023-06-23 18:38:15 +00:00
final NumberFormat f4 = NumberFormat . decimalPatternDigits ( decimalDigits: 4 ) ;
2023-06-22 19:02:49 +00:00
final DateFormat dateFormat = DateFormat . yMMMd ( ) . add_Hms ( ) ;
2023-06-17 21:50:52 +00:00
class MainView extends StatefulWidget {
const MainView ( { Key ? key } ) : super ( key: key ) ;
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 ) ) ;
}
class _MainState extends State < MainView > with SingleTickerProviderStateMixin {
final bodyGlobalKey = GlobalKey ( ) ;
final List < Widget > myTabs = [
const Tab ( text: " Tetra League " ) ,
2023-06-20 20:53:28 +00:00
const Tab ( text: " TL Records " ) ,
2023-06-26 17:13:53 +00:00
const Tab ( text: " History " ) ,
2023-06-17 21:50:52 +00:00
const Tab ( text: " 40 Lines " ) ,
const Tab ( text: " Blitz " ) ,
const Tab ( text: " Other " ) ,
] ;
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 ( ) {
teto . open ( ) ;
_scrollController = ScrollController ( ) ;
2023-06-21 22:05:14 +00:00
_tabController = TabController ( length: 6 , vsync: this ) ;
2023-06-20 20:53:28 +00:00
_getPreferences ( )
. then ( ( value ) = > changePlayer ( prefs . getString ( " player " ) ? ? " dan63047 " ) ) ;
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 ( ) ;
}
void changePlayer ( String player ) {
setState ( ( ) {
_searchFor = player ;
2023-06-21 22:05:14 +00:00
me = fetch ( _searchFor ) ;
2023-06-17 21:50:52 +00:00
} ) ;
}
2023-06-21 22:05:14 +00:00
Future < List > fetch ( String nickOrID ) async {
TetrioPlayer me = await teto . fetchPlayer ( nickOrID ) ;
setState ( ( ) { _titleNickname = me . username ; } ) ;
2023-06-26 17:13:53 +00:00
var tlStream = await teto . getTLStream ( me . userId ) ;
List < TetraLeagueAlphaRecord > tlMatches = [ ] ;
2023-06-22 19:02:49 +00:00
bool isTracking = await teto . isPlayerTracking ( me . userId ) ;
List < TetrioPlayer > states = [ ] ;
2023-06-28 16:50:40 +00:00
TetraLeagueAlpha ? compareWith = null ;
var uniqueTL = Set ( ) ;
2023-06-22 19:02:49 +00:00
if ( isTracking ) {
teto . storeState ( me ) ;
2023-06-23 18:38:15 +00:00
teto . saveTLMatchesFromStream ( await teto . getTLStream ( me . userId ) ) ;
2023-06-22 19:02:49 +00:00
states . addAll ( await teto . getPlayer ( me . userId ) ) ;
2023-06-28 16:50:40 +00:00
states . forEach ( ( element ) {
if ( uniqueTL . isNotEmpty & & uniqueTL . last ! = element . tlSeason1 ) uniqueTL . add ( element . tlSeason1 ) ;
if ( uniqueTL . isEmpty ) uniqueTL . add ( element . tlSeason1 ) ;
} ) ;
compareWith = uniqueTL . toList ( ) [ uniqueTL . length - 2 ] ;
2023-06-26 17:13:53 +00:00
chartsData = < DropdownMenuItem < List < FlSpot > > > [
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 . rating ) ] , child: const Text ( " Tetra Rating " ) ) ,
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 " ) ) ,
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . apm ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . apm ! ) ] , child: const Text ( " Attack Per Minute " ) ) ,
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . pps ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . pps ! ) ] , child: const Text ( " Pieces Per Second " ) ) ,
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . vs ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . vs ! ) ] , child: const Text ( " Versus Score " ) ) ,
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . nerdStats ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . nerdStats ! . app ) ] , child: const Text ( " Attack Per Piece " ) ) ,
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . nerdStats ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . nerdStats ! . dss ) ] , child: const Text ( " Downstack Per Second " ) ) ,
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . nerdStats ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . nerdStats ! . dsp ) ] , child: const Text ( " Downstack Per Piece " ) ) ,
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 " ) ) ,
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . nerdStats ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . nerdStats ! . cheese ) ] , child: const Text ( " Cheese Index " ) ) ,
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . nerdStats ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . nerdStats ! . gbe ) ] , child: const Text ( " Garbage Efficiency " ) ) ,
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . nerdStats ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . nerdStats ! . nyaapp ) ] , child: const Text ( " Weighted APP " ) ) ,
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . nerdStats ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . nerdStats ! . area ) ] , child: const Text ( " Area " ) ) ,
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . estTr ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . estTr ! . esttr ) ] , child: const Text ( " Est. of TR " ) ) ,
DropdownMenuItem ( value: [ for ( var tl in uniqueTL ) if ( tl . esttracc ! = null ) FlSpot ( tl . timestamp . millisecondsSinceEpoch . toDouble ( ) , tl . esttracc ! ) ] , child: const Text ( " Accuracy of Est. " ) ) ,
2023-06-26 17:13:53 +00:00
] ;
tlMatches . addAll ( await teto . getTLMatchesbyPlayerID ( me . userId ) ) ;
for ( var match in tlStream . records ) {
if ( ! tlMatches . contains ( match ) ) tlMatches . add ( match ) ;
2023-06-23 18:38:15 +00:00
}
2023-06-26 17:13:53 +00:00
tlMatches . sort ( ( a , b ) {
2023-06-23 18:38:15 +00:00
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-06-26 17:13:53 +00:00
} else {
tlMatches = tlStream . records ;
}
Map < String , dynamic > records = await teto . fetchRecords ( me . userId ) ;
2023-06-28 16:50:40 +00:00
return [ me , records , states , tlMatches , compareWith , isTracking ] ;
2023-06-23 18:38:15 +00:00
}
2023-06-17 21:50:52 +00:00
void _justUpdate ( ) {
setState ( ( ) { } ) ;
}
@ override
Widget build ( BuildContext context ) {
return Scaffold (
drawer: NavDrawer ( changePlayer ) ,
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 ,
actions: [
! _searchBoolean
? IconButton (
onPressed: ( ) {
setState ( ( ) {
_searchBoolean = true ;
} ) ;
} ,
icon: const Icon ( Icons . search ) ,
tooltip: " Search player " ,
)
: IconButton (
onPressed: ( ) {
setState ( ( ) {
_searchBoolean = false ;
} ) ;
} ,
icon: const Icon ( Icons . clear ) ,
tooltip: " Close search " ,
) ,
PopupMenuButton (
itemBuilder: ( BuildContext context ) = > < PopupMenuEntry > [
const PopupMenuItem (
value: " /states " ,
child: Text ( ' Show stored data ' ) ,
) ,
const PopupMenuItem (
value: " /calc " ,
child: Text ( ' Stats Calculator ' ) ,
) ,
const PopupMenuItem (
value: " /settings " ,
child: Text ( ' Settings ' ) ,
) ,
] ,
onSelected: ( value ) {
Navigator . pushNamed ( context , value ) ;
} ,
) ,
] ,
) ,
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:
return const Center (
child: Text ( ' none case of FutureBuilder ' ,
2023-06-20 20:53:28 +00:00
style: TextStyle (
fontFamily: " Eurostile Round Extended " ,
fontSize: 42 ) ,
textAlign: TextAlign . center ) ) ;
2023-06-17 21:50:52 +00:00
case ConnectionState . waiting:
2023-06-20 20:53:28 +00:00
return const Center (
child: CircularProgressIndicator ( color: Colors . white ) ) ;
2023-06-17 21:50:52 +00:00
case ConnectionState . active:
return const Center (
child: Text ( ' active case of FutureBuilder ' ,
2023-06-20 20:53:28 +00:00
style: TextStyle (
fontFamily: " Eurostile Round Extended " ,
fontSize: 42 ) ,
textAlign: TextAlign . center ) ) ;
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-06-26 17:13:53 +00:00
notificationPredicate: ( notification ) {
// with NestedScrollView local(depth == 2) OverscrollNotification are not sent
if ( notification is OverscrollNotification | | Platform . isIOS ) {
return notification . depth = = 2 ;
}
return notification . depth = = 0 ;
} ,
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 ,
tabs: myTabs ,
) ,
) ,
] ;
} ,
body: TabBarView (
controller: _tabController ,
children: [
TLThingy (
tl: snapshot . data ! [ 0 ] . tlSeason1 ,
2023-06-28 16:50:40 +00:00
userID: snapshot . data ! [ 0 ] . userId ,
oldTl: snapshot . data ! [ 4 ] , ) ,
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 ) ,
_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 )
] ,
) ,
2023-06-17 21:50:52 +00:00
) ,
) ;
} else if ( snapshot . hasError ) {
return Center (
2023-06-20 20:53:28 +00:00
child: Text ( ' ${ snapshot . error } ' ,
style: const TextStyle (
fontFamily: " Eurostile Round Extended " ,
fontSize: 42 ) ,
textAlign: TextAlign . center ) ) ;
2023-06-17 21:50:52 +00:00
}
break ;
default :
return const Center (
child: Text ( ' default case of FutureBuilder ' ,
2023-06-20 20:53:28 +00:00
style: TextStyle (
fontFamily: " Eurostile Round Extended " ,
fontSize: 42 ) ,
textAlign: TextAlign . center ) ) ;
2023-06-17 21:50:52 +00:00
}
return const Center (
2023-06-20 20:53:28 +00:00
child: Text ( ' end 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:
return const Center ( child: Text ( ' none case of StreamBuilder ' ) ) ;
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-06-17 21:50:52 +00:00
List < String > keys = allPlayers . keys . toList ( ) ;
return NestedScrollView (
headerSliverBuilder: ( context , value ) {
return [
const SliverToBoxAdapter (
child: DrawerHeader (
child: Text (
' Players you track ' ,
style: TextStyle ( color: Colors . white , fontSize: 25 ) ,
) ) ) ,
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 ( ) ;
} ,
) ,
)
] ;
} ,
body: ListView . builder (
itemCount: allPlayers . length ,
itemBuilder: ( context , index ) {
return ListTile (
2023-06-20 20:53:28 +00:00
title: Text (
allPlayers [ keys [ index ] ] ? . last . username as String ) ,
2023-06-17 21:50:52 +00:00
onTap: ( ) {
widget . changePlayer ( keys [ index ] ) ;
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 ;
2023-06-20 20:53:28 +00:00
2023-06-26 17:13:53 +00:00
const _TLRecords ( { required this . userID , required this . data } ) ;
2023-06-20 20:53:28 +00:00
@ override
Widget build ( BuildContext context ) {
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 } " ,
style: const TextStyle (
fontFamily: " Eurostile Round Extended " ,
fontSize: 28 , ) ) ,
title: Text ( " vs. ${ value . endContext . firstWhere ( ( element ) = > element . userId ! = userID ) . username } " ) ,
subtitle: Text ( dateFormat . format ( value . timestamp ) ) ,
trailing: Column ( mainAxisAlignment: MainAxisAlignment . center ,
children: [
Text ( " ${ f2 . format ( value . endContext . firstWhere ( ( element ) = > element . userId = = userID ) . secondary ) } : ${ f2 . format ( value . endContext . firstWhere ( ( element ) = > element . userId ! = userID ) . secondary ) } APM " , style: const TextStyle ( height: 1.1 ) ) ,
Text ( " ${ f2 . format ( value . endContext . firstWhere ( ( element ) = > element . userId = = userID ) . tertiary ) } : ${ f2 . format ( value . endContext . firstWhere ( ( element ) = > element . userId ! = userID ) . tertiary ) } PPS " , style: const TextStyle ( height: 1.1 ) ) ,
Text ( " ${ f2 . format ( value . endContext . firstWhere ( ( element ) = > element . userId = = userID ) . extra ) } : ${ f2 . format ( value . endContext . firstWhere ( ( element ) = > element . userId ! = userID ) . extra ) } VS " , style: const TextStyle ( height: 1.1 ) ) ,
] ) ,
onTap: ( ) { Navigator . push (
context ,
MaterialPageRoute (
builder: ( context ) = > TlMatchResultView ( record: value , initPlayerId: userID ) ,
) ,
) ; } ,
) ]
: [ const Center ( child: Text ( " No records " , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 28 ) ) ) ] ,
) ;
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 ,
value: chartsData [ chartsIndex ] . value ,
onChanged: ( value ) {
chartsIndex = chartsData . indexWhere ( ( element ) = > element . value = = value ) ;
update ( ) ;
}
) ,
if ( chartsData [ chartsIndex ] . value ! . length > 1 ) _HistoryChartThigy ( data: chartsData [ chartsIndex ] . value ! , title: " ss " , yAxisTitle: chartsShortTitles [ chartsIndex ] , bigScreen: bigScreen , leftSpace: bigScreen ? 80 : 45 , yFormat: bigScreen ? f2 : NumberFormat . compact ( ) , )
else const Center ( child: Text ( " Not enough data " , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 28 ) ) )
2023-06-22 19:02:49 +00:00
] ,
) ,
] : [ const Center ( child: Text ( " No history saved " , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 28 ) ) ) ] ) ;
}
}
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-06-26 17:13:53 +00:00
Padding ( padding: bigScreen ? const EdgeInsets . fromLTRB ( 40 , 40 , 40 , 48 ) : const EdgeInsets . fromLTRB ( 0 , 40 , 0 , 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-06-22 19:02:49 +00:00
titlesData: FlTitlesData ( topTitles: AxisTitles ( sideTitles: SideTitles ( showTitles: false ) ) ,
rightTitles: 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 ,
child: Text ( DateFormat ( DateFormat . YEAR_ABBR_MONTH_DAY ) . 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 ) {
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-06-20 20:53:28 +00:00
Text ( " 40 Lines " ,
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 ( " Blitz " ,
style: TextStyle (
fontFamily: " Eurostile Round Extended " ,
fontSize: bigScreen ? 42 : 28 ) ) ,
2023-06-17 21:50:52 +00:00
if ( record ! . stream . contains ( " 40l " ) )
2023-06-20 20:53:28 +00:00
Text (
timeInSec . format (
record ! . endContext ! . finalTime . inMicroseconds /
1000000 ) ,
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-06-17 21:50:52 +00:00
Text ( " Obtained ${ dateFormat . format ( record ! . timestamp ! ) } " ,
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 ,
playerStatLabel: " 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 ,
playerStatLabel: " Score \n Per Piece " ,
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 ,
playerStatLabel: " Pieces \n Placed " ,
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 ,
playerStatLabel: " Pieces \n Per Second " ,
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 ,
playerStatLabel: " Finesse \n Faults " ,
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-06-17 21:50:52 +00:00
playerStatLabel: " Finesse \n Percentage " ,
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 ,
playerStatLabel: " Key \n Presses " ,
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 ,
playerStatLabel: " KP Per \n Piece " ,
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 ,
playerStatLabel: " KP Per \n Second " ,
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-06-20 20:53:28 +00:00
mainAxisAlignment:
MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-06-20 20:53:28 +00:00
const Text ( " All Clears: " ,
style: TextStyle ( fontSize: 24 ) ) ,
2023-06-17 21:50:52 +00:00
Text (
2023-06-20 20:53:28 +00:00
record ! . endContext ! . clears . allClears
. toString ( ) ,
2023-06-17 21:50:52 +00:00
style: const TextStyle ( fontSize: 24 ) ,
) ,
] ,
) ,
Row (
2023-06-20 20:53:28 +00:00
mainAxisAlignment:
MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-06-20 20:53:28 +00:00
const Text ( " Holds: " ,
style: TextStyle ( fontSize: 24 ) ) ,
2023-06-17 21:50:52 +00:00
Text (
record ! . endContext ! . holds . toString ( ) ,
style: const TextStyle ( fontSize: 24 ) ,
) ,
] ,
) ,
Row (
2023-06-20 20:53:28 +00:00
mainAxisAlignment:
MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-06-20 20:53:28 +00:00
const Text ( " T-spins total: " ,
style: TextStyle ( fontSize: 24 ) ) ,
2023-06-17 21:50:52 +00:00
Text (
record ! . endContext ! . tSpins . toString ( ) ,
style: const TextStyle ( fontSize: 24 ) ,
) ,
] ,
) ,
Row (
2023-06-20 20:53:28 +00:00
mainAxisAlignment:
MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-06-20 20:53:28 +00:00
const Text ( " - T-spin zero: " ,
style: TextStyle ( fontSize: 18 ) ) ,
2023-06-17 21:50:52 +00:00
Text (
2023-06-20 20:53:28 +00:00
record ! . endContext ! . clears . tSpinZeros
. toString ( ) ,
2023-06-17 21:50:52 +00:00
style: const TextStyle ( fontSize: 18 ) ,
) ,
] ,
) ,
Row (
2023-06-20 20:53:28 +00:00
mainAxisAlignment:
MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-06-20 20:53:28 +00:00
const Text ( " - T-spin singles: " ,
style: TextStyle ( fontSize: 18 ) ) ,
2023-06-17 21:50:52 +00:00
Text (
2023-06-20 20:53:28 +00:00
record ! . endContext ! . clears . tSpinSingles
. toString ( ) ,
2023-06-17 21:50:52 +00:00
style: const TextStyle ( fontSize: 18 ) ,
) ,
] ,
) ,
Row (
2023-06-20 20:53:28 +00:00
mainAxisAlignment:
MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-06-20 20:53:28 +00:00
const Text ( " - T-spin doubles: " ,
style: TextStyle ( fontSize: 18 ) ) ,
2023-06-17 21:50:52 +00:00
Text (
2023-06-20 20:53:28 +00:00
record ! . endContext ! . clears . tSpinDoubles
. toString ( ) ,
2023-06-17 21:50:52 +00:00
style: const TextStyle ( fontSize: 18 ) ,
) ,
] ,
) ,
Row (
2023-06-20 20:53:28 +00:00
mainAxisAlignment:
MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-06-20 20:53:28 +00:00
const Text ( " - T-spin triples: " ,
style: TextStyle ( fontSize: 18 ) ) ,
2023-06-17 21:50:52 +00:00
Text (
2023-06-20 20:53:28 +00:00
record ! . endContext ! . clears . tSpinTriples
. toString ( ) ,
2023-06-17 21:50:52 +00:00
style: const TextStyle ( fontSize: 18 ) ,
) ,
] ,
) ,
Row (
2023-06-20 20:53:28 +00:00
mainAxisAlignment:
MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-06-20 20:53:28 +00:00
const Text ( " - T-spin mini zero: " ,
style: TextStyle ( fontSize: 18 ) ) ,
2023-06-17 21:50:52 +00:00
Text (
2023-06-20 20:53:28 +00:00
record ! . endContext ! . clears . tSpinMiniZeros
. toString ( ) ,
2023-06-17 21:50:52 +00:00
style: const TextStyle ( fontSize: 18 ) ,
) ,
] ,
) ,
Row (
2023-06-20 20:53:28 +00:00
mainAxisAlignment:
MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-06-20 20:53:28 +00:00
const Text ( " - T-spin mini singles: " ,
style: TextStyle ( fontSize: 18 ) ) ,
2023-06-17 21:50:52 +00:00
Text (
2023-06-20 20:53:28 +00:00
record ! . endContext ! . clears . tSpinMiniSingles
. toString ( ) ,
2023-06-17 21:50:52 +00:00
style: const TextStyle ( fontSize: 18 ) ,
) ,
] ,
) ,
Row (
2023-06-20 20:53:28 +00:00
mainAxisAlignment:
MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-06-20 20:53:28 +00:00
const Text ( " - T-spin mini doubles: " ,
style: TextStyle ( fontSize: 18 ) ) ,
2023-06-17 21:50:52 +00:00
Text (
2023-06-20 20:53:28 +00:00
record ! . endContext ! . clears . tSpinMiniDoubles
. toString ( ) ,
2023-06-17 21:50:52 +00:00
style: const TextStyle ( fontSize: 18 ) ,
) ,
] ,
) ,
Row (
2023-06-20 20:53:28 +00:00
mainAxisAlignment:
MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-06-20 20:53:28 +00:00
const Text ( " Line clears: " ,
style: TextStyle ( fontSize: 24 ) ) ,
2023-06-17 21:50:52 +00:00
Text (
record ! . endContext ! . lines . toString ( ) ,
style: const TextStyle ( fontSize: 24 ) ,
) ,
] ,
) ,
Row (
2023-06-20 20:53:28 +00:00
mainAxisAlignment:
MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-06-20 20:53:28 +00:00
const Text ( " - Singles: " ,
style: TextStyle ( fontSize: 18 ) ) ,
2023-06-17 21:50:52 +00:00
Text (
2023-06-20 20:53:28 +00:00
record ! . endContext ! . clears . singles
. toString ( ) ,
2023-06-17 21:50:52 +00:00
style: const TextStyle ( fontSize: 18 ) ,
) ,
] ,
) ,
Row (
2023-06-20 20:53:28 +00:00
mainAxisAlignment:
MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-06-20 20:53:28 +00:00
const Text ( " - Doubles: " ,
style: TextStyle ( fontSize: 18 ) ) ,
2023-06-17 21:50:52 +00:00
Text (
2023-06-20 20:53:28 +00:00
record ! . endContext ! . clears . doubles
. toString ( ) ,
2023-06-17 21:50:52 +00:00
style: const TextStyle ( fontSize: 18 ) ,
) ,
] ,
) ,
Row (
2023-06-20 20:53:28 +00:00
mainAxisAlignment:
MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-06-20 20:53:28 +00:00
const Text ( " - Triples: " ,
style: TextStyle ( fontSize: 18 ) ) ,
2023-06-17 21:50:52 +00:00
Text (
2023-06-20 20:53:28 +00:00
record ! . endContext ! . clears . triples
. toString ( ) ,
2023-06-17 21:50:52 +00:00
style: const TextStyle ( fontSize: 18 ) ,
) ,
] ,
) ,
Row (
2023-06-20 20:53:28 +00:00
mainAxisAlignment:
MainAxisAlignment . spaceBetween ,
2023-06-17 21:50:52 +00:00
children: [
2023-06-20 20:53:28 +00:00
const Text ( " - Quads: " ,
style: TextStyle ( fontSize: 18 ) ) ,
2023-06-17 21:50:52 +00:00
Text (
record ! . endContext ! . clears . quads . toString ( ) ,
style: const TextStyle ( fontSize: 18 ) ,
) ,
] ,
) ,
] ,
) ,
) ,
) ,
]
2023-06-20 20:53:28 +00:00
: [
2023-06-22 19:02:49 +00:00
const Text ( " No record " , style: TextStyle ( fontFamily: " Eurostile Round Extended " , 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-06-20 20:53:28 +00:00
const _OtherThingy ( { Key ? key , required this . zen , required this . bio } )
: super ( key: key ) ;
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-06-17 21:50:52 +00:00
itemCount: 1 ,
itemBuilder: ( BuildContext context , int index ) {
return Column (
children: [
2023-06-20 20:53:28 +00:00
Text ( " Other info " ,
style: TextStyle (
fontFamily: " Eurostile Round Extended " ,
fontSize: bigScreen ? 42 : 28 ) ) ,
2023-06-17 21:50:52 +00:00
if ( zen ! = null )
Padding (
padding: const EdgeInsets . fromLTRB ( 0 , 48 , 0 , 48 ) ,
child: Column (
children: [
2023-06-20 20:53:28 +00:00
Text ( " Zen " ,
style: TextStyle (
fontFamily: " Eurostile Round Extended " ,
fontSize: bigScreen ? 42 : 28 ) ) ,
Text (
" Level ${ NumberFormat . decimalPattern ( ) . format ( zen ! . level ) } " ,
style: TextStyle (
fontFamily: " Eurostile Round Extended " ,
fontSize: bigScreen ? 42 : 28 ) ) ,
Text (
" Score ${ NumberFormat . decimalPattern ( ) . format ( zen ! . score ) } " ,
style: const TextStyle ( fontSize: 18 ) ) ,
2023-06-17 21:50:52 +00:00
] ,
) ,
) ,
if ( bio ! = null )
Padding (
padding: const EdgeInsets . fromLTRB ( 0 , 0 , 0 , 48 ) ,
child: Column (
children: [
2023-06-20 20:53:28 +00:00
Text ( " Bio " ,
style: TextStyle (
fontFamily: " Eurostile Round Extended " ,
fontSize: bigScreen ? 42 : 28 ) ) ,
2023-06-17 21:50:52 +00:00
Text ( bio ! , style: const TextStyle ( fontSize: 18 ) ) ,
] ,
) ,
) ,
] ,
) ;
} ,
) ;
} ) ;
}
}