A lot of things, 2.0.3 is ready to release

- Can fetch S2 history
- New averages
- Damage calculator fix
- Icon for linux runner
- We can build .deb now
This commit is contained in:
dan63047 2025-01-18 01:00:46 +03:00
parent 0ee2b83bd7
commit ee3bbe6369
25 changed files with 273 additions and 161 deletions

View File

@ -62,6 +62,8 @@ jobs:
type: 'zip' type: 'zip'
filename: TetraStats-${{github.ref_name}}-linux.zip filename: TetraStats-${{github.ref_name}}-linux.zip
directory: build/linux/x64/release/bundle directory: build/linux/x64/release/bundle
- name: Build .deb package
run: dart run flutter_to_debian
- name: Push to Releases - name: Push to Releases
uses: ncipollo/release-action@v1 uses: ncipollo/release-action@v1
with: with:
@ -69,7 +71,7 @@ jobs:
allowUpdates: true allowUpdates: true
replacesArtifacts: false replacesArtifacts: false
discussionCategory: autobuilded-releases discussionCategory: autobuilded-releases
artifacts: "build/linux/x64/release/bundle/TetraStats-${{github.ref_name}}-linux.zip" artifacts: "build/linux/x64/release/bundle/TetraStats-${{github.ref_name}}-linux.zip,build/linux/x64/release/debian/*"
tag: Auto-${{ github.run_number }} tag: Auto-${{ github.run_number }}
body: Build with GitHub Action workflow body: Build with GitHub Action workflow
token: ${{ secrets.TOKEN }} token: ${{ secrets.TOKEN }}

View File

@ -1,11 +1,11 @@
# This file tracks properties of this Flutter project. # This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc. # Used by Flutter tool to assess capabilities and perform upgrades etc.
# #
# This file should be version controlled. # This file should be version controlled and should not be manually edited.
version: version:
revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf revision: "b0850beeb25f6d5b10426284f506557f66181b36"
channel: stable channel: "stable"
project_type: app project_type: app
@ -13,26 +13,26 @@ project_type: app
migration: migration:
platforms: platforms:
- platform: root - platform: root
create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf create_revision: b0850beeb25f6d5b10426284f506557f66181b36
base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf base_revision: b0850beeb25f6d5b10426284f506557f66181b36
- platform: android - platform: android
create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf create_revision: b0850beeb25f6d5b10426284f506557f66181b36
base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf base_revision: b0850beeb25f6d5b10426284f506557f66181b36
- platform: ios - platform: ios
create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf create_revision: b0850beeb25f6d5b10426284f506557f66181b36
base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf base_revision: b0850beeb25f6d5b10426284f506557f66181b36
- platform: linux - platform: linux
create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf create_revision: b0850beeb25f6d5b10426284f506557f66181b36
base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf base_revision: b0850beeb25f6d5b10426284f506557f66181b36
- platform: macos - platform: macos
create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf create_revision: b0850beeb25f6d5b10426284f506557f66181b36
base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf base_revision: b0850beeb25f6d5b10426284f506557f66181b36
- platform: web - platform: web
create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf create_revision: b0850beeb25f6d5b10426284f506557f66181b36
base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf base_revision: b0850beeb25f6d5b10426284f506557f66181b36
- platform: windows - platform: windows
create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf create_revision: b0850beeb25f6d5b10426284f506557f66181b36
base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf base_revision: b0850beeb25f6d5b10426284f506557f66181b36
# User provided section # User provided section

View File

@ -0,0 +1,5 @@
package com.dan63.tetra_stats
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity()

3
debian/debian.yaml vendored
View File

@ -2,10 +2,11 @@ flutter_app:
command: tetra_stats command: tetra_stats
arch: x64 arch: x64
parent: /usr/local/lib parent: /usr/local/lib
nonInteractive: true
control: control:
Package: tetra-stats Package: tetra-stats
Version: 0.2.0 Version: 2.0.3
Architecture: amd64 Architecture: amd64
Essential: no Essential: no
Priority: optional Priority: optional

View File

@ -1,5 +1,5 @@
[Desktop Entry] [Desktop Entry]
Version=0.2.0 Version=2.0.3
Name=Tetra Stats Name=Tetra Stats
GenericName=Tetra Stats GenericName=Tetra Stats
Comment=Track your and other player stats in TETR.IO Comment=Track your and other player stats in TETR.IO

View File

