Merge pull request #88 from flixcoo/feature/46-implement-native-rating-dialogs

Implement native rating dialogs
This commit is contained in:
2025-07-10 20:46:30 +02:00
committed by GitHub
25 changed files with 393 additions and 94 deletions

View File

@@ -11,6 +11,4 @@ linter:
prefer_const_literals_to_create_immutables: true prefer_const_literals_to_create_immutables: true
unnecessary_const: true unnecessary_const: true
lines_longer_than_80_chars: false lines_longer_than_80_chars: false
constant_identifier_names: false
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@@ -1,5 +1,6 @@
arb-dir: lib/l10n arb-dir: lib/l10n/arb
template-arb-file: app_de.arb template-arb-file: app_de.arb
untranslated-messages-file: lib/l10n/untranslated_messages.json untranslated-messages-file: lib/l10n/arb/untranslated_messages.json
nullable-getter: false nullable-getter: false
output-localization-file: app_localizations.dart output-localization-file: app_localizations.dart
output-dir: lib/l10n/generated

22
lib/core/constants.dart Normal file
View File

@@ -0,0 +1,22 @@
import 'package:rate_my_app/rate_my_app.dart';
class Constants {
static const String appDevPhase = 'Beta';
static const String INSTAGRAM_LINK = 'https://instagram.felixkirchner.de';
static const String GITHUB_LINK = 'https://github.felixkirchner.de';
static const String GITHUB_ISSUES_LINK =
'https://cabocounter-issues.felixkirchner.de';
static const String GITHUB_WIKI_LINK =
'https://cabocounter-wiki.felixkirchner.de';
static const String EMAIL = 'cabocounter@felixkirchner.de';
static const String PRIVACY_POLICY_LINK =
'https://www.privacypolicies.com/live/1b3759d4-b2f1-4511-8e3b-21bb1626be68';
static RateMyApp rateMyApp = RateMyApp(
appStoreIdentifier: '6747105718',
minDays: 15,
remindDays: 45,
minLaunches: 15,
remindLaunches: 40);
}

View File

@@ -30,6 +30,16 @@
} }
} }
}, },
"pre_rating_title": "Gefällt dir die App?",
"pre_rating_message": "Feedback hilft mir, die App zu verbessern. Vielen Dank!",
"yes": "Ja",
"no": "Nein",
"bad_rating_title": "Unzufrieden mit der App?",
"bad_rating_message": "Schreib mir gerne direkt eine E-Mail, damit wir dein Problem lösen können!",
"contact_email": "E-Mail schreiben",
"email_subject": "Feedback: Cabo Counter App",
"email_body": "Ich habe folgendes Feedback...",
"overview": "Übersicht", "overview": "Übersicht",
"new_game": "Neues Spiel", "new_game": "Neues Spiel",
"game_title": "Titel des Spiels", "game_title": "Titel des Spiels",
@@ -103,6 +113,7 @@
"create_issue": "Issue erstellen", "create_issue": "Issue erstellen",
"wiki": "Wiki", "wiki": "Wiki",
"app_version": "App-Version", "app_version": "App-Version",
"privacy_policy": "Datenschutzerklärung",
"build": "Build-Nr.", "build": "Build-Nr.",
"loading": "Lädt...", "loading": "Lädt...",

View File

@@ -30,6 +30,16 @@
} }
} }
}, },
"pre_rating_title": "Do you like the app?",
"pre_rating_message": "Feedback helps me to continuously improve the app. Thank you!",
"yes": "Yes",
"no": "No",
"bad_rating_title": "Not satisfied?",
"bad_rating_message": "If you are not satisfied with the app, please let me know before leaving a bad rating. I will try to fix the issue as soon as possible.",
"contact_email": "Contact via E-Mail",
"email_subject": "Feedback: Cabo Counter App",
"email_body": "I have the following feedback...",
"overview": "Overview", "overview": "Overview",
"new_game": "New Game", "new_game": "New Game",
"game_title": "Game Title", "game_title": "Game Title",
@@ -103,6 +113,7 @@
"create_issue": "Create Issue", "create_issue": "Create Issue",
"wiki": "Wiki", "wiki": "Wiki",
"app_version": "App Version", "app_version": "App Version",
"privacy_policy": "Privacy Policy",
"loading": "Loading...", "loading": "Loading...",
"build": "Build No.", "build": "Build No.",

