News + bugfixes
This commit is contained in:
parent
7ed93d3fb1
commit
7060eb6e43
|
@ -1037,6 +1037,24 @@ class Distinguishment {
|
|||
}
|
||||
}
|
||||
|
||||
class News {
|
||||
late String id;
|
||||
late String stream;
|
||||
late String type;
|
||||
late Map<String, dynamic> data;
|
||||
late DateTime timestamp;
|
||||
|
||||
News({required this.type, required this.id, required this.stream, required this.data, required this.timestamp});
|
||||
|
||||
News.fromJson(Map<String, dynamic> json){
|
||||
id = json["_id"];
|
||||
stream = json["stream"];
|
||||
type = json["type"];
|
||||
data = json["data"];
|
||||
timestamp = DateTime.parse(json['ts']);
|
||||
}
|
||||
}
|
||||
|
||||
class TetrioPlayersLeaderboard {
|
||||
late String type;
|
||||
late DateTime timestamp;
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
/// To regenerate, run: `dart run slang`
|
||||
///
|
||||
/// Locales: 2
|
||||
/// Strings: 940 (470 per locale)
|
||||
/// Strings: 970 (485 per locale)
|
||||
///
|
||||
/// Built on 2023-09-23 at 18:57 UTC
|
||||
/// Built on 2023-10-07 at 16:34 UTC
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
|
@ -163,6 +163,8 @@ class _StringsEn implements BaseTranslations<AppLocale, _StringsEn> {
|
|||
String get distinguishment => 'Distinguishment';
|
||||
String get zen => 'Zen';
|
||||
String get bio => 'Bio';
|
||||
String get news => 'News';
|
||||
late final _StringsNewsPartsEn newsParts = _StringsNewsPartsEn._(_root);
|
||||
String get openSearch => 'Search player';
|
||||
String get closeSearch => 'Close search';
|
||||
String get refresh => 'Refresh';
|
||||
|
@ -562,6 +564,28 @@ class _StringsEn implements BaseTranslations<AppLocale, _StringsEn> {
|
|||
};
|
||||
}
|
||||
|
||||
// Path: newsParts
|
||||
class _StringsNewsPartsEn {
|
||||
_StringsNewsPartsEn._(this._root);
|
||||
|
||||
final _StringsEn _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
String get leaderboardStart => 'Got ';
|
||||
String get leaderboardMiddle => 'on ';
|
||||
String get personalbest => 'Got a new PB in ';
|
||||
String get personalbestMiddle => 'of ';
|
||||
String get badgeStart => 'Obtained a ';
|
||||
String get badgeEnd => 'badge';
|
||||
String get rankupStart => 'Obtained ';
|
||||
String rankupMiddle({required Object r}) => '${r} rank ';
|
||||
String get rankupEnd => 'in Tetra League';
|
||||
String get tetoSupporter => 'TETR.IO supporter';
|
||||
String get supporterStart => 'Become a ';
|
||||
String get supporterGiftStart => 'Received the gift of ';
|
||||
String unknownNews({required Object type}) => 'Unknown news of type ${type}';
|
||||
}
|
||||
|
||||
// Path: statCellNum
|
||||
class _StringsStatCellNumEn {
|
||||
_StringsStatCellNumEn._(this._root);
|
||||
|
@ -571,7 +595,8 @@ class _StringsStatCellNumEn {
|
|||
// Translations
|
||||
String get xpLevel => 'XP Level';
|
||||
String get xpProgress => 'Progress to next level';
|
||||
String get xpFrom0To5000 => 'Progress from 0 XP to level 5000';
|
||||
String xpFrom0ToLevel({required Object n}) => 'Progress from 0 XP to level ${n}';
|
||||
String get xpLeft => 'XP left';
|
||||
String get hoursPlayed => 'Hours\nPlayed';
|
||||
String get onlineGames => 'Online\nGames';
|
||||
String get gamesWon => 'Games\nWon';
|
||||
|
@ -708,6 +733,8 @@ class _StringsRu implements _StringsEn {
|
|||
@override String get distinguishment => 'Заслуга';
|
||||
@override String get zen => 'Дзен';
|
||||
@override String get bio => 'Биография';
|
||||
@override String get news => 'Новости';
|
||||
@override late final _StringsNewsPartsRu newsParts = _StringsNewsPartsRu._(_root);
|
||||
@override String get openSearch => 'Искать игрока';
|
||||
@override String get closeSearch => 'Закрыть поиск';
|
||||
@override String get refresh => 'Обновить';
|
||||
|
@ -1107,6 +1134,28 @@ class _StringsRu implements _StringsEn {
|
|||
};
|
||||
}
|
||||
|
||||
// Path: newsParts
|
||||
class _StringsNewsPartsRu implements _StringsNewsPartsEn {
|
||||
_StringsNewsPartsRu._(this._root);
|
||||
|
||||
@override final _StringsRu _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
@override String get leaderboardStart => 'Взял ';
|
||||
@override String get leaderboardMiddle => 'в таблице ';
|
||||
@override String get personalbest => 'Поставил новый ЛР в ';
|
||||
@override String get personalbestMiddle => 'с результатом в ';
|
||||
@override String get badgeStart => 'Заработал значок ';
|
||||
@override String get badgeEnd => '';
|
||||
@override String get rankupStart => 'Заработал ';
|
||||
@override String rankupMiddle({required Object r}) => '${r} ранг ';
|
||||
@override String get rankupEnd => 'в Тетра Лиге';
|
||||
@override String get tetoSupporter => 'TETR.IO supporter';
|
||||
@override String get supporterStart => 'Стал обладателем ';
|
||||
@override String get supporterGiftStart => 'Получил подарок в виде ';
|
||||
@override String unknownNews({required Object type}) => 'Неизвестная новость типа ${type}';
|
||||
}
|
||||
|
||||
// Path: statCellNum
|
||||
class _StringsStatCellNumRu implements _StringsStatCellNumEn {
|
||||
_StringsStatCellNumRu._(this._root);
|
||||
|
@ -1116,7 +1165,8 @@ class _StringsStatCellNumRu implements _StringsStatCellNumEn {
|
|||
// Translations
|
||||
@override String get xpLevel => 'Уровень\nопыта';
|
||||
@override String get xpProgress => 'Прогресс до следующего уровня';
|
||||
@override String get xpFrom0To5000 => 'Прогресс от 0 XP до 5000 уровня';
|
||||
@override String xpFrom0ToLevel({required Object n}) => 'Прогресс от 0 XP до ${n} уровня';
|
||||
@override String get xpLeft => 'XP осталось';
|
||||
@override String get hoursPlayed => 'Часов\nСыграно';
|
||||
@override String get onlineGames => 'Онлайн\nИгр';
|
||||
@override String get gamesWon => 'Онлайн\nПобед';
|
||||
|
@ -1232,6 +1282,20 @@ extension on _StringsEn {
|
|||
case 'distinguishment': return 'Distinguishment';
|
||||
case 'zen': return 'Zen';
|
||||
case 'bio': return 'Bio';
|
||||
case 'news': return 'News';
|
||||
case 'newsParts.leaderboardStart': return 'Got ';
|
||||
case 'newsParts.leaderboardMiddle': return 'on ';
|
||||
case 'newsParts.personalbest': return 'Got a new PB in ';
|
||||
case 'newsParts.personalbestMiddle': return 'of ';
|
||||
case 'newsParts.badgeStart': return 'Obtained a ';
|
||||
case 'newsParts.badgeEnd': return 'badge';
|
||||
case 'newsParts.rankupStart': return 'Obtained ';
|
||||
case 'newsParts.rankupMiddle': return ({required Object r}) => '${r} rank ';
|
||||
case 'newsParts.rankupEnd': return 'in Tetra League';
|
||||
case 'newsParts.tetoSupporter': return 'TETR.IO supporter';
|
||||
case 'newsParts.supporterStart': return 'Become a ';
|
||||
case 'newsParts.supporterGiftStart': return 'Received the gift of ';
|
||||
case 'newsParts.unknownNews': return ({required Object type}) => 'Unknown news of type ${type}';
|
||||
case 'openSearch': return 'Search player';
|
||||
case 'closeSearch': return 'Close search';
|
||||
case 'refresh': return 'Refresh';
|
||||
|
@ -1356,7 +1420,8 @@ extension on _StringsEn {
|
|||
case 'notForWeb': return 'Function is not available for web version';
|
||||
case 'statCellNum.xpLevel': return 'XP Level';
|
||||
case 'statCellNum.xpProgress': return 'Progress to next level';
|
||||
case 'statCellNum.xpFrom0To5000': return 'Progress from 0 XP to level 5000';
|
||||
case 'statCellNum.xpFrom0ToLevel': return ({required Object n}) => 'Progress from 0 XP to level ${n}';
|
||||
case 'statCellNum.xpLeft': return 'XP left';
|
||||
case 'statCellNum.hoursPlayed': return 'Hours\nPlayed';
|
||||
case 'statCellNum.onlineGames': return 'Online\nGames';
|
||||
case 'statCellNum.gamesWon': return 'Games\nWon';
|
||||
|
@ -1712,6 +1777,20 @@ extension on _StringsRu {
|
|||
case 'distinguishment': return 'Заслуга';
|
||||
case 'zen': return 'Дзен';
|
||||
case 'bio': return 'Биография';
|
||||
case 'news': return 'Новости';
|
||||
case 'newsParts.leaderboardStart': return 'Взял ';
|
||||
case 'newsParts.leaderboardMiddle': return 'в таблице ';
|
||||
case 'newsParts.personalbest': return 'Поставил новый ЛР в ';
|
||||
case 'newsParts.personalbestMiddle': return 'с результатом в ';
|
||||
case 'newsParts.badgeStart': return 'Заработал значок ';
|
||||
case 'newsParts.badgeEnd': return '';
|
||||
case 'newsParts.rankupStart': return 'Заработал ';
|
||||
case 'newsParts.rankupMiddle': return ({required Object r}) => '${r} ранг ';
|
||||
case 'newsParts.rankupEnd': return 'в Тетра Лиге';
|
||||
case 'newsParts.tetoSupporter': return 'TETR.IO supporter';
|
||||
case 'newsParts.supporterStart': return 'Стал обладателем ';
|
||||
case 'newsParts.supporterGiftStart': return 'Получил подарок в виде ';
|
||||
case 'newsParts.unknownNews': return ({required Object type}) => 'Неизвестная новость типа ${type}';
|
||||
case 'openSearch': return 'Искать игрока';
|
||||
case 'closeSearch': return 'Закрыть поиск';
|
||||
case 'refresh': return 'Обновить';
|
||||
|
@ -1836,7 +1915,8 @@ extension on _StringsRu {
|
|||
case 'notForWeb': return 'Функция недоступна для веб версии';
|
||||
case 'statCellNum.xpLevel': return 'Уровень\nопыта';
|
||||
case 'statCellNum.xpProgress': return 'Прогресс до следующего уровня';
|
||||
case 'statCellNum.xpFrom0To5000': return 'Прогресс от 0 XP до 5000 уровня';
|
||||
case 'statCellNum.xpFrom0ToLevel': return ({required Object n}) => 'Прогресс от 0 XP до ${n} уровня';
|
||||
case 'statCellNum.xpLeft': return 'XP осталось';
|
||||
case 'statCellNum.hoursPlayed': return 'Часов\nСыграно';
|
||||
case 'statCellNum.onlineGames': return 'Онлайн\nИгр';
|
||||
case 'statCellNum.gamesWon': return 'Онлайн\nПобед';
|
||||
|
|
|
@ -53,8 +53,9 @@ class TetrioService extends DB {
|
|||
final Map<String, TetrioPlayer> _playersCache = {};
|
||||
final Map<String, Map<String, dynamic>> _recordsCache = {};
|
||||
final Map<String, TetrioPlayersLeaderboard> _leaderboardsCache = {};
|
||||
final Map<String, List<News>> _newsCache = {};
|
||||
final Map<String, TetraLeagueAlphaStream> _tlStreamsCache = {}; // i'm trying to respect oskware api It should look something like {"cached_until": TetrioPlayer}
|
||||
final client = UserAgentClient("Tetra Stats v1.2.3 (dm @dan63047 if someone abuse that software)", http.Client());
|
||||
final client = UserAgentClient("ebany u rot yatogo kazino blyat' (Tetra Stats v1.2.4 dev build)", http.Client());
|
||||
static final TetrioService _shared = TetrioService._sharedInstance();
|
||||
factory TetrioService() => _shared;
|
||||
late final StreamController<Map<String, List<TetrioPlayer>>> _tetrioStreamController;
|
||||
|
@ -237,6 +238,63 @@ class TetrioService extends DB {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Future<List<News>> getNews(String userID) async
|
||||
Future<List<News>> fetchNews(String userID) async{
|
||||
try{
|
||||
var cached = _newsCache.entries.firstWhere((element) => element.value[0].stream == "user_$userID");
|
||||
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){
|
||||
developer.log("fetchNews: News for $userID retrieved from cache, that expires ${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)}", name: "services/tetrio_crud");
|
||||
return cached.value;
|
||||
}else{
|
||||
_newsCache.remove(cached.key);
|
||||
developer.log("fetchNews: Cached news for $userID expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
|
||||
}
|
||||
}catch(e){
|
||||
developer.log("fetchNews: Trying to retrieve news for $userID", name: "services/tetrio_crud");
|
||||
}
|
||||
|
||||
Uri url;
|
||||
if (kIsWeb) {
|
||||
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "tetrioNews", "user": userID.toLowerCase().trim(), "limit": "100"});
|
||||
} else {
|
||||
url = Uri.https('ch.tetr.io', 'api/news/user_${userID.toLowerCase().trim()}', {"limit": "100"});
|
||||
}
|
||||
try {
|
||||
final response = await client.get(url);
|
||||
|
||||
switch (response.statusCode) {
|
||||
case 200:
|
||||
var payload = jsonDecode(response.body);
|
||||
if (payload['success']) {
|
||||
List<News> news = [for (var entry in payload['data']['news']) News.fromJson(entry)];
|
||||
developer.log("fetchNews: $userID news retrieved and cached", name: "services/tetrio_crud");
|
||||
_newsCache[payload['cache']['cached_until'].toString()] = news;
|
||||
return news;
|
||||
} else {
|
||||
developer.log("fetchNews: User dosen't exist", name: "services/tetrio_crud", error: response.body);
|
||||
throw TetrioPlayerNotExist();
|
||||
}
|
||||
case 403:
|
||||
throw TetrioForbidden();
|
||||
case 429:
|
||||
throw TetrioTooManyRequests();
|
||||
case 418:
|
||||
throw TetrioOskwareBridgeProblem();
|
||||
case 500:
|
||||
case 502:
|
||||
case 503:
|
||||
case 504:
|
||||
throw TetrioInternalProblem();
|
||||
default:
|
||||
developer.log("fetchNews: Failed to fetch stream", name: "services/tetrio_crud", error: response.statusCode);
|
||||
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
|
||||
}
|
||||
} on http.ClientException catch (e, s) {
|
||||
developer.log("$e, $s");
|
||||
throw http.ClientException(e.message, e.uri);
|
||||
}
|
||||
}
|
||||
|
||||
Future<TetraLeagueAlphaStream> getTLStream(String userID) async {
|
||||
try{
|
||||
var cached = _tlStreamsCache.entries.firstWhere((element) => element.value.userId == userID);
|
||||
|
|
|
@ -50,6 +50,14 @@ Future<void> copyToClipboard(String text) async {
|
|||
await Clipboard.setData(ClipboardData(text: text));
|
||||
}
|
||||
|
||||
String get40lTime(int microseconds){
|
||||
if (microseconds > 60000000) {
|
||||
return "${(microseconds/1000000/60).floor()}:${(secs.format(microseconds /1000000 % 60))}";
|
||||
} else{
|
||||
return timeInSec.format(microseconds / 1000000);
|
||||
}
|
||||
}
|
||||
|
||||
class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
|
||||
final bodyGlobalKey = GlobalKey();
|
||||
bool _searchBoolean = false;
|
||||
|
@ -128,7 +136,10 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
|
|||
}
|
||||
_searchFor = me.userId;
|
||||
setState((){_titleNickname = me.username;});
|
||||
var tlStream = await teto.getTLStream(me.userId);
|
||||
List<dynamic> requests = await Future.wait([teto.getTLStream(_searchFor), teto.fetchRecords(_searchFor), teto.fetchNews(_searchFor)]);
|
||||
TetraLeagueAlphaStream tlStream = requests[0] as TetraLeagueAlphaStream;
|
||||
Map<String, dynamic> records = requests[1] as Map<String, dynamic>;
|
||||
List<News> news = requests[2] as List<News>;
|
||||
List<TetraLeagueAlphaRecord> tlMatches = [];
|
||||
bool isTracking = await teto.isPlayerTracking(me.userId);
|
||||
List<TetrioPlayer> states = [];
|
||||
|
@ -136,7 +147,7 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
|
|||
var uniqueTL = <dynamic>{};
|
||||
if (isTracking){
|
||||
await teto.storeState(me);
|
||||
await teto.saveTLMatchesFromStream(await teto.getTLStream(me.userId));
|
||||
await teto.saveTLMatchesFromStream(tlStream);
|
||||
tlMatches.addAll(await teto.getTLMatchesbyPlayerID(me.userId));
|
||||
for (var match in tlStream.records) {
|
||||
if (!tlMatches.contains(match)) tlMatches.add(match);
|
||||
|
@ -180,8 +191,7 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
|
|||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.estTr != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.estTr!.esttr)], child: Text(t.statCellNum.estOfTR.replaceAll(RegExp(r'\n'), " "))),
|
||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.esttracc != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.esttracc!)], child: Text(t.statCellNum.accOfEst.replaceAll(RegExp(r'\n'), " "))),
|
||||
];
|
||||
Map<String, dynamic> records = await teto.fetchRecords(me.userId);
|
||||
return [me, records, states, tlMatches, compareWith, isTracking];
|
||||
return [me, records, states, tlMatches, compareWith, isTracking, news];
|
||||
}
|
||||
|
||||
void _justUpdate() {
|
||||
|
@ -335,7 +345,9 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
|
|||
? snapshot.data![1]['blitz'][0]
|
||||
: null),
|
||||
_OtherThingy(
|
||||
zen: snapshot.data![1]['zen'], bio: snapshot.data![0].bio, distinguishment: snapshot.data![0].distinguishment,)
|
||||
zen: snapshot.data![1]['zen'], bio: snapshot.data![0].bio,
|
||||
distinguishment: snapshot.data![0].distinguishment,
|
||||
newsletter: snapshot.data![6],)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -454,7 +466,7 @@ class _NavDrawerState extends State<NavDrawer> {
|
|||
final allPlayers = (snapshot.data != null)
|
||||
? snapshot.data as Map<String, List<TetrioPlayer>>
|
||||
: <String, List<TetrioPlayer>>{};
|
||||
List<String> keys = allPlayers.keys.toList();
|
||||
List<String> keys = allPlayers.keys.toList().reversed.toList(); // this is so dumb
|
||||
return NestedScrollView(
|
||||
headerSliverBuilder: (context, value) {
|
||||
return [
|
||||
|
@ -669,15 +681,7 @@ class _RecordThingy extends StatelessWidget {
|
|||
fontFamily: "Eurostile Round Extended",
|
||||
fontSize: bigScreen ? 42 : 28)),
|
||||
if (record!.stream.contains("40l"))
|
||||
if (record!.endContext!.finalTime.inMicroseconds > 60000000) Text(
|
||||
"${(record!.endContext!.finalTime.inMicroseconds/1000000/60).floor()}:${(secs.format(record!.endContext!.finalTime.inMicroseconds /1000000 % 60))}",
|
||||
style: TextStyle(
|
||||
fontFamily: "Eurostile Round Extended",
|
||||
fontSize: bigScreen ? 42 : 28))
|
||||
else Text(
|
||||
timeInSec.format(
|
||||
record!.endContext!.finalTime.inMicroseconds /
|
||||
1000000),
|
||||
Text(get40lTime(record!.endContext!.finalTime.inMicroseconds),
|
||||
style: TextStyle(
|
||||
fontFamily: "Eurostile Round Extended",
|
||||
fontSize: bigScreen ? 42 : 28))
|
||||
|
@ -897,7 +901,8 @@ class _OtherThingy extends StatelessWidget {
|
|||
final TetrioZen? zen;
|
||||
final String? bio;
|
||||
final Distinguishment? distinguishment;
|
||||
const _OtherThingy({Key? key, required this.zen, required this.bio, required this.distinguishment})
|
||||
final List<News>? newsletter;
|
||||
const _OtherThingy({Key? key, required this.zen, required this.bio, required this.distinguishment, this.newsletter})
|
||||
: super(key: key);
|
||||
|
||||
List<InlineSpan> getDistinguishmentSetOfWidgets(String text) {
|
||||
|
@ -924,15 +929,115 @@ class _OtherThingy extends StatelessWidget {
|
|||
return result;
|
||||
}
|
||||
|
||||
ListTile getNewsTile(News news){
|
||||
Map<String, String> gametypes = {
|
||||
"40l": t.sprint,
|
||||
"blitz": t.blitz,
|
||||
"5mblast": "5,000,000 Blast"
|
||||
};
|
||||
|
||||
switch (news.type) {
|
||||
case "leaderboard":
|
||||
return ListTile(
|
||||
title: RichText(
|
||||
text: TextSpan(
|
||||
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16),
|
||||
text: t.newsParts.leaderboardStart,
|
||||
children: [
|
||||
TextSpan(text: "№${news.data["rank"]} ", style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
TextSpan(text: t.newsParts.leaderboardMiddle),
|
||||
TextSpan(text: "№${gametypes[news.data["gametype"]]}", style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
]
|
||||
)
|
||||
),
|
||||
subtitle: Text(dateFormat.format(news.timestamp)),
|
||||
);
|
||||
case "personalbest":
|
||||
return ListTile(
|
||||
title: RichText(
|
||||
text: TextSpan(
|
||||
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16),
|
||||
text: t.newsParts.personalbest,
|
||||
children: [
|
||||
TextSpan(text: "${gametypes[news.data["gametype"]]} ", style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
TextSpan(text: t.newsParts.personalbestMiddle),
|
||||
TextSpan(text: news.data["gametype"] == "blitz" ? NumberFormat.decimalPattern().format(news.data["result"]) : get40lTime((news.data["result"]*1000).floor()), style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
]
|
||||
)
|
||||
),
|
||||
subtitle: Text(dateFormat.format(news.timestamp)),
|
||||
);
|
||||
case "badge":
|
||||
return ListTile(
|
||||
title: RichText(
|
||||
text: TextSpan(
|
||||
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16),
|
||||
text: t.newsParts.badgeStart,
|
||||
children: [
|
||||
TextSpan(text: "${news.data["label"]} ", style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
TextSpan(text: t.newsParts.badgeEnd)
|
||||
]
|
||||
)
|
||||
),
|
||||
subtitle: Text(dateFormat.format(news.timestamp)),
|
||||
);
|
||||
case "rankup":
|
||||
return ListTile(
|
||||
title: RichText(
|
||||
text: TextSpan(
|
||||
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16),
|
||||
text: t.newsParts.rankupStart,
|
||||
children: [
|
||||
TextSpan(text: t.newsParts.rankupMiddle(r: news.data["rank"].toString().toUpperCase()), style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
TextSpan(text: t.newsParts.rankupEnd)
|
||||
]
|
||||
)
|
||||
),
|
||||
subtitle: Text(dateFormat.format(news.timestamp)),
|
||||
);
|
||||
case "supporter":
|
||||
return ListTile(
|
||||
title: RichText(
|
||||
text: TextSpan(
|
||||
style: TextStyle(fontFamily: 'Eurostile Round', fontSize: 16),
|
||||
text: t.newsParts.supporterStart,
|
||||
children: [
|
||||
TextSpan(text: t.newsParts.tetoSupporter, style: TextStyle(fontWeight: FontWeight.bold))
|
||||
]
|
||||
)
|
||||
),
|
||||
subtitle: Text(dateFormat.format(news.timestamp)),
|
||||
);
|
||||
case "supporter_gift":
|
||||
return ListTile(
|
||||
title: RichText(
|
||||
text: TextSpan(
|
||||
style: TextStyle(fontFamily: 'Eurostile Round', fontSize: 16),
|
||||
text: t.newsParts.supporterGiftStart,
|
||||
children: [
|
||||
TextSpan(text: t.newsParts.tetoSupporter, style: TextStyle(fontWeight: FontWeight.bold))
|
||||
]
|
||||
)
|
||||
),
|
||||
subtitle: Text(dateFormat.format(news.timestamp)),
|
||||
);
|
||||
default:
|
||||
return ListTile(
|
||||
title: Text(t.newsParts.unknownNews(type: news.type)),
|
||||
subtitle: Text(dateFormat.format(news.timestamp)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(builder: (context, constraints) {
|
||||
bool bigScreen = constraints.maxWidth > 768;
|
||||
return ListView.builder(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemCount: 1,
|
||||
itemCount: newsletter!.length+1,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return Column(
|
||||
return index == 0 ? Column(
|
||||
children: [
|
||||
if (distinguishment != null)
|
||||
Padding(
|
||||
|
@ -962,7 +1067,7 @@ class _OtherThingy extends StatelessWidget {
|
|||
),
|
||||
if (zen != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
|
||||
padding: const EdgeInsets.fromLTRB(0, 0, 0, 48),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(t.zen, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
|
||||
|
@ -971,9 +1076,10 @@ class _OtherThingy extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
|
||||
if (newsletter != null && newsletter!.isNotEmpty)
|
||||
Text(t.news, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
|
||||
],
|
||||
);
|
||||
) : getNewsTile(newsletter![index-1]);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
|
@ -377,7 +377,7 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
|
|||
_ListEntry(value: widget.rank[0].pps, label: t.statCellNum.pps.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 2),
|
||||
_ListEntry(value: widget.rank[0].vs, label: t.statCellNum.vs.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 2),
|
||||
_ListEntry(value: widget.rank[1]["avgAPP"], label: t.statCellNum.app.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 3),
|
||||
_ListEntry(value: widget.rank[1]["avgAPP"], label: "VS / APM", id: "", username: "", approximate: true, fractionDigits: 3),
|
||||
_ListEntry(value: widget.rank[1]["avgVSAPM"], label: "VS / APM", id: "", username: "", approximate: true, fractionDigits: 3),
|
||||
_ListEntry(value: widget.rank[1]["avgDSS"], label: t.statCellNum.dss.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 3),
|
||||
_ListEntry(value: widget.rank[1]["avgDSP"], label: t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 3),
|
||||
_ListEntry(value: widget.rank[1]["avgAPPDSP"], label: t.statCellNum.appdsp.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 3),
|
||||
|
|
|
@ -29,7 +29,7 @@ class StatCellNum extends StatelessWidget {
|
|||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
f.format(playerStat),
|
||||
fractionDigits == null ? f.format(playerStat.floor()) : f.format(playerStat),
|
||||
style: TextStyle(
|
||||
fontFamily: "Eurostile Round Extended",
|
||||
fontSize: isScreenBig ? 32 : 24,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
||||
import 'package:tetra_stats/data_objects/tetrio.dart';
|
||||
import 'package:tetra_stats/gen/strings.g.dart';
|
||||
import 'package:tetra_stats/views/compare_view.dart';
|
||||
|
@ -7,6 +8,17 @@ import 'package:intl/intl.dart';
|
|||
import 'dart:developer' as developer;
|
||||
import 'package:tetra_stats/widgets/stat_sell_num.dart';
|
||||
|
||||
const Map<int, double> xpTableScuffed = { // level: xp required
|
||||
05000: 67009018.4885772,
|
||||
10000: 763653437.386,
|
||||
15000: 2337651144.54149,
|
||||
20000: 4572735210.50902,
|
||||
25000: 7376166347.04745,
|
||||
30000: 10693620096.2168,
|
||||
40000: 18728882739.482,
|
||||
50000: 28468683855.2853
|
||||
};
|
||||
|
||||
Future<void> copyToClipboard(String text) async {
|
||||
await Clipboard.setData(ClipboardData(text: text));
|
||||
}
|
||||
|
@ -15,6 +27,7 @@ class UserThingy extends StatelessWidget {
|
|||
final TetrioPlayer player;
|
||||
final bool showStateTimestamp;
|
||||
final Function setState;
|
||||
|
||||
const UserThingy({Key? key, required this.player, required this.showStateTimestamp, required this.setState}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -25,212 +38,302 @@ class UserThingy extends StatelessWidget {
|
|||
bool bigScreen = constraints.maxWidth > 768;
|
||||
double bannerHeight = bigScreen ? 240 : 120;
|
||||
double pfpHeight = 128;
|
||||
int xpTableID = 0;
|
||||
|
||||
while (player.xp > xpTableScuffed.values.toList()[xpTableID]) {
|
||||
xpTableID++;
|
||||
}
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flex(
|
||||
direction: Axis.vertical,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
if (player.bannerRevision != null)
|
||||
Image.network(
|
||||
"https://tetr.io/user-content/banners/${player.userId}.jpg?rv=${player.bannerRevision}",
|
||||
fit: BoxFit.cover,
|
||||
height: bannerHeight,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
developer.log("Error with building banner image", name: "main_view", error: error, stackTrace: stackTrace);
|
||||
return Container();
|
||||
},
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.fromLTRB(0, player.bannerRevision != null ? bannerHeight / 1.4 : pfpHeight, 0, 0),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(1000),
|
||||
child: player.role == "banned"
|
||||
? Image.asset(
|
||||
"res/avatars/tetrio_banned.png",
|
||||
fit: BoxFit.fitHeight,
|
||||
height: pfpHeight,
|
||||
)
|
||||
: player.avatarRevision != null
|
||||
? Image.network("https://tetr.io/user-content/avatars/${player.userId}.jpg?rv=${player.avatarRevision}",
|
||||
fit: BoxFit.fitHeight, height: 128, errorBuilder: (context, error, stackTrace) {
|
||||
developer.log("Error with building profile picture", name: "main_view", error: error, stackTrace: stackTrace);
|
||||
return Image.asset(
|
||||
"res/avatars/tetrio_anon.png",
|
||||
fit: BoxFit.fitHeight,
|
||||
height: pfpHeight,
|
||||
);
|
||||
})
|
||||
: Image.asset(
|
||||
"res/avatars/tetrio_anon.png",
|
||||
fit: BoxFit.fitHeight,
|
||||
height: pfpHeight,
|
||||
),
|
||||
),
|
||||
if (player.bannerRevision != null)
|
||||
Image.network("https://tetr.io/user-content/banners/${player.userId}.jpg?rv=${player.bannerRevision}",
|
||||
fit: BoxFit.cover,
|
||||
height: bannerHeight,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
developer.log("Error with building banner image", name: "main_view", error: error, stackTrace: stackTrace);
|
||||
return Container();
|
||||
},
|
||||
),
|
||||
if (player.verified)
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
pfpHeight - 22,
|
||||
bigScreen // verified icon top padding:
|
||||
? (player.bannerRevision != null ? bannerHeight + pfpHeight - 96 : pfpHeight + pfpHeight - 32) // for big screen
|
||||
: (player.bannerRevision != null ? bannerHeight + pfpHeight - 58 : pfpHeight + pfpHeight - 32), // for small screen
|
||||
0,
|
||||
0),
|
||||
child: const Icon(Icons.verified),
|
||||
)
|
||||
],
|
||||
),
|
||||
Flexible(
|
||||
child: Column(
|
||||
children: [
|
||||
Text(player.username, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
|
||||
TextButton(
|
||||
child: Text(player.userId, style: const TextStyle(fontFamily: "Eurostile Round Condensed", fontSize: 14)),
|
||||
onPressed: () {
|
||||
copyToClipboard(player.userId);
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.copiedToClipboard)));
|
||||
}),
|
||||
],
|
||||
)),
|
||||
showStateTimestamp
|
||||
? Text(t.fetchDate(date: dateFormat.format(player.state)))
|
||||
: Wrap(direction: Axis.horizontal, alignment: WrapAlignment.center, spacing: 25, crossAxisAlignment: WrapCrossAlignment.start, children: [
|
||||
FutureBuilder(
|
||||
future: teto.isPlayerTracking(player.userId),
|
||||
builder: (context, snapshot) {
|
||||
switch (snapshot.connectionState) {
|
||||
case ConnectionState.none:
|
||||
case ConnectionState.waiting:
|
||||
case ConnectionState.active:
|
||||
case ConnectionState.done:
|
||||
if (snapshot.data != null && snapshot.data!) {
|
||||
return Column(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.person_remove),
|
||||
onPressed: () {
|
||||
teto.deletePlayerToTrack(player.userId).then((value) => setState());
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.stoppedBeingTracked)));
|
||||
},
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(8, player.bannerRevision != null ? bannerHeight / 1.4 : 0, 8, bigScreen ? 16 : 0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Wrap(
|
||||
direction: bigScreen ? Axis.horizontal : Axis.vertical,
|
||||
alignment: WrapAlignment.spaceBetween,
|
||||
spacing: bigScreen ? 25 : 0,
|
||||
//mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
children: [
|
||||
Wrap(
|
||||
direction: bigScreen ? Axis.horizontal : Axis.vertical,
|
||||
alignment: WrapAlignment.start,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
spacing: bigScreen ? 20 : 0,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
children: [
|
||||
Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(1000),
|
||||
child: player.role == "banned"
|
||||
? Image.asset("res/avatars/tetrio_banned.png", fit: BoxFit.fitHeight, height: pfpHeight,)
|
||||
: player.avatarRevision != null
|
||||
? Image.network("https://tetr.io/user-content/avatars/${player.userId}.jpg?rv=${player.avatarRevision}",
|
||||
fit: BoxFit.fitHeight, height: 128, errorBuilder: (context, error, stackTrace) {
|
||||
developer.log("Error with building profile picture", name: "main_view", error: error, stackTrace: stackTrace);
|
||||
return Image.asset("res/avatars/tetrio_anon.png", fit: BoxFit.fitHeight, height: pfpHeight);
|
||||
})
|
||||
: Image.asset("res/avatars/tetrio_anon.png", fit: BoxFit.fitHeight, height: pfpHeight),
|
||||
),
|
||||
Text(t.stopTracking, textAlign: TextAlign.center)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Column(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.person_add),
|
||||
onPressed: () {
|
||||
teto.addPlayerToTrack(player).then((value) => setState());
|
||||
teto.storeState(player);
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.becameTracked)));
|
||||
},
|
||||
),
|
||||
Text(t.track, textAlign: TextAlign.center)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}),
|
||||
Column(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.balance),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => CompareView(greenSide: [player, null, player.tlSeason1], redSide: const [null, null, null], greenMode: Mode.player, redMode: Mode.player),
|
||||
if (player.verified)
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(pfpHeight - 22, pfpHeight - 32, 0, 0),
|
||||
child: const Icon(Icons.verified),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Text(t.compare, textAlign: TextAlign.center)
|
||||
],
|
||||
)
|
||||
]),
|
||||
Column(
|
||||
children: [
|
||||
Text(player.username,
|
||||
style: TextStyle(
|
||||
fontFamily: "Eurostile Round Extended",
|
||||
fontSize: bigScreen ? 42 : 28,
|
||||
shadows: const <Shadow>[
|
||||
Shadow(
|
||||
offset: Offset(0.0, 0.0),
|
||||
blurRadius: 3.0,
|
||||
color: Colors.black,
|
||||
),
|
||||
Shadow(
|
||||
offset: Offset(0.0, 0.0),
|
||||
blurRadius: 8.0,
|
||||
color: Colors.black,
|
||||
),
|
||||
],
|
||||
)),
|
||||
TextButton(
|
||||
child: Text(player.userId, style: const TextStyle(fontFamily: "Eurostile Round Condensed", fontSize: 14)),
|
||||
onPressed: () {
|
||||
copyToClipboard(player.userId);
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.copiedToClipboard)));
|
||||
}),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
showStateTimestamp
|
||||
? Text(t.fetchDate(date: dateFormat.format(player.state)))
|
||||
: Wrap(direction: Axis.horizontal, alignment: WrapAlignment.center, spacing: 25, crossAxisAlignment: WrapCrossAlignment.start, children: [
|
||||
FutureBuilder(
|
||||
future: teto.isPlayerTracking(player.userId),
|
||||
builder: (context, snapshot) {
|
||||
switch (snapshot.connectionState) {
|
||||
case ConnectionState.none:
|
||||
case ConnectionState.waiting:
|
||||
case ConnectionState.active:
|
||||
case ConnectionState.done:
|
||||
if (snapshot.data != null && snapshot.data!) {
|
||||
return Column(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.person_remove,
|
||||
shadows: <Shadow>[
|
||||
Shadow(
|
||||
offset: Offset(0.0, 0.0),
|
||||
blurRadius: 3.0,
|
||||
color: Colors.black,
|
||||
),
|
||||
Shadow(
|
||||
offset: Offset(0.0, 0.0),
|
||||
blurRadius: 8.0,
|
||||
color: Colors.black,
|
||||
),
|
||||
],),
|
||||
onPressed: () {
|
||||
teto.deletePlayerToTrack(player.userId).then((value) => setState());
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.stoppedBeingTracked)));
|
||||
},
|
||||
),
|
||||
Text(t.stopTracking, textAlign: TextAlign.center)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Column(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.person_add,
|
||||
shadows: <Shadow>[
|
||||
Shadow(
|
||||
offset: Offset(0.0, 0.0),
|
||||
blurRadius: 3.0,
|
||||
color: Colors.black,
|
||||
),
|
||||
Shadow(
|
||||
offset: Offset(0.0, 0.0),
|
||||
blurRadius: 8.0,
|
||||
color: Colors.black,
|
||||
),
|
||||
],),
|
||||
onPressed: () {
|
||||
teto.addPlayerToTrack(player).then((value) => setState());
|
||||
teto.storeState(player);
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.becameTracked)));
|
||||
},
|
||||
),
|
||||
Text(t.track, textAlign: TextAlign.center)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}),
|
||||
Column(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.balance,
|
||||
shadows: <Shadow>[
|
||||
Shadow(
|
||||
offset: Offset(0.0, 0.0),
|
||||
blurRadius: 3.0,
|
||||
color: Colors.black,
|
||||
),
|
||||
Shadow(
|
||||
offset: Offset(0.0, 0.0),
|
||||
blurRadius: 8.0,
|
||||
color: Colors.black,
|
||||
),
|
||||
],),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => CompareView(greenSide: [player, null, player.tlSeason1], redSide: const [null, null, null], greenMode: Mode.player, redMode: Mode.player),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Text(t.compare, textAlign: TextAlign.center)
|
||||
],
|
||||
)
|
||||
])
|
||||
]),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (!["banned", "p1nkl0bst3r"].contains(player.role))
|
||||
Wrap(
|
||||
direction: Axis.horizontal,
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 25,
|
||||
crossAxisAlignment: WrapCrossAlignment.start,
|
||||
clipBehavior: Clip.hardEdge, // hard WHAT???
|
||||
children: [
|
||||
StatCellNum(
|
||||
playerStat: player.level,
|
||||
playerStatLabel: t.statCellNum.xpLevel,
|
||||
isScreenBig: bigScreen,
|
||||
alertWidgets: [Text("${NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2).format(player.xp)} XP", style: const TextStyle(fontFamily: "Eurostile Round Extended"),), Text("${t.statCellNum.xpProgress}: ${((player.level - player.level.floor()) * 100).toStringAsFixed(2)} %"), Text("${t.statCellNum.xpFrom0To5000}: ${((player.xp / 67009017.7589378) * 100).toStringAsFixed(2)} %")],
|
||||
okText: t.popupActions.ok,
|
||||
higherIsBetter: true,
|
||||
Wrap(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
direction: Axis.horizontal,
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 25,
|
||||
crossAxisAlignment: WrapCrossAlignment.start,
|
||||
clipBehavior: Clip.hardEdge, // hard WHAT???
|
||||
children: [
|
||||
StatCellNum(
|
||||
playerStat: player.level,
|
||||
playerStatLabel: t.statCellNum.xpLevel,
|
||||
isScreenBig: bigScreen,
|
||||
alertWidgets: [
|
||||
Text(
|
||||
"${NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2).format(player.xp)} XP",
|
||||
style: const TextStyle(fontFamily: "Eurostile Round", fontWeight: FontWeight.bold)
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(0, 8, 0, 8),
|
||||
child: SfLinearGauge(
|
||||
minimum: 0,
|
||||
maximum: 1,
|
||||
interval: 1,
|
||||
ranges: [
|
||||
LinearGaugeRange(startValue: 0, endValue: player.level - player.level.floor(), color: Colors.cyanAccent),
|
||||
LinearGaugeRange(startValue: 0, endValue: (player.xp / xpTableScuffed.values.toList()[xpTableID]), color: Colors.redAccent, position: LinearElementPosition.cross)
|
||||
],
|
||||
// markerPointers: [LinearShapePointer(value: player.level - player.level.floor(), position: LinearElementPosition.inside, shapeType: LinearShapePointerType.triangle, color: Colors.white, height: 20)],
|
||||
showTicks: true,
|
||||
showLabels: false
|
||||
),
|
||||
),
|
||||
if (player.gameTime >= Duration.zero)
|
||||
StatCellNum(
|
||||
playerStat: player.gameTime.inHours,
|
||||
playerStatLabel: t.statCellNum.hoursPlayed,
|
||||
isScreenBig: bigScreen,
|
||||
alertWidgets: [Text("${t.exactGametime}: ${player.gameTime.toString()}")],
|
||||
higherIsBetter: true,),
|
||||
if (player.gamesPlayed >= 0)
|
||||
StatCellNum(
|
||||
playerStat: player.gamesPlayed,
|
||||
isScreenBig: bigScreen,
|
||||
playerStatLabel: t.statCellNum.onlineGames,
|
||||
higherIsBetter: true,),
|
||||
if (player.gamesWon >= 0)
|
||||
StatCellNum(
|
||||
playerStat: player.gamesWon,
|
||||
isScreenBig: bigScreen,
|
||||
playerStatLabel: t.statCellNum.gamesWon,
|
||||
higherIsBetter: true,),
|
||||
if (player.friendCount > 0)
|
||||
StatCellNum(
|
||||
playerStat: player.friendCount,
|
||||
isScreenBig: bigScreen,
|
||||
playerStatLabel: t.statCellNum.friends,
|
||||
higherIsBetter: true,),
|
||||
],
|
||||
Text("${t.statCellNum.xpProgress}: ${((player.level - player.level.floor()) * 100).toStringAsFixed(2)} %"),
|
||||
Text("${t.statCellNum.xpFrom0ToLevel(n: xpTableScuffed.keys.toList()[xpTableID])}: ${((player.xp / xpTableScuffed.values.toList()[xpTableID]) * 100).toStringAsFixed(2)} % (${NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0).format(xpTableScuffed.values.toList()[xpTableID] - player.xp)} ${t.statCellNum.xpLeft})")],
|
||||
okText: t.popupActions.ok,
|
||||
higherIsBetter: true,
|
||||
),
|
||||
if (player.role == "banned") Text(
|
||||
t.bigRedBanned,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontFamily: "Eurostile Round Extended",
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.red,
|
||||
fontSize: bigScreen ? 60 : 45,
|
||||
),
|
||||
),
|
||||
if (player.role == "p1nkl0bst3r") Text(
|
||||
t.p1nkl0bst3rAlert,
|
||||
if (player.gameTime >= Duration.zero)
|
||||
StatCellNum(
|
||||
playerStat: player.gameTime.inHours,
|
||||
playerStatLabel: t.statCellNum.hoursPlayed,
|
||||
isScreenBig: bigScreen,
|
||||
alertWidgets: [Text("${t.exactGametime}: ${player.gameTime.toString()}")],
|
||||
higherIsBetter: true,),
|
||||
if (player.gamesPlayed >= 0)
|
||||
StatCellNum(
|
||||
playerStat: player.gamesPlayed,
|
||||
isScreenBig: bigScreen,
|
||||
playerStatLabel: t.statCellNum.onlineGames,
|
||||
higherIsBetter: true,),
|
||||
if (player.gamesWon >= 0)
|
||||
StatCellNum(
|
||||
playerStat: player.gamesWon,
|
||||
isScreenBig: bigScreen,
|
||||
playerStatLabel: t.statCellNum.gamesWon,
|
||||
higherIsBetter: true,),
|
||||
if (player.friendCount > 0)
|
||||
StatCellNum(
|
||||
playerStat: player.friendCount,
|
||||
isScreenBig: bigScreen,
|
||||
playerStatLabel: t.statCellNum.friends,
|
||||
higherIsBetter: true,),
|
||||
],
|
||||
),
|
||||
if (player.role == "banned") Text(
|
||||
t.bigRedBanned,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontFamily: "Eurostile Round",
|
||||
fontSize: 16,
|
||||
)
|
||||
style: TextStyle(
|
||||
fontFamily: "Eurostile Round Extended",
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.red,
|
||||
fontSize: bigScreen ? 60 : 45,
|
||||
),
|
||||
),
|
||||
if (player.badstanding != null && player.badstanding!)
|
||||
Text(
|
||||
t.bigRedBadStanding,
|
||||
if (player.role == "p1nkl0bst3r") Text(
|
||||
t.p1nkl0bst3rAlert,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontFamily: "Eurostile Round Extended",
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.red,
|
||||
fontSize: bigScreen ? 60 : 45,
|
||||
),
|
||||
style: const TextStyle(
|
||||
fontFamily: "Eurostile Round",
|
||||
fontSize: 16,
|
||||
)
|
||||
),
|
||||
if (player.role != "p1nkl0bst3r") Row(
|
||||
if (player.badstanding != null && player.badstanding!)
|
||||
Text(
|
||||
t.bigRedBadStanding,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontFamily: "Eurostile Round Extended",
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.red,
|
||||
fontSize: bigScreen ? 60 : 45,
|
||||
),
|
||||
),
|
||||
if (player.role != "p1nkl0bst3r") Padding(
|
||||
padding: EdgeInsets.only(top: bigScreen ? 8 : 0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
|
@ -244,6 +347,7 @@ class UserThingy extends StatelessWidget {
|
|||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Wrap(
|
||||
direction: Axis.horizontal,
|
||||
alignment: WrapAlignment.center,
|
||||
|
|
|
@ -12,6 +12,22 @@
|
|||
"distinguishment": "Distinguishment",
|
||||
"zen": "Zen",
|
||||
"bio": "Bio",
|
||||
"news": "News",
|
||||
"newsParts":{
|
||||
"leaderboardStart": "Got ",
|
||||
"leaderboardMiddle": "on ",
|
||||
"personalbest": "Got a new PB in ",
|
||||
"personalbestMiddle": "of ",
|
||||
"badgeStart": "Obtained a ",
|
||||
"badgeEnd": "badge",
|
||||
"rankupStart": "Obtained ",
|
||||
"rankupMiddle": "${r} rank ",
|
||||
"rankupEnd": "in Tetra League",
|
||||
"tetoSupporter": "TETR.IO supporter",
|
||||
"supporterStart": "Become a ",
|
||||
"supporterGiftStart": "Received the gift of ",
|
||||
"unknownNews": "Unknown news of type ${type}"
|
||||
},
|
||||
"openSearch": "Search player",
|
||||
"closeSearch": "Close search",
|
||||
"refresh": "Refresh",
|
||||
|
@ -137,7 +153,8 @@
|
|||
"statCellNum":{
|
||||
"xpLevel": "XP Level",
|
||||
"xpProgress": "Progress to next level",
|
||||
"xpFrom0To5000": "Progress from 0 XP to level 5000",
|
||||
"xpFrom0ToLevel": "Progress from 0 XP to level $n",
|
||||
"xpLeft": "XP left",
|
||||
"hoursPlayed": "Hours\nPlayed",
|
||||
"onlineGames": "Online\nGames",
|
||||
"gamesWon": "Games\nWon",
|
||||
|
|
|
@ -12,6 +12,22 @@
|
|||
"distinguishment": "Заслуга",
|
||||
"zen": "Дзен",
|
||||
"bio": "Биография",
|
||||
"news": "Новости",
|
||||
"newsParts":{
|
||||
"leaderboardStart": "Взял ",
|
||||
"leaderboardMiddle": "в таблице ",
|
||||
"personalbest": "Поставил новый ЛР в ",
|
||||
"personalbestMiddle": "с результатом в ",
|
||||
"badgeStart": "Заработал значок ",
|
||||
"badgeEnd": "",
|
||||
"rankupStart": "Заработал ",
|
||||
"rankupMiddle": "${r} ранг ",
|
||||
"rankupEnd": "в Тетра Лиге",
|
||||
"tetoSupporter": "TETR.IO supporter",
|
||||
"supporterStart": "Стал обладателем ",
|
||||
"supporterGiftStart": "Получил подарок в виде ",
|
||||
"unknownNews": "Неизвестная новость типа ${type}"
|
||||
},
|
||||
"openSearch": "Искать игрока",
|
||||
"closeSearch": "Закрыть поиск",
|
||||
"refresh": "Обновить",
|
||||
|
@ -137,7 +153,8 @@
|
|||
"statCellNum": {
|
||||
"xpLevel": "Уровень\nопыта",
|
||||
"xpProgress": "Прогресс до следующего уровня",
|
||||
"xpFrom0To5000": "Прогресс от 0 XP до 5000 уровня",
|
||||
"xpFrom0ToLevel": "Прогресс от 0 XP до $n уровня",
|
||||
"xpLeft": "XP осталось",
|
||||
"hoursPlayed": "Часов\nСыграно",
|
||||
"onlineGames": "Онлайн\nИгр",
|
||||
"gamesWon": "Онлайн\nПобед",
|
||||
|
|
Loading…
Reference in New Issue