@ -0,0 +1,12 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
const int currentSeason = 2; const int currentSeason = 2;
final DateTime sprintAndBlitzRelevance = DateTime(2024, 8, 25); final DateTime sprintAndBlitzRelevance = DateTime(2025, 1, 16);
const double noTrRd = 60.9; const double noTrRd = 60.9;
const double apmWeight = 1; const double apmWeight = 1;
const double ppsWeight = 45; const double ppsWeight = 45;
@ -210,46 +210,46 @@ const List<Color> achievementColors = [
]; ];
const Map<String, Duration> sprintAverages = { const Map<String, Duration> sprintAverages = {
// based on https://discord.com/channels/673303546107658242/674421736162197515/1277367281264889908 // based on https://discord.com/channels/673303546107658242/1260605501754839060/1329448681539244094
'x+': Duration(seconds: 18, milliseconds: 867), 'x+': Duration(seconds: 19, milliseconds: 223),
'x': Duration(seconds: 23, milliseconds: 277), 'x': Duration(seconds: 24, milliseconds: 832),
'u': Duration(seconds: 28, milliseconds: 853), 'u': Duration(seconds: 32, milliseconds: 586),
'ss': Duration(seconds: 35, milliseconds: 173), 'ss': Duration(seconds: 40, milliseconds: 011),
's+': Duration(seconds: 39, milliseconds: 028), 's+': Duration(seconds: 47, milliseconds: 963),
's': Duration(seconds: 45, milliseconds: 807), 's': Duration(seconds: 54, milliseconds: 413),
's-': Duration(seconds: 48, milliseconds: 840), 's-': Duration(seconds: 61, milliseconds: 740),
'a+': Duration(seconds: 54, milliseconds: 975), 'a+': Duration(seconds: 70, milliseconds: 101),
'a': Duration(seconds: 60, milliseconds: 287), 'a': Duration(seconds: 73, milliseconds: 294),
'a-': Duration(seconds: 64, milliseconds: 019), 'a-': Duration(seconds: 81, milliseconds: 773),
'b+': Duration(seconds: 76, milliseconds: 531), 'b+': Duration(seconds: 88, milliseconds: 647),
'b': Duration(seconds: 77, milliseconds: 635), 'b': Duration(seconds: 97, milliseconds: 699),
'b-': Duration(seconds: 92, milliseconds: 279), 'b-': Duration(seconds: 105, milliseconds: 721),
'c+': Duration(seconds: 97, milliseconds: 911), 'c+': Duration(seconds: 113, milliseconds: 229),
'c': Duration(seconds: 104, milliseconds: 700), 'c': Duration(seconds: 124, milliseconds: 740),
'c-': Duration(seconds: 115, milliseconds: 173), 'c-': Duration(seconds: 129, milliseconds: 382),
'd+': Duration(seconds: 131, milliseconds: 486), 'd+': Duration(seconds: 138, milliseconds: 947),
'd': Duration(seconds: 158, milliseconds: 397), 'd': Duration(seconds: 155, milliseconds: 190),
}; };
const Map<String, int> blitzAverages = { const Map<String, int> blitzAverages = {
'x+': 879378, 'x+': 886046,
'x': 677479, 'x': 631014,
'u': 485962, 'u': 428799,
'ss': 369043, 'ss': 296430,
's+': 279242, 's+': 212237,
's': 245619, 's': 157234,
's-': 199368, 's-': 122791,
'a+': 162035, 'a+': 103031,
'a': 130949, 'a': 90174,
'a-': 111505, 'a-': 73474,
'b+': 97251, 'b+': 60655,
'b': 83580, 'b': 52463,
'b-': 70511, 'b-': 43877,
'c+': 56747, 'c+': 36594,
'c': 43002, 'c': 34014,
'c-': 38925, 'c-': 29613,
'd+': 30483, 'd+': 31521,
'd': 22513, 'd': 23437,
}; };
List<DateTime> seasonStarts = [ List<DateTime> seasonStarts = [

View File

@ -6,7 +6,7 @@
/// Locales: 3 /// Locales: 3
/// Strings: 2295 (765 per locale) /// Strings: 2295 (765 per locale)
/// ///
/// Built on 2024-12-31 at 17:29 UTC /// Built on 2025-01-14 at 21:20 UTC
// coverage:ignore-file // coverage:ignore-file
// ignore_for_file: type=lint // ignore_for_file: type=lint
@ -707,7 +707,7 @@ class _StringsGraphsDestinationEn {
final Translations _root; // ignore: unused_field final Translations _root; // ignore: unused_field
// Translations // Translations
String get fetchAndsaveTLHistory => 'Get player history'; String get fetchAndsaveTLHistory => 'Fetch History';
String get fetchAndSaveOldTLmatches => 'Get Tetra League matches history'; String get fetchAndSaveOldTLmatches => 'Get Tetra League matches history';
String fetchAndsaveTLHistoryResult({required Object number}) => '${number} states was found'; String fetchAndsaveTLHistoryResult({required Object number}) => '${number} states was found';
String fetchAndSaveOldTLmatchesResult({required Object number}) => '${number} matches was found'; String fetchAndSaveOldTLmatchesResult({required Object number}) => '${number} matches was found';
@ -2247,7 +2247,7 @@ class _StringsGraphsDestinationRuRu implements _StringsGraphsDestinationEn {
@override final _StringsRuRu _root; // ignore: unused_field @override final _StringsRuRu _root; // ignore: unused_field
// Translations // Translations
@override String get fetchAndsaveTLHistory => 'Получить историю игрока'; @override String get fetchAndsaveTLHistory => 'Получить историю';
@override String get fetchAndSaveOldTLmatches => 'Получить историю матчей Тетра Лиги'; @override String get fetchAndSaveOldTLmatches => 'Получить историю матчей Тетра Лиги';
@override String fetchAndsaveTLHistoryResult({required Object number}) => '${number} состояний было найдено'; @override String fetchAndsaveTLHistoryResult({required Object number}) => '${number} состояний было найдено';
@override String fetchAndSaveOldTLmatchesResult({required Object number}) => '${number} матчей было найдено'; @override String fetchAndSaveOldTLmatchesResult({required Object number}) => '${number} матчей было найдено';
@ -4925,7 +4925,7 @@ extension on Translations {
case 'actions.ok': return 'OK'; case 'actions.ok': return 'OK';
case 'actions.apply': return 'Apply'; case 'actions.apply': return 'Apply';
case 'actions.refresh': return 'Refresh'; case 'actions.refresh': return 'Refresh';
case 'graphsDestination.fetchAndsaveTLHistory': return 'Get player history'; case 'graphsDestination.fetchAndsaveTLHistory': return 'Fetch History';
case 'graphsDestination.fetchAndSaveOldTLmatches': return 'Get Tetra League matches history'; case 'graphsDestination.fetchAndSaveOldTLmatches': return 'Get Tetra League matches history';
case 'graphsDestination.fetchAndsaveTLHistoryResult': return ({required Object number}) => '${number} states was found'; case 'graphsDestination.fetchAndsaveTLHistoryResult': return ({required Object number}) => '${number} states was found';
case 'graphsDestination.fetchAndSaveOldTLmatchesResult': return ({required Object number}) => '${number} matches was found'; case 'graphsDestination.fetchAndSaveOldTLmatchesResult': return ({required Object number}) => '${number} matches was found';
@ -5739,7 +5739,7 @@ extension on _StringsRuRu {
case 'actions.ok': return 'ОК'; case 'actions.ok': return 'ОК';
case 'actions.apply': return 'Применить'; case 'actions.apply': return 'Применить';
case 'actions.refresh': return 'Обновить'; case 'actions.refresh': return 'Обновить';
case 'graphsDestination.fetchAndsaveTLHistory': return 'Получить историю игрока'; case 'graphsDestination.fetchAndsaveTLHistory': return 'Получить историю';
case 'graphsDestination.fetchAndSaveOldTLmatches': return 'Получить историю матчей Тетра Лиги'; case 'graphsDestination.fetchAndSaveOldTLmatches': return 'Получить историю матчей Тетра Лиги';
case 'graphsDestination.fetchAndsaveTLHistoryResult': return ({required Object number}) => '${number} состояний было найдено'; case 'graphsDestination.fetchAndsaveTLHistoryResult': return ({required Object number}) => '${number} состояний было найдено';
case 'graphsDestination.fetchAndSaveOldTLmatchesResult': return ({required Object number}) => '${number} матчей было найдено'; case 'graphsDestination.fetchAndSaveOldTLmatchesResult': return ({required Object number}) => '${number} матчей было найдено';

View File

@ -88,7 +88,7 @@ class DB {
} }
Future<bool> checkImportingDB(File db) async { Future<bool> checkImportingDB(File db) async {
final newDB = await openDatabase(db.path); final newDB = await openDatabase(db.path); // TODO: Maybe i should use arguments, that this method provides?
var usersTable = await newDB.rawQuery("PRAGMA table_xinfo(`${tetrioUsersTable}`);"); var usersTable = await newDB.rawQuery("PRAGMA table_xinfo(`${tetrioUsersTable}`);");
List<String> usersTableRows = [for (Map<String, Object?> row in usersTable) row["name"] as String]; List<String> usersTableRows = [for (Map<String, Object?> row in usersTable) row["name"] as String];
if (!listEquals(usersTableRows, tetrioUsersTableRows)) return false; if (!listEquals(usersTableRows, tetrioUsersTableRows)) return false;

View File

@ -4,9 +4,11 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:developer' as developer; import 'dart:developer' as developer;
import 'dart:io'; import 'dart:io';
import 'dart:math';
import 'package:path/path.dart'; import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'package:tetra_stats/data_objects/beta_record.dart';
import 'package:tetra_stats/data_objects/cutoff_tetrio.dart'; import 'package:tetra_stats/data_objects/cutoff_tetrio.dart';
import 'package:tetra_stats/data_objects/end_context_multi.dart'; import 'package:tetra_stats/data_objects/end_context_multi.dart';
import 'package:tetra_stats/data_objects/news.dart'; import 'package:tetra_stats/data_objects/news.dart';
@ -24,8 +26,6 @@ import 'package:tetra_stats/data_objects/tetrio_multiplayer_replay.dart';
import 'package:tetra_stats/data_objects/tetrio_player.dart'; import 'package:tetra_stats/data_objects/tetrio_player.dart';
import 'package:tetra_stats/data_objects/tetrio_player_from_leaderboard.dart'; import 'package:tetra_stats/data_objects/tetrio_player_from_leaderboard.dart';
import 'package:tetra_stats/data_objects/tetrio_players_leaderboard.dart'; import 'package:tetra_stats/data_objects/tetrio_players_leaderboard.dart';
import 'package:tetra_stats/data_objects/tetrio_zen.dart';
import 'package:tetra_stats/data_objects/user_records.dart';
import 'package:tetra_stats/main.dart' show packageInfo; import 'package:tetra_stats/main.dart' show packageInfo;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:tetra_stats/services/custom_http_client.dart'; import 'package:tetra_stats/services/custom_http_client.dart';
@ -372,7 +372,7 @@ class TetrioService extends DB {
dbPath = join(docsPath.path, dbName); dbPath = join(docsPath.path, dbName);
} }
var dbFile = File(dbPath); var dbFile = File(dbPath);
var dbSize = (await dbFile.stat()).size; var dbSize = kIsWeb ? -1 : (await dbFile.stat()).size;
var dbTLRecordsQuery = (await db.rawQuery('SELECT COUNT(*) FROM `${tetraLeagueMatchesTable}`')).first['COUNT(*)']! as int; var dbTLRecordsQuery = (await db.rawQuery('SELECT COUNT(*) FROM `${tetraLeagueMatchesTable}`')).first['COUNT(*)']! as int;
var dbTLStatesQuery = (await db.rawQuery('SELECT COUNT(*) FROM `${tetrioLeagueTable}`')).first['COUNT(*)']! as int; var dbTLStatesQuery = (await db.rawQuery('SELECT COUNT(*) FROM `${tetrioLeagueTable}`')).first['COUNT(*)']! as int;
return (dbSize, dbTLRecordsQuery, dbTLStatesQuery); return (dbSize, dbTLRecordsQuery, dbTLStatesQuery);
@ -673,8 +673,7 @@ class TetrioService extends DB {
/// Retrieves Tetra League history from p1nkl0bst3r api for a player with given [id]. Returns a list of states /// Retrieves Tetra League history from p1nkl0bst3r api for a player with given [id]. Returns a list of states
/// (state = instance of [TetrioPlayer] at some point of time). Can throw an exception if fails to retrieve data. /// (state = instance of [TetrioPlayer] at some point of time). Can throw an exception if fails to retrieve data.
Future<List<TetraLeague>> fetchAndsaveTLHistory(String id, int season) async { Future<List<TetraLeague>> fetchAndsaveS1TLHistory(String id) async {
// TODO: find le way to get season 2 history
Uri url; Uri url;
if (kIsWeb) { if (kIsWeb) {
url = Uri.https(webVersionDomain, 'oskware_bridge.php', {"endpoint": "TLHistory", "user": id}); url = Uri.https(webVersionDomain, 'oskware_bridge.php', {"endpoint": "TLHistory", "user": id});
@ -745,6 +744,70 @@ class TetrioService extends DB {
} }
} }
Future<List<TetraLeague>> fetchAndsaveS2TLHistory(String id) async {
final db = getDatabaseOrThrow();
List<BetaRecord> records = [];
int entries = 100;
String? prisecter;
while (entries > 0){
TetraLeagueBetaStream stream = await fetchTLStream(id, prisecter: prisecter);
if (stream.records.isEmpty) break;
records.addAll(stream.records);
prisecter = stream.records.last.prisecter.toString();
entries = stream.records.length;
}
//TetraLeague currentState = await fetchTLSummary(id);
List<TetraLeague> states = [];
//states.add(currentState);
int gp = 0;
int gw = 0;
List<double> last10apm = [];
List<double> last10pps = [];
List<double> last10vs = [];
int bestRankIndex = -1; // -1 - Z; 0 - D, 1 - D+ ... 18 - X+
Batch batch = db.batch();
for (BetaRecord match in records.reversed){
gp++;
if (match.extras.result.contains("victory")) gw++;
last10apm.add(match.results.leaderboard.firstWhere((e) => e.id == id).stats.apm);
if (last10apm.length > 10) last10apm.removeAt(0);
last10pps.add(match.results.leaderboard.firstWhere((e) => e.id == id).stats.pps);
if (last10pps.length > 10) last10pps.removeAt(0);
last10vs.add(match.results.leaderboard.firstWhere((e) => e.id == id).stats.vs);
if (last10vs.length > 10) last10vs.removeAt(0);
double apm = last10apm.reduce((v, e) => v + e) / last10apm.length;
double pps = last10pps.reduce((v, e) => v + e) / last10pps.length;
double vs = last10vs.reduce((v, e) => v + e) / last10vs.length;
TetraLeague state = TetraLeague(
id: id,
timestamp: match.ts,
gamesPlayed: gp,
gamesWon: gw,
bestRank: bestRankIndex != -1 ? ranks[bestRankIndex] : "z",
decaying: false,
tr: match.extras.league[id]?[1]?.tr??-1.0,
glicko: match.extras.league[id]?[1]?.glicko,
rd: match.extras.league[id]?[1]?.rd,
gxe: match.extras.league[id]?[1]?.glicko != null ? 10000 / (1 + pow(10, (((1500 - match.extras.league[id]![1]!.glicko) * pi / sqrt(3 * pow(ln10, 2) * pow(match.extras.league[id]![1]!.rd, 2) + 2500 * (64 * pow(pi, 2) + 147 * pow(ln10, 2))))))) / 100 : -1,
rank: match.extras.league[id]?[1]?.rank??"z",
percentileRank: match.extras.league[id]?[1]?.rank??"z",
percentile: match.extras.league[id]?[1]?.rank != null ? rankCutoffs[match.extras.league[id]![1]!.rank]! : -1,
standing: match.extras.league[id]?[1]?.placement??-1,
standingLocal: -1,
nextAt: -1,
prevAt: -1,
apm: apm,
pps: pps,
vs: vs,
season: currentSeason
);
states.add(state);
batch.insert(tetrioLeagueTable, state.toJson(), conflictAlgorithm: ConflictAlgorithm.replace);
}
batch.commit();
return states;
}
/// Docs later /// Docs later
Future<TetraLeagueAlphaStream> fetchAndSaveOldTLmatches(String userID) async { Future<TetraLeagueAlphaStream> fetchAndSaveOldTLmatches(String userID) async {
Uri url; Uri url;
@ -1129,38 +1192,29 @@ 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 [userID] from Tetra Channel api. Returns `UserRecords`. Future<TetraLeague> fetchTLSummary(String id) async {
/// Throws an exception if fails to retrieve. TetraLeague? cached = _cache.get(id, TetraLeague);
Future<UserRecords> fetchRecords(String userID) async {
UserRecords? cached = _cache.get(userID, UserRecords);
if (cached != null) return cached; if (cached != null) return cached;
Uri url; Uri url;
if (kIsWeb) { if (kIsWeb) {
url = Uri.https(webVersionDomain, 'oskware_bridge.php', {"endpoint": "tetrioUserRecords", "user": userID.toLowerCase().trim()}); url = Uri.https(webVersionDomain, 'oskware_bridge.php', {"endpoint": "Summaries", "id": id});
} else { } else {
url = Uri.https('ch.tetr.io', 'api/users/${userID.toLowerCase().trim()}/records'); url = Uri.https('ch.tetr.io', 'api/users/$id/summaries/league');
} }
try{ try{
final response = await client.get(url); final response = await client.get(url);
switch (response.statusCode) { switch (response.statusCode) {
case 200: case 200:
if (jsonDecode(response.body)['success']) { if (jsonDecode(response.body)['success']) {
Map jsonRecords = jsonDecode(response.body); developer.log("fetchTLSummary: $id TL state retrieved and cached", name: "services/tetrio_crud");
var sprint = jsonRecords['data']['records']['40l']['record'] != null TetraLeague league = TetraLeague.fromJson(jsonDecode(response.body)['data'], DateTime.now(), currentSeason, id);
? RecordSingle.fromJson(jsonRecords['data']['records']['40l']['record'], jsonRecords['data']['records']['40l']['rank'], jsonRecords['data']['records']['40l']['rank_local']) _cache.store(league, jsonDecode(response.body)['cache']['cached_until']);
: null; return league;
var blitz = jsonRecords['data']['records']['blitz']['record'] != null
? RecordSingle.fromJson(jsonRecords['data']['records']['blitz']['record'], jsonRecords['data']['records']['blitz']['rank'], jsonRecords['data']['records']['blitz']['rank_local'])
: null;
var zen = TetrioZen.fromJson(jsonRecords['data']['zen']);
UserRecords result = UserRecords(userID, sprint, blitz, zen);
_cache.store(result, jsonDecode(response.body)['cache']['cached_until']);
developer.log("fetchRecords: $userID records retrieved and cached", name: "services/tetrio_crud");
return result;
} else { } else {
developer.log("fetchRecords User dosen't exist", name: "services/tetrio_crud", error: response.body); developer.log("fetchTLSummary: User dosen't exist", name: "services/tetrio_crud", error: response.body);
throw TetrioPlayerNotExist(); throw TetrioPlayerNotExist();
} }
case 403: case 403:
@ -1175,7 +1229,7 @@ class TetrioService extends DB {
case 504: case 504:
throw TetrioInternalProblem(); throw TetrioInternalProblem();
default: default:
developer.log("fetchRecords Failed to fetch records", name: "services/tetrio_crud", error: response.statusCode); developer.log("fetchTLSummary Failed to fetch TL state", name: "services/tetrio_crud", error: response.statusCode);
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason"); throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
} }
} on http.ClientException catch (e, s) { } on http.ClientException catch (e, s) {
@ -1427,15 +1481,4 @@ class TetrioService extends DB {
} }
return data; return data;
} }
// Future<void> fetchTracked() async {
// for (String userID in (await getAllPlayerToTrack())) {
// TetrioPlayer player = await fetchPlayer(userID);
// storeState(player);
// sleep(Durations.extralong4);
// TetraLeagueBetaStream matches = await fetchTLStream(userID);
// saveTLMatchesFromStream(matches);
// sleep(Durations.extralong4);
// }
// }
} }

View File

@ -76,7 +76,7 @@ class ClearData{
if (rules.combo && rules.comboTable != ComboTables.none) { if (rules.combo && rules.comboTable != ComboTables.none) {
if (combo >= 1){ if (combo >= 1){
if (lines == 1 && rules.comboTable != ComboTables.multiplier) damage += combotable[rules.comboTable]![max(0, min(combo - 1, combotable[rules.comboTable]!.length - 1))]; if (rules.comboTable != ComboTables.multiplier) damage += combotable[rules.comboTable]![max(0, min(combo - 1, combotable[rules.comboTable]!.length - 1))];
else damage *= (1 + COMBO_BONUS * (combo)); else damage *= (1 + COMBO_BONUS * (combo));
} }
if (combo >= 2) { if (combo >= 2) {
@ -166,7 +166,7 @@ class _DestinationCalculatorState extends State<DestinationCalculator> {
List<ClearData> clears = []; List<ClearData> clears = [];
Map<String, int> customClearsChoice = { Map<String, int> customClearsChoice = {
t.calcDestination.noSpinClears: 5, t.calcDestination.noSpinClears: 5,
t.calcDestination.spins: 5 t.stats.spins: 5
}; };
int idCounter = 0; int idCounter = 0;
Rules rules = Rules(); Rules rules = Rules();
@ -400,7 +400,13 @@ class _DestinationCalculatorState extends State<DestinationCalculator> {
), ),
onTap: (){ onTap: (){
setState((){ setState((){
clears.add(ClearData("${key == t.calcDestination.spins ? "${t.stats.spin} " : ""}${clearNames[min(customClearsChoice[key]!, clearNames.length-1)]} (${customClearsChoice[key]!} ${t.stats.lines})", key == t.calcDestination.spins ? Lineclears.TSPIN_PENTA : Lineclears.PENTA, customClearsChoice[key]!, false, key == t.calcDestination.spins).cloneWith(idCounter)); clears.add(ClearData(
"${key == t.stats.spins ? "${t.stats.spin} " : ""}${clearNames[min(customClearsChoice[key]!, clearNames.length-1)]} (${customClearsChoice[key]!} ${t.stats.lines})",
key == t.stats.spins ? Lineclears.TSPIN_PENTA : Lineclears.PENTA,
customClearsChoice[key]!,
false,
key == t.stats.spins).cloneWith(idCounter)
);
}); });
idCounter++; idCounter++;
}, },

View File

@ -51,6 +51,7 @@ class _DestinationGraphsState extends State<DestinationGraphs> {
ValueNotifier<String> historyPlayerUsername = ValueNotifier(""); ValueNotifier<String> historyPlayerUsername = ValueNotifier("");
ValueNotifier<String> historyPlayerAvatarRevizion = ValueNotifier(""); ValueNotifier<String> historyPlayerAvatarRevizion = ValueNotifier("");
List<String> excludeRanks = []; List<String> excludeRanks = [];
late Future<Map<int, Map<Stats, List<_HistoryChartSpot>>>> playerHistory = getHistoryData(fetchData);
late Future<List<_MyScatterSpot>> futureLeague = getTetraLeagueData(_Xchart, Ychart); late Future<List<_MyScatterSpot>> futureLeague = getTetraLeagueData(_Xchart, Ychart);
String searchLeague = ""; String searchLeague = "";
int? TLstatePlayers; int? TLstatePlayers;
@ -143,9 +144,11 @@ class _DestinationGraphsState extends State<DestinationGraphs> {
} }
Future<Map<int, Map<Stats, List<_HistoryChartSpot>>>> getHistoryData(bool fetchHistory) async { Future<Map<int, Map<Stats, List<_HistoryChartSpot>>>> getHistoryData(bool fetchHistory) async {
var playerID = (await teto.fetchPlayer(widget.searchFor)).userId;
if(fetchHistory){ if(fetchHistory){
try{ try{
var history = await teto.fetchAndsaveTLHistory(widget.searchFor, 1); //var history = await Future.wait([teto.fetchAndsaveS1TLHistory(widget.searchFor), teto.fetchAndsaveS2TLHistory(widget.searchFor)]); // S1 history unavaliable because of certificate issue on p1nkl0bst3r side
var history = await teto.fetchAndsaveS2TLHistory(playerID);
if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.graphsDestination.fetchAndsaveTLHistoryResult(number: history.length)))); if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.graphsDestination.fetchAndsaveTLHistoryResult(number: history.length))));
}on TetrioHistoryNotExist{ }on TetrioHistoryNotExist{
if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.noHistorySaved))); if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.noHistorySaved)));
@ -159,19 +162,19 @@ class _DestinationGraphsState extends State<DestinationGraphs> {
} }
List<List<TetraLeague>> states = await Future.wait<List<TetraLeague>>([ List<List<TetraLeague>> states = await Future.wait<List<TetraLeague>>([
teto.getStates(widget.searchFor, season: 1), teto.getStates(widget.searchFor, season: 2), teto.getStates(playerID, season: 1), teto.getStates(playerID, season: 2),
]); ]);
Map<int, Map<Stats, List<_HistoryChartSpot>>> historyData = {}; // [season][metric][spot] Map<int, Map<Stats, List<_HistoryChartSpot>>> historyData = {}; // [season][metric][spot]
for (int season = 0; season < currentSeason; season++){ for (int season = 0; season < currentSeason; season++){
if (states[season].length >= 2){ if (states[season].length >= 2){
Map<Stats, List<_HistoryChartSpot>> statsMap = {}; Map<Stats, List<_HistoryChartSpot>> statsMap = {};
for (var stat in Stats.values) statsMap[stat] = [for (var tl in states[season]) if (tl.getStatByEnum(stat) != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.getStatByEnum(stat)!.toDouble())]; for (var stat in Stats.values) statsMap[stat] = [for (var tl in states[season]) if (tl.getStatByEnum(stat) != null && tl.getStatByEnum(stat) != -1.00) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.getStatByEnum(stat)!.toDouble())];
historyData[season] = statsMap; historyData[season] = statsMap;
} }
} }
fetchData = false; fetchData = false;
historyPlayerUsername.value = await teto.getNicknameByID(widget.searchFor); historyPlayerUsername.value = await teto.getNicknameByID(playerID);
return historyData; return historyData;
} }
@ -234,7 +237,7 @@ class _DestinationGraphsState extends State<DestinationGraphs> {
trendlines:<Trendline>[ trendlines:<Trendline>[
Trendline( Trendline(
isVisible: _smooth, isVisible: _smooth,
period: (selectedGraph.length/175).floor(), period: (selectedGraph.length/100).floor(),
type: TrendlineType.movingAverage, type: TrendlineType.movingAverage,
color: Theme.of(context).colorScheme.primary) color: Theme.of(context).colorScheme.primary)
], ],
@ -250,7 +253,7 @@ class _DestinationGraphsState extends State<DestinationGraphs> {
trendlines:<Trendline>[ trendlines:<Trendline>[
Trendline( Trendline(
isVisible: _smooth, isVisible: _smooth,
period: (selectedGraph.length/175).floor(), period: (selectedGraph.length/100).floor(),
type: TrendlineType.movingAverage, type: TrendlineType.movingAverage,
color: Theme.of(context).colorScheme.primary) color: Theme.of(context).colorScheme.primary)
], ],
@ -532,7 +535,16 @@ class _DestinationGraphsState extends State<DestinationGraphs> {
); );
}); });
}, icon: Icon(Icons.filter_alt)), }, icon: Icon(Icons.filter_alt)),
IconButton(onPressed: () => _zoomPanBehavior.reset(), icon: const Icon(Icons.refresh), alignment: Alignment.center,) IconButton(onPressed: () => _zoomPanBehavior.reset(), icon: const Icon(Icons.refresh), alignment: Alignment.center,),
if (graph == Graph.history) ElevatedButton.icon(
onPressed: (){
setState(() {
fetchData = true;
});
},
label: Text(t.graphsDestination.fetchAndsaveTLHistory),
icon: Icon(Icons.download),
)
], ],
), ),
), ),
@ -581,6 +593,11 @@ class _DestinationGraphsState extends State<DestinationGraphs> {
), ),
); );
} }
void markNeedsBuild() {
}
} }
class _HistoryChartSpot{ class _HistoryChartSpot{

View File

@ -447,7 +447,7 @@ class _DestinationSettings extends State<DestinationSettings> with SingleTickerP
text: TextSpan( text: TextSpan(
style: TextStyle(fontFamily: "Eurostile Round", color: Colors.white), style: TextStyle(fontFamily: "Eurostile Round", color: Colors.white),
children: [ children: [
TextSpan(text: "${bytesToSize(snapshot.data!.$1)} ", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)), TextSpan(text: "${snapshot.data!.$1 == -1 ? "???" : bytesToSize(snapshot.data!.$1)} ", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)),
TextSpan(text: "${t.settingsDestination.bytesOfDataStored}\n"), TextSpan(text: "${t.settingsDestination.bytesOfDataStored}\n"),
TextSpan(text: "${intf.format(snapshot.data!.$2)} ", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)), TextSpan(text: "${intf.format(snapshot.data!.$2)} ", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)),
TextSpan(text: "${t.settingsDestination.TLrecordsSaved}\n"), TextSpan(text: "${t.settingsDestination.TLrecordsSaved}\n"),
@ -457,7 +457,7 @@ class _DestinationSettings extends State<DestinationSettings> with SingleTickerP
) )
); );
} }
if (snapshot.hasError){ return FutureError(snapshot); } if (snapshot.hasError){ return SizedBox(height: 500.0, child: FutureError(snapshot)); }
} }
return Text("huh?"); return Text("huh?");
} }