View File

@@ -18,7 +18,7 @@ import 'app_localizations_en.dart';
/// `supportedLocales` list. For example: /// `supportedLocales` list. For example:
/// ///
/// ```dart /// ```dart
/// import 'l10n/app_localizations.dart'; /// import 'generated/app_localizations.dart';
/// ///
/// return MaterialApp( /// return MaterialApp(
/// localizationsDelegates: AppLocalizations.localizationsDelegates, /// localizationsDelegates: AppLocalizations.localizationsDelegates,
@@ -218,6 +218,60 @@ abstract class AppLocalizations {
/// **'Bist du sicher, dass du das Spiel \"{gameTitle}\" löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'** /// **'Bist du sicher, dass du das Spiel \"{gameTitle}\" löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'**
String delete_game_message(String gameTitle); String delete_game_message(String gameTitle);
/// No description provided for @pre_rating_title.
///
/// In de, this message translates to:
/// **'Gefällt dir die App?'**
String get pre_rating_title;
/// No description provided for @pre_rating_message.
///
/// In de, this message translates to:
/// **'Feedback hilft mir, die App zu verbessern. Vielen Dank!'**
String get pre_rating_message;
/// No description provided for @yes.
///
/// In de, this message translates to:
/// **'Ja'**
String get yes;
/// No description provided for @no.
///
/// In de, this message translates to:
/// **'Nein'**
String get no;
/// No description provided for @bad_rating_title.
///
/// In de, this message translates to:
/// **'Unzufrieden mit der App?'**
String get bad_rating_title;
/// No description provided for @bad_rating_message.
///
/// In de, this message translates to:
/// **'Schreib mir gerne direkt eine E-Mail, damit wir dein Problem lösen können!'**
String get bad_rating_message;
/// No description provided for @contact_email.
///
/// In de, this message translates to:
/// **'E-Mail schreiben'**
String get contact_email;
/// No description provided for @email_subject.
///
/// In de, this message translates to:
/// **'Feedback: Cabo Counter App'**
String get email_subject;
/// No description provided for @email_body.
///
/// In de, this message translates to:
/// **'Ich habe folgendes Feedback...'**
String get email_body;
/// No description provided for @overview. /// No description provided for @overview.
/// ///
/// In de, this message translates to: /// In de, this message translates to:
@@ -566,6 +620,12 @@ abstract class AppLocalizations {
/// **'App-Version'** /// **'App-Version'**
String get app_version; String get app_version;
/// No description provided for @privacy_policy.
///
/// In de, this message translates to:
/// **'Datenschutzerklärung'**
String get privacy_policy;
/// No description provided for @build. /// No description provided for @build.
/// ///
/// In de, this message translates to: /// In de, this message translates to:

View File

@@ -71,6 +71,35 @@ class AppLocalizationsDe extends AppLocalizations {
return 'Bist du sicher, dass du das Spiel \"$gameTitle\" löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'; return 'Bist du sicher, dass du das Spiel \"$gameTitle\" löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.';
} }
@override
String get pre_rating_title => 'Gefällt dir die App?';
@override
String get pre_rating_message =>
'Feedback hilft mir, die App zu verbessern. Vielen Dank!';
@override
String get yes => 'Ja';
@override
String get no => 'Nein';
@override
String get bad_rating_title => 'Unzufrieden mit der App?';
@override
String get bad_rating_message =>
'Schreib mir gerne direkt eine E-Mail, damit wir dein Problem lösen können!';
@override
String get contact_email => 'E-Mail schreiben';
@override
String get email_subject => 'Feedback: Cabo Counter App';
@override
String get email_body => 'Ich habe folgendes Feedback...';
@override @override
String get overview => 'Übersicht'; String get overview => 'Übersicht';
@@ -256,6 +285,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get app_version => 'App-Version'; String get app_version => 'App-Version';
@override
String get privacy_policy => 'Datenschutzerklärung';
@override @override
String get build => 'Build-Nr.'; String get build => 'Build-Nr.';

View File

@@ -71,6 +71,35 @@ class AppLocalizationsEn extends AppLocalizations {
return 'Are you sure you want to delete the game \"$gameTitle\"? This action cannot be undone.'; return 'Are you sure you want to delete the game \"$gameTitle\"? This action cannot be undone.';
} }
@override
String get pre_rating_title => 'Do you like the app?';
@override
String get pre_rating_message =>
'Feedback helps me to continuously improve the app. Thank you!';
@override
String get yes => 'Yes';
@override
String get no => 'No';
@override
String get bad_rating_title => 'Not satisfied?';
@override
String get bad_rating_message =>
'If you are not satisfied with the app, please let me know before leaving a bad rating. I will try to fix the issue as soon as possible.';
@override
String get contact_email => 'Contac via E-Mail';
@override
String get email_subject => 'Feedback: Cabo Counter App';
@override
String get email_body => 'I have the following feedback...';
@override @override
String get overview => 'Overview'; String get overview => 'Overview';
@@ -253,6 +282,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get app_version => 'App Version'; String get app_version => 'App Version';
@override
String get privacy_policy => 'Privacy Policy';
@override @override
String get build => 'Build No.'; String get build => 'Build No.';

