TetraStats/lib/views/main_view_tiles.dart

872 lines
41 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart' hide Badge;
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:intl/intl.dart';
import 'package:syncfusion_flutter_gauges/gauges.dart';
import 'package:tetra_stats/gen/strings.g.dart';
import 'package:tetra_stats/utils/numers_formats.dart';
import 'package:tetra_stats/utils/relative_timestamps.dart';
import 'package:tetra_stats/utils/text_shadow.dart';
import 'package:tetra_stats/widgets/text_timestamp.dart';
import 'package:tetra_stats/data_objects/tetrio.dart';
import 'package:tetra_stats/main.dart';
import 'package:tetra_stats/widgets/user_thingy.dart';
class MainView extends StatefulWidget {
final String? player;
/// The very first view, that user see when he launch this programm.
/// By default it loads my or defined in preferences user stats, but
/// if [player] username or id provided, it loads his stats. Also it hides menu drawer and three dots menu.
const MainView({super.key, this.player});
@override
State<MainView> createState() => _MainState();
}
enum Cards {overview, tetraLeague, quickPlay, quickPlayExpert, sprint, blitz, other}
Map<Cards, String> cardsTitles = {
Cards.overview: "Overview",
Cards.tetraLeague: t.tetraLeague,
Cards.quickPlay: t.quickPlay,
Cards.quickPlayExpert: "${t.quickPlay} ${t.expert}",
Cards.sprint: t.sprint,
Cards.blitz: t.blitz,
Cards.other: t.other
};
TetrioPlayer testPlayer = TetrioPlayer(
userId: "6098518e3d5155e6ec429cdc",
username: "dan63",
registrationTime: DateTime(2002, 2, 25, 9, 30, 01),
avatarRevision: 1704835194288,
bannerRevision: 1661462402700,
role: "user",
country: "BY",
state: DateTime(1970),
badges: [
Badge(badgeId: "kod_founder", label: "Убил оска", ts: DateTime(2023, 6, 27, 18, 51, 49)),
Badge(badgeId: "kod_by_founder", label: "Убит оском", ts: DateTime(2023, 6, 27, 18, 51, 51)),
Badge(badgeId: "5mblast_1", label: "5M Blast Winner"),
Badge(badgeId: "20tsd", label: "20 TSD"),
Badge(badgeId: "allclear", label: "10PC's"),
Badge(badgeId: "100player", label: "Won some shit"),
Badge(badgeId: "founder", label: "osk"),
Badge(badgeId: "early-supporter", label: "Sus"),
Badge(badgeId: "bugbounty", label: "Break some ribbons"),
Badge(badgeId: "infdev", label: "Closed player")
],
friendCount: 69,
gamesPlayed: 13747,
gamesWon: 6523,
gameTime: const Duration(days: 79, minutes: 28, seconds: 23, microseconds: 637591),
xp: 1415239,
supporterTier: 2,
verified: true,
connections: null,
tlSeason1: TetraLeagueAlpha(timestamp: DateTime(1970), gamesPlayed: 28, gamesWon: 14, bestRank: "x", decaying: false, rating: 23500.6194, rank: "x", percentileRank: "x", percentile: 0.00, standing: 1, standingLocal: 1, nextAt: -1, prevAt: 500),
//distinguishment: Distinguishment(type: "twc", detail: "2023"),
bio: "кровбер не в палку, без последнего тспина - 32 атаки. кровбер не в палку, без первого тсм и последнего тспина - 30 атаки. кровбер в палку с б2б - 38 атаки.(5 б2б)(не знаю от чего зависит) кровбер в палку с б2б - 36 атаки.(5 б2б)(не знаю от чего зависит)"
);
News testNews = News("6098518e3d5155e6ec429cdc", [
NewsEntry(type: "personalbest", data: {"gametype": "40l", "result": 23.232}, timestamp: DateTime(2002, 2, 25, 10, 30, 01)),
NewsEntry(type: "personalbest", data: {"gametype": "blitz", "result": 23.232}, timestamp: DateTime(2002, 2, 25, 10, 30, 02)),
NewsEntry(type: "personalbest", data: {"gametype": "5mblast", "result": 23.232}, timestamp: DateTime(2002, 2, 25, 10, 30, 03)),
]);
late ScrollController controller;
class _MainState extends State<MainView> with TickerProviderStateMixin {
@override
void initState() {
teto.open();
controller = ScrollController();
super.initState();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
drawer: const SearchDrawer(),
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Row(
children: [
NavigationRail(
leading: FloatingActionButton(
elevation: 0,
onPressed: () {
Scaffold.of(context).openDrawer();
},
child: const Icon(Icons.search),
),
trailing: IconButton(
onPressed: () {
// Add your onPressed code here!
},
icon: const Icon(Icons.more_horiz_rounded),
),
destinations: const [
NavigationRailDestination(
icon: Icon(Icons.home),
selectedIcon: Icon(Icons.home),
label: Text('First'),
),
NavigationRailDestination(
icon: Icon(Icons.leaderboard),
selectedIcon: Icon(Icons.leaderboard),
label: Text('Second'),
),
NavigationRailDestination(
icon: Icon(Icons.compress),
selectedIcon: Icon(Icons.compress),
label: Text('Third'),
),
NavigationRailDestination(
icon: Icon(Icons.calculate),
selectedIcon: Icon(Icons.calculate),
label: Text('Calc'),
),
NavigationRailDestination(
icon: Icon(Icons.settings),
selectedIcon: Icon(Icons.settings),
label: Text('Third'),
)
],
selectedIndex: 0
),
Row(
children: [
SizedBox(
width: 450.0,
child: Column(
children: [
NewUserThingy(player: testPlayer, showStateTimestamp: false, setState: setState),
Padding(
padding: const EdgeInsets.fromLTRB(4.0, 0.0, 4.0, 0.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(child: ElevatedButton.icon(onPressed: (){print("ok, and?");}, icon: const Icon(Icons.person_add), label: Text(t.track), style: const ButtonStyle(shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(12.0), right: Radius.zero)))))),
Expanded(child: ElevatedButton.icon(onPressed: (){print("ok, and?");}, icon: const Icon(Icons.balance), label: Text(t.compare), style: const ButtonStyle(shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.zero, right: Radius.circular(12.0)))))))
],
),
),
Card(
child: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(20.0, 0.0, 20.0, 0.0),
child: Row(
children: [
const Text("Badges", style: TextStyle(fontFamily: "Eurostile Round Extended")),
const Spacer(),
Text(intf.format(testPlayer.badges.length))
],
),
),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
for (var badge in testPlayer.badges)
IconButton(
onPressed: () => showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(badge.label, style: const TextStyle(fontFamily: "Eurostile Round Extended")),
content: SingleChildScrollView(
child: ListBody(
children: [
Wrap(
direction: Axis.horizontal,
alignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 25,
children: [
Image.asset("res/tetrio_badges/${badge.badgeId}.png"),
Text(badge.ts != null
? t.obtainDate(date: timestamp(badge.ts!))
: t.assignedManualy),
],
)
],
),
),
actions: <Widget>[
TextButton(
child: Text(t.popupActions.ok),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
),
tooltip: badge.label,
icon: Image.asset(
"res/tetrio_badges/${badge.badgeId}.png",
height: 32,
width: 32,
errorBuilder: (context, error, stackTrace) {
return Image.network(
kIsWeb ? "https://ts.dan63.by/oskware_bridge.php?endpoint=TetrioBadge&badge=${badge.badgeId}" : "https://tetr.io/res/badges/${badge.badgeId}.png",
height: 32,
width: 32,
errorBuilder:(context, error, stackTrace) {
return Image.asset("res/icons/kagari.png", height: 32, width: 32);
}
);
},
)
)
],
),
)
],
),
),
if (testPlayer.distinguishment != null) DistinguishmentThingy(testPlayer.distinguishment!),
if (testPlayer.bio != null) Card(
child: Column(
children: [
Row(
children: [
const Spacer(),
Text(t.bio, style: const TextStyle(fontFamily: "Eurostile Round Extended")),
const Spacer()
],
),
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: MarkdownBody(data: testPlayer.bio!, styleSheet: MarkdownStyleSheet(textAlign: WrapAlignment.center)),
)
],
),
),
//if (testNews != null && testNews!.news.isNotEmpty)
Expanded(child: NewsThingy(testNews))
],
)
),
SizedBox(
width: constraints.maxWidth - 450 - 80,
child: Column(
//crossAxisAlignment: CrossAxisAlignment.center,
children: [
Card(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Spacer(),
Text(t.tetraLeague, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42)),
const Spacer()
],
),
),
const Card(),
SegmentedButton<Cards>(
segments: const <ButtonSegment<Cards>>[
ButtonSegment<Cards>(
value: Cards.overview,
label: Text('Overview'),
icon: Icon(Icons.calendar_view_day)),
ButtonSegment<Cards>(
value: Cards.tetraLeague,
label: Text('Tetra League'),
icon: Icon(Icons.calendar_view_week)),
ButtonSegment<Cards>(
value: Cards.quickPlay,
label: Text('Quick Play'),
icon: Icon(Icons.calendar_view_month)),
// ButtonSegment<Cards>(
// value: Cards.quickPlayExpert,
// label: Text('QP Expert'),
// icon: Icon(Icons.calendar_today)),
ButtonSegment<Cards>(
value: Cards.sprint,
label: Text('40 Lines'),
icon: Icon(Icons.calendar_today)),
ButtonSegment<Cards>(
value: Cards.blitz,
label: Text('Blitz'),
icon: Icon(Icons.calendar_today)),
// ButtonSegment<Cards>(
// value: Cards.other,
// label: Text('Other'),
// icon: Icon(Icons.calendar_today)),
],
selected: const <Cards>{Cards.tetraLeague},
onSelectionChanged: (Set<Cards> newSelection) {
setState(() {
// By default there is only a single segment that can be
// selected at one time, so its value is always the first
// item in the selected set.
//calendarView = newSelection.first;
});})
],
),
),
],
),
],
);
},
));
}
}
class NewsThingy extends StatelessWidget{
final News news;
const NewsThingy(this.news, {super.key});
ListTile getNewsTile(NewsEntry news){
Map<String, String> gametypes = {
"40l": t.sprint,
"blitz": t.blitz,
"5mblast": "5,000,000 Blast"
};
// Individuly handle each entry type
switch (news.type) {
case "leaderboard":
return ListTile(
title: RichText(
text: TextSpan(
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16, color: Colors.white),
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(timestamp(news.timestamp)),
);
case "personalbest":
return ListTile(
title: RichText(
text: TextSpan(
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16, color: Colors.white),
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(timestamp(news.timestamp)),
leading: Image.asset(
"res/icons/improvement-local.png",
height: 48,
width: 48,
errorBuilder: (context, error, stackTrace) {
return Image.asset("res/icons/kagari.png", height: 64, width: 64);
},
),
);
case "badge":
return ListTile(
title: RichText(
text: TextSpan(
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16, color: Colors.white),
text: t.newsParts.badgeStart,
children: [
TextSpan(text: "${news.data["label"]} ", style: const TextStyle(fontWeight: FontWeight.bold)),
TextSpan(text: t.newsParts.badgeEnd)
]
)
),
subtitle: Text(timestamp(news.timestamp)),
leading: Image.asset(
"res/tetrio_badges/${news.data["type"]}.png",
height: 48,
width: 48,
errorBuilder: (context, error, stackTrace) {
return Image.asset("res/icons/kagari.png", height: 64, width: 64);
},
),
);
case "rankup":
return ListTile(
title: RichText(
text: TextSpan(
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16, color: Colors.white),
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(timestamp(news.timestamp)),
leading: Image.asset(
"res/tetrio_tl_alpha_ranks/${news.data["rank"]}.png",
height: 48,
width: 48,
errorBuilder: (context, error, stackTrace) {
return Image.asset("res/icons/kagari.png", height: 64, width: 64);
},
),
);
case "supporter":
return ListTile(
title: RichText(
text: TextSpan(
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16, color: Colors.white),
text: t.newsParts.supporterStart,
children: [
TextSpan(text: t.newsParts.tetoSupporter, style: const TextStyle(fontWeight: FontWeight.bold))
]
)
),
subtitle: Text(timestamp(news.timestamp)),
leading: Image.asset(
"res/icons/supporter-tag.png",
height: 48,
width: 48,
errorBuilder: (context, error, stackTrace) {
return Image.asset("res/icons/kagari.png", height: 64, width: 64);
},
),
);
case "supporter_gift":
return ListTile(
title: RichText(
text: TextSpan(
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16, color: Colors.white),
text: t.newsParts.supporterGiftStart,
children: [
TextSpan(text: t.newsParts.tetoSupporter, style: const TextStyle(fontWeight: FontWeight.bold))
]
)
),
subtitle: Text(timestamp(news.timestamp)),
leading: Image.asset(
"res/icons/supporter-tag.png",
height: 48,
width: 48,
errorBuilder: (context, error, stackTrace) {
return Image.asset("res/icons/kagari.png", height: 64, width: 64);
},
),
);
default: // if type is unknown
return ListTile(
title: Text(t.newsParts.unknownNews(type: news.type)),
subtitle: Text(timestamp(news.timestamp)),
);
}
}
@override
Widget build(BuildContext context) {
return Card(
surfaceTintColor: theme.colorScheme.surface,
child: SingleChildScrollView(
child: Column(
children: [
Row(
children: [
const Spacer(),
Text(t.news, style: const TextStyle(fontFamily: "Eurostile Round Extended")),
const Spacer()
]
),
for (NewsEntry entry in news.news) getNewsTile(entry)
],
),
),
);
}
}
class DistinguishmentThingy extends StatelessWidget{
final Distinguishment distinguishment;
const DistinguishmentThingy(this.distinguishment, {super.key});
List<InlineSpan> getDistinguishmentTitle(String? text) {
// TWC champions don't have header in their distinguishments
if (distinguishment.type == "twc") return [const TextSpan(text: "TETR.IO World Champion", style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Colors.yellowAccent))];
// In case if it missing for some other reason, return this
if (text == null) return [const TextSpan(text: "Header is missing", style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Colors.redAccent))];
// Handling placeholders for logos
var exploded = text.split(" "); // wtf PHP reference?
List<InlineSpan> result = [];
for (String shit in exploded){
switch (shit) { // if %% thingy was found, insert svg of icon
case "%osk%":
result.add(WidgetSpan(child: Padding(
padding: const EdgeInsets.only(left: 8),
child: SvgPicture.asset("res/icons/osk.svg", height: 28),
)));
break;
case "%tetrio%":
result.add(WidgetSpan(child: Padding(
padding: const EdgeInsets.only(left: 8),
child: SvgPicture.asset("res/icons/tetrio-logo.svg", height: 28),
)));
break;
default: // if not, insert text span
result.add(TextSpan(text: " $shit", style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Colors.white)));
}
}
return result;
}
/// Distinguishment title is barely predictable thing.
/// Receives [text], which is footer and returns sets of widgets for RichText widget
String getDistinguishmentSubtitle(String? text){
// TWC champions don't have footer in their distinguishments
if (distinguishment.type == "twc") return "${distinguishment.detail} TETR.IO World Championship";
// In case if it missing for some other reason, return this
if (text == null) return "Footer is missing";
// If everything ok, return as it is
return text;
}
Color getCardTint(String type, String detail){
switch(type){
case "staff":
switch(detail){
case "founder": return const Color(0xAAFD82D4);
case "kagarin": return const Color(0xAAFF0060);
case "team": return const Color(0xAAFACC2E);
case "team-minor": return const Color(0xAAF5BD45);
case "administrator": return const Color(0xAAFF4E8A);
case "globalmod": return const Color(0xAAE878FF);
case "communitymod": return const Color(0xAA4E68FB);
case "alumni": return const Color(0xAA6057DB);
default: return theme.colorScheme.surface;
}
case "champion":
switch (detail){
case "blitz":
case "40l": return const Color(0xAACCF5F6);
case "league": return const Color(0xAAFFDB31);
}
case "twc": return const Color(0xAAFFDB31);
default: return theme.colorScheme.surface;
}
return theme.colorScheme.surface;
}
@override
Widget build(BuildContext context) {
return Card(
surfaceTintColor: getCardTint(distinguishment.type, distinguishment.detail??"null"),
child: Column(
children: [
Row(
children: [
const Spacer(),
Text(t.distinguishment, style: const TextStyle(fontFamily: "Eurostile Round Extended")),
const Spacer()
],
),
RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: DefaultTextStyle.of(context).style,
children: getDistinguishmentTitle(distinguishment.header),
),
),
Text(getDistinguishmentSubtitle(distinguishment.footer), style: const TextStyle(fontSize: 18), textAlign: TextAlign.center),
],
),
);
}
}
class NewUserThingy extends StatelessWidget {
final TetrioPlayer player;
final bool showStateTimestamp;
final Function setState;
const NewUserThingy({super.key, required this.player, required this.showStateTimestamp, required this.setState});
Color roleColor(String role){
switch (role){
case "sysop":
return const Color.fromARGB(255, 23, 165, 133);
case "admin":
return const Color.fromARGB(255, 255, 78, 138);
case "mod":
return const Color.fromARGB(255, 204, 128, 242);
case "halfmod":
return const Color.fromARGB(255, 95, 118, 254);
case "bot":
return const Color.fromARGB(255, 60, 93, 55);
case "banned":
return const Color.fromARGB(255, 248, 28, 28);
default:
return Colors.white10;
}
}
@override
Widget build(BuildContext context) {
final t = Translations.of(context);
return LayoutBuilder(builder: (context, constraints) {
//bool bigScreen = constraints.maxWidth > 768;
double pfpHeight = 128;
int xpTableID = 0;
while (player.xp > xpTableScuffed.values.toList()[xpTableID]) {
xpTableID++;
}
return Card(
clipBehavior: Clip.antiAlias,
child: Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Column(
children: [
Container(
constraints: const BoxConstraints(maxWidth: 960),
height: player.bannerRevision != null ? 218.0 : 138.0,
child: Stack(
//clipBehavior: Clip.none,
children: [
if (player.bannerRevision != null) Image.network(kIsWeb ? "https://ts.dan63.by/oskware_bridge.php?endpoint=TetrioBanner&user=${player.userId}&rv=${player.bannerRevision}" : "https://tetr.io/user-content/banners/${player.userId}.jpg?rv=${player.bannerRevision}",
fit: BoxFit.cover,
height: 120,
//width: 450,
errorBuilder: (context, error, stackTrace) {
return Container();
},
),
Positioned(
top: player.bannerRevision != null ? 90.0 : 10.0,
left: 16.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(kIsWeb ? "https://ts.dan63.by/oskware_bridge.php?endpoint=TetrioProfilePicture&user=${player.userId}&rv=${player.avatarRevision}" : "https://tetr.io/user-content/avatars/${player.userId}.jpg?rv=${player.avatarRevision}",
// TODO: osk banner can cause memory leak
fit: BoxFit.fitHeight, height: 128, errorBuilder: (context, error, 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),
)
),
Positioned(
top: player.bannerRevision != null ? 120.0 : 40.0,
left: 160.0,
child: Text(player.username,
//softWrap: true,
overflow: TextOverflow.fade,
style: const TextStyle(
fontFamily: "Eurostile Round Extended",
fontSize: 28,
)
),
),
Positioned(
top: player.bannerRevision != null ? 160.0 : 80.0,
left: 160.0,
child: Row(
children: [
Padding(
padding: const EdgeInsets.only(right: 4.0),
child: Chip(label: Text(player.role.toUpperCase(), style: const TextStyle(shadows: textShadow),), padding: const EdgeInsets.all(0.0), color: WidgetStatePropertyAll(roleColor(player.role))),
),
RichText(
text: TextSpan(
style: const TextStyle(fontFamily: "Eurostile Round"),
children:
[
if (player.friendCount > 0) const WidgetSpan(child: Icon(Icons.person), alignment: PlaceholderAlignment.middle, baseline: TextBaseline.alphabetic),
if (player.friendCount > 0) TextSpan(text: "${intf.format(player.friendCount)} "),
if (player.supporterTier > 0) WidgetSpan(child: Icon(player.supporterTier > 1 ? Icons.star : Icons.star_border, color: player.supporterTier > 1 ? Colors.yellowAccent : Colors.white), alignment: PlaceholderAlignment.middle, baseline: TextBaseline.alphabetic),
if (player.supporterTier > 0) TextSpan(text: player.supporterTier.toString(), style: TextStyle(color: player.supporterTier > 1 ? Colors.yellowAccent : Colors.white)),
]
)
)
],
),
),
Positioned(
top: player.bannerRevision != null ? 193.0 : 113.0,
left: 160.0,
child: RichText(
text: TextSpan(
style: const TextStyle(fontFamily: "Eurostile Round"),
children: [
if (player.country != null) TextSpan(text: "${t.countries[player.country]}"),
TextSpan(text: player.registrationTime == null ? t.wasFromBeginning : timestamp(player.registrationTime!), style: const TextStyle(color: Colors.grey))
]
)
)
),
Positioned(
top: player.bannerRevision != null ? 126.0 : 46.0,
right: 16.0,
child: RichText(
textAlign: TextAlign.end,
text: TextSpan(
style: const TextStyle(fontFamily: "Eurostile Round"),
children: [
TextSpan(text: "Level ${intf.format(player.level.floor())}", recognizer: TapGestureRecognizer()..onTap = (){
showDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
title: Text("Level ${intf.format(player.level.floor())}"),
content: SingleChildScrollView(
child: ListBody(children: [
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)
],
showTicks: true,
showLabels: false
),
),
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})")
]
),
),
actions: <Widget>[
TextButton(
child: const Text("OK"),
onPressed: () {Navigator.of(context).pop();}
)
]
)
);
}),
const TextSpan(text:"\n"),
TextSpan(text: player.gameTime.isNegative ? "-h --m" : playtime(player.gameTime), style: TextStyle(color: player.gameTime.isNegative ? Colors.grey : Colors.white), recognizer: TapGestureRecognizer()..onTap = (){
showDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
title: Text(t.exactGametime),
content: SingleChildScrollView(
child: ListBody(children: [
Text(
//"${intf.format(testPlayer.gameTime.inDays)} d\n${nonsecs.format(testPlayer.gameTime.inHours%24)} h\n${nonsecs.format(testPlayer.gameTime.inMinutes%60)} m\n${nonsecs.format(testPlayer.gameTime.inSeconds%60)} s\n${nonsecs3.format(testPlayer.gameTime.inMilliseconds%1000)} ms\n${nonsecs.format(testPlayer.gameTime.inMicroseconds%1000)} μs",
"${intf.format(testPlayer.gameTime.inDays)}d ${nonsecs.format(testPlayer.gameTime.inHours%24)}h ${nonsecs.format(testPlayer.gameTime.inMinutes%60)}m ${nonsecs.format(testPlayer.gameTime.inSeconds%60)}s ${nonsecs3.format(testPlayer.gameTime.inMilliseconds%1000)}ms ${nonsecs.format(testPlayer.gameTime.inMicroseconds%1000)}μs",
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 24)
),
]
),
),
actions: <Widget>[
TextButton(
child: const Text("OK"),
onPressed: () {Navigator.of(context).pop();}
)
]
)
);
}),
const TextSpan(text:"\n"),
TextSpan(text: player.gamesWon > -1 ? intf.format(player.gamesWon) : "---", style: TextStyle(color: player.gamesWon > -1 ? Colors.white : Colors.grey)),
TextSpan(text: "/${player.gamesPlayed > -1 ? intf.format(player.gamesPlayed) : "---"}", style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
]
)
)
)
],
),
),
// Row(
// mainAxisAlignment: MainAxisAlignment.center,
// crossAxisAlignment: CrossAxisAlignment.center,
// children: [
// ElevatedButton.icon(onPressed: (){print("ok, and?");}, icon: Icon(Icons.person_add), label: Text(t.track), style: ButtonStyle(shape: MaterialStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(8), right: Radius.zero))))),
// ElevatedButton.icon(onPressed: (){print("ok, and?");}, icon: Icon(Icons.balance), label: Text(t.compare), style: ButtonStyle(shape: MaterialStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.zero, right: Radius.circular(8))))))
// ]
// )
],
),
),
);
});
}
}
class SearchDrawer extends StatefulWidget{
const SearchDrawer({super.key});
@override
State<SearchDrawer> createState() => _SearchDrawerState();
}
class _SearchDrawerState extends State<SearchDrawer> {
@override
Widget build(BuildContext context) {
return Drawer(
child: StreamBuilder(
stream: teto.allPlayers,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.done:
case ConnectionState.active:
final allPlayers = (snapshot.data != null)
? snapshot.data as Map<String, String>
: <String, String>{};
allPlayers.remove(prefs.getString("player") ?? "6098518e3d5155e6ec429cdc"); // player from the home button will be delisted
List<String> keys = allPlayers.keys.toList();
return NestedScrollView(
headerSliverBuilder: (BuildContext context, bool value){
return [
SliverToBoxAdapter(
child: SearchBar(
hintText: "Hello",
hintStyle: const WidgetStatePropertyAll(TextStyle(color: Colors.grey)),
trailing: [
IconButton(onPressed: (){print("sas");}, icon: const Icon(Icons.search))
],
),
)
];
},
body: ListView.builder( // Builds list of tracked players.
itemCount: allPlayers.length,
itemBuilder: (context, index) {
var i = allPlayers.length-1-index; // Last players in this map are most recent ones, they are gonna be shown at the top.
return ListTile(
title: Text(allPlayers[keys[i]]??keys[i]), // Takes last known username from list of states
onTap: () {
//widget.changePlayer(keys[i]); // changes to chosen player
Navigator.of(context).pop(); // and closes itself.
},
);
})
);
}
}
)
);
}
}