From 4df644f0f61e7916630f8f1bf4eeaa908a58746c Mon Sep 17 00:00:00 2001 From: dan63047 Date: Fri, 4 Oct 2024 00:00:41 +0300 Subject: [PATCH] Idea for compare view Time mostly spent on `AddNewColumnCard` animation That view should allow to compare multiple players --- lib/views/compare_view_tiles.dart | 307 ++++++++++++++++++++++++++++++ lib/views/main_view_tiles.dart | 15 +- 2 files changed, 321 insertions(+), 1 deletion(-) create mode 100644 lib/views/compare_view_tiles.dart diff --git a/lib/views/compare_view_tiles.dart b/lib/views/compare_view_tiles.dart new file mode 100644 index 0000000..f0eb736 --- /dev/null +++ b/lib/views/compare_view_tiles.dart @@ -0,0 +1,307 @@ +// ignore_for_file: use_build_context_synchronously + +import 'dart:io'; +import 'dart:math'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +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_zen.dart'; +import 'package:tetra_stats/gen/strings.g.dart'; +import 'package:tetra_stats/main.dart' show teto; +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/widgets/vs_graphs.dart'; +import 'package:transparent_image/transparent_image.dart'; +import 'package:vector_math/vector_math_64.dart' hide Colors; +import 'package:window_manager/window_manager.dart'; + +enum Mode{ + player, + stats, + averages +} +final DateFormat dateFormat = DateFormat.yMd(LocaleSettings.currentLocale.languageCode).add_Hm(); +var numbersReg = RegExp(r'\d+(\.\d*)*'); +late String oldWindowTitle; + +class CompareView extends StatefulWidget { + final TetrioPlayer initPlayer; + const CompareView(this.initPlayer); + + @override + State createState() => CompareState(); +} + +class CompareState extends State { + late ScrollController _scrollController; + List players = []; + List summaries = []; + + @override + void initState() { + _scrollController = ScrollController(); + players = [widget.initPlayer]; + getSummariesForInit(); + if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS){ + windowManager.getTitle().then((value) => oldWindowTitle = value); + } + super.initState(); + } + + @override + void dispose(){ + if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) windowManager.setTitle(oldWindowTitle); + super.dispose(); + } + + void getSummariesForInit() async { + summaries[0] = await teto.fetchSummaries(widget.initPlayer.userId); + setState(() { + + }); + } + + void addPlayer(String nickname) async { + players.add(await teto.fetchPlayer(nickname)); + summaries.add(await teto.fetchSummaries(players.last.userId)); + setState(() { + + }); + } + + double getWinrateByTR(double yourGlicko, double yourRD, double notyourGlicko,double notyourRD) { + return ((1 / + (1 + pow(10, + (notyourGlicko - yourGlicko) / + (400 * sqrt(1 + (3 * pow(0.0057564273, 2) * + (pow(yourRD, 2) + pow(notyourRD, 2)) / pow(pi, 2) + ))) + ) + ) + )); + } + + void _justUpdate() { + setState(() {}); + } + + @override + Widget build(BuildContext context) { + final t = Translations.of(context); + bool bigScreen = MediaQuery.of(context).size.width > 768; + return Scaffold( + floatingActionButtonLocation: FloatingActionButtonLocation.startTop, + floatingActionButton: Padding( + padding: const EdgeInsets.all(8.0), + child: FloatingActionButton( + onPressed: () => Navigator.pop(context), + tooltip: 'Fuck go back', + child: const Icon(Icons.arrow_back), + ), + ), + body: SingleChildScrollView( + scrollDirection: Axis.horizontal, + controller: _scrollController, + physics: const AlwaysScrollableScrollPhysics(), + child: Table( + defaultColumnWidth: FixedColumnWidth(350), + columnWidths: { + 0: FixedColumnWidth(200.000) + }, + children: [ + TableRow( + children: [ + Center(child: Text("player")), + for (var p in players) HeaderCard(p), + AddNewColumnCard(addPlayer) + ] + ), + TableRow( + children: [ + Text("Account Created"), + for (var p in players) Text(timestamp(p.registrationTime!)), + Container() + ] + ), + TableRow( + children: [ + Text("XP"), + for (var p in players) RichText(text: p.xp.isNegative ? TextSpan(text: "hidden", style: TextStyle(fontFamily: "Eurostile Round", color: Colors.grey)) : TextSpan(text: intf.format(p.xp), style: TextStyle(fontFamily: "Eurostile Round"), children: [TextSpan(text: " (lvl ${intf.format(p.level.floor())})", style: TextStyle(color: Colors.grey))])), + Container() + ] + ), + TableRow( + children: [ + Text("Time Played"), + for (var p in players) Text(p.gameTime.isNegative ? "hidden" : playtime(p.gameTime), style: TextStyle(color: p.gameTime.isNegative ? Colors.grey : Colors.white)), + Container() + ] + ), + TableRow( + children: [ + Text("Online Games Played"), + for (var p in players) Text(p.gamesPlayed.isNegative ? "hidden" : intf.format(p.gamesPlayed), style: TextStyle(color: p.gamesPlayed.isNegative ? Colors.grey : Colors.white)), + Container() + ] + ), + TableRow( + children: [ + Text("Online Games Won"), + for (var p in players) Text(p.gamesWon.isNegative ? "hidden" : intf.format(p.gamesWon), style: TextStyle(color: p.gamesWon.isNegative ? Colors.grey : Colors.white)), + Container() + ] + ), + TableRow( + children: [ + Text("Followers"), + for (var p in players) Text(intf.format(p.friendCount)), + Container() + ] + ), + ], + ), + ), + ); + } +} + +class HeaderCard extends StatelessWidget{ + final TetrioPlayer player; + + const HeaderCard(this.player); + + String fontStyle(int length){ + if (length < 10) return "Eurostile Round Extended"; + else if (length < 13) return "Eurostile Round"; + else return "Eurostile Round Condensed"; + } + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 175, + child: Card( + child: Column( + children: [ + Stack( + alignment: Alignment.topCenter, + clipBehavior: Clip.none, + children: [ + if (player.bannerRevision != null) FadeInImage.memoryNetwork(image: 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}", + placeholder: kTransparentImage, + fit: BoxFit.cover, + height: 120, + fadeInCurve: Easing.standard, fadeInDuration: Durations.long4 + ), + Positioned( + top: 20.0, + child: ClipRRect( + borderRadius: BorderRadius.circular(1000), + child: player.role == "banned" + ? Image.asset("res/avatars/tetrio_banned.png", fit: BoxFit.fitHeight, height: 128) + : player.avatarRevision != null + ? FadeInImage.memoryNetwork(image: 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}", + fit: BoxFit.fitHeight, height: 128, placeholder: kTransparentImage, fadeInCurve: Easing.emphasizedDecelerate, fadeInDuration: Durations.long4) + : Image.asset("res/avatars/tetrio_anon.png", fit: BoxFit.fitHeight, height: 128), + ) + ), + ], + ), + RichText( + text: TextSpan(text: player.username, style: TextStyle( + fontFamily: fontStyle(player.username.length), + fontSize: 28, + shadows: textShadow + ), + ) + ), + ], + ), + ), + ); + }} + +class AddNewColumnCard extends StatefulWidget{ + final Function addPlayer; + + const AddNewColumnCard(this.addPlayer); + + @override + State createState() => _AddNewColumnCardState(); +} + +class _AddNewColumnCardState extends State with SingleTickerProviderStateMixin { + late AnimationController _animController; + late Animation _anim; + + @override + void initState(){ + _animController = AnimationController( + duration: Durations.medium3, + vsync: this, + ); + _anim = new Tween( + begin: 0.0, + end: 1.0, + ).animate(new CurvedAnimation( + parent: _animController, + curve: Easing.standard + )); + super.initState(); + } + + @override + void dispose() { + _animController.dispose(); + super.dispose(); + } + + + @override + Widget build(BuildContext context) { + // TODO: implement build + return SizedBox( + height: 175.0, + child: Card( + child: AnimatedBuilder( + animation: _anim, + builder: (context, child) { + return _anim.value > 0.5 ? Opacity( + opacity: _anim.value*2-1, + child: Container( + transform: Matrix4.translationValues(0, 100-(_anim.value as double)*100, 0), + child: Column( + children: [ + Text("Enter username:"), + TextField( + autofocus: true, + onSubmitted: (value){ + widget.addPlayer(value); + }, + onTapOutside: (event) { + setState((){_animController.animateBack(0);}); + }, + ) + ], + ), + ), + ) : Center( + child: IconButton( + visualDensity: VisualDensity.comfortable, + onPressed: (){setState((){_animController.forward();});}, + icon: Opacity(opacity: 1-(_anim.value as double)*2, child: Transform.translate(offset: Offset.fromDirection(pi*1.5, (_anim.value as double)*50), child: Transform.rotate(angle: (_anim.value as double)*2, child: Icon(Icons.add)))), + constraints: BoxConstraints.expand(), + ), + ); + } + ) + ) + ); + } +} \ No newline at end of file diff --git a/lib/views/main_view_tiles.dart b/lib/views/main_view_tiles.dart index 4344d4a..a3b753b 100644 --- a/lib/views/main_view_tiles.dart +++ b/lib/views/main_view_tiles.dart @@ -39,6 +39,7 @@ import 'package:tetra_stats/utils/relative_timestamps.dart'; import 'package:tetra_stats/utils/text_shadow.dart'; import 'package:tetra_stats/views/singleplayer_record_view.dart'; import 'package:tetra_stats/views/tl_match_view.dart'; +import 'package:tetra_stats/views/compare_view_tiles.dart'; import 'package:tetra_stats/widgets/finesse_thingy.dart'; import 'package:tetra_stats/widgets/graphs.dart'; import 'package:tetra_stats/widgets/lineclears_thingy.dart'; @@ -3548,7 +3549,19 @@ class NewUserThingy extends StatelessWidget { 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.only(bottomLeft: Radius.circular(12.0))))))), - 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.only(bottomRight: Radius.circular(12.0))))))) + Expanded( + child: ElevatedButton.icon( + onPressed: (){ + Navigator.push(context, MaterialPageRoute( + builder: (context) => CompareView(player), + ), + ); + }, + icon: const Icon(Icons.balance), + label: Text(t.compare), + style: const ButtonStyle(shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.only(bottomRight: Radius.circular(12.0))))) + ) + ) ], ) ],