diff --git a/debian/debian.yaml b/debian/debian.yaml new file mode 100644 index 0000000..dc01608 --- /dev/null +++ b/debian/debian.yaml @@ -0,0 +1,14 @@ +flutter_app: + command: tetra_stats + arch: x64 + parent: /usr/local/lib + +control: + Package: tetra-stats + Version: 0.2.0 + Architecture: amd64 + Essential: no + Priority: optional + Depends: + Maintainer: dan63047 + Description: Track your and other player stats in TETR.IO \ No newline at end of file diff --git a/debian/gui/tetra-stats.desktop b/debian/gui/tetra-stats.desktop new file mode 100644 index 0000000..edf46b1 --- /dev/null +++ b/debian/gui/tetra-stats.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Version=0.2.0 +Name=Tetra Stats +GenericName=Tetra Stats +Comment=Track your and other player stats in TETR.IO +Terminal=false +Type=Application +Categories=Utility +Keywords=TETR.IO;tetrio;stats; \ No newline at end of file diff --git a/debian/gui/tetra-stats.png b/debian/gui/tetra-stats.png new file mode 100644 index 0000000..d010f6c Binary files /dev/null and b/debian/gui/tetra-stats.png differ diff --git a/debian/packages/tetra-stats_0.2.0_amd64.deb b/debian/packages/tetra-stats_0.2.0_amd64.deb new file mode 100644 index 0000000..4c71038 Binary files /dev/null and b/debian/packages/tetra-stats_0.2.0_amd64.deb differ diff --git a/lib/main.dart b/lib/main.dart index 65a1168..086d2a7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -16,6 +16,6 @@ void main() { routes: {"/settings": (context) => const SettingsView(), "/states": (context) => const TrackedPlayersView(), "/calc": (context) => const CalcView()}, theme: ThemeData( fontFamily: 'Eurostile Round', - colorScheme: const ColorScheme.dark(primary: Colors.cyanAccent, secondary: Colors.purpleAccent), + colorScheme: const ColorScheme.dark(primary: Colors.cyanAccent, secondary: Colors.white), scaffoldBackgroundColor: Colors.black))); } diff --git a/lib/views/compare_view.dart b/lib/views/compare_view.dart index ff1ec84..0f1a3be 100644 --- a/lib/views/compare_view.dart +++ b/lib/views/compare_view.dart @@ -54,8 +54,12 @@ class CompareState extends State { redSideStates!.add(DropdownMenuItem( value: state, child: Text(dateFormat.format(state.state)))); } - redSideStates!.add(DropdownMenuItem( + redSideStates!.firstWhere((element) => element.value == theRedSide, orElse: () { + redSideStates!.add(DropdownMenuItem( value: theRedSide!, child: const Text("Most recent one"))); + return DropdownMenuItem( + value: theRedSide!, child: const Text("Most recent one")); + },); }on Exception { states = []; redSideStates = null; @@ -78,13 +82,17 @@ class CompareState extends State { greenSideStates = null; try{ states = await teto.getPlayer(theGreenSide!.userId); - greenSideStates = >[]; + greenSideStates = >[]; for (final TetrioPlayer state in states) { greenSideStates!.add(DropdownMenuItem( value: state, child: Text(dateFormat.format(state.state)))); } - greenSideStates!.add(DropdownMenuItem( + greenSideStates!.firstWhere((element) => element.value == theGreenSide, orElse: () { + greenSideStates!.add(DropdownMenuItem( value: theGreenSide!, child: const Text("Most recent one"))); + return DropdownMenuItem( + value: theGreenSide!, child: const Text("Most recent one")); + },); }on Exception { states = []; greenSideStates = null; diff --git a/lib/views/main_view.dart b/lib/views/main_view.dart index d79dd96..ea2aab6 100644 --- a/lib/views/main_view.dart +++ b/lib/views/main_view.dart @@ -118,17 +118,22 @@ class _MainState extends State with SingleTickerProviderStateMixin { List tlMatches = []; bool isTracking = await teto.isPlayerTracking(me.userId); List states = []; - TetraLeagueAlpha? compareWith = null; - var uniqueTL = Set(); + TetraLeagueAlpha? compareWith; + var uniqueTL = {}; if (isTracking){ teto.storeState(me); teto.saveTLMatchesFromStream(await teto.getTLStream(me.userId)); states.addAll(await teto.getPlayer(me.userId)); - states.forEach((element) { + for (var element in states) { if (uniqueTL.isNotEmpty && uniqueTL.last != element.tlSeason1) uniqueTL.add(element.tlSeason1); if (uniqueTL.isEmpty) uniqueTL.add(element.tlSeason1); - }); - compareWith = uniqueTL.toList()[uniqueTL.length - 2]; + } + try{ + compareWith = uniqueTL.toList()[uniqueTL.length - 2]; + }on RangeError { + compareWith = null; + } + chartsData = >>[ DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.rating)], child: const Text("Tetra Rating")), DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.glicko!)], child: const Text("Glicko")), diff --git a/lib/views/settings_view.dart b/lib/views/settings_view.dart index 7d237fa..8ae3fbf 100644 --- a/lib/views/settings_view.dart +++ b/lib/views/settings_view.dart @@ -1,5 +1,9 @@ +import 'dart:io'; +import 'package:file_selector/file_selector.dart'; +import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:tetra_stats/services/crud_exceptions.dart'; import 'package:tetra_stats/services/tetrio_crud.dart'; @@ -12,7 +16,11 @@ class SettingsView extends StatefulWidget { } class SettingsState extends State { - PackageInfo _packageInfo = PackageInfo(appName: "TetraStats", packageName: "idk man", version: "some numbers", buildNumber: "anotherNumber"); + PackageInfo _packageInfo = PackageInfo( + appName: "TetraStats", + packageName: "idk man", + version: "some numbers", + buildNumber: "anotherNumber"); late SharedPreferences prefs; final TetrioService teto = TetrioService(); String defaultNickname = "Checking..."; @@ -67,8 +75,108 @@ class SettingsState extends State { children: [ ListTile( title: const Text("Export local database"), - subtitle: const Text("It contains states and Tetra League records of the tracked players and list of tracked players."), - onTap: (){}, + subtitle: const Text( + "It contains states and Tetra League records of the tracked players and list of tracked players."), + onTap: () { + if (Platform.isLinux || Platform.isWindows) { + showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + title: const Text("Desktop export", + style: TextStyle( + fontFamily: "Eurostile Round Extended")), + content: const SingleChildScrollView( + child: ListBody(children: [ + Text( + "It seems like you using this app on desktop. Check your documents folder, you should find \"TetraStats.db\". Copy it somewhere") + ]), + ), + actions: [ + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + )); + } + if (Platform.isAndroid){ + var downloadFolder = Directory("/storage/emulated/0/Download"); + File exportedDB = File("${downloadFolder.path}/TetraStats.db"); + getApplicationDocumentsDirectory().then((value) { + exportedDB.writeAsBytes(File("${value.path}/TetraStats.db").readAsBytesSync()); + showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + title: const Text("Android export", + style: TextStyle( + fontFamily: "Eurostile Round Extended")), + content: SingleChildScrollView( + child: ListBody(children: [Text("Exported.\n$exportedDB")]), + ), + actions: [ + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + )); + }); + } + }, + ), + ListTile( + title: const Text("Import local database"), + subtitle: const Text("Restore your backup. Notice that already stored database will be overwritten."), + onTap: () { + if(Platform.isAndroid){ + FilePicker.platform.pickFiles( + type: FileType.any, + ).then((value){ + if (value != null){ + var newDB = value.paths[0]!; + teto.close().then((value){ + if(!newDB.endsWith("db")){ + return ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Wrong file type"))); + } + getApplicationDocumentsDirectory().then((value){ + var oldDB = File("${value.path}/TetraStats.db"); + oldDB.writeAsBytes(File(newDB).readAsBytesSync(), flush: true).then((value){ + teto.open(); + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Import successful"))); + }); + }); + }); + } else { + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Operation was cancelled"))); + } + }); + }else{ + const XTypeGroup typeGroup = XTypeGroup( + label: 'Tetra Stats Database', + extensions: ['db'], + ); + openFile(acceptedTypeGroups: [typeGroup]).then((value){ + if (value != null){ + var newDB = value.path; + teto.close().then((value){ + getApplicationDocumentsDirectory().then((value){ + var oldDB = File("${value.path}/TetraStats.db"); + oldDB.writeAsBytes(File(newDB).readAsBytesSync()).then((value){ + teto.open(); + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Import successful"))); + }); + }); + }); + } else { + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Operation was cancelled"))); + } + }); + } + }, ), ListTile( title: const Text("Your TETR.IO account"), @@ -76,7 +184,9 @@ class SettingsState extends State { onTap: () => showDialog( context: context, builder: (BuildContext context) => AlertDialog( - title: const Text("Your TETR.IO account nickname or ID", style: TextStyle(fontFamily: "Eurostile Round Extended")), + title: const Text("Your TETR.IO account nickname or ID", + style: TextStyle( + fontFamily: "Eurostile Round Extended")), content: SingleChildScrollView( child: ListBody(children: [ const Text( diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 2c1ec4f..68ae3da 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,9 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin"); sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 7ea2a80..ee11cfe 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux sqlite3_flutter_libs ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 21b805c..1c7b623 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,7 @@ import FlutterMacOS import Foundation +import file_selector_macos import package_info_plus import path_provider_foundation import shared_preferences_foundation @@ -12,6 +13,7 @@ import sqflite import sqlite3_flutter_libs func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 7734ee3..03a6ef8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -81,6 +81,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9" + url: "https://pub.dev" + source: hosted + version: "0.3.3+4" crypto: dependency: transitive description: @@ -129,6 +137,70 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.4" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: b1729fc96627dd44012d0a901558177418818d6bd428df59dcfeb594e5f66432 + url: "https://pub.dev" + source: hosted + version: "5.3.2" + file_selector: + dependency: "direct main" + description: + name: file_selector + sha256: "2b9acc3587127132da2a8d88d069172c49e32f977211cdf8f61f4b8e68e2a165" + url: "https://pub.dev" + source: hosted + version: "0.9.4" + file_selector_ios: + dependency: transitive + description: + name: file_selector_ios + sha256: "54542b6b35e3ced6246df5fae13cf0b879d14669d0fdff1a53a098f16e23328b" + url: "https://pub.dev" + source: hosted + version: "0.5.1+4" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "770eb1ab057b5ae4326d1c24cc57710758b9a46026349d021d6311bd27580046" + url: "https://pub.dev" + source: hosted + version: "0.9.2" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "7a6f1ae6107265664f3f7f89a66074882c4d506aef1441c9af313c1f7e6f41ce" + url: "https://pub.dev" + source: hosted + version: "0.9.3" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: "412705a646a0ae90f33f37acfae6a0f7cbc02222d6cd34e479421c3e74d3853c" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + file_selector_web: + dependency: transitive + description: + name: file_selector_web + sha256: a890ca514f053e976ad7632cd1e665e2c4543d5acd82ec352a8d5709c55d6363 + url: "https://pub.dev" + source: hosted + version: "0.9.1" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "1372760c6b389842b77156203308940558a2817360154084368608413835fc26" + url: "https://pub.dev" + source: hosted + version: "0.9.3" fl_chart: dependency: "direct main" description: @@ -158,6 +230,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360" + url: "https://pub.dev" + source: hosted + version: "2.0.15" flutter_test: dependency: "direct dev" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 9fa5363..1447080 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,6 +41,8 @@ dependencies: shared_preferences: ^2.1.1 intl: ^0.18.1 syncfusion_flutter_gauges: ^22.1.34 + file_selector: ^0.9.4 + file_picker: ^5.3.2 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 988f3c8..987fb3d 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,9 +6,12 @@ #include "generated_plugin_registrant.h" +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); Sqlite3FlutterLibsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 8abff95..3057813 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_windows sqlite3_flutter_libs )