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 ;
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-21 22:05:14 +00:00
const Tab ( text: " TL 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-22 19:02:49 +00:00
bool isTracking = await teto . isPlayerTracking ( me . userId ) ;
List < TetrioPlayer > states = [ ] ;
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-21 22:05:14 +00:00
Map < String , dynamic > records = await teto . fetchRecords ( me . userId ) ;
2023-06-22 19:02:49 +00:00
return [ me , records , states , isTracking ] ;
2023-06-21 22:05:14 +00:00
}
2023-06-23 18:38:15 +00:00
Future < List < TetraLeagueAlphaRecord > > getTLMatches ( String userID ) async {
var fetched = await teto . getTLStream ( userID ) ;
bool isTracked = await teto . isPlayerTracking ( userID ) ;
if ( ! isTracked ) return fetched . records ;
teto . saveTLMatchesFromStream ( fetched ) ;
var fromdb = await teto . getTLMatchesbyPlayerID ( userID ) ;
for ( var match in fetched . records ) {
if ( ! fromdb . contains ( match ) ) fromdb . add ( match ) ;
}
fromdb . 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 ;
} ) ;
return fromdb ;
}
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 ) {
return NestedScrollView (
controller: _scrollController ,
headerSliverBuilder: ( context , value ) {
return [
SliverToBoxAdapter (
child: UserThingy (
2023-06-21 22:05:14 +00:00
player: snapshot . data ! [ 0 ] ,
2023-06-17 21:50:52 +00:00
showStateTimestamp: false ,
setState: _justUpdate ,
) ) ,
SliverToBoxAdapter (
child: TabBar (
controller: _tabController ,
isScrollable: true ,
tabs: myTabs ,
) ,
) ,
] ;
} ,
body: TabBarView (
controller: _tabController ,
children: [
2023-06-20 20:53:28 +00:00
TLThingy (
2023-06-21 22:05:14 +00:00
tl: snapshot . data ! [ 0 ] . tlSeason1 ,
userID: snapshot . data ! [ 0 ] . userId ) ,
2023-06-23 18:38:15 +00:00
_TLRecords ( userID: snapshot . data ! [ 0 ] . userId , get : getTLMatches , ) ,
2023-06-22 19:02:49 +00:00
_TLHistory ( states: snapshot . data ! [ 2 ] ) ,
2023-06-20 20:53:28 +00:00
_RecordThingy (
2023-06-21 22:05:14 +00:00
record: ( snapshot . data ! [ 1 ] [ ' sprint ' ] . isNotEmpty )
? snapshot . data ! [ 1 ] [ ' sprint ' ] [ 0 ]
2023-06-20 20:53:28 +00:00
: null ) ,
_RecordThingy (
2023-06-21 22:05:14 +00:00
record: ( snapshot . data ! [ 1 ] [ ' blitz ' ] . isNotEmpty )
? snapshot . data ! [ 1 ] [ ' blitz ' ] [ 0 ]
2023-06-20 20:53:28 +00:00
: null ) ,
_OtherThingy (
2023-06-21 22:05:14 +00:00
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-23 18:38:15 +00:00
final Future < List < TetraLeagueAlphaRecord > > Function ( String user ) get ;
2023-06-20 20:53:28 +00:00
2023-06-23 18:38:15 +00:00
const _TLRecords ( { required this . userID , required this . get } ) ;
2023-06-20 20:53:28 +00:00
@ override
Widget build ( BuildContext context ) {
return FutureBuilder (
2023-06-23 18:38:15 +00:00
future: get ( userID ) ,
2023-06-20 20:53:28 +00:00
builder: ( context , snapshot ) {
switch ( snapshot . connectionState ) {
case ConnectionState . none:
case ConnectionState . waiting:
case ConnectionState . active:
return const Center (
child: CircularProgressIndicator ( color: Colors . white ) ) ;
case ConnectionState . done:
if ( snapshot . hasError ) {
2023-06-21 19:17:39 +00:00
return Text ( snapshot . error . toString ( ) , style: const TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 28 ) ) ;
2023-06-20 20:53:28 +00:00
} else {
return ListView (
physics: const ClampingScrollPhysics ( ) ,
2023-06-23 18:38:15 +00:00
children: ( snapshot . data ! . isNotEmpty )
? [ for ( var value in snapshot . data ! ) ListTile (
2023-06-20 20:53:28 +00:00
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 , ) ) ,
2023-06-21 19:17:39 +00:00
title: Text ( " vs. ${ value . endContext . firstWhere ( ( element ) = > element . userId ! = userID ) . username } " ) ,
2023-06-23 18:38:15 +00:00
subtitle: Text ( dateFormat . format ( value . timestamp ) ) ,
trailing: Column ( mainAxisAlignment: MainAxisAlignment . center ,
2023-06-20 20:53:28 +00:00
children: [
2023-06-23 18:38:15 +00:00
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 ) ) ,
2023-06-20 20:53:28 +00:00
] ) ,
2023-06-21 19:17:39 +00:00
onTap: ( ) { Navigator . push (
context ,
MaterialPageRoute (
builder: ( context ) = > TlMatchResultView ( record: value , initPlayerId: userID ) ,
) ,
) ; } ,
2023-06-20 20:53:28 +00:00
) ]
2023-06-22 19:02:49 +00:00
: [ const Center ( child: Text ( " No records " , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 28 ) ) ) ] ,
2023-06-20 20:53:28 +00:00
) ;
}
}
} ) ;
}
}
2023-06-22 19:02:49 +00:00
class _TLHistory extends StatelessWidget {
final List < TetrioPlayer > states ;
2023-06-23 18:38:15 +00:00
const _TLHistory ( { required this . states } ) ;
2023-06-22 19:02:49 +00:00
@ override
Widget build ( BuildContext context ) {
bool bigScreen = MediaQuery . of ( context ) . size . width > 768 ;
2023-06-23 18:38:15 +00:00
List < FlSpot > trData = [ for ( var state in states ) if ( state . tlSeason1 . gamesPlayed > 9 ) FlSpot ( state . state . millisecondsSinceEpoch . toDouble ( ) , state . tlSeason1 . rating ) ] ;
List < FlSpot > apmData = [ for ( var state in states ) if ( state . tlSeason1 . apm ! = null ) FlSpot ( state . state . millisecondsSinceEpoch . toDouble ( ) , state . tlSeason1 . apm ! ) ] ;
List < FlSpot > ppsData = [ for ( var state in states ) if ( state . tlSeason1 . pps ! = null ) FlSpot ( state . state . millisecondsSinceEpoch . toDouble ( ) , state . tlSeason1 . pps ! ) ] ;
List < FlSpot > vsData = [ for ( var state in states ) if ( state . tlSeason1 . vs ! = null ) FlSpot ( state . state . millisecondsSinceEpoch . toDouble ( ) , state . tlSeason1 . vs ! ) ] ;
List < FlSpot > appData = [ for ( var state in states ) if ( state . tlSeason1 . nerdStats ! = null ) FlSpot ( state . state . millisecondsSinceEpoch . toDouble ( ) , state . tlSeason1 . nerdStats ! . app ) ] ;
List < FlSpot > dssData = [ for ( var state in states ) if ( state . tlSeason1 . nerdStats ! = null ) FlSpot ( state . state . millisecondsSinceEpoch . toDouble ( ) , state . tlSeason1 . nerdStats ! . dss ) ] ;
List < FlSpot > dspData = [ for ( var state in states ) if ( state . tlSeason1 . nerdStats ! = null ) FlSpot ( state . state . millisecondsSinceEpoch . toDouble ( ) , state . tlSeason1 . nerdStats ! . dsp ) ] ;
List < FlSpot > appdspData = [ for ( var state in states ) if ( state . tlSeason1 . nerdStats ! = null ) FlSpot ( state . state . millisecondsSinceEpoch . toDouble ( ) , state . tlSeason1 . nerdStats ! . appdsp ) ] ;
List < FlSpot > vsapmData = [ for ( var state in states ) if ( state . tlSeason1 . nerdStats ! = null ) FlSpot ( state . state . millisecondsSinceEpoch . toDouble ( ) , state . tlSeason1 . nerdStats ! . vsapm ) ] ;
List < FlSpot > cheeseData = [ for ( var state in states ) if ( state . tlSeason1 . nerdStats ! = null ) FlSpot ( state . state . millisecondsSinceEpoch . toDouble ( ) , state . tlSeason1 . nerdStats ! . cheese ) ] ;
List < FlSpot > gbeData = [ for ( var state in states ) if ( state . tlSeason1 . nerdStats ! = null ) FlSpot ( state . state . millisecondsSinceEpoch . toDouble ( ) , state . tlSeason1 . nerdStats ! . gbe ) ] ;
List < FlSpot > nyaappData = [ for ( var state in states ) if ( state . tlSeason1 . nerdStats ! = null ) FlSpot ( state . state . millisecondsSinceEpoch . toDouble ( ) , state . tlSeason1 . nerdStats ! . nyaapp ) ] ;
List < FlSpot > areaData = [ for ( var state in states ) if ( state . tlSeason1 . nerdStats ! = null ) FlSpot ( state . state . millisecondsSinceEpoch . toDouble ( ) , state . tlSeason1 . nerdStats ! . area ) ] ;
List < FlSpot > estTrData = [ for ( var state in states ) if ( state . tlSeason1 . estTr ! = null ) FlSpot ( state . state . millisecondsSinceEpoch . toDouble ( ) , state . tlSeason1 . estTr ! . esttr ) ] ;
List < FlSpot > estaccData = [ for ( var state in states ) if ( state . tlSeason1 . esttracc ! = null ) FlSpot ( state . state . millisecondsSinceEpoch . toDouble ( ) , state . tlSeason1 . esttracc ! ) ] ;
2023-06-22 19:02:49 +00:00
return ListView ( physics: const ClampingScrollPhysics ( ) ,
children: states . isNotEmpty ? [
Column (
children: [
2023-06-23 18:38:15 +00:00
if ( trData . length > 1 ) _HistoryChartThigy ( data: trData , title: " Tetra Rating " , yAxisTitle: " TR " , bigScreen: bigScreen , leftSpace: bigScreen ? 80 : 45 , yFormat: bigScreen ? f2 : NumberFormat . compact ( ) , ) ,
if ( apmData . length > 1 ) _HistoryChartThigy ( data: apmData , title: " Attack Per Minute " , yAxisTitle: " APM " , bigScreen: bigScreen , leftSpace: 40 , yFormat: NumberFormat . compact ( ) , ) ,
if ( ppsData . length > 1 ) _HistoryChartThigy ( data: ppsData , title: " Pieces Per Second " , yAxisTitle: " PPS " , bigScreen: bigScreen , leftSpace: 40 , yFormat: NumberFormat . compact ( ) , ) ,
if ( vsData . length > 1 ) _HistoryChartThigy ( data: vsData , title: " Versus Score " , yAxisTitle: " VS " , bigScreen: bigScreen , leftSpace: 40 , yFormat: NumberFormat . compact ( ) , ) ,
if ( appData . length > 1 ) _HistoryChartThigy ( data: appData , title: " Attack Per Piece " , yAxisTitle: " APP " , bigScreen: bigScreen , leftSpace: 48 , yFormat: NumberFormat . compact ( ) , ) ,
if ( dssData . length > 1 ) _HistoryChartThigy ( data: dssData , title: bigScreen ? " Downstack Per Second " : " Downstack \n Per Second " , yAxisTitle: " DS/S " , bigScreen: bigScreen , leftSpace: 48 , yFormat: NumberFormat . compact ( ) , ) ,
if ( dspData . length > 1 ) _HistoryChartThigy ( data: dspData , title: bigScreen ? " Downstack Per Piece " : " Downstack \n Per Piece " , yAxisTitle: " DS/P " , bigScreen: bigScreen , leftSpace: 48 , yFormat: NumberFormat . compact ( ) , ) ,
if ( appdspData . length > 1 ) _HistoryChartThigy ( data: appdspData , title: " APP + DS/P " , yAxisTitle: " APP + DS/P " , bigScreen: bigScreen , leftSpace: 48 , yFormat: NumberFormat . compact ( ) , ) ,
if ( vsapmData . length > 1 ) _HistoryChartThigy ( data: vsapmData , title: " VS/APM " , yAxisTitle: " VS/APM " , bigScreen: bigScreen , leftSpace: 48 , yFormat: NumberFormat . compact ( ) , ) ,
if ( cheeseData . length > 1 ) _HistoryChartThigy ( data: cheeseData , title: " Cheese Index " , yAxisTitle: " Cheese " , bigScreen: bigScreen , leftSpace: 40 , yFormat: NumberFormat . compact ( ) , ) ,
if ( gbeData . length > 1 ) _HistoryChartThigy ( data: gbeData , title: " Garbage Efficiency " , yAxisTitle: " GbE " , bigScreen: bigScreen , leftSpace: 48 , yFormat: NumberFormat . compact ( ) , ) ,
if ( nyaappData . length > 1 ) _HistoryChartThigy ( data: nyaappData , title: " Weighted APP " , yAxisTitle: " wAPP " , bigScreen: bigScreen , leftSpace: 48 , yFormat: NumberFormat . compact ( ) , ) ,
if ( areaData . length > 1 ) _HistoryChartThigy ( data: areaData , title: " Area " , yAxisTitle: " Area " , bigScreen: bigScreen , leftSpace: 40 , yFormat: NumberFormat . compact ( ) , ) ,
if ( estTrData . length > 1 ) _HistoryChartThigy ( data: estTrData , title: " Est. of TR " , yAxisTitle: " eTR " , bigScreen: bigScreen , leftSpace: bigScreen ? 80 : 45 , yFormat: bigScreen ? f2 : NumberFormat . compact ( ) , ) ,
if ( estaccData . length > 1 ) _HistoryChartThigy ( data: estaccData , title: " Accuracy of Est. " , yAxisTitle: " ±eTR " , bigScreen: bigScreen , leftSpace: 60 , yFormat: NumberFormat . compact ( explicitSign: true ) , ) ,
if ( trData . length < = 1 | | apmData . length < = 1 | | ppsData . length < = 1 | | vsData . length < = 1 | | appData . length < = 1 | | dssData . length < = 1 | | dspData . length < = 1 | | appdspData . length < = 1 | | vsapmData . length < = 1 | | cheeseData . length < = 1 | | gbeData . length < = 1 | | nyaappData . length < = 1 | | areaData . length < = 1 | | estTrData . length < = 1 | | estaccData . length < = 1 ) const Center ( child: Text ( " Some charts aren't shown due to lack of data... " , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 28 ) ) )
// Why it's look like a garbage solution???
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-22 19:02:49 +00:00
return AspectRatio (
aspectRatio: bigScreen ? 1.9 : 1.1 ,
child: Stack (
children: [
2023-06-23 18:38:15 +00:00
Row ( mainAxisAlignment: MainAxisAlignment . center , children: [ Text ( title , style: const TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 28 ) ) ] ) ,
Padding ( padding: bigScreen ? const EdgeInsets . fromLTRB ( 40 , 75 , 40 , 48 ) : const EdgeInsets . fromLTRB ( 0 , 80 , 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 (
physics: const ClampingScrollPhysics ( ) ,
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 " ,
isScreenBig: bigScreen ) ,
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 " ,
isScreenBig: bigScreen ) ,
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 ,
isScreenBig: bigScreen ) ,
StatCellNum (
playerStat: record ! . endContext ! . piecesPlaced ,
playerStatLabel: " Pieces \n Placed " ,
isScreenBig: bigScreen ) ,
StatCellNum (
playerStat: record ! . endContext ! . pps ,
playerStatLabel: " Pieces \n Per Second " ,
fractionDigits: 2 ,
isScreenBig: bigScreen ) ,
StatCellNum (
playerStat: record ! . endContext ! . finesse . faults ,
playerStatLabel: " Finesse \n Faults " ,
isScreenBig: bigScreen ) ,
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 ,
isScreenBig: bigScreen ) ,
2023-06-20 20:53:28 +00:00
StatCellNum (
playerStat: record ! . endContext ! . inputs ,
playerStatLabel: " Key \n Presses " ,
isScreenBig: bigScreen ) ,
StatCellNum (
playerStat: record ! . endContext ! . kpp ,
playerStatLabel: " KP Per \n Piece " ,
fractionDigits: 2 ,
isScreenBig: bigScreen ) ,
StatCellNum (
playerStat: record ! . endContext ! . kps ,
playerStatLabel: " KP Per \n Second " ,
fractionDigits: 2 ,
isScreenBig: bigScreen ) ,
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 (
physics: const ClampingScrollPhysics ( ) ,
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 ) ) ,
] ,
) ,
) ,
] ,
) ;
} ,
) ;
} ) ;
}
}