View File

@@ -1,9 +1,9 @@
import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/core/custom_theme.dart';
import 'package:cabo_counter/l10n/generated/app_localizations.dart';
import 'package:cabo_counter/presentation/views/tab_view.dart'; import 'package:cabo_counter/presentation/views/tab_view.dart';
import 'package:cabo_counter/services/config_service.dart'; import 'package:cabo_counter/services/config_service.dart';
import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart';
import 'package:cabo_counter/services/version_service.dart'; import 'package:cabo_counter/services/version_service.dart';
import 'package:cabo_counter/utility/custom_theme.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';

View File

@@ -1,11 +1,13 @@
import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/core/constants.dart';
import 'package:cabo_counter/l10n/generated/app_localizations.dart';
import 'package:cabo_counter/services/version_service.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class InformationView extends StatelessWidget { class AboutView extends StatelessWidget {
const InformationView({super.key}); const AboutView({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -28,9 +30,13 @@ class InformationView extends StatelessWidget {
), ),
), ),
), ),
Text(
'${AppLocalizations.of(context).app_version} ${VersionService.getVersionWithBuild()}',
style: TextStyle(fontSize: 15, color: Colors.grey[300]),
),
Padding( Padding(
padding: padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 30), const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
child: SizedBox( child: SizedBox(
height: 200, height: 200,
child: Image.asset('assets/cabo_counter-logo_rounded.png'), child: Image.asset('assets/cabo_counter-logo_rounded.png'),
@@ -54,15 +60,15 @@ class InformationView extends StatelessWidget {
children: [ children: [
IconButton( IconButton(
onPressed: () => onPressed: () =>
launchUrl(Uri.parse('https://www.instagram.com/fx.kr')), launchUrl(Uri.parse(Constants.INSTAGRAM_LINK)),
icon: const Icon(FontAwesomeIcons.instagram)), icon: const Icon(FontAwesomeIcons.instagram)),
IconButton( IconButton(
onPressed: () => launchUrl( onPressed: () =>
Uri.parse('mailto:felix.kirchner.fk@gmail.com')), launchUrl(Uri.parse('mailto:${Constants.EMAIL}')),
icon: const Icon(CupertinoIcons.envelope)), icon: const Icon(CupertinoIcons.envelope)),
IconButton( IconButton(
onPressed: () => onPressed: () =>
launchUrl(Uri.parse('https://www.github.com/flixcoo')), launchUrl(Uri.parse(Constants.GITHUB_LINK)),
icon: const Icon(FontAwesomeIcons.github)), icon: const Icon(FontAwesomeIcons.github)),
], ],
), ),

View File

@@ -1,11 +1,11 @@
import 'package:cabo_counter/core/custom_theme.dart';
import 'package:cabo_counter/data/game_manager.dart'; import 'package:cabo_counter/data/game_manager.dart';
import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/data/game_session.dart';
import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/l10n/generated/app_localizations.dart';
import 'package:cabo_counter/presentation/views/create_game_view.dart'; import 'package:cabo_counter/presentation/views/create_game_view.dart';
import 'package:cabo_counter/presentation/views/graph_view.dart'; import 'package:cabo_counter/presentation/views/graph_view.dart';
import 'package:cabo_counter/presentation/views/round_view.dart'; import 'package:cabo_counter/presentation/views/round_view.dart';
import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart';
import 'package:cabo_counter/utility/custom_theme.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -235,7 +235,8 @@ class _ActiveGameViewState extends State<ActiveGameView> {
child: Text( child: Text(
AppLocalizations.of(context).end_game, AppLocalizations.of(context).end_game,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.bold, color: Colors.red), fontWeight: FontWeight.bold,
color: CupertinoColors.destructiveRed),
), ),
onPressed: () { onPressed: () {
setState(() { setState(() {

View File

@@ -1,10 +1,10 @@
import 'package:cabo_counter/core/custom_theme.dart';
import 'package:cabo_counter/data/game_manager.dart'; import 'package:cabo_counter/data/game_manager.dart';
import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/data/game_session.dart';
import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/l10n/generated/app_localizations.dart';
import 'package:cabo_counter/presentation/views/active_game_view.dart'; import 'package:cabo_counter/presentation/views/active_game_view.dart';
import 'package:cabo_counter/presentation/views/mode_selection_view.dart'; import 'package:cabo_counter/presentation/views/mode_selection_view.dart';
import 'package:cabo_counter/services/config_service.dart'; import 'package:cabo_counter/services/config_service.dart';
import 'package:cabo_counter/utility/custom_theme.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
enum CreateStatus { enum CreateStatus {

View File

@@ -1,5 +1,5 @@
import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/data/game_session.dart';
import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/l10n/generated/app_localizations.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_charts/charts.dart'; import 'package:syncfusion_flutter_charts/charts.dart';

View File

@@ -1,13 +1,19 @@
import 'package:cabo_counter/core/constants.dart';
import 'package:cabo_counter/core/custom_theme.dart';
import 'package:cabo_counter/data/game_manager.dart'; import 'package:cabo_counter/data/game_manager.dart';
import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/l10n/generated/app_localizations.dart';
import 'package:cabo_counter/presentation/views/active_game_view.dart'; import 'package:cabo_counter/presentation/views/active_game_view.dart';
import 'package:cabo_counter/presentation/views/create_game_view.dart'; import 'package:cabo_counter/presentation/views/create_game_view.dart';
import 'package:cabo_counter/presentation/views/settings_view.dart'; import 'package:cabo_counter/presentation/views/settings_view.dart';
import 'package:cabo_counter/services/config_service.dart'; import 'package:cabo_counter/services/config_service.dart';
import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart';
import 'package:cabo_counter/utility/custom_theme.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
enum PreRatingDialogDecision { yes, no, cancel }
enum BadRatingDialogDecision { email, cancel }
class MainMenuView extends StatefulWidget { class MainMenuView extends StatefulWidget {
const MainMenuView({super.key}); const MainMenuView({super.key});
@@ -29,6 +35,17 @@ class _MainMenuViewState extends State<MainMenuView> {
}); });
}); });
gameManager.addListener(_updateView); gameManager.addListener(_updateView);
WidgetsBinding.instance.addPostFrameCallback((_) async {
await Constants.rateMyApp.init();
if (Constants.rateMyApp.shouldOpenDialog &&
Constants.appDevPhase != 'Beta') {
await Future.delayed(const Duration(milliseconds: 600));
if (!mounted) return;
_handleFeedbackDialog(context);
}
});
} }
void _updateView() { void _updateView() {
@@ -57,14 +74,12 @@ class _MainMenuViewState extends State<MainMenuView> {
icon: const Icon(CupertinoIcons.settings, size: 30)), icon: const Icon(CupertinoIcons.settings, size: 30)),
middle: const Text('Cabo Counter'), middle: const Text('Cabo Counter'),
trailing: IconButton( trailing: IconButton(
onPressed: () => { onPressed: () => Navigator.push(
Navigator.push( context,
context, CupertinoPageRoute(
CupertinoPageRoute( builder: (context) => const CreateGameView(),
builder: (context) => const CreateGameView(), ),
), ),
)
},
icon: const Icon(CupertinoIcons.add)), icon: const Icon(CupertinoIcons.add)),
), ),
child: CupertinoPageScaffold( child: CupertinoPageScaffold(
@@ -128,7 +143,7 @@ class _MainMenuViewState extends State<MainMenuView> {
final String gameTitle = gameManager final String gameTitle = gameManager
.gameList[index].gameTitle; .gameList[index].gameTitle;
return await _showDeleteGamePopup( return await _showDeleteGamePopup(
gameTitle); context, gameTitle);
}, },
onDismissed: (direction) { onDismissed: (direction) {
gameManager gameManager
@@ -204,40 +219,144 @@ class _MainMenuViewState extends State<MainMenuView> {
return AppLocalizations.of(context).unlimited; return AppLocalizations.of(context).unlimited;
} }
/// Handles the feedback dialog when the conditions for rating are met.
/// It shows a dialog asking the user if they like the app,
/// and based on their response, it either opens the rating dialog or an email client for feedback.
Future<void> _handleFeedbackDialog(BuildContext context) async {
final String emailSubject = AppLocalizations.of(context).email_subject;
final String emailBody = AppLocalizations.of(context).email_body;
final Uri emailUri = Uri(
scheme: 'mailto',
path: Constants.EMAIL,
query: 'subject=$emailSubject'
'&body=$emailBody',
);
PreRatingDialogDecision preRatingDecision =
await _showPreRatingDialog(context);
BadRatingDialogDecision badRatingDecision = BadRatingDialogDecision.cancel;
// so that the bad rating dialog is not shown immediately
await Future.delayed(const Duration(milliseconds: 300));
switch (preRatingDecision) {
case PreRatingDialogDecision.yes:
if (context.mounted) Constants.rateMyApp.showStarRateDialog(context);
break;
case PreRatingDialogDecision.no:
if (context.mounted) {
badRatingDecision = await _showBadRatingDialog(context);
}
if (badRatingDecision == BadRatingDialogDecision.email) {
if (context.mounted) {
launchUrl(emailUri);
}
}
break;
case PreRatingDialogDecision.cancel:
break;
}
}
/// Shows a confirmation dialog to delete all game sessions. /// Shows a confirmation dialog to delete all game sessions.
/// Returns true if the user confirms the deletion, false otherwise. /// Returns true if the user confirms the deletion, false otherwise.
/// [gameTitle] is the title of the game session to be deleted. /// [gameTitle] is the title of the game session to be deleted.
Future<bool> _showDeleteGamePopup(String gameTitle) async { Future<bool> _showDeleteGamePopup(
bool? shouldDelete = await showCupertinoDialog<bool>( BuildContext context, String gameTitle) async {
return await showCupertinoDialog<bool>(
context: context, context: context,
builder: (context) { builder: (BuildContext context) {
return CupertinoAlertDialog( return CupertinoAlertDialog(
title: Text(AppLocalizations.of(context).delete_game_title), title: Text(
content: Text( AppLocalizations.of(context).delete_game_title,
AppLocalizations.of(context).delete_game_message(gameTitle)),
actions: [
CupertinoDialogAction(
onPressed: () {
Navigator.pop(context, false);
},
child: Text(AppLocalizations.of(context).cancel),
), ),
CupertinoDialogAction( content: Text(AppLocalizations.of(context)
onPressed: () { .delete_game_message(gameTitle)),
Navigator.pop(context, true); actions: [
}, CupertinoDialogAction(
child: Text( onPressed: () {
AppLocalizations.of(context).delete, Navigator.of(context).pop(false);
style: const TextStyle( },
fontWeight: FontWeight.bold, color: Colors.red), child: Text(AppLocalizations.of(context).cancel),
), ),
), CupertinoDialogAction(
], isDestructiveAction: true,
); isDefaultAction: true,
onPressed: () {
Navigator.of(context).pop(true);
},
child: Text(
AppLocalizations.of(context).delete,
),
)
]);
}, },
) ?? ) ??
false; false;
return shouldDelete; }
/// Shows a dialog asking the user if they like the app.
/// Returns the user's decision as an integer.
/// - PRE_RATING_DIALOG_YES: User likes the app and wants to rate it.
/// - PRE_RATING_DIALOG_NO: User does not like the app and wants to provide feedback.
/// - PRE_RATING_DIALOG_CANCEL: User cancels the dialog.
Future<PreRatingDialogDecision> _showPreRatingDialog(
BuildContext context) async {
return await showCupertinoDialog<PreRatingDialogDecision>(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: Text(AppLocalizations.of(context).pre_rating_title),
content:
Text(AppLocalizations.of(context).pre_rating_message),
actions: [
CupertinoDialogAction(
onPressed: () => Navigator.of(context)
.pop(PreRatingDialogDecision.yes),
isDefaultAction: true,
child: Text(AppLocalizations.of(context).yes),
),
CupertinoDialogAction(
onPressed: () =>
Navigator.of(context).pop(PreRatingDialogDecision.no),
child: Text(AppLocalizations.of(context).no),
),
CupertinoDialogAction(
onPressed: () => Navigator.of(context).pop(),
isDestructiveAction: true,
child: Text(AppLocalizations.of(context).cancel),
)
],
)) ??
PreRatingDialogDecision.cancel;
}
/// Shows a dialog asking the user for feedback if they do not like the app.
/// Returns the user's decision as an integer.
/// - BAD_RATING_DIALOG_EMAIL: User wants to send an email with feedback.
/// - BAD_RATING_DIALOG_CANCEL: User cancels the dialog.
Future<BadRatingDialogDecision> _showBadRatingDialog(
BuildContext context) async {
return await showCupertinoDialog<BadRatingDialogDecision>(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: Text(AppLocalizations.of(context).bad_rating_title),
content:
Text(AppLocalizations.of(context).bad_rating_message),
actions: [
CupertinoDialogAction(
isDefaultAction: true,
onPressed: () => Navigator.of(context)
.pop(BadRatingDialogDecision.email),
child: Text(AppLocalizations.of(context).contact_email),
),
CupertinoDialogAction(
isDestructiveAction: true,
onPressed: () => Navigator.of(context).pop(),
child: Text(AppLocalizations.of(context).cancel))
],
)) ??
BadRatingDialogDecision.cancel;
} }
@override @override

