2024-07-27 19:10:45 +00:00
import ' package:flutter/foundation.dart ' ;
import ' package:flutter/gestures.dart ' ;
import ' package:flutter/material.dart ' hide Badge ;
import ' package:flutter_markdown/flutter_markdown.dart ' ;
import ' package:flutter_svg/flutter_svg.dart ' ;
import ' package:intl/intl.dart ' ;
import ' package:syncfusion_flutter_gauges/gauges.dart ' ;
import ' package:tetra_stats/gen/strings.g.dart ' ;
import ' package:tetra_stats/utils/numers_formats.dart ' ;
import ' package:tetra_stats/utils/relative_timestamps.dart ' ;
import ' package:tetra_stats/utils/text_shadow.dart ' ;
2024-08-06 22:24:31 +00:00
import ' package:tetra_stats/views/tl_match_view.dart ' ;
import ' package:tetra_stats/widgets/list_tile_trailing_stats.dart ' ;
import ' package:tetra_stats/widgets/stat_sell_num.dart ' ;
2024-07-27 19:10:45 +00:00
import ' package:tetra_stats/widgets/text_timestamp.dart ' ;
import ' package:tetra_stats/data_objects/tetrio.dart ' ;
import ' package:tetra_stats/main.dart ' ;
2024-08-06 22:24:31 +00:00
import ' package:tetra_stats/widgets/tl_progress_bar.dart ' ;
2024-08-04 22:23:08 +00:00
import ' package:tetra_stats/widgets/tl_rating_thingy.dart ' ;
2024-07-27 19:10:45 +00:00
import ' package:tetra_stats/widgets/user_thingy.dart ' ;
class MainView extends StatefulWidget {
final String ? player ;
/// The very first view, that user see when he launch this programm.
/// By default it loads my or defined in preferences user stats, but
/// if [player] username or id provided, it loads his stats. Also it hides menu drawer and three dots menu.
const MainView ( { super . key , this . player } ) ;
@ override
State < MainView > createState ( ) = > _MainState ( ) ;
}
2024-08-06 22:24:31 +00:00
enum Page { home , leaderboards , leagueAverages , calculator , settings }
2024-08-01 23:20:36 +00:00
enum Cards { overview , tetraLeague , quickPlay , quickPlayExpert , sprint , blitz , other }
2024-08-06 22:24:31 +00:00
enum CardMod { info , recent }
2024-08-01 23:20:36 +00:00
Map < Cards , String > cardsTitles = {
Cards . overview: " Overview " ,
Cards . tetraLeague: t . tetraLeague ,
Cards . quickPlay: t . quickPlay ,
Cards . quickPlayExpert: " ${ t . quickPlay } ${ t . expert } " ,
Cards . sprint: t . sprint ,
Cards . blitz: t . blitz ,
Cards . other: t . other
} ;
2024-07-27 19:10:45 +00:00
TetrioPlayer testPlayer = TetrioPlayer (
userId: " 6098518e3d5155e6ec429cdc " ,
username: " dan63 " ,
registrationTime: DateTime ( 2002 , 2 , 25 , 9 , 30 , 01 ) ,
avatarRevision: 1704835194288 ,
bannerRevision: 1661462402700 ,
2024-08-03 17:52:20 +00:00
role: " user " ,
2024-07-27 19:10:45 +00:00
country: " BY " ,
state: DateTime ( 1970 ) ,
badges: [
Badge ( badgeId: " kod_founder " , label: " Убил оска " , ts: DateTime ( 2023 , 6 , 27 , 18 , 51 , 49 ) ) ,
Badge ( badgeId: " kod_by_founder " , label: " Убит оском " , ts: DateTime ( 2023 , 6 , 27 , 18 , 51 , 51 ) ) ,
Badge ( badgeId: " 5mblast_1 " , label: " 5M Blast Winner " ) ,
Badge ( badgeId: " 20tsd " , label: " 20 TSD " ) ,
Badge ( badgeId: " allclear " , label: " 10PC's " ) ,
Badge ( badgeId: " 100player " , label: " Won some shit " ) ,
Badge ( badgeId: " founder " , label: " osk " ) ,
Badge ( badgeId: " early-supporter " , label: " Sus " ) ,
Badge ( badgeId: " bugbounty " , label: " Break some ribbons " ) ,
Badge ( badgeId: " infdev " , label: " Closed player " )
] ,
friendCount: 69 ,
gamesPlayed: 13747 ,
gamesWon: 6523 ,
2024-08-03 17:52:20 +00:00
gameTime: const Duration ( days: 79 , minutes: 28 , seconds: 23 , microseconds: 637591 ) ,
2024-07-27 19:10:45 +00:00
xp: 1415239 ,
supporterTier: 2 ,
verified: true ,
connections: null ,
2024-08-04 22:23:08 +00:00
tlSeason1: TetraLeagueAlpha (
timestamp: DateTime ( 1970 ) ,
gamesPlayed: 28 ,
2024-08-07 22:42:04 +00:00
gamesWon: 15 ,
2024-08-04 22:23:08 +00:00
bestRank: " x " ,
decaying: false ,
rating: 23500.6194 ,
glicko: 3847.2134 ,
rd: 61.95383 ,
2024-08-06 22:24:31 +00:00
apm: 62.48 ,
pps: 1.85 ,
vs: 134.32 ,
2024-08-04 22:23:08 +00:00
rank: " x " ,
percentileRank: " x " ,
percentile: 0.00 ,
standing: 1 ,
standingLocal: 1 ,
2024-08-06 22:24:31 +00:00
nextAt: 1 ,
2024-08-04 22:23:08 +00:00
prevAt: 500
) ,
2024-08-03 17:52:20 +00:00
//distinguishment: Distinguishment(type: "twc", detail: "2023"),
2024-07-27 19:10:45 +00:00
bio: " кровбер не в палку, без последнего тспина - 32 атаки. кровбер не в палку, без первого тсм и последнего тспина - 30 атаки. кровбер в палку с б 2б - 38 атаки.(5 б 2б )(не знаю от чего зависит) кровбер в палку с б 2б - 36 атаки.(5 б 2б )(не знаю от чего зависит) "
) ;
News testNews = News ( " 6098518e3d5155e6ec429cdc " , [
NewsEntry ( type: " personalbest " , data: { " gametype " : " 40l " , " result " : 23.232 } , timestamp: DateTime ( 2002 , 2 , 25 , 10 , 30 , 01 ) ) ,
NewsEntry ( type: " personalbest " , data: { " gametype " : " blitz " , " result " : 23.232 } , timestamp: DateTime ( 2002 , 2 , 25 , 10 , 30 , 02 ) ) ,
NewsEntry ( type: " personalbest " , data: { " gametype " : " 5mblast " , " result " : 23.232 } , timestamp: DateTime ( 2002 , 2 , 25 , 10 , 30 , 03 ) ) ,
] ) ;
2024-07-29 20:58:17 +00:00
late ScrollController controller ;
2024-07-27 19:10:45 +00:00
class _MainState extends State < MainView > with TickerProviderStateMixin {
2024-08-04 22:23:08 +00:00
String _searchFor = " 6098518e3d5155e6ec429cdc " ;
2024-08-06 22:24:31 +00:00
Cards rightCard = Cards . tetraLeague ;
2024-08-04 22:23:08 +00:00
final TextEditingController _searchController = TextEditingController ( ) ;
2024-08-07 22:42:04 +00:00
Duration postSeasonLeft = seasonStart . difference ( DateTime . now ( ) ) ;
2024-08-04 22:23:08 +00:00
2024-07-29 20:58:17 +00:00
@ override
void initState ( ) {
2024-08-03 17:52:20 +00:00
teto . open ( ) ;
2024-07-29 20:58:17 +00:00
controller = ScrollController ( ) ;
super . initState ( ) ;
}
2024-08-04 22:23:08 +00:00
void changePlayer ( String player ) {
setState ( ( ) {
_searchFor = player ;
} ) ;
}
2024-07-29 20:58:17 +00:00
@ override
void dispose ( ) {
controller . dispose ( ) ;
2024-08-04 22:23:08 +00:00
_searchController . dispose ( ) ;
2024-07-29 20:58:17 +00:00
super . dispose ( ) ;
}
2024-07-27 19:10:45 +00:00
@ override
Widget build ( BuildContext context ) {
2024-08-01 23:20:36 +00:00
return Scaffold (
2024-08-04 22:23:08 +00:00
drawer: SearchDrawer ( changePlayer: changePlayer , controller: _searchController ) ,
2024-08-01 23:20:36 +00:00
body: LayoutBuilder (
builder: ( BuildContext context , BoxConstraints constraints ) {
return Row (
children: [
NavigationRail (
leading: FloatingActionButton (
elevation: 0 ,
onPressed: ( ) {
2024-08-03 17:52:20 +00:00
Scaffold . of ( context ) . openDrawer ( ) ;
2024-08-01 23:20:36 +00:00
} ,
child: const Icon ( Icons . search ) ,
) ,
trailing: IconButton (
onPressed: ( ) {
// Add your onPressed code here!
} ,
icon: const Icon ( Icons . more_horiz_rounded ) ,
) ,
destinations: const [
NavigationRailDestination (
icon: Icon ( Icons . home ) ,
selectedIcon: Icon ( Icons . home ) ,
label: Text ( ' First ' ) ,
) ,
2024-08-07 22:42:04 +00:00
NavigationRailDestination (
icon: Icon ( Icons . data_thresholding_outlined ) ,
selectedIcon: Icon ( Icons . data_thresholding_outlined ) ,
label: Text ( ' First ' ) ,
) ,
2024-08-01 23:20:36 +00:00
NavigationRailDestination (
icon: Icon ( Icons . leaderboard ) ,
selectedIcon: Icon ( Icons . leaderboard ) ,
label: Text ( ' Second ' ) ,
) ,
NavigationRailDestination (
icon: Icon ( Icons . compress ) ,
selectedIcon: Icon ( Icons . compress ) ,
label: Text ( ' Third ' ) ,
) ,
NavigationRailDestination (
icon: Icon ( Icons . calculate ) ,
selectedIcon: Icon ( Icons . calculate ) ,
label: Text ( ' Calc ' ) ,
) ,
NavigationRailDestination (
icon: Icon ( Icons . settings ) ,
selectedIcon: Icon ( Icons . settings ) ,
label: Text ( ' Third ' ) ,
)
] ,
selectedIndex: 0
) ,
Row (
children: [
SizedBox (
width: 450.0 ,
2024-08-04 22:23:08 +00:00
child: FutureBuilder < TetrioPlayer > ( future: teto . fetchPlayer ( _searchFor ) , builder: ( context , snapshot ) {
switch ( snapshot . connectionState ) {
case ConnectionState . none:
case ConnectionState . waiting:
case ConnectionState . active:
return const Center ( child: CircularProgressIndicator ( ) ) ;
case ConnectionState . done:
if ( snapshot . hasData ) {
return Column (
children: [
NewUserThingy ( player: snapshot . data ! , showStateTimestamp: false , setState: setState ) ,
if ( snapshot . data ! . badges . isNotEmpty ) BadgesThingy ( badges: snapshot . data ! . badges ) ,
if ( snapshot . data ! . distinguishment ! = null ) DistinguishmentThingy ( snapshot . data ! . distinguishment ! ) ,
if ( snapshot . data ! . bio ! = null ) Card (
child: Column (
children: [
Row (
children: [
const Spacer ( ) ,
Text ( t . bio , style: const TextStyle ( fontFamily: " Eurostile Round Extended " ) ) ,
const Spacer ( )
] ,
) ,
Padding (
padding: const EdgeInsets . only ( bottom: 8.0 ) ,
child: MarkdownBody ( data: snapshot . data ! . bio ! , styleSheet: MarkdownStyleSheet ( textAlign: WrapAlignment . center ) ) ,
)
] ,
) ,
2024-08-01 23:20:36 +00:00
) ,
2024-08-04 22:23:08 +00:00
//if (testNews != null && testNews!.news.isNotEmpty)
Expanded (
child: FutureBuilder < News > (
future: teto . fetchNews ( _searchFor ) ,
builder: ( context , snapshot ) {
switch ( snapshot . connectionState ) {
case ConnectionState . none:
case ConnectionState . waiting:
case ConnectionState . active:
return Card ( child: Center ( child: CircularProgressIndicator ( ) ) ) ;
case ConnectionState . done:
if ( snapshot . hasData ) {
return NewsThingy ( snapshot . data ! ) ;
} else if ( snapshot . hasError ) {
return Card ( child: Column ( children: [
Text ( snapshot . error . toString ( ) , style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 42 , fontWeight: FontWeight . bold ) , textAlign: TextAlign . center ) ,
Text ( snapshot . stackTrace . toString ( ) )
]
) ) ;
}
}
return Text ( " what? " ) ;
}
) ,
)
] ,
) ;
} else {
return Center ( child:
Column (
mainAxisSize: MainAxisSize . min ,
2024-07-29 20:58:17 +00:00
children: [
2024-08-04 22:23:08 +00:00
Text ( snapshot . error ! = null ? snapshot . error . toString ( ) : " lol " , style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 42 , fontWeight: FontWeight . bold ) , textAlign: TextAlign . center ) ,
Padding (
padding: const EdgeInsets . only ( top: 8.0 ) ,
child: Text ( snapshot . stackTrace ! = null ? snapshot . stackTrace . toString ( ) : " lol " , textAlign: TextAlign . center ) ,
) ,
2024-07-29 20:58:17 +00:00
] ,
2024-08-01 23:20:36 +00:00
)
2024-08-04 22:23:08 +00:00
) ;
}
}
} ,
) ) ,
2024-08-01 23:20:36 +00:00
SizedBox (
width: constraints . maxWidth - 450 - 80 ,
child: Column (
//crossAxisAlignment: CrossAxisAlignment.center,
children: [
2024-08-07 22:42:04 +00:00
SizedBox (
height: constraints . maxHeight - 64 ,
child: SingleChildScrollView (
child: Column (
//mainAxisSize: MainAxisSize.min,
children: [
Card (
child: Row (
mainAxisSize: MainAxisSize . min ,
children: [
const Spacer ( ) ,
Text ( t . tetraLeague , style: const TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 42 ) ) ,
const Spacer ( )
] ,
) ,
) ,
Card (
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
Text ( t . seasonStarts ) ,
Center ( child: Text ( countdown ( postSeasonLeft ) , textAlign: TextAlign . center , style: const TextStyle ( fontSize: 32.0 ) ) ) ,
] ,
) ,
) ,
TetraLeagueThingy ( league: testPlayer . tlSeason1 ! ) ,
Card (
child: Row (
mainAxisSize: MainAxisSize . min ,
children: [
const Spacer ( ) ,
Text ( t . nerdStats , style: const TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 42 ) ) ,
const Spacer ( )
] ,
) ,
) ,
NerdStatsThingy ( nerdStats: testPlayer . tlSeason1 ! . nerdStats ! )
] ,
) ,
2024-08-01 23:20:36 +00:00
) ,
2024-07-29 20:58:17 +00:00
) ,
2024-08-06 22:24:31 +00:00
SegmentedButton < CardMod > (
showSelectedIcon: false ,
selected: < CardMod > { CardMod . info } ,
segments: < ButtonSegment < CardMod > > [
ButtonSegment < CardMod > (
value: CardMod . info ,
label: Text ( ' PB ' ) ,
//icon: Icon(Icons.calendar_view_day)
) ,
ButtonSegment < CardMod > (
value: CardMod . recent ,
label: Text ( ' Recent ' ) ,
//icon: Icon(Icons.calendar_view_day)
) ,
]
) ,
2024-08-01 23:20:36 +00:00
SegmentedButton < Cards > (
2024-08-06 22:24:31 +00:00
showSelectedIcon: false ,
segments: < ButtonSegment < Cards > > [
2024-08-01 23:20:36 +00:00
ButtonSegment < Cards > (
value: Cards . overview ,
2024-08-06 22:24:31 +00:00
//label: Text('Overview'),
2024-08-01 23:20:36 +00:00
icon: Icon ( Icons . calendar_view_day ) ) ,
ButtonSegment < Cards > (
value: Cards . tetraLeague ,
2024-08-06 22:24:31 +00:00
//label: Text('Tetra League'),
icon: SvgPicture . asset ( " res/icons/league.svg " , height: 16 , colorFilter: ColorFilter . mode ( theme . colorScheme . primary , BlendMode . modulate ) ) ) ,
2024-08-01 23:20:36 +00:00
ButtonSegment < Cards > (
value: Cards . quickPlay ,
2024-08-06 22:24:31 +00:00
//label: Text('Quick Play'),
icon: SvgPicture . asset ( " res/icons/qp.svg " , height: 16 , colorFilter: ColorFilter . mode ( theme . colorScheme . primary , BlendMode . modulate ) ) ) ,
2024-08-01 23:20:36 +00:00
// ButtonSegment<Cards>(
// value: Cards.quickPlayExpert,
// label: Text('QP Expert'),
// icon: Icon(Icons.calendar_today)),
ButtonSegment < Cards > (
value: Cards . sprint ,
2024-08-06 22:24:31 +00:00
//label: Text('40 Lines'),
icon: SvgPicture . asset ( " res/icons/40l.svg " , height: 16 , colorFilter: ColorFilter . mode ( theme . colorScheme . primary , BlendMode . modulate ) ) ) ,
2024-08-01 23:20:36 +00:00
ButtonSegment < Cards > (
value: Cards . blitz ,
2024-08-06 22:24:31 +00:00
//label: Text('Blitz'),
icon: SvgPicture . asset ( " res/icons/blitz.svg " , height: 16 , colorFilter: ColorFilter . mode ( theme . colorScheme . primary , BlendMode . modulate ) ) ) ,
2024-08-01 23:20:36 +00:00
// ButtonSegment<Cards>(
// value: Cards.other,
// label: Text('Other'),
// icon: Icon(Icons.calendar_today)),
] ,
2024-08-06 22:24:31 +00:00
selected: < Cards > { rightCard } ,
2024-08-01 23:20:36 +00:00
onSelectionChanged: ( Set < Cards > newSelection ) {
setState ( ( ) {
2024-08-06 22:24:31 +00:00
rightCard = newSelection . first ;
2024-08-01 23:20:36 +00:00
} ) ; } )
] ,
) ,
2024-07-27 19:10:45 +00:00
) ,
2024-08-06 22:24:31 +00:00
// SizedBox(
// width: 450,
// child: _TLRecords(userID: "snapshot.data![0].userId", changePlayer: changePlayer, data: [], wasActiveInTL: true, oldMathcesHere: false, separateScrollController: true)
// )
2024-08-01 23:20:36 +00:00
] ,
2024-07-29 20:58:17 +00:00
) ,
2024-08-01 23:20:36 +00:00
] ,
) ;
} ,
) ) ;
2024-07-27 19:10:45 +00:00
}
}
class NewsThingy extends StatelessWidget {
final News news ;
2024-08-03 17:52:20 +00:00
const NewsThingy ( this . news , { super . key } ) ;
2024-07-27 19:10:45 +00:00
ListTile getNewsTile ( NewsEntry news ) {
Map < String , String > gametypes = {
" 40l " : t . sprint ,
" blitz " : t . blitz ,
2024-08-04 22:23:08 +00:00
" 5mblast " : " 5,000,000 Blast " ,
" zenith " : " Quick Play " ,
" zenithex " : " Quick Play Expert " ,
2024-07-27 19:10:45 +00:00
} ;
// Individuly handle each entry type
switch ( news . type ) {
case " leaderboard " :
return ListTile (
title: RichText (
text: TextSpan (
style: const TextStyle ( fontFamily: ' Eurostile Round ' , fontSize: 16 , color: Colors . white ) ,
text: t . newsParts . leaderboardStart ,
children: [
TextSpan ( text: " № ${ news . data [ " rank " ] } " , style: const TextStyle ( fontWeight: FontWeight . bold ) ) ,
TextSpan ( text: t . newsParts . leaderboardMiddle ) ,
TextSpan ( text: " № ${ gametypes [ news . data [ " gametype " ] ] } " , style: const TextStyle ( fontWeight: FontWeight . bold ) ) ,
]
)
) ,
subtitle: Text ( timestamp ( news . timestamp ) ) ,
) ;
case " personalbest " :
return ListTile (
title: RichText (
text: TextSpan (
style: const TextStyle ( fontFamily: ' Eurostile Round ' , fontSize: 16 , color: Colors . white ) ,
text: t . newsParts . personalbest ,
children: [
TextSpan ( text: " ${ gametypes [ news . data [ " gametype " ] ] } " , style: const TextStyle ( fontWeight: FontWeight . bold ) ) ,
TextSpan ( text: t . newsParts . personalbestMiddle ) ,
2024-08-04 22:23:08 +00:00
TextSpan ( text: switch ( news . data [ " gametype " ] ) {
" blitz " = > NumberFormat . decimalPattern ( ) . format ( news . data [ " result " ] ) ,
" 40l " = > get40lTime ( ( news . data [ " result " ] * 1000 ) . floor ( ) ) ,
" 5mblast " = > get40lTime ( ( news . data [ " result " ] * 1000 ) . floor ( ) ) ,
" zenith " = > " ${ f2 . format ( news . data [ " result " ] ) } m. " ,
" zenithex " = > " ${ f2 . format ( news . data [ " result " ] ) } m. " ,
_ = > " unknown "
} ,
style: const TextStyle ( fontWeight: FontWeight . bold )
) ,
2024-07-27 19:10:45 +00:00
]
)
) ,
subtitle: Text ( timestamp ( news . timestamp ) ) ,
leading: Image . asset (
" res/icons/improvement-local.png " ,
height: 48 ,
width: 48 ,
errorBuilder: ( context , error , stackTrace ) {
return Image . asset ( " res/icons/kagari.png " , height: 64 , width: 64 ) ;
} ,
) ,
) ;
case " badge " :
return ListTile (
title: RichText (
text: TextSpan (
style: const TextStyle ( fontFamily: ' Eurostile Round ' , fontSize: 16 , color: Colors . white ) ,
text: t . newsParts . badgeStart ,
children: [
TextSpan ( text: " ${ news . data [ " label " ] } " , style: const TextStyle ( fontWeight: FontWeight . bold ) ) ,
TextSpan ( text: t . newsParts . badgeEnd )
]
)
) ,
subtitle: Text ( timestamp ( news . timestamp ) ) ,
leading: Image . asset (
" res/tetrio_badges/ ${ news . data [ " type " ] } .png " ,
height: 48 ,
width: 48 ,
errorBuilder: ( context , error , stackTrace ) {
return Image . asset ( " res/icons/kagari.png " , height: 64 , width: 64 ) ;
} ,
) ,
) ;
case " rankup " :
return ListTile (
title: RichText (
text: TextSpan (
style: const TextStyle ( fontFamily: ' Eurostile Round ' , fontSize: 16 , color: Colors . white ) ,
text: t . newsParts . rankupStart ,
children: [
TextSpan ( text: t . newsParts . rankupMiddle ( r: news . data [ " rank " ] . toString ( ) . toUpperCase ( ) ) , style: const TextStyle ( fontWeight: FontWeight . bold ) ) ,
TextSpan ( text: t . newsParts . rankupEnd )
]
)
) ,
subtitle: Text ( timestamp ( news . timestamp ) ) ,
leading: Image . asset (
" res/tetrio_tl_alpha_ranks/ ${ news . data [ " rank " ] } .png " ,
height: 48 ,
width: 48 ,
errorBuilder: ( context , error , stackTrace ) {
return Image . asset ( " res/icons/kagari.png " , height: 64 , width: 64 ) ;
} ,
) ,
) ;
case " supporter " :
return ListTile (
title: RichText (
text: TextSpan (
style: const TextStyle ( fontFamily: ' Eurostile Round ' , fontSize: 16 , color: Colors . white ) ,
text: t . newsParts . supporterStart ,
children: [
TextSpan ( text: t . newsParts . tetoSupporter , style: const TextStyle ( fontWeight: FontWeight . bold ) )
]
)
) ,
subtitle: Text ( timestamp ( news . timestamp ) ) ,
leading: Image . asset (
" res/icons/supporter-tag.png " ,
height: 48 ,
width: 48 ,
errorBuilder: ( context , error , stackTrace ) {
return Image . asset ( " res/icons/kagari.png " , height: 64 , width: 64 ) ;
} ,
) ,
) ;
case " supporter_gift " :
return ListTile (
title: RichText (
text: TextSpan (
style: const TextStyle ( fontFamily: ' Eurostile Round ' , fontSize: 16 , color: Colors . white ) ,
text: t . newsParts . supporterGiftStart ,
children: [
TextSpan ( text: t . newsParts . tetoSupporter , style: const TextStyle ( fontWeight: FontWeight . bold ) )
]
)
) ,
subtitle: Text ( timestamp ( news . timestamp ) ) ,
leading: Image . asset (
" res/icons/supporter-tag.png " ,
height: 48 ,
width: 48 ,
errorBuilder: ( context , error , stackTrace ) {
return Image . asset ( " res/icons/kagari.png " , height: 64 , width: 64 ) ;
} ,
) ,
) ;
default : // if type is unknown
return ListTile (
title: Text ( t . newsParts . unknownNews ( type: news . type ) ) ,
subtitle: Text ( timestamp ( news . timestamp ) ) ,
) ;
}
}
@ override
Widget build ( BuildContext context ) {
return Card (
child: SingleChildScrollView (
child: Column (
children: [
Row (
children: [
2024-08-03 17:52:20 +00:00
const Spacer ( ) ,
Text ( t . news , style: const TextStyle ( fontFamily: " Eurostile Round Extended " ) ) ,
const Spacer ( )
2024-07-27 19:10:45 +00:00
]
) ,
2024-08-04 22:23:08 +00:00
if ( news . news . isEmpty ) Center ( child: Text ( " Empty list " ) )
else for ( NewsEntry entry in news . news ) getNewsTile ( entry )
2024-07-27 19:10:45 +00:00
] ,
) ,
) ,
) ;
}
}
class DistinguishmentThingy extends StatelessWidget {
final Distinguishment distinguishment ;
2024-08-03 17:52:20 +00:00
const DistinguishmentThingy ( this . distinguishment , { super . key } ) ;
2024-07-27 19:10:45 +00:00
List < InlineSpan > getDistinguishmentTitle ( String ? text ) {
// TWC champions don't have header in their distinguishments
if ( distinguishment . type = = " twc " ) return [ const TextSpan ( text: " TETR.IO World Champion " , style: TextStyle ( fontSize: 28 , fontWeight: FontWeight . bold , color: Colors . yellowAccent ) ) ] ;
// In case if it missing for some other reason, return this
if ( text = = null ) return [ const TextSpan ( text: " Header is missing " , style: TextStyle ( fontSize: 28 , fontWeight: FontWeight . bold , color: Colors . redAccent ) ) ] ;
// Handling placeholders for logos
var exploded = text . split ( " " ) ; // wtf PHP reference?
List < InlineSpan > result = [ ] ;
for ( String shit in exploded ) {
switch ( shit ) { // if %% thingy was found, insert svg of icon
case " %osk% " :
result . add ( WidgetSpan ( child: Padding (
padding: const EdgeInsets . only ( left: 8 ) ,
child: SvgPicture . asset ( " res/icons/osk.svg " , height: 28 ) ,
) ) ) ;
break ;
case " %tetrio% " :
result . add ( WidgetSpan ( child: Padding (
padding: const EdgeInsets . only ( left: 8 ) ,
child: SvgPicture . asset ( " res/icons/tetrio-logo.svg " , height: 28 ) ,
) ) ) ;
break ;
default : // if not, insert text span
result . add ( TextSpan ( text: " $ shit " , style: const TextStyle ( fontSize: 28 , fontWeight: FontWeight . bold , color: Colors . white ) ) ) ;
}
}
return result ;
}
/// Distinguishment title is barely predictable thing.
/// Receives [text], which is footer and returns sets of widgets for RichText widget
String getDistinguishmentSubtitle ( String ? text ) {
// TWC champions don't have footer in their distinguishments
if ( distinguishment . type = = " twc " ) return " ${ distinguishment . detail } TETR.IO World Championship " ;
// In case if it missing for some other reason, return this
if ( text = = null ) return " Footer is missing " ;
// If everything ok, return as it is
return text ;
}
Color getCardTint ( String type , String detail ) {
switch ( type ) {
case " staff " :
switch ( detail ) {
2024-08-03 17:52:20 +00:00
case " founder " : return const Color ( 0xAAFD82D4 ) ;
case " kagarin " : return const Color ( 0xAAFF0060 ) ;
case " team " : return const Color ( 0xAAFACC2E ) ;
case " team-minor " : return const Color ( 0xAAF5BD45 ) ;
case " administrator " : return const Color ( 0xAAFF4E8A ) ;
case " globalmod " : return const Color ( 0xAAE878FF ) ;
case " communitymod " : return const Color ( 0xAA4E68FB ) ;
case " alumni " : return const Color ( 0xAA6057DB ) ;
2024-07-27 19:10:45 +00:00
default : return theme . colorScheme . surface ;
}
case " champion " :
switch ( detail ) {
case " blitz " :
2024-08-03 17:52:20 +00:00
case " 40l " : return const Color ( 0xAACCF5F6 ) ;
case " league " : return const Color ( 0xAAFFDB31 ) ;
2024-07-27 19:10:45 +00:00
}
2024-08-03 17:52:20 +00:00
case " twc " : return const Color ( 0xAAFFDB31 ) ;
2024-07-27 19:10:45 +00:00
default : return theme . colorScheme . surface ;
}
return theme . colorScheme . surface ;
}
@ override
Widget build ( BuildContext context ) {
return Card (
surfaceTintColor: getCardTint ( distinguishment . type , distinguishment . detail ? ? " null " ) ,
child: Column (
children: [
Row (
children: [
2024-08-03 17:52:20 +00:00
const Spacer ( ) ,
Text ( t . distinguishment , style: const TextStyle ( fontFamily: " Eurostile Round Extended " ) ) ,
const Spacer ( )
2024-07-27 19:10:45 +00:00
] ,
) ,
RichText (
textAlign: TextAlign . center ,
text: TextSpan (
style: DefaultTextStyle . of ( context ) . style ,
children: getDistinguishmentTitle ( distinguishment . header ) ,
) ,
) ,
Text ( getDistinguishmentSubtitle ( distinguishment . footer ) , style: const TextStyle ( fontSize: 18 ) , textAlign: TextAlign . center ) ,
] ,
) ,
) ;
}
}
2024-08-04 22:23:08 +00:00
class BadgesThingy extends StatelessWidget {
final List < Badge > badges ;
const BadgesThingy ( { super . key , required this . badges } ) ;
@ override
Widget build ( BuildContext context ) {
return Card (
child: Column (
children: [
Padding (
padding: const EdgeInsets . fromLTRB ( 20.0 , 0.0 , 20.0 , 0.0 ) ,
child: Row (
children: [
const Text ( " Badges " , style: TextStyle ( fontFamily: " Eurostile Round Extended " ) ) ,
const Spacer ( ) ,
Text ( intf . format ( badges . length ) )
] ,
) ,
) ,
SingleChildScrollView (
scrollDirection: Axis . horizontal ,
child: Row (
children: [
for ( var badge in badges )
IconButton (
onPressed: ( ) = > showDialog < void > (
context: context ,
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
? t . obtainDate ( date: timestamp ( badge . ts ! ) )
: t . assignedManualy ) ,
] ,
)
] ,
) ,
) ,
actions: < Widget > [
TextButton (
child: Text ( t . popupActions . ok ) ,
onPressed: ( ) {
Navigator . of ( context ) . pop ( ) ;
} ,
) ,
] ,
) ;
} ,
) ,
tooltip: badge . label ,
icon: Image . asset (
" res/tetrio_badges/ ${ badge . badgeId } .png " ,
height: 32 ,
width: 32 ,
errorBuilder: ( context , error , stackTrace ) {
return Image . network (
kIsWeb ? " https://ts.dan63.by/oskware_bridge.php?endpoint=TetrioBadge&badge= ${ badge . badgeId } " : " https://tetr.io/res/badges/ ${ badge . badgeId } .png " ,
height: 32 ,
width: 32 ,
errorBuilder: ( context , error , stackTrace ) {
return Image . asset ( " res/icons/kagari.png " , height: 32 , width: 32 ) ;
}
) ;
} ,
)
)
] ,
) ,
)
] ,
) ,
) ;
}
}
2024-07-27 19:10:45 +00:00
class NewUserThingy extends StatelessWidget {
final TetrioPlayer player ;
final bool showStateTimestamp ;
final Function setState ;
const NewUserThingy ( { super . key , required this . player , required this . showStateTimestamp , required this . setState } ) ;
Color roleColor ( String role ) {
switch ( role ) {
case " sysop " :
2024-08-03 17:52:20 +00:00
return const Color . fromARGB ( 255 , 23 , 165 , 133 ) ;
2024-07-27 19:10:45 +00:00
case " admin " :
2024-08-03 17:52:20 +00:00
return const Color . fromARGB ( 255 , 255 , 78 , 138 ) ;
2024-07-27 19:10:45 +00:00
case " mod " :
2024-08-03 17:52:20 +00:00
return const Color . fromARGB ( 255 , 204 , 128 , 242 ) ;
2024-07-27 19:10:45 +00:00
case " halfmod " :
2024-08-03 17:52:20 +00:00
return const Color . fromARGB ( 255 , 95 , 118 , 254 ) ;
2024-07-27 19:10:45 +00:00
case " bot " :
2024-08-03 17:52:20 +00:00
return const Color . fromARGB ( 255 , 60 , 93 , 55 ) ;
2024-07-27 19:10:45 +00:00
case " banned " :
2024-08-03 17:52:20 +00:00
return const Color . fromARGB ( 255 , 248 , 28 , 28 ) ;
2024-07-27 19:10:45 +00:00
default :
return Colors . white10 ;
}
}
2024-08-04 22:23:08 +00:00
String fontStyle ( int length ) {
if ( length < 10 ) return " Eurostile Round Extended " ;
else if ( length < 13 ) return " Eurostile Round " ;
else return " Eurostile Round Condensed " ;
}
2024-07-27 19:10:45 +00:00
@ override
Widget build ( BuildContext context ) {
final t = Translations . of ( context ) ;
return LayoutBuilder ( builder: ( context , constraints ) {
2024-08-03 17:52:20 +00:00
//bool bigScreen = constraints.maxWidth > 768;
2024-07-27 19:10:45 +00:00
double pfpHeight = 128 ;
int xpTableID = 0 ;
while ( player . xp > xpTableScuffed . values . toList ( ) [ xpTableID ] ) {
xpTableID + + ;
}
return Card (
clipBehavior: Clip . antiAlias ,
2024-08-04 22:23:08 +00:00
child: Column (
children: [
Padding (
padding: const EdgeInsets . only ( bottom: 4.0 ) ,
child: Container (
2024-08-03 17:52:20 +00:00
constraints: const BoxConstraints ( maxWidth: 960 ) ,
2024-07-27 19:10:45 +00:00
height: player . bannerRevision ! = null ? 218.0 : 138.0 ,
child: Stack (
//clipBehavior: Clip.none,
children: [
if ( player . bannerRevision ! = null ) Image . network ( kIsWeb ? " https://ts.dan63.by/oskware_bridge.php?endpoint=TetrioBanner&user= ${ player . userId } &rv= ${ player . bannerRevision } " : " https://tetr.io/user-content/banners/ ${ player . userId } .jpg?rv= ${ player . bannerRevision } " ,
fit: BoxFit . cover ,
height: 120 ,
errorBuilder: ( context , error , stackTrace ) {
return Container ( ) ;
} ,
) ,
Positioned (
top: player . bannerRevision ! = null ? 90.0 : 10.0 ,
left: 16.0 ,
child: ClipRRect (
borderRadius: BorderRadius . circular ( 1000 ) ,
child: player . role = = " banned "
? Image . asset ( " res/avatars/tetrio_banned.png " , fit: BoxFit . fitHeight , height: pfpHeight , )
: player . avatarRevision ! = null
? Image . network ( kIsWeb ? " https://ts.dan63.by/oskware_bridge.php?endpoint=TetrioProfilePicture&user= ${ player . userId } &rv= ${ player . avatarRevision } " : " https://tetr.io/user-content/avatars/ ${ player . userId } .jpg?rv= ${ player . avatarRevision } " ,
// TODO: osk banner can cause memory leak
fit: BoxFit . fitHeight , height: 128 , errorBuilder: ( context , error , stackTrace ) {
return Image . asset ( " res/avatars/tetrio_anon.png " , fit: BoxFit . fitHeight , height: pfpHeight ) ;
} )
: Image . asset ( " res/avatars/tetrio_anon.png " , fit: BoxFit . fitHeight , height: pfpHeight ) ,
)
) ,
Positioned (
top: player . bannerRevision ! = null ? 120.0 : 40.0 ,
left: 160.0 ,
child: Text ( player . username ,
//softWrap: true,
overflow: TextOverflow . fade ,
2024-08-04 22:23:08 +00:00
style: TextStyle (
fontFamily: fontStyle ( player . username . length ) ,
2024-07-27 19:10:45 +00:00
fontSize: 28 ,
)
) ,
) ,
Positioned (
top: player . bannerRevision ! = null ? 160.0 : 80.0 ,
left: 160.0 ,
child: Row (
children: [
Padding (
padding: const EdgeInsets . only ( right: 4.0 ) ,
2024-08-03 17:52:20 +00:00
child: Chip ( label: Text ( player . role . toUpperCase ( ) , style: const TextStyle ( shadows: textShadow ) , ) , padding: const EdgeInsets . all ( 0.0 ) , color: WidgetStatePropertyAll ( roleColor ( player . role ) ) ) ,
2024-07-27 19:10:45 +00:00
) ,
RichText (
text: TextSpan (
2024-08-03 17:52:20 +00:00
style: const TextStyle ( fontFamily: " Eurostile Round " ) ,
2024-07-27 19:10:45 +00:00
children:
[
2024-08-03 17:52:20 +00:00
if ( player . friendCount > 0 ) const WidgetSpan ( child: Icon ( Icons . person ) , alignment: PlaceholderAlignment . middle , baseline: TextBaseline . alphabetic ) ,
2024-07-27 19:10:45 +00:00
if ( player . friendCount > 0 ) TextSpan ( text: " ${ intf . format ( player . friendCount ) } " ) ,
if ( player . supporterTier > 0 ) WidgetSpan ( child: Icon ( player . supporterTier > 1 ? Icons . star : Icons . star_border , color: player . supporterTier > 1 ? Colors . yellowAccent : Colors . white ) , alignment: PlaceholderAlignment . middle , baseline: TextBaseline . alphabetic ) ,
if ( player . supporterTier > 0 ) TextSpan ( text: player . supporterTier . toString ( ) , style: TextStyle ( color: player . supporterTier > 1 ? Colors . yellowAccent : Colors . white ) ) ,
]
)
)
] ,
) ,
) ,
Positioned (
top: player . bannerRevision ! = null ? 193.0 : 113.0 ,
left: 160.0 ,
child: RichText (
text: TextSpan (
2024-08-03 17:52:20 +00:00
style: const TextStyle ( fontFamily: " Eurostile Round " ) ,
2024-07-27 19:10:45 +00:00
children: [
if ( player . country ! = null ) TextSpan ( text: " ${ t . countries [ player . country ] } • " ) ,
2024-08-03 17:52:20 +00:00
TextSpan ( text: player . registrationTime = = null ? t . wasFromBeginning : timestamp ( player . registrationTime ! ) , style: const TextStyle ( color: Colors . grey ) )
2024-07-27 19:10:45 +00:00
]
)
)
) ,
Positioned (
top: player . bannerRevision ! = null ? 126.0 : 46.0 ,
right: 16.0 ,
child: RichText (
textAlign: TextAlign . end ,
text: TextSpan (
2024-08-03 17:52:20 +00:00
style: const TextStyle ( fontFamily: " Eurostile Round " ) ,
2024-07-27 19:10:45 +00:00
children: [
2024-08-06 22:24:31 +00:00
TextSpan ( text: " Level ${ intf . format ( player . level . floor ( ) ) } " , style: TextStyle ( decoration: TextDecoration . underline , decorationColor: Colors . white70 , decorationStyle: TextDecorationStyle . dotted ) , recognizer: TapGestureRecognizer ( ) . . onTap = ( ) {
2024-07-27 19:10:45 +00:00
showDialog (
context: context ,
builder: ( BuildContext context ) = > AlertDialog (
2024-08-06 22:24:31 +00:00
title: Text ( " Level ${ intf . format ( player . level . floor ( ) ) } " , textAlign: TextAlign . center ) ,
2024-07-27 19:10:45 +00:00
content: SingleChildScrollView (
child: ListBody ( children: [
Text (
" ${ NumberFormat . decimalPatternDigits ( locale: LocaleSettings . currentLocale . languageCode , decimalDigits: 2 ) . format ( player . xp ) } XP " ,
style: const TextStyle ( fontFamily: " Eurostile Round " , fontWeight: FontWeight . bold )
) ,
Padding (
padding: const EdgeInsets . fromLTRB ( 0 , 8 , 0 , 8 ) ,
child: SfLinearGauge (
minimum: 0 ,
maximum: 1 ,
interval: 1 ,
ranges: [
LinearGaugeRange ( startValue: 0 , endValue: player . level - player . level . floor ( ) , color: Colors . cyanAccent ) ,
LinearGaugeRange ( startValue: 0 , endValue: ( player . xp / xpTableScuffed . values . toList ( ) [ xpTableID ] ) , color: Colors . redAccent , position: LinearElementPosition . cross )
] ,
showTicks: true ,
showLabels: false
) ,
) ,
Text ( " ${ t . statCellNum . xpProgress } : ${ ( ( player . level - player . level . floor ( ) ) * 100 ) . toStringAsFixed ( 2 ) } % " ) ,
Text ( " ${ t . statCellNum . xpFrom0ToLevel ( n: xpTableScuffed . keys . toList ( ) [ xpTableID ] ) } : ${ ( ( player . xp / xpTableScuffed . values . toList ( ) [ xpTableID ] ) * 100 ) . toStringAsFixed ( 2 ) } % ( ${ NumberFormat . decimalPatternDigits ( locale: LocaleSettings . currentLocale . languageCode , decimalDigits: 0 ) . format ( xpTableScuffed . values . toList ( ) [ xpTableID ] - player . xp ) } ${ t . statCellNum . xpLeft } ) " )
]
) ,
) ,
actions: < Widget > [
TextButton (
2024-08-03 17:52:20 +00:00
child: const Text ( " OK " ) ,
2024-07-27 19:10:45 +00:00
onPressed: ( ) { Navigator . of ( context ) . pop ( ) ; }
)
]
)
) ;
} ) ,
2024-08-03 17:52:20 +00:00
const TextSpan ( text: " \n " ) ,
2024-08-06 22:24:31 +00:00
TextSpan ( text: player . gameTime . isNegative ? " -h --m " : playtime ( player . gameTime ) , style: TextStyle ( color: player . gameTime . isNegative ? Colors . grey : Colors . white , decoration: player . gameTime . isNegative ? null : TextDecoration . underline , decorationColor: Colors . white70 , decorationStyle: TextDecorationStyle . dotted ) , recognizer: ! player . gameTime . isNegative ? ( TapGestureRecognizer ( ) . . onTap = ( ) {
2024-07-27 19:10:45 +00:00
showDialog (
context: context ,
builder: ( BuildContext context ) = > AlertDialog (
2024-08-06 22:24:31 +00:00
title: Text ( t . exactGametime , textAlign: TextAlign . center ) ,
2024-07-27 19:10:45 +00:00
content: SingleChildScrollView (
child: ListBody ( children: [
Text (
2024-08-04 22:23:08 +00:00
" ${ intf . format ( player . gameTime . inDays ) } d ${ nonsecs . format ( player . gameTime . inHours % 24 ) } h ${ nonsecs . format ( player . gameTime . inMinutes % 60 ) } m ${ nonsecs . format ( player . gameTime . inSeconds % 60 ) } s ${ nonsecs3 . format ( player . gameTime . inMilliseconds % 1000 ) } ms ${ nonsecs3 . format ( player . gameTime . inMicroseconds % 1000 ) } μs " ,
2024-07-27 19:10:45 +00:00
style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 24 )
) ,
2024-08-06 22:24:31 +00:00
Padding (
padding: const EdgeInsets . only ( top: 8.0 ) ,
child: Text ( " It's ${ f4 . format ( player . gameTime . inSeconds / 31536000 ) } years, " ) ,
) ,
Text ( " ${ f4 . format ( player . gameTime . inSeconds / 2628000 ) } monts, " ) ,
Text ( " ${ f4 . format ( player . gameTime . inSeconds / 3600 ) } hours, " ) ,
Text ( " ${ f2 . format ( player . gameTime . inMilliseconds / 60000 ) } minutes, " ) ,
Text ( " ${ intf . format ( player . gameTime . inSeconds ) } seconds " ) ,
2024-07-27 19:10:45 +00:00
]
) ,
) ,
actions: < Widget > [
TextButton (
2024-08-03 17:52:20 +00:00
child: const Text ( " OK " ) ,
2024-07-27 19:10:45 +00:00
onPressed: ( ) { Navigator . of ( context ) . pop ( ) ; }
)
]
)
) ;
2024-08-04 22:23:08 +00:00
} ) : null ) ,
2024-08-03 17:52:20 +00:00
const TextSpan ( text: " \n " ) ,
TextSpan ( text: player . gamesWon > - 1 ? intf . format ( player . gamesWon ) : " --- " , style: TextStyle ( color: player . gamesWon > - 1 ? Colors . white : Colors . grey ) ) ,
TextSpan ( text: " / ${ player . gamesPlayed > - 1 ? intf . format ( player . gamesPlayed ) : " --- " } " , style: const TextStyle ( fontFamily: " Eurostile Round Condensed " , color: Colors . grey ) ) ,
2024-07-27 19:10:45 +00:00
]
)
)
)
] ,
2024-08-04 22:23:08 +00:00
) ,
2024-07-27 19:10:45 +00:00
) ,
2024-08-04 22:23:08 +00:00
) ,
Row (
mainAxisAlignment: MainAxisAlignment . center ,
children: [
Expanded ( child: ElevatedButton . icon ( onPressed: ( ) { print ( " ok, and? " ) ; } , icon: const Icon ( Icons . person_add ) , label: Text ( t . track ) , style: const ButtonStyle ( shape: WidgetStatePropertyAll ( RoundedRectangleBorder ( borderRadius: BorderRadius . only ( bottomLeft: Radius . circular ( 12.0 ) ) ) ) ) ) ) ,
Expanded ( child: ElevatedButton . icon ( onPressed: ( ) { print ( " ok, and? " ) ; } , icon: const Icon ( Icons . balance ) , label: Text ( t . compare ) , style: const ButtonStyle ( shape: WidgetStatePropertyAll ( RoundedRectangleBorder ( borderRadius: BorderRadius . only ( bottomRight: Radius . circular ( 12.0 ) ) ) ) ) ) )
] ,
)
] ,
2024-07-27 19:10:45 +00:00
) ,
) ;
} ) ;
}
2024-08-03 17:52:20 +00:00
}
class SearchDrawer extends StatefulWidget {
2024-08-04 22:23:08 +00:00
final Function changePlayer ;
final TextEditingController controller ;
const SearchDrawer ( { super . key , required this . changePlayer , required this . controller } ) ;
2024-08-03 17:52:20 +00:00
@ override
State < SearchDrawer > createState ( ) = > _SearchDrawerState ( ) ;
}
class _SearchDrawerState extends State < SearchDrawer > {
@ override
Widget build ( BuildContext context ) {
return Drawer (
child: StreamBuilder (
stream: teto . allPlayers ,
builder: ( context , snapshot ) {
switch ( snapshot . connectionState ) {
case ConnectionState . none:
case ConnectionState . waiting:
case ConnectionState . done:
case ConnectionState . active:
final allPlayers = ( snapshot . data ! = null )
? snapshot . data as Map < String , String >
: < String , String > { } ;
allPlayers . remove ( prefs . getString ( " player " ) ? ? " 6098518e3d5155e6ec429cdc " ) ; // player from the home button will be delisted
List < String > keys = allPlayers . keys . toList ( ) ;
return NestedScrollView (
headerSliverBuilder: ( BuildContext context , bool value ) {
return [
SliverToBoxAdapter (
child: SearchBar (
2024-08-04 22:23:08 +00:00
controller: widget . controller ,
2024-08-03 17:52:20 +00:00
hintText: " Hello " ,
hintStyle: const WidgetStatePropertyAll ( TextStyle ( color: Colors . grey ) ) ,
trailing: [
2024-08-04 22:23:08 +00:00
IconButton ( onPressed: ( ) { setState ( ( ) {
widget . changePlayer ( widget . controller . value . text ) ;
Navigator . of ( context ) . pop ( ) ;
} ) ; } , icon: const Icon ( Icons . search ) )
2024-08-03 17:52:20 +00:00
] ,
2024-08-04 22:23:08 +00:00
onSubmitted: ( value ) {
setState ( ( ) {
widget . changePlayer ( value ) ;
Navigator . of ( context ) . pop ( ) ;
} ) ;
} ,
2024-08-03 17:52:20 +00:00
) ,
2024-08-07 22:42:04 +00:00
) ,
SliverToBoxAdapter (
child: ListTile (
title: Text ( prefs . getString ( " player " ) ? ? " dan63 " ) ,
onTap: ( ) {
widget . changePlayer ( " 6098518e3d5155e6ec429cdc " ) ;
Navigator . of ( context ) . pop ( ) ;
} ,
) ,
2024-08-03 17:52:20 +00:00
)
] ;
} ,
body: ListView . builder ( // Builds list of tracked players.
itemCount: allPlayers . length ,
itemBuilder: ( context , index ) {
var i = allPlayers . length - 1 - index ; // Last players in this map are most recent ones, they are gonna be shown at the top.
return ListTile (
title: Text ( allPlayers [ keys [ i ] ] ? ? keys [ i ] ) , // Takes last known username from list of states
onTap: ( ) {
2024-08-04 22:23:08 +00:00
widget . changePlayer ( keys [ i ] ) ; // changes to chosen player
2024-08-03 17:52:20 +00:00
Navigator . of ( context ) . pop ( ) ; // and closes itself.
} ,
) ;
} )
) ;
}
}
)
) ;
}
2024-08-04 22:23:08 +00:00
}
class TetraLeagueThingy extends StatelessWidget {
final TetraLeagueAlpha league ;
const TetraLeagueThingy ( { super . key , required this . league } ) ;
@ override
Widget build ( BuildContext context ) {
return Card (
child: Column (
children: [
2024-08-06 22:24:31 +00:00
TLRatingThingy ( userID: " w " , tlData: league ) ,
TLProgress ( tlData: league , ) ,
2024-08-07 22:42:04 +00:00
Row (
// spacing: 25.0,
// alignment: WrapAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment . center ,
2024-08-06 22:24:31 +00:00
children: [
2024-08-07 22:42:04 +00:00
Expanded (
child: Center (
child: Table (
defaultColumnWidth: IntrinsicColumnWidth ( ) ,
children: [
TableRow ( children: [
Text ( " APM: " , style: TextStyle ( fontSize: 21 ) ) ,
Text ( league . apm ! = null ? f2 . format ( league . apm ) : " --- " , textAlign: TextAlign . right , style: TextStyle ( fontSize: 21 ) ) ,
//Text(" APM", style: TextStyle(fontSize: 21))
] ) ,
TableRow ( children: [
Text ( " PPS: " , style: TextStyle ( fontSize: 21 ) ) ,
Text ( league . apm ! = null ? f2 . format ( league . pps ) : " --- " , textAlign: TextAlign . right , style: TextStyle ( fontSize: 21 ) ) ,
//Text(" PPS", style: TextStyle(fontSize: 21))
] ) ,
TableRow ( children: [
Text ( " VS: " , style: TextStyle ( fontSize: 21 ) ) ,
Text ( league . apm ! = null ? f2 . format ( league . vs ) : " --- " , textAlign: TextAlign . right , style: TextStyle ( fontSize: 21 ) ) ,
// Text(" VS", style: TextStyle(fontSize: 21))
] )
] ,
) ,
) ,
2024-08-06 22:24:31 +00:00
) ,
SizedBox (
height: 128.0 ,
width: 128.0 ,
child: SfRadialGauge (
axes: [
RadialAxis (
// startAngle: 180,
// endAngle: 0,
minimum: 0.4 ,
maximum: 0.6 ,
//radiusFactor: 1.5,
showTicks: true ,
showLabels: false ,
interval: 0.1 ,
//labelsPosition: ElementsPosition.outside,
ranges: [
GaugeRange ( startValue: 0 , endValue: league . winrate , color: theme . colorScheme . primary )
] ,
annotations: [
GaugeAnnotation ( widget: Container ( child:
2024-08-07 22:42:04 +00:00
Text ( percentage . format ( league . winrate ) , textAlign: TextAlign . center , style: TextStyle ( fontSize: 25 , fontWeight: FontWeight . bold ) ) ) ,
2024-08-06 22:24:31 +00:00
angle: 90 , positionFactor: 0.1
) ,
2024-08-07 22:42:04 +00:00
GaugeAnnotation ( widget: Container ( child:
Text ( t . statCellNum . winrate , textAlign: TextAlign . center ) ) ,
angle: 270 , positionFactor: 0.4
)
2024-08-06 22:24:31 +00:00
] ,
)
]
) ,
) ,
2024-08-07 22:42:04 +00:00
Expanded (
child: Center (
child: Table (
defaultColumnWidth: IntrinsicColumnWidth ( ) ,
children: [
TableRow ( children: [
//Text("APM: ", style: TextStyle(fontSize: 21)),
Text ( intf . format ( league . gamesPlayed ) , textAlign: TextAlign . right , style: TextStyle ( fontSize: 21 ) ) ,
Text ( " Games " , style: TextStyle ( fontSize: 21 ) )
] ) ,
TableRow ( children: [
//Text("PPS: ", style: TextStyle(fontSize: 21)),
Text ( intf . format ( league . gamesWon ) , textAlign: TextAlign . right , style: TextStyle ( fontSize: 21 ) ) ,
Text ( " Won " , style: TextStyle ( fontSize: 21 ) )
] ) ,
TableRow ( children: [
//Text("VS: ", style: TextStyle(fontSize: 21)),
Text ( " № ${ intf . format ( league . standingLocal ) } " , textAlign: TextAlign . right , style: TextStyle ( fontSize: 21 ) ) ,
Text ( " in BY " , style: TextStyle ( fontSize: 21 ) )
] )
] ,
) ,
) ,
) ,
] ,
) ,
] ,
) ,
) ;
}
}
class NerdStatsThingy extends StatelessWidget {
final NerdStats nerdStats ;
const NerdStatsThingy ( { super . key , required this . nerdStats } ) ;
@ override
Widget build ( BuildContext context ) {
return Card (
child: Column (
children: [
Row (
children: [
SizedBox (
height: 256.0 ,
width: 256.0 ,
child: SfRadialGauge (
axes: [
RadialAxis (
startAngle: 120 ,
endAngle: 240 ,
minimum: 0.0 ,
maximum: 1.0 ,
//radiusFactor: 1.5,
showTicks: true ,
showLabels: false ,
interval: 0.1 ,
//labelsPosition: ElementsPosition.outside,
ranges: [
GaugeRange ( startValue: 0 , endValue: nerdStats . app , color: theme . colorScheme . primary )
] ,
annotations: [
GaugeAnnotation ( widget: Container ( child:
RichText (
textAlign: TextAlign . center ,
text: TextSpan (
style: TextStyle ( fontFamily: " Eurostile Round " ) ,
children: [
TextSpan ( text: " APP \n " ) ,
TextSpan ( text: f3 . format ( nerdStats . app ) , style: TextStyle ( fontSize: 25 , fontWeight: FontWeight . bold ) ) ,
//TextSpan(text: "\nAPP"),
]
) ) ) ,
angle: 180 , positionFactor: 0.5
) ,
] ,
) ,
RadialAxis (
startAngle: 300 ,
endAngle: 60 ,
isInversed: true ,
minimum: 1.8 ,
maximum: 2.4 ,
//radiusFactor: 1.5,
showTicks: true ,
showLabels: false ,
interval: 0.1 ,
//labelsPosition: ElementsPosition.outside,
ranges: [
GaugeRange ( startValue: 0 , endValue: nerdStats . vsapm , color: theme . colorScheme . primary )
] ,
annotations: [
GaugeAnnotation ( widget: Container ( child:
RichText (
textAlign: TextAlign . center ,
text: TextSpan (
style: TextStyle ( fontFamily: " Eurostile Round " ) ,
children: [
TextSpan ( text: " VS/APM \n " ) ,
TextSpan ( text: f3 . format ( nerdStats . vsapm ) , style: TextStyle ( fontSize: 25 , fontWeight: FontWeight . bold ) ) ,
]
) ) ) ,
angle: 0 , positionFactor: 0.5
)
] ,
)
]
) ,
) ,
Wrap (
2024-08-06 22:24:31 +00:00
children: [
2024-08-07 22:42:04 +00:00
GaugetThingy ( value: nerdStats . dss , min: 0 , max: 1.0 , label: t . statCellNum . dss , sideSize: 128.0 , fractionDigits: 3 ) ,
GaugetThingy ( value: nerdStats . dsp , min: 0 , max: 1.0 , label: t . statCellNum . dsp , sideSize: 128.0 , fractionDigits: 3 ) ,
GaugetThingy ( value: nerdStats . appdsp , min: 0 , max: 1.2 , label: t . statCellNum . appdsp , sideSize: 128.0 , fractionDigits: 3 )
2024-08-06 22:24:31 +00:00
] ,
2024-08-07 22:42:04 +00:00
)
]
) ,
] ,
)
) ;
}
}
class GaugetThingy extends StatelessWidget {
final double value ;
final double min ;
final double max ;
final String label ;
final double sideSize ;
final int fractionDigits ;
GaugetThingy ( { super . key , required this . value , required this . min , required this . max , required this . label , required this . sideSize , required this . fractionDigits } ) ;
@ override
Widget build ( BuildContext context ) {
return SizedBox (
height: sideSize ,
width: sideSize ,
child: SfRadialGauge (
axes: [
RadialAxis (
// startAngle: 180,
// endAngle: 0,
minimum: min ,
maximum: max ,
//radiusFactor: 1.5,
showTicks: true ,
showLabels: false ,
interval: 0.1 ,
//labelsPosition: ElementsPosition.outside,
ranges: [
GaugeRange ( startValue: 0 , endValue: value , color: theme . colorScheme . primary )
] ,
annotations: [
GaugeAnnotation ( widget: Container ( child:
Text ( f3 . format ( value ) , textAlign: TextAlign . center , style: TextStyle ( fontSize: 25 , fontWeight: FontWeight . bold ) ) ) ,
angle: 90 , positionFactor: 0.25
2024-08-06 22:24:31 +00:00
) ,
2024-08-07 22:42:04 +00:00
GaugeAnnotation ( widget: Container ( child:
Text ( label , textAlign: TextAlign . center , style: TextStyle ( height: . 9 ) ) ) ,
angle: 270 , positionFactor: 0.4
)
2024-08-06 22:24:31 +00:00
] ,
)
2024-08-07 22:42:04 +00:00
]
2024-08-04 22:23:08 +00:00
) ,
) ;
}
2024-08-07 22:42:04 +00:00
2024-08-06 22:24:31 +00:00
}
class _TLRecords extends StatelessWidget {
final String userID ;
final Function changePlayer ;
final List < BetaRecord > data ;
final bool wasActiveInTL ;
final bool oldMathcesHere ;
final bool separateScrollController ;
/// Widget, that displays Tetra League records.
/// Accepts list of TL records ([data]) and [userID] of player from the view
const _TLRecords ( { required this . userID , required this . changePlayer , required this . data , required this . wasActiveInTL , required this . oldMathcesHere , this . separateScrollController = false } ) ;
@ override
Widget build ( BuildContext context ) {
if ( data . isEmpty ) {
return Center ( child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
Text ( t . noRecords , style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 28 ) ) ,
if ( wasActiveInTL ) Text ( t . errors . actionSuggestion ) ,
if ( wasActiveInTL ) TextButton ( onPressed: ( ) { changePlayer ( userID , fetchTLmatches: true ) ; } , child: Text ( t . fetchAndSaveOldTLmatches ) )
] ,
) ) ;
}
bool bigScreen = MediaQuery . of ( context ) . size . width > = 768 ;
int length = data . length ;
return ListView . builder (
physics: const AlwaysScrollableScrollPhysics ( ) ,
controller: separateScrollController ? ScrollController ( ) : null ,
itemCount: oldMathcesHere ? length : length + 1 ,
itemBuilder: ( BuildContext context , int index ) {
if ( index = = length ) {
return Center ( child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
Text ( t . noOldRecords ( n: length ) , style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 28 ) ) ,
if ( wasActiveInTL ) Text ( t . errors . actionSuggestion ) ,
if ( wasActiveInTL ) TextButton ( onPressed: ( ) { changePlayer ( userID , fetchTLmatches: true ) ; } , child: Text ( t . fetchAndSaveOldTLmatches ) )
] ,
) ) ;
}
var accentColor = data [ index ] . results . leaderboard . firstWhere ( ( element ) = > element . id = = userID ) . wins > data [ index ] . results . leaderboard . firstWhere ( ( element ) = > element . id ! = userID ) . wins ? Colors . green : Colors . red ;
return Container (
decoration: BoxDecoration (
gradient: LinearGradient (
stops: const [ 0 , 0.05 ] ,
colors: [ accentColor , Colors . transparent ]
)
) ,
child: ListTile (
leading: Text ( " ${ data [ index ] . results . leaderboard . firstWhere ( ( element ) = > element . id = = userID ) . wins } : ${ data [ index ] . results . leaderboard . firstWhere ( ( element ) = > element . id ! = userID ) . wins } " ,
style: bigScreen ? const TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 28 , shadows: textShadow ) : const TextStyle ( fontSize: 28 , shadows: textShadow ) ) ,
title: Text ( " vs. ${ data [ index ] . results . leaderboard . firstWhere ( ( element ) = > element . id ! = userID ) . username } " ) ,
subtitle: Text ( timestamp ( data [ index ] . ts ) , style: const TextStyle ( color: Colors . grey ) ) ,
trailing: TrailingStats (
data [ index ] . results . leaderboard . firstWhere ( ( element ) = > element . id = = userID ) . stats . apm ,
data [ index ] . results . leaderboard . firstWhere ( ( element ) = > element . id = = userID ) . stats . pps ,
data [ index ] . results . leaderboard . firstWhere ( ( element ) = > element . id = = userID ) . stats . vs ,
data [ index ] . results . leaderboard . firstWhere ( ( element ) = > element . id ! = userID ) . stats . apm ,
data [ index ] . results . leaderboard . firstWhere ( ( element ) = > element . id ! = userID ) . stats . pps ,
data [ index ] . results . leaderboard . firstWhere ( ( element ) = > element . id ! = userID ) . stats . vs ,
) ,
onTap: ( ) = > Navigator . push ( context , MaterialPageRoute ( builder: ( context ) = > TlMatchResultView ( record: data [ index ] , initPlayerId: userID ) ) ) //Navigator.push(context, MaterialPageRoute(builder: (context) => TlMatchResultView(record: data[index], initPlayerId: userID))),
) ,
) ;
} ) ;
}
2024-07-27 19:10:45 +00:00
}