A lot of bugfixes, ready for 2.0.0
Bug fixes: Bages now always loads from tetrio Compare view add new player thingy now can show loading indicator leaderboard fixes Full leaderboard now can show it's relevance Some small ui fixes Lazy loading for TL records
18
README.md
|
@ -2,21 +2,19 @@
|
||||||
|
|
||||||
Track your and other players stats in TETR.IO
|
Track your and other players stats in TETR.IO
|
||||||
|
|
||||||
|
Tetra Stats works with TETR.IO Tetra Channel API, providing data from it and calculating some addtitional metrics, based on this data.
|
||||||
|
|
||||||
You can [download an app](https://github.com/dan63047/TetraStats/releases), or [use web version](https://ts.dan63.by).
|
You can [download an app](https://github.com/dan63047/TetraStats/releases), or [use web version](https://ts.dan63.by).
|
||||||
|
|
||||||
![Screenshot of the app 1](https://imgur.com/e8CYvj3.png)
|
![Screenshot of the app 1](https://imgur.com/e8CYvj3.png)
|
||||||
|
|
||||||
# Available functionality
|
# Available functionality
|
||||||
- Advanced stats for players
|
- Advanced stats for players
|
||||||
|
- Charts for analyzing players Tetra League standing and Tetra League itself
|
||||||
- Ranks cutoffs
|
- Ranks cutoffs
|
||||||
- Minimums, averages, and maximums for every stat of every rank, as well, as whole leaderboard
|
- Full and sortable Tetra League leagerboard
|
||||||
- Chart for analyzing tetra league state
|
- Stats and Damage Calculator
|
||||||
- Local database, that can store players data
|
- Local database, that can store players data
|
||||||
- Comparison to players, rank averages, and player stats from the past
|
|
||||||
- Stats Calculator
|
|
||||||
- Player history in charts
|
|
||||||
- Tetra League matches history
|
|
||||||
- Time-weighted stats in Tetra League matches
|
|
||||||
|
|
||||||
# Special thanks
|
# Special thanks
|
||||||
- **kerrmunism** — formulas
|
- **kerrmunism** — formulas
|
||||||
|
@ -24,5 +22,7 @@ You can [download an app](https://github.com/dan63047/TetraStats/releases), or [
|
||||||
- **neko_ab4093** — Simplified Chinese localization
|
- **neko_ab4093** — Simplified Chinese localization
|
||||||
- **osk** and his team — TETR.IO
|
- **osk** and his team — TETR.IO
|
||||||
|
|
||||||
## Legal note
|
## Legal notes
|
||||||
I do NOT own any assets located in `/res/*`, excluding app icon (`/res/icons/app.png`) and localization (`/res/i18n/*`), which is distributed under GNU license (as well, as this software)
|
Tetra Stats is not associated with TETR.IO or osk in any capacity.
|
||||||
|
|
||||||
|
I do NOT own any assets located in `/res/*`, excluding app icon (`/res/icons/app.png`), localization (`/res/i18n/*`) and images (`/res/images/*`), which is distributed under GNU license (as well, as this software)
|
|
@ -28,7 +28,7 @@ class Summaries {
|
||||||
json['40l']['rank_local']);
|
json['40l']['rank_local']);
|
||||||
if (json['blitz']['record'] != null)
|
if (json['blitz']['record'] != null)
|
||||||
blitz = RecordSingle.fromJson(json['blitz']['record'],
|
blitz = RecordSingle.fromJson(json['blitz']['record'],
|
||||||
json['blitz']['rank'], json['40l']['rank_local']);
|
json['blitz']['rank'], json['blitz']['rank_local']);
|
||||||
if (json['zenith']['record'] != null)
|
if (json['zenith']['record'] != null)
|
||||||
zenith = RecordSingle.fromJson(json['zenith']['record'],
|
zenith = RecordSingle.fromJson(json['zenith']['record'],
|
||||||
json['zenith']['rank'], json['zenith']['rank_local']);
|
json['zenith']['rank'], json['zenith']['rank_local']);
|
||||||
|
|
|
@ -38,7 +38,7 @@ class Garbage{ // charsys where???
|
||||||
late int sent;
|
late int sent;
|
||||||
late int recived;
|
late int recived;
|
||||||
int? attack;
|
int? attack;
|
||||||
late int cleared;
|
int? cleared;
|
||||||
int? sent_normal;
|
int? sent_normal;
|
||||||
int? maxspike;
|
int? maxspike;
|
||||||
int? maxspike_nomult;
|
int? maxspike_nomult;
|
||||||
|
@ -65,7 +65,7 @@ class Garbage{ // charsys where???
|
||||||
}
|
}
|
||||||
|
|
||||||
Garbage operator + (Garbage other){
|
Garbage operator + (Garbage other){
|
||||||
return Garbage(sent: sent + other.sent, recived: recived + other.recived, attack: attack??0 + (other.attack??0), cleared: cleared + other.cleared);
|
return Garbage(sent: sent + other.sent, recived: recived + other.recived, attack: attack??0 + (other.attack??0), cleared: (cleared??0) + (other.cleared??0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ import 'package:tetra_stats/services/sqlite_db_controller.dart';
|
||||||
import 'package:csv/csv.dart';
|
import 'package:csv/csv.dart';
|
||||||
|
|
||||||
const String dbName = "TetraStats.db";
|
const String dbName = "TetraStats.db";
|
||||||
const String webVersionDomain = "ts.dan63.by";
|
const String webVersionDomain = "tsbeta.dan63.by";
|
||||||
const String tetrioUsersTable = "tetrioUsers";
|
const String tetrioUsersTable = "tetrioUsers";
|
||||||
const String tetrioUsersToTrackTable = "tetrioUsersToTrack";
|
const String tetrioUsersToTrackTable = "tetrioUsersToTrack";
|
||||||
const String tetraLeagueMatchesTable = "tetrioAlphaLeagueMathces";
|
const String tetraLeagueMatchesTable = "tetrioAlphaLeagueMathces";
|
||||||
|
@ -888,13 +888,17 @@ class TetrioService extends DB {
|
||||||
Future<List<RecordSingle>> fetchTetrioRecordsLeaderboard({String? prisecter, String? lb, String? country}) async{
|
Future<List<RecordSingle>> fetchTetrioRecordsLeaderboard({String? prisecter, String? lb, String? country}) async{
|
||||||
Uri url;
|
Uri url;
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
url = Uri.https(webVersionDomain, 'oskware_bridge.php', {"endpoint": "TLLeaderboard"});
|
url = Uri.https(webVersionDomain, 'oskware_bridge.php', {
|
||||||
} else {
|
"endpoint": "RecordsLeaderboard",
|
||||||
url = Uri.https('ch.tetr.io', 'api/records/${lb??"40l_global"}', {
|
"lb": lb??"40l",
|
||||||
"limit": "100",
|
|
||||||
if (prisecter != null) "after": prisecter,
|
if (prisecter != null) "after": prisecter,
|
||||||
if (country != null) "country": country
|
if (country != null) "country": country
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
url = Uri.https('ch.tetr.io', 'api/records/${lb??"40l"}_${country != null ? "country_${country}":"global"}', {
|
||||||
|
"limit": "100",
|
||||||
|
if (prisecter != null) "after": prisecter
|
||||||
|
});
|
||||||
}
|
}
|
||||||
try{
|
try{
|
||||||
final response = await client.get(url);
|
final response = await client.get(url);
|
||||||
|
@ -908,12 +912,12 @@ class TetrioService extends DB {
|
||||||
for (Map<String, dynamic> entry in rawJson['data']['entries']) {
|
for (Map<String, dynamic> entry in rawJson['data']['entries']) {
|
||||||
leaderboard.add(RecordSingle.fromJson(entry, -1, -1));
|
leaderboard.add(RecordSingle.fromJson(entry, -1, -1));
|
||||||
}
|
}
|
||||||
developer.log("fetchTLLeaderboard: Leaderboard retrieved and cached", name: "services/tetrio_crud");
|
developer.log("fetchTetrioRecordsLeaderboard: Leaderboard retrieved and cached", name: "services/tetrio_crud");
|
||||||
//_leaderboardsCache[rawJson['cache']['cached_until'].toString()] = leaderboard;
|
//_leaderboardsCache[rawJson['cache']['cached_until'].toString()] = leaderboard;
|
||||||
//_cache.store(leaderboard, rawJson['cache']['cached_until']);
|
//_cache.store(leaderboard, rawJson['cache']['cached_until']);
|
||||||
return leaderboard;
|
return leaderboard;
|
||||||
} else { // idk how to hit that one
|
} else { // idk how to hit that one
|
||||||
developer.log("fetchTLLeaderboard: Bruh", name: "services/tetrio_crud", error: rawJson);
|
developer.log("fetchTetrioRecordsLeaderboard: Bruh", name: "services/tetrio_crud", error: rawJson);
|
||||||
throw Exception("Failed to get leaderboard (problems on the tetr.io side)"); // will it be on tetr.io side?
|
throw Exception("Failed to get leaderboard (problems on the tetr.io side)"); // will it be on tetr.io side?
|
||||||
}
|
}
|
||||||
case 403:
|
case 403:
|
||||||
|
@ -928,7 +932,7 @@ class TetrioService extends DB {
|
||||||
case 504:
|
case 504:
|
||||||
throw TetrioInternalProblem();
|
throw TetrioInternalProblem();
|
||||||
default:
|
default:
|
||||||
developer.log("fetchTLLeaderboard: Failed to fetch leaderboard", name: "services/tetrio_crud", error: response.statusCode);
|
developer.log("fetchTetrioRecordsLeaderboard: Failed to fetch leaderboard", 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) {
|
||||||
|
@ -990,15 +994,22 @@ class TetrioService extends DB {
|
||||||
|
|
||||||
/// Retrieves avaliable Tetra League matches from Tetra Channel api. Returns stream object (fake stream).
|
/// Retrieves avaliable Tetra League matches from Tetra Channel api. Returns stream object (fake stream).
|
||||||
/// Throws an exception if fails to retrieve.
|
/// Throws an exception if fails to retrieve.
|
||||||
Future<TetraLeagueBetaStream> fetchTLStream(String userID) async {
|
Future<TetraLeagueBetaStream> fetchTLStream(String userID, {String? prisecter}) async {
|
||||||
TetraLeagueBetaStream? cached = _cache.get(userID, TetraLeagueBetaStream);
|
// TetraLeagueBetaStream? cached = _cache.get(userID, TetraLeagueBetaStream);
|
||||||
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": "tetrioUserTL", "user": userID.toLowerCase().trim()});
|
url = Uri.https(webVersionDomain, 'oskware_bridge.php', {
|
||||||
|
"endpoint": "tetrioUserTL",
|
||||||
|
"user": userID.toLowerCase().trim(),
|
||||||
|
if (prisecter != null) "after": prisecter
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
url = Uri.https('ch.tetr.io', 'api/users/${userID.toLowerCase().trim()}/records/league/recent');
|
url = Uri.https('ch.tetr.io', 'api/users/${userID.toLowerCase().trim()}/records/league/recent', {
|
||||||
|
"limit": "100",
|
||||||
|
if (prisecter != null) "after": prisecter
|
||||||
|
});
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
final response = await client.get(url);
|
final response = await client.get(url);
|
||||||
|
|
|
@ -30,9 +30,9 @@ class AboutCard extends StatelessWidget{
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(child: Column(
|
return Card(child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text(title, style: Theme.of(context).textTheme.titleSmall, textAlign: TextAlign.center),
|
Text(title, style: Theme.of(context).textTheme.titleMedium, textAlign: TextAlign.center),
|
||||||
Divider(),
|
Divider(),
|
||||||
Text(value, textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, fontWeight: FontWeight.w500, color: Colors.white)),
|
Text(value, textAlign: TextAlign.center, style: Theme.of(context).textTheme.headlineMedium),
|
||||||
if (undervalue != null) Text(undervalue!, textAlign: TextAlign.center),
|
if (undervalue != null) Text(undervalue!, textAlign: TextAlign.center),
|
||||||
Divider(),
|
Divider(),
|
||||||
Padding(
|
Padding(
|
||||||
|
@ -115,7 +115,7 @@ class AboutState extends State<AboutView> {
|
||||||
]),
|
]),
|
||||||
Card(child: Center(child: Padding(
|
Card(child: Center(child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(0.0, 6.0, 0.0, 18.0),
|
padding: const EdgeInsets.fromLTRB(0.0, 6.0, 0.0, 18.0),
|
||||||
child: Text(t.aboutView.credits, style: Theme.of(context).textTheme.titleLarge, textAlign: TextAlign.center),
|
child: Text(t.aboutView.credits, style: Theme.of(context).textTheme.titleSmall, textAlign: TextAlign.center),
|
||||||
))),
|
))),
|
||||||
Wrap(
|
Wrap(
|
||||||
direction: Axis.horizontal,
|
direction: Axis.horizontal,
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'dart:developer' as developer;
|
||||||
import 'package:tetra_stats/data_objects/aggregate_stats.dart';
|
import 'package:tetra_stats/data_objects/aggregate_stats.dart';
|
||||||
import 'package:tetra_stats/data_objects/record_single.dart';
|
import 'package:tetra_stats/data_objects/record_single.dart';
|
||||||
import 'package:tetra_stats/data_objects/summaries.dart';
|
import 'package:tetra_stats/data_objects/summaries.dart';
|
||||||
|
@ -14,6 +15,7 @@ import 'package:tetra_stats/data_objects/tetrio_player.dart';
|
||||||
import 'package:tetra_stats/data_objects/tetrio_zen.dart';
|
import 'package:tetra_stats/data_objects/tetrio_zen.dart';
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
import 'package:tetra_stats/main.dart' show teto;
|
import 'package:tetra_stats/main.dart' show teto;
|
||||||
|
import 'package:tetra_stats/services/crud_exceptions.dart';
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
import 'package:tetra_stats/utils/relative_timestamps.dart';
|
import 'package:tetra_stats/utils/relative_timestamps.dart';
|
||||||
import 'package:tetra_stats/utils/text_shadow.dart';
|
import 'package:tetra_stats/utils/text_shadow.dart';
|
||||||
|
@ -406,7 +408,7 @@ class CompareState extends State<CompareView> {
|
||||||
Text(s.league.playstyle != null ? f4.format(s.league.playstyle!.infds) : "---"),
|
Text(s.league.playstyle != null ? f4.format(s.league.playstyle!.infds) : "---"),
|
||||||
]);
|
]);
|
||||||
formattedValues[2].add([
|
formattedValues[2].add([
|
||||||
RichText(text: TextSpan(text: zenithRun != null ? "${f2.format(zenithRun.stats.zenith!.altitude)} m" : "---", style: TextStyle(fontFamily: "Eurostile Round"), children: [if (zenithRun != null && oldZenithRun) TextSpan(text: " (${zenithRun.revolution})", style: TextStyle(color: Colors.grey))])),
|
RichText(text: TextSpan(text: zenithRun != null ? "${f2.format(zenithRun.stats.zenith!.altitude)} m" : "---", style: TextStyle(fontFamily: "Eurostile Round", color: Colors.white), children: [if (zenithRun != null && oldZenithRun) TextSpan(text: " (${zenithRun.revolution})", style: TextStyle(color: Colors.grey))])),
|
||||||
Text(zenithRun != null ? "№ "+intf.format(zenithRun.rank) : "---"),
|
Text(zenithRun != null ? "№ "+intf.format(zenithRun.rank) : "---"),
|
||||||
Text(zenithRun != null ? f2.format(zenithRun.aggregateStats.apm) : "---"),
|
Text(zenithRun != null ? f2.format(zenithRun.aggregateStats.apm) : "---"),
|
||||||
Text(zenithRun != null ? f2.format(zenithRun.aggregateStats.pps) : "---"),
|
Text(zenithRun != null ? f2.format(zenithRun.aggregateStats.pps) : "---"),
|
||||||
|
@ -434,7 +436,7 @@ class CompareState extends State<CompareView> {
|
||||||
Text(zenithRun?.aggregateStats.playstyle != null ? f4.format(zenithRun!.aggregateStats.playstyle.infds) : "---"),
|
Text(zenithRun?.aggregateStats.playstyle != null ? f4.format(zenithRun!.aggregateStats.playstyle.infds) : "---"),
|
||||||
]);
|
]);
|
||||||
formattedValues[3].add([
|
formattedValues[3].add([
|
||||||
RichText(text: TextSpan(text: zenithExRun != null ? "${f2.format(zenithExRun.stats.zenith!.altitude)} m" : "---", style: TextStyle(fontFamily: "Eurostile Round"), children: [if (zenithExRun != null && oldZenithExRun) TextSpan(text: " (${zenithExRun.revolution})", style: TextStyle(color: Colors.grey))])),
|
RichText(text: TextSpan(text: zenithExRun != null ? "${f2.format(zenithExRun.stats.zenith!.altitude)} m" : "---", style: TextStyle(fontFamily: "Eurostile Round", color: Colors.white), children: [if (zenithExRun != null && oldZenithExRun) TextSpan(text: " (${zenithExRun.revolution})", style: TextStyle(color: Colors.grey))])),
|
||||||
Text(zenithExRun != null ? "№ "+intf.format(zenithExRun.rank) : "---"),
|
Text(zenithExRun != null ? "№ "+intf.format(zenithExRun.rank) : "---"),
|
||||||
Text(zenithExRun != null ? f2.format(zenithExRun.aggregateStats.apm) : "---"),
|
Text(zenithExRun != null ? f2.format(zenithExRun.aggregateStats.apm) : "---"),
|
||||||
Text(zenithExRun != null ? f2.format(zenithExRun.aggregateStats.pps) : "---"),
|
Text(zenithExRun != null ? f2.format(zenithExRun.aggregateStats.pps) : "---"),
|
||||||
|
@ -738,19 +740,31 @@ class CompareState extends State<CompareView> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void addPlayer(String nickname) async {
|
Future<Exception?> addPlayer(String nickname) async {
|
||||||
|
try {
|
||||||
if (nickname.startsWith("\$avg")){
|
if (nickname.startsWith("\$avg")){
|
||||||
await addRankAverages(nickname.substring(4).toLowerCase());
|
await addRankAverages(nickname.substring(4).toLowerCase());
|
||||||
|
return null;
|
||||||
}else{
|
}else{
|
||||||
players.add(await teto.fetchPlayer(nickname));
|
late TetrioPlayer player;
|
||||||
summaries.add(await teto.fetchSummaries(players.last.userId));
|
late Summaries summary;
|
||||||
|
List<dynamic> requests = await Future.wait([teto.fetchPlayer(nickname), teto.fetchSummaries(players.last.userId)]);
|
||||||
|
player = requests[0];
|
||||||
|
summary = requests[1];
|
||||||
|
players.add(player);
|
||||||
|
summaries.add(summary);
|
||||||
addvaluesEntrys(players.last, summaries.last);
|
addvaluesEntrys(players.last, summaries.last);
|
||||||
nicknames.add(players.last.username);
|
nicknames.add(players.last.username);
|
||||||
}
|
}
|
||||||
|
} on Exception catch (e) {
|
||||||
|
developer.log("Failed to add player:", error: e);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
best = recalculateBestEntries();
|
best = recalculateBestEntries();
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addRankAverages(String rank) async {
|
Future<void> addRankAverages(String rank) async {
|
||||||
|
@ -832,7 +846,8 @@ class CompareState extends State<CompareView> {
|
||||||
child: const Icon(Icons.arrow_back),
|
child: const Icon(Icons.arrow_back),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: SingleChildScrollView(
|
body: SafeArea(
|
||||||
|
child: SingleChildScrollView(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
|
@ -932,6 +947,7 @@ class CompareState extends State<CompareView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1001,7 +1017,7 @@ class HeaderCard extends StatelessWidget{
|
||||||
}}
|
}}
|
||||||
|
|
||||||
class AddNewColumnCard extends StatefulWidget{
|
class AddNewColumnCard extends StatefulWidget{
|
||||||
final Function addPlayer;
|
final Future<Exception?> Function(String) addPlayer;
|
||||||
|
|
||||||
const AddNewColumnCard(this.addPlayer);
|
const AddNewColumnCard(this.addPlayer);
|
||||||
|
|
||||||
|
@ -1010,10 +1026,9 @@ class AddNewColumnCard extends StatefulWidget{
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AddNewColumnCardState extends State<AddNewColumnCard> with SingleTickerProviderStateMixin {
|
class _AddNewColumnCardState extends State<AddNewColumnCard> with SingleTickerProviderStateMixin {
|
||||||
// TODO: make spinner while awaiting for data
|
|
||||||
// TODO: show error if failed to retrieve data
|
|
||||||
late AnimationController _animController;
|
late AnimationController _animController;
|
||||||
late Animation _anim;
|
late Animation _anim;
|
||||||
|
bool showSpinner = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState(){
|
void initState(){
|
||||||
|
@ -1037,6 +1052,14 @@ class _AddNewColumnCardState extends State<AddNewColumnCard> with SingleTickerPr
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getExcepitonText(Exception e){
|
||||||
|
return switch(e.runtimeType){
|
||||||
|
TetrioPlayerNotExist => t.errors.noSuchUser,
|
||||||
|
ConnectionIssue => t.errors.connection(code: (e as ConnectionIssue).code, message: e.message),
|
||||||
|
_ => e.toString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
|
@ -1055,11 +1078,28 @@ class _AddNewColumnCardState extends State<AddNewColumnCard> with SingleTickerPr
|
||||||
TextField(
|
TextField(
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
onSubmitted: (value){
|
onSubmitted: (value){
|
||||||
widget.addPlayer(value);
|
widget.addPlayer(value).then((onValue){
|
||||||
|
showSpinner = false;
|
||||||
|
setState(() {
|
||||||
|
if (onValue != null) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(getExcepitonText(onValue))));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
setState(() {
|
||||||
|
showSpinner = true;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
onTapOutside: (event) {
|
onTapOutside: (event) {
|
||||||
setState((){_animController.animateBack(0);});
|
setState((){_animController.animateBack(0);});
|
||||||
},
|
},
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
|
child: Opacity(
|
||||||
|
opacity: showSpinner ? 1.0 : 0.0,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
value: showSpinner ? null : 0.0,
|
||||||
|
)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -53,7 +53,8 @@ class _DestinationGraphsState extends State<DestinationGraphs> {
|
||||||
List<String> excludeRanks = [];
|
List<String> excludeRanks = [];
|
||||||
late Future<List<_MyScatterSpot>> futureLeague = getTetraLeagueData(_Xchart, Ychart);
|
late Future<List<_MyScatterSpot>> futureLeague = getTetraLeagueData(_Xchart, Ychart);
|
||||||
String searchLeague = "";
|
String searchLeague = "";
|
||||||
//Duration postSeasonLeft = seasonStart.difference(DateTime.now());
|
int? TLstatePlayers;
|
||||||
|
DateTime? TLrelevance;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState(){
|
void initState(){
|
||||||
|
@ -177,6 +178,8 @@ class _DestinationGraphsState extends State<DestinationGraphs> {
|
||||||
|
|
||||||
Future<List<_MyScatterSpot>> getTetraLeagueData(Stats x, Stats y) async {
|
Future<List<_MyScatterSpot>> getTetraLeagueData(Stats x, Stats y) async {
|
||||||
TetrioPlayersLeaderboard leaderboard = await teto.fetchTLLeaderboard();
|
TetrioPlayersLeaderboard leaderboard = await teto.fetchTLLeaderboard();
|
||||||
|
TLrelevance = leaderboard.timestamp;
|
||||||
|
TLstatePlayers = leaderboard.leaderboard.length;
|
||||||
List<_MyScatterSpot> _spots = [
|
List<_MyScatterSpot> _spots = [
|
||||||
for (TetrioPlayerFromLeaderboard entry in leaderboard.leaderboard)
|
for (TetrioPlayerFromLeaderboard entry in leaderboard.leaderboard)
|
||||||
if (excludeRanks.indexOf(entry.rank) == -1) _MyScatterSpot(
|
if (excludeRanks.indexOf(entry.rank) == -1) _MyScatterSpot(
|
||||||
|
@ -359,6 +362,25 @@ class _DestinationGraphsState extends State<DestinationGraphs> {
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
alignment: WrapAlignment.center,
|
alignment: WrapAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
|
if (graph == Graph.leagueState && TLstatePlayers != null && TLrelevance != null) Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 8.0),
|
||||||
|
child: RichText(
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
text: TextSpan(
|
||||||
|
style: TextStyle(color: Colors.white, fontFamily: "Eurostile Round"),
|
||||||
|
children: [
|
||||||
|
TextSpan(text: t.stats.players(n: TLstatePlayers!)),
|
||||||
|
TextSpan(text: "\n"),
|
||||||
|
TextSpan(text: timestamp(TLrelevance!))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
if (graph == Graph.history) Row(
|
if (graph == Graph.history) Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
@ -421,7 +443,10 @@ class _DestinationGraphsState extends State<DestinationGraphs> {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (graph == Graph.history)
|
if (graph == Graph.history)
|
||||||
_gamesPlayedInsteadOfDateAndTime = value! as bool;
|
_gamesPlayedInsteadOfDateAndTime = value! as bool;
|
||||||
else _Xchart = value! as Stats;
|
else{
|
||||||
|
_Xchart = value! as Stats;
|
||||||
|
setState((){futureLeague = getTetraLeagueData(_Xchart, Ychart);});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
@ -437,6 +462,7 @@ class _DestinationGraphsState extends State<DestinationGraphs> {
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
Ychart = value!;
|
Ychart = value!;
|
||||||
|
futureLeague = getTetraLeagueData(_Xchart, Ychart);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_layout_grid/flutter_layout_grid.dart';
|
import 'package:flutter_layout_grid/flutter_layout_grid.dart';
|
||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
|
@ -238,28 +238,10 @@ class ZenithCard extends StatelessWidget {
|
||||||
splitsCard(),
|
splitsCard(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (record != null) Card(
|
if (record != null) Card(child: Center(child: Text(t.nerdStats, style: Theme.of(context).textTheme.titleLarge, textAlign: TextAlign.center))),
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Spacer(),
|
|
||||||
Text(t.nerdStats, style: Theme.of(context).textTheme.titleLarge),
|
|
||||||
const Spacer()
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (record != null) NerdStatsThingy(nerdStats: record!.aggregateStats.nerdStats, width: width),
|
if (record != null) NerdStatsThingy(nerdStats: record!.aggregateStats.nerdStats, width: width),
|
||||||
if (record != null) Graphs(record!.aggregateStats.apm, record!.aggregateStats.pps, record!.aggregateStats.vs, record!.aggregateStats.nerdStats, record!.aggregateStats.playstyle),
|
if (record != null) Graphs(record!.aggregateStats.apm, record!.aggregateStats.pps, record!.aggregateStats.vs, record!.aggregateStats.nerdStats, record!.aggregateStats.playstyle),
|
||||||
if (achievements.isNotEmpty) Card(
|
if (achievements.isNotEmpty) Card(child: Center(child: Text(t.relatedAchievements, style: Theme.of(context).textTheme.titleLarge, textAlign: TextAlign.center))),
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Spacer(),
|
|
||||||
Text(t.relatedAchievements, style: Theme.of(context).textTheme.titleLarge),
|
|
||||||
const Spacer()
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (achievements.isNotEmpty) Wrap(
|
if (achievements.isNotEmpty) Wrap(
|
||||||
direction: Axis.horizontal,
|
direction: Axis.horizontal,
|
||||||
children: [
|
children: [
|
||||||
|
@ -290,7 +272,13 @@ class RecordCard extends StatelessWidget {
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
if (closestAverage != null) Padding(padding: const EdgeInsets.only(right: 8.0),
|
if (closestAverage != null) Padding(padding: const EdgeInsets.only(right: 8.0),
|
||||||
child: Image.asset("res/tetrio_tl_alpha_ranks/${closestAverage!.key}.png", height: 96)
|
child: Tooltip(message: "${t.rankView.avgForRank(rank: closestAverage!.key.toUpperCase())}: ${
|
||||||
|
switch(record!.gamemode){
|
||||||
|
"40l" => get40lTime(closestAverage!.value.inMicroseconds),
|
||||||
|
"blitz" => NumberFormat.decimalPattern().format(closestAverage!.value),
|
||||||
|
_ => closestAverage!.value.toString()
|
||||||
|
}
|
||||||
|
}", child: Image.asset("res/tetrio_tl_alpha_ranks/${closestAverage!.key}.png", height: 96))
|
||||||
),
|
),
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
@ -327,7 +315,7 @@ class RecordCard extends StatelessWidget {
|
||||||
if (record!.rank != -1) TextSpan(text: "№ ${intf.format(record!.rank)}", style: TextStyle(color: getColorOfRank(record!.rank))),
|
if (record!.rank != -1) TextSpan(text: "№ ${intf.format(record!.rank)}", style: TextStyle(color: getColorOfRank(record!.rank))),
|
||||||
if (record!.rank != -1) const TextSpan(text: " • "),
|
if (record!.rank != -1) const TextSpan(text: " • "),
|
||||||
if (record!.countryRank != -1) TextSpan(text: "№ ${intf.format(record!.countryRank)} ${t.localStanding}", style: TextStyle(color: getColorOfRank(record!.countryRank))),
|
if (record!.countryRank != -1) TextSpan(text: "№ ${intf.format(record!.countryRank)} ${t.localStanding}", style: TextStyle(color: getColorOfRank(record!.countryRank))),
|
||||||
if (record!.countryRank != -1) const TextSpan(text: " • "),
|
if (record!.countryRank != -1) TextSpan(text: width > 600.0 ? " • " : "\n"),
|
||||||
TextSpan(text: timestamp(record!.timestamp)),
|
TextSpan(text: timestamp(record!.timestamp)),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
@ -505,7 +493,13 @@ class RecordSummary extends StatelessWidget{
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
if (closestAverage != null && record != null) Padding(padding: const EdgeInsets.only(right: 8.0),
|
if (closestAverage != null && record != null) Padding(padding: const EdgeInsets.only(right: 8.0),
|
||||||
child: Image.asset("res/tetrio_tl_alpha_ranks/${closestAverage!.key}.png", height: 96))
|
child: Tooltip(message: "${t.rankView.avgForRank(rank: closestAverage!.key.toUpperCase())}: ${
|
||||||
|
switch(record!.gamemode){
|
||||||
|
"40l" => get40lTime(closestAverage!.value.inMicroseconds),
|
||||||
|
"blitz" => NumberFormat.decimalPattern().format(closestAverage!.value),
|
||||||
|
_ => closestAverage!.value.toString()
|
||||||
|
}
|
||||||
|
}", child: Image.asset("res/tetrio_tl_alpha_ranks/${closestAverage!.key}.png", height: 96)))
|
||||||
else !hideRank ? Image.asset("res/tetrio_tl_alpha_ranks/z.png", height: 96) : Container(),
|
else !hideRank ? Image.asset("res/tetrio_tl_alpha_ranks/z.png", height: 96) : Container(),
|
||||||
if (record != null) Column(
|
if (record != null) Column(
|
||||||
crossAxisAlignment: hideRank ? CrossAxisAlignment.center : CrossAxisAlignment.start,
|
crossAxisAlignment: hideRank ? CrossAxisAlignment.center : CrossAxisAlignment.start,
|
||||||
|
@ -944,30 +938,10 @@ class _DestinationHomeState extends State<DestinationHome> with SingleTickerProv
|
||||||
// )
|
// )
|
||||||
// ),
|
// ),
|
||||||
// ),
|
// ),
|
||||||
if (data.nerdStats != null) Card(
|
if (data.nerdStats != null) Card(child: Center(child: Text(t.nerdStats, style: Theme.of(context).textTheme.titleLarge, textAlign: TextAlign.center))),
|
||||||
//surfaceTintColor: rankColors[data.rank],
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Spacer(),
|
|
||||||
Text(t.nerdStats, style: Theme.of(context).textTheme.titleLarge),
|
|
||||||
const Spacer()
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (data.nerdStats != null) NerdStatsThingy(nerdStats: toSee.nerdStats!, oldNerdStats: toCompare?.nerdStats, averages: averages, lbPos: lbPos, width: width),
|
if (data.nerdStats != null) NerdStatsThingy(nerdStats: toSee.nerdStats!, oldNerdStats: toCompare?.nerdStats, averages: averages, lbPos: lbPos, width: width),
|
||||||
if (data.nerdStats != null) Graphs(toSee.apm!, toSee.pps!, toSee.vs!, toSee.nerdStats!, toSee.playstyle!),
|
if (data.nerdStats != null) Graphs(toSee.apm!, toSee.pps!, toSee.vs!, toSee.nerdStats!, toSee.playstyle!),
|
||||||
Card(
|
Card(child: Center(child: Text(t.relatedAchievements, style: Theme.of(context).textTheme.titleLarge, textAlign: TextAlign.center))),
|
||||||
//surfaceTintColor: rankColors[data.rank],
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Spacer(),
|
|
||||||
Text(t.relatedAchievements, style: Theme.of(context).textTheme.titleLarge),
|
|
||||||
const Spacer()
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Wrap(
|
Wrap(
|
||||||
direction: Axis.horizontal,
|
direction: Axis.horizontal,
|
||||||
children: [
|
children: [
|
||||||
|
@ -1162,7 +1136,6 @@ class _DestinationHomeState extends State<DestinationHome> with SingleTickerProv
|
||||||
@override
|
@override
|
||||||
initState(){
|
initState(){
|
||||||
_transition = AnimationController(vsync: this, duration: Durations.long4);
|
_transition = AnimationController(vsync: this, duration: Durations.long4);
|
||||||
|
|
||||||
_offsetAnimation = Tween<Offset>(
|
_offsetAnimation = Tween<Offset>(
|
||||||
begin: Offset.zero,
|
begin: Offset.zero,
|
||||||
end: const Offset(1.5, 0.0),
|
end: const Offset(1.5, 0.0),
|
||||||
|
@ -1170,7 +1143,6 @@ class _DestinationHomeState extends State<DestinationHome> with SingleTickerProv
|
||||||
parent: _transition,
|
parent: _transition,
|
||||||
curve: Curves.elasticIn,
|
curve: Curves.elasticIn,
|
||||||
));
|
));
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1404,7 +1376,7 @@ class _DestinationHomeState extends State<DestinationHome> with SingleTickerProv
|
||||||
if (snapshot.data!.player!.role == "banned") FakeDistinguishmentThingy(banned: true)
|
if (snapshot.data!.player!.role == "banned") FakeDistinguishmentThingy(banned: true)
|
||||||
else if (snapshot.data!.player!.badstanding == true) FakeDistinguishmentThingy(badStanding: true),
|
else if (snapshot.data!.player!.badstanding == true) FakeDistinguishmentThingy(badStanding: true),
|
||||||
rigthCard(snapshot, sprintAchievements, blitzAchievements, tlAchievements, qpAchievements, qpExAchievements, width),
|
rigthCard(snapshot, sprintAchievements, blitzAchievements, tlAchievements, qpAchievements, qpExAchievements, width),
|
||||||
if (rightCard == Cards.overview) Card(
|
if (rightCard == Cards.overview && snapshot.data?.player?.bio != null) Card(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
|
|
|
@ -30,38 +30,19 @@ class InfoCard extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
child: viewportWidth > 768.0 ? SizedBox(
|
child: SizedBox(
|
||||||
width: 450,
|
width: viewportWidth > 768.0 ? 450 : viewportWidth,
|
||||||
height: height,
|
height: viewportWidth > 768.0 ? height : null,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Image.asset(assetLink, fit: BoxFit.cover, height: 300.0),
|
Image.asset(assetLink, fit: BoxFit.cover, height: viewportWidth > 768.0 ? 300.0 : 150.0, width: viewportWidth > 768.0 ? null : viewportWidth),
|
||||||
TextButton(child: Text(title, style: Theme.of(context).textTheme.titleLarge!.copyWith(decoration: TextDecoration.underline, decorationColor: Colors.white70, decorationStyle: TextDecorationStyle.dotted), textAlign: TextAlign.center), onPressed: onPressed),
|
TextButton(child: Text(title, style: (viewportWidth > 768.0 ? Theme.of(context).textTheme.titleLarge : Theme.of(context).textTheme.titleSmall)!.copyWith(decoration: TextDecoration.underline, decorationColor: Colors.white70, decorationStyle: TextDecorationStyle.dotted), textAlign: TextAlign.center), onPressed: onPressed),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(12.0),
|
padding: const EdgeInsets.all(12.0),
|
||||||
child: Text(description),
|
child: Text(description),
|
||||||
),
|
),
|
||||||
Spacer()
|
if (viewportWidth > 768.0) Spacer()
|
||||||
],
|
|
||||||
),
|
|
||||||
) : SizedBox(
|
|
||||||
width: viewportWidth,
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Image.asset(assetLink, fit: BoxFit.cover, width: 200.0),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
TextButton(child: Text(title, style: Theme.of(context).textTheme.titleLarge!.copyWith(decoration: TextDecoration.underline, decorationColor: Colors.white70, decorationStyle: TextDecorationStyle.dotted, fontSize: 28), textAlign: TextAlign.center), onPressed: onPressed),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(12.0),
|
|
||||||
child: Text(description),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -75,7 +56,7 @@ class _DestinationInfo extends State<DestinationInfo> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
List<Widget> cards = [
|
List<Widget> cards = [
|
||||||
InfoCard(
|
InfoCard(
|
||||||
height: widget.constraints.maxHeight - 77,
|
height: widget.constraints.maxHeight,
|
||||||
viewportWidth: widget.constraints.maxWidth,
|
viewportWidth: widget.constraints.maxWidth,
|
||||||
assetLink: "res/images/info card 1.png",
|
assetLink: "res/images/info card 1.png",
|
||||||
title: t.infoDestination.sprintAndBlitzAverages,
|
title: t.infoDestination.sprintAndBlitzAverages,
|
||||||
|
@ -87,7 +68,7 @@ class _DestinationInfo extends State<DestinationInfo> {
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
InfoCard(
|
InfoCard(
|
||||||
height: widget.constraints.maxHeight - 77,
|
height: widget.constraints.maxHeight,
|
||||||
viewportWidth: widget.constraints.maxWidth,
|
viewportWidth: widget.constraints.maxWidth,
|
||||||
assetLink: "res/images/info card 2.png",
|
assetLink: "res/images/info card 2.png",
|
||||||
title: t.infoDestination.tetraStatsWiki,
|
title: t.infoDestination.tetraStatsWiki,
|
||||||
|
@ -97,7 +78,7 @@ class _DestinationInfo extends State<DestinationInfo> {
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
InfoCard(
|
InfoCard(
|
||||||
height: widget.constraints.maxHeight - 77,
|
height: widget.constraints.maxHeight,
|
||||||
viewportWidth: widget.constraints.maxWidth,
|
viewportWidth: widget.constraints.maxWidth,
|
||||||
assetLink: "res/images/info card 3.png",
|
assetLink: "res/images/info card 3.png",
|
||||||
title: t.infoDestination.about,
|
title: t.infoDestination.about,
|
||||||
|
@ -114,11 +95,14 @@ class _DestinationInfo extends State<DestinationInfo> {
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Card(
|
Card(
|
||||||
child: Center(child: Text(t.infoDestination.title, style: Theme.of(context).textTheme.titleLarge)),
|
child: Center(child: Text(t.infoDestination.title, style: widget.constraints.maxWidth > 768.0 ? Theme.of(context).textTheme.titleLarge : Theme.of(context).textTheme.titleSmall!.copyWith(height: 1.1))),
|
||||||
),
|
),
|
||||||
SingleChildScrollView(
|
SizedBox(
|
||||||
scrollDirection: Axis.horizontal,
|
height: widget.constraints.maxWidth > 768.0 ? widget.constraints.maxHeight - 61 : widget.constraints.maxHeight - 170,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: widget.constraints.maxWidth > 768.0 ? Axis.horizontal : Axis.vertical,
|
||||||
child: widget.constraints.maxWidth > 768.0 ? Row(children: cards) : Column(children: cards),
|
child: widget.constraints.maxWidth > 768.0 ? Row(children: cards) : Column(children: cards),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,12 +3,14 @@ import 'dart:math';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:tetra_stats/data_objects/tetrio_constants.dart';
|
import 'package:tetra_stats/data_objects/tetrio_constants.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/gen/strings.g.dart';
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
import 'package:tetra_stats/main.dart';
|
import 'package:tetra_stats/main.dart';
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
import 'package:tetra_stats/utils/relative_timestamps.dart';
|
import 'package:tetra_stats/utils/relative_timestamps.dart';
|
||||||
import 'package:tetra_stats/views/user_view.dart';
|
import 'package:tetra_stats/views/user_view.dart';
|
||||||
import 'package:tetra_stats/widgets/future_error.dart';
|
import 'package:tetra_stats/widgets/future_error.dart';
|
||||||
|
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
||||||
|
|
||||||
class DestinationLeaderboards extends StatefulWidget{
|
class DestinationLeaderboards extends StatefulWidget{
|
||||||
final BoxConstraints constraints;
|
final BoxConstraints constraints;
|
||||||
|
@ -59,6 +61,8 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
|
||||||
List<DropdownMenuEntry> _stats = [for (MapEntry e in chartsShortTitles.entries) DropdownMenuEntry(value: e.key, label: e.value)];
|
List<DropdownMenuEntry> _stats = [for (MapEntry e in chartsShortTitles.entries) DropdownMenuEntry(value: e.key, label: e.value)];
|
||||||
String? _country;
|
String? _country;
|
||||||
Stats stat = Stats.tr;
|
Stats stat = Stats.tr;
|
||||||
|
int? fullTLlbPlayers;
|
||||||
|
DateTime? fullTLlbTimestamp;
|
||||||
|
|
||||||
bool? getTotalFilterValue(){
|
bool? getTotalFilterValue(){
|
||||||
if (_excludeRanks.isEmpty) return true;
|
if (_excludeRanks.isEmpty) return true;
|
||||||
|
@ -74,24 +78,36 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
|
||||||
try {
|
try {
|
||||||
_isFetchingData = true;
|
_isFetchingData = true;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
TetrioPlayersLeaderboard? fullLB;
|
||||||
|
|
||||||
|
if (_currentLb == Leaderboards.fullTL){
|
||||||
|
fullLB = await teto.fetchTLLeaderboard();
|
||||||
|
fullTLlbPlayers = fullLB.leaderboard.length;
|
||||||
|
fullTLlbTimestamp = fullLB.timestamp;
|
||||||
|
_reachedTheEnd = true;
|
||||||
|
}
|
||||||
|
|
||||||
final items = switch(_currentLb){
|
final items = switch(_currentLb){
|
||||||
Leaderboards.tl => await teto.fetchTetrioLeaderboard(prisecter: prisecter, country: _country),
|
Leaderboards.tl => await teto.fetchTetrioLeaderboard(prisecter: prisecter, country: _country),
|
||||||
Leaderboards.fullTL => (await teto.fetchTLLeaderboard()).getStatRankingFromLB(stat, country: _country??""),
|
Leaderboards.fullTL => fullLB!.getStatRankingFromLB(stat, country: _country??""),
|
||||||
Leaderboards.xp => await teto.fetchTetrioLeaderboard(prisecter: prisecter, lb: "xp", country: _country),
|
Leaderboards.xp => await teto.fetchTetrioLeaderboard(prisecter: prisecter, lb: "xp", country: _country),
|
||||||
Leaderboards.ar => await teto.fetchTetrioLeaderboard(prisecter: prisecter, lb: "ar", country: _country),
|
Leaderboards.ar => await teto.fetchTetrioLeaderboard(prisecter: prisecter, lb: "ar", country: _country),
|
||||||
Leaderboards.sprint => await teto.fetchTetrioRecordsLeaderboard(prisecter: prisecter, country: _country),
|
Leaderboards.sprint => await teto.fetchTetrioRecordsLeaderboard(prisecter: prisecter, country: _country),
|
||||||
Leaderboards.blitz => await teto.fetchTetrioRecordsLeaderboard(prisecter: prisecter, lb: "blitz_global", country: _country),
|
Leaderboards.blitz => await teto.fetchTetrioRecordsLeaderboard(prisecter: prisecter, lb: "blitz", country: _country),
|
||||||
Leaderboards.zenith => await teto.fetchTetrioRecordsLeaderboard(prisecter: prisecter, lb: "zenith_global", country: _country),
|
Leaderboards.zenith => await teto.fetchTetrioRecordsLeaderboard(prisecter: prisecter, lb: "zenith", country: _country),
|
||||||
Leaderboards.zenithex => await teto.fetchTetrioRecordsLeaderboard(prisecter: prisecter, lb: "zenithex_global", country: _country),
|
Leaderboards.zenithex => await teto.fetchTetrioRecordsLeaderboard(prisecter: prisecter, lb: "zenithex", country: _country),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (_currentLb == Leaderboards.fullTL && _excludeRanks.isNotEmpty) items.removeWhere((e) => _excludeRanks.indexOf((e as TetrioPlayerFromLeaderboard).rank) != -1);
|
if (_currentLb == Leaderboards.fullTL && _excludeRanks.isNotEmpty) items.removeWhere((e) => _excludeRanks.indexOf((e as TetrioPlayerFromLeaderboard).rank) != -1);
|
||||||
if (_currentLb == Leaderboards.fullTL || items.isEmpty) _reachedTheEnd = true;
|
if (items.isEmpty) _reachedTheEnd = true;
|
||||||
list.addAll((_reverse && _currentLb == Leaderboards.fullTL) ? items.reversed : items);
|
list.addAll((_reverse && _currentLb == Leaderboards.fullTL) ? items.reversed : items);
|
||||||
|
if (items.isNotEmpty){
|
||||||
_dataStreamController.add(list);
|
_dataStreamController.add(list);
|
||||||
prisecter = list.last.prisecter.toString();
|
prisecter = list.last.prisecter.toString();
|
||||||
|
} else{
|
||||||
|
_dataStreamController.add([]);
|
||||||
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_dataStreamController.addError(e);
|
_dataStreamController.addError(e);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -179,8 +195,8 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
|
||||||
),
|
),
|
||||||
if (_currentLb == Leaderboards.fullTL) IconButton(
|
if (_currentLb == Leaderboards.fullTL) IconButton(
|
||||||
color: _excludeRanks.isNotEmpty ? Theme.of(context).colorScheme.primary : null,
|
color: _excludeRanks.isNotEmpty ? Theme.of(context).colorScheme.primary : null,
|
||||||
onPressed: (){
|
onPressed: () async {
|
||||||
showDialog(context: context, builder: (BuildContext context) {
|
await showDialog(context: context, builder: (BuildContext context) {
|
||||||
return StatefulBuilder(
|
return StatefulBuilder(
|
||||||
builder: (context, StateSetter setAlertState) {
|
builder: (context, StateSetter setAlertState) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
|
@ -216,11 +232,8 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
TextButton(
|
TextButton(
|
||||||
child: const Text("Apply"),
|
child: const Text("Apply"),
|
||||||
onPressed: () {Navigator.of(context).pop(); setState((){
|
onPressed: () {
|
||||||
_currentLb = Leaderboards.fullTL;
|
Navigator.of(context).pop();
|
||||||
list.clear();
|
|
||||||
prisecter = null;
|
|
||||||
_fetchData();});
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
@ -228,22 +241,30 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
setState(() {
|
||||||
|
_currentLb = Leaderboards.fullTL;
|
||||||
|
list.clear();
|
||||||
|
prisecter = null;
|
||||||
|
_isFetchingData = false;
|
||||||
|
_reachedTheEnd = false;
|
||||||
|
_fetchData();
|
||||||
|
});
|
||||||
}, icon: Icon(Icons.filter_alt)),
|
}, icon: Icon(Icons.filter_alt)),
|
||||||
if (_currentLb == Leaderboards.fullTL) IconButton(
|
if (_currentLb == Leaderboards.fullTL) IconButton(
|
||||||
color: _reverse ? Theme.of(context).colorScheme.primary : null,
|
color: _reverse ? Theme.of(context).colorScheme.primary : null,
|
||||||
icon: Transform.rotate(angle: _reverse ? pi : 0.0, child: Icon(Icons.filter_list)),
|
icon: Transform.rotate(angle: _reverse ? pi : 0.0, child: Icon(Icons.filter_list)),
|
||||||
onPressed: (){
|
onPressed: (){
|
||||||
setState((){
|
|
||||||
_reverse = !_reverse;
|
_reverse = !_reverse;
|
||||||
_currentLb = Leaderboards.fullTL;
|
|
||||||
list.clear();
|
list.clear();
|
||||||
prisecter = null;
|
prisecter = null;
|
||||||
|
_isFetchingData = false;
|
||||||
|
_reachedTheEnd = false;
|
||||||
_fetchData();
|
_fetchData();
|
||||||
});
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
if (_currentLb == Leaderboards.fullTL && fullTLlbPlayers != null && fullTLlbTimestamp != null) Text("${t.stats.players(n: fullTLlbPlayers!)} • ${t.sprintAndBlitsRelevance(date: timestamp(fullTLlbTimestamp!))}"),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
|
@ -284,7 +305,7 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
|
||||||
},
|
},
|
||||||
Leaderboards.xp => Text("LVL ${f2.format(snapshot.data![index].level)}", style: trailingStyle),
|
Leaderboards.xp => Text("LVL ${f2.format(snapshot.data![index].level)}", style: trailingStyle),
|
||||||
Leaderboards.ar => Text("${intf.format(snapshot.data![index].ar)} AR", style: trailingStyle),
|
Leaderboards.ar => Text("${intf.format(snapshot.data![index].ar)} AR", style: trailingStyle),
|
||||||
Leaderboards.sprint => Text(get40lTime(snapshot.data![index].stats.finalTime.inMicroseconds), style: trailingStyle),
|
Leaderboards.sprint => Text(getALittleBitMoreNormalTime(snapshot.data![index].stats.finalTime), style: trailingStyle),
|
||||||
Leaderboards.blitz => Text(intf.format(snapshot.data![index].stats.score), style: trailingStyle),
|
Leaderboards.blitz => Text(intf.format(snapshot.data![index].stats.score), style: trailingStyle),
|
||||||
Leaderboards.zenith => Text("${f2.format(snapshot.data![index].stats.zenith!.altitude)} m", style: trailingStyle),
|
Leaderboards.zenith => Text("${f2.format(snapshot.data![index].stats.zenith!.altitude)} m", style: trailingStyle),
|
||||||
Leaderboards.zenithex => Text("${f2.format(snapshot.data![index].stats.zenith!.altitude)} m", style: trailingStyle)
|
Leaderboards.zenithex => Text("${f2.format(snapshot.data![index].stats.zenith!.altitude)} m", style: trailingStyle)
|
||||||
|
@ -297,7 +318,7 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
|
||||||
},
|
},
|
||||||
Leaderboards.xp => "${f2.format(snapshot.data![index].xp)} XP${snapshot.data![index].playtime.isNegative ? "" : ", ${playtime(snapshot.data![index].playtime)} of gametime"}",
|
Leaderboards.xp => "${f2.format(snapshot.data![index].xp)} XP${snapshot.data![index].playtime.isNegative ? "" : ", ${playtime(snapshot.data![index].playtime)} of gametime"}",
|
||||||
Leaderboards.ar => "${snapshot.data![index].ar_counts}",
|
Leaderboards.ar => "${snapshot.data![index].ar_counts}",
|
||||||
Leaderboards.sprint => "${intf.format(snapshot.data![index].stats.finesse.faults)} FF, ${f2.format(snapshot.data![index].stats.kpp)} KPP, ${f2.format(snapshot.data![index].stats.kps)} KPS, ${f2.format(snapshot.data![index].stats.pps)} PPS, ${intf.format(snapshot.data![index].stats.piecesPlaced)} P",
|
Leaderboards.sprint => "${snapshot.data?[index]?.stats?.finesse?.faults != null ? intf.format(snapshot.data![index].stats.finesse.faults) : "?"} FF, ${f2.format(snapshot.data![index].stats.kpp)} KPP, ${f2.format(snapshot.data![index].stats.kps)} KPS, ${f2.format(snapshot.data![index].stats.pps)} PPS, ${intf.format(snapshot.data![index].stats.piecesPlaced)} P",
|
||||||
Leaderboards.blitz => "lvl ${snapshot.data![index].stats.level}, ${f2.format(snapshot.data![index].stats.pps)} PPS, ${f2.format(snapshot.data![index].stats.spp)} SPP",
|
Leaderboards.blitz => "lvl ${snapshot.data![index].stats.level}, ${f2.format(snapshot.data![index].stats.pps)} PPS, ${f2.format(snapshot.data![index].stats.spp)} SPP",
|
||||||
Leaderboards.zenith => "${f2.format(snapshot.data![index].aggregateStats.apm)} APM, ${f2.format(snapshot.data![index].aggregateStats.pps)} PPS, ${intf.format(snapshot.data![index].stats.kills)} KO's, ${f2.format(snapshot.data![index].stats.cps)} climb speed (${f2.format(snapshot.data![index].stats.zenith!.peakrank)} peak), ${intf.format(snapshot.data![index].stats.topBtB)} B2B",
|
Leaderboards.zenith => "${f2.format(snapshot.data![index].aggregateStats.apm)} APM, ${f2.format(snapshot.data![index].aggregateStats.pps)} PPS, ${intf.format(snapshot.data![index].stats.kills)} KO's, ${f2.format(snapshot.data![index].stats.cps)} climb speed (${f2.format(snapshot.data![index].stats.zenith!.peakrank)} peak), ${intf.format(snapshot.data![index].stats.topBtB)} B2B",
|
||||||
Leaderboards.zenithex => "${f2.format(snapshot.data![index].aggregateStats.apm)} APM, ${f2.format(snapshot.data![index].aggregateStats.pps)} PPS, ${intf.format(snapshot.data![index].stats.kills)} KO's, ${f2.format(snapshot.data![index].stats.cps)} climb speed (${f2.format(snapshot.data![index].stats.zenith!.peakrank)} peak), ${intf.format(snapshot.data![index].stats.topBtB)} B2B"
|
Leaderboards.zenithex => "${f2.format(snapshot.data![index].aggregateStats.apm)} APM, ${f2.format(snapshot.data![index].aggregateStats.pps)} PPS, ${intf.format(snapshot.data![index].stats.kills)} KO's, ${f2.format(snapshot.data![index].stats.cps)} climb speed (${f2.format(snapshot.data![index].stats.zenith!.peakrank)} peak), ${intf.format(snapshot.data![index].stats.topBtB)} B2B"
|
||||||
|
|
|
@ -144,8 +144,8 @@ class _FirstTimeState extends State<FirstTimeView> with SingleTickerProviderStat
|
||||||
_animController.animateTo(0.9);
|
_animController.animateTo(0.9);
|
||||||
setState((){
|
setState((){
|
||||||
userSet = true;
|
userSet = true;
|
||||||
title = "Nice to see you, ${nickname}";
|
title = t.firstTimeView.niceToSeeYou(n: nickname);
|
||||||
subtitle = "Let's take a look at your stats...";
|
subtitle = t.firstTimeView.letsTakeALook;
|
||||||
});
|
});
|
||||||
Timer(Duration(seconds: 2), () => _animController.animateTo(1.0, duration: Duration(seconds: 1)));
|
Timer(Duration(seconds: 2), () => _animController.animateTo(1.0, duration: Duration(seconds: 1)));
|
||||||
Timer(Duration(seconds: 3), () => context.replace("/"));
|
Timer(Duration(seconds: 3), () => context.replace("/"));
|
||||||
|
|
|
@ -84,7 +84,7 @@ Future<FetchResults> getData(String searchFor, {bool withHistory = false}) async
|
||||||
}
|
}
|
||||||
|
|
||||||
return FetchResults(true, player, states.reversed.toList(), summaries, news, cutoffs, averages, _meAmongEveryone, isTracking, null);
|
return FetchResults(true, player, states.reversed.toList(), summaries, news, cutoffs, averages, _meAmongEveryone, isTracking, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
class MainView extends StatefulWidget {
|
class MainView extends StatefulWidget {
|
||||||
final String? player;
|
final String? player;
|
||||||
|
|
|
@ -34,74 +34,86 @@ class _RankState extends State<RankView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget partOfTheWidget(List<dynamic>? data){
|
Widget partOfTheWidget(List<dynamic>? data){
|
||||||
|
double? avgAPM = data != null ? data[0].apm : widget.cutoffTetrio.apm;
|
||||||
|
double? avgPPS = data != null ? data[0].pps : widget.cutoffTetrio.pps;
|
||||||
|
double? avgVS = data != null ? data[0].vs : widget.cutoffTetrio.vs;
|
||||||
|
double? avgAPP = data != null ? data[1]["avgAPP"] : widget.cutoffTetrio.nerdStats?.app;
|
||||||
|
double? avgVSAPM = data != null ? data[1]["avgVSAPM"] : widget.cutoffTetrio.nerdStats?.vsapm;
|
||||||
|
double? avgDSS = data != null ? data[1]["avgDSS"] : widget.cutoffTetrio.nerdStats?.dss;
|
||||||
|
double? avgDSP = data != null ? data[1]["avgDSP"] : widget.cutoffTetrio.nerdStats?.dsp;
|
||||||
|
double? avgAPPDSP = data != null ? data[1]["avgAPPDSP"] : widget.cutoffTetrio.nerdStats?.appdsp;
|
||||||
|
double? avgCheese = data != null ? data[1]["avgCheese"] : widget.cutoffTetrio.nerdStats?.cheese;
|
||||||
|
double? avgGbE = data != null ? data[1]["avgGBE"] : widget.cutoffTetrio.nerdStats?.gbe;
|
||||||
|
double? avgNyaAPP = data != null ? data[1]["avgNyaAPP"] : widget.cutoffTetrio.nerdStats?.nyaapp;
|
||||||
|
double? avgArea = data != null ? data[1]["avgArea"] : widget.cutoffTetrio.nerdStats?.area;
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Divider(),
|
Divider(),
|
||||||
Text(t.rankView.avgStats, style: Theme.of(context).textTheme.displayLarge),
|
Text(t.rankView.avgStats, style: Theme.of(context).textTheme.displayLarge),
|
||||||
Text("${f2.format(data != null ? data[0].apm : widget.cutoffTetrio.apm)} ${t.stats.apm.short} • ${f2.format(data != null ? data[0].pps : widget.cutoffTetrio.pps)} ${t.stats.pps.short} • ${f2.format(data != null ? data[0].vs : widget.cutoffTetrio.vs)} ${t.stats.vs.short}", style: Theme.of(context).textTheme.displayLarge),
|
Text("${avgAPM != null ? f2.format(avgAPM) : "-.--"} ${t.stats.apm.short} • ${avgPPS != null ? f2.format(avgPPS) : "-.--"} ${t.stats.pps.short} • ${avgVS != null ? f2.format(avgVS) : "-.--"} ${t.stats.vs.short}", style: Theme.of(context).textTheme.displayLarge),
|
||||||
Divider(),
|
Divider(),
|
||||||
Center(child: Text(t.rankView.avgNerdStats, style: Theme.of(context).textTheme.displayLarge)),
|
Center(child: Text(t.rankView.avgNerdStats, style: Theme.of(context).textTheme.displayLarge)),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(t.stats.app.full, style: Theme.of(context).textTheme.displayLarge),
|
Text(t.stats.app.full, style: Theme.of(context).textTheme.displayLarge),
|
||||||
Text(f3.format(data != null ? data[1]["avgAPP"] : widget.cutoffTetrio.nerdStats?.app), style: Theme.of(context).textTheme.displayLarge)
|
Text(avgAPP != null ? f3.format(avgAPP) : "-.---", style: Theme.of(context).textTheme.displayLarge)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(t.stats.vsapm.full, style: Theme.of(context).textTheme.displayLarge),
|
Text(t.stats.vsapm.full, style: Theme.of(context).textTheme.displayLarge),
|
||||||
Text(f3.format(data != null ? data[1]["avgVSAPM"] : widget.cutoffTetrio.nerdStats?.vsapm), style: Theme.of(context).textTheme.displayLarge)
|
Text(avgVSAPM != null ? f3.format(avgVSAPM) : "-.---", style: Theme.of(context).textTheme.displayLarge)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(t.stats.dss.full, style: Theme.of(context).textTheme.displayLarge),
|
Text(t.stats.dss.full, style: Theme.of(context).textTheme.displayLarge),
|
||||||
Text(f3.format(data != null ? data[1]["avgDSS"] : widget.cutoffTetrio.nerdStats?.dss), style: Theme.of(context).textTheme.displayLarge)
|
Text(avgDSS != null ? f3.format(avgDSS) : "-.---", style: Theme.of(context).textTheme.displayLarge)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(t.stats.dsp.full, style: Theme.of(context).textTheme.displayLarge),
|
Text(t.stats.dsp.full, style: Theme.of(context).textTheme.displayLarge),
|
||||||
Text(f3.format(data != null ? data[1]["avgDSP"] : widget.cutoffTetrio.nerdStats?.dsp), style: Theme.of(context).textTheme.displayLarge)
|
Text(avgDSP != null ? f3.format(avgDSP) : "-.---", style: Theme.of(context).textTheme.displayLarge)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(t.stats.appdsp.full, style: Theme.of(context).textTheme.displayLarge),
|
Text(t.stats.appdsp.full, style: Theme.of(context).textTheme.displayLarge),
|
||||||
Text(f3.format(data != null ? data[1]["avgAPPDSP"] : widget.cutoffTetrio.nerdStats?.appdsp), style: Theme.of(context).textTheme.displayLarge)
|
Text(avgAPPDSP != null ? f3.format(avgAPPDSP) : "-.---", style: Theme.of(context).textTheme.displayLarge)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(t.stats.cheese.full, style: Theme.of(context).textTheme.displayLarge),
|
Text(t.stats.cheese.full, style: Theme.of(context).textTheme.displayLarge),
|
||||||
Text(f2.format(data != null ? data[1]["avgCheese"] : widget.cutoffTetrio.nerdStats?.cheese), style: Theme.of(context).textTheme.displayLarge)
|
Text(avgCheese != null ? f3.format(avgCheese) : "--.--", style: Theme.of(context).textTheme.displayLarge)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(t.stats.gbe.full, style: Theme.of(context).textTheme.displayLarge),
|
Text(t.stats.gbe.full, style: Theme.of(context).textTheme.displayLarge),
|
||||||
Text(f3.format(data != null ? data[1]["avgGBE"] : widget.cutoffTetrio.nerdStats?.gbe), style: Theme.of(context).textTheme.displayLarge)
|
Text(avgGbE != null ? f3.format(avgGbE) : "-.---", style: Theme.of(context).textTheme.displayLarge)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(t.stats.nyaapp.full, style: Theme.of(context).textTheme.displayLarge),
|
Text(t.stats.nyaapp.full, style: Theme.of(context).textTheme.displayLarge),
|
||||||
Text(f3.format(data != null ? data[1]["avgNyaAPP"] : widget.cutoffTetrio.nerdStats?.nyaapp), style: Theme.of(context).textTheme.displayLarge)
|
Text(avgNyaAPP != null ? f3.format(avgNyaAPP) : "-.---", style: Theme.of(context).textTheme.displayLarge)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(t.stats.area.full, style: Theme.of(context).textTheme.displayLarge),
|
Text(t.stats.area.full, style: Theme.of(context).textTheme.displayLarge),
|
||||||
Text(f1.format(data != null ? data[1]["avgArea"] : widget.cutoffTetrio.nerdStats?.area), style: Theme.of(context).textTheme.displayLarge)
|
Text(avgArea != null ? f3.format(avgArea) : "---.-", style: Theme.of(context).textTheme.displayLarge)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart' hide Badge;
|
import 'package:flutter/material.dart' hide Badge;
|
||||||
import 'package:tetra_stats/data_objects/badge.dart';
|
import 'package:tetra_stats/data_objects/badge.dart';
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
|
import 'package:tetra_stats/services/tetrio_crud.dart' show webVersionDomain;
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
||||||
|
|
||||||
|
@ -45,7 +46,12 @@ class BadgesThingy extends StatelessWidget{
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
spacing: 25,
|
spacing: 25,
|
||||||
children: [
|
children: [
|
||||||
Image.asset("res/tetrio_badges/${badge.badgeId}.png"),
|
Image.network(
|
||||||
|
kIsWeb ? "https://${webVersionDomain}/oskware_bridge.php?endpoint=TetrioBadge&badge=${badge.badgeId}" : "https://tetr.io/res/badges/${badge.badgeId}.png",
|
||||||
|
errorBuilder:(context, error, stackTrace) {
|
||||||
|
return ErrorWidget(error);
|
||||||
|
}
|
||||||
|
),
|
||||||
Text(badge.ts != null
|
Text(badge.ts != null
|
||||||
? t.obtainDate(date: timestamp(badge.ts!))
|
? t.obtainDate(date: timestamp(badge.ts!))
|
||||||
: t.assignedManualy),
|
: t.assignedManualy),
|
||||||
|
@ -66,18 +72,12 @@ class BadgesThingy extends StatelessWidget{
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
tooltip: badge.label,
|
tooltip: badge.label,
|
||||||
icon: Image.asset(
|
icon: Image.network(
|
||||||
"res/tetrio_badges/${badge.badgeId}.png",
|
kIsWeb ? "https://${webVersionDomain}/oskware_bridge.php?endpoint=TetrioBadge&badge=${badge.badgeId}" : "https://tetr.io/res/badges/${badge.badgeId}.png",
|
||||||
height: 32,
|
|
||||||
errorBuilder: (context, error, stackTrace) {
|
|
||||||
return Image.network(
|
|
||||||
kIsWeb ? "https://ts.dan63.by/oskware_bridge.php?endpoint=TetrioBadge&badge=${badge.badgeId}" : "https://tetr.io/res/badges/${badge.badgeId}.png",
|
|
||||||
height: 32,
|
height: 32,
|
||||||
errorBuilder:(context, error, stackTrace) {
|
errorBuilder:(context, error, stackTrace) {
|
||||||
return Image.asset("res/icons/kagari.png", height: 32, width: 32);
|
return Image.asset("res/icons/kagari.png", height: 32, width: 32);
|
||||||
}
|
}
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:tetra_stats/data_objects/news.dart';
|
import 'package:tetra_stats/data_objects/news.dart';
|
||||||
import 'package:tetra_stats/data_objects/news_entry.dart';
|
import 'package:tetra_stats/data_objects/news_entry.dart';
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
|
import 'package:tetra_stats/services/tetrio_crud.dart' show webVersionDomain;
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
import 'package:tetra_stats/utils/relative_timestamps.dart';
|
import 'package:tetra_stats/utils/relative_timestamps.dart';
|
||||||
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
||||||
|
@ -73,14 +75,14 @@ class NewsThingy extends StatelessWidget{
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
subtitle: Text(timestamp(news.timestamp)),
|
subtitle: Text(timestamp(news.timestamp)),
|
||||||
leading: Image.asset(
|
leading: Image.network(
|
||||||
"res/tetrio_badges/${news.data["type"]}.png",
|
kIsWeb ? "https://${webVersionDomain}/oskware_bridge.php?endpoint=TetrioBadge&badge=${news.data["type"]}" : "https://tetr.io/res/badges/${news.data["type"]}.png",
|
||||||
height: 48,
|
height: 48,
|
||||||
width: 48,
|
width: 48,
|
||||||
errorBuilder: (context, error, stackTrace) {
|
errorBuilder:(context, error, stackTrace) {
|
||||||
return Image.asset("res/icons/kagari.png", height: 64, width: 64);
|
return Image.asset("res/icons/kagari.png", height: 32, width: 32);
|
||||||
},
|
}
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
case "rankup":
|
case "rankup":
|
||||||
return ListTile(
|
return ListTile(
|
||||||
|
|
|
@ -1,37 +1,116 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:tetra_stats/data_objects/beta_record.dart';
|
import 'package:tetra_stats/data_objects/beta_record.dart';
|
||||||
import 'package:tetra_stats/main.dart';
|
import 'package:tetra_stats/main.dart';
|
||||||
import 'package:tetra_stats/widgets/beta_league_entry_thingy.dart';
|
import 'package:tetra_stats/widgets/beta_league_entry_thingy.dart';
|
||||||
import 'package:tetra_stats/widgets/future_error.dart';
|
import 'package:tetra_stats/widgets/future_error.dart';
|
||||||
|
|
||||||
class TLRecords extends StatelessWidget {
|
class TLRecords extends StatefulWidget {
|
||||||
final String userID;
|
final String userID;
|
||||||
|
|
||||||
/// Widget, that displays Tetra League records.
|
/// Widget, that displays Tetra League records.
|
||||||
/// Accepts list of TL records ([data]) and [userID] of player from the view
|
/// Accepts list of TL records ([data]) and [userID] of player from the view
|
||||||
const TLRecords(this.userID);
|
const TLRecords(this.userID);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<TLRecords> createState() => _TLRecordsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TLRecordsState extends State<TLRecords> {
|
||||||
|
List<BetaRecord> records = [];
|
||||||
|
bool isFetchingRecords = false;
|
||||||
|
bool reachedEndOfRecords = false;
|
||||||
|
final StreamController<List<dynamic>> _recordsStreamController = StreamController<List<dynamic>>.broadcast();
|
||||||
|
Stream<List<dynamic>> get recordsStream => _recordsStreamController.stream;
|
||||||
|
String? recordsPrisecter;
|
||||||
|
late final ScrollController _scrollController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState(){
|
||||||
|
_scrollController = ScrollController();
|
||||||
|
_scrollController.addListener(() {
|
||||||
|
_scrollController.addListener(() {
|
||||||
|
final maxScroll = _scrollController.position.maxScrollExtent;
|
||||||
|
final currentScroll = _scrollController.position.pixels;
|
||||||
|
|
||||||
|
if (currentScroll == maxScroll) {
|
||||||
|
_fetchRecord(widget.userID);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
_fetchRecord(widget.userID);
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _fetchRecord(String userID) async {
|
||||||
|
if (isFetchingRecords || reachedEndOfRecords) {
|
||||||
|
// Avoid fetching new data while already fetching
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
isFetchingRecords = true;
|
||||||
|
|
||||||
|
final items = (await teto.fetchTLStream(userID, prisecter: recordsPrisecter)).records;
|
||||||
|
|
||||||
|
if (items.isEmpty) reachedEndOfRecords = true;
|
||||||
|
records.addAll(items);
|
||||||
|
if (items.isNotEmpty){
|
||||||
|
_recordsStreamController.add(records);
|
||||||
|
recordsPrisecter = records.last.prisecter.toString();
|
||||||
|
} else{
|
||||||
|
_recordsStreamController.add([]);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_recordsStreamController.addError(e);
|
||||||
|
} finally {
|
||||||
|
// Set to false when data fetching is complete
|
||||||
|
isFetchingRecords = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> resetRecords(String userID) async {
|
||||||
|
records.clear();
|
||||||
|
recordsPrisecter = null;
|
||||||
|
reachedEndOfRecords = false;
|
||||||
|
_fetchRecord(userID);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FutureBuilder(
|
return StreamBuilder(
|
||||||
future: teto.fetchTLStream(userID),
|
stream: recordsStream,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
switch (snapshot.connectionState){
|
switch (snapshot.connectionState){
|
||||||
case ConnectionState.none:
|
case ConnectionState.none:
|
||||||
case ConnectionState.waiting:
|
case ConnectionState.waiting:
|
||||||
case ConnectionState.active:
|
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
case ConnectionState.active:
|
||||||
case ConnectionState.done:
|
case ConnectionState.done:
|
||||||
if (snapshot.hasData){
|
if (snapshot.hasData){
|
||||||
return Column(
|
return SizedBox(
|
||||||
children: [
|
height: MediaQuery.of(context).size.height - 130,
|
||||||
for (BetaRecord record in snapshot.data!.records) BetaLeagueEntryThingy(record, userID)
|
child: ListView.builder(
|
||||||
],
|
controller: _scrollController,
|
||||||
|
itemCount: records.length,
|
||||||
|
prototypeItem: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 4.0),
|
||||||
|
child: ListTile(
|
||||||
|
leading: Text("0"),
|
||||||
|
title: Text("ehhh...", style: TextStyle(fontSize: 22)),
|
||||||
|
trailing: SizedBox(height: 36, width: 1),
|
||||||
|
subtitle: const Text("eh\n...", style: TextStyle(color: Colors.grey, fontSize: 12)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
itemBuilder: (BuildContext context, int index){
|
||||||
|
return BetaLeagueEntryThingy(records[index], widget.userID);
|
||||||
|
}
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (snapshot.hasError){ return SizedBox(height: 500, child: Center(child: FutureError(snapshot))); }
|
if (snapshot.hasError){ return SizedBox(height: 500, child: Center(child: FutureError(snapshot))); }
|
||||||
|
return const Center(child: Text("whar?"));
|
||||||
}
|
}
|
||||||
return const Text("what?");
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.dart' show Size;
|
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
||||||
import 'package:tetra_stats/data_objects/tetrio_constants.dart';
|
import 'package:tetra_stats/data_objects/tetrio_constants.dart';
|
||||||
|
@ -201,7 +201,7 @@ class _UserThingyState extends State<UserThingy> with SingleTickerProviderStateM
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
top: widget.player.bannerRevision != null ? 160.0 : 80.0,
|
top: (kIsWeb || !Platform.isAndroid) ? widget.player.bannerRevision != null ? 160.0 : 80.0 : widget.player.bannerRevision != null ? 152.0 : 72.0,
|
||||||
left: 160.0,
|
left: 160.0,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|
|
@ -83,15 +83,21 @@ class ZenithThingy extends StatelessWidget{
|
||||||
if (zenith!.rank != -1) TextSpan(text: "№ ${intf.format(zenith!.rank)}", style: TextStyle(color: getColorOfRank(zenith!.rank))),
|
if (zenith!.rank != -1) TextSpan(text: "№ ${intf.format(zenith!.rank)}", style: TextStyle(color: getColorOfRank(zenith!.rank))),
|
||||||
if (zenith!.rank != -1) const TextSpan(text: " • "),
|
if (zenith!.rank != -1) const TextSpan(text: " • "),
|
||||||
if (zenith!.countryRank != -1) TextSpan(text: "№ ${intf.format(zenith!.countryRank)} local", style: TextStyle(color: getColorOfRank(zenith!.countryRank))),
|
if (zenith!.countryRank != -1) TextSpan(text: "№ ${intf.format(zenith!.countryRank)} local", style: TextStyle(color: getColorOfRank(zenith!.countryRank))),
|
||||||
if (zenith!.countryRank != -1) const TextSpan(text: " • "),
|
if (zenith!.countryRank != -1) TextSpan(text: width > 400.0 ? " • " : "\n"),
|
||||||
TextSpan(text: timestamp(zenith!.timestamp)),
|
TextSpan(text: timestamp(zenith!.timestamp)),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (zenith != null && (zenith!.extras as ZenithExtras).mods.isNotEmpty) Container(width: 16.0),
|
if (zenith != null && (zenith!.extras as ZenithExtras).mods.isNotEmpty && width > 600.0) Container(width: 16.0),
|
||||||
if (zenith != null && (zenith!.extras as ZenithExtras).mods.isNotEmpty) for (String mod in (zenith!.extras as ZenithExtras).mods) Image.asset("res/icons/${mod}.png", height: 64.0)
|
if (zenith != null && (zenith!.extras as ZenithExtras).mods.isNotEmpty && width > 600.0) for (String mod in (zenith!.extras as ZenithExtras).mods) Image.asset("res/icons/${mod}.png", height: 64.0)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (zenith != null && (zenith!.extras as ZenithExtras).mods.isNotEmpty && width <= 600.0) Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
for (String mod in (zenith!.extras as ZenithExtras).mods) Image.asset("res/icons/${mod}.png", height: 32.0)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (zenith != null) Row(
|
if (zenith != null) Row(
|
||||||
|
|
|
@ -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.0-beta2+40
|
version: 2.0.0+41
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0'
|
sdk: '>=3.0.0'
|
||||||
|
@ -88,7 +88,6 @@ flutter:
|
||||||
- res/avatars/
|
- res/avatars/
|
||||||
- res/icons/
|
- res/icons/
|
||||||
- res/tetrio_tl_alpha_ranks/
|
- res/tetrio_tl_alpha_ranks/
|
||||||
- res/tetrio_badges/
|
|
||||||
- res/images/
|
- res/images/
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
|
|
|
@ -188,7 +188,7 @@
|
||||||
"relevance": "на момент $timestamp",
|
"relevance": "на момент $timestamp",
|
||||||
"actual": "Требование",
|
"actual": "Требование",
|
||||||
"target": "Цель",
|
"target": "Цель",
|
||||||
"cutoffTR": "Требуемый TR",
|
"cutoffTR": "Треб. TR",
|
||||||
"targetTR": "Целевой TR",
|
"targetTR": "Целевой TR",
|
||||||
"state": "Состояние",
|
"state": "Состояние",
|
||||||
"advanced": "Продвинутая",
|
"advanced": "Продвинутая",
|
||||||
|
|
|
@ -0,0 +1,929 @@
|
||||||
|
{
|
||||||
|
"locales(map)": {
|
||||||
|
"en": "英语 (English)",
|
||||||
|
"ru-RU": "俄语 (Русский)",
|
||||||
|
"zh-CN": "简体中文"
|
||||||
|
},
|
||||||
|
"gamemodes(map)": {
|
||||||
|
"league": "Tetra 联赛",
|
||||||
|
"zenith": "快速游戏",
|
||||||
|
"zenithex": "快速游戏 · 专家模式",
|
||||||
|
"40l": "40行竞速",
|
||||||
|
"blitz": "闪电战",
|
||||||
|
"5mblast": "5,000,000 Blast",
|
||||||
|
"zen": "禅意模式"
|
||||||
|
},
|
||||||
|
"destinations": {
|
||||||
|
"home": "主页",
|
||||||
|
"graphs": "图表",
|
||||||
|
"leaderboards": "排行榜",
|
||||||
|
"cutoffs": "段位分界线",
|
||||||
|
"calc": "计算器",
|
||||||
|
"info": "信息中心",
|
||||||
|
"data": "已保存的数据",
|
||||||
|
"settings": "设置"
|
||||||
|
},
|
||||||
|
"playerRole(map)": {
|
||||||
|
"user": "用户",
|
||||||
|
"banned": "已封禁",
|
||||||
|
"bot": "机器人",
|
||||||
|
"sysop": "系统操作员",
|
||||||
|
"admin": "管理员",
|
||||||
|
"mod": "管理员",
|
||||||
|
"halfmod": "社区管理员",
|
||||||
|
"anon": "匿名用户"
|
||||||
|
},
|
||||||
|
"goBackButton": "返回",
|
||||||
|
"nanow": "目前不可用...",
|
||||||
|
"seasonEnds": "当前赛季还有${countdown}结束",
|
||||||
|
"seasonEnded": "赛季已结束",
|
||||||
|
"overallPB": "生涯最佳:$pb m",
|
||||||
|
"gamesUntilRanked": "还有${left}局才可获得段位",
|
||||||
|
"numOfVictories": "~${wins}次胜局",
|
||||||
|
"promotionOnNextWin": "下一场胜局即可升段",
|
||||||
|
"numOfdefeats": "~${losses}次负局",
|
||||||
|
"demotionOnNextLoss": "下一场负局即可掉段",
|
||||||
|
"records": "记录",
|
||||||
|
"nerdStats": "详细信息",
|
||||||
|
"playstyles": "游戏方式",
|
||||||
|
"horoscopes": "散点图",
|
||||||
|
"relatedAchievements": "相关成就",
|
||||||
|
"season": "赛季",
|
||||||
|
"smooth": "平滑",
|
||||||
|
"dateAndTime": "日期和时间:",
|
||||||
|
"TLfullLBnote": "很大,但允许你通过玩家的数据对玩家进行排序,还可以按段位筛选玩家",
|
||||||
|
"rank": "段位",
|
||||||
|
"verdictGeneral": "比 $rank 段平均数据$n $verdict",
|
||||||
|
"verdictBetter": "好",
|
||||||
|
"verdictWorse": "差",
|
||||||
|
"localStanding": "本地",
|
||||||
|
"xp": {
|
||||||
|
"title": "经验等级",
|
||||||
|
"progressToNextLevel": "到下一等级的进度:$percentage",
|
||||||
|
"progressTowardsGoal": "从0级到$goal级的进度:$percentage (还差 $left 点经验值)"
|
||||||
|
},
|
||||||
|
"gametime": {
|
||||||
|
"title": "精确游戏时长",
|
||||||
|
"gametimeAday": "平均每天$gametime",
|
||||||
|
"breakdown": "相当于 $years 年,\n$months 月,\n$days 天,\n$minutes 分钟,\n$seconds 秒"
|
||||||
|
},
|
||||||
|
"track": "跟踪",
|
||||||
|
"stopTracking": "停止跟踪",
|
||||||
|
"supporter": "${tier}级会员",
|
||||||
|
"comparingWith": "${newDate}的数据与${oldDate}相比",
|
||||||
|
"compare": "比较",
|
||||||
|
"comparison": "比较",
|
||||||
|
"enterUsername": "输入用户名或者\\$avgX (X是一个段位)",
|
||||||
|
"general": "常规",
|
||||||
|
"badges": "勋章",
|
||||||
|
"obtainDate": "于${date}获得",
|
||||||
|
"assignedManualy": "此徽章由TETR.IO管理员手动颁发",
|
||||||
|
"distinguishment": "区别",
|
||||||
|
"banned": "已封禁",
|
||||||
|
"bannedSubtext": "由于 TETR.IO 规则或服务条款被违反 而被封禁",
|
||||||
|
"badStanding": "信誉不佳",
|
||||||
|
"badStandingSubtext": "近期有一次或多次违禁行为",
|
||||||
|
"botAccount": "机器人账号",
|
||||||
|
"botAccountSubtext": "由$botMaintainers管理",
|
||||||
|
"copiedToClipboard": "已复制到剪贴板!",
|
||||||
|
"bio": "个性签名",
|
||||||
|
"news": "新闻",
|
||||||
|
"matchResult": {
|
||||||
|
"victory": "胜利",
|
||||||
|
"defeat": "失败",
|
||||||
|
"tie": "平局",
|
||||||
|
"dqvictory": "对手被取消资格",
|
||||||
|
"dqdefeat": "被取消资格",
|
||||||
|
"nocontest": "无竞赛记录",
|
||||||
|
"nullified": "竞赛记录已取消"
|
||||||
|
},
|
||||||
|
"distinguishments": {
|
||||||
|
"noHeader": "缺少标题",
|
||||||
|
"noFooter": "缺少标题",
|
||||||
|
"twc": "TETR.IO 世界冠军",
|
||||||
|
"twcYear": "$year TETR.IO 世界杯"
|
||||||
|
},
|
||||||
|
"newsEntries": {
|
||||||
|
"leaderboard(rich)": "在$gametype中荣获第$rank名",
|
||||||
|
"personalbest(rich)": "在$gametype中取得新纪录:$pb",
|
||||||
|
"badge(rich)": "获得勋章 $badge",
|
||||||
|
"rankup(rich)": "升 $rank",
|
||||||
|
"supporter(rich)": "成为${s(TETR.IO supporter)}",
|
||||||
|
"supporter_gift(rich)": "被赠送${s(TETR.IO supporter)}",
|
||||||
|
"unknown(rich)": "未知新闻类型 $type"
|
||||||
|
},
|
||||||
|
"rankupMiddle": "${r} 段",
|
||||||
|
"copyUserID": "点击以复制用户 ID",
|
||||||
|
"searchHint": "用户名或 ID",
|
||||||
|
"navMenu": "导航菜单",
|
||||||
|
"navMenuTooltip": "打开导航菜单",
|
||||||
|
"refresh": "刷新数据",
|
||||||
|
"searchButton": "搜索",
|
||||||
|
"trackedPlayers": "跟踪的玩家",
|
||||||
|
"standing": "名次",
|
||||||
|
"previousSeasons": "上赛季",
|
||||||
|
"recent": "最近",
|
||||||
|
"top": "前",
|
||||||
|
"noRecord": "暂无记录",
|
||||||
|
"sprintAndBlitsRelevance": "${date}",
|
||||||
|
"snackBarMessages": {
|
||||||
|
"stateRemoved": "成功移除${date}时的状态!",
|
||||||
|
"matchRemoved": "成功移除${date}时的一局!",
|
||||||
|
"notForWeb": "此功能在网络版本中不可用",
|
||||||
|
"importSuccess": "导入成功",
|
||||||
|
"importCancelled": "导入已取消"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"noRecords": "暂无记录",
|
||||||
|
"notEnoughData": "数据不足",
|
||||||
|
"noHistorySaved": "没有保存历史记录",
|
||||||
|
"connection": "连接错误:${code} ${message}",
|
||||||
|
"noSuchUser": "用户不存在",
|
||||||
|
"noSuchUserSub": "您输入的内容有误,或者用户不存在",
|
||||||
|
"discordNotAssigned": "没有指定Discord ID的用户",
|
||||||
|
"discordNotAssignedSub": "请确保您提供了有效的 ID",
|
||||||
|
"history": "缺少该玩家的历史",
|
||||||
|
"actionSuggestion": "也许,您想要",
|
||||||
|
"p1nkl0bst3rTLmatches": "没有找到Tetra联赛比赛",
|
||||||
|
"clientException": "你尚未连接",
|
||||||
|
"forbidden": "您的 IP 地址已被封禁",
|
||||||
|
"forbiddenSub": "如果你在使用VPN,请关闭。如果仍然不可以,请联系$nickname",
|
||||||
|
"tooManyRequests": "您的评分已经被限制",
|
||||||
|
"tooManyRequestsSub": "请稍后重试",
|
||||||
|
"internal": "TETR.IO 出现了问题!",
|
||||||
|
"internalSub": "osk,或许,已经知道了",
|
||||||
|
"internalWebVersion": "TETR.IO (也许是oskware_bridge,我不知道到底是哪儿) 出现了问题!",
|
||||||
|
"internalWebVersionSub": "如果 osk status 页面显示一切都很正常,请联系dan63047",
|
||||||
|
"oskwareBridge": "oskware_bridge 出现了问题!",
|
||||||
|
"oskwareBridgeSub": "请联系dan63047",
|
||||||
|
"p1nkl0bst3rForbidden": "第三方API屏蔽了您的 IP 地址",
|
||||||
|
"p1nkl0bst3rTooManyRequests": "第三方API请求过多,请稍后再试",
|
||||||
|
"p1nkl0bst3rinternal": "p1nkl0bst3r 那边出现了问题!",
|
||||||
|
"p1nkl0bst3rinternalWebVersion": "p1nkl0bst3r 那边(也许是oskware_bridge,我不知道到底是哪儿)出现了问题!",
|
||||||
|
"replayAlreadySaved": "回放已保存",
|
||||||
|
"replayExpired": "回放已过期或不再可用",
|
||||||
|
"replayRejected": "第三方API屏蔽了您的 IP 地址"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"cancel": "取消",
|
||||||
|
"submit": "确定",
|
||||||
|
"ok": "确定",
|
||||||
|
"apply": "应用",
|
||||||
|
"refresh": "刷新"
|
||||||
|
},
|
||||||
|
"graphsDestination": {
|
||||||
|
"fetchAndsaveTLHistory": "获取玩家历史",
|
||||||
|
"fetchAndSaveOldTLmatches": "获取 Tetra 联赛历史记录",
|
||||||
|
"fetchAndsaveTLHistoryResult": "找到 ${number} 个状态",
|
||||||
|
"fetchAndSaveOldTLmatchesResult": "找到 ${number} 场比赛",
|
||||||
|
"gamesPlayed": "游玩次数:$games",
|
||||||
|
"dateAndTime": "日期和时间",
|
||||||
|
"filterModaleTitle": "在图表上筛选等级"
|
||||||
|
},
|
||||||
|
"filterModale": {
|
||||||
|
"all": "全部"
|
||||||
|
},
|
||||||
|
"cutoffsDestination": {
|
||||||
|
"title": "Tetra 联赛 状态",
|
||||||
|
"relevance": "$timestamp",
|
||||||
|
"actual": "实际",
|
||||||
|
"target": "目标",
|
||||||
|
"cutoffTR": "分段 TR",
|
||||||
|
"targetTR": "目标 TR",
|
||||||
|
"state": "状态",
|
||||||
|
"advanced": "高级选项",
|
||||||
|
"players": "玩家($n)",
|
||||||
|
"moreInfo": "更多信息",
|
||||||
|
"NumberOne": "№ 1 is $tr TR",
|
||||||
|
"inflated": "高于目标 $tr",
|
||||||
|
"notInflated": "不偏高",
|
||||||
|
"deflated": "低于目标 $tr",
|
||||||
|
"notDeflated": "不偏低",
|
||||||
|
"wellDotDotDot": "嗯…",
|
||||||
|
"fromPlace": "自 № $n",
|
||||||
|
"viewButton": "查看"
|
||||||
|
},
|
||||||
|
"rankView": {
|
||||||
|
"rankTitle": "$rank 段数据",
|
||||||
|
"everyoneTitle": "全部排行榜",
|
||||||
|
"trRange": "TR 范围",
|
||||||
|
"supposedToBe": "应为",
|
||||||
|
"gap": "相差 $value",
|
||||||
|
"trGap": "相差 $value TR",
|
||||||
|
"deflationGap": "偏低量",
|
||||||
|
"inflationGap": "偏高量",
|
||||||
|
"LBposRange": "排行榜位置范围",
|
||||||
|
"overpopulated": "比期望的多 $players",
|
||||||
|
"underpopulated": "比期望的少 $players",
|
||||||
|
"PlayersEqualSupposedToBe": "符合",
|
||||||
|
"avgStats": "平均数据",
|
||||||
|
"avgForRank": "$rank 段平均数据",
|
||||||
|
"avgNerdStats": "平均详细信息",
|
||||||
|
"minimums": "最小值",
|
||||||
|
"maximums": "最大值"
|
||||||
|
},
|
||||||
|
"stateView": {
|
||||||
|
"title": "$date的状态"
|
||||||
|
},
|
||||||
|
"tlMatchView": {
|
||||||
|
"match": "匹配",
|
||||||
|
"vs": "vs",
|
||||||
|
"winner": "获胜者",
|
||||||
|
"roundNumber": "第$n回合",
|
||||||
|
"statsFor": "状态",
|
||||||
|
"numberOfRounds": "回合数",
|
||||||
|
"matchLength": "比赛时长",
|
||||||
|
"roundLength": "回合时长",
|
||||||
|
"matchStats": "比赛数据",
|
||||||
|
"downloadReplay": "下载 .ttrm 回放",
|
||||||
|
"openReplay": "在 TETR.IO 中打开回放"
|
||||||
|
},
|
||||||
|
"calcDestination": {
|
||||||
|
"placeholders": "输入你的$stat",
|
||||||
|
"tip": "输入值并按 \"计算\" 来查看TA的详细信息",
|
||||||
|
"statsCalcButton": "计算",
|
||||||
|
"damageCalcTip": "点击左侧的操作在此添加",
|
||||||
|
"actions": "操作",
|
||||||
|
"results": "结果",
|
||||||
|
"rules": "规则",
|
||||||
|
"noSpinClears": "非 Spin 清除",
|
||||||
|
"spins": "Spin",
|
||||||
|
"miniSpins": "Mini spin",
|
||||||
|
"noLineclear": "无清除(连消结束)",
|
||||||
|
"custom": "自定义",
|
||||||
|
"multiplier": "倍增",
|
||||||
|
"pcDamage": "全消伤害",
|
||||||
|
"comboTable": "连击",
|
||||||
|
"b2bChaining": "B2B增伤",
|
||||||
|
"surgeStartAtB2B": "开始于B2B",
|
||||||
|
"surgeStartAmount": "初始值",
|
||||||
|
"totalDamage": "累计伤害",
|
||||||
|
"lineclears": "清除行数",
|
||||||
|
"combo": "连击",
|
||||||
|
"surge": "B2B充能",
|
||||||
|
"pcs": "全消"
|
||||||
|
},
|
||||||
|
"infoDestination": {
|
||||||
|
"title": "信息中心",
|
||||||
|
"sprintAndBlitzAverages": "40 行 & 闪电战平均数据",
|
||||||
|
"sprintAndBlitzAveragesDescription": "计算40 行 & 闪电战平均数据是个很繁琐的过程,所以很久才会更新一次。 点击标题查看完整的 40 行 & 闪电战平均数据表",
|
||||||
|
"tetraStatsWiki": "Tetra Stats Wiki",
|
||||||
|
"tetraStatsWikiDescription": "查看更多关于Tetra Stats提供的功能和数据",
|
||||||
|
"about": "关于 Tetra Stats",
|
||||||
|
"aboutDescription": "由 dan63 开发"
|
||||||
|
},
|
||||||
|
"leaderboardsDestination": {
|
||||||
|
"title": "排行榜",
|
||||||
|
"tl": "Tetra 联赛(当前赛季)",
|
||||||
|
"fullTL": "Tetra 联赛(当前赛季,完整)",
|
||||||
|
"ar": "成就点"
|
||||||
|
},
|
||||||
|
"savedDataDestination": {
|
||||||
|
"title": "已保存的数据",
|
||||||
|
"tip": "选择左边的昵称以查看与之相关的数据",
|
||||||
|
"seasonTLstates": "第$s赛季状态",
|
||||||
|
"TLrecords": "联赛记录"
|
||||||
|
},
|
||||||
|
"settingsDestination": {
|
||||||
|
"title": "设置",
|
||||||
|
"general": "常规",
|
||||||
|
"customization": "自定义设置",
|
||||||
|
"database": "本地数据库",
|
||||||
|
"checking": "正在检查...",
|
||||||
|
"enterToSubmit": "按回车键提交",
|
||||||
|
"account": "您的 TETR.IO 账号",
|
||||||
|
"accountDescription": "该玩家的状态将在启动此应用后立即加载。 默认情况下,它会加载我的数据。如要更改,请在此输入您的昵称。",
|
||||||
|
"done": "完成!",
|
||||||
|
"noSuchAccount": "账号不存在",
|
||||||
|
"language": "语言",
|
||||||
|
"languageDescription": "Tetra Stats 有$languages。默认情况下,应用程序将选择您的系统语言,如果您的系统区域设置不可用,则选择英语。",
|
||||||
|
"languages(plural)": {
|
||||||
|
"zero": "0种语言",
|
||||||
|
"one": "$n种语言",
|
||||||
|
"two": "$n种语言",
|
||||||
|
"few": "$n种语言",
|
||||||
|
"many": "$n种语言",
|
||||||
|
"other": "$n种语言"
|
||||||
|
},
|
||||||
|
"updateInTheBackground": "后台更新数据",
|
||||||
|
"updateInTheBackgroundDescription": "如果开启,Tetra Stats将尝试在缓存过期后查询新信息。通常一次/5分钟。",
|
||||||
|
"compareStats": "将TL数据与段位平均水平作比较",
|
||||||
|
"compareStatsDescription": "如果开启,Tetra Stats将提供额外的量度,使您能够将自己与普通玩家的等级相比较。 你看到它的方式——统计信息将以相应的颜色高亮,用光标悬停在它们上面以获取更多信息。",
|
||||||
|
"showPosition": "显示排行榜中的位置",
|
||||||
|
"showPositionDescription": "这可能需要一些时间(和流量),但您可以看到您在排行榜上的位置,按数据排序",
|
||||||
|
"accentColor": "主题色",
|
||||||
|
"accentColorDescription": "这种颜色会在这个应用上可见,而且通常会高亮显示交互界面元素。",
|
||||||
|
"accentColorModale": "选取主题色",
|
||||||
|
"timestamps": "时间戳格式",
|
||||||
|
"timestampsDescriptionPart1": "您可以选择时间戳显示时间的方式。默认情况下,它们以 GMT 时区显示时间,并根据所选区域设置进行格式设置,例如:$d。",
|
||||||
|
"timestampsDescriptionPart2": "这里还有:\n• 以您的时区设置的区域设置:$y\n• 相对时间戳:$r",
|
||||||
|
"timestampsAbsoluteGMT": "GMT",
|
||||||
|
"timestampsAbsoluteLocalTime": "您的时区",
|
||||||
|
"timestampsRelative": "相对",
|
||||||
|
"sheetbotLikeGraphs": "Sheetbot 型雷达图",
|
||||||
|
"sheetbotLikeGraphsDescription": "尽管我认为,图表在 SheetBot 中的工作方式不是很正确,有些人感到困惑,那 -0.5 Stride 看起来不像它在 SheetBot 图表上那样。因此,我们这里有:如果开启,则如果数值为负,则图形上的点可以出现在图形的另一半。",
|
||||||
|
"oskKagariGimmick": "Osk-Kagari",
|
||||||
|
"oskKagariGimmickDescription": "如果开启,osk的段位会显示为:kagari:",
|
||||||
|
"bytesOfDataStored": "存储数据",
|
||||||
|
"TLrecordsSaved": "已保存 Tetra 联赛记录",
|
||||||
|
"TLplayerstatesSaved": "已保存 Tetra 联赛玩家状态",
|
||||||
|
"fixButton": "修复",
|
||||||
|
"compressButton": "压缩",
|
||||||
|
"exportDB": "导出本地数据库",
|
||||||
|
"desktopExportAlertTitle": "桌面导出",
|
||||||
|
"desktopExportText": "看起来您在桌面上使用了这个应用程序。请检查您的文档文件夹,您应该找到\"TetraStats.db\"。请将其复制到某处",
|
||||||
|
"androidExportAlertTitle": "Android 导出",
|
||||||
|
"androidExportText": "已导出。\n${exportedDB}",
|
||||||
|
"importDB": "导入本地数据库",
|
||||||
|
"importDBDescription": "还原您的备份。请注意已存储的数据库将被覆盖。",
|
||||||
|
"importWrongFileType": "文件类型错误!"
|
||||||
|
},
|
||||||
|
"homeNavigation": {
|
||||||
|
"overview": "概览",
|
||||||
|
"standing": "名次",
|
||||||
|
"seasons": "赛季",
|
||||||
|
"mathces": "比赛场次",
|
||||||
|
"pb": "个人最佳",
|
||||||
|
"normal": "普通模式",
|
||||||
|
"expert": "专家模式",
|
||||||
|
"expertRecords": "专家模式记录"
|
||||||
|
},
|
||||||
|
"graphsNavigation": {
|
||||||
|
"history": "玩家历史记录",
|
||||||
|
"league": "联赛状态",
|
||||||
|
"cutoffs": "分段线历史"
|
||||||
|
},
|
||||||
|
"calcNavigation": {
|
||||||
|
"stats": "数据计算器",
|
||||||
|
"damage": "伤害计算器"
|
||||||
|
},
|
||||||
|
"firstTimeView": {
|
||||||
|
"welcome": "欢迎使用 Tetra Stats",
|
||||||
|
"description": "服务,允许您跟踪TETR.IO的各种数据",
|
||||||
|
"nicknameQuestion": "您的昵称是?",
|
||||||
|
"inpuntHint": "在此处输入... (3-16个符号)",
|
||||||
|
"emptyInputError": "不能提交空字符串",
|
||||||
|
"niceToSeeYou": "很高兴见到你,$n",
|
||||||
|
"letsTakeALook": "让我们看看您的统计资料...",
|
||||||
|
"skip": "跳过"
|
||||||
|
},
|
||||||
|
"aboutView": {
|
||||||
|
"title": "关于 Tetra Stats",
|
||||||
|
"about": "Tetra Stats是一种服务,与TETR.IO Tetra Channel API共用,提供数据并根据这种数据计算一些附加度量。 服务允许用户用\"Track\"功能跟踪他们在Tetra League中的进度,这个功能记录每个Tetra Leage更改到本地数据库(非自动) 您必须不时地访问服务。这样,这些更改可以通过图表来查看。\n\nBeanserver blaster 是Tetra Stats的一部分,它被拆解成服务器侧脚本。 它提供完整的Tetra League排行榜,允许Tetra Stats通过任何公式对排行榜进行排序并生成散点图,这允许用户分析Tetra联赛趋势。 它还提供了Tetra League 的评分历史,用户也可以通过图表看到。\n\n我们有一个添加回放分析和锦标赛历史记录的计划,所以随时关注!\n\n服务没有与TETR.IO与osk以任何身份关联。",
|
||||||
|
"appVersion": "版本",
|
||||||
|
"build": "$build",
|
||||||
|
"GHrepo": "GitHub Repository",
|
||||||
|
"submitAnIssue": "提交问题",
|
||||||
|
"credits": "鸣谢",
|
||||||
|
"authorAndDeveloper": "作者 & 开发者",
|
||||||
|
"providedFormulas": "提供的公式",
|
||||||
|
"providedS1history": "提供的 S1 历史",
|
||||||
|
"inoue": "Inoue (回放抓取器)",
|
||||||
|
"zhCNlocale": "简中翻译员",
|
||||||
|
"supportHim": "为他提供支持!"
|
||||||
|
},
|
||||||
|
"stats": {
|
||||||
|
"registrationDate": "注册时间",
|
||||||
|
"gametime": "游玩时长",
|
||||||
|
"ogp": "在线游戏次数",
|
||||||
|
"ogw": "在线游戏胜利次数",
|
||||||
|
"followers": "粉丝",
|
||||||
|
"xp": {
|
||||||
|
"short": "经验值",
|
||||||
|
"full": "经验点"
|
||||||
|
},
|
||||||
|
"tr": {
|
||||||
|
"short": "TR",
|
||||||
|
"full": "Tetra 评分"
|
||||||
|
},
|
||||||
|
"glicko": {
|
||||||
|
"short": "Glicko",
|
||||||
|
"full": "Glicko"
|
||||||
|
},
|
||||||
|
"rd": {
|
||||||
|
"short": "RD",
|
||||||
|
"full": "评分偏差"
|
||||||
|
},
|
||||||
|
"glixare": {
|
||||||
|
"short": "GXE",
|
||||||
|
"full": "GLIXARE"
|
||||||
|
},
|
||||||
|
"s1tr": {
|
||||||
|
"short": "S1 TR",
|
||||||
|
"full": "第 1 赛季式 TR"
|
||||||
|
},
|
||||||
|
"gp": {
|
||||||
|
"short": "GP",
|
||||||
|
"full": "总场数"
|
||||||
|
},
|
||||||
|
"gw": {
|
||||||
|
"short": "GW",
|
||||||
|
"full": "胜场数"
|
||||||
|
},
|
||||||
|
"winrate": {
|
||||||
|
"short": "WR%",
|
||||||
|
"full": "胜率"
|
||||||
|
},
|
||||||
|
"apm": {
|
||||||
|
"short": "APM",
|
||||||
|
"full": "每分钟攻击数"
|
||||||
|
},
|
||||||
|
"pps": {
|
||||||
|
"short": "PPS",
|
||||||
|
"full": "每秒块数"
|
||||||
|
},
|
||||||
|
"vs": {
|
||||||
|
"short": "VS",
|
||||||
|
"full": "VS 分数"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"short": "APP",
|
||||||
|
"full": "每块攻击数"
|
||||||
|
},
|
||||||
|
"vsapm": {
|
||||||
|
"short": "VS/APM",
|
||||||
|
"full": "VS / APM"
|
||||||
|
},
|
||||||
|
"dss": {
|
||||||
|
"short": "DS/S",
|
||||||
|
"full": "每秒挖掘数"
|
||||||
|
},
|
||||||
|
"dsp": {
|
||||||
|
"short": "DS/P",
|
||||||
|
"full": "每块挖掘数"
|
||||||
|
},
|
||||||
|
"appdsp": {
|
||||||
|
"short": "APP+DSP",
|
||||||
|
"full": "APP + DSP"
|
||||||
|
},
|
||||||
|
"cheese": {
|
||||||
|
"short": "CI",
|
||||||
|
"full": "垃圾行混乱指数"
|
||||||
|
},
|
||||||
|
"gbe": {
|
||||||
|
"short": "GbE",
|
||||||
|
"full": "垃圾行效率"
|
||||||
|
},
|
||||||
|
"nyaapp": {
|
||||||
|
"short": "wAPP",
|
||||||
|
"full": "加权APP"
|
||||||
|
},
|
||||||
|
"area": {
|
||||||
|
"short": "面积",
|
||||||
|
"full": "面积"
|
||||||
|
},
|
||||||
|
"etr": {
|
||||||
|
"short": "eTR",
|
||||||
|
"full": "预测 TR"
|
||||||
|
},
|
||||||
|
"etracc": {
|
||||||
|
"short": "±eTR",
|
||||||
|
"full": "预测实际差量"
|
||||||
|
},
|
||||||
|
"opener": {
|
||||||
|
"short": "定式",
|
||||||
|
"full": "定式"
|
||||||
|
},
|
||||||
|
"plonk": {
|
||||||
|
"short": "太极",
|
||||||
|
"full": "太极"
|
||||||
|
},
|
||||||
|
"stride": {
|
||||||
|
"short": "速度",
|
||||||
|
"full": "速度"
|
||||||
|
},
|
||||||
|
"infds": {
|
||||||
|
"short": "挖掘",
|
||||||
|
"full": "挖掘"
|
||||||
|
},
|
||||||
|
"altitude": {
|
||||||
|
"short": "m",
|
||||||
|
"full": "高度"
|
||||||
|
},
|
||||||
|
"climbSpeed": {
|
||||||
|
"short": "CSP",
|
||||||
|
"full": "爬行速度",
|
||||||
|
"gaugetTitle": "爬行速度"
|
||||||
|
},
|
||||||
|
"peakClimbSpeed": {
|
||||||
|
"short": "最高CSP",
|
||||||
|
"full": "最高爬行速度",
|
||||||
|
"gaugetTitle": "最高"
|
||||||
|
},
|
||||||
|
"kos": {
|
||||||
|
"short": "KO's",
|
||||||
|
"full": "击杀"
|
||||||
|
},
|
||||||
|
"b2b": {
|
||||||
|
"short": "B2B",
|
||||||
|
"full": "背靠背/满贯"
|
||||||
|
},
|
||||||
|
"finesse": {
|
||||||
|
"short": "极",
|
||||||
|
"full": "极简率",
|
||||||
|
"widgetTitle": "简率"
|
||||||
|
},
|
||||||
|
"finesseFaults": {
|
||||||
|
"short": "非极简",
|
||||||
|
"full": "非极简操作数"
|
||||||
|
},
|
||||||
|
"totalTime": {
|
||||||
|
"short": "时长",
|
||||||
|
"full": "总时长",
|
||||||
|
"widgetTitle": "总时长"
|
||||||
|
},
|
||||||
|
"level": {
|
||||||
|
"short": "Lvl",
|
||||||
|
"full": "等级"
|
||||||
|
},
|
||||||
|
"pieces": {
|
||||||
|
"short": "P",
|
||||||
|
"full": "块"
|
||||||
|
},
|
||||||
|
"spp": {
|
||||||
|
"short": "SPP",
|
||||||
|
"full": "每块得分"
|
||||||
|
},
|
||||||
|
"kp": {
|
||||||
|
"short": "KP",
|
||||||
|
"full": "按键"
|
||||||
|
},
|
||||||
|
"kpp": {
|
||||||
|
"short": "KPP",
|
||||||
|
"full": "每块按键数"
|
||||||
|
},
|
||||||
|
"kps": {
|
||||||
|
"short": "KPS",
|
||||||
|
"full": "每秒按键数"
|
||||||
|
},
|
||||||
|
"blitzScore": "$p 分",
|
||||||
|
"levelUpRequirement": "还需 $p 升到下一级",
|
||||||
|
"piecesTotal": "放块总数",
|
||||||
|
"piecesWithPerfectFinesse": "极简块数",
|
||||||
|
"score": "分数",
|
||||||
|
"lines": "行数",
|
||||||
|
"linesShort": "行",
|
||||||
|
"pcs": "全消数",
|
||||||
|
"holds": "暂存数",
|
||||||
|
"spike": "最高暴击",
|
||||||
|
"top": "前 $percentage",
|
||||||
|
"topRank": "最高段位:$rank",
|
||||||
|
"floor": "层",
|
||||||
|
"split": "拆分",
|
||||||
|
"total": "总计",
|
||||||
|
"sent": "已发送",
|
||||||
|
"received": "已接收",
|
||||||
|
"placement": "排名",
|
||||||
|
"peak": "最高",
|
||||||
|
"qpWithMods(plural)": {
|
||||||
|
"one": "使用 1 个模组",
|
||||||
|
"two": "使用 $n 个模组",
|
||||||
|
"few": "使用 $n 个模组",
|
||||||
|
"many": "使用 $n 个模组",
|
||||||
|
"other": "使用 $n 个模组"
|
||||||
|
},
|
||||||
|
"inputs(plural)": {
|
||||||
|
"zero": "$n 按键",
|
||||||
|
"one": "$n 按键",
|
||||||
|
"two": "$n 按键",
|
||||||
|
"few": "$n 按键",
|
||||||
|
"many": "$n 按键",
|
||||||
|
"other": "$n 按键"
|
||||||
|
},
|
||||||
|
"tspinsTotal(plural)": {
|
||||||
|
"zero": "总共 $n 次T旋",
|
||||||
|
"one": "总共 $n 次T旋",
|
||||||
|
"two": "总共 $n 次T旋",
|
||||||
|
"few": "总共 $n 次T旋",
|
||||||
|
"many": "总共 $n 次T旋",
|
||||||
|
"other": "总共 $n 次T旋"
|
||||||
|
},
|
||||||
|
"linesCleared(plural)": {
|
||||||
|
"zero": "总共消除 $n 行",
|
||||||
|
"one": "总共消除 $n 行",
|
||||||
|
"two": "总共消除 $n 行",
|
||||||
|
"few": "总共消除 $n 行",
|
||||||
|
"many": "总共消除 $n 行",
|
||||||
|
"other": "总共消除 $n 行"
|
||||||
|
},
|
||||||
|
"graphs": {
|
||||||
|
"attack": "攻击",
|
||||||
|
"speed": "速度",
|
||||||
|
"defense": "防御",
|
||||||
|
"cheese": "奶酪层"
|
||||||
|
},
|
||||||
|
"players(plural)": {
|
||||||
|
"zero": "$n 名玩家",
|
||||||
|
"one": "$n 名玩家",
|
||||||
|
"two": "$n 名玩家",
|
||||||
|
"few": "$n 名玩家",
|
||||||
|
"many": "$n 名玩家",
|
||||||
|
"other": "$n 名玩家"
|
||||||
|
},
|
||||||
|
"games(plural)": {
|
||||||
|
"zero": "$n 次游戏",
|
||||||
|
"one": "$n 次游戏",
|
||||||
|
"two": "$n 次游戏",
|
||||||
|
"few": "$n 次游戏",
|
||||||
|
"many": "$n 次游戏",
|
||||||
|
"other": "$n 次游戏"
|
||||||
|
},
|
||||||
|
"lineClear": {
|
||||||
|
"single": "Single",
|
||||||
|
"double": "Double",
|
||||||
|
"triple": "Triple",
|
||||||
|
"quad": "Quad",
|
||||||
|
"penta": "Penta",
|
||||||
|
"hexa": "Hexa",
|
||||||
|
"hepta": "Hepta",
|
||||||
|
"octa": "Octa",
|
||||||
|
"ennea": "Ennea",
|
||||||
|
"deca": "Deca",
|
||||||
|
"hendeca": "Hendeca",
|
||||||
|
"dodeca": "Dodeca",
|
||||||
|
"triadeca": "Triadeca",
|
||||||
|
"tessaradeca": "Tessaradeca",
|
||||||
|
"pentedeca": "Pentedeca",
|
||||||
|
"hexadeca": "Hexadeca",
|
||||||
|
"heptadeca": "Heptadeca",
|
||||||
|
"octadeca": "Octadeca",
|
||||||
|
"enneadeca": "Enneadeca",
|
||||||
|
"eicosa": "Eicosa",
|
||||||
|
"kagaris": "Kagaris"
|
||||||
|
},
|
||||||
|
"lineClears": {
|
||||||
|
"zero": "Zeros",
|
||||||
|
"single": "Singles",
|
||||||
|
"double": "Doubles",
|
||||||
|
"triple": "Triples",
|
||||||
|
"quad": "Quads",
|
||||||
|
"penta": "Pentas"
|
||||||
|
},
|
||||||
|
"mini": "Mini",
|
||||||
|
"tSpin": "T-spin",
|
||||||
|
"tSpins": "T-spins",
|
||||||
|
"spin": "Spin",
|
||||||
|
"spins": "Spins"
|
||||||
|
},
|
||||||
|
"countries(map)": {
|
||||||
|
"": "全球",
|
||||||
|
"AF": "阿富汗",
|
||||||
|
"AX": "奥兰群岛",
|
||||||
|
"AL": "阿尔巴尼亚",
|
||||||
|
"DZ": "阿尔及利亚",
|
||||||
|
"AS": "美属萨摩亚",
|
||||||
|
"AD": "安道尔",
|
||||||
|
"AO": "安哥拉",
|
||||||
|
"AI": "安圭拉",
|
||||||
|
"AQ": "南极洲",
|
||||||
|
"AG": "安提瓜和巴布达",
|
||||||
|
"AR": "阿根廷",
|
||||||
|
"AM": "亚美尼亚",
|
||||||
|
"AW": "阿鲁巴",
|
||||||
|
"AU": "澳大利亚",
|
||||||
|
"AT": "奥地利",
|
||||||
|
"AZ": "阿塞拜疆",
|
||||||
|
"BS": "巴哈马",
|
||||||
|
"BH": "巴林",
|
||||||
|
"BD": "孟加拉国",
|
||||||
|
"BB": "巴巴多斯",
|
||||||
|
"BY": "白俄罗斯",
|
||||||
|
"BE": "比利时",
|
||||||
|
"BZ": "伯利兹",
|
||||||
|
"BJ": "贝宁",
|
||||||
|
"BM": "百慕大",
|
||||||
|
"BT": "不丹",
|
||||||
|
"BO": "玻利维亚",
|
||||||
|
"BA": "波黑",
|
||||||
|
"BW": "博茨瓦纳",
|
||||||
|
"BV": "布韦岛",
|
||||||
|
"BR": "巴西",
|
||||||
|
"IO": "英属印度洋领地",
|
||||||
|
"BN": "文莱",
|
||||||
|
"BG": "保加利亚",
|
||||||
|
"BF": "布基纳法索",
|
||||||
|
"BI": "布隆迪",
|
||||||
|
"KH": "柬埔寨",
|
||||||
|
"CM": "喀麦隆",
|
||||||
|
"CA": "加拿大",
|
||||||
|
"CV": "佛得角",
|
||||||
|
"BQ": "荷兰加勒比区",
|
||||||
|
"KY": "开曼群岛",
|
||||||
|
"CF": "中非",
|
||||||
|
"TD": "乍得",
|
||||||
|
"CL": "智利",
|
||||||
|
"CN": "中国",
|
||||||
|
"CX": "圣诞岛",
|
||||||
|
"CC": "科科斯群岛",
|
||||||
|
"CO": "哥伦比亚",
|
||||||
|
"KM": "科摩罗",
|
||||||
|
"CG": "刚果(布)",
|
||||||
|
"CD": "刚果(金)",
|
||||||
|
"CK": "库克群岛",
|
||||||
|
"CR": "哥斯达黎加",
|
||||||
|
"CI": "科特迪瓦",
|
||||||
|
"HR": "克罗地亚",
|
||||||
|
"CU": "古巴",
|
||||||
|
"CW": "库拉索",
|
||||||
|
"CY": "塞浦路斯",
|
||||||
|
"CZ": "捷克",
|
||||||
|
"DK": "丹麦",
|
||||||
|
"DJ": "吉布提",
|
||||||
|
"DM": "多米尼克",
|
||||||
|
"DO": "多米尼加共和国",
|
||||||
|
"EC": "厄瓜多尔",
|
||||||
|
"EG": "埃及",
|
||||||
|
"SV": "萨尔瓦多",
|
||||||
|
"GB-ENG": "英格兰",
|
||||||
|
"GQ": "赤道几内亚",
|
||||||
|
"ER": "厄立特里亚",
|
||||||
|
"EE": "爱沙尼亚",
|
||||||
|
"ET": "埃塞俄比亚",
|
||||||
|
"EU": "欧洲",
|
||||||
|
"FK": "福克兰群岛 (马尔维纳斯)",
|
||||||
|
"FO": "法罗群岛",
|
||||||
|
"FJ": "斐济",
|
||||||
|
"FI": "芬兰",
|
||||||
|
"FR": "法国",
|
||||||
|
"GF": "法属圭亚那",
|
||||||
|
"PF": "法属波利尼西亚",
|
||||||
|
"TF": "法属南部领地",
|
||||||
|
"GA": "加蓬",
|
||||||
|
"GM": "冈比亚",
|
||||||
|
"GE": "格鲁吉亚",
|
||||||
|
"DE": "德国",
|
||||||
|
"GH": "加纳",
|
||||||
|
"GI": "直布罗陀",
|
||||||
|
"GR": "希腊",
|
||||||
|
"GL": "格陵兰",
|
||||||
|
"GD": "格林纳达",
|
||||||
|
"GP": "瓜德罗普",
|
||||||
|
"GU": "关岛",
|
||||||
|
"GT": "危地马拉",
|
||||||
|
"GG": "根西",
|
||||||
|
"GN": "几内亚",
|
||||||
|
"GW": "几内亚比绍",
|
||||||
|
"GY": "圭亚那",
|
||||||
|
"HT": "海地",
|
||||||
|
"HM": "赫德岛和麦克唐纳群岛",
|
||||||
|
"VA": "梵蒂冈",
|
||||||
|
"HN": "洪都拉斯",
|
||||||
|
"HK": "中国香港",
|
||||||
|
"HU": "匈牙利",
|
||||||
|
"IS": "冰岛",
|
||||||
|
"IN": "印度",
|
||||||
|
"ID": "印尼",
|
||||||
|
"IR": "伊朗",
|
||||||
|
"IQ": "伊拉克",
|
||||||
|
"IE": "爱尔兰",
|
||||||
|
"IM": "马恩岛",
|
||||||
|
"IL": "以色列",
|
||||||
|
"IT": "意大利",
|
||||||
|
"JM": "牙买加",
|
||||||
|
"JP": "日本",
|
||||||
|
"JE": "泽西",
|
||||||
|
"JO": "约旦",
|
||||||
|
"KZ": "哈萨克斯坦",
|
||||||
|
"KE": "肯尼亚",
|
||||||
|
"KI": "基里巴斯",
|
||||||
|
"KP": "朝鲜",
|
||||||
|
"KR": "韩国",
|
||||||
|
"XK": "科索沃",
|
||||||
|
"KW": "科威特",
|
||||||
|
"KG": "吉尔吉斯斯坦",
|
||||||
|
"LA": "老挝",
|
||||||
|
"LV": "拉托维亚",
|
||||||
|
"LB": "黎巴嫩",
|
||||||
|
"LS": "莱索托",
|
||||||
|
"LR": "利比里亚",
|
||||||
|
"LY": "利比亚",
|
||||||
|
"LI": "列支敦士登",
|
||||||
|
"LT": "立陶宛",
|
||||||
|
"LU": "卢森堡",
|
||||||
|
"MO": "中国澳门",
|
||||||
|
"MK": "马其顿",
|
||||||
|
"MG": "马达加斯加",
|
||||||
|
"MW": "马拉维",
|
||||||
|
"MY": "马来西亚",
|
||||||
|
"MV": "马尔代夫",
|
||||||
|
"ML": "马里",
|
||||||
|
"MT": "马耳他",
|
||||||
|
"MH": "马绍尔群岛",
|
||||||
|
"MQ": "马提尼克",
|
||||||
|
"MR": "毛里塔尼亚",
|
||||||
|
"MU": "毛里求斯",
|
||||||
|
"YT": "马约特岛",
|
||||||
|
"MX": "墨西哥",
|
||||||
|
"FM": "密克罗尼西亚",
|
||||||
|
"MD": "摩尔多瓦",
|
||||||
|
"MC": "摩纳哥",
|
||||||
|
"ME": "黑山",
|
||||||
|
"MA": "摩洛哥",
|
||||||
|
"MN": "蒙古",
|
||||||
|
"MS": "蒙特塞拉特",
|
||||||
|
"MZ": "莫桑比克",
|
||||||
|
"MM": "缅甸",
|
||||||
|
"NA": "纳米比亚",
|
||||||
|
"NR": "瑙鲁",
|
||||||
|
"NP": "尼泊尔",
|
||||||
|
"NL": "荷兰",
|
||||||
|
"AN": "荷属安地列斯",
|
||||||
|
"NC": "新喀里多尼亚",
|
||||||
|
"NZ": "新西兰",
|
||||||
|
"NI": "尼加拉瓜",
|
||||||
|
"NE": "尼日尔",
|
||||||
|
"NG": "尼日利亚",
|
||||||
|
"NU": "纽埃",
|
||||||
|
"NF": "诺福克岛",
|
||||||
|
"GB-NIR": "北爱尔兰",
|
||||||
|
"MP": "北马里亚纳群岛",
|
||||||
|
"NO": "挪威",
|
||||||
|
"OM": "阿曼",
|
||||||
|
"PK": "巴基斯坦",
|
||||||
|
"PW": "帕劳",
|
||||||
|
"PS": "巴勒斯坦",
|
||||||
|
"PA": "巴拿马",
|
||||||
|
"PG": "巴布亚新几内亚",
|
||||||
|
"PY": "巴拉圭",
|
||||||
|
"PE": "秘鲁",
|
||||||
|
"PH": "菲律宾",
|
||||||
|
"PN": "皮特凯恩",
|
||||||
|
"PL": "波兰",
|
||||||
|
"PT": "葡萄牙",
|
||||||
|
"PR": "波多黎哥",
|
||||||
|
"QA": "卡塔尔",
|
||||||
|
"RE": "留尼汪",
|
||||||
|
"RO": "罗马尼亚",
|
||||||
|
"RU": "俄罗斯",
|
||||||
|
"RW": "卢旺达",
|
||||||
|
"BL": "圣巴泰勒米",
|
||||||
|
"SH": "圣赫勒拿-阿森松-特里斯坦达库尼亚",
|
||||||
|
"KN": "圣基茨和尼维斯",
|
||||||
|
"LC": "圣卢西亚",
|
||||||
|
"MF": "圣马丁",
|
||||||
|
"PM": "圣皮埃尔和密克隆",
|
||||||
|
"VC": "圣文森特和格林纳丁斯",
|
||||||
|
"WS": "萨摩亚",
|
||||||
|
"SM": "圣马利诺",
|
||||||
|
"ST": "圣多美和普林西比",
|
||||||
|
"SA": "沙特阿拉伯",
|
||||||
|
"GB-SCT": "苏格兰",
|
||||||
|
"SN": "塞内加尔",
|
||||||
|
"RS": "塞尔维亚",
|
||||||
|
"SC": "塞舌尔",
|
||||||
|
"SL": "塞拉利昂",
|
||||||
|
"SG": "新加坡",
|
||||||
|
"SX": "荷属圣马丁",
|
||||||
|
"SK": "斯洛伐克",
|
||||||
|
"SI": "斯洛文尼亚",
|
||||||
|
"SB": "所罗门",
|
||||||
|
"SO": "索马里",
|
||||||
|
"ZA": "南非",
|
||||||
|
"GS": "南乔治亚和南桑威奇",
|
||||||
|
"SS": "南苏丹",
|
||||||
|
"ES": "西班牙",
|
||||||
|
"LK": "斯里兰卡",
|
||||||
|
"SD": "苏丹",
|
||||||
|
"SR": "苏里南",
|
||||||
|
"SJ": "斯瓦尔巴和扬马延",
|
||||||
|
"SZ": "斯威士兰",
|
||||||
|
"SE": "瑞典",
|
||||||
|
"CH": "瑞士",
|
||||||
|
"SY": "叙利亚",
|
||||||
|
"TW": "中国台湾",
|
||||||
|
"TJ": "塔吉克斯坦",
|
||||||
|
"TZ": "坦桑尼亚",
|
||||||
|
"TH": "泰国",
|
||||||
|
"TL": "东帝汶",
|
||||||
|
"TG": "多哥",
|
||||||
|
"TK": "托克劳",
|
||||||
|
"TO": "汤加",
|
||||||
|
"TT": "特立尼达和多巴哥",
|
||||||
|
"TN": "突尼斯",
|
||||||
|
"TR": "土耳其",
|
||||||
|
"TM": "土库曼斯坦",
|
||||||
|
"TC": "特克斯河凯科斯群岛",
|
||||||
|
"TV": "图瓦卢",
|
||||||
|
"UG": "乌干达",
|
||||||
|
"UA": "乌克兰",
|
||||||
|
"AE": "阿联酋",
|
||||||
|
"GB": "英国",
|
||||||
|
"US": "美国",
|
||||||
|
"UY": "乌拉圭",
|
||||||
|
"UM": "美国本土外小岛屿",
|
||||||
|
"UZ": "乌兹别克斯坦",
|
||||||
|
"VU": "瓦努阿图",
|
||||||
|
"VE": "委内瑞拉",
|
||||||
|
"VN": "越南",
|
||||||
|
"VG": "英属维京群岛",
|
||||||
|
"VI": "美属维京群岛",
|
||||||
|
"GB-WLS": "威尔士",
|
||||||
|
"WF": "瓦利斯群岛和富图纳群岛",
|
||||||
|
"EH": "西撒哈拉",
|
||||||
|
"YE": "也门",
|
||||||
|
"ZM": "赞比亚",
|
||||||
|
"ZW": "津巴布韦",
|
||||||
|
"XX": "未知",
|
||||||
|
"XM": "月球"
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 149 KiB |
Before Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 28 KiB |