TetrioService now should eat less ram

Also app now checks if valid nickname was entered in "Your TETR.IO account" dialog. No need to paste userID, app will do it by itself.
This commit is contained in:
dan63047 2024-02-06 23:38:52 +03:00
parent 07929ca6f7
commit 7099f7471a
8 changed files with 81 additions and 82 deletions

View File

@ -6,7 +6,7 @@
/// Locales: 2 /// Locales: 2
/// Strings: 1016 (508 per locale) /// Strings: 1016 (508 per locale)
/// ///
/// Built on 2024-02-03 at 12:49 UTC /// Built on 2024-02-06 at 20:25 UTC
// coverage:ignore-file // coverage:ignore-file
// ignore_for_file: type=lint // ignore_for_file: type=lint
@ -224,8 +224,8 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
String get importCancelled => 'Operation was cancelled'; String get importCancelled => 'Operation was cancelled';
String get importSuccess => 'Import successful'; String get importSuccess => 'Import successful';
String get yourID => 'Your TETR.IO account'; String get yourID => 'Your TETR.IO account';
String get yourIDAlertTitle => 'Your TETR.IO account nickname or ID'; String get yourIDAlertTitle => 'Your nickname in TETR.IO';
String get yourIDText => 'Every time when app loads, stats of that player will be fetched. Please prefer ID over nickname because nickname can be changed.'; String get yourIDText => 'When app loads, it will retrieve data for this account';
String get language => 'Language'; String get language => 'Language';
String get aboutApp => 'About app'; String get aboutApp => 'About app';
String aboutAppText({required Object appName, required Object packageName, required Object version, required Object buildNumber}) => '${appName} (${packageName}) Version ${version} Build ${buildNumber}\n\nDeveloped by dan63047\nFormulas provided by kerrmunism\nHistory provided by p1nkl0bst3r\nTETR.IO replay grabber API by szy'; String aboutAppText({required Object appName, required Object packageName, required Object version, required Object buildNumber}) => '${appName} (${packageName}) Version ${version} Build ${buildNumber}\n\nDeveloped by dan63047\nFormulas provided by kerrmunism\nHistory provided by p1nkl0bst3r\nTETR.IO replay grabber API by szy';
@ -816,8 +816,8 @@ class _StringsRu implements Translations {
@override String get importCancelled => 'Операция была отменена'; @override String get importCancelled => 'Операция была отменена';
@override String get importSuccess => 'Успешно импортировано'; @override String get importSuccess => 'Успешно импортировано';
@override String get yourID => 'Ваш аккаунт в TETR.IO'; @override String get yourID => 'Ваш аккаунт в TETR.IO';
@override String get yourIDAlertTitle => 'Никнейм или ID вашего аккаунта в TETR.IO'; @override String get yourIDAlertTitle => 'Ваш ник в TETR.IO';
@override String get yourIDText => 'Каждый раз, когда приложение запускается, приложение будет получать статистику этого игрока. Пожалуйста, отдайте предпочтение ID, так как никнейм можно изменить.'; @override String get yourIDText => 'При запуске приложения оно будет получать статистику этого игрока.';
@override String get language => 'Язык (Language)'; @override String get language => 'Язык (Language)';
@override String get aboutApp => 'О приложении'; @override String get aboutApp => 'О приложении';
@override String aboutAppText({required Object appName, required Object packageName, required Object version, required Object buildNumber}) => '${appName} (${packageName}) Версия ${version} Сборка ${buildNumber}\n\nРазработал dan63047\nФормулы предоставил kerrmunism\nИсторию предоставляет p1nkl0bst3r\nВозможность скачивать повторы из TETR.IO предоставляет szy'; @override String aboutAppText({required Object appName, required Object packageName, required Object version, required Object buildNumber}) => '${appName} (${packageName}) Версия ${version} Сборка ${buildNumber}\n\nРазработал dan63047\nФормулы предоставил kerrmunism\nИсторию предоставляет p1nkl0bst3r\nВозможность скачивать повторы из TETR.IO предоставляет szy';
@ -1400,8 +1400,8 @@ extension on Translations {
case 'importCancelled': return 'Operation was cancelled'; case 'importCancelled': return 'Operation was cancelled';
case 'importSuccess': return 'Import successful'; case 'importSuccess': return 'Import successful';
case 'yourID': return 'Your TETR.IO account'; case 'yourID': return 'Your TETR.IO account';
case 'yourIDAlertTitle': return 'Your TETR.IO account nickname or ID'; case 'yourIDAlertTitle': return 'Your nickname in TETR.IO';
case 'yourIDText': return 'Every time when app loads, stats of that player will be fetched. Please prefer ID over nickname because nickname can be changed.'; case 'yourIDText': return 'When app loads, it will retrieve data for this account';
case 'language': return 'Language'; case 'language': return 'Language';
case 'aboutApp': return 'About app'; case 'aboutApp': return 'About app';
case 'aboutAppText': return ({required Object appName, required Object packageName, required Object version, required Object buildNumber}) => '${appName} (${packageName}) Version ${version} Build ${buildNumber}\n\nDeveloped by dan63047\nFormulas provided by kerrmunism\nHistory provided by p1nkl0bst3r\nTETR.IO replay grabber API by szy'; case 'aboutAppText': return ({required Object appName, required Object packageName, required Object version, required Object buildNumber}) => '${appName} (${packageName}) Version ${version} Build ${buildNumber}\n\nDeveloped by dan63047\nFormulas provided by kerrmunism\nHistory provided by p1nkl0bst3r\nTETR.IO replay grabber API by szy';
@ -1918,8 +1918,8 @@ extension on _StringsRu {
case 'importCancelled': return 'Операция была отменена'; case 'importCancelled': return 'Операция была отменена';
case 'importSuccess': return 'Успешно импортировано'; case 'importSuccess': return 'Успешно импортировано';
case 'yourID': return 'Ваш аккаунт в TETR.IO'; case 'yourID': return 'Ваш аккаунт в TETR.IO';
case 'yourIDAlertTitle': return 'Никнейм или ID вашего аккаунта в TETR.IO'; case 'yourIDAlertTitle': return 'Ваш ник в TETR.IO';
case 'yourIDText': return 'Каждый раз, когда приложение запускается, приложение будет получать статистику этого игрока. Пожалуйста, отдайте предпочтение ID, так как никнейм можно изменить.'; case 'yourIDText': return 'При запуске приложения оно будет получать статистику этого игрока.';
case 'language': return 'Язык (Language)'; case 'language': return 'Язык (Language)';
case 'aboutApp': return 'О приложении'; case 'aboutApp': return 'О приложении';
case 'aboutAppText': return ({required Object appName, required Object packageName, required Object version, required Object buildNumber}) => '${appName} (${packageName}) Версия ${version} Сборка ${buildNumber}\n\nРазработал dan63047\nФормулы предоставил kerrmunism\nИсторию предоставляет p1nkl0bst3r\nВозможность скачивать повторы из TETR.IO предоставляет szy'; case 'aboutAppText': return ({required Object appName, required Object packageName, required Object version, required Object buildNumber}) => '${appName} (${packageName}) Версия ${version} Сборка ${buildNumber}\n\nРазработал dan63047\nФормулы предоставил kerrmunism\nИсторию предоставляет p1nkl0bst3r\nВозможность скачивать повторы из TETR.IO предоставляет szy';

View File

@ -27,7 +27,7 @@ const String endContext2 = "endContext2";
const String statesCol = "jsonStates"; const String statesCol = "jsonStates";
const String player1id = "player1id"; const String player1id = "player1id";
const String player2id = "player2id"; const String player2id = "player2id";
/// Table, that store players data, their stats and some moments of time /// Table, that store players data, their stats at some moments of time
const String createTetrioUsersTable = ''' const String createTetrioUsersTable = '''
CREATE TABLE IF NOT EXISTS "tetrioUsers" ( CREATE TABLE IF NOT EXISTS "tetrioUsers" (
"id" TEXT UNIQUE, "id" TEXT UNIQUE,
@ -66,7 +66,7 @@ const String createTetrioTLReplayStats = '''
'''; ''';
class TetrioService extends DB { class TetrioService extends DB {
Map<String, List<TetrioPlayer>> _players = {}; final Map<String, String> _players = {};
// I'm trying to send as less requests, as possible, so i'm caching the results of those requests. // I'm trying to send as less requests, as possible, so i'm caching the results of those requests.
// Usually those maps looks like this: {"cached_until_unix_milliseconds": Object} // Usually those maps looks like this: {"cached_until_unix_milliseconds": Object}
@ -82,9 +82,9 @@ class TetrioService extends DB {
/// We should have only one instanse of this service /// We should have only one instanse of this service
static final TetrioService _shared = TetrioService._sharedInstance(); static final TetrioService _shared = TetrioService._sharedInstance();
factory TetrioService() => _shared; factory TetrioService() => _shared;
late final StreamController<Map<String, List<TetrioPlayer>>> _tetrioStreamController; late final StreamController<Map<String, String>> _tetrioStreamController;
TetrioService._sharedInstance() { TetrioService._sharedInstance() {
_tetrioStreamController = StreamController<Map<String, List<TetrioPlayer>>>.broadcast(onListen: () { _tetrioStreamController = StreamController<Map<String, String>>.broadcast(onListen: () {
_tetrioStreamController.sink.add(_players); _tetrioStreamController.sink.add(_players);
}); });
} }
@ -95,17 +95,15 @@ class TetrioService extends DB {
await _loadPlayers(); await _loadPlayers();
} }
Stream<Map<String, List<TetrioPlayer>>> get allPlayers => _tetrioStreamController.stream; Stream<Map<String, String>> get allPlayers => _tetrioStreamController.stream;
/// Loading and sending to the stream everyone. /// Loading and sending to the stream everyone.
Future<void> _loadPlayers() async { Future<void> _loadPlayers() async {
final allPlayers = await getAllPlayers(); final allPlayers = await getAllPlayerToTrack();
try{ for (var element in allPlayers) {
_players = allPlayers.toList().first; // ??? _players[element] = await getNicknameByID(element);
}catch (e){
developer.log("_loadPlayers: allPlayers.toList().first did oopsie", name: "services/tetrio_crud", error: e);
_players = {};
} }
developer.log("_loadPlayers: $_players", name: "services/tetrio_crud");
_tetrioStreamController.add(_players); _tetrioStreamController.add(_players);
} }
@ -128,7 +126,10 @@ class TetrioService extends DB {
Future<String> getNicknameByID(String id) async { Future<String> getNicknameByID(String id) async {
if (id.length <= 16) return id; // nicknames can be up to 16 symbols in length, that's how i'm differentiate nickname from ids if (id.length <= 16) return id; // nicknames can be up to 16 symbols in length, that's how i'm differentiate nickname from ids
try{ try{
return await getPlayer(id).then((value) => value.last.username); await ensureDbIsOpen();
final db = getDatabaseOrThrow();
var request = await db.query(tetrioUsersTable, limit: 1, where: '$idCol = ?', whereArgs: [id.toLowerCase()]);
return request.first[nickCol] as String;
} catch (e){ } catch (e){
return await fetchPlayer(id).then((value) => value.username); return await fetchPlayer(id).then((value) => value.username);
} }
@ -350,16 +351,8 @@ class TetrioService extends DB {
// trying to dump it to local DB // trying to dump it to local DB
await ensureDbIsOpen(); await ensureDbIsOpen();
final db = getDatabaseOrThrow(); final db = getDatabaseOrThrow();
late List<TetrioPlayer> states; List<TetrioPlayer> states = await getPlayer(id);
try{ if (states.isEmpty) await createPlayer(history.first);
// checking if tetra stats aware about that player TODO: is it necessary?
states = _players[id]!;
}catch(e){
// if somehow not - create it
var player = await fetchPlayer(id);
await createPlayer(player);
states = _players[id]!;
}
states.insertAll(0, history.reversed); states.insertAll(0, history.reversed);
final Map<String, dynamic> statesJson = {}; final Map<String, dynamic> statesJson = {};
for (var e in states) { // making one big json out of this list for (var e in states) { // making one big json out of this list
@ -367,7 +360,6 @@ class TetrioService extends DB {
} }
// and putting it to local DB // and putting it to local DB
await db.update(tetrioUsersTable, {idCol: id, nickCol: nick, statesCol: jsonEncode(statesJson)}, where: '$idCol = ?', whereArgs: [id]); await db.update(tetrioUsersTable, {idCol: id, nickCol: nick, statesCol: jsonEncode(statesJson)}, where: '$idCol = ?', whereArgs: [id]);
_tetrioStreamController.add(_players);
return history; return history;
case 404: case 404:
developer.log("fetchTLHistory: Probably, history doesn't exist", name: "services/tetrio_crud", error: response.statusCode); developer.log("fetchTLHistory: Probably, history doesn't exist", name: "services/tetrio_crud", error: response.statusCode);
@ -732,7 +724,7 @@ class TetrioService extends DB {
await db.delete(tetrioTLReplayStatsTable, where: '$idCol = ?', whereArgs: [rID]); await db.delete(tetrioTLReplayStatsTable, where: '$idCol = ?', whereArgs: [rID]);
} }
/// Retrieves Blitz, 40 Lines and Zen records for a given [playerID] from Tetra Channel api. Returns Map, which contains user id (`user`), /// Retrieves Blitz, 40 Lines and Zen records for a given [userID] from Tetra Channel api. Returns Map, which contains user id (`user`),
/// Blitz (`blitz`) and 40 Lines (`sprint`) record objects and Zen object (`zen`). Throws an exception if fails to retrieve. /// Blitz (`blitz`) and 40 Lines (`sprint`) record objects and Zen object (`zen`). Throws an exception if fails to retrieve.
Future<Map<String, dynamic>> fetchRecords(String userID) async { Future<Map<String, dynamic>> fetchRecords(String userID) async {
try{ try{
@ -811,9 +803,7 @@ class TetrioService extends DB {
// converting to json and store // converting to json and store
final Map<String, dynamic> statesJson = {(tetrioPlayer.state.millisecondsSinceEpoch ~/ 1000).toString(): tetrioPlayer.toJson()}; final Map<String, dynamic> statesJson = {(tetrioPlayer.state.millisecondsSinceEpoch ~/ 1000).toString(): tetrioPlayer.toJson()};
db.insert(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)}); db.insert(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)});
_players.addEntries({ _players.addEntries({tetrioPlayer.userId: tetrioPlayer.username}.entries);
tetrioPlayer.userId: [tetrioPlayer]
}.entries);
_tetrioStreamController.add(_players); _tetrioStreamController.add(_players);
} }
@ -841,7 +831,6 @@ class TetrioService extends DB {
await ensureDbIsOpen(); await ensureDbIsOpen();
final db = getDatabaseOrThrow(); final db = getDatabaseOrThrow();
final players = await db.query(tetrioUsersToTrackTable); final players = await db.query(tetrioUsersToTrackTable);
developer.log("getAllPlayerToTrack: $players", name: "services/tetrio_crud");
return players.map((noteRow) => noteRow["id"].toString()); return players.map((noteRow) => noteRow["id"].toString());
} }
@ -853,25 +842,22 @@ class TetrioService extends DB {
if (deletedPlayer != 1) { if (deletedPlayer != 1) {
throw CouldNotDeletePlayer(); throw CouldNotDeletePlayer();
} else { } else {
// _players.removeWhere((key, value) => key == id); _players.removeWhere((key, value) => key == id);
// _tetrioStreamController.add(_players); _tetrioStreamController.add(_players);
} }
} }
/// Saves state (which is [tetrioPlayer]) to the local database. /// Saves state (which is [tetrioPlayer]) to the local database.
Future<void> storeState(TetrioPlayer tetrioPlayer) async { Future<void> storeState(TetrioPlayer tetrioPlayer) async {
await ensureDbIsOpen(); // if tetrio player doesn't have entry in database - just calling different function
final db = getDatabaseOrThrow(); List<TetrioPlayer> states = await getPlayer(tetrioPlayer.userId);
late List<TetrioPlayer> states; if (states.isEmpty) {
try { // retrieveing previous states await createPlayer(tetrioPlayer);
states = _players[tetrioPlayer.userId]!; return;
} catch (e) { // nothing found - player not exist - create them
await createPlayer(tetrioPlayer);
states = await getPlayer(tetrioPlayer.userId);
} }
// we not going to add state, that is same, as the previous // we not going to add state, that is same, as the previous
bool test = _players[tetrioPlayer.userId]!.last.isSameState(tetrioPlayer); bool test = states.last.isSameState(tetrioPlayer);
if (test == false) states.add(tetrioPlayer); if (test == false) states.add(tetrioPlayer);
// Making map of the states // Making map of the states
@ -880,21 +866,21 @@ class TetrioService extends DB {
// Saving in format: {"unix_seconds": json_of_state} // Saving in format: {"unix_seconds": json_of_state}
statesJson.addEntries({(e.state.millisecondsSinceEpoch ~/ 1000).toString(): e.toJson()}.entries); statesJson.addEntries({(e.state.millisecondsSinceEpoch ~/ 1000).toString(): e.toJson()}.entries);
} }
// Rewrite our database // Rewrite our database
await ensureDbIsOpen();
final db = getDatabaseOrThrow();
await db.update(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)}, await db.update(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)},
where: '$idCol = ?', whereArgs: [tetrioPlayer.userId]); where: '$idCol = ?', whereArgs: [tetrioPlayer.userId]);
_players[tetrioPlayer.userId]!.add(tetrioPlayer);
_tetrioStreamController.add(_players);
} }
/// Remove state (which is [tetrioPlayer]) from the local database /// Remove state (which is [tetrioPlayer]) from the local database
Future<void> deleteState(TetrioPlayer tetrioPlayer) async { Future<void> deleteState(TetrioPlayer tetrioPlayer) async {
await ensureDbIsOpen(); await ensureDbIsOpen();
final db = getDatabaseOrThrow(); final db = getDatabaseOrThrow();
late List<TetrioPlayer> states; List<TetrioPlayer> states = await getPlayer(tetrioPlayer.userId);
// removing state from map that contain every state of each user // removing state from map that contain every state of each user
_players[tetrioPlayer.userId]!.removeWhere((element) => element.state == tetrioPlayer.state); states.removeWhere((element) => element.state == tetrioPlayer.state);
states = _players[tetrioPlayer.userId]!;
// Making map of the states (without deleted one) // Making map of the states (without deleted one)
final Map<String, dynamic> statesJson = {}; final Map<String, dynamic> statesJson = {};
@ -904,7 +890,6 @@ class TetrioService extends DB {
// Rewriting database entry with new json // Rewriting database entry with new json
await db.update(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)}, await db.update(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)},
where: '$idCol = ?', whereArgs: [tetrioPlayer.userId]); where: '$idCol = ?', whereArgs: [tetrioPlayer.userId]);
_players[tetrioPlayer.userId]!.add(tetrioPlayer);
_tetrioStreamController.add(_players); _tetrioStreamController.add(_players);
} }
@ -924,7 +909,7 @@ class TetrioService extends DB {
rawStates.forEach((k, v) => states.add(TetrioPlayer.fromJson(v, DateTime.fromMillisecondsSinceEpoch(int.parse(k) * 1000), id, results.first[nickCol] as String))); rawStates.forEach((k, v) => states.add(TetrioPlayer.fromJson(v, DateTime.fromMillisecondsSinceEpoch(int.parse(k) * 1000), id, results.first[nickCol] as String)));
// updating the stream // updating the stream
_players.removeWhere((key, value) => key == id); _players.removeWhere((key, value) => key == id);
_players.addEntries({states.last.userId: states}.entries); _players.addEntries({states.last.userId: states.last.username}.entries);
_tetrioStreamController.add(_players); _tetrioStreamController.add(_players);
return states; return states;
} }
@ -1034,20 +1019,18 @@ class TetrioService extends DB {
} }
} }
/// Basucally, retrieves whole [tetrioUsersTable] and do stupud things idk /// Retrieves whole [tetrioUsersTable] and returns Map with [TetrioPlayer] objects of everyone in database
/// Returns god knows what. TODO: Rewrite this shit Future<Map<String, List<TetrioPlayer>>> getAllPlayers() async {
Future<Iterable<Map<String, List<TetrioPlayer>>>> getAllPlayers() async {
await ensureDbIsOpen(); await ensureDbIsOpen();
final db = getDatabaseOrThrow(); final db = getDatabaseOrThrow();
final players = await db.query(tetrioUsersTable); final players = await db.query(tetrioUsersTable);
Map<String, List<TetrioPlayer>> data = {}; Map<String, List<TetrioPlayer>> data = {};
return players.map((row) { for (var entry in players){
// what the fuck am i doing here? var test = json.decode(entry['jsonStates'] as String);
var test = json.decode(row['jsonStates'] as String);
List<TetrioPlayer> states = []; List<TetrioPlayer> states = [];
test.forEach((k, v) => states.add(TetrioPlayer.fromJson(v, DateTime.fromMillisecondsSinceEpoch(int.parse(k) * 1000), row[idCol] as String, row[nickCol] as String))); test.forEach((k, v) => states.add(TetrioPlayer.fromJson(v, DateTime.fromMillisecondsSinceEpoch(int.parse(k) * 1000), entry[idCol] as String, entry[nickCol] as String)));
data.addEntries({states.last.userId: states}.entries); data.addEntries({states.last.userId: states}.entries);
return data; }
}); return data;
} }
} }

View File

@ -494,8 +494,9 @@ class _NavDrawerState extends State<NavDrawer> {
case ConnectionState.waiting: case ConnectionState.waiting:
case ConnectionState.active: case ConnectionState.active:
final allPlayers = (snapshot.data != null) final allPlayers = (snapshot.data != null)
? snapshot.data as Map<String, List<TetrioPlayer>> ? snapshot.data as Map<String, String>
: <String, List<TetrioPlayer>>{}; : <String, String>{};
allPlayers.remove(prefs.getString("player") ?? "6098518e3d5155e6ec429cdc"); // player from the home button will be delisted
List<String> keys = allPlayers.keys.toList(); List<String> keys = allPlayers.keys.toList();
return NestedScrollView( return NestedScrollView(
headerSliverBuilder: (context, value) { headerSliverBuilder: (context, value) {
@ -550,7 +551,7 @@ class _NavDrawerState extends State<NavDrawer> {
itemBuilder: (context, index) { itemBuilder: (context, index) {
var i = allPlayers.length-1-index; // Last players in this map are most recent ones, they are gonna be shown at the top. 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( return ListTile(
title: Text(allPlayers[keys[i]]?.last.username as String), // Takes last known username from list of states title: Text(allPlayers[keys[i]]??keys[i]), // Takes last known username from list of states
onTap: () { onTap: () {
widget.changePlayer(keys[i]); // changes to chosen player widget.changePlayer(keys[i]); // changes to chosen player
Navigator.of(context).pop(); // and closes itself. Navigator.of(context).pop(); // and closes itself.

View File

@ -1,4 +1,5 @@
import 'dart:io'; import 'dart:io';
import 'package:tetra_stats/data_objects/tetrio.dart';
import 'package:tetra_stats/main.dart' show packageInfo; import 'package:tetra_stats/main.dart' show packageInfo;
import 'package:file_selector/file_selector.dart'; import 'package:file_selector/file_selector.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
@ -66,6 +67,11 @@ class SettingsState extends State<SettingsView> {
await _setDefaultNickname(player); await _setDefaultNickname(player);
} }
Future<void> _removePlayer() async {
await prefs.remove('player');
await _setDefaultNickname("dan63047");
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final t = Translations.of(context); final t = Translations.of(context);
@ -212,9 +218,21 @@ class SettingsState extends State<SettingsView> {
), ),
TextButton( TextButton(
child: Text(t.popupActions.submit), child: Text(t.popupActions.submit),
onPressed: () { onPressed: () async {
_setPlayer(_playertext.text.toLowerCase().trim()); if (_playertext.text.isEmpty) {
Navigator.of(context).pop(); _removePlayer();
Navigator.of(context).pop();
return;
}
late TetrioPlayer user;
try{
user = await teto.fetchPlayer(_playertext.text.toLowerCase().trim());
}on Exception{
if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.noSuchUser)));
return;
}
_setPlayer(user.userId);
if (context.mounted) Navigator.of(context).pop();
setState(() {}); setState(() {});
}, },
) )

View File

@ -70,14 +70,15 @@ class TrackedPlayersState extends State<TrackedPlayersView> {
), ),
backgroundColor: Colors.black, backgroundColor: Colors.black,
body: SafeArea( body: SafeArea(
child: StreamBuilder( child: FutureBuilder(
stream: teto.allPlayers, future: teto.getAllPlayers(),
builder: (context, snapshot) { builder: (context, snapshot) {
switch (snapshot.connectionState) { switch (snapshot.connectionState) {
case ConnectionState.none: case ConnectionState.none:
return const Center(child: Text('none case of StreamBuilder'));
case ConnectionState.waiting: case ConnectionState.waiting:
case ConnectionState.active: case ConnectionState.active:
return const Center(child: CircularProgressIndicator(color: Colors.white));
case ConnectionState.done:
final allPlayers = (snapshot.data != null) ? snapshot.data as Map<String, List<TetrioPlayer>> : <String, List<TetrioPlayer>>{}; final allPlayers = (snapshot.data != null) ? snapshot.data as Map<String, List<TetrioPlayer>> : <String, List<TetrioPlayer>>{};
List<String> keys = allPlayers.keys.toList(); List<String> keys = allPlayers.keys.toList();
return NestedScrollView( return NestedScrollView(
@ -114,7 +115,7 @@ class TrackedPlayersState extends State<TrackedPlayersView> {
icon: const Icon(Icons.delete_forever), icon: const Icon(Icons.delete_forever),
onPressed: () { onPressed: () {
String nn = allPlayers[keys[index]]!.last.username; String nn = allPlayers[keys[index]]!.last.username;
teto.deletePlayer(keys[index]); setState(() {teto.deletePlayer(keys[index]);});
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.trackedPlayersStatesDeleted(nickname: nn)))); ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.trackedPlayersStatesDeleted(nickname: nn))));
}, },
), ),
@ -128,10 +129,6 @@ class TrackedPlayersState extends State<TrackedPlayersView> {
}, },
); );
})); }));
case ConnectionState.done:
return const Center(
child: Text('done case of StreamBuilder',
style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42), textAlign: TextAlign.center));
} }
})), })),
); );

View File

@ -89,8 +89,8 @@
"importCancelled": "Operation was cancelled", "importCancelled": "Operation was cancelled",
"importSuccess": "Import successful", "importSuccess": "Import successful",
"yourID": "Your TETR.IO account", "yourID": "Your TETR.IO account",
"yourIDAlertTitle": "Your TETR.IO account nickname or ID", "yourIDAlertTitle": "Your nickname in TETR.IO",
"yourIDText": "Every time when app loads, stats of that player will be fetched. Please prefer ID over nickname because nickname can be changed.", "yourIDText": "When app loads, it will retrieve data for this account",
"language": "Language", "language": "Language",
"aboutApp": "About app", "aboutApp": "About app",
"aboutAppText": "${appName} (${packageName}) Version ${version} Build ${buildNumber}\n\nDeveloped by dan63047\nFormulas provided by kerrmunism\nHistory provided by p1nkl0bst3r\nTETR.IO replay grabber API by szy", "aboutAppText": "${appName} (${packageName}) Version ${version} Build ${buildNumber}\n\nDeveloped by dan63047\nFormulas provided by kerrmunism\nHistory provided by p1nkl0bst3r\nTETR.IO replay grabber API by szy",

View File

@ -89,8 +89,8 @@
"importCancelled": "Операция была отменена", "importCancelled": "Операция была отменена",
"importSuccess": "Успешно импортировано", "importSuccess": "Успешно импортировано",
"yourID": "Ваш аккаунт в TETR.IO", "yourID": "Ваш аккаунт в TETR.IO",
"yourIDAlertTitle": "Никнейм или ID вашего аккаунта в TETR.IO", "yourIDAlertTitle": "Ваш ник в TETR.IO",
"yourIDText": "Каждый раз, когда приложение запускается, приложение будет получать статистику этого игрока. Пожалуйста, отдайте предпочтение ID, так как никнейм можно изменить.", "yourIDText": "При запуске приложения оно будет получать статистику этого игрока.",
"language": "Язык (Language)", "language": "Язык (Language)",
"aboutApp": "О приложении", "aboutApp": "О приложении",
"aboutAppText": "${appName} (${packageName}) Версия ${version} Сборка ${buildNumber}\n\nРазработал dan63047\nФормулы предоставил kerrmunism\nИсторию предоставляет p1nkl0bst3r\nВозможность скачивать повторы из TETR.IO предоставляет szy", "aboutAppText": "${appName} (${packageName}) Версия ${version} Сборка ${buildNumber}\n\nРазработал dan63047\nФормулы предоставил kerrmunism\nИсторию предоставляет p1nkl0bst3r\nВозможность скачивать повторы из TETR.IO предоставляет szy",

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB