Error message design

This commit is contained in:
dan63047 2024-09-30 01:02:19 +03:00
parent 1350007e1d
commit af1dec56bc
3 changed files with 142 additions and 21 deletions

View File

@ -798,7 +798,7 @@ class TetrioService extends DB {
} }
} }
Future<List<TetrioPlayerFromLeaderboard>> fetchTetrioLeaderboard({String? prisecter, String? lb}) async { Future<List<TetrioPlayerFromLeaderboard>> fetchTetrioLeaderboard({String? prisecter, String? lb, String? country}) async {
// TetrioPlayersLeaderboard? cached = _cache.get("league", TetrioPlayersLeaderboard); // TetrioPlayersLeaderboard? cached = _cache.get("league", TetrioPlayersLeaderboard);
// if (cached != null) return cached; // if (cached != null) return cached;
@ -808,7 +808,8 @@ class TetrioService extends DB {
} else { } else {
url = Uri.https('ch.tetr.io', 'api/users/by/${lb??"league"}', { url = Uri.https('ch.tetr.io', 'api/users/by/${lb??"league"}', {
"limit": "100", "limit": "100",
if (prisecter != null) "after": prisecter if (prisecter != null) "after": prisecter,
if (country != null) "country": country
}); });
} }
try{ try{
@ -1337,7 +1338,7 @@ class TetrioService extends DB {
switch (response.statusCode) { switch (response.statusCode) {
case 200: case 200:
var json = jsonDecode(response.body); var json = jsonDecode(utf8.decode(response.bodyBytes));
if (json['success']) { if (json['success']) {
// parse and count stats // parse and count stats
TetrioPlayer player = TetrioPlayer.fromJson(json['data'], DateTime.fromMillisecondsSinceEpoch(json['cache']['cached_at'], isUtc: true), json['data']['_id'], json['data']['username'], DateTime.fromMillisecondsSinceEpoch(json['cache']['cached_until'], isUtc: true)); TetrioPlayer player = TetrioPlayer.fromJson(json['data'], DateTime.fromMillisecondsSinceEpoch(json['cache']['cached_at'], isUtc: true), json['data']['_id'], json['data']['username'], DateTime.fromMillisecondsSinceEpoch(json['cache']['cached_until'], isUtc: true));

View File

@ -7,6 +7,7 @@ import 'package:flutter/material.dart' hide Badge;
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:http/http.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:syncfusion_flutter_charts/charts.dart'; import 'package:syncfusion_flutter_charts/charts.dart';
import 'package:syncfusion_flutter_gauges/gauges.dart'; import 'package:syncfusion_flutter_gauges/gauges.dart';
@ -1070,6 +1071,7 @@ class DestinationLeaderboards extends StatefulWidget{
enum Leaderboards{ enum Leaderboards{
tl, tl,
fullTL,
xp, xp,
ar, ar,
sprint, sprint,
@ -1081,7 +1083,8 @@ enum Leaderboards{
class _DestinationLeaderboardsState extends State<DestinationLeaderboards> { class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
//Duration postSeasonLeft = seasonStart.difference(DateTime.now()); //Duration postSeasonLeft = seasonStart.difference(DateTime.now());
final Map<Leaderboards, String> leaderboards = { final Map<Leaderboards, String> leaderboards = {
Leaderboards.tl: "Tetra League", Leaderboards.tl: "Tetra League (Current Season)",
Leaderboards.fullTL: "Tetra League (Current Season, full one)",
Leaderboards.xp: "XP", Leaderboards.xp: "XP",
Leaderboards.ar: "Acievement Points", Leaderboards.ar: "Acievement Points",
Leaderboards.sprint: "40 Lines", Leaderboards.sprint: "40 Lines",
@ -1090,7 +1093,7 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
Leaderboards.zenithex: "Quick Play Expert", Leaderboards.zenithex: "Quick Play Expert",
}; };
Leaderboards _currentLb = Leaderboards.tl; Leaderboards _currentLb = Leaderboards.tl;
final StreamController<List<dynamic>> _dataStreamController = StreamController<List<dynamic>>(); final StreamController<List<dynamic>> _dataStreamController = StreamController<List<dynamic>>.broadcast();
late final ScrollController _scrollController; late final ScrollController _scrollController;
Stream<List<dynamic>> get dataStream => _dataStreamController.stream; Stream<List<dynamic>> get dataStream => _dataStreamController.stream;
List<dynamic> list = []; List<dynamic> list = [];
@ -1108,6 +1111,7 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
final items = switch(_currentLb){ final items = switch(_currentLb){
Leaderboards.tl => await teto.fetchTetrioLeaderboard(prisecter: prisecter), Leaderboards.tl => await teto.fetchTetrioLeaderboard(prisecter: prisecter),
Leaderboards.fullTL => (await teto.fetchTLLeaderboard()).leaderboard,
Leaderboards.xp => await teto.fetchTetrioLeaderboard(prisecter: prisecter, lb: "xp"), Leaderboards.xp => await teto.fetchTetrioLeaderboard(prisecter: prisecter, lb: "xp"),
Leaderboards.ar => await teto.fetchTetrioLeaderboard(prisecter: prisecter, lb: "ar"), Leaderboards.ar => await teto.fetchTetrioLeaderboard(prisecter: prisecter, lb: "ar"),
Leaderboards.sprint => await teto.fetchTetrioRecordsLeaderboard(prisecter: prisecter), Leaderboards.sprint => await teto.fetchTetrioRecordsLeaderboard(prisecter: prisecter),
@ -1139,7 +1143,7 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
final maxScroll = _scrollController.position.maxScrollExtent; final maxScroll = _scrollController.position.maxScrollExtent;
final currentScroll = _scrollController.position.pixels; final currentScroll = _scrollController.position.pixels;
if (currentScroll == maxScroll) { if (currentScroll == maxScroll && _currentLb != Leaderboards.fullTL) {
// When the last item is fully visible, load the next page. // When the last item is fully visible, load the next page.
_fetchData(); _fetchData();
} }
@ -1147,6 +1151,8 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
}); });
} }
static TextStyle trailingStyle = TextStyle(fontSize: 28);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return Row(
@ -1171,9 +1177,10 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
itemCount: leaderboards.length, itemCount: leaderboards.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return Card( return Card(
surfaceTintColor: theme.colorScheme.primary, surfaceTintColor: index == 1 ? Colors.redAccent : theme.colorScheme.primary,
child: ListTile( child: ListTile(
title: Text(leaderboards.values.elementAt(index)), title: Text(leaderboards.values.elementAt(index)),
subtitle: index == 1 ? Text("Heavy, but allows you to sort players by their stats", style: TextStyle(color: Colors.grey, fontSize: 12)) : null,
onTap: () { onTap: () {
_currentLb = leaderboards.keys.elementAt(index); _currentLb = leaderboards.keys.elementAt(index);
list.clear(); list.clear();
@ -1209,24 +1216,44 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
child: ListView.builder( child: ListView.builder(
controller: _scrollController, controller: _scrollController,
itemCount: list.length, itemCount: list.length,
prototypeItem: ListTile(
leading: Text("0"),
title: Text("ehhh...", style: TextStyle(fontSize: 22)),
trailing: SizedBox(height: 36, width: 1),
subtitle: const Text("eh...", style: TextStyle(color: Colors.grey, fontSize: 12)),
),
itemBuilder: (BuildContext context, int index){ itemBuilder: (BuildContext context, int index){
return ListTile( return ListTile(
leading: Text(intf.format(index+1)), leading: Text(intf.format(index+1)),
title: Text(snapshot.data![index].username, style: TextStyle(fontSize: 22)), title: Text(snapshot.data![index].username, style: TextStyle(fontSize: 22)),
trailing: Text(switch (_currentLb){ trailing: switch (_currentLb){
Leaderboards.tl => "${f2.format(snapshot.data![index].tr)} TR", Leaderboards.tl => Row(
Leaderboards.xp => "LVL ${f2.format(snapshot.data![index].level)}", mainAxisSize: MainAxisSize.min,
Leaderboards.ar => "${intf.format(snapshot.data![index].ar)} AR", children: [
Leaderboards.sprint => get40lTime(snapshot.data![index].stats.finalTime.inMicroseconds), Text("${f2.format(snapshot.data![index].tr)} TR", style: trailingStyle),
Leaderboards.blitz => intf.format(snapshot.data![index].stats.score), Image.asset("res/tetrio_tl_alpha_ranks/${snapshot.data![index].rank}.png", height: 36)
Leaderboards.zenith => "${f2.format(snapshot.data![index].stats.zenith!.altitude)} m", ],
Leaderboards.zenithex => "${f2.format(snapshot.data![index].stats.zenith!.altitude)} m" ),
}, style: TextStyle(fontSize: 28)), Leaderboards.fullTL => Row(
mainAxisSize: MainAxisSize.min,
children: [
Text("${f2.format(snapshot.data![index].tr)} TR", style: trailingStyle),
Image.asset("res/tetrio_tl_alpha_ranks/${snapshot.data![index].rank}.png", height: 36)
],
),
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.sprint => Text(get40lTime(snapshot.data![index].stats.finalTime.inMicroseconds), 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.zenithex => Text("${f2.format(snapshot.data![index].stats.zenith!.altitude)} m", style: trailingStyle)
},
subtitle: Text(switch (_currentLb){ subtitle: Text(switch (_currentLb){
Leaderboards.tl => "${f2.format(snapshot.data![index].apm)} APM, ${f2.format(snapshot.data![index].pps)} PPS, ${f2.format(snapshot.data![index].vs)} VS, ${f2.format(snapshot.data![index].nerdStats.app)} APP, ${f2.format(snapshot.data![index].nerdStats.vsapm)} VS/APM", Leaderboards.tl => "${f2.format(snapshot.data![index].apm)} APM, ${f2.format(snapshot.data![index].pps)} PPS, ${f2.format(snapshot.data![index].vs)} VS, ${f2.format(snapshot.data![index].nerdStats.app)} APP, ${f2.format(snapshot.data![index].nerdStats.vsapm)} VS/APM",
Leaderboards.fullTL => "${f2.format(snapshot.data![index].apm)} APM, ${f2.format(snapshot.data![index].pps)} PPS, ${f2.format(snapshot.data![index].vs)} VS, ${f2.format(snapshot.data![index].nerdStats.app)} APP, ${f2.format(snapshot.data![index].nerdStats.vsapm)} VS/APM",
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.pps)} PPS, ${intf.format(snapshot.data![index].stats.piecesPlaced)} P", 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.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"
@ -2662,6 +2689,7 @@ class _DestinationHomeState extends State<DestinationHome> with SingleTickerProv
case ConnectionState.done: case ConnectionState.done:
if (snapshot.hasError){ return FutureError(snapshot); } if (snapshot.hasError){ return FutureError(snapshot); }
if (snapshot.hasData){ if (snapshot.hasData){
if (!snapshot.data!.success) return FetchResultError(snapshot.data!);
blitzBetterThanRankAverage = (snapshot.data!.summaries!.league.rank != "z" && snapshot.data!.summaries!.blitz != null && snapshot.data!.summaries!.league.rank != "x+") ? snapshot.data!.summaries!.blitz!.stats.score > blitzAverages[snapshot.data!.summaries!.league.rank]! : null; blitzBetterThanRankAverage = (snapshot.data!.summaries!.league.rank != "z" && snapshot.data!.summaries!.blitz != null && snapshot.data!.summaries!.league.rank != "x+") ? snapshot.data!.summaries!.blitz!.stats.score > blitzAverages[snapshot.data!.summaries!.league.rank]! : null;
sprintBetterThanRankAverage = (snapshot.data!.summaries!.league.rank != "z" && snapshot.data!.summaries!.sprint != null && snapshot.data!.summaries!.league.rank != "x+") ? snapshot.data!.summaries!.sprint!.stats.finalTime < sprintAverages[snapshot.data!.summaries!.league.rank]! : null; sprintBetterThanRankAverage = (snapshot.data!.summaries!.league.rank != "z" && snapshot.data!.summaries!.sprint != null && snapshot.data!.summaries!.league.rank != "x+") ? snapshot.data!.summaries!.sprint!.stats.finalTime < sprintAverages[snapshot.data!.summaries!.league.rank]! : null;
if (snapshot.data!.summaries!.sprint != null) { if (snapshot.data!.summaries!.sprint != null) {
@ -4208,17 +4236,109 @@ class FutureError extends StatelessWidget{
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Center(child: return TweenAnimationBuilder(
Column( duration: Durations.medium3,
tween: Tween<double>(begin: 0, end: 1),
curve: Easing.standard,
builder: (context, value, child) {
return Container(
transform: Matrix4.translationValues(0, 50-value*50, 0),
child: Opacity(opacity: value, child: child),
);
},
child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Spacer(),
Icon(Icons.error_outline, size: 128.0, color: Colors.red, shadows: [
Shadow(offset: Offset(0.0, 0.0), blurRadius: 30.0, color: Colors.red),
Shadow(offset: Offset(0.0, 0.0), blurRadius: 80.0, color: Colors.red),
]),
Text(snapshot.error.toString(), style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 42, fontWeight: FontWeight.bold), textAlign: TextAlign.center), Text(snapshot.error.toString(), style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 42, fontWeight: FontWeight.bold), textAlign: TextAlign.center),
Padding( Padding(
padding: const EdgeInsets.only(top: 8.0), padding: const EdgeInsets.only(top: 8.0),
child: Text(snapshot.stackTrace.toString(), textAlign: TextAlign.center), child: Text(snapshot.stackTrace.toString(), textAlign: TextAlign.center),
), ),
Spacer()
], ],
) ),
);
}
}
class FetchResultError extends StatelessWidget{
final FetchResults data;
FetchResultError(this.data);
@override
Widget build(BuildContext context) {
IconData icon = Icons.error_outline;
String errText = "";
String? subText;
switch (data.exception.runtimeType){
case TetrioPlayerNotExist:
icon = Icons.search_off;
errText = t.errors.noSuchUser;
subText = t.errors.noSuchUserSub;
break;
case TetrioDiscordNotExist:
icon = Icons.search_off;
errText = t.errors.discordNotAssigned;
subText = t.errors.discordNotAssignedSub;
case ConnectionIssue:
var err = data.exception as ConnectionIssue;
errText = t.errors.connection(code: err.code, message: err.message);
break;
case TetrioForbidden:
icon = Icons.remove_circle;
errText = t.errors.forbidden;
subText = t.errors.forbiddenSub(nickname: 'osk');
break;
case TetrioTooManyRequests:
errText = t.errors.tooManyRequests;
subText = t.errors.tooManyRequestsSub;
break;
case TetrioOskwareBridgeProblem:
errText = t.errors.oskwareBridge;
subText = t.errors.oskwareBridgeSub;
break;
case TetrioInternalProblem:
errText = kIsWeb ? t.errors.internalWebVersion : t.errors.internal;
subText = kIsWeb ? t.errors.internalWebVersionSub : t.errors.internalSub;
break;
case ClientException:
errText = t.errors.clientException;
break;
default:
errText = data.exception.toString();
}
return TweenAnimationBuilder(
duration: Durations.medium3,
tween: Tween<double>(begin: 0, end: 1),
curve: Easing.standard,
builder: (context, value, child) {
return Container(
transform: Matrix4.translationValues(0, 50-value*50, 0),
child: Opacity(opacity: value, child: child),
);
},
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Spacer(),
Icon(icon, size: 128.0, color: Colors.red, shadows: [
Shadow(offset: Offset(0.0, 0.0), blurRadius: 30.0, color: Colors.red),
Shadow(offset: Offset(0.0, 0.0), blurRadius: 80.0, color: Colors.red),
]),
Text(errText, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 42, fontWeight: FontWeight.bold), textAlign: TextAlign.center),
if (subText != null) Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(subText, textAlign: TextAlign.center),
),
Spacer()
],
),
); );
} }
} }

View File

@ -79,7 +79,7 @@ class TLProgress extends StatelessWidget{
], ],
markerPointers: [ markerPointers: [
LinearShapePointer(value: (previousRankTRcutoff != null && nextRankTRcutoff != null) ? getBarTR(tlData.tr)! : getBarPosition(), position: LinearElementPosition.cross, shapeType: LinearShapePointerType.diamond, color: Colors.white, height: 20), LinearShapePointer(value: (previousRankTRcutoff != null && nextRankTRcutoff != null) ? getBarTR(tlData.tr)! : getBarPosition(), position: LinearElementPosition.cross, shapeType: LinearShapePointerType.diamond, color: Colors.white, height: 20),
//if (tlData.standing != -1) LinearWidgetPointer(offset: 4, position: LinearElementPosition.outside, value: (previousRankTRcutoff != null && nextRankTRcutoff != null) ? getBarTR(tlData.tr)! : getBarPosition(), child: Text("${NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0).format(tlData.standing)}", style: const TextStyle(fontSize: 14),)) if (tlData.standing != -1) LinearWidgetPointer(offset: 4, position: LinearElementPosition.outside, value: (previousRankTRcutoff != null && nextRankTRcutoff != null) ? getBarTR(tlData.tr)! : getBarPosition(), child: Text("${NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0).format(tlData.standing)}", style: const TextStyle(fontSize: 14),))
], ],
isMirrored: true, isMirrored: true,
showTicks: true, showTicks: true,