View File

@ -41,7 +41,6 @@ Future<FetchResults> getData(String searchFor, {bool withHistory = false}) async
}else{ }else{
player = await teto.fetchPlayer(searchFor); // Otherwise it's probably a user id or username player = await teto.fetchPlayer(searchFor); // Otherwise it's probably a user id or username
} }
}on TetrioPlayerNotExist{ }on TetrioPlayerNotExist{
return FetchResults(false, null, [], null, null, null, null, null, false, TetrioPlayerNotExist()); return FetchResults(false, null, [], null, null, null, null, null, false, TetrioPlayerNotExist());
} }
@ -62,7 +61,7 @@ Future<FetchResults> getData(String searchFor, {bool withHistory = false}) async
cutoffs = requests.elementAtOrNull(2); cutoffs = requests.elementAtOrNull(2);
averages = requests.elementAtOrNull(3); averages = requests.elementAtOrNull(3);
if(withHistory) await teto.fetchAndsaveTLHistory(player.userId, 1); // Retrieve if needed if(withHistory) await teto.fetchAndsaveS1TLHistory(player.userId); // Retrieve if needed
} on Exception catch (e) { } on Exception catch (e) {
return FetchResults(false, null, [], null, null, null, null, null, false, e); return FetchResults(false, null, [], null, null, null, null, null, false, e);
} }

