First time experience thougths + some stuff

This commit is contained in:
dan63047 2024-11-14 02:20:52 +03:00
parent 6c8e7b9147
commit 1b9249f36b
13 changed files with 411 additions and 72 deletions

View File

@ -46,6 +46,11 @@ class Achievement {
this.total,
this.rank});
@override
String toString(){
return "${name}: ${v}";
}
Achievement.fromJson(Map<String, dynamic> json) {
k = json['k'];
o = json['o'];

View File

@ -262,6 +262,13 @@ enum ComboTables{
multiplier
}
Map<ComboTables, String> comboTablesNames = {
ComboTables.none: "None",
ComboTables.classic: "Classic",
ComboTables.modern: "Modern",
ComboTables.multiplier: "Multiplier"
};
const int BACKTOBACK_BONUS = 1;
const double BACKTOBACK_BONUS_LOG = .8;
const int COMBO_MINIFIER = 1;

View File

@ -7,6 +7,7 @@ import 'dart:developer' as developer;
import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tetra_stats/services/tetrio_crud.dart';
import 'package:tetra_stats/views/first_time_view.dart';
import 'package:window_manager/window_manager.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';
@ -18,6 +19,8 @@ import 'package:go_router/go_router.dart';
late final PackageInfo packageInfo;
late SharedPreferences prefs;
late TetrioService teto;
late GoRouter router;
ThemeData theme = ThemeData(
fontFamily: 'Eurostile Round',
colorScheme: const ColorScheme.dark(
@ -59,20 +62,6 @@ ThemeData theme = ThemeData(
scaffoldBackgroundColor: Colors.black
);
final router = GoRouter(
initialLocation: "/",
routes: [
GoRoute(
path: "/",
builder: (_, __) => const MainView(),
),
GoRoute( // that one intended for Android users, that can open https://ch.tetr.io/u/ links
path: "/u/:userId",
builder: (_, __) => MainView(player: __.pathParameters['userId'])
)
],
);
void main() async {
// Initializing sqflite
if (kIsWeb) {
@ -96,6 +85,25 @@ void main() async {
prefs = await SharedPreferences.getInstance();
teto = TetrioService();
router = GoRouter(
//initialLocation: prefs.getBool("notFirstTime") == true ? "/" : "/hihello",
initialLocation: "/",
routes: [
GoRoute(
path: "/",
builder: (_, __) => const MainView(),
),
GoRoute( // that one intended for Android users, that can open https://ch.tetr.io/u/ links
path: "/u/:userId",
builder: (_, __) => MainView(player: __.pathParameters['userId'])
),
GoRoute(
path: "/hihello",
builder: (_, __) => const FirstTimeView(),
)
],
);
// Choosing the locale
String? locale = prefs.getString("locale");
if (locale == null){

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
Color getColorOfRank(int rank){
if (rank < 1) return Colors.grey;
if (rank == 1) return Colors.yellowAccent;
if (rank == 2) return Colors.blueGrey;
if (rank == 3) return Colors.brown[400]!;

125
lib/views/about_view.dart Normal file
View File

@ -0,0 +1,125 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:tetra_stats/gen/strings.g.dart';
import 'package:tetra_stats/main.dart';
import 'package:tetra_stats/utils/open_in_browser.dart';
import 'package:window_manager/window_manager.dart';
late String oldWindowTitle;
final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode);
class AboutView extends StatefulWidget {
const AboutView({super.key});
@override
State<StatefulWidget> createState() => AboutState();
}
class AboutCard extends StatelessWidget{
final String title;
final String value;
final String? undervalue; //what?
final List<InlineSpan> endvalue; // ...
const AboutCard(this.title, this.value, this.undervalue, this.endvalue);
@override
Widget build(BuildContext context) {
return Card(child: Column(
children: [
Text(title, style: Theme.of(context).textTheme.titleSmall),
Divider(),
Text(value, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, fontWeight: FontWeight.w500, color: Colors.white)),
if (undervalue != null) Text(undervalue!),
Divider(),
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: RichText(text: TextSpan(
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey, height: 0.6),
children: endvalue
)),
)
],
));
}
}
class AboutState extends State<AboutView> {
@override
void initState() {
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS){
windowManager.getTitle().then((value) => oldWindowTitle = value);
windowManager.setTitle("Tetra Stats: ${t.settings}");
}
super.initState();
}
@override
void dispose(){
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) windowManager.setTitle(oldWindowTitle);
super.dispose();
}
@override
Widget build(BuildContext context) {
final t = Translations.of(context);
bool bigScreen = MediaQuery.of(context).size.width >= 368;
return Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.startTop,
floatingActionButton: Padding(
padding: const EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 0.0),
child: FloatingActionButton(
onPressed: () => Navigator.pop(context),
tooltip: 'Fuck go back',
child: const Icon(Icons.arrow_back),
),
),
backgroundColor: Colors.black,
body: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Card(child: Center(child: Padding(
padding: const EdgeInsets.fromLTRB(0.0, 6.0, 0.0, 18.0),
child: Text("About Tetra Stats", style: Theme.of(context).textTheme.titleLarge, textAlign: TextAlign.center),
))),
Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 2,
child: Card(child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
textAlign: TextAlign.center,
"Tetra Stats is a service, that works with TETR.IO Tetra Channel API, providing data from it and calculating some addtitional metrics, based on this data. Service allows user to track their progress in Tetra League with \"Track\" function, which records every Tetra League change into local database (not automatically, you have to visit service from time to time), so these changes could be looked through graphs.\n\nBeanserver blaster is a part of a Tetra Stats, that decoupled into a serverside script. It provides full Tetra League leaderboard, allowing Tetra Stats to sort leaderboard by any metric and build scatter chart, that allows user to analyse Tetra League trends. It also provides history of Tetra League ranks cutoffs, which can be viewed by user via graph as well.\n\nThere is a plans to add replay analysis and tournaments history, so stay tuned!\n\nService is not associated with TETR.IO or osk in any capacity."
),
)
],
)),
),
Expanded(
child: Column(
children: [
AboutCard("App Version", packageInfo.version, "Build ${packageInfo.buildNumber}", [TextSpan(text: "${packageInfo.appName} (${packageInfo.packageName}) • "), TextSpan(text: "GitHub Repo", style: TextStyle(decoration: TextDecoration.underline, decorationColor: Colors.white70, decorationStyle: TextDecorationStyle.dotted, color: Theme.of(context).colorScheme.primary), recognizer: TapGestureRecognizer()..onTap = (){
launchInBrowser(Uri.https("github.com", "dan63047/TetraStats"));
})]),
AboutCard("Developed By", "dan63", null, [TextSpan(text: "Support him!", style: TextStyle(decoration: TextDecoration.underline, decorationColor: Colors.white70, decorationStyle: TextDecorationStyle.dotted, color: Theme.of(context).colorScheme.primary), recognizer: TapGestureRecognizer()..onTap = (){launchInBrowser(Uri.https("dan63.by", "donate"));})]),
],
),
),
],
)
],
)),
);
}
}

