2023-05-06 21:14:12 +00:00
import ' package:flutter/material.dart ' ;
import ' package:http/http.dart ' as http ;
2023-05-07 17:58:01 +00:00
import ' dart:convert ' ;
2023-05-29 19:10:14 +00:00
import ' dart:math ' ;
2023-05-07 17:58:01 +00:00
import ' package:tetra_stats/data_objects/tetrio.dart ' ;
2023-05-16 20:07:18 +00:00
import ' package:tetra_stats/services/tetrio_crud.dart ' ;
2023-05-20 20:41:01 +00:00
import ' package:tetra_stats/services/sqlite_db_controller.dart ' ;
2023-05-06 21:14:12 +00:00
2023-05-30 20:37:10 +00:00
extension StringExtension on String {
String capitalize ( ) {
2023-06-03 14:56:08 +00:00
return " ${ this [ 0 ] . toUpperCase ( ) } ${ substring ( 1 ) . toLowerCase ( ) } " ;
2023-05-30 20:37:10 +00:00
}
}
2023-06-03 14:56:08 +00:00
String _searchFor = " dan63047 " ;
2023-05-29 19:10:14 +00:00
late Future < TetrioPlayer > me ;
2023-05-20 20:41:01 +00:00
DB db = DB ( ) ;
2023-05-16 20:07:18 +00:00
TetrioService teto = TetrioService ( ) ;
2023-05-29 19:10:14 +00:00
const allowedHeightForPlayerIdInPixels = 40.0 ;
2023-05-25 19:21:56 +00:00
const allowedHeightForPlayerBioInPixels = 30.0 ;
const givenTextHeightByScreenPercentage = 0.3 ;
2023-06-03 14:56:08 +00:00
enum ThreeDotsItems { compare , states , settings }
2023-05-11 16:08:42 +00:00
2023-05-07 17:58:01 +00:00
class MainView extends StatefulWidget {
const MainView ( { Key ? key } ) : super ( key: key ) ;
2023-05-06 21:14:12 +00:00
2023-05-29 19:10:14 +00:00
String get title = > " Tetra Stats: $ _searchFor " ;
2023-05-07 17:58:01 +00:00
@ override
2023-06-03 14:56:08 +00:00
State < MainView > createState ( ) = > _MyHomePageState ( ) ;
2023-05-07 17:58:01 +00:00
}
2023-05-29 19:10:14 +00:00
Future < TetrioPlayer > fetchTetrioPlayer ( String user ) async {
2023-06-01 21:30:38 +00:00
var url = Uri . https ( ' ch.tetr.io ' , ' api/users/ ${ user . toLowerCase ( ) } ' ) ;
2023-05-29 19:10:14 +00:00
final response = await http . get ( url ) ;
// final response = await http.get(Uri.parse('https://ch.tetr.io/'));
2023-05-07 17:58:01 +00:00
2023-05-29 19:10:14 +00:00
if ( response . statusCode = = 200 ) {
// If the server did return a 200 OK response,
// then parse the JSON.
2023-05-30 20:37:10 +00:00
return jsonDecode ( response . body ) [ ' success ' ]
? TetrioPlayer . fromJson (
jsonDecode ( response . body ) [ ' data ' ] [ ' user ' ] ,
DateTime . fromMillisecondsSinceEpoch (
jsonDecode ( response . body ) [ ' cache ' ] [ ' cached_at ' ] ,
isUtc: true ) )
: throw Exception ( " User doesn't exist " ) ;
2023-05-29 19:10:14 +00:00
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception ( ' Failed to fetch player ' ) ;
2023-05-07 17:58:01 +00:00
}
2023-05-29 19:10:14 +00:00
}
2023-05-11 16:08:42 +00:00
2023-06-03 14:56:08 +00:00
class _MyHomePageState extends State < MainView >
with SingleTickerProviderStateMixin {
final bodyGlobalKey = GlobalKey ( ) ;
final List < Widget > myTabs = [
Tab ( text: " Tetra League " ) ,
Tab ( text: " 40 Lines " ) ,
Tab ( text: " Blitz " ) ,
Tab ( text: " Other " ) ,
] ;
2023-05-29 19:10:14 +00:00
bool _searchBoolean = false ;
2023-06-03 14:56:08 +00:00
late TabController _tabController ;
late ScrollController _scrollController ;
late bool fixedScroll ;
2023-05-29 19:10:14 +00:00
Widget _searchTextField ( ) {
return TextField (
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 ) = > setState ( ( ) {
me = fetchTetrioPlayer ( value ) ;
_searchFor = value ;
} ) ,
) ;
2023-05-06 21:14:12 +00:00
}
2023-06-03 14:56:08 +00:00
@ override
void initState ( ) {
_scrollController = ScrollController ( ) ;
//_scrollController.addListener(_scrollListener);
_tabController = TabController ( length: 4 , vsync: this ) ;
//_tabController.addListener(_smoothScrollToTop);
me = fetchTetrioPlayer ( " dan63047 " ) ;
super . initState ( ) ;
}
@ override
void dispose ( ) {
_tabController . dispose ( ) ;
_scrollController . dispose ( ) ;
super . dispose ( ) ;
}
_scrollListener ( ) {
if ( fixedScroll ) {
_scrollController . jumpTo ( 0 ) ;
}
}
_smoothScrollToTop ( ) {
_scrollController . animateTo (
0 ,
duration: Duration ( microseconds: 300 ) ,
curve: Curves . ease ,
) ;
setState ( ( ) {
fixedScroll = _tabController . index = = 2 ;
} ) ;
}
_buildTabContext ( int lineCount ) = > Container (
child: ListView . builder (
physics: const ClampingScrollPhysics ( ) ,
itemCount: lineCount ,
itemBuilder: ( BuildContext context , int index ) {
return Text ( ' some content ' ) ;
} ,
) ,
) ;
2023-05-06 21:14:12 +00:00
@ override
Widget build ( BuildContext context ) {
2023-05-07 17:58:01 +00:00
return Scaffold (
2023-05-25 19:21:56 +00:00
drawer: NavDrawer ( ) ,
2023-05-29 19:10:14 +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 ,
actions: [
! _searchBoolean
? IconButton (
onPressed: ( ) {
setState ( ( ) {
//add
_searchBoolean = true ;
} ) ;
} ,
icon: const Icon ( Icons . search ) ,
tooltip: " Search player " ,
)
: IconButton (
onPressed: ( ) {
setState ( ( ) {
//add
_searchBoolean = false ;
} ) ;
} ,
icon: const Icon ( Icons . clear ) ,
tooltip: " Close search " ,
2023-05-25 19:21:56 +00:00
) ,
2023-05-29 19:10:14 +00:00
PopupMenuButton (
2023-06-03 14:56:08 +00:00
itemBuilder: ( BuildContext context ) = >
< PopupMenuEntry < ThreeDotsItems > > [
const PopupMenuItem < ThreeDotsItems > (
value: ThreeDotsItems . compare ,
2023-05-29 19:10:14 +00:00
child: Text ( ' Compare ' ) ,
2023-05-25 19:21:56 +00:00
) ,
2023-06-03 14:56:08 +00:00
const PopupMenuItem < ThreeDotsItems > (
value: ThreeDotsItems . states ,
2023-05-29 19:10:14 +00:00
child: Text ( ' States ' ) ,
2023-05-25 19:21:56 +00:00
) ,
2023-06-03 14:56:08 +00:00
const PopupMenuItem < ThreeDotsItems > (
value: ThreeDotsItems . settings ,
2023-05-29 19:10:14 +00:00
child: Text ( ' Settings ' ) ,
2023-05-25 19:21:56 +00:00
) ,
2023-05-29 19:10:14 +00:00
] ,
2023-05-25 19:21:56 +00:00
) ,
] ,
2023-05-06 21:14:12 +00:00
) ,
2023-05-29 19:10:14 +00:00
body: SafeArea (
child: FutureBuilder < TetrioPlayer > (
future: me ,
builder: ( context , snapshot ) {
if ( snapshot . hasData ) {
2023-06-03 14:56:08 +00:00
bool bigScreen = MediaQuery . of ( context ) . size . width > 768 ;
return NestedScrollView (
controller: _scrollController ,
headerSliverBuilder: ( context , value ) {
return [
SliverToBoxAdapter (
child: _UserThingy ( player: snapshot . data ! ) ) ,
SliverToBoxAdapter (
child: TabBar (
controller: _tabController ,
isScrollable: true ,
tabs: myTabs ,
) ,
) ,
] ;
} ,
body: TabBarView (
controller: _tabController ,
children: [
ListView . builder (
physics: const ClampingScrollPhysics ( ) ,
itemCount: 1 ,
itemBuilder: ( BuildContext context , int index ) {
return Column (
children: ( snapshot . data ! . tlSeason1 . gamesPlayed > 0 )
? [
Text ( " Tetra League " ,
style: TextStyle (
fontFamily:
" Eurostile Round Extended " ,
fontSize: bigScreen ? 42 : 28 ) ) ,
if ( snapshot . data ! . tlSeason1 . gamesPlayed > =
10 )
Center (
child: Wrap (
direction: Axis . horizontal ,
alignment: WrapAlignment . center ,
crossAxisAlignment:
WrapCrossAlignment . center ,
clipBehavior: Clip . hardEdge ,
children: [
Image . asset (
" res/tetrio_tl_alpha_ranks/ ${ snapshot . data ! . tlSeason1 . rank } .png " ,
height: bigScreen ? 256 : 128 ,
) ,
Text (
" ${ snapshot . data ! . tlSeason1 . rating . toStringAsFixed ( 2 ) } TR " ,
style: TextStyle (
fontFamily:
" Eurostile Round Extended " ,
fontSize:
bigScreen ? 42 : 28 ) ) ,
Text (
" Top ${ ( snapshot . data ! . tlSeason1 . percentile * 100 ) . toStringAsFixed ( 2 ) } % ( ${ snapshot . data ! . tlSeason1 . percentileRank . toUpperCase ( ) } ) • Top Rank: ${ snapshot . data ! . tlSeason1 . bestRank . toUpperCase ( ) } • Glicko: ${ snapshot . data ! . tlSeason1 . glicko ? . toStringAsFixed ( 2 ) } ± ${ snapshot . data ! . tlSeason1 . rd ? . toStringAsFixed ( 2 ) } ${ snapshot . data ! . tlSeason1 . decaying ? ' • Decaying ' : ' ' } " ,
textAlign: TextAlign . center ,
) ,
] ,
) ,
)
else
Row (
mainAxisAlignment:
MainAxisAlignment . center ,
children: [
Column (
children: [
Text (
" ${ 10 - snapshot . data ! . tlSeason1 . gamesPlayed } games until being ranked " ,
softWrap: true ,
style: TextStyle (
fontFamily:
" Eurostile Round Extended " ,
fontSize: bigScreen ? 42 : 28 ,
overflow:
TextOverflow . visible ,
) ) ,
] ,
)
] ,
) ,
Padding (
padding:
const EdgeInsets . fromLTRB ( 0 , 16 , 0 , 48 ) ,
child: Wrap (
direction: Axis . horizontal ,
alignment: WrapAlignment . center ,
spacing: 25 ,
crossAxisAlignment:
WrapCrossAlignment . start ,
clipBehavior: Clip . hardEdge ,
children: [
if ( snapshot . data ! . tlSeason1 . apm ! =
null )
_StatCellNum (
playerStat:
snapshot . data ! . tlSeason1 . apm ! ,
isScreenBig: bigScreen ,
fractionDigits: 2 ,
playerStatLabel:
" Attack \n Per Minute " ) ,
if ( snapshot . data ! . tlSeason1 . pps ! =
null )
_StatCellNum (
playerStat:
snapshot . data ! . tlSeason1 . pps ! ,
isScreenBig: bigScreen ,
fractionDigits: 2 ,
playerStatLabel:
" Pieces \n Per Second " ) ,
if ( snapshot . data ! . tlSeason1 . apm ! =
null )
_StatCellNum (
playerStat:
snapshot . data ! . tlSeason1 . vs ! ,
isScreenBig: bigScreen ,
fractionDigits: 2 ,
playerStatLabel: " Versus \n Score " ) ,
if ( snapshot . data ! . tlSeason1 . standing >
0 )
_StatCellNum (
playerStat: snapshot
. data ! . tlSeason1 . standing ,
isScreenBig: bigScreen ,
playerStatLabel:
" Leaderboard \n placement " ) ,
if ( snapshot
. data ! . tlSeason1 . standingLocal >
0 )
_StatCellNum (
playerStat: snapshot . data !
. tlSeason1 . standingLocal ,
isScreenBig: bigScreen ,
playerStatLabel:
" Country LB \n placement " ) ,
_StatCellNum (
playerStat: snapshot
. data ! . tlSeason1 . gamesPlayed ,
isScreenBig: bigScreen ,
playerStatLabel: " Games \n played " ) ,
_StatCellNum (
playerStat: snapshot
. data ! . tlSeason1 . gamesWon ,
isScreenBig: bigScreen ,
playerStatLabel: " Games \n won " ) ,
_StatCellNum (
playerStat: snapshot
. data ! . tlSeason1 . winrate *
100 ,
isScreenBig: bigScreen ,
fractionDigits: 2 ,
playerStatLabel:
" Winrate \n precentage " ) ,
] ,
) ,
) ,
if ( snapshot . data ! . tlSeason1 . apm ! = null & &
snapshot . data ! . tlSeason1 . pps ! = null & &
snapshot . data ! . tlSeason1 . apm ! = null )
Column (
children: [
Text ( " Nerd Stats " ,
style: TextStyle (
fontFamily:
" Eurostile Round Extended " ,
fontSize: bigScreen ? 42 : 28 ) ) ,
Padding (
padding: const EdgeInsets . fromLTRB (
0 , 16 , 0 , 48 ) ,
child: Wrap (
direction: Axis . horizontal ,
alignment: WrapAlignment . center ,
spacing: 25 ,
crossAxisAlignment:
WrapCrossAlignment . start ,
clipBehavior: Clip . hardEdge ,
children: [
_StatCellNum (
playerStat: snapshot
. data ! . tlSeason1 . app ! ,
isScreenBig: bigScreen ,
fractionDigits: 3 ,
playerStatLabel:
" Attack \n Per Piece " ) ,
_StatCellNum (
playerStat: snapshot
. data ! . tlSeason1 . vsapm ! ,
isScreenBig: bigScreen ,
fractionDigits: 3 ,
playerStatLabel: " VS/APM " ) ,
_StatCellNum (
playerStat: snapshot
. data ! . tlSeason1 . dss ! ,
isScreenBig: bigScreen ,
fractionDigits: 3 ,
playerStatLabel:
" Downstack \n Per Second " ) ,
_StatCellNum (
playerStat: snapshot
. data ! . tlSeason1 . dsp ! ,
isScreenBig: bigScreen ,
fractionDigits: 3 ,
playerStatLabel:
" Downstack \n Per Piece " ) ,
_StatCellNum (
playerStat: snapshot . data !
. tlSeason1 . cheese ! ,
isScreenBig: bigScreen ,
fractionDigits: 2 ,
playerStatLabel:
" Cheese \n Index " ) ,
_StatCellNum (
playerStat: snapshot
. data ! . tlSeason1 . gbe ! ,
isScreenBig: bigScreen ,
fractionDigits: 3 ,
playerStatLabel:
" Garbage \n Efficiency " ) ,
_StatCellNum (
playerStat: snapshot . data !
. tlSeason1 . nyaapp ! ,
isScreenBig: bigScreen ,
fractionDigits: 3 ,
playerStatLabel:
" Weighted \n APP " ) ,
_StatCellNum (
playerStat: snapshot
. data ! . tlSeason1 . area ! ,
isScreenBig: bigScreen ,
fractionDigits: 1 ,
playerStatLabel: " Area " )
] ) ,
)
] ,
)
]
: [
Text ( " That user never played Tetra League " ,
style: TextStyle (
fontFamily:
" Eurostile Round Extended " ,
fontSize: bigScreen ? 42 : 28 ) ) ,
] ,
) ;
} ,
) ,
Container (
child: Text ( " 40 Lines " ,
style: TextStyle (
fontFamily: " Eurostile Round Extended " ,
fontSize: bigScreen ? 42 : 28 ) ) ,
) ,
Container (
child: Text ( " Blitz " ,
style: TextStyle (
fontFamily: " Eurostile Round Extended " ,
fontSize: bigScreen ? 42 : 28 ) ) ,
) ,
Container (
child: Text ( " Other info " ,
style: TextStyle (
fontFamily: " Eurostile Round Extended " ,
fontSize: bigScreen ? 42 : 28 ) ) ,
) ,
] ,
) ,
2023-05-29 19:10:14 +00:00
) ;
} else if ( snapshot . hasError ) {
2023-06-01 21:30:38 +00:00
return Center (
child: Text ( ' ${ snapshot . error } ' ,
style: const TextStyle (
fontFamily: " Eurostile Round Extended " ,
fontSize: 42 ) ) ) ;
2023-05-29 19:10:14 +00:00
}
2023-06-01 21:30:38 +00:00
return const Center (
child: CircularProgressIndicator (
color: Colors . white ,
) ) ;
2023-05-29 19:10:14 +00:00
} ,
) ,
) ,
2023-05-25 19:21:56 +00:00
) ;
}
}
2023-05-07 17:58:01 +00:00
2023-05-25 19:21:56 +00:00
class NavDrawer extends StatelessWidget {
@ override
Widget build ( BuildContext context ) {
return Drawer (
child: ListView (
padding: EdgeInsets . zero ,
children: < Widget > [
const DrawerHeader (
2023-05-29 19:10:14 +00:00
child: Text (
' Side menu ' ,
style: TextStyle ( color: Colors . white , fontSize: 25 ) ,
) ) ,
2023-05-25 19:21:56 +00:00
ListTile (
leading: const Icon ( Icons . input ) ,
title: const Text ( ' Welcome ' ) ,
onTap: ( ) = > { } ,
) ,
ListTile (
leading: const Icon ( Icons . verified_user ) ,
title: const Text ( ' Profile ' ) ,
onTap: ( ) = > { Navigator . of ( context ) . pop ( ) } ,
) ,
ListTile (
leading: const Icon ( Icons . settings ) ,
title: const Text ( ' Settings ' ) ,
onTap: ( ) = > { Navigator . of ( context ) . pop ( ) } ,
) ,
ListTile (
leading: const Icon ( Icons . border_color ) ,
title: const Text ( ' Feedback ' ) ,
onTap: ( ) = > { Navigator . of ( context ) . pop ( ) } ,
) ,
ListTile (
leading: const Icon ( Icons . exit_to_app ) ,
title: const Text ( ' Logout ' ) ,
onTap: ( ) = > { Navigator . of ( context ) . pop ( ) } ,
) ,
ListTile (
leading: const Icon ( Icons . input ) ,
title: const Text ( ' Welcome ' ) ,
onTap: ( ) = > { } ,
) ,
ListTile (
leading: const Icon ( Icons . verified_user ) ,
title: const Text ( ' Profile ' ) ,
onTap: ( ) = > { Navigator . of ( context ) . pop ( ) } ,
) ,
ListTile (
leading: const Icon ( Icons . settings ) ,
title: const Text ( ' Settings ' ) ,
onTap: ( ) = > { Navigator . of ( context ) . pop ( ) } ,
) ,
ListTile (
leading: const Icon ( Icons . border_color ) ,
title: const Text ( ' Feedback ' ) ,
onTap: ( ) = > { Navigator . of ( context ) . pop ( ) } ,
) ,
ListTile (
leading: const Icon ( Icons . exit_to_app ) ,
title: const Text ( ' Logout ' ) ,
onTap: ( ) = > { Navigator . of ( context ) . pop ( ) } ,
) ,
ListTile (
leading: const Icon ( Icons . input ) ,
title: const Text ( ' Welcome ' ) ,
onTap: ( ) = > { } ,
) ,
ListTile (
leading: const Icon ( Icons . verified_user ) ,
title: const Text ( ' Profile ' ) ,
onTap: ( ) = > { Navigator . of ( context ) . pop ( ) } ,
) ,
ListTile (
leading: const Icon ( Icons . settings ) ,
title: const Text ( ' Settings ' ) ,
onTap: ( ) = > { Navigator . of ( context ) . pop ( ) } ,
) ,
ListTile (
leading: const Icon ( Icons . border_color ) ,
title: const Text ( ' Feedback ' ) ,
onTap: ( ) = > { Navigator . of ( context ) . pop ( ) } ,
) ,
ListTile (
leading: const Icon ( Icons . exit_to_app ) ,
title: const Text ( ' Logout ' ) ,
onTap: ( ) = > { Navigator . of ( context ) . pop ( ) } ,
2023-05-07 17:58:01 +00:00
) ,
] ,
2023-05-06 21:14:12 +00:00
) ,
) ;
}
2023-05-11 16:08:42 +00:00
}
2023-05-29 19:10:14 +00:00
2023-06-03 14:56:08 +00:00
class _StatCellNum extends StatelessWidget {
const _StatCellNum (
{ required this . playerStat ,
required this . playerStatLabel ,
required this . isScreenBig ,
this . snackBar ,
this . fractionDigits } ) ;
final num playerStat ;
final String playerStatLabel ;
final bool isScreenBig ;
final String ? snackBar ;
final int ? fractionDigits ;
@ override
Widget build ( BuildContext context ) {
return Column (
children: [
Text (
fractionDigits ! = null
? playerStat . toStringAsFixed ( fractionDigits ! )
: playerStat . floor ( ) . toString ( ) ,
style: TextStyle (
fontFamily: " Eurostile Round Extended " ,
fontSize: isScreenBig ? 32 : 24 ,
) ,
) ,
snackBar = = null
? Text (
playerStatLabel ,
textAlign: TextAlign . center ,
style: const TextStyle (
fontFamily: " Eurostile Round " ,
fontSize: 16 ,
) ,
)
: TextButton (
onPressed: ( ) {
ScaffoldMessenger . of ( context )
. showSnackBar ( SnackBar ( content: Text ( snackBar ! ) ) ) ;
} ,
style: ButtonStyle (
padding: MaterialStateProperty . all ( EdgeInsets . zero ) ) ,
child: Text (
playerStatLabel ,
textAlign: TextAlign . center ,
style: const TextStyle (
fontFamily: " Eurostile Round " ,
fontSize: 16 ,
) ,
) ) ,
] ,
) ;
}
}
2023-05-29 19:10:14 +00:00
class _UserThingy extends StatelessWidget {
final TetrioPlayer player ;
2023-05-30 20:37:10 +00:00
_UserThingy ( { Key ? key , required this . player } ) : super ( key: key ) ;
2023-05-29 19:10:14 +00:00
@ override
Widget build ( BuildContext context ) {
return LayoutBuilder ( builder: ( context , constraints ) {
2023-06-01 21:30:38 +00:00
bool bigScreen = constraints . maxWidth > 768 ;
double bannerHeight = bigScreen ? 240 : 120 ;
double pfpHeight = bigScreen ? 64 : 32 ;
2023-05-29 19:10:14 +00:00
return Column (
children: [
Flex (
direction: Axis . vertical ,
mainAxisSize: MainAxisSize . min ,
mainAxisAlignment: MainAxisAlignment . center ,
children: [
2023-05-30 20:37:10 +00:00
Stack (
alignment: Alignment . topCenter ,
children: [
if ( player . bannerRevision ! = null )
Image . network (
" https://tetr.io/user-content/banners/ ${ player . userId } .jpg?rv= ${ player . bannerRevision } " ,
fit: BoxFit . cover ,
2023-06-01 21:30:38 +00:00
height: bannerHeight ,
2023-05-30 20:37:10 +00:00
) ,
Container (
padding: EdgeInsets . fromLTRB (
2023-06-01 21:30:38 +00:00
0 ,
player . bannerRevision ! = null
? bannerHeight / 1.4
: pfpHeight ,
0 ,
0 ) ,
2023-05-30 20:37:10 +00:00
child: ClipRRect (
borderRadius: BorderRadius . circular ( 1000 ) ,
2023-06-01 21:30:38 +00:00
child: player . role = = " banned "
? Image . asset (
" res/avatars/tetrio_banned.png " ,
fit: BoxFit . fitHeight ,
height: 128 ,
)
: Image . network (
" https://tetr.io/user-content/avatars/ ${ player . userId } .jpg?rv= ${ player . avatarRevision } " ,
fit: BoxFit . fitHeight ,
height: 128 ,
errorBuilder: ( context , error , stackTrace ) = >
Image . asset (
" res/avatars/tetrio_anon.png " ,
fit: BoxFit . fitHeight ,
height: 128 ,
) ,
) ,
2023-05-30 20:37:10 +00:00
) ,
) ,
] ,
2023-05-29 19:10:14 +00:00
) ,
Flexible (
child: Column (
children: [
Text ( player . username ,
2023-06-01 21:30:38 +00:00
style: TextStyle (
2023-05-29 19:10:14 +00:00
fontFamily: " Eurostile Round Extended " ,
2023-06-01 21:30:38 +00:00
fontSize: bigScreen ? 42 : 28 ) ) ,
2023-05-29 19:10:14 +00:00
Text (
player . userId ,
style: const TextStyle (
fontFamily: " Eurostile Round Condensed " ,
fontSize: 14 ) ,
) ,
] ,
) ,
) ,
] ,
) ,
2023-05-30 20:37:10 +00:00
( player . role ! = " banned " )
? Wrap (
direction: Axis . horizontal ,
alignment: WrapAlignment . center ,
spacing: 25 ,
crossAxisAlignment: WrapCrossAlignment . start ,
clipBehavior: Clip . hardEdge , // hard WHAT???
children: [
_StatCellNum (
playerStat: player . level ,
2023-06-01 21:30:38 +00:00
playerStatLabel: " XP Level " ,
isScreenBig: bigScreen ,
2023-05-30 20:37:10 +00:00
snackBar:
" ${ player . xp . floor ( ) . toString ( ) } XP, ${ ( ( player . level - player . level . floor ( ) ) * 100 ) . toStringAsFixed ( 2 ) } % until next level " ,
) ,
if ( player . gameTime > = Duration . zero )
_StatCellNum (
2023-06-03 14:56:08 +00:00
playerStat: player . gameTime . inHours ,
2023-05-30 20:37:10 +00:00
playerStatLabel: " Hours \n Played " ,
2023-06-01 21:30:38 +00:00
isScreenBig: bigScreen ,
2023-05-30 20:37:10 +00:00
snackBar: player . gameTime . toString ( ) ,
) ,
if ( player . gamesPlayed > = 0 )
_StatCellNum (
playerStat: player . gamesPlayed ,
2023-06-01 21:30:38 +00:00
isScreenBig: bigScreen ,
playerStatLabel: " Online \n Games " ) ,
2023-05-30 20:37:10 +00:00
if ( player . gamesWon > = 0 )
_StatCellNum (
playerStat: player . gamesWon ,
2023-06-01 21:30:38 +00:00
isScreenBig: bigScreen ,
2023-05-30 20:37:10 +00:00
playerStatLabel: " Games \n Won " ) ,
if ( player . friendCount > 0 )
_StatCellNum (
playerStat: player . friendCount ,
2023-06-01 21:30:38 +00:00
isScreenBig: bigScreen ,
2023-05-30 20:37:10 +00:00
playerStatLabel: " Friends " ) ,
] ,
)
: Text (
" BANNED " ,
textAlign: TextAlign . center ,
2023-06-01 21:30:38 +00:00
style: TextStyle (
2023-05-30 20:37:10 +00:00
fontFamily: " Eurostile Round Extended " ,
fontWeight: FontWeight . w900 ,
color: Colors . red ,
2023-06-01 21:30:38 +00:00
fontSize: bigScreen ? 60 : 45 ,
2023-05-30 20:37:10 +00:00
) ,
) ,
2023-06-03 14:56:08 +00:00
if ( player . badstanding ! = null & & player . badstanding ! )
Text (
" BAD STANDING " ,
textAlign: TextAlign . center ,
style: TextStyle (
fontFamily: " Eurostile Round Extended " ,
fontWeight: FontWeight . w900 ,
color: Colors . red ,
fontSize: bigScreen ? 60 : 45 ,
) ,
) ,
2023-06-01 21:30:38 +00:00
Row (
mainAxisAlignment: MainAxisAlignment . center ,
children: [
2023-06-01 22:01:18 +00:00
Expanded (
child: Text (
2023-06-03 14:56:08 +00:00
" ${ player . country ! = null ? " $ {player.country?.toUpperCase() } • " : " " } $ { player . role . capitalize ( ) } account $ { player . registrationTime = = null ? " that was from very beginning " : ' created ${ player . registrationTime } ' } • $ { player . supporterTier = = 0 ? " Not a supporter " : " Supporter tier ${ player . supporterTier } " } " ,
2023-06-01 22:01:18 +00:00
textAlign: TextAlign . center ,
2023-06-01 21:30:38 +00:00
style: const TextStyle (
fontFamily: " Eurostile Round " ,
fontSize: 16 ,
) ) ,
2023-06-01 22:01:18 +00:00
)
2023-06-01 21:30:38 +00:00
] ,
) ,
2023-05-29 19:10:14 +00:00
Wrap (
direction: Axis . horizontal ,
alignment: WrapAlignment . center ,
spacing: 25 ,
crossAxisAlignment: WrapCrossAlignment . start ,
2023-05-30 20:37:10 +00:00
clipBehavior: Clip . hardEdge ,
2023-05-29 19:10:14 +00:00
children: [
2023-06-01 21:30:38 +00:00
for ( var badge in player . badges )
IconButton (
onPressed: ( ) = > showDialog < void > (
context: context ,
barrierDismissible: false , // user must tap button!
builder: ( BuildContext context ) {
return AlertDialog (
title: Text (
badge . label ,
style: const TextStyle (
fontFamily: " Eurostile Round Extended " ) ,
) ,
content: SingleChildScrollView (
child: ListBody (
children: [
Wrap (
direction: Axis . horizontal ,
alignment: WrapAlignment . center ,
crossAxisAlignment:
WrapCrossAlignment . center ,
spacing: 25 ,
children: [
Image . asset (
" res/tetrio_badges/ ${ badge . badgeId } .png " ) ,
Text ( badge . ts ! = null
? " Obtained ${ badge . ts } "
: " That badge was assigned manualy by TETR.IO admins " ) ,
] ,
)
] ,
) ,
) ,
actions: < Widget > [
TextButton (
child: const Text ( ' OK ' ) ,
onPressed: ( ) {
Navigator . of ( context ) . pop ( ) ;
} ,
) ,
] ,
) ;
} ,
) ,
tooltip: badge . label ,
icon: Image . asset (
" res/tetrio_badges/ ${ badge . badgeId } .png " ,
height: 64 ,
width: 64 ,
) )
2023-05-29 19:10:14 +00:00
] ,
2023-05-30 20:37:10 +00:00
) ,
2023-05-29 19:10:14 +00:00
] ,
) ;
} ) ;
}
}