2024-06-16 21:04:07 +00:00
// ignore_for_file: type_literal_in_constant_pattern, use_build_context_synchronously
2024-01-01 17:26:09 +00:00
2024-06-03 23:42:44 +00:00
import ' dart:async ' ;
2023-06-26 17:13:53 +00:00
import ' dart:io ' ;
2023-07-20 20:56:00 +00:00
import ' package:flutter/foundation.dart ' ;
2024-01-13 18:49:36 +00:00
import ' package:flutter/gestures.dart ' ;
2023-06-17 21:50:52 +00:00
import ' package:flutter/material.dart ' ;
2023-08-21 15:39:04 +00:00
import ' package:flutter_svg/flutter_svg.dart ' ;
2023-09-23 19:09:36 +00:00
import ' package:http/http.dart ' ;
2023-06-17 21:50:52 +00:00
import ' package:intl/intl.dart ' ;
import ' package:shared_preferences/shared_preferences.dart ' ;
import ' package:flutter/services.dart ' ;
2024-04-30 23:03:42 +00:00
import ' package:syncfusion_flutter_charts/charts.dart ' ;
2024-08-17 23:39:20 +00:00
import ' package:tetra_stats/data_objects/tetra_stats.dart ' ;
2023-06-17 21:50:52 +00:00
import ' package:tetra_stats/data_objects/tetrio.dart ' ;
2023-07-10 17:42:20 +00:00
import ' package:tetra_stats/gen/strings.g.dart ' ;
2024-06-03 23:42:44 +00:00
import ' package:tetra_stats/main.dart ' show prefs , teto ;
2023-06-17 21:50:52 +00:00
import ' package:tetra_stats/services/crud_exceptions.dart ' ;
2024-03-14 22:53:19 +00:00
import ' package:tetra_stats/utils/numers_formats.dart ' ;
2024-06-15 22:49:57 +00:00
import ' package:tetra_stats/utils/open_in_browser.dart ' ;
2024-06-14 20:47:36 +00:00
import ' package:tetra_stats/utils/relative_timestamps.dart ' ;
2024-02-08 21:39:54 +00:00
import ' package:tetra_stats/utils/text_shadow.dart ' ;
2024-06-14 20:47:36 +00:00
import ' package:tetra_stats/views/singleplayer_record_view.dart ' ;
2023-06-21 19:17:39 +00:00
import ' package:tetra_stats/views/tl_match_view.dart ' show TlMatchResultView ;
2024-07-31 21:50:15 +00:00
import ' package:tetra_stats/views/zenith_record_view.dart ' ;
2024-03-18 22:39:41 +00:00
import ' package:tetra_stats/widgets/finesse_thingy.dart ' ;
import ' package:tetra_stats/widgets/lineclears_thingy.dart ' ;
2024-03-02 22:26:31 +00:00
import ' package:tetra_stats/widgets/list_tile_trailing_stats.dart ' ;
2024-06-14 20:47:36 +00:00
import ' package:tetra_stats/widgets/recent_sp_games.dart ' ;
2024-02-08 21:39:54 +00:00
import ' package:tetra_stats/widgets/search_box.dart ' ;
2024-06-14 20:47:36 +00:00
import ' package:tetra_stats/widgets/singleplayer_record.dart ' ;
2024-06-13 21:12:48 +00:00
import ' package:tetra_stats/widgets/sp_trailing_stats.dart ' ;
2023-06-17 21:50:52 +00:00
import ' package:tetra_stats/widgets/stat_sell_num.dart ' ;
2024-06-11 16:30:13 +00:00
import ' package:tetra_stats/widgets/text_timestamp.dart ' ;
2023-06-17 21:50:52 +00:00
import ' package:tetra_stats/widgets/tl_thingy.dart ' ;
import ' package:tetra_stats/widgets/user_thingy.dart ' ;
2024-07-31 21:50:15 +00:00
import ' package:tetra_stats/widgets/zenith_thingy.dart ' ;
2023-10-08 17:20:42 +00:00
import ' package:window_manager/window_manager.dart ' ;
import ' package:flutter_markdown/flutter_markdown.dart ' ;
2023-12-26 22:02:47 +00:00
import ' package:go_router/go_router.dart ' ;
2023-06-17 21:50:52 +00:00
2024-01-13 18:49:36 +00:00
int _chartsIndex = 0 ;
2024-09-03 21:07:27 +00:00
int _season = currentSeason - 1 ;
2024-03-24 16:38:06 +00:00
bool _gamesPlayedInsteadOfDateAndTime = false ;
2024-04-30 23:03:42 +00:00
late ZoomPanBehavior _zoomPanBehavior ;
2024-04-20 22:37:31 +00:00
bool _smooth = false ;
2024-01-26 20:56:24 +00:00
List _historyShortTitles = [ " TR " , " Glicko " , " RD " , " APM " , " PPS " , " VS " , " APP " , " DS/S " , " DS/P " , " APP + DS/P " , " VS/APM " , " Cheese " , " GbE " , " wAPP " , " Area " , " eTR " , " ±eTR " , " Opener " , " Plonk " , " Inf. DS " , " Stride " ] ;
2024-01-13 18:49:36 +00:00
late ScrollController _scrollController ;
2023-06-17 21:50:52 +00:00
class MainView extends StatefulWidget {
2023-07-09 16:50:17 +00:00
final String ? player ;
2024-01-26 20:56:24 +00:00
/// 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.
2024-01-13 18:49:36 +00:00
const MainView ( { super . key , this . player } ) ;
2023-06-17 21:50:52 +00:00
@ override
State < MainView > createState ( ) = > _MainState ( ) ;
}
Future < void > copyToClipboard ( String text ) async {
await Clipboard . setData ( ClipboardData ( text: text ) ) ;
}
2024-01-13 18:49:36 +00:00
class _MainState extends State < MainView > with TickerProviderStateMixin {
2024-03-14 22:53:19 +00:00
Future < List > me = Future . delayed ( const Duration ( seconds: 60 ) , ( ) = > [ null , null , null , null , null , null ] ) ; // I love lists shut up
TetrioPlayersLeaderboard ? everyone ;
PlayerLeaderboardPosition ? meAmongEveryone ;
2024-08-16 22:40:09 +00:00
TetraLeague ? rankAverages ;
2024-04-20 22:37:31 +00:00
double ? thatRankCutoff ;
double ? nextRankCutoff ;
2024-05-01 23:12:52 +00:00
double ? thatRankGlickoCutoff ;
double ? nextRankGlickoCutoff ;
2024-03-14 22:53:19 +00:00
String _searchFor = " 6098518e3d5155e6ec429cdc " ; // who we looking for
2024-04-20 22:37:31 +00:00
String _titleNickname = " " ;
2024-03-14 22:53:19 +00:00
/// Each dropdown menu item contains list of dots for the graph
2024-09-03 21:07:27 +00:00
/// chartsData[season-1][chart]
List < List < DropdownMenuItem < List < _HistoryChartSpot > > > > chartsData = [ ] ;
2024-03-20 22:56:13 +00:00
//var tableData = <TableRow>[];
2023-06-17 21:50:52 +00:00
final bodyGlobalKey = GlobalKey ( ) ;
2024-01-26 20:56:24 +00:00
bool _showSearchBar = false ;
2024-06-16 21:04:07 +00:00
Timer backgroundUpdate = Timer ( const Duration ( days: 365 ) , ( ) { } ) ;
2024-03-20 22:56:13 +00:00
bool _TLHistoryWasFetched = false ;
2023-06-17 21:50:52 +00:00
late TabController _tabController ;
2024-03-13 22:44:53 +00:00
late TabController _wideScreenTabController ;
2024-07-31 21:50:15 +00:00
bool zenithEX = false ;
2023-06-17 21:50:52 +00:00
2024-03-14 22:53:19 +00:00
String get title = > " Tetra Stats: $ _titleNickname " ;
2023-06-17 21:50:52 +00:00
@ override
void initState ( ) {
2023-07-22 12:07:57 +00:00
initDB ( ) ;
2023-06-17 21:50:52 +00:00
_scrollController = ScrollController ( ) ;
2024-07-31 21:50:15 +00:00
_tabController = TabController ( length: 9 , vsync: this ) ;
_wideScreenTabController = TabController ( length: 5 , vsync: this ) ;
2024-04-30 23:03:42 +00:00
_zoomPanBehavior = ZoomPanBehavior (
enablePinching: true ,
enableSelectionZooming: true ,
enableMouseWheelZooming : true ,
enablePanning: true ,
) ;
2024-01-26 20:56:24 +00:00
// We need to show something
if ( widget . player ! = null ) { // if we have user input,
changePlayer ( widget . player ! ) ; // it's gonna be user input
2023-07-09 16:50:17 +00:00
} else {
2024-01-26 20:56:24 +00:00
_getPreferences ( ) // otherwise, checking for preferences
2024-04-20 22:37:31 +00:00
. then ( ( value ) = > changePlayer ( prefs . getString ( " player " ) ? ? " 6098518e3d5155e6ec429cdc " ) ) ; // no preferences - loading me
2023-07-09 16:50:17 +00:00
}
2023-06-17 21:50:52 +00:00
super . initState ( ) ;
}
@ override
void dispose ( ) {
_tabController . dispose ( ) ;
_scrollController . dispose ( ) ;
super . dispose ( ) ;
}
Future < void > _getPreferences ( ) async {
prefs = await SharedPreferences . getInstance ( ) ;
}
2024-01-26 20:56:24 +00:00
/// That function initiate search of data about [player]. If [fetchHistory] is true,
/// also attempting to retrieve players history. Can trow an Exception if fails
2024-02-03 13:02:58 +00:00
void changePlayer ( String player , { bool fetchHistory = false , bool fetchTLmatches = false } ) {
2023-06-17 21:50:52 +00:00
setState ( ( ) {
_searchFor = player ;
2024-02-03 13:02:58 +00:00
me = fetch ( _searchFor , fetchHistory: fetchHistory , fetchTLmatches: fetchTLmatches ) ;
2023-06-17 21:50:52 +00:00
} ) ;
}
2023-07-22 12:07:57 +00:00
void initDB ( ) async {
await teto . open ( ) ;
}
2024-01-26 20:56:24 +00:00
/// Retrieves data from 3 different Tetra Channel API endpoints + 1 endpoint from p1nkl0bst3r's API
2024-02-03 13:02:58 +00:00
/// using [nickOrID] of player.
2024-01-26 20:56:24 +00:00
///
2024-02-03 13:02:58 +00:00
/// If [fetchHistory] is true, also retrieves players history from p1nkl0bst3r's API. If [fetchTLmatches] is true, also retrieves players old Tetra League
/// matches from p1nkl0bst3r's API. Returns list which contains [TetrioPlayer], his records, previous states, TL matches, previous TL state,
/// if player tracked (bool), news entries and topTR.
2024-01-26 20:56:24 +00:00
///
2024-02-03 13:02:58 +00:00
/// If at least one request to Tetra Channel API fails, whole function will throw an exception.
Future < List > fetch ( String nickOrID , { bool fetchHistory = false , bool fetchTLmatches = false } ) async {
2023-07-29 18:01:49 +00:00
TetrioPlayer me ;
2024-03-20 22:56:13 +00:00
_TLHistoryWasFetched = false ;
2024-06-03 23:42:44 +00:00
backgroundUpdate . cancel ( ) ;
2024-01-26 20:56:24 +00:00
// If user trying to search with discord id
2023-07-29 18:01:49 +00:00
if ( nickOrID . startsWith ( " ds: " ) ) {
2024-01-26 20:56:24 +00:00
me = await teto . fetchPlayer ( nickOrID . substring ( 3 ) , isItDiscordID: true ) ; // we trying to get him with that
2023-07-29 18:01:49 +00:00
} else {
2024-01-26 20:56:24 +00:00
me = await teto . fetchPlayer ( nickOrID ) ; // Otherwise it's probably a user id or username
2023-07-29 18:01:49 +00:00
}
2024-01-26 20:56:24 +00:00
_searchFor = me . userId ; // gonna use user id for next requests
// Change view title and window title if avaliable
setState ( ( ) { _titleNickname = me . username ; } ) ;
2024-03-14 22:53:19 +00:00
if ( ! kIsWeb & & ! Platform . isAndroid & & ! Platform . isIOS ) await windowManager . setTitle ( title ) ;
2024-01-26 20:56:24 +00:00
// Requesting Tetra League (alpha), records, news and top TR of player
2024-08-17 23:39:20 +00:00
List < dynamic > requests ;
Summaries summaries = await teto . fetchSummaries ( _searchFor ) ;
2024-07-27 19:10:45 +00:00
late TetraLeagueBetaStream tlStream ;
2024-06-03 21:06:00 +00:00
late News news ;
2024-07-31 21:50:15 +00:00
// late SingleplayerStream recentSprint;
// late SingleplayerStream recentBlitz;
// late SingleplayerStream sprint;
// late SingleplayerStream blitz;
late SingleplayerStream recentZenith ;
late SingleplayerStream recentZenithEX ;
2024-08-17 23:39:20 +00:00
late TetrioPlayerFromLeaderboard ? topOne ;
2024-07-31 21:50:15 +00:00
// late TopTr? topTR;
2024-08-17 23:39:20 +00:00
requests = await Future . wait ( [
2024-07-27 19:10:45 +00:00
teto . fetchSummaries ( _searchFor ) ,
2024-02-01 00:15:32 +00:00
teto . fetchTLStream ( _searchFor ) ,
2024-01-26 20:56:24 +00:00
teto . fetchNews ( _searchFor ) ,
2024-07-31 21:50:15 +00:00
teto . fetchStream ( _searchFor , " zenith/recent " ) ,
teto . fetchStream ( _searchFor , " zenithex/recent " ) ,
2024-08-24 14:41:07 +00:00
teto . fetchCutoffsBeanserver ( ) ,
2024-08-17 23:39:20 +00:00
( summaries . league . rank ! = " z " ? summaries . league . rank = = " x+ " : summaries . league . percentileRank = = " x+ " ) ? teto . fetchTopOneFromTheLeaderboard ( ) : Future . delayed ( Duration . zero , ( ) = > null ) ,
2024-01-26 20:56:24 +00:00
] ) ;
2024-08-24 14:41:07 +00:00
//prefs.getBool("showPositions") != true ? teto.fetchCutoffsBeanserver() : Future.delayed(Duration.zero, ()=><Map<String, double>>[]),
2024-08-17 23:39:20 +00:00
//(summaries.league.gamesPlayed > 9) ? teto.fetchTopTR(_searchFor) : Future.delayed(Duration.zero, () => null) // can retrieve this only if player has TR
2024-07-27 19:10:45 +00:00
summaries = requests [ 0 ] as Summaries ;
tlStream = requests [ 1 ] as TetraLeagueBetaStream ;
// records = requests[1] as UserRecords;
2024-06-03 21:06:00 +00:00
news = requests [ 2 ] as News ;
2024-07-31 21:50:15 +00:00
recentZenith = requests [ 3 ] as SingleplayerStream ;
recentZenithEX = requests [ 4 ] as SingleplayerStream ;
2024-07-27 19:10:45 +00:00
// recent = requests[3] as SingleplayerStream;
// sprint = requests[4] as SingleplayerStream;
// blitz = requests[5] as SingleplayerStream;
2024-08-17 23:39:20 +00:00
topOne = requests [ 6 ] as TetrioPlayerFromLeaderboard ? ;
2024-07-27 19:10:45 +00:00
// topTR = requests[8] as TopTr?; // No TR - no Top TR
2024-01-26 20:56:24 +00:00
2024-03-06 22:34:15 +00:00
meAmongEveryone = teto . getCachedLeaderboardPositions ( me . userId ) ;
2024-04-29 22:45:11 +00:00
if ( prefs . getBool ( " showPositions " ) = = true ) {
2024-03-06 22:34:15 +00:00
// Get tetra League leaderboard
everyone = teto . getCachedLeaderboard ( ) ;
everyone ? ? = await teto . fetchTLLeaderboard ( ) ;
2024-07-31 21:50:15 +00:00
if ( meAmongEveryone = = null & & everyone ! . leaderboard . isNotEmpty ) {
2024-08-18 22:02:04 +00:00
meAmongEveryone = await compute ( everyone ! . getLeaderboardPosition , { me . userId: summaries . league } ) ;
2024-04-29 22:45:11 +00:00
if ( meAmongEveryone ! = null ) teto . cacheLeaderboardPositions ( me . userId , meAmongEveryone ! ) ;
}
2024-05-02 22:26:12 +00:00
}
2024-08-17 23:39:20 +00:00
Map < String , double > ? cutoffs = ( requests [ 5 ] as Cutoffs ? ) ? . tr ;
Map < String , double > ? cutoffsGlicko = ( requests [ 5 ] as Cutoffs ? ) ? . glicko ;
2024-05-04 20:04:48 +00:00
2024-08-17 23:39:20 +00:00
if ( summaries . league . gamesPlayed > 9 ) {
thatRankCutoff = cutoffs ? [ summaries . league . rank ! = " z " ? summaries . league . rank : summaries . league . percentileRank ] ;
thatRankGlickoCutoff = cutoffsGlicko ? [ summaries . league . rank ! = " z " ? summaries . league . rank : summaries . league . percentileRank ] ;
nextRankCutoff = ( summaries . league . rank ! = " z " ? summaries . league . rank = = " x+ " : summaries . league . percentileRank = = " x+ " ) ? topOne ? . tr ? ? 25000 : cutoffs ? [ ranks . elementAtOrNull ( ranks . indexOf ( summaries . league . rank ! = " z " ? summaries . league . rank : summaries . league . percentileRank ) + 1 ) ] ;
nextRankGlickoCutoff = ( summaries . league . rank ! = " z " ? summaries . league . rank = = " x+ " : summaries . league . percentileRank = = " x+ " ) ? topOne ? . glicko ? ? double . infinity : cutoffsGlicko ? [ ranks . elementAtOrNull ( ranks . indexOf ( summaries . league . rank ! = " z " ? summaries . league . rank : summaries . league . percentileRank ) + 1 ) ] ;
}
2024-03-04 22:05:59 +00:00
2024-08-19 17:59:25 +00:00
if ( everyone ! = null & & summaries . league . gamesPlayed > 9 ) rankAverages = everyone ? . averages [ summaries . league . percentileRank ] ? [ 0 ] ;
2024-03-10 22:34:30 +00:00
2024-01-26 20:56:24 +00:00
// Making list of Tetra League matches
2024-09-01 21:44:19 +00:00
bool isTracking = await teto . isPlayerTracking ( me . userId ) ;
2024-09-03 21:07:27 +00:00
List < List < TetraLeague > > states = await Future . wait < List < TetraLeague > > ( [
teto . getStates ( me . userId , season: 1 ) , teto . getStates ( me . userId , season: 2 ) ,
] ) ;
2024-02-03 13:02:58 +00:00
List < TetraLeagueAlphaRecord > storedRecords = await teto . getTLMatchesbyPlayerID ( me . userId ) ; // get old matches
2024-09-01 21:44:19 +00:00
if ( isTracking ) { // if tracked - save data to local DB
await teto . storeState ( summaries . league ) ;
//await teto.saveTLMatchesFromStream(tlStream);
}
2024-07-27 19:10:45 +00:00
TetraLeagueAlphaStream ? oldMatches ;
2024-02-01 14:38:11 +00:00
// building list of TL matches
2024-02-03 13:02:58 +00:00
if ( fetchTLmatches ) {
try {
2024-07-27 19:10:45 +00:00
oldMatches = await teto . fetchAndSaveOldTLmatches ( _searchFor ) ;
if ( context . mounted ) ScaffoldMessenger . of ( context ) . showSnackBar ( SnackBar ( content: Text ( t . fetchAndSaveOldTLmatchesResult ( number: oldMatches . records . length ) ) ) ) ;
2024-02-03 13:02:58 +00:00
} on TetrioHistoryNotExist {
if ( context . mounted ) ScaffoldMessenger . of ( context ) . showSnackBar ( SnackBar ( content: Text ( t . errors . p1nkl0bst3rTLmatches ) ) ) ;
} on P1nkl0bst3rForbidden {
if ( context . mounted ) ScaffoldMessenger . of ( context ) . showSnackBar ( SnackBar ( content: Text ( t . errors . p1nkl0bst3rForbidden ) ) ) ;
} on P1nkl0bst3rInternalProblem {
if ( context . mounted ) ScaffoldMessenger . of ( context ) . showSnackBar ( SnackBar ( content: Text ( t . errors . p1nkl0bst3rinternal ) ) ) ;
} on P1nkl0bst3rTooManyRequests {
if ( context . mounted ) ScaffoldMessenger . of ( context ) . showSnackBar ( SnackBar ( content: Text ( t . errors . p1nkl0bst3rTooManyRequests ) ) ) ;
2024-03-20 22:56:13 +00:00
} finally {
_TLHistoryWasFetched = true ;
2024-02-03 13:02:58 +00:00
}
2024-03-20 22:56:13 +00:00
}
2024-07-28 17:38:14 +00:00
if ( storedRecords . isNotEmpty ) {
_TLHistoryWasFetched = true ;
tlStream . addFromAlphaStream ( storedRecords ) ;
}
2024-07-27 19:10:45 +00:00
// tlMatches.sort((a, b) { // Newest matches gonna be shown at the top of the list
// if(a.ts.isBefore(b.ts)) return 1;
// if(a.ts.isAtSameMomentAs(b.ts)) return 0;
// if(a.ts.isAfter(b.ts)) return -1;
// return 0;
// });
2024-01-26 20:56:24 +00:00
// Handling history
2024-02-03 13:02:58 +00:00
if ( fetchHistory ) {
try {
var history = await teto . fetchAndsaveTLHistory ( _searchFor ) ; // Retrieve if needed
if ( context . mounted ) ScaffoldMessenger . of ( context ) . showSnackBar ( SnackBar ( content: Text ( t . fetchAndsaveTLHistoryResult ( number: history . length ) ) ) ) ;
} on TetrioHistoryNotExist {
if ( context . mounted ) ScaffoldMessenger . of ( context ) . showSnackBar ( SnackBar ( content: Text ( t . noHistorySaved ) ) ) ;
} on P1nkl0bst3rForbidden {
if ( context . mounted ) ScaffoldMessenger . of ( context ) . showSnackBar ( SnackBar ( content: Text ( t . errors . p1nkl0bst3rForbidden ) ) ) ;
} on P1nkl0bst3rInternalProblem {
if ( context . mounted ) ScaffoldMessenger . of ( context ) . showSnackBar ( SnackBar ( content: Text ( t . errors . p1nkl0bst3rinternal ) ) ) ;
} on P1nkl0bst3rTooManyRequests {
if ( context . mounted ) ScaffoldMessenger . of ( context ) . showSnackBar ( SnackBar ( content: Text ( t . errors . p1nkl0bst3rTooManyRequests ) ) ) ;
}
}
2024-09-01 21:44:19 +00:00
//states.addAll(await teto.getPlayer(me.userId));
// for (var element in states) { // For graphs I need only unique entries
// if (element.tlSeason1 != null && uniqueTL.isNotEmpty && uniqueTL.last != element.tlSeason1) uniqueTL.add(element.tlSeason1!);
// if (uniqueTL.isEmpty) uniqueTL.add(summaries.league);
// }
2024-01-29 21:13:07 +00:00
// Also i need previous Tetra League State for comparison if avaliable
2024-09-03 21:07:27 +00:00
TetraLeague ? compareWith ;
if ( states [ 1 ] . length > = 2 | | states [ 0 ] . length > = 2 ) {
compareWith = states [ 1 ] . length > = 2 ? states [ 1 ] . elementAtOrNull ( states . length - 2 ) : null ;
chartsData = [ for ( List < TetraLeague > s in states ) < DropdownMenuItem < List < _HistoryChartSpot > > > [ // Dumping charts data into dropdown menu items, while cheking if every entry is valid
DropdownMenuItem ( value: [ for ( var tl in s ) if ( tl . gamesPlayed > 9 ) _HistoryChartSpot ( tl . timestamp , tl . gamesPlayed , tl . rank , tl . tr ) ] , child: Text ( t . statCellNum . tr ) ) ,
DropdownMenuItem ( value: [ for ( var tl in s ) if ( tl . gamesPlayed > 9 ) _HistoryChartSpot ( tl . timestamp , tl . gamesPlayed , tl . rank , tl . glicko ! ) ] , child: const Text ( " Glicko " ) ) ,
DropdownMenuItem ( value: [ for ( var tl in s ) if ( tl . gamesPlayed > 9 ) _HistoryChartSpot ( tl . timestamp , tl . gamesPlayed , tl . rank , tl . rd ! ) ] , child: const Text ( " Rating Deviation " ) ) ,
DropdownMenuItem ( value: [ for ( var tl in s ) if ( tl . apm ! = null ) _HistoryChartSpot ( tl . timestamp , tl . gamesPlayed , tl . rank , tl . apm ! ) ] , child: Text ( t . statCellNum . apm . replaceAll ( RegExp ( r'\n' ) , " " ) ) ) ,
DropdownMenuItem ( value: [ for ( var tl in s ) if ( tl . pps ! = null ) _HistoryChartSpot ( tl . timestamp , tl . gamesPlayed , tl . rank , tl . pps ! ) ] , child: Text ( t . statCellNum . pps . replaceAll ( RegExp ( r'\n' ) , " " ) ) ) ,
DropdownMenuItem ( value: [ for ( var tl in s ) if ( tl . vs ! = null ) _HistoryChartSpot ( tl . timestamp , tl . gamesPlayed , tl . rank , tl . vs ! ) ] , child: Text ( t . statCellNum . vs . replaceAll ( RegExp ( r'\n' ) , " " ) ) ) ,
DropdownMenuItem ( value: [ for ( var tl in s ) if ( tl . nerdStats ! = null ) _HistoryChartSpot ( tl . timestamp , tl . gamesPlayed , tl . rank , tl . nerdStats ! . app ) ] , child: Text ( t . statCellNum . app . replaceAll ( RegExp ( r'\n' ) , " " ) ) ) ,
DropdownMenuItem ( value: [ for ( var tl in s ) if ( tl . nerdStats ! = null ) _HistoryChartSpot ( tl . timestamp , tl . gamesPlayed , tl . rank , tl . nerdStats ! . dss ) ] , child: Text ( t . statCellNum . dss . replaceAll ( RegExp ( r'\n' ) , " " ) ) ) ,
DropdownMenuItem ( value: [ for ( var tl in s ) if ( tl . nerdStats ! = null ) _HistoryChartSpot ( tl . timestamp , tl . gamesPlayed , tl . rank , tl . nerdStats ! . dsp ) ] , child: Text ( t . statCellNum . dsp . replaceAll ( RegExp ( r'\n' ) , " " ) ) ) ,
DropdownMenuItem ( value: [ for ( var tl in s ) if ( tl . nerdStats ! = null ) _HistoryChartSpot ( tl . timestamp , tl . gamesPlayed , tl . rank , tl . nerdStats ! . appdsp ) ] , child: const Text ( " APP + DS/P " ) ) ,
DropdownMenuItem ( value: [ for ( var tl in s ) if ( tl . nerdStats ! = null ) _HistoryChartSpot ( tl . timestamp , tl . gamesPlayed , tl . rank , tl . nerdStats ! . vsapm ) ] , child: const Text ( " VS/APM " ) ) ,
DropdownMenuItem ( value: [ for ( var tl in s ) if ( tl . nerdStats ! = null ) _HistoryChartSpot ( tl . timestamp , tl . gamesPlayed , tl . rank , tl . nerdStats ! . cheese ) ] , child: Text ( t . statCellNum . cheese . replaceAll ( RegExp ( r'\n' ) , " " ) ) ) ,
DropdownMenuItem ( value: [ for ( var tl in s ) if ( tl . nerdStats ! = null ) _HistoryChartSpot ( tl . timestamp , tl . gamesPlayed , tl . rank , tl . nerdStats ! . gbe ) ] , child: Text ( t . statCellNum . gbe . replaceAll ( RegExp ( r'\n' ) , " " ) ) ) ,
DropdownMenuItem ( value: [ for ( var tl in s ) if ( tl . nerdStats ! = null ) _HistoryChartSpot ( tl . timestamp , tl . gamesPlayed , tl . rank , tl . nerdStats ! . nyaapp ) ] , child: Text ( t . statCellNum . nyaapp . replaceAll ( RegExp ( r'\n' ) , " " ) ) ) ,
DropdownMenuItem ( value: [ for ( var tl in s ) if ( tl . nerdStats ! = null ) _HistoryChartSpot ( tl . timestamp , tl . gamesPlayed , tl . rank , tl . nerdStats ! . area ) ] , child: Text ( t . statCellNum . area . replaceAll ( RegExp ( r'\n' ) , " " ) ) ) ,
DropdownMenuItem ( value: [ for ( var tl in s ) if ( tl . estTr ! = null ) _HistoryChartSpot ( tl . timestamp , tl . gamesPlayed , tl . rank , tl . estTr ! . esttr ) ] , child: Text ( t . statCellNum . estOfTR . replaceAll ( RegExp ( r'\n' ) , " " ) ) ) ,
DropdownMenuItem ( value: [ for ( var tl in s ) if ( tl . esttracc ! = null ) _HistoryChartSpot ( tl . timestamp , tl . gamesPlayed , tl . rank , tl . esttracc ! ) ] , child: Text ( t . statCellNum . accOfEst . replaceAll ( RegExp ( r'\n' ) , " " ) ) ) ,
DropdownMenuItem ( value: [ for ( var tl in s ) if ( tl . playstyle ! = null ) _HistoryChartSpot ( tl . timestamp , tl . gamesPlayed , tl . rank , tl . playstyle ! . opener ) ] , child: const Text ( " Opener " ) ) ,
DropdownMenuItem ( value: [ for ( var tl in s ) if ( tl . playstyle ! = null ) _HistoryChartSpot ( tl . timestamp , tl . gamesPlayed , tl . rank , tl . playstyle ! . plonk ) ] , child: const Text ( " Plonk " ) ) ,
DropdownMenuItem ( value: [ for ( var tl in s ) if ( tl . playstyle ! = null ) _HistoryChartSpot ( tl . timestamp , tl . gamesPlayed , tl . rank , tl . playstyle ! . infds ) ] , child: const Text ( " Inf. DS " ) ) ,
DropdownMenuItem ( value: [ for ( var tl in s ) if ( tl . playstyle ! = null ) _HistoryChartSpot ( tl . timestamp , tl . gamesPlayed , tl . rank , tl . playstyle ! . stride ) ] , child: const Text ( " Stride " ) ) ,
] ] ;
2024-03-14 22:53:19 +00:00
} else {
compareWith = null ;
chartsData = [ ] ;
}
2024-06-03 23:42:44 +00:00
2024-06-04 22:14:34 +00:00
if ( prefs . getBool ( " updateInBG " ) = = true ) {
backgroundUpdate = Timer ( me . cachedUntil ! . difference ( DateTime . now ( ) ) , ( ) {
changePlayer ( me . userId ) ;
} ) ;
}
2024-09-03 21:07:27 +00:00
return [ me , summaries , news , tlStream , recentZenith , recentZenithEX , states [ currentSeason - 1 ] ] ;
2024-07-27 19:10:45 +00:00
//return [me, records, states, tlMatches, compareWith, isTracking, news, topTR, recent, sprint, blitz, tlMatches.elementAtOrNull(0)?.timestamp];
2023-06-23 18:38:15 +00:00
}
2024-01-26 20:56:24 +00:00
/// Triggers widgets rebuild
2023-06-17 21:50:52 +00:00
void _justUpdate ( ) {
setState ( ( ) { } ) ;
}
2024-07-31 21:50:15 +00:00
void toggleZenith ( ) {
setState ( ( ) { zenithEX = ! zenithEX ; } ) ;
}
2023-06-17 21:50:52 +00:00
@ override
Widget build ( BuildContext context ) {
2023-07-10 17:42:20 +00:00
final t = Translations . of ( context ) ;
2024-03-13 22:44:53 +00:00
bool bigScreen = MediaQuery . of ( context ) . size . width > 1024 ;
2023-06-17 21:50:52 +00:00
return Scaffold (
2024-01-26 20:56:24 +00:00
drawer: widget . player = = null ? NavDrawer ( changePlayer ) : null , // Side menu hidden if player provided
drawerEdgeDragWidth: MediaQuery . of ( context ) . size . width * 0.2 , // 20% of left side of the screen used of Drawer gesture
2023-06-17 21:50:52 +00:00
appBar: AppBar (
2024-03-14 22:53:19 +00:00
title: _showSearchBar ? SearchBox ( onSubmit: changePlayer , bigScreen: MediaQuery . of ( context ) . size . width > 768 ) : Text ( title , style: const TextStyle ( shadows: textShadow ) ) ,
2023-06-17 21:50:52 +00:00
backgroundColor: Colors . black ,
2024-01-26 20:56:24 +00:00
actions: widget . player = = null ? [ // search bar and PopupMenuButton hidden if player provided TODO: Subject to change
_showSearchBar
? IconButton (
onPressed: ( ) {
setState ( ( ) {
_showSearchBar = false ;
} ) ;
} ,
icon: const Icon ( Icons . clear ) ,
tooltip: t . closeSearch ,
)
: IconButton (
onPressed: ( ) {
setState ( ( ) {
_showSearchBar = true ;
} ) ;
} ,
icon: const Icon ( Icons . search ) ,
tooltip: t . openSearch ,
) ,
2023-06-17 21:50:52 +00:00
PopupMenuButton (
itemBuilder: ( BuildContext context ) = > < PopupMenuEntry > [
2023-07-10 17:42:20 +00:00
PopupMenuItem (
2023-07-09 16:50:17 +00:00
value: " refresh " ,
2023-07-10 17:42:20 +00:00
child: Text ( t . refresh ) ,
2023-07-09 16:50:17 +00:00
) ,
2023-07-20 20:56:00 +00:00
PopupMenuItem (
2023-12-26 22:02:47 +00:00
value: " history " ,
2023-07-20 20:56:00 +00:00
child: Text ( t . fetchAndsaveTLHistory ) ,
) ,
2024-02-03 13:02:58 +00:00
PopupMenuItem (
value: " TLmatches " ,
child: Text ( t . fetchAndSaveOldTLmatches ) ,
) ,
2023-07-10 17:42:20 +00:00
PopupMenuItem (
2023-06-17 21:50:52 +00:00
value: " /states " ,
2023-07-10 17:42:20 +00:00
child: Text ( t . showStoredData ) ,
2023-06-17 21:50:52 +00:00
) ,
2023-07-10 17:42:20 +00:00
PopupMenuItem (
2023-06-17 21:50:52 +00:00
value: " /calc " ,
2023-07-10 17:42:20 +00:00
child: Text ( t . statsCalc ) ,
2023-06-17 21:50:52 +00:00
) ,
2023-07-10 17:42:20 +00:00
PopupMenuItem (
2023-06-17 21:50:52 +00:00
value: " /settings " ,
2023-07-10 17:42:20 +00:00
child: Text ( t . settings ) ,
2023-06-17 21:50:52 +00:00
) ,
] ,
onSelected: ( value ) {
2024-01-04 23:04:05 +00:00
switch ( value ) {
case " refresh " :
changePlayer ( _searchFor ) ;
break ;
case " history " :
changePlayer ( _searchFor , fetchHistory: true ) ;
break ;
2024-02-03 13:02:58 +00:00
case " TLmatches " :
changePlayer ( _searchFor , fetchTLmatches: true ) ;
break ;
2024-01-04 23:04:05 +00:00
default :
context . go ( value ) ;
}
2023-06-17 21:50:52 +00:00
} ,
) ,
2023-07-11 17:02:35 +00:00
] : null ,
2023-06-17 21:50:52 +00:00
) ,
body: SafeArea (
2023-06-21 22:05:14 +00:00
child: FutureBuilder < List < dynamic > > (
2023-06-17 21:50:52 +00:00
future: me ,
builder: ( context , snapshot ) {
switch ( snapshot . connectionState ) {
case ConnectionState . none:
case ConnectionState . waiting:
case ConnectionState . active:
2023-07-10 17:42:20 +00:00
return const Center ( child: CircularProgressIndicator ( color: Colors . white ) ) ;
2023-06-17 21:50:52 +00:00
case ConnectionState . done:
if ( snapshot . hasData ) {
2023-06-26 17:13:53 +00:00
return RefreshIndicator (
onRefresh: ( ) {
return Future ( ( ) = > changePlayer ( snapshot . data ! [ 0 ] . userId ) ) ;
2023-06-17 21:50:52 +00:00
} ,
2024-01-22 18:56:43 +00:00
notificationPredicate: ( notification ) {
// with NestedScrollView local(depth == 2) OverscrollNotification are not sent
if ( ! kIsWeb & & ( notification is OverscrollNotification | | Platform . isIOS ) ) {
return notification . depth = = 2 ;
}
return notification . depth = = 0 ;
} ,
2023-06-26 17:13:53 +00:00
child: NestedScrollView (
2024-03-24 16:38:06 +00:00
scrollBehavior: ScrollConfiguration . of ( context ) . copyWith ( scrollbars: false , physics: const AlwaysScrollableScrollPhysics ( ) ) ,
2024-03-13 22:44:53 +00:00
controller: _scrollController ,
2023-06-26 17:13:53 +00:00
headerSliverBuilder: ( context , value ) {
return [
SliverToBoxAdapter (
child: UserThingy (
player: snapshot . data ! [ 0 ] ,
showStateTimestamp: false ,
setState: _justUpdate ,
) ) ,
SliverToBoxAdapter (
child: TabBar (
2024-03-13 22:44:53 +00:00
controller: bigScreen ? _wideScreenTabController : _tabController ,
2024-01-13 18:49:36 +00:00
padding: const EdgeInsets . all ( 0.0 ) ,
2023-06-26 17:13:53 +00:00
isScrollable: true ,
2024-03-13 22:44:53 +00:00
tabs: bigScreen ? [
Tab ( text: t . tetraLeague , ) ,
Tab ( text: t . history ) ,
2024-07-31 21:50:15 +00:00
Tab ( text: t . quickPlay ) ,
2024-03-13 22:44:53 +00:00
Tab ( text: " ${ t . sprint } & ${ t . blitz } " ) ,
Tab ( text: t . other ) ,
] : [
2023-07-10 17:42:20 +00:00
Tab ( text: t . tetraLeague ) ,
Tab ( text: t . tlRecords ) ,
Tab ( text: t . history ) ,
2024-07-31 21:50:15 +00:00
Tab ( text: t . quickPlay ) ,
Tab ( text: " ${ t . quickPlay } ${ t . recent } " ) ,
2023-07-10 17:42:20 +00:00
Tab ( text: t . sprint ) ,
Tab ( text: t . blitz ) ,
2024-06-16 17:13:26 +00:00
Tab ( text: t . recentRuns ) ,
2023-07-10 17:42:20 +00:00
Tab ( text: t . other ) ,
] ,
2023-06-26 17:13:53 +00:00
) ,
) ,
] ;
} ,
body: TabBarView (
2024-03-13 22:44:53 +00:00
controller: bigScreen ? _wideScreenTabController : _tabController ,
children: bigScreen ? [
Row (
mainAxisAlignment: MainAxisAlignment . center ,
children: [
Container (
width: MediaQuery . of ( context ) . size . width - 450 ,
2024-03-20 22:56:13 +00:00
constraints: const BoxConstraints ( maxWidth: 1024 ) ,
2024-03-13 22:44:53 +00:00
child: TLThingy (
2024-07-27 19:10:45 +00:00
tl: snapshot . data ! [ 1 ] . league ,
2024-03-13 22:44:53 +00:00
userID: snapshot . data ! [ 0 ] . userId ,
2024-09-02 21:17:09 +00:00
states: snapshot . data ! [ 6 ] ,
2024-07-27 19:10:45 +00:00
//topTR: snapshot.data![7]?.tr,
//lastMatchPlayed: snapshot.data![11],
2024-03-13 22:44:53 +00:00
bot: snapshot . data ! [ 0 ] . role = = " bot " ,
guest: snapshot . data ! [ 0 ] . role = = " anon " ,
2024-08-17 23:39:20 +00:00
thatRankCutoff: thatRankCutoff ,
thatRankCutoffGlicko: thatRankGlickoCutoff ,
2024-08-24 14:41:07 +00:00
thatRankTarget: snapshot . data ! [ 1 ] . league . rank ! = " z " ? rankTargets [ snapshot . data ! [ 1 ] . league . rank ] : null ,
2024-08-17 23:39:20 +00:00
nextRankCutoff: nextRankCutoff ,
nextRankCutoffGlicko: nextRankGlickoCutoff ,
2024-08-24 14:41:07 +00:00
nextRankTarget: ( snapshot . data ! [ 1 ] . league . rank ! = " z " & & snapshot . data ! [ 1 ] . league . rank ! = " x " ) ? rankTargets [ ranks . elementAtOrNull ( ranks . indexOf ( snapshot . data ! [ 1 ] . league . rank ) + 1 ) ] : null ,
2024-08-19 17:59:25 +00:00
averages: rankAverages ,
lbPositions: meAmongEveryone
2024-03-13 22:44:53 +00:00
) ,
) ,
SizedBox (
width: 450 ,
2024-07-27 19:10:45 +00:00
child: _TLRecords ( userID: snapshot . data ! [ 0 ] . userId , changePlayer: changePlayer , data: snapshot . data ! [ 3 ] . records , wasActiveInTL: true , oldMathcesHere: _TLHistoryWasFetched , separateScrollController: true )
2024-03-13 22:44:53 +00:00
) ,
] , ) ,
2024-07-27 19:10:45 +00:00
_History ( chartsData: chartsData , changePlayer: changePlayer , userID: _searchFor , update: _justUpdate , wasActiveInTL: snapshot . data ! [ 1 ] . league . gamesPlayed > 0 ) ,
2024-07-31 21:50:15 +00:00
Row (
mainAxisAlignment: MainAxisAlignment . center ,
children: [
Container (
width: MediaQuery . of ( context ) . size . width - 450 ,
constraints: const BoxConstraints ( maxWidth: 1024 ) ,
2024-08-03 17:52:20 +00:00
child: SingleChildScrollView ( child: ZenithThingy ( record: snapshot . data ! [ 1 ] . zenith , recordEX: snapshot . data ! [ 1 ] . zenithEx , parentZenithToggle: toggleZenith , initEXvalue: zenithEX ) )
2024-07-31 21:50:15 +00:00
) ,
SizedBox (
width: 450.0 ,
child: _ZenithRecords ( userID: snapshot . data ! [ 0 ] . userId , data: snapshot . data ! [ zenithEX ? 5 : 4 ] , separateScrollController: true ) ,
)
] ,
) ,
2024-07-27 19:10:45 +00:00
_TwoRecordsThingy ( sprint: snapshot . data ! [ 1 ] . sprint , blitz: snapshot . data ! [ 1 ] . blitz , rank: snapshot . data ! [ 1 ] . league . percentileRank , recent: SingleplayerStream ( userId: " userId " , records: [ ] , type: " recent " ) , sprintStream: SingleplayerStream ( userId: " userId " , records: [ ] , type: " 40l " ) , blitzStream: SingleplayerStream ( userId: " userId " , records: [ ] , type: " blitz " ) ) ,
_OtherThingy ( zen: snapshot . data ! [ 1 ] . zen , bio: snapshot . data ! [ 0 ] . bio , distinguishment: snapshot . data ! [ 0 ] . distinguishment , newsletter: snapshot . data ! [ 2 ] )
2024-03-13 22:44:53 +00:00
] : [
2024-03-06 22:34:15 +00:00
TLThingy (
2024-07-27 19:41:42 +00:00
tl: snapshot . data ! [ 1 ] . league ,
2024-03-06 22:34:15 +00:00
userID: snapshot . data ! [ 0 ] . userId ,
2024-07-27 19:41:42 +00:00
states: const [ ] , //snapshot.data![2],
//topTR: snapshot.data![7]?.tr,
//lastMatchPlayed: snapshot.data![11],
2024-03-06 22:34:15 +00:00
bot: snapshot . data ! [ 0 ] . role = = " bot " ,
guest: snapshot . data ! [ 0 ] . role = = " anon " ,
2024-08-17 23:39:20 +00:00
thatRankCutoff: thatRankCutoff ,
thatRankCutoffGlicko: thatRankGlickoCutoff ,
2024-08-24 14:41:07 +00:00
thatRankTarget: snapshot . data ! [ 1 ] . league . rank ! = " z " ? rankTargets [ snapshot . data ! [ 1 ] . league . rank ] : null ,
2024-08-17 23:39:20 +00:00
nextRankCutoff: nextRankCutoff ,
nextRankCutoffGlicko: nextRankGlickoCutoff ,
2024-08-24 14:41:07 +00:00
nextRankTarget: ( snapshot . data ! [ 1 ] . league . rank ! = " z " & & snapshot . data ! [ 1 ] . league . rank ! = " x " ) ? rankTargets [ ranks . elementAtOrNull ( ranks . indexOf ( snapshot . data ! [ 1 ] . league . rank ) + 1 ) ] : null ,
2024-08-19 17:59:25 +00:00
averages: rankAverages ,
lbPositions: meAmongEveryone
2024-03-06 22:34:15 +00:00
) ,
2024-07-27 19:41:42 +00:00
_TLRecords ( userID: snapshot . data ! [ 0 ] . userId , changePlayer: changePlayer , data: snapshot . data ! [ 3 ] . records , wasActiveInTL: true , oldMathcesHere: _TLHistoryWasFetched , separateScrollController: true ) ,
_History ( chartsData: chartsData , changePlayer: changePlayer , userID: _searchFor , update: _justUpdate , wasActiveInTL: snapshot . data ! [ 1 ] . league . gamesPlayed > 0 ) ,
2024-08-03 17:52:20 +00:00
SingleChildScrollView ( child: ZenithThingy ( record: snapshot . data ! [ 1 ] . zenith , recordEX: snapshot . data ! [ 1 ] . zenithEx , parentZenithToggle: toggleZenith , initEXvalue: zenithEX ) ) ,
2024-07-31 21:50:15 +00:00
_ZenithRecords ( userID: snapshot . data ! [ 0 ] . userId , data: snapshot . data ! [ zenithEX ? 5 : 4 ] , separateScrollController: true ) ,
2024-07-27 19:41:42 +00:00
SingleplayerRecord ( record: snapshot . data ! [ 1 ] . sprint , rank: snapshot . data ! [ 1 ] . league . percentileRank , stream: SingleplayerStream ( userId: " userId " , records: [ ] , type: " 40l " ) ) ,
SingleplayerRecord ( record: snapshot . data ! [ 1 ] . blitz , rank: snapshot . data ! [ 1 ] . league . percentileRank , stream: SingleplayerStream ( userId: " userId " , records: [ ] , type: " Blitz " ) ) ,
_RecentSingleplayersThingy ( SingleplayerStream ( userId: " userId " , records: [ ] , type: " recent " ) ) ,
_OtherThingy ( zen: snapshot . data ! [ 1 ] . zen , bio: snapshot . data ! [ 0 ] . bio , distinguishment: snapshot . data ! [ 0 ] . distinguishment , newsletter: snapshot . data ! [ 2 ] )
2023-06-26 17:13:53 +00:00
] ,
) ,
2023-06-17 21:50:52 +00:00
) ,
) ;
} else if ( snapshot . hasError ) {
2023-07-11 17:02:35 +00:00
String errText = " " ;
2024-03-20 22:56:13 +00:00
String ? subText ;
2023-07-11 17:02:35 +00:00
switch ( snapshot . error . runtimeType ) {
case TetrioPlayerNotExist:
errText = t . errors . noSuchUser ;
2024-03-20 22:56:13 +00:00
subText = t . errors . noSuchUserSub ;
2023-07-11 17:02:35 +00:00
break ;
2024-03-20 22:56:13 +00:00
case TetrioDiscordNotExist:
errText = t . errors . discordNotAssigned ;
subText = t . errors . discordNotAssignedSub ;
2023-07-11 17:02:35 +00:00
case ConnectionIssue:
var err = snapshot . error as ConnectionIssue ;
errText = t . errors . connection ( code: err . code , message: err . message ) ;
break ;
2023-09-23 19:09:36 +00:00
case TetrioForbidden:
errText = t . errors . forbidden ;
2024-03-20 22:56:13 +00:00
subText = t . errors . forbiddenSub ( nickname: ' osk ' ) ;
2023-09-23 19:09:36 +00:00
break ;
case TetrioTooManyRequests:
errText = t . errors . tooManyRequests ;
2024-03-20 22:56:13 +00:00
subText = t . errors . tooManyRequestsSub ;
2023-09-23 19:09:36 +00:00
break ;
case TetrioOskwareBridgeProblem:
errText = t . errors . oskwareBridge ;
2024-03-20 22:56:13 +00:00
subText = t . errors . oskwareBridgeSub ;
2023-09-23 19:09:36 +00:00
break ;
case TetrioInternalProblem:
errText = kIsWeb ? t . errors . internalWebVersion : t . errors . internal ;
2024-03-20 22:56:13 +00:00
subText = kIsWeb ? t . errors . internalWebVersionSub : t . errors . internalSub ;
2023-09-23 19:09:36 +00:00
break ;
case ClientException:
errText = t . errors . clientException ;
2023-07-11 17:02:35 +00:00
break ;
default :
errText = snapshot . error . toString ( ) ;
2024-08-12 23:07:59 +00:00
subText = snapshot . stackTrace . toString ( ) ;
2023-07-11 17:02:35 +00:00
}
2024-03-20 22:56:13 +00:00
return Center ( child:
Column (
mainAxisSize: MainAxisSize . min ,
children: [
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 ) ,
2024-05-04 20:04:48 +00:00
child: Text ( subText , style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 18 ) , textAlign: TextAlign . center ) ,
2024-03-20 22:56:13 +00:00
) ,
] ,
)
) ;
2023-06-17 21:50:52 +00:00
}
break ;
}
2023-10-09 18:48:50 +00:00
return const Center ( child: Text ( ' default case of FutureBuilder ' , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 42 ) , textAlign: TextAlign . center ) ) ;
2023-06-17 21:50:52 +00:00
} ,
) ,
) ,
) ;
}
}
class NavDrawer extends StatefulWidget {
final Function changePlayer ;
2024-01-26 20:56:24 +00:00
/// Thing, that shows from the left side of the view.
/// Requires [changePlayer] function in order to be able to change players on main view
2023-06-17 21:50:52 +00:00
const NavDrawer ( this . changePlayer , { super . key } ) ;
@ override
State < NavDrawer > createState ( ) = > _NavDrawerState ( ) ;
}
class _NavDrawerState extends State < NavDrawer > {
String homePlayerNickname = " Checking... " ;
@ override
void initState ( ) {
super . initState ( ) ;
_setHomePlayerNickname ( prefs . getString ( " player " ) ) ;
}
@ override
void dispose ( ) {
super . dispose ( ) ;
}
2024-01-26 20:56:24 +00:00
/// Sets username for home button in NavDrawer.
/// Accepts [id] or username. If it's not provided, sets my nickname.
/// Otherwise, sets username or [id] if failed to find
Future < void > _setHomePlayerNickname ( String ? id ) async {
if ( id ! = null ) {
2023-06-17 21:50:52 +00:00
try {
2024-01-26 20:56:24 +00:00
homePlayerNickname = await teto . getNicknameByID ( id ) ;
2023-06-17 21:50:52 +00:00
} on TetrioPlayerNotExist {
2024-01-26 20:56:24 +00:00
homePlayerNickname = id ;
2023-06-17 21:50:52 +00:00
}
} else {
2024-05-04 21:17:26 +00:00
homePlayerNickname = " dan63047 " ;
2023-06-17 21:50:52 +00:00
}
setState ( ( ) { } ) ;
}
@ 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 . active:
2023-06-20 20:53:28 +00:00
final allPlayers = ( snapshot . data ! = null )
2024-02-06 20:38:52 +00:00
? snapshot . data as Map < String , String >
: < String , String > { } ;
allPlayers . remove ( prefs . getString ( " player " ) ? ? " 6098518e3d5155e6ec429cdc " ) ; // player from the home button will be delisted
2023-10-08 17:20:42 +00:00
List < String > keys = allPlayers . keys . toList ( ) ;
2023-06-17 21:50:52 +00:00
return NestedScrollView (
headerSliverBuilder: ( context , value ) {
return [
2023-07-11 17:02:35 +00:00
SliverToBoxAdapter (
2023-06-17 21:50:52 +00:00
child: DrawerHeader (
2023-07-11 17:02:35 +00:00
child: Text ( t . playersYouTrack , style: const TextStyle ( color: Colors . white , fontSize: 25 ) ,
2023-06-17 21:50:52 +00:00
) ) ) ,
SliverToBoxAdapter (
2024-01-26 20:56:24 +00:00
child: ListTile ( // Home button
2023-06-17 21:50:52 +00:00
leading: const Icon ( Icons . home ) ,
title: Text ( homePlayerNickname ) ,
onTap: ( ) {
2024-04-20 22:37:31 +00:00
widget . changePlayer ( prefs . getString ( " player " ) ? ? " 6098518e3d5155e6ec429cdc " ) ; // changes player on main view to the one from preferences
2024-01-26 20:56:24 +00:00
Navigator . of ( context ) . pop ( ) ; // and then NavDrawer closes itself.
2023-06-17 21:50:52 +00:00
} ,
) ,
2023-07-07 20:32:57 +00:00
) ,
SliverToBoxAdapter (
2024-01-26 20:56:24 +00:00
child: ListTile ( // Leaderboard button
2023-07-07 20:32:57 +00:00
leading: const Icon ( Icons . leaderboard ) ,
2023-07-10 17:42:20 +00:00
title: Text ( t . tlLeaderboard ) ,
2023-07-07 20:32:57 +00:00
onTap: ( ) {
2024-06-11 16:30:13 +00:00
context . go ( " /leaderboard " ) ;
2023-07-07 20:32:57 +00:00
} ,
) ,
2023-07-11 17:02:35 +00:00
) ,
2023-09-04 18:00:13 +00:00
SliverToBoxAdapter (
2024-01-26 20:56:24 +00:00
child: ListTile ( // Rank averages button
2023-09-04 18:00:13 +00:00
leading: const Icon ( Icons . compress ) ,
title: Text ( t . rankAveragesViewTitle ) ,
onTap: ( ) {
2024-06-11 16:30:13 +00:00
context . go ( " /LBvalues " ) ;
2023-09-04 18:00:13 +00:00
} ,
) ,
) ,
2024-05-28 21:05:01 +00:00
SliverToBoxAdapter (
child: ListTile ( // Rank averages button
leading: const Icon ( Icons . bar_chart ) ,
title: Text ( t . sprintAndBlitsViewTitle ) ,
onTap: ( ) {
2024-06-11 16:30:13 +00:00
context . go ( " /sprintAndBlitzAverages " ) ;
2024-05-28 21:05:01 +00:00
} ,
) ,
) ,
2023-07-11 17:02:35 +00:00
const SliverToBoxAdapter ( child: Divider ( ) )
2023-06-17 21:50:52 +00:00
] ;
} ,
2024-01-26 20:56:24 +00:00
body: ListView . builder ( // Builds list of tracked players.
2023-06-17 21:50:52 +00:00
itemCount: allPlayers . length ,
itemBuilder: ( context , index ) {
2024-01-26 20:56:24 +00:00
var i = allPlayers . length - 1 - index ; // Last players in this map are most recent ones, they are gonna be shown at the top.
2023-06-17 21:50:52 +00:00
return ListTile (
2024-02-06 20:38:52 +00:00
title: Text ( allPlayers [ keys [ i ] ] ? ? keys [ i ] ) , // Takes last known username from list of states
2023-06-17 21:50:52 +00:00
onTap: ( ) {
2024-01-26 20:56:24 +00:00
widget . changePlayer ( keys [ i ] ) ; // changes to chosen player
Navigator . of ( context ) . pop ( ) ; // and closes itself.
2023-06-17 21:50:52 +00:00
} ,
) ;
} ) ) ;
case ConnectionState . done:
2024-01-26 20:56:24 +00:00
return const Center ( child: Text ( ' done case of StreamBuilder ' ) ) ; // what if that thing breaks?
2023-06-17 21:50:52 +00:00
}
} ,
) ,
) ;
}
}
2023-06-20 20:53:28 +00:00
class _TLRecords extends StatelessWidget {
final String userID ;
2024-03-17 23:15:44 +00:00
final Function changePlayer ;
2024-07-27 19:10:45 +00:00
final List < BetaRecord > data ;
2024-03-17 23:15:44 +00:00
final bool wasActiveInTL ;
2024-03-20 22:56:13 +00:00
final bool oldMathcesHere ;
2024-03-24 16:38:06 +00:00
final bool separateScrollController ;
2024-01-26 20:56:24 +00:00
/// Widget, that displays Tetra League records.
/// Accepts list of TL records ([data]) and [userID] of player from the view
2024-03-24 16:38:06 +00:00
const _TLRecords ( { required this . userID , required this . changePlayer , required this . data , required this . wasActiveInTL , required this . oldMathcesHere , this . separateScrollController = false } ) ;
2023-06-20 20:53:28 +00:00
@ override
Widget build ( BuildContext context ) {
2024-03-20 22:56:13 +00:00
if ( data . isEmpty ) {
return Center ( child: Column (
2024-03-17 23:15:44 +00:00
mainAxisSize: MainAxisSize . min ,
children: [
Text ( t . noRecords , style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 28 ) ) ,
2024-03-20 22:56:13 +00:00
if ( wasActiveInTL ) Text ( t . errors . actionSuggestion ) ,
2024-03-17 23:15:44 +00:00
if ( wasActiveInTL ) TextButton ( onPressed: ( ) { changePlayer ( userID , fetchTLmatches: true ) ; } , child: Text ( t . fetchAndSaveOldTLmatches ) )
] ,
) ) ;
2024-03-20 22:56:13 +00:00
}
2024-03-13 22:44:53 +00:00
bool bigScreen = MediaQuery . of ( context ) . size . width > = 768 ;
2024-03-20 22:56:13 +00:00
int length = data . length ;
2024-02-03 13:02:58 +00:00
return ListView . builder (
physics: const AlwaysScrollableScrollPhysics ( ) ,
2024-03-24 16:38:06 +00:00
controller: separateScrollController ? ScrollController ( ) : null ,
2024-03-20 22:56:13 +00:00
itemCount: oldMathcesHere ? length : length + 1 ,
2024-02-03 13:02:58 +00:00
itemBuilder: ( BuildContext context , int index ) {
2024-03-20 22:56:13 +00:00
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 ) )
] ,
) ) ;
}
2024-07-27 19:10:45 +00:00
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 ;
2024-02-08 21:39:54 +00:00
return Container (
decoration: BoxDecoration (
gradient: LinearGradient (
stops: const [ 0 , 0.05 ] ,
colors: [ accentColor , Colors . transparent ]
)
) ,
child: ListTile (
2024-07-27 19:10:45 +00:00
leading: Text ( " ${ data [ index ] . results . leaderboard . firstWhere ( ( element ) = > element . id = = userID ) . wins } : ${ data [ index ] . results . leaderboard . firstWhere ( ( element ) = > element . id ! = userID ) . wins } " ,
2024-02-08 21:39:54 +00:00
style: bigScreen ? const TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 28 , shadows: textShadow ) : const TextStyle ( fontSize: 28 , shadows: textShadow ) ) ,
2024-07-27 19:10:45 +00:00
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 ) ) ,
2024-03-02 22:26:31 +00:00
trailing: TrailingStats (
2024-07-27 19:10:45 +00:00
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 ,
2024-03-02 22:26:31 +00:00
) ,
2024-07-28 17:12:47 +00:00
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-02-08 21:39:54 +00:00
) ,
2023-06-26 17:13:53 +00:00
) ;
2024-02-03 13:02:58 +00:00
} ) ;
2023-06-20 20:53:28 +00:00
}
}
2024-07-31 21:50:15 +00:00
class _ZenithRecords extends StatelessWidget {
final String userID ;
final SingleplayerStream data ;
final bool separateScrollController ;
/// Widget, that displays Quick Play records.
/// Accepts list of TL records ([data]) and [userID] of player from the view
const _ZenithRecords ( { required this . userID , required this . data , this . separateScrollController = false } ) ;
@ override
Widget build ( BuildContext context ) {
if ( data . records . isEmpty ) {
return Center ( child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
Text ( t . noRecords , style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 28 ) ) ,
] ,
) ) ;
}
bool bigScreen = MediaQuery . of ( context ) . size . width > = 768 ;
int length = data . records . length ;
return ListView . builder (
physics: const AlwaysScrollableScrollPhysics ( ) ,
controller: separateScrollController ? ScrollController ( ) : null ,
itemCount: 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 ) ) ,
] ,
) ) ;
}
const TextStyle style = TextStyle ( height: 1.1 , fontWeight: FontWeight . w100 , fontSize: 13 ) ;
return Container (
child: ListTile (
leading: Text ( " QP " ,
style: bigScreen ? const TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 28 , shadows: textShadow ) : const TextStyle ( fontSize: 28 , shadows: textShadow ) ) ,
title: Text ( " ${ f2 . format ( data . records [ index ] . stats . zenith ! . altitude ) } m ${ ( data . records [ index ] . extras as ZenithExtras ) . mods . isNotEmpty ? " ( $ {t.withModsPlural(n: (data.records[index].extras as ZenithExtras).mods.length) } ) " : " " } " ),
subtitle: Text ( timestamp ( data . records [ index ] . timestamp ) , style: const TextStyle ( color: Colors . grey ) ) ,
trailing: Column (
mainAxisSize: MainAxisSize . min ,
crossAxisAlignment: CrossAxisAlignment . end ,
children: [
Text ( " ${ f2 . format ( data . records [ index ] . aggregateStats . apm ) } APM, ${ f2 . format ( data . records [ index ] . aggregateStats . pps ) } PPS " , style: style , textAlign: TextAlign . right ) ,
Text ( " ${ f2 . format ( data . records [ index ] . stats . cps ) } CSP ( ${ f2 . format ( data . records [ index ] . stats . zenith ! . peakrank ) } peak) " , style: style , textAlign: TextAlign . right ) ,
Text ( " ${ data . records [ index ] . stats . kills } KO's, ${ getMoreNormalTime ( data . records [ index ] . stats . finalTime ) } " , style: style , textAlign: TextAlign . right )
] ,
) ,
onTap: ( ) = > Navigator . push ( context , MaterialPageRoute ( builder: ( context ) = > ZenithRecordView ( record: data . records [ index ] ) ) ) //Navigator.push(context, MaterialPageRoute(builder: (context) => TlMatchResultView(record: data[index], initPlayerId: userID))),
) ,
) ;
} ) ;
}
}
2023-06-26 17:13:53 +00:00
class _History extends StatelessWidget {
2024-09-03 21:07:27 +00:00
final List < List < DropdownMenuItem < List < _HistoryChartSpot > > > > chartsData ;
2024-03-17 23:15:44 +00:00
final String userID ;
2023-06-26 17:13:53 +00:00
final Function update ;
2024-03-17 23:15:44 +00:00
final Function changePlayer ;
final bool wasActiveInTL ;
2024-01-26 20:56:24 +00:00
/// Widget, that can show history of some stat of the player on the graph.
/// Requires player [states], which is list of states and function [update], which rebuild widgets
2024-04-30 23:03:42 +00:00
const _History ( { required this . chartsData , required this . userID , required this . changePlayer , required this . update , required this . wasActiveInTL } ) ;
2023-06-22 19:02:49 +00:00
@ override
Widget build ( BuildContext context ) {
2024-03-20 22:56:13 +00:00
if ( chartsData . isEmpty ) {
return Center ( child: Column (
2024-03-17 23:15:44 +00:00
mainAxisSize: MainAxisSize . min ,
children: [
Text ( t . noHistorySaved , style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 28 ) ) ,
2024-03-20 22:56:13 +00:00
if ( wasActiveInTL ) Text ( t . errors . actionSuggestion ) ,
if ( wasActiveInTL ) TextButton ( onPressed: ( ) { changePlayer ( userID , fetchHistory: true ) ; } , child: Text ( t . fetchAndsaveTLHistory ) )
2024-03-17 23:15:44 +00:00
] ,
) ) ;
2024-03-20 22:56:13 +00:00
}
bool bigScreen = MediaQuery . of ( context ) . size . width > 768 ;
2024-09-03 21:07:27 +00:00
List < _HistoryChartSpot > selectedGraph = chartsData [ _season ] [ _chartsIndex ] . value ! ;
2024-03-20 22:56:13 +00:00
return SingleChildScrollView (
scrollDirection: Axis . vertical ,
2024-05-04 20:04:48 +00:00
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
Wrap (
spacing: 20 ,
crossAxisAlignment: WrapCrossAlignment . center ,
children: [
2024-09-03 21:07:27 +00:00
Row (
mainAxisSize: MainAxisSize . min ,
children: [
const Padding ( padding: EdgeInsets . all ( 8.0 ) , child: Text ( " Season: " , style: TextStyle ( fontSize: 22 ) ) ) ,
DropdownButton (
items: [ for ( int i = 1 ; i < = currentSeason ; i + + ) DropdownMenuItem ( value: i - 1 , child: Text ( " $ i " ) ) ] ,
value: _season ,
onChanged: ( value ) {
_season = value ! ;
update ( ) ;
}
) ,
] ,
) ,
2024-05-04 20:04:48 +00:00
Row (
mainAxisSize: MainAxisSize . min ,
children: [
const Padding ( padding: EdgeInsets . all ( 8.0 ) , child: Text ( " X: " , style: TextStyle ( fontSize: 22 ) ) ) ,
DropdownButton (
items: const [ DropdownMenuItem ( value: false , child: Text ( " Date & Time " ) ) , DropdownMenuItem ( value: true , child: Text ( " Games Played " ) ) ] ,
value: _gamesPlayedInsteadOfDateAndTime ,
onChanged: ( value ) {
_gamesPlayedInsteadOfDateAndTime = value ! ;
update ( ) ;
}
) ,
2024-04-20 22:37:31 +00:00
] ,
2024-05-04 20:04:48 +00:00
) ,
Row (
mainAxisSize: MainAxisSize . min ,
children: [
const Padding ( padding: EdgeInsets . all ( 8.0 ) , child: Text ( " Y: " , style: TextStyle ( fontSize: 22 ) ) ) ,
DropdownButton (
2024-09-03 21:07:27 +00:00
items: chartsData [ _season ] ,
value: chartsData [ _season ] [ _chartsIndex ] . value ,
2024-05-04 20:04:48 +00:00
onChanged: ( value ) {
2024-09-03 21:07:27 +00:00
_chartsIndex = chartsData [ _season ] . indexWhere ( ( element ) = > element . value = = value ) ;
2024-05-04 20:04:48 +00:00
update ( ) ;
}
) ,
] ,
) ,
if ( selectedGraph . length > 300 ) Row (
mainAxisSize: MainAxisSize . min ,
children: [
Checkbox ( value: _smooth ,
checkColor: Colors . black ,
onChanged: ( ( value ) {
_smooth = value ! ;
update ( ) ;
} ) ) ,
Text ( t . smooth , style: const TextStyle ( color: Colors . white , fontSize: 22 ) )
] ,
) ,
IconButton ( onPressed: ( ) = > _zoomPanBehavior . reset ( ) , icon: const Icon ( Icons . refresh ) , alignment: Alignment . center , )
] ,
) ,
2024-09-03 21:07:27 +00:00
if ( chartsData [ _season ] [ _chartsIndex ] . value ! . length > 1 ) _HistoryChartThigy ( data: selectedGraph , smooth: _smooth , yAxisTitle: _historyShortTitles [ _chartsIndex ] , bigScreen: bigScreen , leftSpace: bigScreen ? 80 : 45 , yFormat: bigScreen ? f2 : NumberFormat . compact ( ) , xFormat: NumberFormat . compact ( ) )
else if ( chartsData [ _season ] [ _chartsIndex ] . value ! . length < = 1 ) Center ( child: Column (
2024-05-04 20:04:48 +00:00
mainAxisSize: MainAxisSize . min ,
children: [
Text ( t . notEnoughData , style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 28 ) ) ,
2024-09-03 21:07:27 +00:00
if ( wasActiveInTL & & _season = = 0 ) Text ( t . errors . actionSuggestion ) ,
if ( wasActiveInTL & & _season = = 0 ) TextButton ( onPressed: ( ) { changePlayer ( userID , fetchHistory: true ) ; } , child: Text ( t . fetchAndsaveTLHistory ) )
2024-05-04 20:04:48 +00:00
] ,
) )
] ,
) ,
2024-03-20 22:56:13 +00:00
) ;
2023-06-22 19:02:49 +00:00
}
}
2024-04-30 23:03:42 +00:00
class _HistoryChartSpot {
final DateTime timestamp ;
final int gamesPlayed ;
final String rank ;
final double stat ;
const _HistoryChartSpot ( this . timestamp , this . gamesPlayed , this . rank , this . stat ) ;
}
2024-01-13 18:49:36 +00:00
class _HistoryChartThigy extends StatefulWidget {
2024-04-30 23:03:42 +00:00
final List < _HistoryChartSpot > data ;
2024-04-20 22:37:31 +00:00
final bool smooth ;
2023-06-22 19:02:49 +00:00
final String yAxisTitle ;
final bool bigScreen ;
2023-06-23 18:38:15 +00:00
final double leftSpace ;
final NumberFormat yFormat ;
2024-03-24 16:38:06 +00:00
final NumberFormat ? xFormat ;
2024-01-26 20:56:24 +00:00
/// Implements graph for the _History widget. Requires [data] which is a list of dots for the graph. [yAxisTitle] used to keep track of changes.
2024-02-08 21:39:54 +00:00
/// [bigScreen] tells if screen wide enough, [leftSpace] sets size, reserved for titles on the left from the graph and [yFormat] sets number format
2024-01-26 20:56:24 +00:00
/// for left titles
2024-04-30 23:03:42 +00:00
const _HistoryChartThigy ( { required this . data , required this . smooth , required this . yAxisTitle , required this . bigScreen , required this . leftSpace , required this . yFormat , this . xFormat } ) ;
2024-01-13 18:49:36 +00:00
2023-06-22 19:02:49 +00:00
@ override
2024-01-13 18:49:36 +00:00
State < _HistoryChartThigy > createState ( ) = > _HistoryChartThigyState ( ) ;
}
class _HistoryChartThigyState extends State < _HistoryChartThigy > {
2024-01-22 18:00:24 +00:00
late String previousAxisTitle ;
2024-03-24 16:38:06 +00:00
late bool previousGamesPlayedInsteadOfDateAndTime ;
2024-04-30 23:03:42 +00:00
late TooltipBehavior _tooltipBehavior ;
2024-01-13 18:49:36 +00:00
@ override
void initState ( ) {
super . initState ( ) ;
2024-04-30 23:03:42 +00:00
_tooltipBehavior = TooltipBehavior (
color: Colors . black ,
borderColor: Colors . white ,
enable: true ,
animationDuration: 0 ,
builder: ( dynamic data , dynamic point , dynamic series ,
int pointIndex , int seriesIndex ) {
return Padding (
padding: const EdgeInsets . all ( 8.0 ) ,
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
Padding (
padding: const EdgeInsets . only ( bottom: 8.0 ) ,
child: Text (
" ${ f4 . format ( data . stat ) } ${ widget . yAxisTitle } " ,
2024-05-04 20:04:48 +00:00
style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 20 ) ,
2024-04-30 23:03:42 +00:00
) ,
) ,
2024-06-11 16:30:13 +00:00
Text ( _gamesPlayedInsteadOfDateAndTime ? t . gamesPlayed ( games: t . games ( n: data . gamesPlayed ) ) : timestamp ( data . timestamp ) )
2024-04-30 23:03:42 +00:00
] ,
) ,
) ;
}
) ;
2024-01-22 18:00:24 +00:00
previousAxisTitle = widget . yAxisTitle ;
2024-03-24 16:38:06 +00:00
previousGamesPlayedInsteadOfDateAndTime = _gamesPlayedInsteadOfDateAndTime ;
2024-01-22 18:00:24 +00:00
}
@ override
void dispose ( ) {
super . dispose ( ) ;
}
2024-01-13 18:49:36 +00:00
@ override
Widget build ( BuildContext context ) {
2024-03-24 16:38:06 +00:00
if ( ( previousAxisTitle ! = widget . yAxisTitle ) | | ( previousGamesPlayedInsteadOfDateAndTime ! = _gamesPlayedInsteadOfDateAndTime ) ) {
2024-01-22 18:00:24 +00:00
previousAxisTitle = widget . yAxisTitle ;
2024-03-24 16:38:06 +00:00
previousGamesPlayedInsteadOfDateAndTime = _gamesPlayedInsteadOfDateAndTime ;
setState ( ( ) { } ) ;
2024-01-22 18:00:24 +00:00
}
2024-03-24 16:38:06 +00:00
EdgeInsets padding = widget . bigScreen ? const EdgeInsets . fromLTRB ( 40 , 30 , 40 , 30 ) : const EdgeInsets . fromLTRB ( 0 , 40 , 16 , 48 ) ;
2023-06-26 17:13:53 +00:00
return SizedBox (
width: MediaQuery . of ( context ) . size . width ,
2024-01-13 18:49:36 +00:00
height: MediaQuery . of ( context ) . size . height - 104 ,
2024-04-30 23:03:42 +00:00
child: Padding ( padding: padding ,
2024-01-13 18:49:36 +00:00
child: Listener (
2024-01-16 22:55:21 +00:00
behavior: HitTestBehavior . translucent ,
2024-01-13 18:49:36 +00:00
onPointerSignal: ( signal ) {
2024-04-30 23:03:42 +00:00
if ( signal is PointerScrollEvent ) {
2024-01-13 18:49:36 +00:00
setState ( ( ) {
2024-01-22 18:00:24 +00:00
_scrollController . jumpTo ( _scrollController . position . maxScrollExtent - signal . scrollDelta . dy ) ; // TODO: find a better way to stop scrolling in NestedScrollView
2024-01-13 18:49:36 +00:00
} ) ;
}
} ,
2024-04-30 23:03:42 +00:00
child: SfCartesianChart (
tooltipBehavior: _tooltipBehavior ,
zoomPanBehavior: _zoomPanBehavior ,
2024-05-04 20:04:48 +00:00
primaryXAxis: _gamesPlayedInsteadOfDateAndTime ? const NumericAxis ( ) : const DateTimeAxis ( ) ,
primaryYAxis: const NumericAxis (
2024-04-30 23:03:42 +00:00
rangePadding: ChartRangePadding . additional ,
2023-06-22 19:02:49 +00:00
) ,
2024-06-15 22:49:57 +00:00
margin: const EdgeInsets . all ( 0 ) ,
2024-04-30 23:03:42 +00:00
series: < CartesianSeries > [
2024-05-01 23:12:52 +00:00
if ( _gamesPlayedInsteadOfDateAndTime ) StepLineSeries < _HistoryChartSpot , int > (
2024-04-30 23:03:42 +00:00
enableTooltip: true ,
dataSource: widget . data ,
animationDuration: 0 ,
2024-05-01 23:12:52 +00:00
opacity: _smooth ? 0 : 1 ,
xValueMapper: ( _HistoryChartSpot data , _ ) = > data . gamesPlayed ,
yValueMapper: ( _HistoryChartSpot data , _ ) = > data . stat ,
2024-06-15 22:49:57 +00:00
color: Theme . of ( context ) . colorScheme . primary ,
2024-05-01 23:12:52 +00:00
trendlines: < Trendline > [
Trendline (
isVisible: _smooth ,
period: ( widget . data . length / 175 ) . floor ( ) ,
type: TrendlineType . movingAverage ,
2024-06-15 22:49:57 +00:00
color: Theme . of ( context ) . colorScheme . primary )
2024-05-01 23:12:52 +00:00
] ,
)
else StepLineSeries < _HistoryChartSpot , DateTime > (
enableTooltip: true ,
dataSource: widget . data ,
animationDuration: 0 ,
opacity: _smooth ? 0 : 1 ,
2024-04-30 23:03:42 +00:00
xValueMapper: ( _HistoryChartSpot data , _ ) = > data . timestamp ,
yValueMapper: ( _HistoryChartSpot data , _ ) = > data . stat ,
2024-06-15 22:49:57 +00:00
color: Theme . of ( context ) . colorScheme . primary ,
2024-05-01 23:12:52 +00:00
trendlines: < Trendline > [
Trendline (
isVisible: _smooth ,
period: ( widget . data . length / 175 ) . floor ( ) ,
type: TrendlineType . movingAverage ,
2024-06-15 22:49:57 +00:00
color: Theme . of ( context ) . colorScheme . primary )
2024-05-01 23:12:52 +00:00
] ,
2024-04-30 23:03:42 +00:00
) ,
] ,
) ,
) ,
2023-06-22 19:02:49 +00:00
)
) ;
}
}
2024-03-17 23:15:44 +00:00
class _TwoRecordsThingy extends StatelessWidget {
final RecordSingle ? sprint ;
final RecordSingle ? blitz ;
2024-06-12 22:32:45 +00:00
final SingleplayerStream recent ;
2024-06-13 21:12:48 +00:00
final SingleplayerStream sprintStream ;
final SingleplayerStream blitzStream ;
2024-03-17 23:15:44 +00:00
final String ? rank ;
2024-06-13 21:12:48 +00:00
const _TwoRecordsThingy ( { required this . sprint , required this . blitz , this . rank , required this . recent , required this . sprintStream , required this . blitzStream } ) ;
2024-03-17 23:15:44 +00:00
Color getColorOfRank ( int rank ) {
if ( rank = = 1 ) return Colors . yellowAccent ;
if ( rank = = 2 ) return Colors . blueGrey ;
if ( rank = = 3 ) return Colors . brown [ 400 ] ! ;
if ( rank < = 9 ) return Colors . blueAccent ;
if ( rank < = 99 ) return Colors . greenAccent ;
return Colors . grey ;
}
@ override
Widget build ( BuildContext context ) {
late MapEntry closestAverageBlitz ;
late bool blitzBetterThanClosestAverage ;
2024-08-20 17:17:59 +00:00
bool ? blitzBetterThanRankAverage = ( rank ! = null & & rank ! = " z " & & rank ! = " x+ " & & blitz ! = null ) ? blitz ! . stats . score > blitzAverages [ rank ] ! : null ;
2024-03-17 23:15:44 +00:00
late MapEntry closestAverageSprint ;
late bool sprintBetterThanClosestAverage ;
2024-08-20 17:17:59 +00:00
bool ? sprintBetterThanRankAverage = ( rank ! = null & & rank ! = " z " & & rank ! = " x+ " & & sprint ! = null ) ? sprint ! . stats . finalTime < sprintAverages [ rank ] ! : null ;
2024-03-17 23:15:44 +00:00
if ( sprint ! = null ) {
2024-07-27 19:10:45 +00:00
closestAverageSprint = sprintAverages . entries . singleWhere ( ( element ) = > element . value = = sprintAverages . values . reduce ( ( a , b ) = > ( a - sprint ! . stats . finalTime ) . abs ( ) < ( b - sprint ! . stats . finalTime ) . abs ( ) ? a : b ) ) ;
sprintBetterThanClosestAverage = sprint ! . stats . finalTime < closestAverageSprint . value ;
2024-03-17 23:15:44 +00:00
}
if ( blitz ! = null ) {
2024-07-27 19:10:45 +00:00
closestAverageBlitz = blitzAverages . entries . singleWhere ( ( element ) = > element . value = = blitzAverages . values . reduce ( ( a , b ) = > ( a - blitz ! . stats . score ) . abs ( ) < ( b - blitz ! . stats . score ) . abs ( ) ? a : b ) ) ;
blitzBetterThanClosestAverage = blitz ! . stats . score > closestAverageBlitz . value ;
2024-03-17 23:15:44 +00:00
}
return SingleChildScrollView ( child: Padding (
padding: const EdgeInsets . only ( top: 20.0 ) ,
2024-06-15 22:49:57 +00:00
child: Wrap (
alignment: WrapAlignment . spaceEvenly ,
crossAxisAlignment: WrapCrossAlignment . start ,
2024-03-17 23:15:44 +00:00
children: [
Column (
mainAxisAlignment: MainAxisAlignment . start ,
children: [
Row (
mainAxisSize: MainAxisSize . min ,
children: [
2024-03-20 22:56:13 +00:00
Padding ( padding: const EdgeInsets . only ( right: 8.0 ) ,
2024-03-17 23:15:44 +00:00
child: sprint ! = null ? Image . asset ( " res/tetrio_tl_alpha_ranks/ ${ closestAverageSprint . key } .png " , height: 96 ) : Image . asset ( " res/tetrio_tl_alpha_ranks/z.png " , height: 96 )
) ,
Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
2024-03-20 22:56:13 +00:00
Text ( t . sprint , style: const TextStyle ( height: 0.1 , fontFamily: " Eurostile Round Extended " , fontSize: 18 ) ) ,
2024-03-17 23:15:44 +00:00
RichText ( text: TextSpan (
2024-07-27 19:10:45 +00:00
text: sprint ! = null ? get40lTime ( sprint ! . stats . finalTime . inMicroseconds ) : " --- " ,
2024-03-17 23:15:44 +00:00
style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 36 , fontWeight: FontWeight . w500 , color: sprint ! = null ? Colors . white : Colors . grey ) ,
2024-07-27 19:10:45 +00:00
//children: [TextSpan(text: get40lTime(record!.stats.finalTime.inMicroseconds), style: TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100))]
2024-03-17 23:15:44 +00:00
) ,
) ,
if ( sprint ! = null ) RichText ( text: TextSpan (
text: " " ,
style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 14 , color: Colors . grey ) ,
children: [
2024-08-20 17:17:59 +00:00
if ( rank ! = null & & rank ! = " z " & & rank ! = " x+ " ) TextSpan ( text: " ${ t . verdictGeneral ( n: readableTimeDifference ( sprint ! . stats . finalTime , sprintAverages [ rank ] ! ) , verdict: sprintBetterThanRankAverage ? ? false ? t . verdictBetter : t . verdictWorse , rank: rank ! . toUpperCase ( ) ) } \n " , style: TextStyle (
2024-03-17 23:15:44 +00:00
color: sprintBetterThanRankAverage ? ? false ? Colors . greenAccent : Colors . redAccent
2024-03-18 22:39:41 +00:00
) )
2024-07-27 19:10:45 +00:00
else TextSpan ( text: " ${ t . verdictGeneral ( n: readableTimeDifference ( sprint ! . stats . finalTime , closestAverageSprint . value ) , verdict: sprintBetterThanClosestAverage ? t . verdictBetter : t . verdictWorse , rank: closestAverageSprint . key . toUpperCase ( ) ) } \n " , style: TextStyle (
2024-03-20 22:56:13 +00:00
color: sprintBetterThanClosestAverage ? Colors . greenAccent : Colors . redAccent
2024-03-17 23:15:44 +00:00
) ) ,
2024-08-03 17:52:20 +00:00
TextSpan ( text: " № ${ sprint ! . rank } " , style: TextStyle ( color: getColorOfRank ( sprint ! . rank ) ) ) ,
const TextSpan ( text: " • " ) ,
2024-06-12 22:32:45 +00:00
TextSpan ( text: timestamp ( sprint ! . timestamp ) ) ,
2024-03-17 23:15:44 +00:00
]
) ,
) ,
] , ) ,
] ,
) ,
if ( sprint ! = null ) Wrap (
//mainAxisSize: MainAxisSize.max,
alignment: WrapAlignment . spaceBetween ,
spacing: 20 ,
children: [
2024-07-27 19:10:45 +00:00
StatCellNum ( playerStat: sprint ! . stats . piecesPlaced , playerStatLabel: t . statCellNum . pieces , isScreenBig: true , higherIsBetter: true , smallDecimal: false ) ,
StatCellNum ( playerStat: sprint ! . stats . pps , playerStatLabel: t . statCellNum . pps , fractionDigits: 2 , isScreenBig: true , higherIsBetter: true , smallDecimal: false ) ,
StatCellNum ( playerStat: sprint ! . stats . kpp , playerStatLabel: t . statCellNum . kpp , fractionDigits: 2 , isScreenBig: true , higherIsBetter: true , smallDecimal: false ) ,
2024-03-17 23:15:44 +00:00
] ,
) ,
2024-07-27 19:10:45 +00:00
if ( sprint ! = null ) FinesseThingy ( sprint ? . stats . finesse , sprint ? . stats . finessePercentage ) ,
if ( sprint ! = null ) LineclearsThingy ( sprint ! . stats . clears , sprint ! . stats . lines , sprint ! . stats . holds , sprint ! . stats . tSpins ) ,
if ( sprint ! = null ) Text ( " ${ sprint ! . stats . inputs } KP • ${ f2 . format ( sprint ! . stats . kps ) } KPS " ) ,
2024-07-10 16:21:03 +00:00
if ( sprint ! = null ) Wrap (
2024-06-15 22:49:57 +00:00
alignment: WrapAlignment . spaceBetween ,
crossAxisAlignment: WrapCrossAlignment . start ,
spacing: 20 ,
children: [
2024-06-16 21:04:07 +00:00
TextButton ( onPressed: ( ) { launchInBrowser ( Uri . parse ( " https://tetr.io/#r: ${ sprint ! . replayId } " ) ) ; } , child: Text ( t . openSPreplay ) ) ,
TextButton ( onPressed: ( ) { launchInBrowser ( Uri . parse ( " https://inoue.szy.lol/api/replay/ ${ sprint ! . replayId } " ) ) ; } , child: Text ( t . downloadSPreplay ) ) ,
2024-06-15 22:49:57 +00:00
] ,
) ,
2024-06-13 21:12:48 +00:00
if ( sprintStream . records . length > 1 ) SizedBox (
width: 400 ,
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
for ( int i = 1 ; i < sprintStream . records . length ; i + + ) ListTile (
2024-06-14 20:47:36 +00:00
onTap: ( ) = > Navigator . push ( context , MaterialPageRoute ( builder: ( context ) = > SingleplayerRecordView ( record: sprintStream . records [ i ] ) ) ) ,
2024-06-13 21:12:48 +00:00
leading: Text ( " # ${ i + 1 } " , style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 28 , shadows: textShadow , height: 0.9 ) ) ,
2024-07-27 19:10:45 +00:00
title: Text ( get40lTime ( sprintStream . records [ i ] . stats . finalTime . inMicroseconds ) ,
2024-06-16 21:04:07 +00:00
style: const TextStyle ( fontSize: 18 ) ) ,
subtitle: Text ( timestamp ( sprintStream . records [ i ] . timestamp ) , style: const TextStyle ( color: Colors . grey , height: 0.85 ) ) ,
2024-08-13 22:45:28 +00:00
trailing: SpTrailingStats ( sprintStream . records [ i ] , sprintStream . records [ i ] . gamemode )
2024-06-13 21:12:48 +00:00
)
] ,
) ,
)
2024-03-17 23:15:44 +00:00
]
) ,
Column (
mainAxisSize: MainAxisSize . min ,
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
2024-03-18 22:39:41 +00:00
crossAxisAlignment: CrossAxisAlignment . center ,
2024-03-17 23:15:44 +00:00
children: [
Row (
mainAxisSize: MainAxisSize . min ,
children: [
Column (
crossAxisAlignment: CrossAxisAlignment . end ,
children: [
Text ( t . blitz , style: const TextStyle ( height: 0.1 , fontFamily: " Eurostile Round Extended " , fontSize: 18 ) ) ,
RichText (
text: TextSpan (
text: " " ,
2024-03-24 16:38:06 +00:00
style: const TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 36 , fontWeight: FontWeight . w500 , color: Colors . white ) ,
2024-03-17 23:15:44 +00:00
children: [
2024-07-27 19:10:45 +00:00
TextSpan ( text: blitz ! = null ? NumberFormat . decimalPattern ( ) . format ( blitz ! . stats . score ) : " --- " ) ,
2024-03-17 23:15:44 +00:00
//WidgetSpan(child: Image.asset("res/icons/kagari.png", height: 48))
]
) ,
) ,
if ( blitz ! = null ) RichText (
textAlign: TextAlign . end ,
text: TextSpan (
text: " " ,
style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 14 , color: Colors . grey ) ,
children: [
2024-08-20 17:17:59 +00:00
if ( rank ! = null & & rank ! = " z " & & rank ! = " x+ " ) TextSpan ( text: " ${ t . verdictGeneral ( n: readableIntDifference ( blitz ! . stats . score , blitzAverages [ rank ] ! ) , verdict: blitzBetterThanRankAverage ? ? false ? t . verdictBetter : t . verdictWorse , rank: rank ! . toUpperCase ( ) ) } \n " , style: TextStyle (
2024-03-17 23:15:44 +00:00
color: blitzBetterThanRankAverage ? ? false ? Colors . greenAccent : Colors . redAccent
2024-03-18 22:39:41 +00:00
) )
2024-07-27 19:10:45 +00:00
else TextSpan ( text: " ${ t . verdictGeneral ( n: readableIntDifference ( blitz ! . stats . score , closestAverageBlitz . value ) , verdict: blitzBetterThanClosestAverage ? t . verdictBetter : t . verdictWorse , rank: closestAverageBlitz . key . toUpperCase ( ) ) } \n " , style: TextStyle (
2024-03-18 22:39:41 +00:00
color: blitzBetterThanClosestAverage ? Colors . greenAccent : Colors . redAccent
2024-03-17 23:15:44 +00:00
) ) ,
2024-06-12 22:32:45 +00:00
TextSpan ( text: timestamp ( blitz ! . timestamp ) ) ,
2024-08-03 17:52:20 +00:00
const TextSpan ( text: " • " ) ,
TextSpan ( text: " № ${ blitz ! . rank } " , style: TextStyle ( color: getColorOfRank ( blitz ! . rank ) ) ) ,
2024-03-17 23:15:44 +00:00
]
) ,
) ,
] , ) ,
2024-03-20 22:56:13 +00:00
Padding ( padding: const EdgeInsets . only ( left: 8.0 ) ,
2024-03-17 23:15:44 +00:00
child: blitz ! = null ? Image . asset ( " res/tetrio_tl_alpha_ranks/ ${ closestAverageBlitz . key } .png " , height: 96 ) : Image . asset ( " res/tetrio_tl_alpha_ranks/z.png " , height: 96 ) ) ,
] ,
) ,
if ( blitz ! = null ) Wrap (
//mainAxisSize: MainAxisSize.max,
alignment: WrapAlignment . spaceBetween ,
crossAxisAlignment: WrapCrossAlignment . start ,
spacing: 20 ,
children: [
2024-07-27 19:10:45 +00:00
StatCellNum ( playerStat: blitz ! . stats . level , playerStatLabel: t . statCellNum . level , isScreenBig: true , higherIsBetter: true , smallDecimal: false ) ,
StatCellNum ( playerStat: blitz ! . stats . pps , playerStatLabel: t . statCellNum . pps , fractionDigits: 2 , isScreenBig: true , higherIsBetter: true , smallDecimal: false ) ,
StatCellNum ( playerStat: blitz ! . stats . spp , playerStatLabel: t . statCellNum . spp , fractionDigits: 2 , isScreenBig: true , higherIsBetter: true )
2024-03-17 23:15:44 +00:00
] ,
) ,
2024-07-27 19:10:45 +00:00
if ( blitz ! = null ) FinesseThingy ( blitz ? . stats . finesse , blitz ? . stats . finessePercentage ) ,
if ( blitz ! = null ) LineclearsThingy ( blitz ! . stats . clears , blitz ! . stats . lines , blitz ! . stats . holds , blitz ! . stats . tSpins ) ,
if ( blitz ! = null ) Text ( " ${ blitz ! . stats . piecesPlaced } P • ${ blitz ! . stats . inputs } KP • ${ f2 . format ( blitz ! . stats . kpp ) } KPP • ${ f2 . format ( blitz ! . stats . kps ) } KPS " ) ,
2024-07-10 16:21:03 +00:00
if ( blitz ! = null ) Wrap (
2024-06-15 22:49:57 +00:00
alignment: WrapAlignment . spaceBetween ,
crossAxisAlignment: WrapCrossAlignment . start ,
spacing: 20 ,
children: [
2024-06-16 21:04:07 +00:00
TextButton ( onPressed: ( ) { launchInBrowser ( Uri . parse ( " https://tetr.io/#r: ${ blitz ! . replayId } " ) ) ; } , child: Text ( t . openSPreplay ) ) ,
TextButton ( onPressed: ( ) { launchInBrowser ( Uri . parse ( " https://inoue.szy.lol/api/replay/ ${ blitz ! . replayId } " ) ) ; } , child: Text ( t . downloadSPreplay ) ) ,
2024-06-15 22:49:57 +00:00
] ,
) ,
2024-06-13 21:12:48 +00:00
if ( blitzStream . records . length > 1 ) SizedBox (
width: 400 ,
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
2024-07-03 21:01:23 +00:00
for ( int i = 1 ; i < blitzStream . records . length ; i + + ) ListTile (
2024-07-20 13:21:14 +00:00
onTap: ( ) = > Navigator . push ( context , MaterialPageRoute ( builder: ( context ) = > SingleplayerRecordView ( record: blitzStream . records [ i ] ) ) ) ,
leading: Text ( " # ${ i + 1 } " , style: const TextStyle ( fontFamily: " Eurostile Round " , fontSize: 28 , shadows: textShadow , height: 0.9 ) ) ,
2024-07-27 19:10:45 +00:00
title: Text ( " ${ NumberFormat . decimalPattern ( ) . format ( blitzStream . records [ i ] . stats . score ) } points " ,
2024-07-20 13:21:14 +00:00
style: const TextStyle ( fontSize: 18 ) ) ,
subtitle: Text ( timestamp ( blitzStream . records [ i ] . timestamp ) , style: const TextStyle ( color: Colors . grey , height: 0.85 ) ) ,
2024-08-13 22:45:28 +00:00
trailing: SpTrailingStats ( blitzStream . records [ i ] , blitzStream . records [ i ] . gamemode )
2024-07-20 13:21:14 +00:00
)
2024-06-13 21:12:48 +00:00
] ,
) ,
)
2024-03-17 23:15:44 +00:00
] ,
) ,
2024-06-12 22:32:45 +00:00
SizedBox (
2024-06-13 21:12:48 +00:00
width: 400 ,
2024-06-14 20:47:36 +00:00
child: RecentSingleplayerGames ( recent: recent ) ,
2024-06-12 22:32:45 +00:00
)
2024-03-17 23:15:44 +00:00
] ) ,
) ) ;
}
}
2024-06-14 20:47:36 +00:00
class _RecentSingleplayersThingy extends StatelessWidget {
final SingleplayerStream recent ;
2024-01-26 20:56:24 +00:00
2024-06-14 20:47:36 +00:00
const _RecentSingleplayersThingy ( this . recent ) ;
2024-03-18 22:39:41 +00:00
2023-06-17 21:50:52 +00:00
@ override
Widget build ( BuildContext context ) {
2024-06-14 20:47:36 +00:00
return SingleChildScrollView (
child: RecentSingleplayerGames ( recent: recent , hideTitle: true )
2024-03-18 22:39:41 +00:00
) ;
2023-06-17 21:50:52 +00:00
}
2024-07-29 20:58:17 +00:00
}
2023-06-17 21:50:52 +00:00
class _OtherThingy extends StatelessWidget {
final TetrioZen ? zen ;
final String ? bio ;
2023-08-21 15:39:04 +00:00
final Distinguishment ? distinguishment ;
2024-06-03 21:06:00 +00:00
final News ? newsletter ;
2024-01-26 20:56:24 +00:00
/// Widget, that shows players [distinguishment], [bio], [zen] and [newsletter]
2024-01-13 18:49:36 +00:00
const _OtherThingy ( { required this . zen , required this . bio , required this . distinguishment , this . newsletter } ) ;
2023-06-17 21:50:52 +00:00
2024-01-26 20:56:24 +00:00
/// Distinguishment title is not very predictable thing.
/// Receives [text], which is header and returns sets of widgets for RichText widget
2024-01-01 17:26:09 +00:00
List < InlineSpan > getDistinguishmentTitle ( String ? text ) {
2024-01-26 20:56:24 +00:00
// TWC champions don't have header in their distinguishments
2024-01-01 17:26:09 +00:00
if ( distinguishment ? . type = = " twc " ) return [ const TextSpan ( text: " TETR.IO World Champion " , style: TextStyle ( fontSize: 28 , fontWeight: FontWeight . bold , color: Colors . yellowAccent ) ) ] ;
2024-01-26 20:56:24 +00:00
// In case if it missing for some other reason, return this
2024-01-01 17:26:09 +00:00
if ( text = = null ) return [ const TextSpan ( text: " Header is missing " , style: TextStyle ( fontSize: 28 , fontWeight: FontWeight . bold , color: Colors . redAccent ) ) ] ;
2024-01-26 20:56:24 +00:00
// Handling placeholders for logos
var exploded = text . split ( " " ) ; // wtf PHP reference?
2023-08-21 15:39:04 +00:00
List < InlineSpan > result = [ ] ;
for ( String shit in exploded ) {
2024-01-26 20:56:24 +00:00
switch ( shit ) { // if %% thingy was found, insert svg of icon
2023-08-21 15:39:04 +00:00
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 ;
2024-01-26 20:56:24 +00:00
default : // if not, insert text span
2024-01-22 18:00:24 +00:00
result . add ( TextSpan ( text: " $ shit " , style: const TextStyle ( fontSize: 28 , fontWeight: FontWeight . bold , color: Colors . white ) ) ) ;
2023-08-21 15:39:04 +00:00
}
}
return result ;
}
2024-01-26 20:56:24 +00:00
/// Distinguishment title is barely predictable thing.
/// Receives [text], which is footer and returns sets of widgets for RichText widget
2024-01-01 17:26:09 +00:00
String getDistinguishmentSubtitle ( String ? text ) {
2024-01-26 20:56:24 +00:00
// TWC champions don't have footer in their distinguishments
2024-01-01 17:26:09 +00:00
if ( distinguishment ? . type = = " twc " ) return " ${ distinguishment ? . detail } TETR.IO World Championship " ;
2024-01-26 20:56:24 +00:00
// In case if it missing for some other reason, return this
2024-01-01 17:26:09 +00:00
if ( text = = null ) return " Footer is missing " ;
2024-01-26 20:56:24 +00:00
// If everything ok, return as it is
2024-01-01 17:26:09 +00:00
return text ;
}
2024-01-26 20:56:24 +00:00
/// Handles [news] entry and returns widget that contains this entry
2024-06-03 21:06:00 +00:00
ListTile getNewsTile ( NewsEntry news ) {
2023-10-07 16:44:54 +00:00
Map < String , String > gametypes = {
" 40l " : t . sprint ,
" blitz " : t . blitz ,
2024-07-27 19:10:45 +00:00
" 5mblast " : " 5,000,000 Blast " ,
" zenith " : " Quick Play " ,
" zenithex " : " Quick Play Expert " ,
2023-10-07 16:44:54 +00:00
} ;
2024-01-26 20:56:24 +00:00
// Individuly handle each entry type
2023-10-07 16:44:54 +00:00
switch ( news . type ) {
case " leaderboard " :
return ListTile (
title: RichText (
text: TextSpan (
2024-01-22 18:00:24 +00:00
style: const TextStyle ( fontFamily: ' Eurostile Round ' , fontSize: 16 , color: Colors . white ) ,
2023-10-07 16:44:54 +00:00
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 ) ) ,
]
)
) ,
2024-06-11 16:30:13 +00:00
subtitle: Text ( timestamp ( news . timestamp ) ) ,
2023-10-07 16:44:54 +00:00
) ;
case " personalbest " :
return ListTile (
title: RichText (
text: TextSpan (
2024-01-22 18:00:24 +00:00
style: const TextStyle ( fontFamily: ' Eurostile Round ' , fontSize: 16 , color: Colors . white ) ,
2023-10-07 16:44:54 +00:00
text: t . newsParts . personalbest ,
children: [
TextSpan ( text: " ${ gametypes [ news . data [ " gametype " ] ] } " , style: const TextStyle ( fontWeight: FontWeight . bold ) ) ,
TextSpan ( text: t . newsParts . personalbestMiddle ) ,
2024-07-27 19:10:45 +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. " ,
2024-07-29 20:58:17 +00:00
" zenithex " = > " ${ f2 . format ( news . data [ " result " ] ) } m. " ,
2024-07-27 19:10:45 +00:00
_ = > " unknown "
} ,
style: const TextStyle ( fontWeight: FontWeight . bold )
) ,
2023-10-07 16:44:54 +00:00
]
)
) ,
2024-06-11 16:30:13 +00:00
subtitle: Text ( timestamp ( news . timestamp ) ) ,
2023-10-08 17:20:42 +00:00
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 ) ;
} ,
) ,
2023-10-07 16:44:54 +00:00
) ;
case " badge " :
return ListTile (
title: RichText (
text: TextSpan (
2024-01-22 18:00:24 +00:00
style: const TextStyle ( fontFamily: ' Eurostile Round ' , fontSize: 16 , color: Colors . white ) ,
2023-10-07 16:44:54 +00:00
text: t . newsParts . badgeStart ,
children: [
TextSpan ( text: " ${ news . data [ " label " ] } " , style: const TextStyle ( fontWeight: FontWeight . bold ) ) ,
TextSpan ( text: t . newsParts . badgeEnd )
]
)
) ,
2024-06-11 16:30:13 +00:00
subtitle: Text ( timestamp ( news . timestamp ) ) ,
2023-10-08 17:20:42 +00:00
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 ) ;
} ,
) ,
2023-10-07 16:44:54 +00:00
) ;
case " rankup " :
return ListTile (
title: RichText (
text: TextSpan (
2024-01-22 18:00:24 +00:00
style: const TextStyle ( fontFamily: ' Eurostile Round ' , fontSize: 16 , color: Colors . white ) ,
2023-10-07 16:44:54 +00:00
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 )
]
)
) ,
2024-06-11 16:30:13 +00:00
subtitle: Text ( timestamp ( news . timestamp ) ) ,
2023-10-08 17:20:42 +00:00
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 ) ;
} ,
) ,
2023-10-07 16:44:54 +00:00
) ;
case " supporter " :
return ListTile (
title: RichText (
text: TextSpan (
2024-01-22 18:00:24 +00:00
style: const TextStyle ( fontFamily: ' Eurostile Round ' , fontSize: 16 , color: Colors . white ) ,
2023-10-07 16:44:54 +00:00
text: t . newsParts . supporterStart ,
children: [
2023-10-08 17:20:42 +00:00
TextSpan ( text: t . newsParts . tetoSupporter , style: const TextStyle ( fontWeight: FontWeight . bold ) )
2023-10-07 16:44:54 +00:00
]
)
) ,
2024-06-11 16:30:13 +00:00
subtitle: Text ( timestamp ( news . timestamp ) ) ,
2023-10-08 17:20:42 +00:00
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 ) ;
} ,
) ,
2023-10-07 16:44:54 +00:00
) ;
case " supporter_gift " :
return ListTile (
title: RichText (
text: TextSpan (
2024-01-22 18:00:24 +00:00
style: const TextStyle ( fontFamily: ' Eurostile Round ' , fontSize: 16 , color: Colors . white ) ,
2023-10-07 16:44:54 +00:00
text: t . newsParts . supporterGiftStart ,
children: [
2023-10-08 17:20:42 +00:00
TextSpan ( text: t . newsParts . tetoSupporter , style: const TextStyle ( fontWeight: FontWeight . bold ) )
2023-10-07 16:44:54 +00:00
]
)
) ,
2024-06-11 16:30:13 +00:00
subtitle: Text ( timestamp ( news . timestamp ) ) ,
2023-10-08 17:20:42 +00:00
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 ) ;
} ,
) ,
2023-10-07 16:44:54 +00:00
) ;
2024-01-26 20:56:24 +00:00
default : // if type is unknown
2023-10-07 16:44:54 +00:00
return ListTile (
title: Text ( t . newsParts . unknownNews ( type: news . type ) ) ,
2024-06-11 16:30:13 +00:00
subtitle: Text ( timestamp ( news . timestamp ) ) ,
2023-10-07 16:44:54 +00:00
) ;
}
}
2024-03-17 23:15:44 +00:00
Widget getShit ( BuildContext context , bool bigScreen , bool showNewsTitle ) {
return Column (
children: [
if ( distinguishment ! = null )
Padding (
padding: const EdgeInsets . fromLTRB ( 0 , 0 , 0 , 48 ) ,
child: Column (
children: [
Text ( t . distinguishment , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: bigScreen ? 42 : 28 ) , textAlign: TextAlign . center ) ,
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 ) ,
] ,
) ,
) ,
if ( bio ! = null )
Padding (
padding: const EdgeInsets . fromLTRB ( 8 , 0 , 8 , 48 ) ,
child: Column (
children: [
Text ( t . bio , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: bigScreen ? 42 : 28 ) ) ,
MarkdownBody ( data: bio ! , styleSheet: MarkdownStyleSheet ( textScaleFactor: 1.5 , textAlign: WrapAlignment . center ) ) // Text(bio!, style: const TextStyle(fontSize: 18)),
] ,
) ,
) ,
if ( zen ! = null )
Padding (
padding: const EdgeInsets . fromLTRB ( 0 , 0 , 0 , 48 ) ,
child: Column (
children: [
Text ( t . zen , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: bigScreen ? 42 : 28 ) ) ,
Text ( " ${ t . statCellNum . level } ${ NumberFormat . decimalPattern ( ) . format ( zen ! . level ) } " , style: const TextStyle ( fontSize: 28 , fontWeight: FontWeight . bold ) ) ,
Text ( " ${ t . statCellNum . score } ${ NumberFormat . decimalPattern ( ) . format ( zen ! . score ) } " , style: const TextStyle ( fontSize: 18 ) ) ,
2024-07-29 20:58:17 +00:00
Container (
2024-08-03 17:52:20 +00:00
constraints: const BoxConstraints ( maxWidth: 300.0 ) ,
2024-07-29 20:58:17 +00:00
child: Row ( children: [
2024-08-03 17:52:20 +00:00
const Text ( " Score requirement to level up: " ) ,
const Spacer ( ) ,
2024-07-29 20:58:17 +00:00
Text ( intf . format ( zen ! . scoreRequirement ) )
] , ) ,
)
2024-03-17 23:15:44 +00:00
] ,
) ,
) ,
2024-06-03 21:06:00 +00:00
if ( newsletter ! = null & & newsletter ! . news . isNotEmpty & & showNewsTitle )
2024-03-17 23:15:44 +00:00
Text ( t . news , style: TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: bigScreen ? 42 : 28 ) ) ,
] ,
) ;
}
2023-06-17 21:50:52 +00:00
@ override
Widget build ( BuildContext context ) {
return LayoutBuilder ( builder: ( context , constraints ) {
bool bigScreen = constraints . maxWidth > 768 ;
2024-03-17 23:15:44 +00:00
if ( constraints . maxWidth > = 1024 ) {
return Row (
children: [
SizedBox ( width: 450 , child: getShit ( context , true , false ) ) ,
SizedBox ( width: constraints . maxWidth - 450 , child: ListView . builder (
physics: const AlwaysScrollableScrollPhysics ( ) ,
2024-06-03 21:06:00 +00:00
itemCount: newsletter ! . news . length + 1 ,
2024-03-17 23:15:44 +00:00
itemBuilder: ( BuildContext context , int index ) {
2024-06-03 21:06:00 +00:00
return index = = 0 ? Center ( child: Text ( t . news , style: const TextStyle ( fontFamily: " Eurostile Round Extended " , fontSize: 42 ) ) ) : getNewsTile ( newsletter ! . news [ index - 1 ] ) ;
2024-03-17 23:15:44 +00:00
}
) )
]
) ;
}
else {
return ListView . builder (
2023-06-26 17:13:53 +00:00
physics: const AlwaysScrollableScrollPhysics ( ) ,
2024-06-03 21:06:00 +00:00
itemCount: newsletter ! . news . length + 1 ,
2023-06-17 21:50:52 +00:00
itemBuilder: ( BuildContext context , int index ) {
2024-06-03 21:06:00 +00:00
return index = = 0 ? getShit ( context , bigScreen , true ) : getNewsTile ( newsletter ! . news [ index - 1 ] ) ;
2023-06-17 21:50:52 +00:00
} ,
) ;
2024-03-17 23:15:44 +00:00
}
2023-06-17 21:50:52 +00:00
} ) ;
}
}