View File

@@ -1,5 +1,5 @@
import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/core/custom_theme.dart';
import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:cabo_counter/l10n/generated/app_localizations.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
class ModeSelectionMenu extends StatelessWidget { class ModeSelectionMenu extends StatelessWidget {

View File

@@ -1,7 +1,7 @@
import 'package:cabo_counter/core/custom_theme.dart';
import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/data/game_session.dart';
import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/l10n/generated/app_localizations.dart';
import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart';
import 'package:cabo_counter/utility/custom_theme.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';

View File

@@ -1,10 +1,11 @@
import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/core/constants.dart';
import 'package:cabo_counter/core/custom_theme.dart';
import 'package:cabo_counter/l10n/generated/app_localizations.dart';
import 'package:cabo_counter/presentation/widgets/custom_form_row.dart'; import 'package:cabo_counter/presentation/widgets/custom_form_row.dart';
import 'package:cabo_counter/presentation/widgets/stepper.dart'; import 'package:cabo_counter/presentation/widgets/custom_stepper.dart';
import 'package:cabo_counter/services/config_service.dart'; import 'package:cabo_counter/services/config_service.dart';
import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart';
import 'package:cabo_counter/services/version_service.dart'; import 'package:cabo_counter/services/version_service.dart';
import 'package:cabo_counter/utility/custom_theme.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@@ -53,7 +54,7 @@ class _SettingsViewState extends State<SettingsView> {
CustomFormRow( CustomFormRow(
prefixText: 'Cabo-Strafe', prefixText: 'Cabo-Strafe',
prefixIcon: CupertinoIcons.bolt_fill, prefixIcon: CupertinoIcons.bolt_fill,
suffixWidget: Stepper( suffixWidget: CustomStepper(
key: _stepperKey1, key: _stepperKey1,
initialValue: ConfigService.caboPenalty, initialValue: ConfigService.caboPenalty,
minValue: 0, minValue: 0,
@@ -70,7 +71,7 @@ class _SettingsViewState extends State<SettingsView> {
CustomFormRow( CustomFormRow(
prefixText: 'Punkte-Limit', prefixText: 'Punkte-Limit',
prefixIcon: FontAwesomeIcons.bullseye, prefixIcon: FontAwesomeIcons.bullseye,
suffixWidget: Stepper( suffixWidget: CustomStepper(
key: _stepperKey2, key: _stepperKey2,
initialValue: ConfigService.pointLimit, initialValue: ConfigService.pointLimit,
minValue: 30, minValue: 30,
@@ -93,7 +94,6 @@ class _SettingsViewState extends State<SettingsView> {
setState(() { setState(() {
_stepperKey1 = UniqueKey(); _stepperKey1 = UniqueKey();
_stepperKey2 = UniqueKey(); _stepperKey2 = UniqueKey();
print('Config reset to default');
}); });
}, },
) )
@@ -142,17 +142,25 @@ class _SettingsViewState extends State<SettingsView> {
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
children: [ children: [
CustomFormRow( CustomFormRow(
prefixText: AppLocalizations.of(context).create_issue, prefixText: AppLocalizations.of(context).wiki,
prefixIcon: FontAwesomeIcons.github, prefixIcon: CupertinoIcons.book,
onPressed: () => launchUrl(Uri.parse( onPressed: () =>
'https://github.com/flixcoo/Cabo-Counter/issues')), launchUrl(Uri.parse(Constants.GITHUB_WIKI_LINK)),
suffixWidget: const CupertinoListTileChevron(), suffixWidget: const CupertinoListTileChevron(),
), ),
CustomFormRow( CustomFormRow(
prefixText: AppLocalizations.of(context).wiki, prefixText:
prefixIcon: CupertinoIcons.book, AppLocalizations.of(context).privacy_policy,
onPressed: () => launchUrl(Uri.parse( prefixIcon: CupertinoIcons.doc_append,
'https://github.com/flixcoo/Cabo-Counter/wiki')), onPressed: () => launchUrl(
Uri.parse(Constants.PRIVACY_POLICY_LINK)),
suffixWidget: const CupertinoListTileChevron(),
),
CustomFormRow(
prefixText: AppLocalizations.of(context).error_found,
prefixIcon: FontAwesomeIcons.github,
onPressed: () => launchUrl(
Uri.parse(Constants.GITHUB_ISSUES_LINK)),
suffixWidget: const CupertinoListTileChevron(), suffixWidget: const CupertinoListTileChevron(),
), ),
CustomFormRow( CustomFormRow(

View File

@@ -1,7 +1,7 @@
import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/core/custom_theme.dart';
import 'package:cabo_counter/presentation/views/information_view.dart'; import 'package:cabo_counter/l10n/generated/app_localizations.dart';
import 'package:cabo_counter/presentation/views/about_view.dart';
import 'package:cabo_counter/presentation/views/main_menu_view.dart'; import 'package:cabo_counter/presentation/views/main_menu_view.dart';
import 'package:cabo_counter/utility/custom_theme.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
class TabView extends StatefulWidget { class TabView extends StatefulWidget {
@@ -39,7 +39,7 @@ class _TabViewState extends State<TabView> {
if (index == 0) { if (index == 0) {
return const MainMenuView(); return const MainMenuView();
} else { } else {
return const InformationView(); return const AboutView();
} }
}); });
}, },

View File

@@ -1,5 +1,5 @@
import 'package:cabo_counter/presentation/widgets/stepper.dart'; import 'package:cabo_counter/core/custom_theme.dart';
import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:cabo_counter/presentation/widgets/custom_stepper.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
class CustomFormRow extends StatefulWidget { class CustomFormRow extends StatefulWidget {
@@ -43,7 +43,7 @@ class _CustomFormRowState extends State<CustomFormRow> {
Text(widget.prefixText), Text(widget.prefixText),
], ],
), ),
padding: suffixWidget is Stepper padding: suffixWidget is CustomStepper
? const EdgeInsets.fromLTRB(15, 0, 0, 0) ? const EdgeInsets.fromLTRB(15, 0, 0, 0)
: const EdgeInsets.symmetric(vertical: 10, horizontal: 15), : const EdgeInsets.symmetric(vertical: 10, horizontal: 15),
child: suffixWidget, child: suffixWidget,

View File

@@ -1,13 +1,13 @@
import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:cabo_counter/core/custom_theme.dart';
import 'package:flutter/cupertino.dart'; // Für iOS-Style import 'package:flutter/cupertino.dart'; // Für iOS-Style
class Stepper extends StatefulWidget { class CustomStepper extends StatefulWidget {
final int minValue; final int minValue;
final int maxValue; final int maxValue;
final int? initialValue; final int? initialValue;
final int step; final int step;
final ValueChanged<int> onChanged; final ValueChanged<int> onChanged;
const Stepper({ const CustomStepper({
super.key, super.key,
required this.minValue, required this.minValue,
required this.maxValue, required this.maxValue,
@@ -18,10 +18,10 @@ class Stepper extends StatefulWidget {
@override @override
// ignore: library_private_types_in_public_api // ignore: library_private_types_in_public_api
_StepperState createState() => _StepperState(); _CustomStepperState createState() => _CustomStepperState();
} }
class _StepperState extends State<Stepper> { class _CustomStepperState extends State<CustomStepper> {
late int _value; late int _value;
@override @override

View File

@@ -1,4 +1,4 @@
import 'package:cabo_counter/utility/globals.dart'; import 'package:cabo_counter/core/constants.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
class VersionService { class VersionService {
@@ -19,7 +19,7 @@ class VersionService {
if (_version == '-.-.-') { if (_version == '-.-.-') {
return getVersionNumber(); return getVersionNumber();
} }
return '${Globals.appDevPhase} $_version'; return '${Constants.appDevPhase} $_version';
} }
static String getBuildNumber() { static String getBuildNumber() {

View File

@@ -1,3 +0,0 @@
class Globals {
static String appDevPhase = 'Beta';
}

View File

@@ -2,7 +2,7 @@ name: cabo_counter
description: "Mobile app for the card game Cabo" description: "Mobile app for the card game Cabo"
publish_to: 'none' publish_to: 'none'
version: 0.4.0+382 version: 0.4.0+467
environment: environment:
sdk: ^3.5.4 sdk: ^3.5.4
@@ -27,6 +27,7 @@ dependencies:
intl: any intl: any
syncfusion_flutter_charts: ^30.1.37 syncfusion_flutter_charts: ^30.1.37
uuid: ^4.5.1 uuid: ^4.5.1
rate_my_app: ^2.3.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: