2024-09-14 22:05:50 +00:00
import ' dart:async ' ;
2024-10-19 23:19:10 +00:00
import ' dart:io ' ;
2024-10-20 17:03:15 +00:00
import ' dart:math ' ;
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 ;
2024-10-19 23:19:10 +00:00
import ' package:flutter_colorpicker/flutter_colorpicker.dart ' ;
2024-07-27 19:10:45 +00:00
import ' package:flutter_svg/flutter_svg.dart ' ;
2024-09-29 22:02:19 +00:00
import ' package:http/http.dart ' ;
2024-07-27 19:10:45 +00:00
import ' package:intl/intl.dart ' ;
import ' package:syncfusion_flutter_gauges/gauges.dart ' ;
2024-09-05 21:42:21 +00:00
import ' package:tetra_stats/data_objects/badge.dart ' ;
import ' package:tetra_stats/data_objects/beta_record.dart ' ;
import ' package:tetra_stats/data_objects/distinguishment.dart ' ;
import ' package:tetra_stats/data_objects/est_tr.dart ' ;
import ' package:tetra_stats/data_objects/nerd_stats.dart ' ;
import ' package:tetra_stats/data_objects/news.dart ' ;
import ' package:tetra_stats/data_objects/news_entry.dart ' ;
import ' package:tetra_stats/data_objects/p1nkl0bst3r.dart ' ;
import ' package:tetra_stats/data_objects/playstyle.dart ' ;
import ' package:tetra_stats/data_objects/record_extras.dart ' ;
import ' package:tetra_stats/data_objects/record_single.dart ' ;
import ' package:tetra_stats/data_objects/summaries.dart ' ;
import ' package:tetra_stats/data_objects/tetra_league.dart ' ;
import ' package:tetra_stats/data_objects/tetrio_constants.dart ' ;
import ' package:tetra_stats/data_objects/tetrio_player.dart ' ;
2024-07-27 19:10:45 +00:00
import ' package:tetra_stats/gen/strings.g.dart ' ;
2024-08-08 23:01:46 +00:00
import ' package:tetra_stats/services/crud_exceptions.dart ' ;
2024-08-09 22:52:50 +00:00
import ' package:tetra_stats/utils/colors_functions.dart ' ;
2024-10-19 23:19:10 +00:00
import ' package:tetra_stats/utils/filesizes_converter.dart ' ;
2024-07-27 19:10:45 +00:00
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-10-17 22:17:23 +00:00
import ' package:tetra_stats/views/destination_calculator.dart ' ;
import ' package:tetra_stats/views/destination_cutoffs.dart ' ;
import ' package:tetra_stats/views/destination_graphs.dart ' ;
import ' package:tetra_stats/views/destination_home.dart ' ;
import ' package:tetra_stats/views/destination_leaderboards.dart ' ;
import ' package:tetra_stats/views/destination_saved_data.dart ' ;
2024-08-06 22:24:31 +00:00
import ' package:tetra_stats/views/tl_match_view.dart ' ;
2024-10-03 21:00:41 +00:00
import ' package:tetra_stats/views/compare_view_tiles.dart ' ;
2024-08-09 22:52:50 +00:00
import ' package:tetra_stats/widgets/graphs.dart ' ;
2024-08-06 22:24:31 +00:00
import ' package:tetra_stats/widgets/list_tile_trailing_stats.dart ' ;
2024-07-27 19:10:45 +00:00
import ' package:tetra_stats/widgets/text_timestamp.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-07-27 19:10:45 +00:00
import ' package:tetra_stats/widgets/user_thingy.dart ' ;
2024-09-11 22:41:02 +00:00
import ' package:transparent_image/transparent_image.dart ' ;
2024-10-20 17:03:15 +00:00
import ' package:vector_math/vector_math_64.dart ' hide Colors ;
2024-07-27 19:10:45 +00:00
2024-10-16 23:10:08 +00:00
// TODO: Refactor it
2024-08-31 23:00:26 +00:00
var fDiff = NumberFormat ( " +#,###.####;-#,###.#### " ) ;
2024-09-10 21:22:17 +00:00
late Future < FetchResults > _data ;
late Future < News > _newsData ;
2024-08-31 23:00:26 +00:00
2024-10-17 22:17:23 +00:00
Future < FetchResults > getData ( String searchFor ) async {
TetrioPlayer player ;
try {
if ( searchFor . startsWith ( " ds: " ) ) {
player = await teto . fetchPlayer ( searchFor . substring ( 3 ) , isItDiscordID: true ) ; // we trying to get him with that
} else {
player = await teto . fetchPlayer ( searchFor ) ; // Otherwise it's probably a user id or username
}
} on TetrioPlayerNotExist {
2024-10-20 17:03:15 +00:00
return FetchResults ( false , null , [ ] , null , null , false , TetrioPlayerNotExist ( ) ) ;
2024-10-17 22:17:23 +00:00
}
late Summaries summaries ;
late Cutoffs cutoffs ;
List < dynamic > requests = await Future . wait ( [
teto . fetchSummaries ( player . userId ) ,
teto . fetchCutoffsBeanserver ( ) ,
] ) ;
List < TetraLeague > states = await teto . getStates ( player . userId , season: currentSeason ) ;
summaries = requests [ 0 ] ;
cutoffs = requests [ 1 ] ;
bool isTracking = await teto . isPlayerTracking ( player . userId ) ;
if ( isTracking ) { // if tracked - save data to local DB
await teto . storeState ( summaries . league ) ;
}
2024-10-20 17:03:15 +00:00
return FetchResults ( true , player , states , summaries , cutoffs , isTracking , null ) ;
2024-10-17 22:17:23 +00:00
}
2024-07-27 19:10:45 +00:00
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-09 22:52:50 +00:00
enum Cards { overview , tetraLeague , quickPlay , sprint , blitz }
2024-08-24 14:41:07 +00:00
enum CardMod { info , records , ex , exRecords }
2024-08-01 23:20:36 +00:00
Map < Cards , String > cardsTitles = {
Cards . overview: " Overview " ,
Cards . tetraLeague: t . tetraLeague ,
Cards . quickPlay: t . quickPlay ,
2024-08-09 22:52:50 +00:00
//Cards.quickPlayExpert: "${t.quickPlay} ${t.expert}",
2024-08-01 23:20:36 +00:00
Cards . sprint: t . sprint ,
Cards . blitz: t . blitz ,
2024-08-09 22:52:50 +00:00
//Cards.other: t.other
2024-08-01 23:20:36 +00:00
} ;
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-08 23:01:46 +00:00
int destination = 0 ;
2024-08-04 22:23:08 +00:00
String _searchFor = " 6098518e3d5155e6ec429cdc " ;
final TextEditingController _searchController = TextEditingController ( ) ;
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 ( ) ;
2024-09-10 21:22:17 +00:00
changePlayer ( _searchFor ) ;
2024-07-29 20:58:17 +00:00
super . initState ( ) ;
}
2024-08-04 22:23:08 +00:00
void changePlayer ( String player ) {
setState ( ( ) {
_searchFor = player ;
2024-10-17 22:17:23 +00:00
_data = getData ( _searchFor ) ;
2024-09-10 21:22:17 +00:00
_newsData = teto . fetchNews ( _searchFor ) ;
2024-08-04 22:23:08 +00:00
} ) ;
}
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-08-31 23:00:26 +00:00
NavigationRailDestination getDestinationButton ( IconData icon , String title ) {
return NavigationRailDestination (
icon: Tooltip (
message: title ,
child: Icon ( icon )
) ,
selectedIcon: Icon ( icon ) ,
label: Text ( title ) ,
) ;
}
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 (
2024-08-15 21:55:45 +00:00
mainAxisAlignment: MainAxisAlignment . center ,
2024-08-01 23:20:36 +00:00
children: [
2024-09-10 21:22:17 +00:00
TweenAnimationBuilder (
child: NavigationRail (
leading: FloatingActionButton (
elevation: 0 ,
onPressed: ( ) {
Scaffold . of ( context ) . openDrawer ( ) ;
} ,
child: const Icon ( Icons . search ) ,
) ,
trailing: IconButton (
onPressed: ( ) {
// Add your onPressed code here!
} ,
icon: const Icon ( Icons . more_horiz_rounded ) ,
) ,
destinations: [
getDestinationButton ( Icons . home , " Home " ) ,
getDestinationButton ( Icons . data_thresholding_outlined , " Graphs " ) ,
getDestinationButton ( Icons . leaderboard , " Leaderboards " ) ,
getDestinationButton ( Icons . compress , " Cutoffs " ) ,
getDestinationButton ( Icons . calculate , " Calc " ) ,
2024-09-13 22:00:11 +00:00
getDestinationButton ( Icons . info_outline , " Information " ) ,
2024-09-10 21:22:17 +00:00
getDestinationButton ( Icons . storage , " Saved Data " ) ,
getDestinationButton ( Icons . settings , " Settings " ) ,
] ,
selectedIndex: destination ,
onDestinationSelected: ( value ) {
setState ( ( ) {
destination = value ;
} ) ;
} ,
) ,
duration: Durations . long4 ,
tween: Tween < double > ( begin: 0 , end: 1 ) ,
2024-09-11 22:41:02 +00:00
curve: Easing . standard ,
2024-09-10 21:22:17 +00:00
builder: ( context , value , child ) {
return Container (
transform: Matrix4 . translationValues ( - 80 + value * 80 , 0 , 0 ) ,
2024-09-11 22:41:02 +00:00
child: Opacity ( opacity: value , child: child ) ,
2024-09-10 21:22:17 +00:00
) ;
} ,
) ,
2024-08-15 21:55:45 +00:00
Expanded (
child: switch ( destination ) {
2024-10-17 22:17:23 +00:00
0 = > DestinationHome ( searchFor: _searchFor , constraints: constraints , dataFuture: _data , newsFuture: _newsData ) ,
2024-08-15 21:55:45 +00:00
1 = > DestinationGraphs ( searchFor: _searchFor , constraints: constraints ) ,
2 = > DestinationLeaderboards ( constraints: constraints ) ,
2024-09-15 16:38:07 +00:00
3 = > DestinationCutoffs ( constraints: constraints ) ,
2024-09-19 22:38:31 +00:00
4 = > DestinationCalculator ( constraints: constraints ) ,
2024-10-15 22:17:24 +00:00
6 = > DestinationSavedData ( constraints: constraints ) ,
2024-10-19 23:19:10 +00:00
7 = > DestinationSettings ( constraints: constraints ) ,
2024-08-15 21:55:45 +00:00
_ = > Text ( " Unknown destination $ destination " )
} ,
)
2024-08-08 23:01:46 +00:00
] ) ;
} ,
) ) ;
}
}
2024-10-19 23:19:10 +00:00
class DestinationSettings extends StatefulWidget {
final BoxConstraints constraints ;
const DestinationSettings ( { super . key , required this . constraints } ) ;
@ override
State < DestinationSettings > createState ( ) = > _DestinationSettings ( ) ;
}
enum SettingsCardMod {
general ( " General " ) ,
customization ( " Custonization " ) ,
database ( " Local database " ) ;
const SettingsCardMod ( this . title ) ;
final String title ;
}
const TextStyle settingsTitlesStyle = TextStyle ( fontSize: 18 ) ;
const EdgeInsets descriptionPadding = EdgeInsets . fromLTRB ( 12.0 , 0.0 , 12.0 , 8.0 ) ;
class _DestinationSettings extends State < DestinationSettings > {
SettingsCardMod mod = SettingsCardMod . general ;
List < DropdownMenuItem < AppLocale > > locales = < DropdownMenuItem < AppLocale > > [ ] ;
String defaultNickname = " Checking... " ;
late bool oskKagariGimmick ;
late bool sheetbotRadarGraphs ;
late int ratingMode ;
late int timestampMode ;
late bool showPositions ;
late bool showAverages ;
late bool updateInBG ;
final TextEditingController _playertext = TextEditingController ( ) ;
@ override
void initState ( ) {
// if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS){
// windowManager.getTitle().then((value) => oldWindowTitle = value);
// windowManager.setTitle("Tetra Stats: ${t.settings}");
// }
_getPreferences ( ) ;
super . initState ( ) ;
}
@ override
void dispose ( ) {
// if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) windowManager.setTitle(oldWindowTitle);
super . dispose ( ) ;
}
void _getPreferences ( ) {
showPositions = prefs . getBool ( " showPositions " ) ? ? false ;
showAverages = prefs . getBool ( " showAverages " ) ? ? true ;
updateInBG = prefs . getBool ( " updateInBG " ) ? ? false ;
oskKagariGimmick = prefs . getBool ( " oskKagariGimmick " ) ? ? true ;
sheetbotRadarGraphs = prefs . getBool ( " sheetbotRadarGraphs " ) ? ? false ;
ratingMode = prefs . getInt ( " ratingMode " ) ? ? 0 ;
timestampMode = prefs . getInt ( " timestampMode " ) ? ? 0 ;
_setDefaultNickname ( prefs . getString ( " player " ) ) ;
}
Future < void > _setDefaultNickname ( String ? n ) async {
if ( n ! = null ) {
try {
defaultNickname = await teto . getNicknameByID ( n ) ;
} on TetrioPlayerNotExist {
defaultNickname = n ;
}
} else {
defaultNickname = " dan63047 " ;
}
setState ( ( ) { } ) ;
}
Future < void > _setPlayer ( String player ) async {
await prefs . setString ( ' player ' , player ) ;
await _setDefaultNickname ( player ) ;
}
Future < void > _removePlayer ( ) async {
await prefs . remove ( ' player ' ) ;
await _setDefaultNickname ( " 6098518e3d5155e6ec429cdc " ) ;
}
Widget getGeneralSettings ( ) {
return Column (
children: [
Card (
child: Center ( child: Padding (
padding: const EdgeInsets . only ( bottom: 8.0 ) ,
child: Column (
children: [
Text ( SettingsCardMod . general . title , style: const TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 42 ) ) ,
] ,
) ,
) ) ,
) ,
Card (
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
ListTile (
title: Text ( " Your account in TETR.IO " , style: settingsTitlesStyle ) ,
trailing: SizedBox ( width: 150.0 , child: TextField (
keyboardType: TextInputType . text ,
decoration: InputDecoration ( hintText: defaultNickname ) ,
//onChanged: (value) => setState((){rules.surgeInitAtB2b = int.parse(value);}),
) ) ,
) ,
Divider ( ) ,
Padding (
padding: descriptionPadding ,
child: Text ( " Stats of that player will be loaded initially right after launching this app. By default it loads my (dan63) stats. To change that, enter your nickname here. " ) ,
)
] ,
) ,
) ,
Card (
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
ListTile (
title: Text ( " Language " , style: settingsTitlesStyle ) ,
trailing: DropdownButton (
items: locales ,
value: LocaleSettings . currentLocale ,
onChanged: ( value ) {
LocaleSettings . setLocale ( value ! ) ;
if ( value . languageCode = = Platform . localeName . substring ( 0 , 2 ) ) {
prefs . remove ( ' locale ' ) ;
} else {
prefs . setString ( ' locale ' , value . languageCode ) ;
}
} ,
) ,
) ,
Divider ( ) ,
Padding (
padding: descriptionPadding ,
child: Text ( " Tetra Stats was translated on ${ locales . length } languages. By default, app will pick your system one or English, if locale of your system isn't avaliable. " ) ,
)
] ,
) ,
) ,
Card (
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
ListTile (
title: Text ( " Update data in the background " , style: settingsTitlesStyle ) ,
trailing: Switch ( value: updateInBG , onChanged: ( bool value ) {
prefs . setBool ( " updateInBG " , value ) ;
setState ( ( ) {
updateInBG = value ;
} ) ;
} )
) ,
Divider ( ) ,
Padding (
padding: descriptionPadding ,
child: Text ( " If on, Tetra Stats will attempt to retrieve new info once cache expires. Usually that happen every 5 minutes " ) ,
)
] ,
) ,
) ,
Card (
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
ListTile (
title: Text ( " Show leaderboard based stats " , style: settingsTitlesStyle ) ,
trailing: Switch ( value: showAverages , onChanged: ( bool value ) {
prefs . setBool ( " showAverages " , value ) ;
setState ( ( ) {
showAverages = value ;
} ) ;
} ) ,
) ,
Divider ( ) ,
Padding (
padding: descriptionPadding ,
child: Text ( " If on, Tetra Stats gonnna provide additional metrics, which will allow you to compare yourself with average player on your rank. The way you'll see it — stats will be highlited with corresponding color, hover over them with cursor for more info. " ) ,
)
] ,
) ,
) ,
Card (
surfaceTintColor: Colors . redAccent ,
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
ListTile (
title: Text ( " Show position on leaderboard by stats " , style: settingsTitlesStyle ) ,
trailing: Switch ( value: showPositions , onChanged: ( bool value ) {
prefs . setBool ( " showPositions " , value ) ;
setState ( ( ) {
showPositions = value ;
} ) ;
} ) ,
) ,
Divider ( ) ,
Padding (
padding: descriptionPadding ,
child: Text ( " This can take some time (and traffic) to load, but will allow you to see your position on the leaderboard, sorted by a stat " ) ,
)
] ,
) ,
)
]
) ;
}
Widget getCustomizationSettings ( ) {
return Column (
children: [
Card (
child: Center ( child: Padding (
padding: const EdgeInsets . only ( bottom: 8.0 ) ,
child: Column (
children: [
Text ( SettingsCardMod . customization . title , style: const TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 42 ) ) ,
] ,
) ,
) ) ,
) ,
Card (
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
ListTile (
title: Text ( " Accent color " , style: settingsTitlesStyle ) ,
trailing: ColorIndicator ( HSVColor . fromColor ( Theme . of ( context ) . colorScheme . primary ) , width: 25 , height: 25 ) ,
) ,
Divider ( ) ,
Padding (
padding: descriptionPadding ,
child: Text ( " That color is seen across this app and usually highlites interactive UI elements. " ) ,
)
] ,
) ,
) ,
Card (
child: Column (
mainAxisSize: MainAxisSize . min ,
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
ListTile (
title: Text ( " Timestamps format " , style: settingsTitlesStyle ) ,
trailing: DropdownButton (
value: timestampMode ,
items: < DropdownMenuItem > [
DropdownMenuItem ( value: 0 , child: Text ( t . timestampsAbsoluteGMT ) ) ,
DropdownMenuItem ( value: 1 , child: Text ( t . timestampsAbsoluteLocalTime ) ) ,
DropdownMenuItem ( value: 2 , child: Text ( t . timestampsRelative ) )
] ,
onChanged: ( dynamic value ) {
prefs . setInt ( " timestampMode " , value ) ;
setState ( ( ) {
timestampMode = value ;
} ) ;
} ,
) ,
) ,
Divider ( ) ,
Padding (
padding: descriptionPadding ,
child: Text ( " You can choose, in which way timestamps shows time. By default, they show time in GMT timezone, formatted according to chosen locale, example: ${ DateFormat . yMMMd ( LocaleSettings . currentLocale . languageCode ) . add_Hms ( ) . format ( DateTime . utc ( 2023 , DateTime . july , 20 , 21 , 03 , 19 ) ) } . " ) ,
) ,
Padding (
padding: descriptionPadding ,
child: Text ( " There is also: \n • Locale formatted in your timezone: ${ DateFormat . yMMMd ( LocaleSettings . currentLocale . languageCode ) . add_Hms ( ) . format ( DateTime . utc ( 2023 , DateTime . july , 20 , 21 , 03 , 19 ) . toLocal ( ) ) } \n • Relative timestamp: ${ relativeDateTime ( DateTime . utc ( 2023 , DateTime . july , 20 , 21 , 03 , 19 ) ) } " ) ,
)
] ,
) ,
) ,
Card (
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
ListTile (
title: Text ( " Sheetbot-like behavior for radar graphs " , style: settingsTitlesStyle ) ,
trailing: Switch ( value: sheetbotRadarGraphs , onChanged: ( bool value ) {
prefs . setBool ( " sheetbotRadarGraphs " , value ) ;
setState ( ( ) {
sheetbotRadarGraphs = value ;
} ) ;
} ) ,
) ,
Divider ( ) ,
Padding (
padding: descriptionPadding ,
child: Text ( " Altough it was considered by me, that the way graphs work in SheetBot is not very correct, some people were confused to see, that -0.5 stride dosen't look the way it looks on SheetBot graph. Hence, he we are: if this toggle is on, points on the graphs can appear on the opposite half of the graph if value is negative. " ) ,
)
] ,
) ,
) ,
Card (
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
ListTile (
title: Text ( " Osk-Kagari gimmick " , style: settingsTitlesStyle ) ,
trailing: Switch ( value: oskKagariGimmick , onChanged: ( bool value ) {
prefs . setBool ( " oskKagariGimmick " , value ) ;
setState ( ( ) {
oskKagariGimmick = value ;
} ) ;
} ) ,
) ,
Divider ( ) ,
Padding (
padding: descriptionPadding ,
child: Text ( " If on, instead of osk's rank, :kagari: will be rendered. " ) ,
)
] ,
) ,
)
] ,
) ;
}
Widget getDatabaseSettings ( ) {
return Column (
children: [
Card (
child: Center ( child: Column (
children: [
Text ( SettingsCardMod . database . title , style: const TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 42 ) ) ,
Divider ( ) ,
FutureBuilder < ( int , int , int ) > ( future: teto . getDatabaseData ( ) ,
builder: ( context , snapshot ) {
switch ( snapshot . connectionState ) {
case ConnectionState . none:
case ConnectionState . waiting:
return const Center ( child: CircularProgressIndicator ( ) ) ;
case ConnectionState . active:
case ConnectionState . done:
if ( snapshot . hasData ) {
return RichText (
text: TextSpan (
style: TextStyle ( fontFamily: " Eurostile Round " , color: Colors . white ) ,
children: [
TextSpan ( text: " ${ bytesToSize ( snapshot . data ! . $1 ) } " , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 28 ) ) ,
TextSpan ( text: " of data stored \n " ) ,
TextSpan ( text: " ${ intf . format ( snapshot . data ! . $2 ) } " , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 28 ) ) ,
TextSpan ( text: " Tetra League records saved \n " ) ,
TextSpan ( text: " ${ intf . format ( snapshot . data ! . $3 ) } " , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 28 ) ) ,
TextSpan ( text: " Tetra League playerstates saved " ) ,
]
)
) ;
}
if ( snapshot . hasError ) { return FutureError ( snapshot ) ; }
}
return Text ( " huh? " ) ;
}
) ,
Divider ( ) ,
Row (
mainAxisAlignment: MainAxisAlignment . center ,
children: [
Expanded (
child: ElevatedButton . icon (
onPressed: ( ) { teto . removeDuplicatesFromTLMatches ( ) . then ( ( _ ) = > setState ( ( ) { } ) ) ; } ,
icon: const Icon ( Icons . build ) ,
label: Text ( " Fix " ) ,
style: const ButtonStyle ( shape: WidgetStatePropertyAll ( RoundedRectangleBorder ( borderRadius: BorderRadius . only ( bottomLeft: Radius . circular ( 12.0 ) ) ) ) )
)
) ,
Expanded (
child: ElevatedButton . icon (
onPressed: ( ) { teto . compressDB ( ) . then ( ( _ ) = > setState ( ( ) { } ) ) ; } ,
icon: const Icon ( Icons . compress ) ,
label: Text ( " Compress " ) ,
style: const ButtonStyle ( shape: WidgetStatePropertyAll ( RoundedRectangleBorder ( borderRadius: BorderRadius . only ( bottomRight: Radius . circular ( 12.0 ) ) ) ) )
)
)
] ,
)
] ,
) ) ,
) ,
Card (
child: ListTile (
title: Text ( " Export Database " , style: settingsTitlesStyle ) ,
) ,
) ,
Card (
child: ListTile (
title: Text ( " Import Database " , style: settingsTitlesStyle ) ,
) ,
)
] ,
) ;
}
@ override
Widget build ( BuildContext context ) {
final t = Translations . of ( context ) ;
if ( locales . isEmpty ) for ( var v in AppLocale . values ) {
locales . add ( DropdownMenuItem < AppLocale > (
value: v , child: Text ( t . locales [ v . languageTag ] ! ) ) ) ;
}
return Row (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
SizedBox (
width: 450 ,
child: Column (
children: [
const Card (
child: Row (
mainAxisSize: MainAxisSize . min ,
children: [
Spacer ( ) ,
Text ( " Settings " , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 36 ) ) ,
Spacer ( )
] ,
) ,
) ,
for ( SettingsCardMod m in SettingsCardMod . values ) Card (
child: ListTile (
title: Text ( m . title ) ,
trailing: Icon ( Icons . arrow_right , color: mod = = m ? Colors . white : Colors . grey ) ,
onTap: ( ) {
setState ( ( ) {
mod = m ;
} ) ;
} ,
) ,
)
] ,
) ,
) ,
SizedBox (
width: widget . constraints . maxWidth - 450 - 80 ,
child: SingleChildScrollView (
child: switch ( mod ) {
SettingsCardMod . general = > getGeneralSettings ( ) ,
SettingsCardMod . customization = > getCustomizationSettings ( ) ,
SettingsCardMod . database = > getDatabaseSettings ( ) ,
} ,
)
)
] ,
) ;
}
}
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-09 22:52:50 +00:00
if ( news . news . isEmpty ) const Center ( child: Text ( " Empty list " ) )
2024-08-04 22:23:08 +00:00
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-09 22:52:50 +00:00
class FakeDistinguishmentThingy extends StatelessWidget {
final bool banned ;
final bool badStanding ;
final bool bot ;
final String ? botMaintainers ;
FakeDistinguishmentThingy ( { super . key , this . banned = false , this . badStanding = false , this . bot = false , this . botMaintainers } ) ;
Color getCardTint ( ) {
if ( banned ) return Colors . red ;
if ( badStanding ) return Colors . redAccent ;
2024-08-31 23:00:26 +00:00
if ( bot ) return const Color . fromARGB ( 255 , 60 , 93 , 55 ) ;
2024-08-09 22:52:50 +00:00
return theme . colorScheme . surface ;
}
InlineSpan getDistinguishmentTitle ( ) {
String text = " " ;
if ( banned ) text = " banned " ;
if ( badStanding ) text = " bad standing " ;
if ( bot ) text = " bot account " ;
return TextSpan ( text: text . toUpperCase ( ) , style: const TextStyle ( fontSize: 28 , fontWeight: FontWeight . bold , color: Colors . white ) ) ;
}
String getDistinguishmentSubtitle ( ) {
if ( banned ) return " Bans are placed when TETR.IO rules or terms of service are broken " ;
if ( badStanding ) return " One or more recent bans on record " ;
if ( bot ) return " Operated by $ botMaintainers " ;
return " " ;
}
@ override
Widget build ( BuildContext context ) {
return Card (
surfaceTintColor: getCardTint ( ) ,
2024-08-12 23:07:59 +00:00
child: Container (
2024-08-31 23:00:26 +00:00
decoration: banned ? const BoxDecoration (
2024-08-12 23:07:59 +00:00
gradient: LinearGradient (
2024-08-31 23:00:26 +00:00
colors: [ Colors . transparent , Color . fromARGB ( 171 , 244 , 67 , 54 ) , Color . fromARGB ( 171 , 244 , 67 , 54 ) ] ,
2024-08-12 23:07:59 +00:00
stops: [ 0.1 , 0.9 , 0.01 ] ,
tileMode: TileMode . mirror ,
begin: Alignment . topLeft ,
end: AlignmentDirectional ( - 0.95 , - 0.95 )
)
) : null ,
child: Column (
children: [
Center (
child: RichText (
textAlign: TextAlign . center ,
text: TextSpan (
style: DefaultTextStyle . of ( context ) . style ,
children: [ getDistinguishmentTitle ( ) ] ,
) ,
2024-08-09 22:52:50 +00:00
) ,
) ,
2024-08-12 23:07:59 +00:00
Text ( getDistinguishmentSubtitle ( ) , style: const TextStyle ( fontSize: 18 ) , textAlign: TextAlign . center ) ,
] ,
) ,
2024-08-09 22:52:50 +00:00
) ,
) ;
}
}
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 ,
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 ,
errorBuilder: ( context , error , stackTrace ) {
return Image . asset ( " res/icons/kagari.png " , height: 32 , width: 32 ) ;
}
) ;
} ,
)
)
] ,
) ,
)
] ,
) ,
) ;
}
}
2024-10-19 23:19:10 +00:00
class NewUserThingy extends StatefulWidget {
2024-07-27 19:10:45 +00:00
final TetrioPlayer player ;
final bool showStateTimestamp ;
2024-10-20 17:03:15 +00:00
final bool initIsTracking ;
2024-07-27 19:10:45 +00:00
final Function setState ;
2024-10-20 17:03:15 +00:00
const NewUserThingy ( { super . key , required this . player , required this . initIsTracking , required this . showStateTimestamp , required this . setState } ) ;
2024-07-27 19:10:45 +00:00
2024-10-19 23:19:10 +00:00
@ override
State < NewUserThingy > createState ( ) = > _NewUserThingyState ( ) ;
}
class _NewUserThingyState extends State < NewUserThingy > with SingleTickerProviderStateMixin {
late AnimationController _addToTrackAnimController ;
late Animation _addToTrackAnim ;
@ override
void initState ( ) {
_addToTrackAnimController = AnimationController (
2024-10-20 17:03:15 +00:00
value: widget . initIsTracking ? 1.0 : 0.0 ,
duration: Durations . extralong4 ,
2024-10-19 23:19:10 +00:00
vsync: this ,
) ;
_addToTrackAnim = new Tween (
begin: 0.0 ,
end: 1.0 ,
) . animate ( new CurvedAnimation (
parent: _addToTrackAnimController ,
2024-10-20 17:03:15 +00:00
curve: Cubic ( . 15 , - 0.40 , . 86 , - 0.39 ) ,
reverseCurve: Cubic ( 0 , . 99 , . 99 , 1.01 )
2024-10-19 23:19:10 +00:00
) ) ;
2024-10-20 17:03:15 +00:00
2024-10-19 23:19:10 +00:00
super . initState ( ) ;
}
@ override
void dispose ( ) {
_addToTrackAnimController . dispose ( ) ;
super . dispose ( ) ;
}
2024-07-27 19:10:45 +00:00
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 ) {
double pfpHeight = 128 ;
int xpTableID = 0 ;
2024-10-19 23:19:10 +00:00
while ( widget . player . xp > xpTableScuffed . values . toList ( ) [ xpTableID ] ) {
2024-07-27 19:10:45 +00:00
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-10-19 23:19:10 +00:00
height: widget . player . bannerRevision ! = null ? 218.0 : 138.0 ,
2024-07-27 19:10:45 +00:00
child: Stack (
//clipBehavior: Clip.none,
children: [
2024-08-31 23:00:26 +00:00
// TODO: osk banner can cause memory leak
2024-10-19 23:19:10 +00:00
if ( widget . player . bannerRevision ! = null ) FadeInImage . memoryNetwork ( image: kIsWeb ? " https://ts.dan63.by/oskware_bridge.php?endpoint=TetrioBanner&user= ${ widget . player . userId } &rv= ${ widget . player . bannerRevision } " : " https://tetr.io/user-content/banners/ ${ widget . player . userId } .jpg?rv= ${ widget . player . bannerRevision } " ,
2024-09-11 22:41:02 +00:00
placeholder: kTransparentImage ,
2024-07-27 19:10:45 +00:00
fit: BoxFit . cover ,
height: 120 ,
2024-09-11 22:41:02 +00:00
fadeInCurve: Easing . standard , fadeInDuration: Durations . long4
2024-07-27 19:10:45 +00:00
) ,
Positioned (
2024-10-19 23:19:10 +00:00
top: widget . player . bannerRevision ! = null ? 90.0 : 10.0 ,
2024-07-27 19:10:45 +00:00
left: 16.0 ,
child: ClipRRect (
borderRadius: BorderRadius . circular ( 1000 ) ,
2024-10-19 23:19:10 +00:00
child: widget . player . role = = " banned "
2024-07-27 19:10:45 +00:00
? Image . asset ( " res/avatars/tetrio_banned.png " , fit: BoxFit . fitHeight , height: pfpHeight , )
2024-10-19 23:19:10 +00:00
: widget . player . avatarRevision ! = null
? FadeInImage . memoryNetwork ( image: kIsWeb ? " https://ts.dan63.by/oskware_bridge.php?endpoint=TetrioProfilePicture&user= ${ widget . player . userId } &rv= ${ widget . player . avatarRevision } " : " https://tetr.io/user-content/avatars/ ${ widget . player . userId } .jpg?rv= ${ widget . player . avatarRevision } " ,
2024-09-11 22:41:02 +00:00
fit: BoxFit . fitHeight , height: 128 , placeholder: kTransparentImage , fadeInCurve: Easing . emphasizedDecelerate , fadeInDuration: Durations . long4 )
2024-07-27 19:10:45 +00:00
: Image . asset ( " res/avatars/tetrio_anon.png " , fit: BoxFit . fitHeight , height: pfpHeight ) ,
)
) ,
Positioned (
2024-10-19 23:19:10 +00:00
top: widget . player . bannerRevision ! = null ? 120.0 : 40.0 ,
2024-07-27 19:10:45 +00:00
left: 160.0 ,
2024-08-31 23:00:26 +00:00
child: Tooltip (
2024-10-19 23:19:10 +00:00
message: " ${ widget . player . userId } \n (Click to copy user ID) " ,
child: RichText ( text: TextSpan ( text: widget . player . username , style: TextStyle (
fontFamily: fontStyle ( widget . player . username . length ) ,
2024-08-31 23:00:26 +00:00
fontSize: 28 ,
) ,
recognizer: TapGestureRecognizer ( ) . . onTap = ( ) {
2024-10-19 23:19:10 +00:00
copyToClipboard ( widget . player . userId ) ;
2024-08-31 23:00:26 +00:00
ScaffoldMessenger . of ( context ) . showSnackBar ( SnackBar ( content: Text ( t . copiedToClipboard ) ) ) ;
}
)
)
2024-07-27 19:10:45 +00:00
) ,
) ,
Positioned (
2024-10-19 23:19:10 +00:00
top: widget . player . bannerRevision ! = null ? 160.0 : 80.0 ,
2024-07-27 19:10:45 +00:00
left: 160.0 ,
child: Row (
children: [
Padding (
padding: const EdgeInsets . only ( right: 4.0 ) ,
2024-10-19 23:19:10 +00:00
child: Chip ( label: Text ( widget . player . role . toUpperCase ( ) , style: const TextStyle ( shadows: textShadow ) , ) , padding: const EdgeInsets . all ( 0.0 ) , color: WidgetStatePropertyAll ( roleColor ( widget . 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-10-19 23:19:10 +00:00
if ( widget . player . friendCount > 0 ) const WidgetSpan ( child: Icon ( Icons . person ) , alignment: PlaceholderAlignment . middle , baseline: TextBaseline . alphabetic ) ,
if ( widget . player . friendCount > 0 ) TextSpan ( text: " ${ intf . format ( widget . player . friendCount ) } " ) ,
if ( widget . player . supporterTier > 0 ) WidgetSpan ( child: Icon ( widget . player . supporterTier > 1 ? Icons . star : Icons . star_border , color: widget . player . supporterTier > 1 ? Colors . yellowAccent : Colors . white ) , alignment: PlaceholderAlignment . middle , baseline: TextBaseline . alphabetic ) ,
if ( widget . player . supporterTier > 0 ) TextSpan ( text: widget . player . supporterTier . toString ( ) , style: TextStyle ( color: widget . player . supporterTier > 1 ? Colors . yellowAccent : Colors . white ) ) ,
2024-07-27 19:10:45 +00:00
]
)
)
] ,
) ,
) ,
Positioned (
2024-10-19 23:19:10 +00:00
top: widget . player . bannerRevision ! = null ? 193.0 : 113.0 ,
2024-07-27 19:10:45 +00:00
left: 160.0 ,
2024-08-31 23:00:26 +00:00
child: SizedBox (
width: 270 ,
child: RichText (
text: TextSpan (
style: const TextStyle ( fontFamily: " Eurostile Round " ) ,
children: [
2024-10-19 23:19:10 +00:00
if ( widget . player . country ! = null ) TextSpan ( text: " ${ t . countries [ widget . player . country ] } • " ) ,
TextSpan ( text: timestamp ( widget . player . registrationTime ) , style: const TextStyle ( color: Colors . grey ) )
2024-08-31 23:00:26 +00:00
]
)
) ,
2024-07-27 19:10:45 +00:00
)
) ,
Positioned (
2024-10-19 23:19:10 +00:00
top: widget . player . bannerRevision ! = null ? 126.0 : 46.0 ,
2024-07-27 19:10:45 +00:00
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-10-19 23:19:10 +00:00
TextSpan ( text: " Level ${ ( widget . player . level . isNegative | | widget . player . level . isNaN ) ? " --- " : intf . format ( widget . player . level . floor ( ) ) } " , style: TextStyle ( decoration: ( widget . player . level . isNegative | | widget . player . level . isNaN ) ? null : TextDecoration . underline , decorationColor: Colors . white70 , decorationStyle: TextDecorationStyle . dotted , color: ( widget . player . level . isNegative | | widget . player . level . isNaN ) ? Colors . grey : Colors . white ) , recognizer: ( widget . player . level . isNegative | | widget . player . level . isNaN ) ? null : TapGestureRecognizer ( ) ? . . onTap = ( ) {
2024-07-27 19:10:45 +00:00
showDialog (
context: context ,
builder: ( BuildContext context ) = > AlertDialog (
2024-10-19 23:19:10 +00:00
title: Text ( " Level ${ intf . format ( widget . player . level . floor ( ) ) } " , textAlign: TextAlign . center ) ,
2024-07-27 19:10:45 +00:00
content: SingleChildScrollView (
child: ListBody ( children: [
Text (
2024-10-19 23:19:10 +00:00
" ${ NumberFormat . decimalPatternDigits ( locale: LocaleSettings . currentLocale . languageCode , decimalDigits: 2 ) . format ( widget . player . xp ) } XP " ,
2024-07-27 19:10:45 +00:00
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: [
2024-10-19 23:19:10 +00:00
LinearGaugeRange ( startValue: 0 , endValue: widget . player . level - widget . player . level . floor ( ) , color: Colors . cyanAccent ) ,
LinearGaugeRange ( startValue: 0 , endValue: ( widget . player . xp / xpTableScuffed . values . toList ( ) [ xpTableID ] ) , color: Colors . redAccent , position: LinearElementPosition . cross )
2024-07-27 19:10:45 +00:00
] ,
showTicks: true ,
showLabels: false
) ,
) ,
2024-10-19 23:19:10 +00:00
Text ( " ${ t . statCellNum . xpProgress } : ${ ( ( widget . player . level - widget . player . level . floor ( ) ) * 100 ) . toStringAsFixed ( 2 ) } % " ) ,
Text ( " ${ t . statCellNum . xpFrom0ToLevel ( n: xpTableScuffed . keys . toList ( ) [ xpTableID ] ) } : ${ ( ( widget . player . xp / xpTableScuffed . values . toList ( ) [ xpTableID ] ) * 100 ) . toStringAsFixed ( 2 ) } % ( ${ NumberFormat . decimalPatternDigits ( locale: LocaleSettings . currentLocale . languageCode , decimalDigits: 0 ) . format ( xpTableScuffed . values . toList ( ) [ xpTableID ] - widget . player . xp ) } ${ t . statCellNum . xpLeft } ) " )
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-03 17:52:20 +00:00
const TextSpan ( text: " \n " ) ,
2024-10-19 23:19:10 +00:00
TextSpan ( text: widget . player . gameTime . isNegative ? " -h --m " : playtime ( widget . player . gameTime ) , style: TextStyle ( color: widget . player . gameTime . isNegative ? Colors . grey : Colors . white , decoration: widget . player . gameTime . isNegative ? null : TextDecoration . underline , decorationColor: Colors . white70 , decorationStyle: TextDecorationStyle . dotted ) , recognizer: ! widget . 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-10-19 23:19:10 +00:00
" ${ intf . format ( widget . player . gameTime . inDays ) } d ${ nonsecs . format ( widget . player . gameTime . inHours % 24 ) } h ${ nonsecs . format ( widget . player . gameTime . inMinutes % 60 ) } m ${ nonsecs . format ( widget . player . gameTime . inSeconds % 60 ) } s ${ nonsecs3 . format ( widget . player . gameTime . inMilliseconds % 1000 ) } ms ${ nonsecs3 . format ( widget . 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 ) ,
2024-10-19 23:19:10 +00:00
child: Text ( " It's ${ f4 . format ( widget . player . gameTime . inSeconds / 31536000 ) } years, " ) ,
2024-08-06 22:24:31 +00:00
) ,
2024-10-19 23:19:10 +00:00
Text ( " ${ f4 . format ( widget . player . gameTime . inSeconds / 2628000 ) } monts, " ) ,
Text ( " ${ f4 . format ( widget . player . gameTime . inSeconds / 3600 ) } hours, " ) ,
Text ( " ${ f2 . format ( widget . player . gameTime . inMilliseconds / 60000 ) } minutes, " ) ,
Text ( " ${ intf . format ( widget . 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 " ) ,
2024-10-19 23:19:10 +00:00
TextSpan ( text: widget . player . gamesWon > - 1 ? intf . format ( widget . player . gamesWon ) : " --- " , style: TextStyle ( color: widget . player . gamesWon > - 1 ? Colors . white : Colors . grey ) ) ,
TextSpan ( text: " / ${ widget . player . gamesPlayed > - 1 ? intf . format ( widget . 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: [
2024-10-19 23:19:10 +00:00
Expanded (
child: AnimatedBuilder (
animation: _addToTrackAnim ,
builder: ( context , child ) {
2024-10-20 17:03:15 +00:00
double firstButtonPosition = 0 + ( _addToTrackAnim . value as double ) * 25 ;
double secondButtonPosition = - 25 + ( _addToTrackAnim . value as double ) * 25 ;
2024-10-19 23:19:10 +00:00
double firstButtonOpacity = 1 - ( _addToTrackAnim . value as double ) * 2 ;
double secondButtonOpacity = _addToTrackAnim . value * 2 - 1 ;
return ElevatedButton . icon (
onPressed: ( ) {
2024-10-20 17:03:15 +00:00
_addToTrackAnimController . value = = 1 ? teto . deletePlayerToTrack ( widget . player . userId ) : teto . addPlayerToTrack ( widget . player ) ;
2024-10-19 23:19:10 +00:00
_addToTrackAnim . isCompleted ? _addToTrackAnimController . reverse ( ) : _addToTrackAnimController . forward ( ) ;
} ,
2024-10-20 17:03:15 +00:00
icon: _addToTrackAnim . value < 0.5 ? Opacity (
opacity: min ( 1 , firstButtonOpacity ) ,
child: Transform . translate (
offset: Offset ( 0 , _addToTrackAnim . status = = AnimationStatus . forward ? firstButtonPosition * 4 : firstButtonPosition ) ,
child: Transform . rotate (
angle: _addToTrackAnim . status = = AnimationStatus . forward ? ( _addToTrackAnim . value as double ) * 2 : 0 ,
child: const Icon ( Icons . person_add ) ,
) ,
) ,
2024-10-19 23:19:10 +00:00
) : Container (
transform: Matrix4 . translationValues ( 0 , secondButtonPosition , 0 ) ,
child: Opacity (
2024-10-20 17:03:15 +00:00
opacity: max ( 0 , secondButtonOpacity ) ,
2024-10-19 23:19:10 +00:00
child: const Icon ( Icons . person_remove )
)
) ,
label: _addToTrackAnim . value < 0.5 ? Container (
transform: Matrix4 . translationValues ( 0 , firstButtonPosition , 0 ) ,
child: Opacity (
2024-10-20 17:03:15 +00:00
opacity: min ( 1 , firstButtonOpacity ) ,
child: Text ( _addToTrackAnimController . isAnimating & & _addToTrackAnim . status = = AnimationStatus . forward ? " Done! " : " Track " )
2024-10-19 23:19:10 +00:00
)
) : Container (
transform: Matrix4 . translationValues ( 0 , secondButtonPosition , 0 ) ,
child: Opacity (
2024-10-20 17:03:15 +00:00
opacity: max ( 0 , secondButtonOpacity ) ,
child: Text ( " Stop tracking " )
2024-10-19 23:19:10 +00:00
)
) ,
style: const ButtonStyle ( shape: WidgetStatePropertyAll ( RoundedRectangleBorder ( borderRadius: BorderRadius . only ( bottomLeft: Radius . circular ( 12.0 ) ) ) ) ) ) ;
} ,
) ) ,
2024-10-03 21:00:41 +00:00
Expanded (
child: ElevatedButton . icon (
onPressed: ( ) {
Navigator . push ( context , MaterialPageRoute (
2024-10-19 23:19:10 +00:00
builder: ( context ) = > CompareView ( widget . player ) ,
2024-10-03 21:00:41 +00:00
) ,
) ;
} ,
icon: const Icon ( Icons . balance ) ,
label: Text ( t . compare ) ,
style: const ButtonStyle ( shape: WidgetStatePropertyAll ( RoundedRectangleBorder ( borderRadius: BorderRadius . only ( bottomRight: Radius . circular ( 12.0 ) ) ) ) )
)
)
2024-08-04 22:23:08 +00:00
] ,
)
] ,
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 {
2024-08-16 22:40:09 +00:00
final TetraLeague league ;
2024-09-08 22:10:51 +00:00
final TetraLeague ? toCompare ;
2024-09-01 21:44:19 +00:00
final Cutoffs ? cutoffs ;
2024-08-04 22:23:08 +00:00
2024-09-08 22:10:51 +00:00
const TetraLeagueThingy ( { super . key , required this . league , this . toCompare , this . cutoffs } ) ;
2024-08-04 22:23:08 +00:00
@ override
Widget build ( BuildContext context ) {
return Card (
2024-09-05 21:42:21 +00:00
//surfaceTintColor: rankColors[league.rank],
2024-08-04 22:23:08 +00:00
child: Column (
children: [
2024-09-09 22:38:52 +00:00
TLRatingThingy ( userID: " w " , tlData: league , oldTl: toCompare , showPositions: true ) ,
2024-09-01 21:44:19 +00:00
TLProgress (
tlData: league ,
previousRankTRcutoff: cutoffs ! = null ? cutoffs ! . tr [ league . rank ! = " z " ? league . rank : league . percentileRank ] : null ,
nextRankTRcutoff: cutoffs ! = null ? ( league . rank ! = " z " ? league . rank = = " x+ " : league . percentileRank = = " x+ " ) ? 25000 : cutoffs ! . tr [ ranks . elementAtOrNull ( ranks . indexOf ( league . rank ! = " z " ? league . rank : league . percentileRank ) + 1 ) ] : null ,
2024-09-08 22:10:51 +00:00
previousRankTRcutoffTarget: league . rank ! = " z " ? rankTargets [ league . rank ] : null ,
nextRankTRcutoffTarget: ( league . rank ! = " z " & & league . rank ! = " x+ " ) ? rankTargets [ ranks . elementAtOrNull ( ranks . indexOf ( league . rank ) + 1 ) ] : null ,
2024-09-01 21:44:19 +00:00
previousGlickoCutoff: cutoffs ! = null ? cutoffs ! . glicko [ league . rank ! = " z " ? league . rank : league . percentileRank ] : null ,
nextRankGlickoCutoff: cutoffs ! = null ? ( league . rank ! = " z " ? league . rank = = " x+ " : league . percentileRank = = " x+ " ) ? 25000 : cutoffs ! . glicko [ ranks . elementAtOrNull ( ranks . indexOf ( league . rank ! = " z " ? league . rank : league . percentileRank ) + 1 ) ] : null ,
) ,
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 (
2024-08-09 22:52:50 +00:00
defaultColumnWidth: const IntrinsicColumnWidth ( ) ,
2024-08-07 22:42:04 +00:00
children: [
TableRow ( children: [
2024-08-15 21:55:45 +00:00
Text ( f2 . format ( league . apm ? ? 0.00 ) , textAlign: TextAlign . right , style: const TextStyle ( fontSize: 21 ) ) ,
2024-09-08 22:10:51 +00:00
const Text ( " APM " , style: TextStyle ( fontSize: 21 ) ) ,
if ( toCompare ! = null ) Text ( " ( ${ comparef2 . format ( league . apm ! - toCompare ! . apm ! ) } ) " , textAlign: TextAlign . right , style: TextStyle ( fontSize: 21 , color: getDifferenceColor ( league . apm ! - toCompare ! . apm ! ) ) )
2024-08-07 22:42:04 +00:00
] ) ,
TableRow ( children: [
2024-08-15 21:55:45 +00:00
Text ( f2 . format ( league . pps ? ? 0.00 ) , textAlign: TextAlign . right , style: const TextStyle ( fontSize: 21 ) ) ,
2024-09-08 22:10:51 +00:00
const Text ( " PPS " , style: TextStyle ( fontSize: 21 ) ) ,
if ( toCompare ! = null ) Text ( " ( ${ comparef2 . format ( league . pps ! - toCompare ! . pps ! ) } ) " , textAlign: TextAlign . right , style: TextStyle ( fontSize: 21 , color: getDifferenceColor ( league . pps ! - toCompare ! . pps ! ) ) )
2024-08-07 22:42:04 +00:00
] ) ,
TableRow ( children: [
2024-08-15 21:55:45 +00:00
Text ( f2 . format ( league . vs ? ? 0.00 ) , textAlign: TextAlign . right , style: const TextStyle ( fontSize: 21 ) ) ,
2024-09-08 22:10:51 +00:00
const Text ( " VS " , style: TextStyle ( fontSize: 21 ) ) ,
if ( toCompare ! = null ) Text ( " ( ${ comparef2 . format ( league . vs ! - toCompare ! . vs ! ) } ) " , textAlign: TextAlign . right , style: TextStyle ( fontSize: 21 , color: getDifferenceColor ( league . vs ! - toCompare ! . vs ! ) ) )
2024-08-07 22:42:04 +00:00
] )
] ,
) ,
) ,
2024-08-06 22:24:31 +00:00
) ,
SizedBox (
height: 128.0 ,
width: 128.0 ,
2024-08-12 23:07:59 +00:00
child: ClipRRect (
borderRadius: BorderRadius . circular ( 1000 ) ,
child: SfRadialGauge (
backgroundColor: Colors . black ,
axes: [
RadialAxis (
2024-08-24 14:41:07 +00:00
minimum: 0.0 ,
maximum: 1.0 ,
2024-08-12 23:07:59 +00:00
radiusFactor: 1.01 ,
showTicks: true ,
showLabels: false ,
2024-08-24 14:41:07 +00:00
interval: 0.25 ,
minorTicksPerInterval: 0 ,
2024-08-12 23:07:59 +00:00
ranges: [
GaugeRange ( startValue: 0 , endValue: league . winrate , color: theme . colorScheme . primary )
] ,
annotations: [
GaugeAnnotation ( widget: Container ( child:
Text ( percentage . format ( league . winrate ) , textAlign: TextAlign . center , style: const TextStyle ( fontSize: 25 , fontWeight: FontWeight . bold ) ) ) ,
angle: 90 , positionFactor: 0.1
) ,
GaugeAnnotation ( widget: Container ( child:
Text ( t . statCellNum . winrate , textAlign: TextAlign . center ) ) ,
angle: 270 , positionFactor: 0.4
2024-09-08 22:10:51 +00:00
) ,
if ( toCompare ! = null ) GaugeAnnotation ( widget: Container ( child:
Text ( comparef2 . format ( ( league . winrate - toCompare ! . winrate ) * 100 ) , textAlign: TextAlign . center , style: TextStyle ( color: getDifferenceColor ( league . winrate - toCompare ! . winrate ) ) ) ) ,
angle: 90 , positionFactor: 0.45
2024-08-12 23:07:59 +00:00
)
] ,
)
]
) ,
2024-08-06 22:24:31 +00:00
) ,
) ,
2024-08-07 22:42:04 +00:00
Expanded (
child: Center (
child: Table (
2024-08-09 22:52:50 +00:00
defaultColumnWidth: const IntrinsicColumnWidth ( ) ,
2024-08-07 22:42:04 +00:00
children: [
TableRow ( children: [
//Text("APM: ", style: TextStyle(fontSize: 21)),
2024-08-09 22:52:50 +00:00
Text ( intf . format ( league . gamesPlayed ) , textAlign: TextAlign . right , style: const TextStyle ( fontSize: 21 ) ) ,
2024-09-08 22:10:51 +00:00
const Text ( " Games " , style: TextStyle ( fontSize: 21 ) ) ,
if ( toCompare ! = null ) Text ( " ( ${ comparef2 . format ( league . gamesPlayed - toCompare ! . gamesPlayed ) } ) " , textAlign: TextAlign . right , style: TextStyle ( fontSize: 21 , color: Colors . grey ) )
2024-08-07 22:42:04 +00:00
] ) ,
TableRow ( children: [
//Text("PPS: ", style: TextStyle(fontSize: 21)),
2024-08-09 22:52:50 +00:00
Text ( intf . format ( league . gamesWon ) , textAlign: TextAlign . right , style: const TextStyle ( fontSize: 21 ) ) ,
2024-09-08 22:10:51 +00:00
const Text ( " Won " , style: TextStyle ( fontSize: 21 ) ) ,
if ( toCompare ! = null ) Text ( " ( ${ comparef2 . format ( league . gamesWon - toCompare ! . gamesWon ) } ) " , textAlign: TextAlign . right , style: TextStyle ( fontSize: 21 , color: Colors . grey ) )
2024-09-09 22:38:52 +00:00
] ) ,
TableRow ( children: [
//Text("VS: ", style: TextStyle(fontSize: 21)),
Tooltip ( child: Text ( " ${ league . gxe . isNegative ? " --- " : f3 . format ( league . gxe ) } " , textAlign: TextAlign . right , style: TextStyle ( fontSize: 21 , color: league . standingLocal . isNegative ? Colors . grey : Colors . white ) ) , message: " ${ f2 . format ( league . s1tr ) } " , ) ,
Text ( " GLIXARE " , style: TextStyle ( fontSize: 21 , color: league . standingLocal . isNegative ? Colors . grey : Colors . white ) ) ,
if ( toCompare ! = null ) Text ( " ( ${ comparef . format ( league . gxe - toCompare ! . gxe ) } ) " , textAlign: TextAlign . right , style: TextStyle ( fontSize: 21 , color: getDifferenceColor ( league . standingLocal - toCompare ! . standingLocal ) ) )
] ) ,
2024-08-07 22:42:04 +00:00
] ,
) ,
) ,
) ,
] ,
) ,
] ,
) ,
) ;
}
}
class NerdStatsThingy extends StatelessWidget {
final NerdStats nerdStats ;
2024-09-08 22:10:51 +00:00
final NerdStats ? oldNerdStats ;
2024-08-07 22:42:04 +00:00
2024-09-08 22:10:51 +00:00
const NerdStatsThingy ( { super . key , required this . nerdStats , this . oldNerdStats } ) ;
2024-08-07 22:42:04 +00:00
@ override
Widget build ( BuildContext context ) {
return Card (
child: Column (
children: [
2024-08-15 21:55:45 +00:00
Padding (
2024-09-08 22:10:51 +00:00
padding: const EdgeInsets . fromLTRB ( 12.0 , 8.0 , 12.0 , 8.0 ) ,
2024-08-15 21:55:45 +00:00
child: Row (
crossAxisAlignment: CrossAxisAlignment . center ,
mainAxisSize: MainAxisSize . min ,
children: [
SizedBox (
height: 256.0 ,
width: 256.0 ,
child: ClipRRect (
borderRadius: BorderRadius . circular ( 1000 ) ,
child: SfRadialGauge (
backgroundColor: Colors . black ,
axes: [
RadialAxis (
startAngle: 200 ,
endAngle: 340 ,
minimum: 0.0 ,
maximum: 1.0 ,
radiusFactor: 1.01 ,
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: const TextStyle ( fontFamily: " Eurostile Round " ) ,
children: [
const TextSpan ( text: " APP \n " ) ,
TextSpan ( text: f3 . format ( nerdStats . app ) , style: const TextStyle ( fontSize: 25 , fontFamily: " Eurostile Round Extended " , fontWeight: FontWeight . w100 ) ) ,
2024-09-08 22:10:51 +00:00
if ( oldNerdStats ! = null ) TextSpan ( text: " \n ${ comparef . format ( nerdStats . app - oldNerdStats ! . app ) } " , style: TextStyle ( color: getDifferenceColor ( nerdStats . app - oldNerdStats ! . app ) ) ) ,
2024-08-15 21:55:45 +00:00
]
) ) ) ,
angle: 270 , positionFactor: 0.5
) ,
] ,
) ,
RadialAxis (
startAngle: 20 ,
endAngle: 160 ,
isInversed: true ,
minimum: 1.8 ,
maximum: 2.4 ,
radiusFactor: 1.01 ,
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: const TextStyle ( fontFamily: " Eurostile Round " ) ,
children: [
const TextSpan ( text: " VS/APM \n " ) ,
TextSpan ( text: f3 . format ( nerdStats . vsapm ) , style: const TextStyle ( fontSize: 25 , fontFamily: " Eurostile Round Extended " , fontWeight: FontWeight . w100 ) ) ,
2024-09-08 22:10:51 +00:00
if ( oldNerdStats ! = null ) TextSpan ( text: " \n ${ comparef . format ( nerdStats . vsapm - oldNerdStats ! . vsapm ) } " , style: TextStyle ( color: getDifferenceColor ( nerdStats . vsapm - oldNerdStats ! . vsapm ) ) ) ,
2024-08-15 21:55:45 +00:00
]
) ) ) ,
angle: 90 , positionFactor: 0.5
)
] ,
)
]
) ,
2024-08-12 23:07:59 +00:00
) ,
2024-08-07 22:42:04 +00:00
) ,
2024-08-15 21:55:45 +00:00
Expanded (
child: Wrap (
alignment: WrapAlignment . center ,
2024-09-08 22:10:51 +00:00
spacing: 10.0 ,
runSpacing: 10.0 ,
runAlignment: WrapAlignment . start ,
2024-08-15 21:55:45 +00:00
children: [
2024-09-08 22:10:51 +00:00
GaugetThingy ( value: nerdStats . dss , oldValue: oldNerdStats ? . dss , min: 0 , max: 1.0 , tickInterval: . 2 , label: " DS/S " , sideSize: 128.0 , fractionDigits: 3 , moreIsBetter: true ) ,
GaugetThingy ( value: nerdStats . dsp , oldValue: oldNerdStats ? . dsp , min: 0 , max: 1.0 , tickInterval: . 2 , label: " DS/P " , sideSize: 128.0 , fractionDigits: 3 , moreIsBetter: true ) ,
GaugetThingy ( value: nerdStats . appdsp , oldValue: oldNerdStats ? . appdsp , min: 0 , max: 1.2 , tickInterval: . 2 , label: " APP+DS/P " , sideSize: 128.0 , fractionDigits: 3 , moreIsBetter: true ) ,
GaugetThingy ( value: nerdStats . cheese , oldValue: oldNerdStats ? . cheese , min: - 80 , max: 80 , tickInterval: 40 , label: " Cheese " , sideSize: 128.0 , fractionDigits: 2 , moreIsBetter: false ) ,
GaugetThingy ( value: nerdStats . gbe , oldValue: oldNerdStats ? . gbe , min: 0 , max: 1.0 , tickInterval: . 2 , label: " GbE " , sideSize: 128.0 , fractionDigits: 3 , moreIsBetter: true ) ,
GaugetThingy ( value: nerdStats . nyaapp , oldValue: oldNerdStats ? . nyaapp , min: 0 , max: 1.2 , tickInterval: . 2 , label: " wAPP " , sideSize: 128.0 , fractionDigits: 3 , moreIsBetter: true ) ,
GaugetThingy ( value: nerdStats . area , oldValue: oldNerdStats ? . area , min: 0 , max: 1000 , tickInterval: 100 , label: " Area " , sideSize: 128.0 , fractionDigits: 1 , moreIsBetter: true ) ,
2024-08-15 21:55:45 +00:00
] ,
) ,
)
]
) ,
2024-08-07 22:42:04 +00:00
) ,
] ,
)
) ;
2024-08-08 23:01:46 +00:00
}
}
class EstTrThingy extends StatelessWidget {
final EstTr estTr ;
const EstTrThingy ( { super . key , required this . estTr } ) ;
2024-08-07 22:42:04 +00:00
2024-08-08 23:01:46 +00:00
@ override
Widget build ( BuildContext context ) {
2024-08-09 22:52:50 +00:00
return const Card (
//child: ,
) ;
}
}
class GraphsThingy extends StatelessWidget {
final double apm ;
final double pps ;
final double vs ;
final NerdStats nerdStats ;
final Playstyle playstyle ;
const GraphsThingy ( { super . key , required this . nerdStats , required this . playstyle , required this . apm , required this . pps , required this . vs } ) ;
@ override
Widget build ( BuildContext context ) {
return Card (
child: Padding (
padding: const EdgeInsets . only ( top: 8.0 ) ,
child: Center ( child: Graphs ( apm , pps , vs , nerdStats , playstyle ) ) ,
) ,
) ;
2024-08-08 23:01:46 +00:00
}
2024-08-09 22:52:50 +00:00
2024-08-07 22:42:04 +00:00
}
class GaugetThingy extends StatelessWidget {
final double value ;
final double min ;
final double max ;
2024-09-08 22:10:51 +00:00
final double ? oldValue ;
final bool moreIsBetter ;
2024-08-08 23:01:46 +00:00
final double tickInterval ;
2024-08-07 22:42:04 +00:00
final String label ;
final double sideSize ;
final int fractionDigits ;
2024-09-08 22:10:51 +00:00
GaugetThingy ( { super . key , required this . value , required this . min , required this . max , this . oldValue , required this . tickInterval , required this . label , required this . sideSize , required this . fractionDigits , required this . moreIsBetter } ) ;
2024-08-07 22:42:04 +00:00
@ override
Widget build ( BuildContext context ) {
2024-08-08 23:01:46 +00:00
NumberFormat f = NumberFormat . decimalPatternDigits ( locale: LocaleSettings . currentLocale . languageCode , decimalDigits: fractionDigits ) ;
2024-08-12 23:07:59 +00:00
return ClipRRect (
borderRadius: BorderRadius . circular ( 1000 ) ,
child: SizedBox (
height: sideSize ,
width: sideSize ,
child: SfRadialGauge (
backgroundColor: Colors . black ,
axes: [
RadialAxis (
radiusFactor: 1.01 ,
minimum: min ,
maximum: max ,
showTicks: true ,
showLabels: false ,
interval: tickInterval ,
//labelsPosition: ElementsPosition.outside,
ranges: [
GaugeRange ( startValue: 0 , endValue: value , color: theme . colorScheme . primary )
] ,
annotations: [
GaugeAnnotation ( widget: Container ( child:
Text ( f . format ( value ) , textAlign: TextAlign . center , style: const TextStyle ( fontSize: 25 , fontWeight: FontWeight . bold ) ) ) ,
angle: 90 , positionFactor: 0.10
) ,
GaugeAnnotation ( widget: Container ( child:
Text ( label , textAlign: TextAlign . center , style: const TextStyle ( height: . 9 ) ) ) ,
angle: 270 , positionFactor: 0.4
2024-09-08 22:10:51 +00:00
) ,
if ( oldValue ! = null ) GaugeAnnotation ( widget: Container ( child:
Text ( comparef2 . format ( value - oldValue ! ) , textAlign: TextAlign . center , style: TextStyle ( color: getDifferenceColor ( moreIsBetter ? value - oldValue ! : oldValue ! - value ) ) ) ) ,
angle: 90 , positionFactor: 0.45
2024-08-12 23:07:59 +00:00
)
] ,
)
]
) ,
2024-08-04 22:23:08 +00:00
) ,
) ;
}
2024-08-09 22:52:50 +00:00
}
class ZenithThingy extends StatelessWidget {
final RecordSingle ? zenith ;
const ZenithThingy ( { super . key , required this . zenith } ) ;
@ override
Widget build ( BuildContext context ) {
return Card (
child: Padding (
padding: const EdgeInsets . fromLTRB ( 8.0 , 0.0 , 8.0 , 0.0 ) ,
child: Column (
children: [
Row (
mainAxisAlignment: MainAxisAlignment . center ,
children: [
Column (
mainAxisSize: MainAxisSize . min ,
children: [
RichText (
text: TextSpan (
text: zenith ! = null ? " ${ f2 . format ( zenith ! . stats . zenith ! . altitude ) } m " : " --- m " ,
style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 36 , fontWeight: FontWeight . w500 , color: zenith ! = null ? Colors . white : Colors . grey ) ,
) ,
) ,
if ( zenith ! = null ) RichText (
text: TextSpan (
text: " " ,
style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 14 , color: Colors . grey ) ,
children: [
2024-08-13 22:45:28 +00:00
if ( zenith ! . rank ! = - 1 ) TextSpan ( text: " № ${ intf . format ( zenith ! . rank ) } " , style: TextStyle ( color: getColorOfRank ( zenith ! . rank ) ) ) ,
2024-08-09 22:52:50 +00:00
if ( zenith ! . rank ! = - 1 ) const TextSpan ( text: " • " ) ,
2024-08-13 22:45:28 +00:00
if ( zenith ! . countryRank ! = - 1 ) TextSpan ( text: " № ${ intf . format ( zenith ! . countryRank ) } local " , style: TextStyle ( color: getColorOfRank ( zenith ! . countryRank ) ) ) ,
2024-08-09 22:52:50 +00:00
if ( zenith ! . countryRank ! = - 1 ) const TextSpan ( text: " • " ) ,
TextSpan ( text: timestamp ( zenith ! . timestamp ) ) ,
]
) ,
) ,
] ,
) ,
if ( zenith ! = null & & ( zenith ! . extras as ZenithExtras ) . mods . isNotEmpty ) Container ( width: 16.0 ) ,
if ( zenith ! = null & & ( zenith ! . extras as ZenithExtras ) . mods . isNotEmpty ) for ( String mod in ( zenith ! . extras as ZenithExtras ) . mods ) Image . asset ( " res/icons/ ${ mod } .png " , height: 64.0 )
] ,
) ,
if ( zenith ! = null ) Row (
children: [
Expanded (
child: Center (
child: Table (
defaultColumnWidth: const IntrinsicColumnWidth ( ) ,
children: [
TableRow ( children: [
const Text ( " APM: " , style: TextStyle ( fontSize: 21 ) ) ,
Text ( f2 . format ( zenith ! . aggregateStats . apm ) , textAlign: TextAlign . right , style: const TextStyle ( fontSize: 21 ) ) ,
] ) ,
TableRow ( children: [
const Text ( " PPS: " , style: TextStyle ( fontSize: 21 ) ) ,
Text ( f2 . format ( zenith ! . aggregateStats . pps ) , textAlign: TextAlign . right , style: const TextStyle ( fontSize: 21 ) ) ,
] ) ,
TableRow ( children: [
const Text ( " VS: " , style: TextStyle ( fontSize: 21 ) ) ,
Text ( f2 . format ( zenith ! . aggregateStats . vs ) , textAlign: TextAlign . right , style: const TextStyle ( fontSize: 21 ) ) ,
] )
] ,
) ,
) ,
) ,
Expanded (
child: Center (
child: Table (
defaultColumnWidth: const IntrinsicColumnWidth ( ) ,
children: [
TableRow ( children: [
2024-08-31 23:00:26 +00:00
Text ( intf . format ( zenith ! . stats . kills ) , textAlign: TextAlign . right , style: const TextStyle ( fontSize: 21 ) ) ,
2024-08-09 22:52:50 +00:00
const Text ( " KO's " , style: TextStyle ( fontSize: 21 ) )
] ) ,
TableRow ( children: [
Text ( f2 . format ( zenith ! . stats . cps ) , textAlign: TextAlign . right , style: const TextStyle ( fontSize: 21 ) ) ,
const Text ( " CPS " , style: TextStyle ( fontSize: 21 ) )
] ) ,
TableRow ( children: [
Text ( f2 . format ( zenith ! . stats . zenith ! . peakrank ) , textAlign: TextAlign . right , style: const TextStyle ( fontSize: 21 ) ) ,
const Text ( " Peak CPS " , style: TextStyle ( fontSize: 21 ) )
] )
] ,
) ,
) ,
) ,
] ,
)
]
) ,
)
) ;
}
2024-08-07 22:42:04 +00:00
2024-08-06 22:24:31 +00:00
}
2024-10-17 22:17:23 +00:00
class TLRecords extends StatelessWidget {
2024-08-06 22:24:31 +00:00
final String userID ;
final Function changePlayer ;
final List < BetaRecord > data ;
final bool wasActiveInTL ;
final bool oldMathcesHere ;
/// Widget, that displays Tetra League records.
/// Accepts list of TL records ([data]) and [userID] of player from the view
2024-10-17 22:17:23 +00:00
const TLRecords ( { required this . userID , required this . changePlayer , required this . data , required this . wasActiveInTL , required this . oldMathcesHere } ) ;
2024-08-06 22:24:31 +00:00
@ 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 ( ) ,
2024-10-16 23:10:08 +00:00
//controller: separateScrollController ? ScrollController() : null,
2024-08-06 22:24:31 +00:00
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-08-31 23:00:26 +00:00
}
class TLRatingThingy extends StatelessWidget {
final String userID ;
final TetraLeague tlData ;
final TetraLeague ? oldTl ;
final double ? topTR ;
2024-09-01 21:44:19 +00:00
final bool ? showPositions ;
2024-08-31 23:00:26 +00:00
final DateTime ? lastMatchPlayed ;
2024-09-01 21:44:19 +00:00
const TLRatingThingy ( { super . key , required this . userID , required this . tlData , this . oldTl , this . topTR , this . lastMatchPlayed , this . showPositions } ) ;
2024-08-31 23:00:26 +00:00
@ override
Widget build ( BuildContext context ) {
bool oskKagariGimmick = prefs . getBool ( " oskKagariGimmick " ) ? ? true ;
bool bigScreen = MediaQuery . of ( context ) . size . width > = 768 ;
String decimalSeparator = f4 . symbols . DECIMAL_SEP ;
List < String > formatedTR = f4 . format ( tlData . tr ) . split ( decimalSeparator ) ;
List < String > formatedGlicko = tlData . glicko ! = null ? f4 . format ( tlData . glicko ) . split ( decimalSeparator ) : [ " --- " , " -- " ] ;
List < String > formatedPercentile = f4 . format ( tlData . percentile * 100 ) . split ( decimalSeparator ) ;
//DateTime now = DateTime.now();
//bool beforeS1end = now.isBefore(seasonEnd);
//int daysLeft = seasonEnd.difference(now).inDays;
//int safeRD = min(100, (100 + ((tlData.rd! >= 100 && tlData.decaying) ? 7 : max(0, 7 - (lastMatchPlayed != null ? now.difference(lastMatchPlayed!).inDays : 7))) - daysLeft).toInt());
return Wrap (
direction: Axis . horizontal ,
alignment: WrapAlignment . spaceAround ,
crossAxisAlignment: WrapCrossAlignment . center ,
clipBehavior: Clip . hardEdge ,
children: [
( userID = = " 5e32fc85ab319c2ab1beb07c " & & oskKagariGimmick ) // he love her so much, you can't even imagine
? Image . asset ( " res/icons/kagari.png " , height: 128 ) // Btw why she wearing Kazamatsuri high school uniform?
: Image . asset ( " res/tetrio_tl_alpha_ranks/ ${ tlData . rank } .png " , height: 128 ) ,
Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
RichText (
text: TextSpan (
2024-09-09 22:38:52 +00:00
style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 20 , color: Colors . white , height: 0.9 ) ,
2024-08-31 23:00:26 +00:00
children: ( tlData . gamesPlayed > 9 ) ? switch ( prefs . getInt ( " ratingMode " ) ) {
1 = > [
TextSpan ( text: formatedGlicko [ 0 ] , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: bigScreen ? 42 : 28 ) ) ,
if ( formatedGlicko . elementAtOrNull ( 1 ) ! = null ) TextSpan ( text: decimalSeparator + formatedGlicko [ 1 ] ) ,
TextSpan ( text: " Glicko " , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: bigScreen ? 42 : 28 ) )
] ,
2 = > [
TextSpan ( text: " ${ t . top } ${ formatedPercentile [ 0 ] } " , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: bigScreen ? 42 : 28 ) ) ,
if ( formatedPercentile . elementAtOrNull ( 1 ) ! = null ) TextSpan ( text: decimalSeparator + formatedPercentile [ 1 ] ) ,
TextSpan ( text: " % " , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: bigScreen ? 42 : 28 ) )
] ,
_ = > [
TextSpan ( text: formatedTR [ 0 ] , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: bigScreen ? 42 : 28 ) ) ,
if ( formatedTR . elementAtOrNull ( 1 ) ! = null ) TextSpan ( text: decimalSeparator + formatedTR [ 1 ] ) ,
TextSpan ( text: " TR " , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: bigScreen ? 42 : 28 ) )
] ,
} : [ TextSpan ( text: " --- \n " , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: bigScreen ? 42 : 28 , color: Colors . grey ) ) , TextSpan ( text: t . gamesUntilRanked ( left: 10 - tlData . gamesPlayed ) , style: const TextStyle ( color: Colors . grey , fontSize: 14 ) ) , ]
)
) ,
2024-09-09 22:38:52 +00:00
if ( oldTl ! = null ) RichText (
2024-08-31 23:00:26 +00:00
textAlign: TextAlign . center ,
2024-09-09 22:38:52 +00:00
softWrap: true ,
text: TextSpan (
style: DefaultTextStyle . of ( context ) . style ,
children: [
TextSpan ( text: switch ( prefs . getInt ( " ratingMode " ) ) {
1 = > " ${ fDiff . format ( tlData . glicko ! - oldTl ! . glicko ! ) } Glicko " ,
2 = > " ${ fDiff . format ( tlData . percentile * 100 - oldTl ! . percentile * 100 ) } % " ,
_ = > " ${ fDiff . format ( tlData . tr - oldTl ! . tr ) } TR "
} ,
style: TextStyle (
color: getDifferenceColor ( switch ( prefs . getInt ( " ratingMode " ) ) {
1 = > tlData . glicko ! - oldTl ! . glicko ! ,
2 = > tlData . percentile - oldTl ! . percentile ,
_ = > tlData . tr - oldTl ! . tr
} )
) ,
) ,
const TextSpan ( text: " • " , style: TextStyle ( color: Colors . grey ) ) ,
TextSpan ( text: switch ( prefs . getInt ( " ratingMode " ) ) {
1 = > " ${ fDiff . format ( tlData . tr - oldTl ! . tr ) } TR " ,
_ = > " ${ fDiff . format ( tlData . glicko ! - oldTl ! . glicko ! ) } Glicko "
} ,
style: TextStyle (
color: getDifferenceColor ( switch ( prefs . getInt ( " ratingMode " ) ) {
1 = > tlData . tr - oldTl ! . tr ,
_ = > tlData . glicko ! - oldTl ! . glicko !
} )
) ,
) ,
const TextSpan ( text: " • " , style: TextStyle ( color: Colors . grey ) ) ,
TextSpan (
text: " ${ fDiff . format ( tlData . rd ! - oldTl ! . rd ! ) } RD " ,
style: TextStyle ( color: getDifferenceColor ( oldTl ! . rd ! - tlData . rd ! ) )
)
] ,
2024-08-31 23:00:26 +00:00
) ,
) ,
if ( tlData . gamesPlayed > 9 ) Column (
children: [
RichText (
textAlign: TextAlign . center ,
softWrap: true ,
text: TextSpan (
style: DefaultTextStyle . of ( context ) . style ,
children: [
TextSpan ( text: prefs . getInt ( " ratingMode " ) = = 2 ? " ${ f2 . format ( tlData . tr ) } TR • % ${ t . rank } : ${ tlData . percentileRank . toUpperCase ( ) } " : " ${ t . top } ${ f2 . format ( tlData . percentile * 100 ) } % ( ${ tlData . percentileRank . toUpperCase ( ) } ) " ) ,
if ( tlData . bestRank ! = " z " ) const TextSpan ( text: " • " ) ,
if ( tlData . bestRank ! = " z " ) TextSpan ( text: " ${ t . topRank } : ${ tlData . bestRank . toUpperCase ( ) } " ) ,
if ( topTR ! = null ) TextSpan ( text: " ( ${ f2 . format ( topTR ) } TR) " ) ,
TextSpan ( text: " • ${ prefs . getInt ( " ratingMode " ) = = 1 ? " $ {f2.format(tlData.tr) } TR • RD: " : " Glicko: ${ tlData . glicko ! = null ? f2 . format ( tlData . glicko ) : " --- " } ± " } " ),
TextSpan ( text: f2 . format ( tlData . rd ! ) , style: tlData . decaying ? TextStyle ( color: tlData . rd ! > 98 ? Colors . red : Colors . yellow ) : null ) ,
if ( tlData . decaying ) WidgetSpan ( child: Icon ( Icons . trending_up , color: tlData . rd ! > 98 ? Colors . red : Colors . yellow , ) , alignment: PlaceholderAlignment . middle , baseline: TextBaseline . alphabetic ) ,
//if (beforeS1end) tlData.rd! <= safeRD ? TextSpan(text: " (Safe)", style: TextStyle(color: Colors.greenAccent)) : TextSpan(text: " (> ${safeRD} RD !!!)", style: TextStyle(color: Colors.redAccent))
] ,
) ,
) ,
] ,
) ,
2024-09-01 21:44:19 +00:00
if ( showPositions = = true ) RichText (
2024-08-31 23:00:26 +00:00
textAlign: TextAlign . start ,
text: TextSpan (
text: " " ,
style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 14 , color: Colors . grey ) ,
children: [
if ( tlData . standing ! = - 1 ) TextSpan ( text: " № ${ intf . format ( tlData . standing ) } " , style: TextStyle ( color: getColorOfRank ( tlData . standing ) ) ) ,
if ( tlData . standing ! = - 1 | | tlData . standingLocal ! = - 1 ) const TextSpan ( text: " • " ) ,
if ( tlData . standingLocal ! = - 1 ) TextSpan ( text: " № ${ intf . format ( tlData . standingLocal ) } local " , style: TextStyle ( color: getColorOfRank ( tlData . standingLocal ) ) ) ,
if ( tlData . standing ! = - 1 & & tlData . standingLocal ! = - 1 ) const TextSpan ( text: " • " ) ,
TextSpan ( text: timestamp ( tlData . timestamp ) ) ,
]
) ,
) ,
] ,
) ,
] ,
) ;
}
2024-09-14 22:05:50 +00:00
}
class FutureError extends StatelessWidget {
final AsyncSnapshot snapshot ;
FutureError ( this . snapshot ) ;
@ override
Widget build ( BuildContext context ) {
2024-09-29 22:02:19 +00:00
return TweenAnimationBuilder (
duration: Durations . medium3 ,
tween: Tween < double > ( begin: 0 , end: 1 ) ,
curve: Easing . standard ,
builder: ( context , value , child ) {
return Container (
transform: Matrix4 . translationValues ( 0 , 50 - value * 50 , 0 ) ,
child: Opacity ( opacity: value , child: child ) ,
) ;
} ,
child: Column (
2024-09-14 22:05:50 +00:00
mainAxisSize: MainAxisSize . min ,
children: [
2024-09-29 22:02:19 +00:00
Spacer ( ) ,
Icon ( Icons . error_outline , size: 128.0 , color: Colors . red , shadows: [
Shadow ( offset: Offset ( 0.0 , 0.0 ) , blurRadius: 30.0 , color: Colors . red ) ,
Shadow ( offset: Offset ( 0.0 , 0.0 ) , blurRadius: 80.0 , color: Colors . red ) ,
] ) ,
2024-09-14 22:05:50 +00:00
Text ( snapshot . error . toString ( ) , style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 42 , fontWeight: FontWeight . bold ) , textAlign: TextAlign . center ) ,
Padding (
padding: const EdgeInsets . only ( top: 8.0 ) ,
2024-10-01 21:46:43 +00:00
child: Text ( snapshot . stackTrace . toString ( ) , textAlign: TextAlign . left , style: TextStyle ( fontFamily: " Monospace " ) ) ,
2024-09-14 22:05:50 +00:00
) ,
2024-09-29 22:02:19 +00:00
Spacer ( )
2024-09-14 22:05:50 +00:00
] ,
2024-09-29 22:02:19 +00:00
) ,
) ;
}
}
class FetchResultError extends StatelessWidget {
final FetchResults data ;
FetchResultError ( this . data ) ;
@ override
Widget build ( BuildContext context ) {
IconData icon = Icons . error_outline ;
String errText = " " ;
String ? subText ;
switch ( data . exception . runtimeType ) {
case TetrioPlayerNotExist:
icon = Icons . search_off ;
errText = t . errors . noSuchUser ;
subText = t . errors . noSuchUserSub ;
break ;
case TetrioDiscordNotExist:
icon = Icons . search_off ;
errText = t . errors . discordNotAssigned ;
subText = t . errors . discordNotAssignedSub ;
case ConnectionIssue:
var err = data . exception as ConnectionIssue ;
errText = t . errors . connection ( code: err . code , message: err . message ) ;
break ;
case TetrioForbidden:
icon = Icons . remove_circle ;
errText = t . errors . forbidden ;
subText = t . errors . forbiddenSub ( nickname: ' osk ' ) ;
break ;
case TetrioTooManyRequests:
errText = t . errors . tooManyRequests ;
subText = t . errors . tooManyRequestsSub ;
break ;
case TetrioOskwareBridgeProblem:
errText = t . errors . oskwareBridge ;
subText = t . errors . oskwareBridgeSub ;
break ;
case TetrioInternalProblem:
errText = kIsWeb ? t . errors . internalWebVersion : t . errors . internal ;
subText = kIsWeb ? t . errors . internalWebVersionSub : t . errors . internalSub ;
break ;
case ClientException:
errText = t . errors . clientException ;
break ;
default :
errText = data . exception . toString ( ) ;
}
return TweenAnimationBuilder (
duration: Durations . medium3 ,
tween: Tween < double > ( begin: 0 , end: 1 ) ,
curve: Easing . standard ,
builder: ( context , value , child ) {
return Container (
transform: Matrix4 . translationValues ( 0 , 50 - value * 50 , 0 ) ,
child: Opacity ( opacity: value , child: child ) ,
) ;
} ,
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
Spacer ( ) ,
Icon ( icon , size: 128.0 , color: Colors . red , shadows: [
Shadow ( offset: Offset ( 0.0 , 0.0 ) , blurRadius: 30.0 , color: Colors . red ) ,
Shadow ( offset: Offset ( 0.0 , 0.0 ) , blurRadius: 80.0 , color: Colors . red ) ,
] ) ,
Text ( errText , style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 42 , fontWeight: FontWeight . bold ) , textAlign: TextAlign . center ) ,
if ( subText ! = null ) Padding (
padding: const EdgeInsets . only ( top: 8.0 ) ,
child: Text ( subText , textAlign: TextAlign . center ) ,
) ,
Spacer ( )
] ,
) ,
2024-09-14 22:05:50 +00:00
) ;
}
2024-07-27 19:10:45 +00:00
}