View File

@ -57,44 +57,38 @@ class SprintAndBlitzState extends State<SprintAndBlitzView> {
constraints: const BoxConstraints(maxWidth: 600), constraints: const BoxConstraints(maxWidth: 600),
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Table(
mainAxisSize: MainAxisSize.min, defaultVerticalAlignment: TableCellVerticalAlignment.middle,
border: TableBorder.all(color: Colors.grey.shade900),
columnWidths: const {0: FixedColumnWidth(48)},
children: [ children: [
Table( TableRow(
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
border: TableBorder.all(color: Colors.grey.shade900),
columnWidths: const {0: FixedColumnWidth(48)},
children: [ children: [
TableRow( Text(t.rank, textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white)),
children: [ Padding(
Text(t.rank, textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white)), padding: const EdgeInsets.only(right: 8.0),
Padding( child: Text(t.gamemodes["40l"]!, textAlign: TextAlign.right, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round" : "Eurostile Round Condensed", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white)),
padding: const EdgeInsets.only(right: 8.0),
child: Text(t.gamemodes["40l"]!, textAlign: TextAlign.right, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round" : "Eurostile Round Condensed", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white)),
),
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Text(t.gamemodes["blitz"]!, textAlign: TextAlign.right, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round" : "Eurostile Round Condensed", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white)),
),
]
), ),
for (MapEntry<String, Duration> sprintEntry in sprintAverages.entries) TableRow( Padding(
decoration: BoxDecoration(gradient: LinearGradient(colors: [rankColors[sprintEntry.key]!.withAlpha(100), rankColors[sprintEntry.key]!.withAlpha(200)])), padding: const EdgeInsets.only(right: 8.0),
children: [ child: Text(t.gamemodes["blitz"]!, textAlign: TextAlign.right, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round" : "Eurostile Round Condensed", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white)),
Container(decoration: BoxDecoration(boxShadow: [BoxShadow(color: Colors.black.withAlpha(132), blurRadius: 32.0, blurStyle: BlurStyle.inner)]), child: Image.asset("res/tetrio_tl_alpha_ranks/${sprintEntry.key}.png", height: 48)), ),
Padding( ]
padding: const EdgeInsets.only(right: 8.0),
child: Text(getALittleBitMoreNormalTime(sprintEntry.value), textAlign: TextAlign.right, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round" : "Eurostile Round Condensed", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white, shadows: textShadow)),
),
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Text(NumberFormat.decimalPattern().format(blitzAverages[sprintEntry.key]), textAlign: TextAlign.right, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round" : "Eurostile Round Condensed", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white, shadows: textShadow)),
),
]
)
],
), ),
Text(t.sprintAndBlitsRelevance(date: dateFormat.format(DateTime(2024, 8, 25)))) for (MapEntry<String, Duration> sprintEntry in sprintAverages.entries) TableRow(
decoration: BoxDecoration(gradient: LinearGradient(colors: [rankColors[sprintEntry.key]!.withAlpha(100), rankColors[sprintEntry.key]!.withAlpha(200)])),
children: [
Container(decoration: BoxDecoration(boxShadow: [BoxShadow(color: Colors.black.withAlpha(132), blurRadius: 32.0, blurStyle: BlurStyle.inner)]), child: Image.asset("res/tetrio_tl_alpha_ranks/${sprintEntry.key}.png", height: 48)),
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Text(getALittleBitMoreNormalTime(sprintEntry.value), textAlign: TextAlign.right, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round" : "Eurostile Round Condensed", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white, shadows: textShadow)),
),
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Text(NumberFormat.decimalPattern().format(blitzAverages[sprintEntry.key]), textAlign: TextAlign.right, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round" : "Eurostile Round Condensed", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white, shadows: textShadow)),
),
]
)
], ],
), ),
), ),

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:tetra_stats/data_objects/beta_record.dart'; import 'package:tetra_stats/data_objects/beta_record.dart';
import 'package:tetra_stats/gen/strings.g.dart'; import 'package:tetra_stats/gen/strings.g.dart';
import 'package:tetra_stats/utils/numers_formats.dart'; import 'package:tetra_stats/utils/numers_formats.dart';
@ -9,8 +10,8 @@ import 'package:tetra_stats/widgets/text_timestamp.dart';
class BetaLeagueEntryThingy extends StatelessWidget{ class BetaLeagueEntryThingy extends StatelessWidget{
final BetaRecord record; final BetaRecord record;
final String userID; final String userID;
// TODO: Rating delta string is too long for small screens final bool wide;
const BetaLeagueEntryThingy(this.record, this.userID); const BetaLeagueEntryThingy(this.record, this.userID, this.wide);
TextSpan matchResult(String result){ TextSpan matchResult(String result){
return switch(result){ return switch(result){
@ -57,6 +58,7 @@ class BetaLeagueEntryThingy extends StatelessWidget{
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
NumberFormat diff = wide ? fDiff : comparef2;
double? deltaTR = (record.extras.league[userID]?[1]?.tr != null && record.extras.league[userID]?[0]?.tr != null) ? record.extras.league[userID]![1]!.tr - record.extras.league[userID]![0]!.tr : null; double? deltaTR = (record.extras.league[userID]?[1]?.tr != null && record.extras.league[userID]?[0]?.tr != null) ? record.extras.league[userID]![1]!.tr - record.extras.league[userID]![0]!.tr : null;
double? deltaGlicko = (record.extras.league[userID]?[1]?.glicko != null && record.extras.league[userID]?[0]?.glicko != null) ? record.extras.league[userID]![1]!.glicko - record.extras.league[userID]![0]!.glicko : null; double? deltaGlicko = (record.extras.league[userID]?[1]?.glicko != null && record.extras.league[userID]?[0]?.glicko != null) ? record.extras.league[userID]![1]!.glicko - record.extras.league[userID]![0]!.glicko : null;
double? deltaRD = (record.extras.league[userID]?[1]?.rd != null && record.extras.league[userID]?[0]?.rd != null) ? record.extras.league[userID]![1]!.rd - record.extras.league[userID]![0]!.rd : null; double? deltaRD = (record.extras.league[userID]?[1]?.rd != null && record.extras.league[userID]?[0]?.rd != null) ? record.extras.league[userID]![1]!.rd - record.extras.league[userID]![0]!.rd : null;
@ -88,7 +90,7 @@ class BetaLeagueEntryThingy extends StatelessWidget{
text: ", ${timestamp(record.ts)}\n" text: ", ${timestamp(record.ts)}\n"
), ),
TextSpan( TextSpan(
text: deltaTR != null ? "${fDiff.format(deltaTR)} TR" : "??? TR", text: deltaTR != null ? "${diff.format(deltaTR)} TR" : "??? TR",
style: TextStyle( style: TextStyle(
color: deltaColor(deltaTR) color: deltaColor(deltaTR)
) )
@ -97,7 +99,7 @@ class BetaLeagueEntryThingy extends StatelessWidget{
text: ", " text: ", "
), ),
TextSpan( TextSpan(
text: deltaGlicko != null ? "${fDiff.format(deltaGlicko)} Glicko" : "??? Glicko", text: deltaGlicko != null ? "${diff.format(deltaGlicko)} Glicko" : "??? Glicko",
style: TextStyle( style: TextStyle(
color: deltaColor(deltaGlicko) color: deltaColor(deltaGlicko)
) )
@ -106,7 +108,7 @@ class BetaLeagueEntryThingy extends StatelessWidget{
text: ", " text: ", "
), ),
TextSpan( TextSpan(
text: deltaRD != null ? "${fDiff.format(deltaRD)} RD" : "??? RD", text: deltaRD != null ? "${diff.format(deltaRD)} RD" : "??? RD",
style: TextStyle( style: TextStyle(
color: Colors.grey color: Colors.grey
) )

View File

@ -65,7 +65,7 @@ class TLRatingThingy extends StatelessWidget{
} : [TextSpan(text: "---\n", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28, color: Colors.grey)), TextSpan(text: t.gamesUntilRanked(left: 10-tlData.gamesPlayed), style: const TextStyle(color: Colors.grey, fontSize: 14)),] } : [TextSpan(text: "---\n", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28, color: Colors.grey)), TextSpan(text: t.gamesUntilRanked(left: 10-tlData.gamesPlayed), style: const TextStyle(color: Colors.grey, fontSize: 14)),]
) )
), ),
if (oldTl != null) RichText( if (oldTl != null && oldTl!.tr != -1.0) RichText(
textAlign: TextAlign.center, textAlign: TextAlign.center,
softWrap: true, softWrap: true,
text: TextSpan( text: TextSpan(

View File

@ -103,7 +103,7 @@ class _TLRecordsState extends State<TLRecords> {
), ),
), ),
itemBuilder: (BuildContext context, int index){ itemBuilder: (BuildContext context, int index){
return BetaLeagueEntryThingy(records[index], widget.userID); return BetaLeagueEntryThingy(records[index], widget.userID, MediaQuery.of(context).size.width >= 768.0);
} }
), ),
); );

