Now it's possible to set player for app init.

Also added some logging,
Changed come things.
Now i gonna think about sqlite database
This commit is contained in:
dan63047 2023-06-07 00:04:49 +03:00
parent d69713664f
commit 10f4063bf2
11 changed files with 264 additions and 183 deletions

View File

@ -1,5 +1,6 @@
import 'dart:math'; import 'dart:math';
import 'package:vector_math/vector_math.dart'; import 'package:vector_math/vector_math.dart';
import 'dart:developer' as developer;
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'dart:convert'; import 'dart:convert';
@ -70,7 +71,8 @@ class TetrioPlayer {
double get level => 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<String, dynamic> json, DateTime stateTime) { TetrioPlayer.fromJson(Map<String, dynamic> json, DateTime stateTime, bool fetchRecords) {
developer.log("TetrioPlayer.fromJson $stateTime: $json", name: "data_objects/tetrio");
userId = json['_id']; userId = json['_id'];
username = json['username']; username = json['username'];
state = stateTime; state = stateTime;
@ -95,6 +97,8 @@ class TetrioPlayer {
connections = Connections.fromJson(json['connections']); connections = Connections.fromJson(json['connections']);
distinguishment = json['distinguishment'] != null ? Distinguishment.fromJson(json['distinguishment']) : null; distinguishment = json['distinguishment'] != null ? Distinguishment.fromJson(json['distinguishment']) : null;
friendCount = json['friend_count'] ?? 0; friendCount = json['friend_count'] ?? 0;
badstanding = json['badstanding'];
if (fetchRecords) {
var url = Uri.https('ch.tetr.io', 'api/users/$userId/records'); var url = Uri.https('ch.tetr.io', 'api/users/$userId/records');
Future response = http.get(url); Future response = http.get(url);
response.then((value) { response.then((value) {
@ -108,10 +112,11 @@ class TetrioPlayer {
: []; : [];
zen = TetrioZen.fromJson(jsonRecords['data']['zen']); zen = TetrioZen.fromJson(jsonRecords['data']['zen']);
} else { } else {
developer.log("TetrioPlayer.fromJson exception", name: "data_objects/tetrio", error: value.statusCode);
throw Exception('Failed to fetch player'); throw Exception('Failed to fetch player');
} }
}); });
badstanding = json['badstanding']; }
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@ -137,6 +142,7 @@ class TetrioPlayer {
data['friend_count'] = friendCount; data['friend_count'] = friendCount;
data['badstanding'] = badstanding; data['badstanding'] = badstanding;
data['bot'] = bot; data['bot'] = bot;
developer.log("TetrioPlayer.toJson: $bot", name: "data_objects/tetrio");
return data; return data;
} }
@ -468,7 +474,6 @@ class EstTr {
final double _apm; final double _apm;
final double _pps; final double _pps;
final double _vs; final double _vs;
final double _rating;
final double _rd; final double _rd;
final double _app; final double _app;
final double _dss; final double _dss;
@ -478,7 +483,7 @@ class EstTr {
late double srarea; late double srarea;
late double statrank; late double statrank;
EstTr(this._apm, this._pps, this._vs, this._rating, this._rd, this._app, this._dss, this._dsp, this._gbe) { EstTr(this._apm, this._pps, this._vs, this._rd, this._app, this._dss, this._dsp, this._gbe) {
srarea = (_apm * 0) + (_pps * 135) + (_vs * 0) + (_app * 290) + (_dss * 0) + (_dsp * 700) + (_gbe * 0); srarea = (_apm * 0) + (_pps * 135) + (_vs * 0) + (_app * 290) + (_dss * 0) + (_dsp * 700) + (_gbe * 0);
statrank = 11.2 * atan((srarea - 93) / 130) + 1; statrank = 11.2 * atan((srarea - 93) / 130) + 1;
if (statrank <= 0) statrank = 0.001; if (statrank <= 0) statrank = 0.001;
@ -493,11 +498,10 @@ class EstTr {
class Playstyle { class Playstyle {
final double _apm; final double _apm;
final double _pps; final double _pps;
final double _vs; //final double _vs;
final double _rd;
final double _app; final double _app;
final double _vsapm; final double _vsapm;
final double _dss; //final double _dss;
final double _dsp; final double _dsp;
final double _gbe; final double _gbe;
final double _srarea; final double _srarea;
@ -507,12 +511,12 @@ class Playstyle {
late double stride; late double stride;
late double infds; late double infds;
Playstyle(this._apm, this._pps, this._vs, this._rd, this._app, this._vsapm, this._dss, this._dsp, this._gbe, this._srarea, this._statrank) { Playstyle(this._apm, this._pps, this._app, this._vsapm, this._dsp, this._gbe, this._srarea, this._statrank) {
double nmapm = ((_apm / _srarea) / ((0.069 * pow(1.0017, (pow(_statrank, 5) / 4700))) + _statrank / 360)) - 1; double nmapm = ((_apm / _srarea) / ((0.069 * pow(1.0017, (pow(_statrank, 5) / 4700))) + _statrank / 360)) - 1;
double nmpps = ((_pps / _srarea) / (0.0084264 * pow(2.14, (-2 * (_statrank / 2.7 + 1.03))) - _statrank / 5750 + 0.0067)) - 1; double nmpps = ((_pps / _srarea) / (0.0084264 * pow(2.14, (-2 * (_statrank / 2.7 + 1.03))) - _statrank / 5750 + 0.0067)) - 1;
double nmvs = ((_vs / _srarea) / (0.1333 * pow(1.0021, ((pow(_statrank, 7) * (_statrank / 16.5)) / 1400000)) + _statrank / 133)) - 1; //double nmvs = ((_vs / _srarea) / (0.1333 * pow(1.0021, ((pow(_statrank, 7) * (_statrank / 16.5)) / 1400000)) + _statrank / 133)) - 1;
double nmapp = (_app / (0.1368803292 * pow(1.0024, (pow(_statrank, 5) / 2800)) + _statrank / 54)) - 1; double nmapp = (_app / (0.1368803292 * pow(1.0024, (pow(_statrank, 5) / 2800)) + _statrank / 54)) - 1;
double nmdss = (_dss / (0.01436466667 * pow(4.1, ((_statrank - 9.6) / 2.9)) + _statrank / 140 + 0.01)) - 1; //double nmdss = (_dss / (0.01436466667 * pow(4.1, ((_statrank - 9.6) / 2.9)) + _statrank / 140 + 0.01)) - 1;
double nmdsp = (_dsp / (0.02136327583 * pow(14, ((_statrank - 14.75) / 3.9)) + _statrank / 152 + 0.022)) - 1; double nmdsp = (_dsp / (0.02136327583 * pow(14, ((_statrank - 14.75) / 3.9)) + _statrank / 152 + 0.022)) - 1;
double nmgbe = (_gbe / (_statrank / 350 + 0.005948424455 * pow(3.8, ((_statrank - 6.1) / 4)) + 0.006)) - 1; double nmgbe = (_gbe / (_statrank / 350 + 0.005948424455 * pow(3.8, ((_statrank - 6.1) / 4)) + 0.006)) - 1;
double nmvsapm = (_vsapm / (-pow(((_statrank - 16) / 36), 2) + 2.133)) - 1; double nmvsapm = (_vsapm / (-pow(((_statrank - 16) / 36), 2) + 2.133)) - 1;
@ -662,12 +666,9 @@ class TetraLeagueAlpha {
nextAt = json['next_at']; nextAt = json['next_at'];
percentileRank = json['percentile_rank']; percentileRank = json['percentile_rank'];
nerdStats = (apm != null && pps != null && apm != null) ? NerdStats(apm!, pps!, vs!) : null; nerdStats = (apm != null && pps != null && apm != null) ? NerdStats(apm!, pps!, vs!) : null;
estTr = estTr = (nerdStats != null) ? EstTr(apm!, pps!, vs!, (rd != null) ? rd! : 69, nerdStats!.app, nerdStats!.dss, nerdStats!.dsp, nerdStats!.gbe) : null;
(nerdStats != null) ? EstTr(apm!, pps!, vs!, rating, (rd != null) ? rd! : 69, nerdStats!.app, nerdStats!.dss, nerdStats!.dsp, nerdStats!.gbe) : null; playstyle =
playstyle = (nerdStats != null) (nerdStats != null) ? Playstyle(apm!, pps!, nerdStats!.app, nerdStats!.vsapm, nerdStats!.dsp, nerdStats!.gbe, estTr!.srarea, estTr!.statrank) : null;
? Playstyle(apm!, pps!, vs!, (rd != null) ? rd! : 69, nerdStats!.app, nerdStats!.vsapm, nerdStats!.dss, nerdStats!.dsp, nerdStats!.gbe, estTr!.srarea,
estTr!.statrank)
: null;
} }
double? get esttracc => (estTr != null) ? estTr!.esttr - rating : null; double? get esttracc => (estTr != null) ? estTr!.esttr - rating : null;
@ -701,6 +702,7 @@ class RecordSingle {
late String userId; late String userId;
late String replayId; late String replayId;
late String ownId; late String ownId;
late String stream;
DateTime? timestamp; DateTime? timestamp;
EndContextSingle? endContext; EndContextSingle? endContext;
int? rank; int? rank;
@ -708,9 +710,11 @@ class RecordSingle {
RecordSingle({required this.userId, required this.replayId, required this.ownId, this.timestamp, this.endContext, this.rank}); RecordSingle({required this.userId, required this.replayId, required this.ownId, this.timestamp, this.endContext, this.rank});
RecordSingle.fromJson(Map<String, dynamic> json, int? ran) { RecordSingle.fromJson(Map<String, dynamic> json, int? ran) {
developer.log("RecordSingle.fromJson: $json", name: "data_objects/tetrio");
ownId = json['_id']; ownId = json['_id'];
endContext = json['endcontext'] != null ? EndContextSingle.fromJson(json['endcontext']) : null; endContext = json['endcontext'] != null ? EndContextSingle.fromJson(json['endcontext']) : null;
replayId = json['replayid']; replayId = json['replayid'];
stream = json['stream'];
timestamp = DateTime.parse(json['ts']); timestamp = DateTime.parse(json['ts']);
userId = json['user']['_id']; userId = json['user']['_id'];
rank = ran; rank = ran;

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:path/path.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'package:tetra_stats/views/main_view.dart'; import 'package:tetra_stats/views/main_view.dart';
import 'package:tetra_stats/views/compare_view.dart'; import 'package:tetra_stats/views/compare_view.dart';
@ -12,6 +11,8 @@ void main() {
runApp(MaterialApp( runApp(MaterialApp(
home: const MainView(), home: const MainView(),
routes: {"/settings": (context) => const SettingsView(), "/compare": (context) => const CompareView(), "/states": (context) => const StatesView()}, routes: {"/settings": (context) => const SettingsView(), "/compare": (context) => const CompareView(), "/states": (context) => const StatesView()},
theme: ThemeData(fontFamily: 'Eurostile Round', colorScheme: const ColorScheme.dark(), scaffoldBackgroundColor: Colors.black), theme: ThemeData(
)); fontFamily: 'Eurostile Round',
colorScheme: const ColorScheme.dark(primary: Colors.cyanAccent, secondary: Colors.purpleAccent),
scaffoldBackgroundColor: Colors.black)));
} }

View File

@ -1,17 +0,0 @@
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 {}

View File

@ -1,11 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'package:sqflite/sqflite.dart'; import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart' import 'package:path_provider/path_provider.dart' show MissingPlatformDirectoryException, getApplicationDocumentsDirectory;
show MissingPlatformDirectoryException, getApplicationDocumentsDirectory;
import 'package:tetra_stats/services/crud_exceptions.dart'; import 'package:tetra_stats/services/crud_exceptions.dart';
import 'package:tetra_stats/services/tetrio_crud.dart'; import 'package:tetra_stats/services/tetrio_crud.dart';
import 'package:tetra_stats/services/settings_crud.dart';
import 'package:path/path.dart' show join; import 'package:path/path.dart' show join;
const String dbName = "TetraStats.db"; const String dbName = "TetraStats.db";
@ -22,7 +19,6 @@ class DB {
final db = await openDatabase(dbPath); final db = await openDatabase(dbPath);
_db = db; _db = db;
await db.execute(createTetrioUsersTable); await db.execute(createTetrioUsersTable);
await db.execute(createSettingsTable);
} on MissingPlatformDirectoryException { } on MissingPlatformDirectoryException {
throw UnableToGetDocuments(); throw UnableToGetDocuments();
} }

View File

@ -1,9 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:sqflite/sqflite.dart'; import 'dart:developer' as developer;
import 'package:path_provider/path_provider.dart' // import 'package:sqflite/sqflite.dart';
show MissingPlatformDirectoryException, getApplicationDocumentsDirectory; // import 'package:path_provider/path_provider.dart' show MissingPlatformDirectoryException, getApplicationDocumentsDirectory;
import 'package:path/path.dart' show join; // import 'package:path/path.dart' show join;
import 'package:tetra_stats/services/crud_exceptions.dart'; import 'package:tetra_stats/services/crud_exceptions.dart';
import 'package:tetra_stats/services/sqlite_db_controller.dart'; import 'package:tetra_stats/services/sqlite_db_controller.dart';
import 'package:tetra_stats/data_objects/tetrio.dart'; import 'package:tetra_stats/data_objects/tetrio.dart';
@ -23,19 +23,22 @@ const String createTetrioUsersTable = '''
class TetrioService { class TetrioService {
Map<String, List<TetrioPlayer>> _players = {}; Map<String, List<TetrioPlayer>> _players = {};
final _tetrioStreamController = final _tetrioStreamController = StreamController<Map<String, List<TetrioPlayer>>>.broadcast();
StreamController<Map<String, List<TetrioPlayer>>>.broadcast();
TetrioService(DB udb) {
_cachePlayers(udb);
}
Future<void> _cachePlayers(DB udb) async { Future<void> _cachePlayers(DB udb) async {
final allPlayers = await getAllPlayers(udb: udb); final allPlayers = await getAllPlayers(udb: udb);
_players = allPlayers.first; _players = allPlayers.first;
_tetrioStreamController.add(_players); _tetrioStreamController.add(_players);
developer.log("_cachePlayers: $_players", name: "services/tetrio_crud");
} }
Future<void> deletePlayer({required String id, required DB udb}) async { Future<void> deletePlayer({required String id, required DB udb}) async {
final db = udb.getDatabaseOrThrow(); final db = udb.getDatabaseOrThrow();
final deletedPlayer = await db.delete(tetrioUsersTable, final deletedPlayer = await db.delete(tetrioUsersTable, where: '$idCol = ?', whereArgs: [id.toLowerCase()]);
where: '$idCol = ?', whereArgs: [id.toLowerCase()]);
if (deletedPlayer != 1) { if (deletedPlayer != 1) {
throw CouldNotDeletePlayer(); throw CouldNotDeletePlayer();
} else { } else {
@ -54,25 +57,14 @@ class TetrioService {
// } // }
// } // }
Future<void> createPlayer( Future<void> createPlayer({required TetrioPlayer tetrioPlayer, required DB udb}) async {
{required TetrioPlayer tetrioPlayer, required DB udb}) async {
final db = udb.getDatabaseOrThrow(); final db = udb.getDatabaseOrThrow();
final results = await db.query(tetrioUsersTable, final results = await db.query(tetrioUsersTable, limit: 1, where: '$idCol = ?', whereArgs: [tetrioPlayer.userId.toLowerCase()]);
limit: 1,
where: '$idCol = ?',
whereArgs: [tetrioPlayer.userId.toLowerCase()]);
if (results.isNotEmpty) { if (results.isNotEmpty) {
throw TetrioPlayerAlreadyExist(); throw TetrioPlayerAlreadyExist();
} }
final Map<String, dynamic> statesJson = { final Map<String, dynamic> statesJson = {tetrioPlayer.state.millisecondsSinceEpoch.toString(): tetrioPlayer.toJson()};
tetrioPlayer.state.millisecondsSinceEpoch.toString(): db.insert(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)});
tetrioPlayer.toJson()
};
db.insert(tetrioUsersTable, {
idCol: tetrioPlayer.userId,
nickCol: tetrioPlayer.username,
statesCol: jsonEncode(statesJson)
});
_players.addEntries({ _players.addEntries({
tetrioPlayer.userId: [tetrioPlayer] tetrioPlayer.userId: [tetrioPlayer]
}.entries); }.entries);
@ -81,40 +73,28 @@ class TetrioService {
Future<void> storeState(TetrioPlayer tetrioPlayer, DB udb) async { Future<void> storeState(TetrioPlayer tetrioPlayer, DB udb) async {
final db = udb.getDatabaseOrThrow(); final db = udb.getDatabaseOrThrow();
List<TetrioPlayer> states = List<TetrioPlayer> states = await getPlayer(id: tetrioPlayer.userId, udb: udb);
await getPlayer(id: tetrioPlayer.userId, udb: udb);
states.add(tetrioPlayer); states.add(tetrioPlayer);
final Map<String, dynamic> statesJson = {}; final Map<String, dynamic> statesJson = {};
for (var e in states) { for (var e in states) {
statesJson.addEntries( statesJson.addEntries({e.state.millisecondsSinceEpoch.toString(): e.toJson()}.entries);
{e.state.millisecondsSinceEpoch.toString(): e.toJson()}.entries);
} }
db.update( db.update(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)},
tetrioUsersTable, where: '$idCol = ?', whereArgs: [tetrioPlayer.userId]);
{
idCol: tetrioPlayer.userId,
nickCol: tetrioPlayer.username,
statesCol: jsonEncode(statesJson)
},
where: '$idCol = ?',
whereArgs: [tetrioPlayer.userId]);
_players[tetrioPlayer.userId]!.add(tetrioPlayer); _players[tetrioPlayer.userId]!.add(tetrioPlayer);
_tetrioStreamController.add(_players); _tetrioStreamController.add(_players);
} }
Future<List<TetrioPlayer>> getPlayer( Future<List<TetrioPlayer>> getPlayer({required String id, required DB udb}) async {
{required String id, required DB udb}) async {
final db = udb.getDatabaseOrThrow(); final db = udb.getDatabaseOrThrow();
List<TetrioPlayer> states = []; List<TetrioPlayer> states = [];
final results = await db.query(tetrioUsersTable, final results = await db.query(tetrioUsersTable, limit: 1, where: '$idCol = ?', whereArgs: [id.toLowerCase()]);
limit: 1, where: '$idCol = ?', whereArgs: [id.toLowerCase()]);
if (results.isEmpty) { if (results.isEmpty) {
throw TetrioPlayerNotExist(); throw TetrioPlayerNotExist();
} else { } else {
dynamic rawStates = results.first['jsonStates'] as String; dynamic rawStates = results.first['jsonStates'] as String;
rawStates = json.decode(rawStates); rawStates = json.decode(rawStates);
rawStates.forEach((k, v) => states.add(TetrioPlayer.fromJson( rawStates.forEach((k, v) => states.add(TetrioPlayer.fromJson(v, DateTime.fromMillisecondsSinceEpoch(int.parse(k)), false)));
v, DateTime.fromMillisecondsSinceEpoch(int.parse(k)))));
_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}.entries);
_tetrioStreamController.add(_players); _tetrioStreamController.add(_players);
@ -122,17 +102,24 @@ class TetrioService {
} }
} }
Future<Iterable<Map<String, List<TetrioPlayer>>>> getAllPlayers( Future<void> _ensureDbIsOpen(DB udb) async {
{required DB udb}) async { try {
//await _ensureDbIsOpen(); await udb.open();
} on DatabaseAlreadyOpen {
// empty
}
}
Future<Iterable<Map<String, List<TetrioPlayer>>>> getAllPlayers({required DB udb}) async {
await _ensureDbIsOpen(udb);
final db = udb.getDatabaseOrThrow(); final db = udb.getDatabaseOrThrow();
final players = await db.query(tetrioUsersTable); final players = await db.query(tetrioUsersTable);
Map<String, List<TetrioPlayer>> data = {}; Map<String, List<TetrioPlayer>> data = {};
//developer.log("getAllPlayers: $players", name: "services/tetrio_crud");
return players.map((row) { return players.map((row) {
var test = json.decode(row['jsonStates'] as String); var test = json.decode(row['jsonStates'] as String);
List<TetrioPlayer> states = []; List<TetrioPlayer> states = [];
test.forEach( test.forEach((k, v) => states.add(TetrioPlayer.fromJson(v, DateTime.fromMillisecondsSinceEpoch(int.parse(k)), false)));
(k, v) => states.add(TetrioPlayer.fromJson(v, DateTime.now())));
data.addEntries({states.last.userId: states}.entries); data.addEntries({states.last.userId: states}.entries);
return data; return data;
}); });

View File

@ -12,12 +12,12 @@ class CompareState extends State<CompareView> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text("you vs someone"), title: const Text("you vs someone"),
), ),
backgroundColor: Colors.black, backgroundColor: Colors.black,
body: SafeArea( body: SafeArea(
child: ListView( child: ListView(
children: [ children: const [
ListTile( ListTile(
title: Center(child: Text("So thats gonna be the main purpose of the app")), title: Center(child: Text("So thats gonna be the main purpose of the app")),
subtitle: Center(child: Text("We gonna look who is the best")), subtitle: Center(child: Text("We gonna look who is the best")),

View File

@ -1,7 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:path/path.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'dart:developer' as developer;
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:tetra_stats/data_objects/tetrio.dart'; import 'package:tetra_stats/data_objects/tetrio.dart';
import 'package:tetra_stats/services/tetrio_crud.dart'; import 'package:tetra_stats/services/tetrio_crud.dart';
import 'package:tetra_stats/services/sqlite_db_controller.dart'; import 'package:tetra_stats/services/sqlite_db_controller.dart';
@ -16,7 +18,8 @@ extension StringExtension on String {
String _searchFor = "dan63047"; String _searchFor = "dan63047";
Future<TetrioPlayer>? me; Future<TetrioPlayer>? me;
DB db = DB(); DB db = DB();
TetrioService teto = TetrioService(); late TetrioService teto;
late SharedPreferences prefs;
const allowedHeightForPlayerIdInPixels = 40.0; const allowedHeightForPlayerIdInPixels = 40.0;
const allowedHeightForPlayerBioInPixels = 30.0; const allowedHeightForPlayerBioInPixels = 30.0;
const givenTextHeightByScreenPercentage = 0.3; const givenTextHeightByScreenPercentage = 0.3;
@ -30,6 +33,10 @@ class MainView extends StatefulWidget {
State<MainView> createState() => _MainState(); State<MainView> createState() => _MainState();
} }
Future<void> copyToClipboard(String text) async {
await Clipboard.setData(ClipboardData(text: text));
}
Future<TetrioPlayer> fetchTetrioPlayer(String user) async { Future<TetrioPlayer> fetchTetrioPlayer(String user) async {
var url = Uri.https('ch.tetr.io', 'api/users/${user.toLowerCase().trim()}'); var url = Uri.https('ch.tetr.io', 'api/users/${user.toLowerCase().trim()}');
final response = await http.get(url); final response = await http.get(url);
@ -37,11 +44,13 @@ Future<TetrioPlayer> fetchTetrioPlayer(String user) async {
if (response.statusCode == 200) { if (response.statusCode == 200) {
if (jsonDecode(response.body)['success']) { if (jsonDecode(response.body)['success']) {
return TetrioPlayer.fromJson( return TetrioPlayer.fromJson(
jsonDecode(response.body)['data']['user'], DateTime.fromMillisecondsSinceEpoch(jsonDecode(response.body)['cache']['cached_at'], isUtc: true)); jsonDecode(response.body)['data']['user'], DateTime.fromMillisecondsSinceEpoch(jsonDecode(response.body)['cache']['cached_at'], isUtc: true), true);
} else { } else {
developer.log("fetchTetrioPlayer User dosen't exist", name: "main_view", error: response.body);
throw Exception("User doesn't exist"); throw Exception("User doesn't exist");
} }
} else { } else {
developer.log("fetchTetrioPlayer Failed to fetch player", name: "main_view", error: response.statusCode);
throw Exception('Failed to fetch player'); throw Exception('Failed to fetch player');
} }
} }
@ -62,7 +71,7 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
Widget _searchTextField() { Widget _searchTextField() {
return TextField( return TextField(
maxLength: 25, maxLength: 25,
decoration: InputDecoration(counter: Offstage()), decoration: const InputDecoration(counter: Offstage()),
style: const TextStyle( style: const TextStyle(
shadows: <Shadow>[ shadows: <Shadow>[
Shadow( Shadow(
@ -78,7 +87,6 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
], ],
), ),
onSubmitted: (String value) { onSubmitted: (String value) {
_tabController.animateTo(0, duration: Duration(milliseconds: 300));
changePlayer(value); changePlayer(value);
}, },
); );
@ -86,10 +94,13 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
@override @override
void initState() { void initState() {
db.open();
teto = TetrioService(db);
_scrollController = ScrollController(); _scrollController = ScrollController();
_tabController = TabController(length: 4, vsync: this); _tabController = TabController(length: 4, vsync: this);
changePlayer("dan63047"); _getPreferences().then((value) => changePlayer(prefs.getString("player") ?? "dan63047"));
super.initState(); super.initState();
developer.log("Main view initialized", name: "main_view");
} }
@override @override
@ -97,28 +108,34 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
_tabController.dispose(); _tabController.dispose();
_scrollController.dispose(); _scrollController.dispose();
super.dispose(); super.dispose();
developer.log("Main view disposed", name: "main_view");
}
Future<void> _getPreferences() async {
prefs = await SharedPreferences.getInstance();
} }
void changePlayer(String player) { void changePlayer(String player) {
setState(() { setState(() {
_tabController.animateTo(0, duration: const Duration(milliseconds: 300));
_searchFor = player; _searchFor = player;
me = fetchTetrioPlayer(player); me = fetchTetrioPlayer(player);
}); });
} }
_scrollListener() { // _scrollListener() {
if (fixedScroll) { // if (fixedScroll) {
_scrollController.jumpTo(0); // _scrollController.jumpTo(0);
} // }
} // }
_smoothScrollToTop() { // _smoothScrollToTop() {
_scrollController.animateTo( // _scrollController.animateTo(
0, // 0,
duration: const Duration(microseconds: 300), // duration: const Duration(microseconds: 300),
curve: Curves.ease, // curve: Curves.ease,
); // );
} // }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -150,7 +167,6 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
? IconButton( ? IconButton(
onPressed: () { onPressed: () {
setState(() { setState(() {
//add
_searchBoolean = true; _searchBoolean = true;
}); });
}, },
@ -160,7 +176,6 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
: IconButton( : IconButton(
onPressed: () { onPressed: () {
setState(() { setState(() {
//add
_searchBoolean = false; _searchBoolean = false;
}); });
}, },
@ -192,6 +207,7 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
child: FutureBuilder<TetrioPlayer>( child: FutureBuilder<TetrioPlayer>(
future: me, future: me,
builder: (context, snapshot) { builder: (context, snapshot) {
developer.log("builder ($context): $snapshot", name: "main_view");
if (snapshot.connectionState == ConnectionState.waiting) { if (snapshot.connectionState == ConnectionState.waiting) {
return const Center( return const Center(
child: CircularProgressIndicator( child: CircularProgressIndicator(
@ -210,8 +226,10 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
controller: _tabController, controller: _tabController,
isScrollable: true, isScrollable: true,
tabs: myTabs, tabs: myTabs,
onTap: (int sus) { onTap: (int tabId) {
setState(() {}); setState(() {
developer.log("Tab changed to $tabId", name: "main_view");
});
}, },
), ),
), ),
@ -438,9 +456,9 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
radarShape: RadarShape.polygon, radarShape: RadarShape.polygon,
tickCount: 4, tickCount: 4,
ticksTextStyle: const TextStyle(color: Colors.transparent, fontSize: 10), ticksTextStyle: const TextStyle(color: Colors.transparent, fontSize: 10),
radarBorderData: BorderSide(color: Colors.transparent, width: 1), radarBorderData: const BorderSide(color: Colors.transparent, width: 1),
gridBorderData: BorderSide(color: Colors.white24, width: 1), gridBorderData: const BorderSide(color: Colors.white24, width: 1),
tickBorderData: BorderSide(color: Colors.transparent, width: 1), tickBorderData: const BorderSide(color: Colors.transparent, width: 1),
getTitle: (index, angle) { getTitle: (index, angle) {
switch (index) { switch (index) {
case 0: case 0:
@ -492,16 +510,16 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
fillColor: Colors.transparent, fillColor: Colors.transparent,
borderColor: Colors.transparent, borderColor: Colors.transparent,
dataEntries: [ dataEntries: [
RadarEntry(value: 0), const RadarEntry(value: 0),
RadarEntry(value: 0), const RadarEntry(value: 0),
RadarEntry(value: 0), const RadarEntry(value: 0),
RadarEntry(value: 0), const RadarEntry(value: 0),
RadarEntry(value: 0), const RadarEntry(value: 0),
RadarEntry(value: 0), const RadarEntry(value: 0),
RadarEntry(value: 0), const RadarEntry(value: 0),
RadarEntry(value: 0), const RadarEntry(value: 0),
RadarEntry(value: 0), const RadarEntry(value: 0),
RadarEntry(value: 0), const RadarEntry(value: 0),
], ],
) )
], ],
@ -521,9 +539,9 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
radarShape: RadarShape.polygon, radarShape: RadarShape.polygon,
tickCount: 4, tickCount: 4,
ticksTextStyle: const TextStyle(color: Colors.transparent, fontSize: 10), ticksTextStyle: const TextStyle(color: Colors.transparent, fontSize: 10),
radarBorderData: BorderSide(color: Colors.transparent, width: 1), radarBorderData: const BorderSide(color: Colors.transparent, width: 1),
gridBorderData: BorderSide(color: Colors.white24, width: 1), gridBorderData: const BorderSide(color: Colors.white24, width: 1),
tickBorderData: BorderSide(color: Colors.transparent, width: 1), tickBorderData: const BorderSide(color: Colors.transparent, width: 1),
getTitle: (index, angle) { getTitle: (index, angle) {
switch (index) { switch (index) {
case 0: case 0:
@ -557,20 +575,20 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
fillColor: Colors.transparent, fillColor: Colors.transparent,
borderColor: Colors.transparent, borderColor: Colors.transparent,
dataEntries: [ dataEntries: [
RadarEntry(value: 0), const RadarEntry(value: 0),
RadarEntry(value: 0), const RadarEntry(value: 0),
RadarEntry(value: 0), const RadarEntry(value: 0),
RadarEntry(value: 0), const RadarEntry(value: 0),
], ],
), ),
RadarDataSet( RadarDataSet(
fillColor: Colors.transparent, fillColor: Colors.transparent,
borderColor: Colors.transparent, borderColor: Colors.transparent,
dataEntries: [ dataEntries: [
RadarEntry(value: 1), const RadarEntry(value: 1),
RadarEntry(value: 1), const RadarEntry(value: 1),
RadarEntry(value: 1), const RadarEntry(value: 1),
RadarEntry(value: 1), const RadarEntry(value: 1),
], ],
) )
], ],
@ -1087,7 +1105,8 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
), ),
); );
} else if (snapshot.hasError) { } else if (snapshot.hasError) {
return Center(child: Text('${snapshot.error}', style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42))); return Center(
child: Text('${snapshot.error}', style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42), textAlign: TextAlign.center));
} }
return const Center( return const Center(
child: CircularProgressIndicator( child: CircularProgressIndicator(
@ -1101,8 +1120,8 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
} }
class NavDrawer extends StatelessWidget { class NavDrawer extends StatelessWidget {
Function changePlayer; final Function changePlayer;
NavDrawer(this.changePlayer, {super.key}); const NavDrawer(this.changePlayer, {super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -1116,10 +1135,11 @@ class NavDrawer extends StatelessWidget {
style: TextStyle(color: Colors.white, fontSize: 25), style: TextStyle(color: Colors.white, fontSize: 25),
)), )),
ListTile( ListTile(
leading: const Icon(Icons.verified_user), leading: const Icon(Icons.home),
title: const Text('dan63047'), title: Text(prefs.getString("player") ?? "dan63047"),
onTap: () { onTap: () {
changePlayer('dan63047'); developer.log("Navigator changed player", name: "main_view");
changePlayer(prefs.getString("player") ?? "dan63047");
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
), ),
@ -1178,7 +1198,7 @@ class _StatCellNum extends StatelessWidget {
class _UserThingy extends StatelessWidget { class _UserThingy extends StatelessWidget {
final TetrioPlayer player; final TetrioPlayer player;
_UserThingy({Key? key, required this.player}) : super(key: key); const _UserThingy({Key? key, required this.player}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -1201,6 +1221,12 @@ class _UserThingy extends StatelessWidget {
"https://tetr.io/user-content/banners/${player.userId}.jpg?rv=${player.bannerRevision}", "https://tetr.io/user-content/banners/${player.userId}.jpg?rv=${player.bannerRevision}",
fit: BoxFit.cover, fit: BoxFit.cover,
height: bannerHeight, height: bannerHeight,
errorBuilder: (context, error, stackTrace) {
developer.log("Error with building banner image", name: "main_view", error: error, stackTrace: stackTrace);
return const Placeholder(
color: Colors.black,
);
},
), ),
Container( Container(
padding: EdgeInsets.fromLTRB(0, player.bannerRevision != null ? bannerHeight / 1.4 : pfpHeight, 0, 0), padding: EdgeInsets.fromLTRB(0, player.bannerRevision != null ? bannerHeight / 1.4 : pfpHeight, 0, 0),
@ -1212,15 +1238,20 @@ class _UserThingy extends StatelessWidget {
fit: BoxFit.fitHeight, fit: BoxFit.fitHeight,
height: 128, height: 128,
) )
: Image.network( : player.avatarRevision != null
"https://tetr.io/user-content/avatars/${player.userId}.jpg?rv=${player.avatarRevision}", ? Image.network("https://tetr.io/user-content/avatars/${player.userId}.jpg?rv=${player.avatarRevision}",
fit: BoxFit.fitHeight, fit: BoxFit.fitHeight, height: 128, errorBuilder: (context, error, stackTrace) {
height: 128, developer.log("Error with building profile picture", name: "main_view", error: error, stackTrace: stackTrace);
errorBuilder: (context, error, stackTrace) => Image.asset( return Image.asset(
"res/avatars/tetrio_anon.png",
fit: BoxFit.fitHeight,
height: 128,
);
})
: Image.asset(
"res/avatars/tetrio_anon.png", "res/avatars/tetrio_anon.png",
fit: BoxFit.fitHeight, fit: BoxFit.fitHeight,
height: 128, height: 128,
),
), ),
), ),
), ),
@ -1230,10 +1261,12 @@ class _UserThingy extends StatelessWidget {
child: Column( child: Column(
children: [ children: [
Text(player.username, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), Text(player.username, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
Text( TextButton(
player.userId, child: Text(player.userId, style: const TextStyle(fontFamily: "Eurostile Round Condensed", fontSize: 14)),
style: const TextStyle(fontFamily: "Eurostile Round Condensed", fontSize: 14), onPressed: () {
), copyToClipboard(player.userId);
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Copied to clipboard!")));
}),
], ],
), ),
), ),
@ -1311,7 +1344,6 @@ class _UserThingy extends StatelessWidget {
IconButton( IconButton(
onPressed: () => showDialog<void>( onPressed: () => showDialog<void>(
context: context, context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) { builder: (BuildContext context) {
return AlertDialog( return AlertDialog(
title: Text( title: Text(
@ -1350,6 +1382,10 @@ class _UserThingy extends StatelessWidget {
"res/tetrio_badges/${badge.badgeId}.png", "res/tetrio_badges/${badge.badgeId}.png",
height: 64, height: 64,
width: 64, width: 64,
errorBuilder: (context, error, stackTrace) {
developer.log("Error with building $badge", name: "main_view", error: error, stackTrace: stackTrace);
return Image.asset("res/icons/kagari.png", height: 64, width: 64);
},
)) ))
], ],
), ),

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SettingsView extends StatefulWidget { class SettingsView extends StatefulWidget {
const SettingsView({Key? key}) : super(key: key); const SettingsView({Key? key}) : super(key: key);
@ -10,11 +11,14 @@ class SettingsView extends StatefulWidget {
class SettingsState extends State<SettingsView> { class SettingsState extends State<SettingsView> {
PackageInfo _packageInfo = PackageInfo(appName: "TetraStats", packageName: "idk man", version: "some numbers", buildNumber: "anotherNumber"); PackageInfo _packageInfo = PackageInfo(appName: "TetraStats", packageName: "idk man", version: "some numbers", buildNumber: "anotherNumber");
late SharedPreferences prefs;
final TextEditingController _playertext = TextEditingController();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_initPackageInfo(); _initPackageInfo();
_getPreferences();
} }
Future<void> _initPackageInfo() async { Future<void> _initPackageInfo() async {
@ -24,6 +28,14 @@ class SettingsState extends State<SettingsView> {
}); });
} }
Future<void> _getPreferences() async {
prefs = await SharedPreferences.getInstance();
}
Future<void> _setPlayer(String player) async {
await prefs.setString('player', player);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -37,22 +49,23 @@ class SettingsState extends State<SettingsView> {
ListTile( ListTile(
title: const Text("So there you gonna be able to change some settings"), title: const Text("So there you gonna be able to change some settings"),
subtitle: const Text( subtitle: const Text(
"They are not implemented yet. But its gonna be possible to change player for main view init, save logs, as well as import and export app sqlite database."), "Only \"Your TETR.IO account nickname or ID\" implemented yet. But its gonna be possible to change player for main view init, save logs, as well as import and export app sqlite database."),
trailing: Switch( trailing: Switch(
value: true, value: true,
onChanged: (bool value) {}, onChanged: (bool value) {},
), ),
), ),
ListTile( ListTile(
title: const Text("Very egg"), title: const Text("Your TETR.IO account nickname or ID"),
subtitle: const Text("very ass"), subtitle:
trailing: const Text("dan63047"), const Text("Every time when app loads, stats of that player will be fetched. Please prefer ID over nickname because nickname can be changed."),
trailing: Text(prefs.getString("player") ?? "dan63047"),
onTap: () => showDialog( onTap: () => showDialog(
context: context, context: context,
builder: (BuildContext context) => AlertDialog( builder: (BuildContext context) => AlertDialog(
title: const Text("Your username in TETR.IO", style: TextStyle(fontFamily: "Eurostile Round Extended")), title: const Text("Your TETR.IO account nickname or ID", style: TextStyle(fontFamily: "Eurostile Round Extended")),
content: SingleChildScrollView( content: SingleChildScrollView(
child: ListBody(children: [const TextField()]), child: ListBody(children: [TextField(controller: _playertext)]),
), ),
actions: <Widget>[ actions: <Widget>[
TextButton( TextButton(
@ -64,13 +77,15 @@ class SettingsState extends State<SettingsView> {
TextButton( TextButton(
child: const Text('Submit'), child: const Text('Submit'),
onPressed: () { onPressed: () {
_setPlayer(_playertext.text.toLowerCase().trim());
Navigator.of(context).pop(); Navigator.of(context).pop();
setState(() {});
}, },
) )
], ],
)), )),
), ),
Divider(), const Divider(),
ListTile( ListTile(
title: const Text("About app"), title: const Text("About app"),
subtitle: Text("${_packageInfo.appName} (${_packageInfo.packageName}) Version ${_packageInfo.version} Build ${_packageInfo.buildNumber}"), subtitle: Text("${_packageInfo.appName} (${_packageInfo.packageName}) Version ${_packageInfo.version} Build ${_packageInfo.buildNumber}"),

View File

@ -7,10 +7,12 @@ import Foundation
import package_info_plus import package_info_plus
import path_provider_foundation import path_provider_foundation
import shared_preferences_foundation
import sqflite import sqflite
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
} }

View File

@ -264,6 +264,62 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.2.4" version: "4.2.4"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: "16d3fb6b3692ad244a695c0183fca18cf81fd4b821664394a781de42386bf022"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "6478c6bbbecfe9aced34c483171e90d7c078f5883558b30ec3163cf18402c749"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: e014107bb79d6d3297196f4f2d0db54b5d1f85b8ea8ff63b8e8b391a02700feb
url: "https://pub.dev"
source: hosted
version: "2.2.2"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "9d387433ca65717bbf1be88f4d5bb18f10508917a8fa2fb02e0fd0d7479a9afa"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: fb5cf25c0235df2d0640ac1b1174f6466bd311f621574997ac59018a6664548d
url: "https://pub.dev"
source: hosted
version: "2.2.0"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: "74083203a8eae241e0de4a0d597dbedab3b8fef5563f33cf3c12d7e93c655ca5"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "5e588e2efef56916a3b229c3bfe81e6a525665a454519ca51dbcc4236a274173"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter

View File

@ -14,7 +14,7 @@ publish_to: 'none'
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 0.0.1+1 version: 0.0.2+2
environment: environment:
sdk: '>=2.19.6 <3.0.0' sdk: '>=2.19.6 <3.0.0'
@ -37,6 +37,7 @@ dependencies:
path: ^1.8.2 path: ^1.8.2
fl_chart: ^0.62.0 fl_chart: ^0.62.0
package_info_plus: ^4.0.2 package_info_plus: ^4.0.2
shared_preferences: ^2.1.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: