From e119ecf11b128d9835290ac55f988b6138dad0a5 Mon Sep 17 00:00:00 2001 From: dan63047 Date: Sat, 20 May 2023 23:41:01 +0300 Subject: [PATCH] I'm not sure about that CRUD shit, but i hope it will work --- lib/data_objects/tetrio.dart | 35 +++--- lib/services/crud_exceptions.dart | 13 ++ lib/services/settings_crud.dart | 17 +++ lib/services/sqlite_db_controller.dart | 49 ++++++++ lib/services/tetrio_crud.dart | 163 +++++++++++++++---------- lib/views/main_view.dart | 21 ++-- 6 files changed, 210 insertions(+), 88 deletions(-) create mode 100644 lib/services/crud_exceptions.dart create mode 100644 lib/services/settings_crud.dart create mode 100644 lib/services/sqlite_db_controller.dart diff --git a/lib/data_objects/tetrio.dart b/lib/data_objects/tetrio.dart index 9233dd8..a226e6e 100644 --- a/lib/data_objects/tetrio.dart +++ b/lib/data_objects/tetrio.dart @@ -62,11 +62,10 @@ class TetrioPlayer { this.zen, }); - double get level{ - return pow((xp / 500), 0.6) + - (xp / (5000 + (max(0, xp - 4 * pow(10, 6)) / 5000))) + - 1; - } + double get level => + pow((xp / 500), 0.6) + + (xp / (5000 + (max(0, xp - 4 * pow(10, 6)) / 5000))) + + 1; TetrioPlayer.fromJson(Map json, DateTime stateTime) { userId = json['_id']; @@ -101,11 +100,15 @@ class TetrioPlayer { var url = Uri.https('ch.tetr.io', 'api/users/$userId/records'); final response = await http.get(url); if (response.statusCode == 200) { - if(jsonDecode(response.body)['data']['records']['40l']['record'] != null){ - sprint.add(RecordSingle.fromJson(jsonDecode(response.body)['data']['records']['40l']['record'])); + if (jsonDecode(response.body)['data']['records']['40l']['record'] != + null) { + sprint.add(RecordSingle.fromJson( + jsonDecode(response.body)['data']['records']['40l']['record'])); } - if(jsonDecode(response.body)['data']['records']['blitz']['record'] != null){ - blitz.add(RecordSingle.fromJson(jsonDecode(response.body)['data']['records']['blitz']['record'])); + if (jsonDecode(response.body)['data']['records']['blitz']['record'] != + null) { + blitz.add(RecordSingle.fromJson( + jsonDecode(response.body)['data']['records']['blitz']['record'])); } zen = TetrioZen.fromJson(jsonDecode(response.body)['data']['zen']); } else { @@ -120,12 +123,12 @@ class TetrioPlayer { data['_id'] = userId; data['username'] = username; data['role'] = role; - data['ts'] = registrationTime; + data['ts'] = registrationTime?.toString(); data['badges'] = badges.map((v) => v.toJson()).toList(); data['xp'] = xp; data['gamesplayed'] = gamesPlayed; data['gameswon'] = gamesWon; - data['gametime'] = gameTime; + data['gametime'] = gameTime.inMicroseconds / 1000000; data['country'] = country; data['supporter_tier'] = supporterTier; data['verified'] = verified; @@ -139,7 +142,7 @@ class TetrioPlayer { return data; } - bool isSameState(TetrioPlayer other){ + bool isSameState(TetrioPlayer other) { if (userId != other.userId) return false; if (username != other.username) return false; if (role != other.role) return false; @@ -162,7 +165,7 @@ class TetrioPlayer { } @override - String toString(){ + String toString() { return "$username ($userId)"; } @@ -190,12 +193,12 @@ class Badge { final Map data = {}; data['id'] = badgeId; data['label'] = label; - data['ts'] = ts; + data['ts'] = ts?.toString(); return data; } @override - String toString(){ + String toString() { return "Badge $label ($badgeId)"; } @@ -582,6 +585,8 @@ class TetraLeagueAlpha { percentileRank = json['percentile_rank']; } + double? get app => apm! / (pps! * 60); + Map toJson() { final Map data = {}; data['gamesplayed'] = gamesPlayed; diff --git a/lib/services/crud_exceptions.dart b/lib/services/crud_exceptions.dart new file mode 100644 index 0000000..1443869 --- /dev/null +++ b/lib/services/crud_exceptions.dart @@ -0,0 +1,13 @@ +class DatabaseAlreadyOpen implements Exception {} + +class DatabaseIsNotOpen implements Exception {} + +class UnableToGetDocuments implements Exception {} + +class CouldNotDeletePlayer implements Exception {} + +class CouldNotUpdatePlayer implements Exception {} + +class TetrioPlayerAlreadyExist implements Exception {} + +class TetrioPlayerNotExist implements Exception {} diff --git a/lib/services/settings_crud.dart b/lib/services/settings_crud.dart new file mode 100644 index 0000000..2404151 --- /dev/null +++ b/lib/services/settings_crud.dart @@ -0,0 +1,17 @@ +import 'dart:async'; +import 'dart:convert'; +import 'package:sqflite/sqflite.dart'; +import 'package:path_provider/path_provider.dart' + show MissingPlatformDirectoryException, getApplicationDocumentsDirectory; +import 'package:tetra_stats/services/crud_exceptions.dart'; +import 'package:path/path.dart' show join; + +const String dbName = "TetraStats.db"; +const String tetrioUsersTable = "settings"; +const String userTetrioId = "userTetrioId"; +const String createSettingsTable = ''' + CREATE TABLE IF NOT EXISTS "settings" ( + "userTetrioId" TEXT + )'''; + +class SettingsService {} diff --git a/lib/services/sqlite_db_controller.dart b/lib/services/sqlite_db_controller.dart new file mode 100644 index 0000000..4758ff3 --- /dev/null +++ b/lib/services/sqlite_db_controller.dart @@ -0,0 +1,49 @@ +import 'dart:async'; +import 'dart:convert'; +import 'package:sqflite/sqflite.dart'; +import 'package:path_provider/path_provider.dart' + show MissingPlatformDirectoryException, getApplicationDocumentsDirectory; +import 'package:tetra_stats/services/crud_exceptions.dart'; +import 'package:tetra_stats/services/tetrio_crud.dart'; +import 'package:tetra_stats/services/settings_crud.dart'; +import 'package:path/path.dart' show join; + +const String dbName = "TetraStats.db"; + +class DB { + Database? _db; + Future open() async { + if (_db != null) { + throw DatabaseAlreadyOpen(); + } + try { + final docsPath = await getApplicationDocumentsDirectory(); + final dbPath = join(docsPath.path, dbName); + final db = await openDatabase(dbPath); + _db = db; + await db.execute(createTetrioUsersTable); + await db.execute(createSettingsTable); + } on MissingPlatformDirectoryException { + throw UnableToGetDocuments(); + } + } + + Future close() async { + final db = _db; + if (db == null) { + throw DatabaseIsNotOpen(); + } else { + await db.close(); + _db = null; + } + } + + Database getDatabaseOrThrow() { + final db = _db; + if (db == null) { + throw DatabaseIsNotOpen(); + } else { + return db; + } + } +} diff --git a/lib/services/tetrio_crud.dart b/lib/services/tetrio_crud.dart index 165d47a..3f602d9 100644 --- a/lib/services/tetrio_crud.dart +++ b/lib/services/tetrio_crud.dart @@ -1,9 +1,11 @@ import 'dart:async'; import 'dart:convert'; - import 'package:sqflite/sqflite.dart'; -import 'package:path_provider/path_provider.dart' show MissingPlatformDirectoryException, getApplicationDocumentsDirectory; +import 'package:path_provider/path_provider.dart' + show MissingPlatformDirectoryException, getApplicationDocumentsDirectory; import 'package:path/path.dart' show join; +import 'package:tetra_stats/services/crud_exceptions.dart'; +import 'package:tetra_stats/services/sqlite_db_controller.dart'; import 'package:tetra_stats/data_objects/tetrio.dart'; const String dbName = "TetraStats.db"; @@ -19,89 +21,120 @@ const String createTetrioUsersTable = ''' PRIMARY KEY("id") );'''; -class DatabaseAlreadyOpen implements Exception {} -class DatabaseIsNotOpen implements Exception {} -class UnableToGetDocuments implements Exception {} -class CouldNotDeletePlayer implements Exception{} -class TetrioPlayerAlreadyExist implements Exception{} -class TetrioPlayerNotExist implements Exception{} - -class TetrioService{ - Database? _db; +class TetrioService { Map> _players = {}; - final _tetrioStreamController = StreamController>>.broadcast(); + final _tetrioStreamController = + StreamController>>.broadcast(); - Database _getDatabaseOrThrow(){ - final db = _db; - if(db == null){ - throw DatabaseIsNotOpen(); - }else{ - return db; - } + Future _cachePlayers(DB udb) async { + final allPlayers = await getAllPlayers(udb: udb); + _players = allPlayers.first; + _tetrioStreamController.add(_players); } - Future open() async{ - if (_db != null){ - throw DatabaseAlreadyOpen(); - } - try{ - final docsPath = await getApplicationDocumentsDirectory(); - final dbPath = join(docsPath.path, dbName); - final db = await openDatabase(dbPath); - _db = db; - await db.execute(createTetrioUsersTable); - } on MissingPlatformDirectoryException { - throw UnableToGetDocuments(); - } - } - - Future close() async{ - final db = _db; - if(db == null){ - throw DatabaseIsNotOpen(); - }else{ - await db.close(); - _db = null; - } - } - - Future _cachePlayers() async{ - //final allPlayers = await getAllPlayers(); - } - - Future deleteTetrioPlayer({required String id}) async{ - final db = _getDatabaseOrThrow(); - final deletedPlayer = await db.delete(tetrioUsersTable, where: '$idCol = ?', whereArgs: [id.toLowerCase()]); - if (deletedPlayer != 1){ + Future deletePlayer({required String id, required DB udb}) async { + final db = udb.getDatabaseOrThrow(); + final deletedPlayer = await db.delete(tetrioUsersTable, + where: '$idCol = ?', whereArgs: [id.toLowerCase()]); + if (deletedPlayer != 1) { throw CouldNotDeletePlayer(); + } else { + _players.removeWhere((key, value) => key == id); + _tetrioStreamController.add(_players); } } - Future storeUser({required TetrioPlayer tetrioPlayer}) async{ - final db = _getDatabaseOrThrow(); - final results = await db.query(tetrioUsersTable, limit: 1, where: '$idCol = ?', whereArgs: [tetrioPlayer.userId.toLowerCase()]); - if(results.isNotEmpty){ + // Future > getOrCreatePlayer({required String id}) async { + // try{ + // final player = await getPlayer(id: id); + // return player; + // } on TetrioPlayerNotExist{ + + // final player = await createPlayer(tetrioPlayer: tetrioPlayer) + // } + // } + + Future createPlayer( + {required TetrioPlayer tetrioPlayer, required DB udb}) async { + final db = udb.getDatabaseOrThrow(); + final results = await db.query(tetrioUsersTable, + limit: 1, + where: '$idCol = ?', + whereArgs: [tetrioPlayer.userId.toLowerCase()]); + if (results.isNotEmpty) { throw TetrioPlayerAlreadyExist(); } - final Map statesJson = {tetrioPlayer.state.toString(): tetrioPlayer.toJson().toString()}; + final Map statesJson = { + tetrioPlayer.state.millisecondsSinceEpoch.toString(): + tetrioPlayer.toJson() + }; db.insert(tetrioUsersTable, { idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, - statesCol: statesJson + statesCol: jsonEncode(statesJson) }); + _players.addEntries({ + tetrioPlayer.userId: [tetrioPlayer] + }.entries); + _tetrioStreamController.add(_players); } - Future> getUser({required String id}) async{ - final db = _getDatabaseOrThrow(); + Future storeState(TetrioPlayer tetrioPlayer, DB udb) async { + final db = udb.getDatabaseOrThrow(); + List states = + await getPlayer(id: tetrioPlayer.userId, udb: udb); + states.add(tetrioPlayer); + final Map statesJson = {}; + for (var e in states) { + statesJson.addEntries( + {e.state.millisecondsSinceEpoch.toString(): e.toJson()}.entries); + } + db.update( + tetrioUsersTable, + { + idCol: tetrioPlayer.userId, + nickCol: tetrioPlayer.username, + statesCol: jsonEncode(statesJson) + }, + where: '$idCol = ?', + whereArgs: [tetrioPlayer.userId]); + _players[tetrioPlayer.userId]!.add(tetrioPlayer); + _tetrioStreamController.add(_players); + } + + Future> getPlayer( + {required String id, required DB udb}) async { + final db = udb.getDatabaseOrThrow(); List states = []; - final results = await db.query(tetrioUsersTable, limit: 1, where: '$idCol = ?', whereArgs: [id.toLowerCase()]); - if(results.isEmpty){ + final results = await db.query(tetrioUsersTable, + limit: 1, where: '$idCol = ?', whereArgs: [id.toLowerCase()]); + if (results.isEmpty) { throw TetrioPlayerNotExist(); - }else{ + } else { dynamic rawStates = results.first['jsonStates'] as String; rawStates = json.decode(rawStates); - rawStates.forEach((k,v) => states.add(TetrioPlayer.fromJson(v, DateTime.now()))); + rawStates.forEach((k, v) => states.add(TetrioPlayer.fromJson( + v, DateTime.fromMillisecondsSinceEpoch(int.parse(k))))); + _players.removeWhere((key, value) => key == id); + _players.addEntries({states.last.userId: states}.entries); + _tetrioStreamController.add(_players); return states; } } -} \ No newline at end of file + + Future>>> getAllPlayers( + {required DB udb}) async { + //await _ensureDbIsOpen(); + final db = udb.getDatabaseOrThrow(); + final players = await db.query(tetrioUsersTable); + Map> data = {}; + return players.map((row) { + var test = json.decode(row['jsonStates'] as String); + List states = []; + test.forEach( + (k, v) => states.add(TetrioPlayer.fromJson(v, DateTime.now()))); + data.addEntries({states.last.userId: states}.entries); + return data; + }); + } +} diff --git a/lib/views/main_view.dart b/lib/views/main_view.dart index 426244f..610b4c5 100644 --- a/lib/views/main_view.dart +++ b/lib/views/main_view.dart @@ -3,9 +3,11 @@ import 'package:http/http.dart' as http; import 'dart:convert'; import 'package:tetra_stats/data_objects/tetrio.dart'; import 'package:tetra_stats/services/tetrio_crud.dart'; +import 'package:tetra_stats/services/sqlite_db_controller.dart'; String _searchFor = ""; late TetrioPlayer me; +DB db = DB(); TetrioService teto = TetrioService(); class MainView extends StatefulWidget { @@ -18,14 +20,18 @@ class MainView extends StatefulWidget { class _MainViewState extends State { Future fetchTetrioPlayer(String user) async { var url = Uri.https('ch.tetr.io', 'api/users/$user'); - teto.open(); + db.open(); final response = await http.get(url); // final response = await http.get(Uri.parse('https://ch.tetr.io/')); if (response.statusCode == 200) { // If the server did return a 200 OK response, // then parse the JSON. - return TetrioPlayer.fromJson(jsonDecode(response.body)['data']['user'], DateTime.fromMillisecondsSinceEpoch(jsonDecode(response.body)['cache']['cached_at'], isUtc: true)); + return TetrioPlayer.fromJson( + jsonDecode(response.body)['data']['user'], + DateTime.fromMillisecondsSinceEpoch( + jsonDecode(response.body)['cache']['cached_at'], + isUtc: true)); } else { // If the server did not return a 200 OK response, // then throw an exception. @@ -54,9 +60,6 @@ class _MainViewState extends State { child: TextField( onChanged: (String value) { _searchFor = value; - setState(() { - - }); }, onSubmitted: (String value) { setState(() { @@ -77,7 +80,7 @@ class _MainViewState extends State { builder: (context, snapshot) { if (snapshot.hasData) { snapshot.data!.getRecords(); - teto.getUser(id: snapshot.data!.userId); + teto.storeState(snapshot.data!, db); return Flexible( child: Column(children: [ Text(snapshot.data!.username.toString()), @@ -105,9 +108,11 @@ class _MainViewState extends State { Text( "№${snapshot.data!.tlSeason1.standing} (№${snapshot.data!.tlSeason1.standingLocal} in country)"), Text( - "${snapshot.data!.tlSeason1.apm} APM, ${snapshot.data!.tlSeason1.pps} PPS, ${snapshot.data!.tlSeason1.vs} VS"), + "${snapshot.data!.tlSeason1.apm} APM, ${snapshot.data!.tlSeason1.pps} PPS, ${snapshot.data!.tlSeason1.vs} VS, ${snapshot.data!.tlSeason1.app?.toStringAsFixed(3)} APP"), const Text("\n40 Lines", softWrap: true), - Text(snapshot.data!.sprint.isNotEmpty ? snapshot.data!.sprint[0].toString() : "No record"), + Text(snapshot.data!.sprint.isNotEmpty + ? snapshot.data!.sprint[0].toString() + : "No record"), ])); } else if (snapshot.hasError) { return Text('${snapshot.error}');