View File

@ -665,7 +665,7 @@ class CompareState extends State<CompareView> {
void getSummariesForInit() async {
summaries.add(await teto.fetchSummaries(widget.initPlayer.userId));
if (summaries[0].league.nerdStats != null) nicknames.add(players[0].username);
nicknames.add(players[0].username);
addvaluesEntrys(players.first, summaries.first);
best = recalculateBestEntries();
setState(() {
@ -678,7 +678,7 @@ class CompareState extends State<CompareView> {
summaries.add(await teto.fetchSummaries(players.last.userId));
addvaluesEntrys(players.last, summaries.last);
best = recalculateBestEntries();
if (summaries.last.league.nerdStats != null) nicknames.add(players.last.username);
nicknames.add(players.last.username);
setState(() {
});
@ -688,6 +688,7 @@ class CompareState extends State<CompareView> {
int id = players.indexWhere((e) => e.username == nickname);
players.removeAt(id);
summaries.removeAt(id);
nicknames.remove(nickname);
for (int i = 0; i < 7; i++){
rawValues[i].removeAt(id);
formattedValues[i].removeAt(id);
@ -786,7 +787,9 @@ class CompareState extends State<CompareView> {
),
]
),
//VsGraphs(stats: [for (var s in summaries) if (s.league.nerdStats != null) AggregateStats.precalculated(s.league.apm!, s.league.pps!, s.league.vs!, s.league.nerdStats!, s.league.playstyle!)], nicknames: nicknames)
if (i == 1) VsGraphs(stats: [for (var s in summaries) if (s.league.nerdStats != null) AggregateStats.precalculated(s.league.apm!, s.league.pps!, s.league.vs!, s.league.nerdStats!, s.league.playstyle!)], nicknames: [for (int i = 0; i < summaries.length; i++) if (summaries[i].league.nerdStats != null) nicknames[i]]),
if (i == 2) VsGraphs(stats: [for (var s in summaries) if ((s.zenith != null || s.zenithCareerBest != null) && (s.zenith?.aggregateStats??s.zenithCareerBest!.aggregateStats).apm > 0.00) s.zenith?.aggregateStats??s.zenithCareerBest!.aggregateStats], nicknames: [for (int i = 0; i < summaries.length; i++) if ((summaries[i].zenith != null || summaries[i].zenithCareerBest != null) && (summaries[i].zenith?.aggregateStats??summaries[i].zenithCareerBest!.aggregateStats).apm > 0.00) nicknames[i]]),
if (i == 3) VsGraphs(stats: [for (var s in summaries) if ((s.zenithEx != null || s.zenithExCareerBest != null) && (s.zenithEx?.aggregateStats??s.zenithExCareerBest!.aggregateStats).apm > 0.00) s.zenithEx?.aggregateStats??s.zenithExCareerBest!.aggregateStats], nicknames: [for (int i = 0; i < summaries.length; i++) if ((summaries[i].zenithEx != null || summaries[i].zenithExCareerBest != null) && (summaries[i].zenithEx?.aggregateStats??summaries[i].zenithExCareerBest!.aggregateStats).apm > 0.00) nicknames[i]]),
],
),
),

View File

@ -388,14 +388,27 @@ class _DestinationCalculatorState extends State<DestinationCalculator> {
child: Column(
children: [
Card(
child: ListTile(
title: Text("Multiplier", style: mainToggleInRules),
trailing: SizedBox(width: 90.0, child: TextField(
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'[0-9.]'))],
decoration: InputDecoration(hintText: rules.multiplier.toString()),
onChanged: (value) => setState((){rules.multiplier = double.parse(value);}),
)),
child: Column(
children: [
ListTile(
title: Text("Multiplier", style: mainToggleInRules),
trailing: SizedBox(width: 90.0, child: TextField(
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'[0-9.]'))],
decoration: InputDecoration(hintText: rules.multiplier.toString()),
onChanged: (value) => setState((){rules.multiplier = double.parse(value);}),
)),
),
ListTile(
title: Text("Perfect Clear Damage"),
trailing: SizedBox(width: 90.0, child: TextField(
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'[0-9]'))],
decoration: InputDecoration(hintText: rules.pcDamage.toString()),
onChanged: (value) => setState((){rules.pcDamage = int.parse(value);}),
)),
),
],
),
),
Card(
@ -408,7 +421,7 @@ class _DestinationCalculatorState extends State<DestinationCalculator> {
if (rules.combo) ListTile(
title: Text("Combo Table"),
trailing: DropdownButton(
items: [for (var v in ComboTables.values) DropdownMenuItem(value: v.index, child: Text(v.name))],
items: [for (var v in ComboTables.values) if (v != ComboTables.none) DropdownMenuItem(value: v.index, child: Text(comboTablesNames[v]!))],
value: rules.comboTable.index,
onChanged: (v) => setState((){rules.comboTable = ComboTables.values[v!];}),
),
@ -509,7 +522,7 @@ class _DestinationCalculatorState extends State<DestinationCalculator> {
Text("Combo: ${intf.format(comboDamage)}"),
Text("B2B: ${intf.format(b2bDamage)}"),
Text("Surge: ${intf.format(surgeDamage)}"),
Text("PC's: ${intf.format(pcDamage)}")
Text("PCs: ${intf.format(pcDamage)}")
],
),
if (totalDamage > 0) SfLinearGauge(

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:intl/intl.dart';
import 'package:tetra_stats/data_objects/achievement.dart';
import 'package:tetra_stats/data_objects/cutoff_tetrio.dart';
import 'package:tetra_stats/data_objects/news.dart';
import 'package:tetra_stats/data_objects/p1nkl0bst3r.dart';
@ -42,11 +43,10 @@ import 'package:tetra_stats/widgets/zenith_thingy.dart';
class DestinationHome extends StatefulWidget{
final String searchFor;
final Future<FetchResults> dataFuture;
final Future<News>? newsFuture;
final BoxConstraints constraints;
final bool noSidebar;
const DestinationHome({super.key, required this.searchFor, required this.dataFuture, this.newsFuture, required this.constraints, this.noSidebar = false});
const DestinationHome({super.key, required this.searchFor, required this.dataFuture, required this.constraints, this.noSidebar = false});
@override
State<DestinationHome> createState() => _DestinationHomeState();
@ -57,13 +57,14 @@ class FetchResults{
TetrioPlayer? player;
List<TetraLeague> states;
Summaries? summaries;
News? news;
Cutoffs? cutoffs;
CutoffsTetrio? averages;
PlayerLeaderboardPosition? playerPos;
bool isTracked;
Exception? exception;
FetchResults(this.success, this.player, this.states, this.summaries, this.cutoffs, this.averages, this.playerPos, this.isTracked, this.exception);
FetchResults(this.success, this.player, this.states, this.summaries, this.news, this.cutoffs, this.averages, this.playerPos, this.isTracked, this.exception);
}
class RecordSummary extends StatelessWidget{
@ -139,6 +140,93 @@ class RecordSummary extends StatelessWidget{
],
);
}
}
class AchievementSummary extends StatelessWidget{
final Achievement? achievement;
const AchievementSummary({this.achievement});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.fromLTRB(20.0, 8.0, 20.0, 12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(achievement?.name??"---", style: Theme.of(context).textTheme.titleSmall, textAlign: TextAlign.center),
const Divider(),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Container(
constraints: BoxConstraints(
maxWidth: 512.0,
maxHeight: 512.0,
//minWidth: 256,
minHeight: 64.0,
),
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)),
heightFactor: 0.125,
widthFactor: 0.125,
child: Image.asset("res/icons/achievements.png", width: 2048, height: 2048, scale: 1),
),
),
),
),
//ClipRect(clipper: Rect.fromLTRB(0, 0, 64, 64), child: ),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
RichText(
textAlign: TextAlign.start,
text: TextSpan(
text: achievement?.v == null ? "---" : switch(achievement!.vt){
1 => intf.format(achievement!.v),
2 => get40lTime((achievement!.v! * 1000).floor()),
3 => get40lTime((achievement!.v!.abs() * 1000).floor()),
4 => "${f2.format(achievement!.v!)} m",
5 => "${intf.format(achievement!.pos!+1)}",
6 => intf.format(achievement!.v!.abs()),
_ => "lol"
},
style: TextStyle(fontFamily: "Eurostile Round", fontSize: 36, fontWeight: FontWeight.w500, color: Colors.white, height: 0.9),
),
),
if (achievement != null) RichText(
textAlign: TextAlign.start,
text: TextSpan(
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey),
children: [
TextSpan(text: "${achievement!.object}\n"),
if (achievement!.vt == 4) TextSpan(text: "Floor ${achievement?.a != null ? achievement!.a! : "-"}"),
if (achievement!.vt == 4) TextSpan(text: ""),
if (achievement!.vt != 5) TextSpan(text: (achievement?.pos != null && !achievement!.pos!.isNegative) ? "${intf.format(achievement!.pos!+1)}" : "№ ---", style: TextStyle(color: achievement?.pos != null ? getColorOfRank(achievement!.pos!+1) : Colors.grey)),
if (achievement!.vt != 5) TextSpan(text: "", style: TextStyle(color: achievement?.pos != null ? getColorOfRank(achievement!.pos!+1) : Colors.grey)),
TextSpan(text: "Top ${achievement?.pos != null ? percentage.format(achievement!.pos! / achievement!.total!) : "---%"}", style: TextStyle(color: achievement?.pos != null ? getColorOfRank(achievement!.pos!+1) : Colors.grey)),
]
),
),
],
),
),
],
),
const Divider(),
Text(achievement?.t != null ? timestamp(achievement!.t!) : "---", style: const TextStyle(color: Colors.grey))
],
),
),
);
}
}
@ -730,7 +818,7 @@ class _DestinationHomeState extends State<DestinationHome> with SingleTickerProv
);
}
Widget getRecordCard(RecordSingle? record, bool? betterThanRankAverage, MapEntry? closestAverage, bool? betterThanClosestAverage, String? rank){
Widget getRecordCard(RecordSingle? record, List<Achievement> achievements, bool? betterThanRankAverage, MapEntry? closestAverage, bool? betterThanClosestAverage, String? rank){
if (record == null) {
return const Card(
child: Center(child: Text("No record", style: TextStyle(fontSize: 42))),
@ -895,7 +983,13 @@ class _DestinationHomeState extends State<DestinationHome> with SingleTickerProv
],
),
),
)
),
Wrap(
direction: Axis.horizontal,
children: [
for (Achievement achievement in achievements) FractionallySizedBox(widthFactor: 0.5, child: AchievementSummary(achievement: achievement)),
],
),
]
);
}
@ -965,12 +1059,6 @@ class _DestinationHomeState extends State<DestinationHome> with SingleTickerProv
_transition = AnimationController(vsync: this, duration: Durations.long4);
// _transition.addListener((){
// setState(() {
// });
// });
_offsetAnimation = Tween<Offset>(
begin: Offset.zero,
end: const Offset(1.5, 0.0),
@ -1012,6 +1100,21 @@ class _DestinationHomeState extends State<DestinationHome> with SingleTickerProv
closestAverageBlitz = blitzAverages.entries.last;
blitzBetterThanClosestAverage = false;
}
List<Achievement> sprintAchievements = <Achievement>[
snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 5),
snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 7),
snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 8),
snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 9),
snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 36),
snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 37),
snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 38),
snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 48),
];
List<Achievement> blitzAchievements = <Achievement>[
snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 6),
snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 39),
snapshot.data!.summaries!.achievements.firstWhere((e) => e.k == 52),
];
return TweenAnimationBuilder(
duration: Durations.long4,
tween: Tween<double>(begin: 0, end: 1),
@ -1051,24 +1154,8 @@ class _DestinationHomeState extends State<DestinationHome> with SingleTickerProv
],
),
),
//if (testNews != null && testNews!.news.isNotEmpty)
Expanded(
child: FutureBuilder<News>(
future: widget.newsFuture,
builder: (context, snapshot) {
switch (snapshot.connectionState){
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
return const Card(child: Center(child: CircularProgressIndicator()));
case ConnectionState.done:
if (snapshot.hasData){
return NewsThingy(snapshot.data!);
}else if (snapshot.hasError){ return FutureError(snapshot); }
}
return const Text("what?");
}
),
child: NewsThingy(snapshot.data!.news!)
)
],
),
@ -1097,12 +1184,12 @@ class _DestinationHomeState extends State<DestinationHome> with SingleTickerProv
CardMod.exRecords => getListOfRecords("zenithex/recent", "zenithex/top", widget.constraints),
},
Cards.sprint => switch (cardMod){
CardMod.info => getRecordCard(snapshot.data?.summaries!.sprint, sprintBetterThanRankAverage, closestAverageSprint, sprintBetterThanClosestAverage, snapshot.data!.summaries!.league.rank),
CardMod.info => getRecordCard(snapshot.data?.summaries!.sprint, sprintAchievements, sprintBetterThanRankAverage, closestAverageSprint, sprintBetterThanClosestAverage, snapshot.data!.summaries!.league.rank),
CardMod.records => getListOfRecords("40l/recent", "40l/top", widget.constraints),
_ => const Center(child: Text("huh?"))
},
Cards.blitz => switch (cardMod){
CardMod.info => getRecordCard(snapshot.data?.summaries!.blitz, blitzBetterThanRankAverage, closestAverageBlitz, blitzBetterThanClosestAverage, snapshot.data!.summaries!.league.rank),
CardMod.info => getRecordCard(snapshot.data?.summaries!.blitz, blitzAchievements, blitzBetterThanRankAverage, closestAverageBlitz, blitzBetterThanClosestAverage, snapshot.data!.summaries!.league.rank),
CardMod.records => getListOfRecords("blitz/recent", "blitz/top", widget.constraints),
_ => const Center(child: Text("huh?"))
},