View File

@ -47,7 +47,7 @@ class TetraLeagueThingy extends StatelessWidget{
message: "${t.stats.glixare.full}", message: "${t.stats.glixare.full}",
child: Tooltip(child: Text(" ${t.stats.glixare.short}", style: TextStyle(fontSize: width > 768.0 ? 21 : 18, color: league.gxe.isNegative ? Colors.grey : Colors.white)), message: "Glixare") child: Tooltip(child: Text(" ${t.stats.glixare.short}", style: TextStyle(fontSize: width > 768.0 ? 21 : 18, color: league.gxe.isNegative ? Colors.grey : Colors.white)), message: "Glixare")
), ),
if (toCompare != null) Text(" (${comparef.format(league.gxe-toCompare!.gxe)})", textAlign: TextAlign.right, style: TextStyle(fontSize: width > 768.0 ? 21 : 18, color: getDifferenceColor(league.gxe-toCompare!.gxe))), if (toCompare != null) Text(toCompare!.gxe != -1 ? " (${comparef.format(league.gxe-toCompare!.gxe)})" : "(---)", textAlign: TextAlign.right, style: TextStyle(fontSize: width > 768.0 ? 21 : 18, color: toCompare!.gxe != -1 ? getDifferenceColor(league.gxe-toCompare!.gxe) : Colors.grey)),
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)) 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))
]), ]),
]; ];
@ -60,7 +60,7 @@ class TetraLeagueThingy extends StatelessWidget{
child: Column( child: Column(
children: [ children: [
TLRatingThingy(userID: league.id, tlData: league, oldTl: toCompare, showPositions: true), TLRatingThingy(userID: league.id, tlData: league, oldTl: toCompare, showPositions: true),
if (league.gamesPlayed > 9) TLProgress( if (league.gamesPlayed > 9 && league.percentileRank != "z") TLProgress(
tlData: league, tlData: league,
previousRankTRcutoff: cutoffs != null ? cutoffs!.tr[league.rank != "z" ? league.rank : league.percentileRank] : null, previousRankTRcutoff: cutoffs != null ? cutoffs!.tr[league.rank != "z" ? league.rank : league.percentileRank] : null,
nextRankTRcutoff: cutoffs != null ? cutoffs!.tr[ranks2[ranks2.indexOf(league.rank != "z" ? league.rank : league.percentileRank)-1]] : null, nextRankTRcutoff: cutoffs != null ? cutoffs!.tr[ranks2[ranks2.indexOf(league.rank != "z" ? league.rank : league.percentileRank)-1]] : null,

View File

@ -19,6 +19,8 @@ static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application); MyApplication* self = MY_APPLICATION(application);
GtkWindow* window = GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
gtk_window_set_icon_from_file(GTK_WINDOW(window),"res/icons/app.png",NULL);
// Use a header bar when running in GNOME as this is the common style used // Use a header bar when running in GNOME as this is the common style used
// by applications and is the setup most users will be using (e.g. Ubuntu // by applications and is the setup most users will be using (e.g. Ubuntu

View File

@ -0,0 +1,12 @@
import Cocoa
import FlutterMacOS
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

View File

@ -328,6 +328,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_to_debian:
dependency: "direct main"
description:
name: flutter_to_debian
sha256: d23534407334b331ce20fbaa8395b9ecc255d0c047136b8998715f36933ee696
url: "https://pub.dev"
source: hosted
version: "2.0.2"
flutter_web_plugins: flutter_web_plugins:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -509,6 +517,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.5" version: "1.0.5"
mime_type:
dependency: transitive
description:
name: mime_type
sha256: d652b613e84dac1af28030a9fba82c0999be05b98163f9e18a0849c6e63838bb
url: "https://pub.dev"
source: hosted
version: "1.0.1"
node_preamble: node_preamble:
dependency: transitive dependency: transitive
description: description:

View File

@ -2,7 +2,7 @@ name: tetra_stats
description: Track your and other player stats in TETR.IO description: Track your and other player stats in TETR.IO
publish_to: 'none' publish_to: 'none'
version: 2.0.2+43 version: 2.0.3+44
environment: environment:
sdk: '>=3.0.0' sdk: '>=3.0.0'
@ -46,6 +46,7 @@ dependencies:
flutter_layout_grid: ^2.0.0 flutter_layout_grid: ^2.0.0
go_router: ^13.0.0 go_router: ^13.0.0
syncfusion_flutter_charts: ^24.2.9 syncfusion_flutter_charts: ^24.2.9
flutter_to_debian: ^2.0.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@ -172,7 +172,7 @@
"refresh": "Обновить" "refresh": "Обновить"
}, },
"graphsDestination": { "graphsDestination": {
"fetchAndsaveTLHistory": "Получить историю игрока", "fetchAndsaveTLHistory": "Получить историю",
"fetchAndSaveOldTLmatches": "Получить историю матчей Тетра Лиги", "fetchAndSaveOldTLmatches": "Получить историю матчей Тетра Лиги",
"fetchAndsaveTLHistoryResult": "${number} состояний было найдено", "fetchAndsaveTLHistoryResult": "${number} состояний было найдено",
"fetchAndSaveOldTLmatchesResult": "${number} матчей было найдено", "fetchAndSaveOldTLmatchesResult": "${number} матчей было найдено",

View File

@ -131,7 +131,7 @@
} }
</style> </style>
<!-- This script adds the flutter initialization JS code --> <!-- This script adds the flutter initialization JS code -->
<script src="flutter.js?version=2.0.2" defer></script> <script src="flutter.js?version=2.0.3" defer></script>
</head> </head>
<body> <body>
<div id="preloader"> <div id="preloader">