diff --git a/lib/data_objects/tetrio_constants.dart b/lib/data_objects/tetrio_constants.dart index 918e93e..7a031f5 100644 --- a/lib/data_objects/tetrio_constants.dart +++ b/lib/data_objects/tetrio_constants.dart @@ -45,6 +45,27 @@ const List ranks = [ "x", "x+" ]; +const List ranks2 = [ + "top1", + "x+", + "x", + "u", + "ss", + "s+", + "s", + "s-", + "a+", + "a", + "a-", + "b+", + "b", + "b-", + "c+", + "c", + "c-", + "d+", + "d" +]; const Map rankCutoffs = { "x+": 0.002, "x": 0.01, @@ -178,6 +199,16 @@ const Map rankColors = { 'top1': Colors.yellowAccent }; +const List achievementColors = [ + Colors.grey, + Color(0xFFB38070), // bronze + Color(0xFF7E9EA7), // silver + Color(0xFFE2A042), // gold + Color(0xFF70D0A3), // platinum + Color(0xFFD590FF), // diamond + Colors.white, +]; + const Map sprintAverages = { // based on https://discord.com/channels/673303546107658242/674421736162197515/1277367281264889908 'x+': Duration(seconds: 18, milliseconds: 867), diff --git a/lib/gen/strings.g.dart b/lib/gen/strings.g.dart index 802593e..fbca647 100644 --- a/lib/gen/strings.g.dart +++ b/lib/gen/strings.g.dart @@ -4,9 +4,9 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 2 -/// Strings: 1528 (764 per locale) +/// Strings: 1530 (765 per locale) /// -/// Built on 2024-12-12 at 21:30 UTC +/// Built on 2024-12-21 at 17:08 UTC // coverage:ignore-file // ignore_for_file: type=lint @@ -187,6 +187,7 @@ class Translations implements BaseTranslations { String get nerdStats => 'Nerd Stats'; String get playstyles => 'Playstyles'; String get horoscopes => 'Horoscopes'; + String get relatedAchievements => 'Related Achievements'; String get season => 'Season'; String get smooth => 'Smooth'; String get dateAndTime => 'Date & Time'; @@ -890,7 +891,7 @@ class _StringsSettingsDestinationEn { // Translations String get title => 'Settings'; String get general => 'General'; - String get customization => 'Custonization'; + String get customization => 'Customization'; String get database => 'Local database'; String get checking => 'Checking...'; String get enterToSubmit => 'Press Enter to submit'; @@ -1726,6 +1727,7 @@ class _StringsRuRu implements Translations { @override String get nerdStats => 'Для Задротов'; @override String get playstyles => 'Стили игры'; @override String get horoscopes => 'Гороскопы'; + @override String get relatedAchievements => 'Достижения режима'; @override String get season => 'Сезон'; @override String get smooth => 'Сглаживание'; @override String get dateAndTime => 'Дата и время'; @@ -3248,6 +3250,7 @@ extension on Translations { case 'nerdStats': return 'Nerd Stats'; case 'playstyles': return 'Playstyles'; case 'horoscopes': return 'Horoscopes'; + case 'relatedAchievements': return 'Related Achievements'; case 'season': return 'Season'; case 'smooth': return 'Smooth'; case 'dateAndTime': return 'Date & Time'; @@ -3477,7 +3480,7 @@ extension on Translations { case 'savedDataDestination.TLrecords': return 'TL Records'; case 'settingsDestination.title': return 'Settings'; case 'settingsDestination.general': return 'General'; - case 'settingsDestination.customization': return 'Custonization'; + case 'settingsDestination.customization': return 'Customization'; case 'settingsDestination.database': return 'Local database'; case 'settingsDestination.checking': return 'Checking...'; case 'settingsDestination.enterToSubmit': return 'Press Enter to submit'; @@ -4061,6 +4064,7 @@ extension on _StringsRuRu { case 'nerdStats': return 'Для Задротов'; case 'playstyles': return 'Стили игры'; case 'horoscopes': return 'Гороскопы'; + case 'relatedAchievements': return 'Достижения режима'; case 'season': return 'Сезон'; case 'smooth': return 'Сглаживание'; case 'dateAndTime': return 'Дата и время'; diff --git a/lib/services/tetrio_crud.dart b/lib/services/tetrio_crud.dart index d8bb86b..b7f107e 100644 --- a/lib/services/tetrio_crud.dart +++ b/lib/services/tetrio_crud.dart @@ -35,7 +35,7 @@ import 'package:tetra_stats/services/sqlite_db_controller.dart'; import 'package:csv/csv.dart'; const String dbName = "TetraStats.db"; -const String webVersionDomain = "tsbeta.dan63.by"; +const String webVersionDomain = "ts.dan63.by"; const String tetrioUsersTable = "tetrioUsers"; const String tetrioUsersToTrackTable = "tetrioUsersToTrack"; const String tetraLeagueMatchesTable = "tetrioAlphaLeagueMathces"; @@ -831,7 +831,12 @@ class TetrioService extends DB { Uri url; if (kIsWeb) { - url = Uri.https(webVersionDomain, 'oskware_bridge.php', {"endpoint": "TLLeaderboard"}); + url = Uri.https(webVersionDomain, 'oskware_bridge.php', { + "endpoint": "leaderboard", + "lb": lb??"league", + if (prisecter != null) "after": prisecter, + if (country != null) "country": country + }); } else { url = Uri.https('ch.tetr.io', 'api/users/by/${lb??"league"}', { "limit": "100", diff --git a/lib/views/destination_home.dart b/lib/views/destination_home.dart index 636ec0e..89c11cd 100644 --- a/lib/views/destination_home.dart +++ b/lib/views/destination_home.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:flutter_layout_grid/flutter_layout_grid.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; @@ -15,6 +17,7 @@ import 'package:tetra_stats/data_objects/summaries.dart'; import 'package:tetra_stats/data_objects/tetra_league.dart'; import 'package:tetra_stats/data_objects/tetrio_constants.dart'; import 'package:tetra_stats/data_objects/tetrio_player.dart'; +import 'package:tetra_stats/data_objects/tetrio_player_from_leaderboard.dart'; import 'package:tetra_stats/gen/strings.g.dart'; import 'package:tetra_stats/main.dart'; import 'package:tetra_stats/utils/colors_functions.dart'; @@ -120,8 +123,9 @@ class ZenithCard extends StatelessWidget { final RecordSingle? record; final bool old; final double width; + final List achievements; - const ZenithCard(this.record, this.old, {this.width = double.infinity}); + const ZenithCard(this.record, this.old, this.achievements, {this.width = double.infinity}); Widget splitsCard(){ return Card( @@ -246,7 +250,23 @@ class ZenithCard extends StatelessWidget { ), ), 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( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Spacer(), + Text(t.relatedAchievements, style: Theme.of(context).textTheme.titleLarge), + const Spacer() + ], + ), + ), + if (achievements.isNotEmpty) Wrap( + direction: Axis.horizontal, + children: [ + for (Achievement achievement in achievements) FractionallySizedBox(widthFactor: 1/((width/600).ceil()), child: AchievementSummary(achievement: achievement)), + ], + ), ], ); } @@ -573,11 +593,10 @@ class AchievementSummary extends StatelessWidget{ ), child: ClipRect( child: Align( - alignment: Alignment.topLeft.add(Alignment(0.285 * (((achievement?.k??1) - 1) % 8), 0.285 * (((achievement?.k??0) - 1) / 8).floor())), - //alignment: Alignment.topLeft.add(Alignment(0.285 * 1, 0)), + alignment: Alignment.topLeft.add(Alignment(0.286 * (((achievement?.k??1) - 1) % 8), 0.286 * (((achievement?.k??0) - 1) / 8).floor())), heightFactor: 0.125, widthFactor: 0.125, - child: Image.asset("res/icons/achievements.png", width: 2048, height: 2048, scale: 1, color: achievement?.v == null ? Colors.grey : Colors.white), + child: Image.asset("res/icons/achievements.png", width: 2048, height: 2048, scale: 1, color: achievement?.v == null ? Colors.grey : achievementColors[min(achievement!.rank!, 6)]), ), ), ), @@ -718,7 +737,6 @@ class _DestinationHomeState extends State with SingleTickerProv 1 2 3 4 5 6 - 7 7 ''' : ''' t 1 @@ -727,10 +745,9 @@ class _DestinationHomeState extends State with SingleTickerProv 4 5 6 - 7 ''', columnSizes: width > 600 ? [auto, auto] : [auto], - rowSizes: width > 600 ? [auto, auto, auto, auto, auto, auto] : [auto, auto, auto, auto, auto, auto, auto, auto], + rowSizes: width > 600 ? [auto, auto, auto, auto, auto] : [auto, auto, auto, auto, auto, auto, auto], columnGap: 0, rowGap: 0, children: [ @@ -869,34 +886,11 @@ class _DestinationHomeState extends State with SingleTickerProv ), ), ).inGridArea('6'), - if (summaries.achievements.isNotEmpty) Card( - child: Padding( - padding: const EdgeInsets.fromLTRB(20.0, 0.0, 20.0, 0.0), - child: Column( - children: [ - if (summaries.achievements.firstWhere((e) => e.k == 16).v != null) Row( - children: [ - const Text("Total height climbed in QP"), - const Spacer(), - Text("${f2.format(summaries.achievements.firstWhere((e) => e.k == 16).v!)} m"), - ], - ), - if (summaries.achievements.firstWhere((e) => e.k == 17).v != null) Row( - children: [ - const Text("KO's in QP"), - const Spacer(), - Text(intf.format(summaries.achievements.firstWhere((e) => e.k == 17).v!)), - ], - ) - ], - ), - ), - ).inGridArea('7') ], ); } - Widget getTetraLeagueCard(TetraLeague data, Cutoffs? cutoffs, CutoffTetrio? averages, List states, PlayerLeaderboardPosition? lbPos, double width){ + Widget getTetraLeagueCard(TetraLeague data, Cutoffs? cutoffs, CutoffTetrio? averages, List states, PlayerLeaderboardPosition? lbPos, double width, List achievements){ TetraLeague toSee; TetraLeague? toCompare; if (currentRangeValues.start.round() == 0){ @@ -963,7 +957,24 @@ class _DestinationHomeState extends State with SingleTickerProv ), ), 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( + //surfaceTintColor: rankColors[data.rank], + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Spacer(), + Text(t.relatedAchievements, style: Theme.of(context).textTheme.titleLarge), + const Spacer() + ], + ), + ), + Wrap( + direction: Axis.horizontal, + children: [ + for (Achievement achievement in achievements) FractionallySizedBox(widthFactor: 1/((width/600).ceil()), child: AchievementSummary(achievement: achievement)), + ], + ), ], ); } @@ -1164,19 +1175,19 @@ class _DestinationHomeState extends State with SingleTickerProv super.initState(); } - Widget rigthCard(AsyncSnapshot snapshot, List sprintAchievements, List blitzAchievements, double width){ + Widget rigthCard(AsyncSnapshot snapshot, List sprintAchievements, List blitzAchievements, List tlAchievements, List qpAchievements, List qpExAchievements, double width){ return switch (rightCard){ Cards.overview => getOverviewCard(snapshot.data!.summaries!, (snapshot.data!.averages != null && snapshot.data!.summaries!.league.rank != "z") ? snapshot.data!.averages!.data[snapshot.data!.summaries!.league.rank] : (snapshot.data!.averages != null && snapshot.data!.summaries!.league.percentileRank != "z") ? snapshot.data!.averages!.data[snapshot.data!.summaries!.league.percentileRank] : null, width), Cards.tetraLeague => switch (cardMod){ - CardMod.info => getTetraLeagueCard(snapshot.data!.summaries!.league, snapshot.data!.cutoffs, (snapshot.data!.averages != null && snapshot.data!.summaries!.league.rank != "z") ? snapshot.data!.averages!.data[snapshot.data!.summaries!.league.rank] : (snapshot.data!.averages != null && snapshot.data!.summaries!.league.percentileRank != "z") ? snapshot.data!.averages!.data[snapshot.data!.summaries!.league.percentileRank] : null, snapshot.data!.states, snapshot.data!.playerPos, width), + CardMod.info => getTetraLeagueCard(snapshot.data!.summaries!.league, snapshot.data!.cutoffs, (snapshot.data!.averages != null && snapshot.data!.summaries!.league.rank != "z") ? snapshot.data!.averages!.data[snapshot.data!.summaries!.league.rank] : (snapshot.data!.averages != null && snapshot.data!.summaries!.league.percentileRank != "z") ? snapshot.data!.averages!.data[snapshot.data!.summaries!.league.percentileRank] : null, snapshot.data!.states, snapshot.data!.playerPos, width, tlAchievements), CardMod.ex => getPreviousSeasonsList(snapshot.data!.summaries!.pastLeague, width), CardMod.records => getRecentTLrecords(widget.constraints, snapshot.data!.player!.userId), _ => const Center(child: Text("huh?")) }, Cards.quickPlay => switch (cardMod){ - CardMod.info => ZenithCard(snapshot.data?.summaries?.zenith != null ? snapshot.data!.summaries!.zenith : snapshot.data!.summaries?.zenithCareerBest, snapshot.data!.summaries?.zenith == null, width: width), + CardMod.info => ZenithCard(snapshot.data?.summaries?.zenith != null ? snapshot.data!.summaries!.zenith : snapshot.data!.summaries?.zenithCareerBest, snapshot.data!.summaries?.zenith == null, qpAchievements, width: width), CardMod.records => getListOfRecords("zenith/recent", "zenith/top", widget.constraints), - CardMod.ex => ZenithCard(snapshot.data?.summaries?.zenithEx != null ? snapshot.data!.summaries!.zenithEx : snapshot.data!.summaries?.zenithExCareerBest, snapshot.data!.summaries?.zenithEx == null, width: width), + CardMod.ex => ZenithCard(snapshot.data?.summaries?.zenithEx != null ? snapshot.data!.summaries!.zenithEx : snapshot.data!.summaries?.zenithExCareerBest, snapshot.data!.summaries?.zenithEx == null, qpExAchievements, width: width), CardMod.exRecords => getListOfRecords("zenithex/recent", "zenithex/top", widget.constraints), }, Cards.sprint => switch (cardMod){ @@ -1224,6 +1235,48 @@ class _DestinationHomeState extends State with SingleTickerProv closestAverageBlitz = blitzAverages.entries.last; blitzBetterThanClosestAverage = false; } + List tlAchievements = snapshot.data!.summaries!.achievements.isNotEmpty ? [ + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 10), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 12), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 13), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 14), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 15), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 47), + ] : []; + List qpAchievements = snapshot.data!.summaries!.achievements.isNotEmpty ? [ + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 16), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 17), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 18), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 20), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 21), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 22), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 23), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 24), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 25), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 26), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 27), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 28), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 29), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 30), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 33), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 41), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 43), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 44), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 45), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 46), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 51), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 54), + ] : []; + List qpExAchievements = snapshot.data!.summaries!.achievements.isNotEmpty ? [ + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 19), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 31), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 32), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 34), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 40), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 49), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 50), + snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 53), + ] : []; List sprintAchievements = snapshot.data!.summaries!.achievements.isNotEmpty ? [ snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 5), snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 7), @@ -1293,7 +1346,7 @@ class _DestinationHomeState extends State with SingleTickerProv child: SlideTransition( position: _offsetAnimation, child: SingleChildScrollView( - child: rigthCard(snapshot, sprintAchievements, blitzAchievements, width - 450), + child: rigthCard(snapshot, sprintAchievements, blitzAchievements, tlAchievements, qpAchievements, qpExAchievements, width - 450), ), ), ), @@ -1351,7 +1404,7 @@ class _DestinationHomeState extends State with SingleTickerProv if (snapshot.data!.player!.role == "bot") FakeDistinguishmentThingy(bot: true, botMaintainers: snapshot.data!.player!.botmaster), if (snapshot.data!.player!.role == "banned") FakeDistinguishmentThingy(banned: true) else if (snapshot.data!.player!.badstanding == true) FakeDistinguishmentThingy(badStanding: true), - rigthCard(snapshot, sprintAchievements, blitzAchievements, width), + rigthCard(snapshot, sprintAchievements, blitzAchievements, tlAchievements, qpAchievements, qpExAchievements, width), if (rightCard == Cards.overview) Card( child: Column( children: [ diff --git a/lib/views/singleplayer_record_view.dart b/lib/views/singleplayer_record_view.dart index 94fbbd0..3614dfc 100644 --- a/lib/views/singleplayer_record_view.dart +++ b/lib/views/singleplayer_record_view.dart @@ -32,8 +32,8 @@ class SingleplayerRecordView extends StatelessWidget { maxWidth: 768 ), child: switch (record.gamemode){ - "zenith" => ZenithCard(record, false, width: MediaQuery.of(context).size.width), - "zenithex" => ZenithCard(record, false, width: MediaQuery.of(context).size.width), + "zenith" => ZenithCard(record, false, [], width: MediaQuery.of(context).size.width), + "zenithex" => ZenithCard(record, false, [], width: MediaQuery.of(context).size.width), _ => SingleplayerRecord(record: record, hideTitle: true) }, ), diff --git a/lib/widgets/tl_thingy.dart b/lib/widgets/tl_thingy.dart index 202f96e..f1adf6c 100644 --- a/lib/widgets/tl_thingy.dart +++ b/lib/widgets/tl_thingy.dart @@ -55,19 +55,19 @@ class TetraLeagueThingy extends StatelessWidget{ @override Widget build(BuildContext context) { + print(ranks2.indexOf(league.rank != "z" ? league.rank : league.percentileRank)-1); return Card( - //surfaceTintColor: rankColors[league.rank], child: Column( children: [ TLRatingThingy(userID: league.id, tlData: league, oldTl: toCompare, showPositions: true), if (league.gamesPlayed > 9) TLProgress( tlData: league, previousRankTRcutoff: cutoffs != null ? cutoffs!.tr[league.rank != "z" ? league.rank : league.percentileRank] : null, - nextRankTRcutoff: cutoffs != null ? (league.rank != "z" ? league.rank == "x+" : league.percentileRank == "x+") ? 25000 : cutoffs!.tr[ranks.elementAtOrNull(ranks.indexOf(league.rank != "z" ? league.rank : league.percentileRank)+1)] : null, + nextRankTRcutoff: cutoffs != null ? cutoffs!.tr[ranks2[ranks2.indexOf(league.rank != "z" ? league.rank : league.percentileRank)-1]] : null, previousRankTRcutoffTarget: league.rank != "z" ? rankTargets[league.rank] : null, - nextRankTRcutoffTarget: (league.rank != "z" && league.rank != "x+") ? rankTargets[ranks.elementAtOrNull(ranks.indexOf(league.rank)+1)] : null, + nextRankTRcutoffTarget: (league.rank != "z" && league.rank != "x+") ? rankTargets[ranks2[ranks2.indexOf(league.rank != "z" ? league.rank : league.percentileRank)-1]] : null, previousGlickoCutoff: cutoffs != null ? cutoffs!.glicko[league.rank != "z" ? league.rank : league.percentileRank] : null, - nextRankGlickoCutoff: cutoffs != null ? (league.rank != "z" ? league.rank == "x+" : league.percentileRank == "x+") ? 25000 : cutoffs!.glicko[ranks.elementAtOrNull(ranks.indexOf(league.rank != "z" ? league.rank : league.percentileRank)+1)] : null, + nextRankGlickoCutoff: cutoffs != null ? cutoffs!.glicko[ranks2[ranks2.indexOf(league.rank != "z" ? league.rank : league.percentileRank)-1]] : null, ), Row( crossAxisAlignment: CrossAxisAlignment.center, diff --git a/lib/widgets/user_thingy.dart b/lib/widgets/user_thingy.dart index 0430862..f1ccbba 100644 --- a/lib/widgets/user_thingy.dart +++ b/lib/widgets/user_thingy.dart @@ -1,7 +1,9 @@ import 'dart:math'; +import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart' show Size; import 'package:intl/intl.dart'; import 'package:syncfusion_flutter_gauges/gauges.dart'; import 'package:tetra_stats/data_objects/tetrio_constants.dart'; @@ -16,6 +18,45 @@ import 'package:tetra_stats/views/compare_view_tiles.dart'; import 'package:tetra_stats/widgets/text_timestamp.dart'; import 'package:transparent_image/transparent_image.dart'; +Future osksFuture = loadImage(Uri.https("tetr.io", "/user-content/banners/5e32fc85ab319c2ab1beb07c.jpg", {"rv": "1628366386763"})); + +Future loadImage(Uri url) async { + final response = await teto.client.get(url); + return await decodeImageFromList(response.bodyBytes); +} + + Widget createCustomImage(ui.Image image) { + return SizedBox( + width: image.width.toDouble(), + height: image.height.toDouble()/64, + child: CustomPaint( + size: Size(128.0, 128.0), + painter: ImagePainter(image), + ), + ); + } + + class ImagePainter extends CustomPainter { + ImagePainter(ui.Image this.image); + final ui.Image image; + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = Colors.red + ..strokeWidth = 5 + ..style = PaintingStyle.stroke; + + canvas.translate(-240, 0); + canvas.scale(0.5); + canvas.drawImage(image, Offset.zero, paint); + } + + @override + bool shouldRepaint(ImagePainter oldDelegate) => + image != oldDelegate.image; + } + class UserThingy extends StatefulWidget { final TetrioPlayer player; final bool showStateTimestamp; @@ -103,14 +144,30 @@ class _UserThingyState extends State with SingleTickerProviderStateM constraints: const BoxConstraints(maxWidth: 960), height: widget.player.bannerRevision != null ? 218.0 : 138.0, child: Stack( - //clipBehavior: Clip.none, children: [ - // TODO: osk banner can cause memory leak - if (widget.player.bannerRevision != null) FadeInImage.memoryNetwork(image: kIsWeb ? "https://ts.dan63.by/oskware_bridge.php?endpoint=TetrioBanner&user=${widget.player.userId}&rv=${widget.player.bannerRevision}" : "https://tetr.io/user-content/banners/${widget.player.userId}.jpg?rv=${widget.player.bannerRevision}", + // Very weird solution to draw only the first frame of the gif + if (widget.player.userId == "5e32fc85ab319c2ab1beb07c") FutureBuilder( + future: osksFuture, + builder: (context, snapshot) { + switch (snapshot.connectionState){ + case ConnectionState.none: + case ConnectionState.waiting: + case ConnectionState.active: + return SizedBox(width: 960); + case ConnectionState.done: + return createCustomImage(snapshot.data!); + } + }, + ) // If not osk, using a normal widget like a normal human being + else if (widget.player.bannerRevision != null) FadeInImage.memoryNetwork( + image: kIsWeb ? "https://ts.dan63.by/oskware_bridge.php?endpoint=TetrioBanner&user=${widget.player.userId}&rv=${widget.player.bannerRevision}" : "https://tetr.io/user-content/banners/${widget.player.userId}.jpg?rv=${widget.player.bannerRevision}", placeholder: kTransparentImage, fit: BoxFit.cover, height: 120, - fadeInCurve: Easing.standard, fadeInDuration: Durations.long4 + fadeInCurve: Easing.standard, fadeInDuration: Durations.long4, + imageErrorBuilder: (context, object, trace){ + return SizedBox(width: 960); + } ), Positioned( top: widget.player.bannerRevision != null ? 90.0 : 10.0, diff --git a/pubspec.yaml b/pubspec.yaml index e24b533..7f7bc40 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: tetra_stats description: Track your and other player stats in TETR.IO publish_to: 'none' -version: 2.0.0-beta+339 +version: 2.0.0-beta2+40 environment: sdk: '>=3.0.0' diff --git a/res/i18n/strings.i18n.json b/res/i18n/strings.i18n.json index e0b5a70..d680cf8 100644 --- a/res/i18n/strings.i18n.json +++ b/res/i18n/strings.i18n.json @@ -47,6 +47,7 @@ "nerdStats": "Nerd Stats", "playstyles": "Playstyles", "horoscopes": "Horoscopes", + "relatedAchievements": "Related Achievements", "season": "Season", "smooth": "Smooth", "dateAndTime": "Date & Time", @@ -286,7 +287,7 @@ "settingsDestination": { "title": "Settings", "general": "General", - "customization": "Custonization", + "customization": "Customization", "database": "Local database", "checking": "Checking...", "enterToSubmit": "Press Enter to submit", diff --git a/res/i18n/strings_ru-RU.i18n.json b/res/i18n/strings_ru-RU.i18n.json index 8fbcbdc..7ac7536 100644 --- a/res/i18n/strings_ru-RU.i18n.json +++ b/res/i18n/strings_ru-RU.i18n.json @@ -47,6 +47,7 @@ "nerdStats": "Для Задротов", "playstyles": "Стили игры", "horoscopes": "Гороскопы", + "relatedAchievements": "Достижения режима", "season": "Сезон", "smooth": "Сглаживание", "dateAndTime": "Дата и время",