View File

@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:tetra_stats/data_objects/tetrio_constants.dart';
import 'package:tetra_stats/gen/strings.g.dart';
import 'package:tetra_stats/utils/open_in_browser.dart';
import 'package:tetra_stats/views/about_view.dart';
import 'package:tetra_stats/views/sprint_and_blitz_averages.dart';
class DestinationInfo extends StatefulWidget{
@ -74,18 +76,23 @@ class _DestinationInfo extends State<DestinationInfo> {
InfoCard(
height: widget.constraints.maxHeight - 77,
assetLink: "res/images/Снимок экрана_2023-11-06_01-00-50.png",
title: "Shizuru!",
description: "Shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru shizuru\nNakatsu Shizuru Nakatsu Shizuru Nakatsu Shizuru Nakatsu Shizuru Nakatsu Shizuru Nakatsu Shizuru Nakatsu Shizuru ",
onPressed: (){}
title: "Tetra Stats Wiki",
description: "Find more information about Tetra Stats functions and statictic, that it provides",
onPressed: (){
launchInBrowser(Uri.https("github.com", "dan63047/TetraStats/wiki"));
}
),
InfoCard(
height: widget.constraints.maxHeight - 77,
assetLink: "res/images/Снимок экрана_2023-11-06_01-00-50.png",
title: "About Tetra Stats",
description: "Developed by dan63\n",
onPressed: (){},
onPressed: (){
Navigator.push(context, MaterialPageRoute(
builder: (context) => AboutView(),
));
},
),
Card()
],
),
)

