diff --git a/lib/data_objects/player_leaderboard_position.dart b/lib/data_objects/player_leaderboard_position.dart index 4a79fa9..3f76ccc 100644 --- a/lib/data_objects/player_leaderboard_position.dart +++ b/lib/data_objects/player_leaderboard_position.dart @@ -9,6 +9,7 @@ class PlayerLeaderboardPosition{ late LeaderboardPosition? gamesPlayed; late LeaderboardPosition? gamesWon; late LeaderboardPosition? winrate; + late LeaderboardPosition? glixare; late LeaderboardPosition? app; late LeaderboardPosition? vsapm; late LeaderboardPosition? dss; @@ -28,6 +29,7 @@ class PlayerLeaderboardPosition{ required this.gamesPlayed, required this.gamesWon, required this.winrate, + required this.glixare, required this.app, required this.vsapm, required this.dss, @@ -48,16 +50,17 @@ class PlayerLeaderboardPosition{ gamesPlayed = results[3]; gamesWon = results[4]; winrate = results[5]; - app = results[6]; - vsapm = results[7]; - dss = results[8]; - dsp = results[9]; - appdsp = results[10]; - cheese = results[11]; - gbe = results[12]; - nyaapp = results[13]; - area = results[14]; - estTr = results[15]; - accOfEst = results[16]; + glixare = results[6]; + app = results[7]; + vsapm = results[8]; + dss = results[9]; + dsp = results[10]; + appdsp = results[11]; + cheese = results[12]; + gbe = results[13]; + nyaapp = results[14]; + area = results[15]; + estTr = results[16]; + accOfEst = results[17]; } } diff --git a/lib/data_objects/tetrio_players_leaderboard.dart b/lib/data_objects/tetrio_players_leaderboard.dart index 86c0086..91b8aa4 100644 --- a/lib/data_objects/tetrio_players_leaderboard.dart +++ b/lib/data_objects/tetrio_players_leaderboard.dart @@ -684,7 +684,7 @@ class TetrioPlayersLeaderboard { copyOfLeaderboard = List.of(leaderboard); copyOfLeaderboard.add(league.values.first.convertToPlayerFromLeaderboard(league.keys.first)); } - List stats = [Stats.apm, Stats.pps, Stats.vs, Stats.gp, Stats.gw, Stats.wr, + List stats = [Stats.apm, Stats.pps, Stats.vs, Stats.gp, Stats.gw, Stats.wr, Stats.gxe, Stats.app, Stats.vsapm, Stats.dss, Stats.dsp, Stats.appdsp, Stats.cheese, Stats.gbe, Stats.nyaapp, Stats.area, Stats.eTR, Stats.acceTR]; List results = []; for (Stats stat in stats) { diff --git a/lib/services/sqlite_db_controller.dart b/lib/services/sqlite_db_controller.dart index b1bb348..2d2a87a 100644 --- a/lib/services/sqlite_db_controller.dart +++ b/lib/services/sqlite_db_controller.dart @@ -86,4 +86,24 @@ class DB { var newDBStats = await dbFile.stat(); return dbStats.size - newDBStats.size; } + + Future checkImportingDB(File db) async { + final newDB = await openDatabase(db.path); + var usersTable = await newDB.rawQuery("PRAGMA table_xinfo(`${tetrioUsersTable}`);"); + List usersTableRows = [for (Map row in usersTable) row["name"] as String]; + if (!listEquals(usersTableRows, tetrioUsersTableRows)) return false; + var usersToTrackTable = await newDB.rawQuery("PRAGMA table_xinfo(`${tetrioUsersToTrackTable}`);"); + List usersToTrackTableRows = [for (Map row in usersToTrackTable) row["name"] as String]; + if (!listEquals(usersToTrackTableRows, tetrioUsersToTrackTableRows)) return false; + var leagueMatchesTable = await newDB.rawQuery("PRAGMA table_xinfo(`${tetraLeagueMatchesTable}`);"); + List leagueMatchesTableRows = [for (Map row in leagueMatchesTable) row["name"] as String]; + if (!listEquals(leagueMatchesTableRows, tetraLeagueMatchesTableRows)) return false; + var tlReplayStatsTable = await newDB.rawQuery("PRAGMA table_xinfo(`${tetrioTLReplayStatsTable}`);"); + List TLReplayStatsTableRows = [for (Map row in tlReplayStatsTable) row["name"] as String]; + if (!listEquals(TLReplayStatsTableRows, tetrioTLReplayStatsTableRows)) return false; + var leagueTable = await newDB.rawQuery("PRAGMA table_xinfo(`${tetrioLeagueTable}`);"); + List leagueTableRows = [for (Map row in leagueTable) row["name"] as String]; + if (!listEquals(leagueTableRows, tetrioLeagueTableRows)) return false; + return true; + } } diff --git a/lib/services/tetrio_crud.dart b/lib/services/tetrio_crud.dart index f26c14f..730d934 100644 --- a/lib/services/tetrio_crud.dart +++ b/lib/services/tetrio_crud.dart @@ -49,6 +49,11 @@ const String endContext2 = "endContext2"; const String statesCol = "jsonStates"; const String player1id = "player1id"; const String player2id = "player2id"; +const List tetrioUsersTableRows = [idCol, nickCol, "jsonStates"]; +const List tetrioUsersToTrackTableRows = [idCol]; +const List tetraLeagueMatchesTableRows = [idCol, replayID, player1id, player2id, timestamp, endContext1, endContext2]; +const List tetrioTLReplayStatsTableRows = [idCol, "data", "freyhoe"]; +const List tetrioLeagueTableRows = [idCol, "gamesplayed", "gameswon", "tr", "glicko", "rd", "gxe", "rank", "bestrank", "apm", "pps", "vs", "decaying", "standing", "standing_local", "percentile", "prev_rank", "prev_at", "next_rank", "next_at", "percentile_rank", "season"]; /// Table, that store players data, their stats at some moments of time const String createTetrioUsersTable = ''' CREATE TABLE IF NOT EXISTS "tetrioUsers" ( diff --git a/lib/views/destination_calculator.dart b/lib/views/destination_calculator.dart index 18629ef..c5c98cd 100644 --- a/lib/views/destination_calculator.dart +++ b/lib/views/destination_calculator.dart @@ -510,7 +510,7 @@ class _DestinationCalculatorState extends State { Text("PC's: ${intf.format(pcDamage)}") ], ), - SfLinearGauge( + if (totalDamage > 0) SfLinearGauge( minimum: 0, maximum: totalDamage.toDouble(), showLabels: false, diff --git a/lib/views/destination_cutoffs.dart b/lib/views/destination_cutoffs.dart index a3037d6..8321033 100644 --- a/lib/views/destination_cutoffs.dart +++ b/lib/views/destination_cutoffs.dart @@ -240,10 +240,10 @@ class _DestinationCutoffsState extends State { style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100, color: Colors.white, shadows: textShadow), children: [ if (rank == "x+") TextSpan(text: "№ 1 is ${f2.format(snapshot.data!.data["top1"]!.tr)} TR", style: const TextStyle(color: Colors.white60, shadows: null)) - else TextSpan(text: snapshot.data!.data[ranks[ranks.indexOf(rank)+1]]!.tr > snapshot.data!.data[ranks[ranks.indexOf(rank)+1]]!.targetTr ? "Inflated from ${NumberFormat.compact().format(snapshot.data!.data[ranks[ranks.indexOf(rank)+1]]!.targetTr)} TR" : "Not inflated", style: TextStyle(color: snapshot.data!.data[ranks[ranks.indexOf(rank)+1]]!.tr > snapshot.data!.data[ranks[ranks.indexOf(rank)+1]]!.targetTr ? Colors.white :Colors.white60, shadows: null)), + else TextSpan(text: snapshot.data!.data[ranks[ranks.indexOf(rank)+1]]!.tr > snapshot.data!.data[ranks[ranks.indexOf(rank)+1]]!.targetTr ? "Inflated on ${f2.format(snapshot.data!.data[ranks[ranks.indexOf(rank)+1]]!.tr - snapshot.data!.data[ranks[ranks.indexOf(rank)+1]]!.targetTr)} TR" : "Not inflated", style: TextStyle(color: snapshot.data!.data[ranks[ranks.indexOf(rank)+1]]!.tr > snapshot.data!.data[ranks[ranks.indexOf(rank)+1]]!.targetTr ? Colors.white :Colors.white60, shadows: null)), TextSpan(text: "\n", style: const TextStyle(color: Colors.white60, shadows: null)), if (rank == "d") TextSpan(text: "Well...", style: const TextStyle(color: Colors.white60, shadows: null)) - else TextSpan(text: snapshot.data!.data[rank]!.tr < snapshot.data!.data[rank]!.targetTr ? "Deflated untill ${NumberFormat.compact().format(snapshot.data!.data[rank]!.targetTr)} TR" : "Not deflated", style: TextStyle(color: snapshot.data!.data[rank]!.tr < snapshot.data!.data[rank]!.targetTr ? Colors.white : Colors.white60, shadows: null)) + else TextSpan(text: snapshot.data!.data[rank]!.tr < snapshot.data!.data[rank]!.targetTr ? "Deflated on ${f2.format(snapshot.data!.data[rank]!.targetTr - snapshot.data!.data[rank]!.tr)} TR" : "Not deflated", style: TextStyle(color: snapshot.data!.data[rank]!.tr < snapshot.data!.data[rank]!.targetTr ? Colors.white : Colors.white60, shadows: null)) ] )), ), diff --git a/lib/views/destination_home.dart b/lib/views/destination_home.dart index 727e01c..47cbd7b 100644 --- a/lib/views/destination_home.dart +++ b/lib/views/destination_home.dart @@ -6,12 +6,12 @@ import 'package:intl/intl.dart'; import 'package:tetra_stats/data_objects/cutoff_tetrio.dart'; import 'package:tetra_stats/data_objects/news.dart'; import 'package:tetra_stats/data_objects/p1nkl0bst3r.dart'; +import 'package:tetra_stats/data_objects/player_leaderboard_position.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/singleplayer_stream.dart'; import 'package:tetra_stats/data_objects/summaries.dart'; import 'package:tetra_stats/data_objects/tetra_league.dart'; -import 'package:tetra_stats/data_objects/tetra_league_beta_stream.dart'; import 'package:tetra_stats/data_objects/tetrio_constants.dart'; import 'package:tetra_stats/data_objects/tetrio_player.dart'; import 'package:tetra_stats/gen/strings.g.dart'; @@ -32,8 +32,9 @@ class DestinationHome extends StatefulWidget{ final Future dataFuture; final Future? newsFuture; final BoxConstraints constraints; + final bool noSidebar; - const DestinationHome({super.key, required this.searchFor, required this.dataFuture, this.newsFuture, required this.constraints}); + const DestinationHome({super.key, required this.searchFor, required this.dataFuture, this.newsFuture, required this.constraints, this.noSidebar = false}); @override State createState() => _DestinationHomeState(); @@ -46,10 +47,11 @@ class FetchResults{ Summaries? summaries; Cutoffs? cutoffs; CutoffsTetrio? averages; + PlayerLeaderboardPosition? playerPos; bool isTracked; Exception? exception; - FetchResults(this.success, this.player, this.states, this.summaries, this.cutoffs, this.averages, this.isTracked, this.exception); + FetchResults(this.success, this.player, this.states, this.summaries, this.cutoffs, this.averages, this.playerPos, this.isTracked, this.exception); } class RecordSummary extends StatelessWidget{ @@ -389,7 +391,7 @@ class _DestinationHomeState extends State with SingleTickerProv ); } - Widget getTetraLeagueCard(TetraLeague data, Cutoffs? cutoffs, CutoffTetrio? averages, List states){ + Widget getTetraLeagueCard(TetraLeague data, Cutoffs? cutoffs, CutoffTetrio? averages, List states, PlayerLeaderboardPosition? lbPos){ TetraLeague? toCompare = states.length >= 2 ? states.elementAtOrNull(states.length-2) : null; return Column( children: [ @@ -409,7 +411,7 @@ class _DestinationHomeState extends State with SingleTickerProv ), ), ), - TetraLeagueThingy(league: data, toCompare: toCompare, cutoffs: cutoffs, averages: averages), + TetraLeagueThingy(league: data, toCompare: toCompare, cutoffs: cutoffs, averages: averages, lbPos: lbPos), if (data.nerdStats != null) Card( //surfaceTintColor: rankColors[data.rank], child: Row( @@ -604,26 +606,6 @@ class _DestinationHomeState extends State with SingleTickerProv ), ), TLRecords(userID), - // Card( - // clipBehavior: Clip.antiAlias, - // child: FutureBuilder( - // future: teto.fetchTLStream(widget.searchFor), - // builder: (context, snapshot) { - // switch (snapshot.connectionState){ - // case ConnectionState.none: - // case ConnectionState.waiting: - // case ConnectionState.active: - // return const Center(child: CircularProgressIndicator()); - // case ConnectionState.done: - // if (snapshot.hasData){ - // return SizedBox(height: constraints.maxHeight - 145, child: TLRecords(userID: userID, changePlayer: (){}, data: snapshot.data!.records, wasActiveInTL: snapshot.data!.records.isNotEmpty, oldMathcesHere: false)); - // } - // if (snapshot.hasError){ return FutureError(snapshot); } - // } - // return const Text("what?"); - // }, - // ), - // ), ], ); } @@ -1080,7 +1062,7 @@ class _DestinationHomeState extends State with SingleTickerProv ), ), SizedBox( - width: widget.constraints.maxWidth - 450 - 80, + width: widget.noSidebar ? widget.constraints.maxWidth - 450 : widget.constraints.maxWidth - 530, child: Column( children: [ SizedBox( @@ -1091,7 +1073,7 @@ class _DestinationHomeState extends State with SingleTickerProv child: switch (rightCard){ Cards.overview => getOverviewCard(snapshot.data!.summaries!, (snapshot.data!.averages != null && snapshot.data!.summaries!.league.rank != "z") ? snapshot.data!.averages!.data[snapshot.data!.summaries!.league.rank] : (snapshot.data!.averages != null && snapshot.data!.summaries!.league.percentileRank != "z") ? snapshot.data!.averages!.data[snapshot.data!.summaries!.league.percentileRank] : null), Cards.tetraLeague => switch (cardMod){ - CardMod.info => getTetraLeagueCard(snapshot.data!.summaries!.league, snapshot.data!.cutoffs, (snapshot.data!.averages != null && snapshot.data!.summaries!.league.rank != "z") ? snapshot.data!.averages!.data[snapshot.data!.summaries!.league.rank] : (snapshot.data!.averages != null && snapshot.data!.summaries!.league.percentileRank != "z") ? snapshot.data!.averages!.data[snapshot.data!.summaries!.league.percentileRank] : null, snapshot.data!.states), + CardMod.info => getTetraLeagueCard(snapshot.data!.summaries!.league, snapshot.data!.cutoffs, (snapshot.data!.averages != null && snapshot.data!.summaries!.league.rank != "z") ? snapshot.data!.averages!.data[snapshot.data!.summaries!.league.rank] : (snapshot.data!.averages != null && snapshot.data!.summaries!.league.percentileRank != "z") ? snapshot.data!.averages!.data[snapshot.data!.summaries!.league.percentileRank] : null, snapshot.data!.states, snapshot.data!.playerPos), CardMod.ex => getPreviousSeasonsList(snapshot.data!.summaries!.pastLeague), CardMod.records => getRecentTLrecords(widget.constraints, snapshot.data!.player!.userId), _ => const Center(child: Text("huh?")) diff --git a/lib/views/destination_leaderboards.dart b/lib/views/destination_leaderboards.dart index 8862e69..e95b987 100644 --- a/lib/views/destination_leaderboards.dart +++ b/lib/views/destination_leaderboards.dart @@ -8,7 +8,6 @@ import 'package:tetra_stats/main.dart'; import 'package:tetra_stats/utils/numers_formats.dart'; import 'package:tetra_stats/utils/relative_timestamps.dart'; import 'package:tetra_stats/views/main_view_tiles.dart'; -import 'package:tetra_stats/views/tl_leaderboard_view.dart'; import 'package:tetra_stats/views/user_view.dart'; class DestinationLeaderboards extends StatefulWidget{ @@ -49,6 +48,7 @@ class _DestinationLeaderboardsState extends State { Stream> get dataStream => _dataStreamController.stream; List list = []; bool _isFetchingData = false; + bool _reachedTheEnd = false; List _excludeRanks = []; bool _reverse = false; String? prisecter; @@ -64,7 +64,7 @@ class _DestinationLeaderboardsState extends State { } Future _fetchData() async { - if (_isFetchingData) { + if (_isFetchingData || _reachedTheEnd) { // Avoid fetching new data while already fetching return; } @@ -84,7 +84,7 @@ class _DestinationLeaderboardsState extends State { }; if (_currentLb == Leaderboards.fullTL && _excludeRanks.isNotEmpty) items.removeWhere((e) => _excludeRanks.indexOf((e as TetrioPlayerFromLeaderboard).rank) != -1); - + if (_currentLb == Leaderboards.fullTL || items.isEmpty) _reachedTheEnd = true; list.addAll((_reverse && _currentLb == Leaderboards.fullTL) ? items.reversed : items); _dataStreamController.add(list); @@ -150,6 +150,7 @@ class _DestinationLeaderboardsState extends State { _currentLb = leaderboards.keys.elementAt(index); list.clear(); prisecter = null; + _reachedTheEnd = false; _fetchData(); }, ), @@ -192,6 +193,7 @@ class _DestinationLeaderboardsState extends State { list.clear(); prisecter = null; _isFetchingData = false; + _reachedTheEnd = false; setState((){_fetchData();}); }) ), @@ -209,6 +211,7 @@ class _DestinationLeaderboardsState extends State { list.clear(); prisecter = null; _isFetchingData = false; + _reachedTheEnd = false; setState((){_fetchData();}); }) ), @@ -266,7 +269,7 @@ class _DestinationLeaderboardsState extends State { }, icon: Icon(Icons.filter_alt)), if (_currentLb == Leaderboards.fullTL) IconButton( color: _reverse ? Theme.of(context).colorScheme.primary : null, - icon: Container(transform: _reverse ? Matrix4.rotationX(pi) : null, child: Icon(Icons.filter_list)), + icon: Transform.rotate(angle: _reverse ? pi : 0.0, child: Icon(Icons.filter_list)), onPressed: (){ setState((){ _reverse = !_reverse; @@ -302,13 +305,21 @@ class _DestinationLeaderboardsState extends State { Image.asset("res/tetrio_tl_alpha_ranks/${snapshot.data![index].rank}.png", height: 36) ], ), - Leaderboards.fullTL => Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text("${f2.format(snapshot.data![index].tr)} TR", style: trailingStyle), - Image.asset("res/tetrio_tl_alpha_ranks/${snapshot.data![index].rank}.png", height: 36) - ], - ), + Leaderboards.fullTL => switch (stat) { + Stats.tr => Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text("${f2.format(snapshot.data![index].tr)} TR", style: trailingStyle), + Image.asset("res/tetrio_tl_alpha_ranks/${snapshot.data![index].rank}.png", height: 36) + ], + ), + Stats.gp => Text("${intf.format(snapshot.data![index].getStatByEnum(stat))} ${chartsShortTitles[stat]}", style: trailingStyle), + Stats.gw => Text("${intf.format(snapshot.data![index].getStatByEnum(stat))} ${chartsShortTitles[stat]}", style: trailingStyle), + Stats.apm => Text("${f2.format(snapshot.data![index].getStatByEnum(stat))} ${chartsShortTitles[stat]}", style: trailingStyle), + Stats.pps => Text("${f2.format(snapshot.data![index].getStatByEnum(stat))} ${chartsShortTitles[stat]}", style: trailingStyle), + Stats.vs => Text("${f2.format(snapshot.data![index].getStatByEnum(stat))} ${chartsShortTitles[stat]}", style: trailingStyle), + _ => Text("${f4.format(snapshot.data![index].getStatByEnum(stat))} ${chartsShortTitles[stat]}", style: trailingStyle) + }, Leaderboards.xp => Text("LVL ${f2.format(snapshot.data![index].level)}", style: trailingStyle), Leaderboards.ar => Text("${intf.format(snapshot.data![index].ar)} AR", style: trailingStyle), Leaderboards.sprint => Text(get40lTime(snapshot.data![index].stats.finalTime.inMicroseconds), style: trailingStyle), @@ -318,7 +329,10 @@ class _DestinationLeaderboardsState extends State { }, subtitle: Text(switch (_currentLb){ Leaderboards.tl => "${f2.format(snapshot.data![index].apm)} APM, ${f2.format(snapshot.data![index].pps)} PPS, ${f2.format(snapshot.data![index].vs)} VS, ${f2.format(snapshot.data![index].nerdStats.app)} APP, ${f2.format(snapshot.data![index].nerdStats.vsapm)} VS/APM", - Leaderboards.fullTL => "${f2.format(snapshot.data![index].apm)} APM, ${f2.format(snapshot.data![index].pps)} PPS, ${f2.format(snapshot.data![index].vs)} VS, ${f2.format(snapshot.data![index].nerdStats.app)} APP, ${f2.format(snapshot.data![index].nerdStats.vsapm)} VS/APM", + Leaderboards.fullTL => switch (stat) { + Stats.tr => "${f2.format(snapshot.data![index].apm)} APM, ${f2.format(snapshot.data![index].pps)} PPS, ${f2.format(snapshot.data![index].vs)} VS, ${f2.format(snapshot.data![index].nerdStats.app)} APP, ${f2.format(snapshot.data![index].nerdStats.vsapm)} VS/APM", + _ => "${f2.format(snapshot.data![index].tr)} TR, ${snapshot.data![index].rank.toUpperCase()} rank" + }, Leaderboards.xp => "${f2.format(snapshot.data![index].xp)} XP${snapshot.data![index].playtime.isNegative ? "" : ", ${playtime(snapshot.data![index].playtime)} of gametime"}", Leaderboards.ar => "${snapshot.data![index].ar_counts}", Leaderboards.sprint => "${intf.format(snapshot.data![index].stats.finesse.faults)} FF, ${f2.format(snapshot.data![index].stats.kpp)} KPP, ${f2.format(snapshot.data![index].stats.kps)} KPS, ${f2.format(snapshot.data![index].stats.pps)} PPS, ${intf.format(snapshot.data![index].stats.piecesPlaced)} P", diff --git a/lib/views/destination_settings.dart b/lib/views/destination_settings.dart index 8c2af2f..30dfc28 100644 --- a/lib/views/destination_settings.dart +++ b/lib/views/destination_settings.dart @@ -1,8 +1,12 @@ import 'dart:async'; import 'dart:io'; +import 'package:file_picker/file_picker.dart'; +import 'package:file_selector/file_selector.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_colorpicker/flutter_colorpicker.dart'; import 'package:intl/intl.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:tetra_stats/data_objects/tetrio_player.dart'; import 'package:tetra_stats/gen/strings.g.dart'; import 'package:tetra_stats/main.dart'; @@ -486,11 +490,118 @@ class _DestinationSettings extends State with SingleTickerP Card( child: ListTile( title: Text("Export Database", style: Theme.of(context).textTheme.displayLarge), + onTap: () { + if (kIsWeb){ + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.notForWeb))); + } else if (Platform.isAndroid){ + var downloadFolder = Directory("/storage/emulated/0/Download"); + File exportedDB = File("${downloadFolder.path}/TetraStats.db"); + getApplicationDocumentsDirectory().then((value) { + exportedDB.writeAsBytes(File("${value.path}/TetraStats.db").readAsBytesSync()); + showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + title: Text(t.androidExportAlertTitle, + style: const TextStyle( + fontFamily: "Eurostile Round Extended")), + content: SingleChildScrollView( + child: ListBody(children: [Text(t.androidExportText(exportedDB: exportedDB))]), + ), + actions: [ + TextButton( + child: Text(t.popupActions.ok), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + )); + }); + } else if (Platform.isLinux || Platform.isWindows) { + showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + title: Text(t.desktopExportAlertTitle, + style: const TextStyle( + fontFamily: "Eurostile Round Extended")), + content: SingleChildScrollView( + child: ListBody(children: [ + Text(t.desktopExportText) + ]), + ), + actions: [ + TextButton( + child: Text(t.popupActions.ok), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + )); + } + } ), ), Card( child: ListTile( title: Text("Import Database", style: Theme.of(context).textTheme.displayLarge), + onTap: (){ + if (kIsWeb){ + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.notForWeb))); + }else if(Platform.isAndroid){ + FilePicker.platform.pickFiles( + type: FileType.any, + ).then((value){ + if (value != null){ + var newDB = value.paths[0]!; + teto.checkImportingDB(File(newDB)).then((v){ + if (v){ + teto.close().then((value){ + getApplicationDocumentsDirectory().then((value){ + var oldDB = File("${value.path}/TetraStats.db"); + oldDB.writeAsBytes(File(newDB).readAsBytesSync()).then((value){ + teto.open(); + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.importSuccess))); + }); + }); + }); + }else{ + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Import Failed: Wrong database sheme"))); + } + }); + } else { + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.importCancelled))); + } + }); + }else{ + const XTypeGroup typeGroup = XTypeGroup( + label: 'Tetra Stats Database', + extensions: ['db'], + ); + openFile(acceptedTypeGroups: [typeGroup]).then((value){ + if (value != null){ + var newDB = value.path; + teto.checkImportingDB(File(newDB)).then((v){ + if (v){ + teto.close().then((value){ + getApplicationDocumentsDirectory().then((value){ + var oldDB = File("${value.path}/TetraStats.db"); + oldDB.writeAsBytes(File(newDB).readAsBytesSync()).then((value){ + teto.open(); + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.importSuccess))); + }); + }); + }); + }else{ + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Import Failed: Wrong database sheme"))); + } + }); + } else { + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.importCancelled))); + } + }); + } + }, ), ) ], diff --git a/lib/views/main_view_tiles.dart b/lib/views/main_view_tiles.dart index 000a534..4367135 100644 --- a/lib/views/main_view_tiles.dart +++ b/lib/views/main_view_tiles.dart @@ -16,6 +16,7 @@ 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/player_leaderboard_position.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'; @@ -24,6 +25,7 @@ import 'package:tetra_stats/data_objects/tetra_league.dart'; import 'package:tetra_stats/data_objects/tetra_league_alpha_record.dart'; import 'package:tetra_stats/data_objects/tetrio_constants.dart'; import 'package:tetra_stats/data_objects/tetrio_player.dart'; +import 'package:tetra_stats/data_objects/tetrio_players_leaderboard.dart'; import 'package:tetra_stats/gen/strings.g.dart'; import 'package:tetra_stats/services/crud_exceptions.dart'; import 'package:tetra_stats/utils/colors_functions.dart'; @@ -53,6 +55,7 @@ import 'package:vector_math/vector_math_64.dart' hide Colors; var fDiff = NumberFormat("+#,###.####;-#,###.####"); late Future _data; late Future _newsData; +TetrioPlayersLeaderboard? _everyone; Future getData(String searchFor) async { TetrioPlayer player; @@ -64,23 +67,33 @@ Future getData(String searchFor) async { } }on TetrioPlayerNotExist{ - return FetchResults(false, null, [], null, null, null, false, TetrioPlayerNotExist()); + return FetchResults(false, null, [], null, null, null, null, false, TetrioPlayerNotExist()); } late Summaries summaries; - late Cutoffs cutoffs; - late CutoffsTetrio averages; + late Cutoffs? cutoffs; + late CutoffsTetrio? averages; try { List requests = await Future.wait([ teto.fetchSummaries(player.userId), teto.fetchCutoffsBeanserver(), - teto.fetchCutoffsTetrio() + if (prefs.getBool("showAverages") == true) teto.fetchCutoffsTetrio() ]); summaries = requests[0]; cutoffs = requests.elementAtOrNull(1); averages = requests.elementAtOrNull(2); } on Exception catch (e) { - return FetchResults(false, null, [], null, null, null, false, e); + return FetchResults(false, null, [], null, null, null, null, false, e); + } + PlayerLeaderboardPosition? _meAmongEveryone; + if (prefs.getBool("showPositions") == true){ + // Get tetra League leaderboard + _everyone = teto.getCachedLeaderboard(); + _everyone ??= await teto.fetchTLLeaderboard(); + if (_everyone!.leaderboard.isNotEmpty){ + _meAmongEveryone = await compute(_everyone!.getLeaderboardPosition, {player.userId: summaries.league}); + if (_meAmongEveryone != null) teto.cacheLeaderboardPositions(player.userId, _meAmongEveryone); + } } List states = await teto.getStates(player.userId, season: currentSeason); @@ -89,7 +102,7 @@ Future getData(String searchFor) async { await teto.storeState(summaries.league); } - return FetchResults(true, player, states, summaries, cutoffs, averages, isTracking, null); + return FetchResults(true, player, states, summaries, cutoffs, averages, _meAmongEveryone, isTracking, null); } class MainView extends StatefulWidget { @@ -579,7 +592,10 @@ class DistinguishmentThingy extends StatelessWidget{ children: getDistinguishmentTitle(distinguishment.header), ), ), - Text(getDistinguishmentSubtitle(distinguishment.footer), style: Theme.of(context).textTheme.displayLarge, textAlign: TextAlign.center), + Padding( + padding: const EdgeInsets.fromLTRB(8.0, 4.0, 8.0, 4.0), + child: Text(getDistinguishmentSubtitle(distinguishment.footer), style: Theme.of(context).textTheme.displayLarge, textAlign: TextAlign.center), + ), ], ), ); @@ -641,7 +657,10 @@ class FakeDistinguishmentThingy extends StatelessWidget{ ), ), ), - Text(getDistinguishmentSubtitle(), style: Theme.of(context).textTheme.displayLarge, textAlign: TextAlign.center), + Padding( + padding: const EdgeInsets.fromLTRB(8.0, 4.0, 8.0, 4.0), + child: Text(getDistinguishmentSubtitle(), style: Theme.of(context).textTheme.displayLarge, textAlign: TextAlign.center), + ), ], ), ), @@ -1103,7 +1122,7 @@ class _SearchDrawerState extends State { SliverToBoxAdapter( child: SearchBar( controller: widget.controller, - hintText: "Hello", + hintText: "Enter the username", hintStyle: const WidgetStatePropertyAll(TextStyle(color: Colors.grey)), trailing: [ IconButton(onPressed: (){setState(() { @@ -1121,12 +1140,22 @@ class _SearchDrawerState extends State { ), SliverToBoxAdapter( child: ListTile( + leading: Icon(Icons.home), title: Text(prefs.getString("player") ?? "dan63"), onTap: () { widget.changePlayer(prefs.getString("playerID") ?? "6098518e3d5155e6ec429cdc"); Navigator.of(context).pop(); }, ), + ), + SliverToBoxAdapter( + child: Divider(), + ), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.only(left: 10.0), + child: Text("Tracked Players", style: Theme.of(context).textTheme.headlineLarge), + ), ) ]; }, @@ -1136,6 +1165,9 @@ class _SearchDrawerState extends State { 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 + trailing: IconButton(onPressed: (){ + teto.deletePlayerToTrack(keys[i]); + }, icon: Icon(Icons.delete, color: Colors.grey)), onTap: () { widget.changePlayer(keys[i]); // changes to chosen player Navigator.of(context).pop(); // and closes itself. @@ -1155,8 +1187,9 @@ class TetraLeagueThingy extends StatelessWidget{ final TetraLeague? toCompare; final Cutoffs? cutoffs; final CutoffTetrio? averages; + final PlayerLeaderboardPosition? lbPos; - const TetraLeagueThingy({super.key, required this.league, this.toCompare, this.cutoffs, this.averages}); + const TetraLeagueThingy({super.key, required this.league, this.toCompare, this.cutoffs, this.averages, this.lbPos}); @override Widget build(BuildContext context) { @@ -1182,22 +1215,27 @@ class TetraLeagueThingy extends StatelessWidget{ Expanded( child: Center( child: Table( + defaultVerticalAlignment: TableCellVerticalAlignment.baseline, + textBaseline: TextBaseline.alphabetic, defaultColumnWidth:const IntrinsicColumnWidth(), children: [ TableRow(children: [ Text(league.apm != null ? f2.format(league.apm) : "-.--", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: league.apm != null ? getStatColor(league.apm!, averages?.apm, true) : Colors.grey)), Text(" APM", style: TextStyle(fontSize: 21, color: league.apm != null ? getStatColor(league.apm!, averages?.apm, true) : Colors.grey)), - if (toCompare != null) Text(" (${comparef2.format(league.apm!-toCompare!.apm!)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: getDifferenceColor(league.apm!-toCompare!.apm!))) + if (toCompare != null) Text(" (${comparef2.format(league.apm!-toCompare!.apm!)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: getDifferenceColor(league.apm!-toCompare!.apm!))), + if (lbPos != null) Text(lbPos?.apm != null ? (lbPos!.apm!.position >= 1000 ? " (${t.top} ${f2.format(lbPos!.apm!.percentage*100)}%)" : " (№ ${lbPos!.apm!.position})") : "(№ ---)", style: TextStyle(color: lbPos?.apm != null ? getColorOfRank(lbPos!.apm!.position) : null)) ]), TableRow(children: [ Text(league.pps != null ? f2.format(league.pps) : "-.--", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: league.pps != null ? getStatColor(league.pps!, averages?.pps, true) : Colors.grey)), Text(" PPS", style: TextStyle(fontSize: 21, color: league.pps != null ? getStatColor(league.pps!, averages?.pps, true) : Colors.grey)), - if (toCompare != null) Text(" (${comparef2.format(league.pps!-toCompare!.pps!)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: getDifferenceColor(league.pps!-toCompare!.pps!))) + if (toCompare != null) Text(" (${comparef2.format(league.pps!-toCompare!.pps!)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: getDifferenceColor(league.pps!-toCompare!.pps!))), + if (lbPos != null) Text(lbPos?.pps != null ? (lbPos!.pps!.position >= 1000 ? " (${t.top} ${f2.format(lbPos!.pps!.percentage*100)}%)" : " (№ ${lbPos!.pps!.position})") : "(№ ---)", style: TextStyle(color: lbPos?.pps != null ? getColorOfRank(lbPos!.pps!.position) : null)) ]), TableRow(children: [ Text(league.vs != null ? f2.format(league.vs) : "-.--", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: league.vs != null ? getStatColor(league.vs!, averages?.vs, true) : Colors.grey)), Text(" VS", style: TextStyle(fontSize: 21, color: league.vs != null ? getStatColor(league.vs!, averages?.vs, true) : Colors.grey)), - if (toCompare != null) Text(" (${comparef2.format(league.vs!-toCompare!.vs!)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: getDifferenceColor(league.vs!-toCompare!.vs!))) + if (toCompare != null) Text(" (${comparef2.format(league.vs!-toCompare!.vs!)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: getDifferenceColor(league.vs!-toCompare!.vs!))), + if (lbPos != null) Text(lbPos?.vs != null ? (lbPos!.vs!.position >= 1000 ? " (${t.top} ${f2.format(lbPos!.vs!.percentage*100)}%)" : " (№ ${lbPos!.vs!.position})") : "(№ ---)", style: TextStyle(color: lbPos?.vs != null ? getColorOfRank(lbPos!.vs!.position) : null)) ]) ], ), @@ -1207,25 +1245,30 @@ class TetraLeagueThingy extends StatelessWidget{ Expanded( child: Center( child: Table( + defaultVerticalAlignment: TableCellVerticalAlignment.baseline, + textBaseline: TextBaseline.alphabetic, defaultColumnWidth:const IntrinsicColumnWidth(), children: [ TableRow(children: [ //Text("APM: ", style: TextStyle(fontSize: 21)), Text(intf.format(league.gamesPlayed), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), 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)) + if (toCompare != null) Text(" (${comparef2.format(league.gamesPlayed-toCompare!.gamesPlayed)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: Colors.grey)), + if (lbPos != null) Text(lbPos?.gamesPlayed != null ? (lbPos!.gamesPlayed!.position >= 1000 ? " (${t.top} ${f2.format(lbPos!.gamesPlayed!.percentage*100)}%)" : " (№ ${lbPos!.gamesPlayed!.position})") : "(№ ---)", style: TextStyle(color: lbPos?.gamesPlayed != null ? getColorOfRank(lbPos!.gamesPlayed!.position) : null)) ]), TableRow(children: [ //Text("PPS: ", style: TextStyle(fontSize: 21)), Text(intf.format(league.gamesWon), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), 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)) + if (toCompare != null) Text(" (${comparef2.format(league.gamesWon-toCompare!.gamesWon)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: Colors.grey)), + if (lbPos != null) Text(lbPos?.gamesWon != null ? (lbPos!.gamesWon!.position >= 1000 ? " (${t.top} ${f2.format(lbPos!.gamesWon!.percentage*100)}%)" : " (№ ${lbPos!.gamesWon!.position})") : "(№ ---)", style: TextStyle(color: lbPos?.gamesWon != null ? getColorOfRank(lbPos!.gamesWon!.position) : null)) ]), 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))) + Tooltip(child: Text("${league.gxe.isNegative ? "---" : f3.format(league.gxe)}", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: league.gxe.isNegative ? Colors.grey : Colors.white)), message: "${f2.format(league.s1tr)}",), + Text(" GLIXARE", style: TextStyle(fontSize: 21, color: league.gxe.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.gxe-toCompare!.gxe))), + if (lbPos != null) Text(lbPos?.glixare != null ? (lbPos!.glixare!.position >= 1000 ? " (${t.top} ${f2.format(lbPos!.glixare!.percentage*100)}%)" : " (№ ${lbPos!.glixare!.position})") : "(№ ---)", style: TextStyle(color: lbPos?.glixare != null ? getColorOfRank(lbPos!.glixare!.position) : null)) ]), ], ), diff --git a/lib/views/tl_match_view.dart b/lib/views/tl_match_view.dart index 5f7844f..c3229b8 100644 --- a/lib/views/tl_match_view.dart +++ b/lib/views/tl_match_view.dart @@ -571,7 +571,10 @@ class TlMatchResultState extends State { final t = Translations.of(context); return Scaffold( appBar: AppBar( - title: Text("${widget.record.results.leaderboard[greenSidePlayer].username.toUpperCase()} ${t.vs} ${widget.record.results.leaderboard[redSidePlayer].username.toUpperCase()} ${t.inTLmatch} ${widget.record.gamemode} ${timestamp(widget.record.ts)}"), + title: Text( + "${widget.record.results.leaderboard[greenSidePlayer].username.toUpperCase()} ${t.vs} ${widget.record.results.leaderboard[redSidePlayer].username.toUpperCase()} ${t.inTLmatch} ${widget.record.gamemode} ${timestamp(widget.record.ts)}", + style: Theme.of(context).textTheme.titleMedium!.copyWith(fontSize: 28), + ), actions: [ PopupMenuButton( enabled: widget.record.gamemode == "league", diff --git a/lib/views/user_view.dart b/lib/views/user_view.dart index 2fdbe05..acb1851 100644 --- a/lib/views/user_view.dart +++ b/lib/views/user_view.dart @@ -46,7 +46,7 @@ class UserState extends State { backgroundColor: Colors.black, floatingActionButtonLocation: FloatingActionButtonLocation.startTop, floatingActionButton: Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 0.0), child: FloatingActionButton( onPressed: () => Navigator.pop(context), tooltip: 'Fuck go back', @@ -56,18 +56,7 @@ class UserState extends State { body: SafeArea( child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { - return Row( - children: [ - Card( - child: Column( - children: [ - Text("oskagalove", style: TextStyle(),) - ] - ), - ), - DestinationHome(searchFor: widget.searchFor, dataFuture: getData(widget.searchFor), newsFuture: teto.fetchNews(widget.searchFor), constraints: constraints), - ], - ); + return DestinationHome(searchFor: widget.searchFor, dataFuture: getData(widget.searchFor), newsFuture: teto.fetchNews(widget.searchFor), constraints: constraints, noSidebar: true); } ) )