View File

@ -0,0 +1,82 @@
import 'package:flutter/material.dart';
class FirstTimeView extends StatefulWidget {
/// The very first view, that user see when he launch this programm.
const FirstTimeView({super.key});
@override
State<FirstTimeView> createState() => _FirstTimeState();
}
class _FirstTimeState extends State<FirstTimeView> with SingleTickerProviderStateMixin {
late AnimationController _transition;
late final Animation<Offset> _offsetAnimation;
@override
void initState() {
_transition = AnimationController(vsync: this, duration: Durations.long4);
_offsetAnimation = Tween<Offset>(
begin: Offset.zero,
end: const Offset(1.5, 0.0),
).animate(CurvedAnimation(
parent: _transition,
curve: Curves.elasticIn,
));
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: TweenAnimationBuilder(
duration: Durations.long4,
tween: Tween<double>(begin: 0, end: 1),
curve: Easing.standard,
builder: (context, value, child) {
return Container(
transform: Matrix4.translationValues(0, 600-value*600, 0),
child: Opacity(opacity: value, child: child),
);
},
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Welcome to Tetra Stats", style: Theme.of(context).textTheme.titleLarge),
Text("Service, that allows you to keep track of various statistics for TETR.IO"),
Padding(
padding: const EdgeInsets.only(top: 24.0),
child: Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("What's your nickname?", style: Theme.of(context).textTheme.titleSmall),
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: SizedBox(width: 400.0, child: TextField(
maxLength: 16,
decoration: InputDecoration(
hintText: "Type it here... (3-16 symbols)",
counter: const Offstage()
),
)),
),
ElevatedButton.icon(onPressed: (){}, icon: Icon(Icons.subdirectory_arrow_left), label: Text("Submit"))
],
),
),
),
)
],
)
)
),
),
);
}
}

View File

@ -23,7 +23,6 @@ import 'package:tetra_stats/views/destination_settings.dart';
import 'package:tetra_stats/main.dart';
late Future<FetchResults> _data;
late Future<News> _newsData;
TetrioPlayersLeaderboard? _everyone;
Future<FetchResults> getData(String searchFor) async {
@ -36,23 +35,26 @@ Future<FetchResults> getData(String searchFor) async {
}
}on TetrioPlayerNotExist{
return FetchResults(false, null, [], null, null, null, null, false, TetrioPlayerNotExist());
return FetchResults(false, null, [], null, null, null, null, null, false, TetrioPlayerNotExist());
}
late Summaries summaries;
late News? news;
late Cutoffs? cutoffs;
late CutoffsTetrio? averages;
try {
List<dynamic> requests = await Future.wait([
teto.fetchSummaries(player.userId),
teto.fetchNews(player.userId),
teto.fetchCutoffsBeanserver(),
if (prefs.getBool("showAverages") == true) teto.fetchCutoffsTetrio()
]);
summaries = requests[0];
cutoffs = requests.elementAtOrNull(1);
averages = requests.elementAtOrNull(2);
news = requests[1];
cutoffs = requests.elementAtOrNull(2);
averages = requests.elementAtOrNull(3);
} on Exception catch (e) {
return FetchResults(false, null, [], null, null, null, null, false, e);
return FetchResults(false, null, [], null, null, null, null, null, false, e);
}
PlayerLeaderboardPosition? _meAmongEveryone;
if (prefs.getBool("showPositions") == true){
@ -71,7 +73,7 @@ Future<FetchResults> getData(String searchFor) async {
await teto.storeState(summaries.league);
}
return FetchResults(true, player, states, summaries, cutoffs, averages, _meAmongEveryone, isTracking, null);
return FetchResults(true, player, states, summaries, news, cutoffs, averages, _meAmongEveryone, isTracking, null);
}
class MainView extends StatefulWidget {
@ -116,7 +118,6 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
setState(() {
_searchFor = player;
_data = getData(_searchFor);
_newsData = teto.fetchNews(_searchFor);
});
}
@ -160,7 +161,7 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
onPressed: () {
// Add your onPressed code here!
},
icon: const Icon(Icons.more_horiz_rounded),
icon: const Icon(Icons.refresh),
),
destinations: [
getDestinationButton(Icons.home, "Home"),
@ -191,7 +192,7 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
),
Expanded(
child: switch (destination){
0 => DestinationHome(searchFor: _searchFor, constraints: constraints, dataFuture: _data, newsFuture: _newsData),
0 => DestinationHome(searchFor: _searchFor, constraints: constraints, dataFuture: _data),
1 => DestinationGraphs(searchFor: _searchFor, constraints: constraints),
2 => DestinationLeaderboards(constraints: constraints),
3 => DestinationCutoffs(constraints: constraints),

View File

@ -56,7 +56,7 @@ class UserState extends State<UserView> {
body: SafeArea(
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return DestinationHome(searchFor: widget.searchFor, dataFuture: getData(widget.searchFor), newsFuture: teto.fetchNews(widget.searchFor), constraints: constraints, noSidebar: true);
return DestinationHome(searchFor: widget.searchFor, dataFuture: getData(widget.searchFor), constraints: constraints, noSidebar: true);
}
)
)

BIN
res/icons/achievements.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB