Beta-Version 0.4.4 (#105)
* Update README.md * Tried new design for im- and export-button * Moved views to presentation folder * Moved widgets to presentation folder * Implemented CustomRowForm Widget * Used new custom form row * Removed double information * Refactored methods to private * Changed label * Modified paddings and text color * Changed string * Updated CustomFormRow padding and pressed handler * Implemented various new forms of CustomFormRow into SettingsView * Implemented VersionService * Updated strings, added wiki button * Corrected replaced string * Added import dialog feedback (got lost in refactoring) * Corrected function duplication * changed suffixWidget assignment and moved stepperKeys * Changed icons * Added rate_my_app package * Renamed folder * Implement native rating dialog * Implemented logic for pre rating and refactored rating dialog * updated launch mode * Small changes * Updated launch mode * Updated linting rules * Renamed folders * Changed l10n files location * Implemented new link constants * Changed privacy policy link * Corrected wiki link * Removed import * Updated links * Updated links to subdomains * Updated file paths * Updated strings * Updated identifiers * Added break in switch case * Updated strings * Implemented new popup * Corrected links * Changed color * Ensured rating dialog wont show in Beta * Refactoring * Adding const * Renamed variables * Corrected links * updated Dialog function * Added version number in about view * Changed order and corrected return * Changed translation * Changed popups because of unmounted context errors * corrected string typo * Replaced int constants with enums * Renamed Stepper to CustomStepper * Changed argument order * Reordered properties * Implemented empty builder for GraphView * Added jitterStip to prevent the graphs overlaying each other * Removed german comments * Added comment to jitter calculation * Overhauled comments in CustomTheme * Updated version * Added Delete all games button to Settings * Updated version * Updated en string * Updated RoundView buttons when game is finished * Changed lock emoji to CuperinoIcons.lock and placed it in trailing of app bar * Simplified comparison * Updated version * Corrected scaling * Updates constant names and lint rule * HOTFIX: Graph showed wrong data * Graph starts at round 0 now where all players have 0 points * Adjusted jitterStep * Removed dead code * Updated Y-Axis and removed values under y = 0 * Changed overflow mode * Replaced string & if statement with visibility widget * updated accessability of graph view * Changed string for GraphView title * Updated comment Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Updated generated files * Updated version in README --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
366
lib/presentation/views/main_menu_view.dart
Normal file
366
lib/presentation/views/main_menu_view.dart
Normal file
@@ -0,0 +1,366 @@
|
||||
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/l10n/generated/app_localizations.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/settings_view.dart';
|
||||
import 'package:cabo_counter/services/config_service.dart';
|
||||
import 'package:cabo_counter/services/local_storage_service.dart';
|
||||
import 'package:flutter/cupertino.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 {
|
||||
const MainMenuView({super.key});
|
||||
|
||||
@override
|
||||
// ignore: library_private_types_in_public_api
|
||||
_MainMenuViewState createState() => _MainMenuViewState();
|
||||
}
|
||||
|
||||
class _MainMenuViewState extends State<MainMenuView> {
|
||||
bool _isLoading = true;
|
||||
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
LocalStorageService.loadGameSessions().then((_) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
});
|
||||
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() {
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListenableBuilder(
|
||||
listenable: gameManager,
|
||||
builder: (context, _) {
|
||||
return CupertinoPageScaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
navigationBar: CupertinoNavigationBar(
|
||||
leading: IconButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
CupertinoPageRoute(
|
||||
builder: (context) => const SettingsView(),
|
||||
),
|
||||
).then((_) {
|
||||
setState(() {});
|
||||
});
|
||||
},
|
||||
icon: const Icon(CupertinoIcons.settings, size: 30)),
|
||||
middle: const Text('Cabo Counter'),
|
||||
trailing: IconButton(
|
||||
onPressed: () => Navigator.push(
|
||||
context,
|
||||
CupertinoPageRoute(
|
||||
builder: (context) => const CreateGameView(),
|
||||
),
|
||||
),
|
||||
icon: const Icon(CupertinoIcons.add)),
|
||||
),
|
||||
child: CupertinoPageScaffold(
|
||||
child: SafeArea(
|
||||
child: _isLoading
|
||||
? const Center(child: CupertinoActivityIndicator())
|
||||
: gameManager.gameList.isEmpty
|
||||
? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 30),
|
||||
Center(
|
||||
child: GestureDetector(
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
CupertinoPageRoute(
|
||||
builder: (context) =>
|
||||
const CreateGameView(),
|
||||
),
|
||||
),
|
||||
child: Icon(
|
||||
CupertinoIcons.plus,
|
||||
size: 60,
|
||||
color: CustomTheme.primaryColor,
|
||||
),
|
||||
)),
|
||||
const SizedBox(height: 10),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 70),
|
||||
child: Text(
|
||||
'${AppLocalizations.of(context).empty_text_1}\n${AppLocalizations.of(context).empty_text_2}',
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: ListView.builder(
|
||||
itemCount: gameManager.gameList.length,
|
||||
itemBuilder: (context, index) {
|
||||
final session = gameManager.gameList[index];
|
||||
return ListenableBuilder(
|
||||
listenable: session,
|
||||
builder: (context, _) {
|
||||
return Dismissible(
|
||||
key: Key(session.gameTitle),
|
||||
background: Container(
|
||||
color: CupertinoColors.destructiveRed,
|
||||
alignment: Alignment.centerRight,
|
||||
padding:
|
||||
const EdgeInsets.only(right: 20.0),
|
||||
child: const Icon(
|
||||
CupertinoIcons.delete,
|
||||
color: CupertinoColors.white,
|
||||
),
|
||||
),
|
||||
direction: DismissDirection.endToStart,
|
||||
confirmDismiss: (direction) async {
|
||||
final String gameTitle = gameManager
|
||||
.gameList[index].gameTitle;
|
||||
return await _showDeleteGamePopup(
|
||||
context, gameTitle);
|
||||
},
|
||||
onDismissed: (direction) {
|
||||
gameManager
|
||||
.removeGameSessionByIndex(index);
|
||||
},
|
||||
dismissThresholds: const {
|
||||
DismissDirection.startToEnd: 0.6
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 10.0),
|
||||
child: CupertinoListTile(
|
||||
backgroundColorActivated:
|
||||
CustomTheme.backgroundColor,
|
||||
title: Text(session.gameTitle),
|
||||
subtitle:
|
||||
session.isGameFinished == true
|
||||
? Text(
|
||||
'\u{1F947} ${session.winner}',
|
||||
style: const TextStyle(
|
||||
fontSize: 14),
|
||||
)
|
||||
: Text(
|
||||
'${AppLocalizations.of(context).mode}: ${_translateGameMode(session.isPointsLimitEnabled)}',
|
||||
style: const TextStyle(
|
||||
fontSize: 14),
|
||||
),
|
||||
trailing: Row(
|
||||
children: [
|
||||
Text('${session.roundNumber}'),
|
||||
const SizedBox(width: 3),
|
||||
const Icon(CupertinoIcons
|
||||
.arrow_2_circlepath_circle_fill),
|
||||
const SizedBox(width: 15),
|
||||
Text('${session.players.length}'),
|
||||
const SizedBox(width: 3),
|
||||
const Icon(
|
||||
CupertinoIcons.person_2_fill),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
final session =
|
||||
gameManager.gameList[index];
|
||||
Navigator.push(
|
||||
context,
|
||||
CupertinoPageRoute(
|
||||
builder: (context) =>
|
||||
ActiveGameView(
|
||||
gameSession: session),
|
||||
),
|
||||
).then((_) {
|
||||
setState(() {});
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Translates the game mode boolean into the corresponding String.
|
||||
/// If [pointLimit] is true, it returns '101 Punkte', otherwise it returns 'Unbegrenzt'.
|
||||
String _translateGameMode(bool pointLimit) {
|
||||
if (pointLimit) {
|
||||
return '${ConfigService.pointLimit} ${AppLocalizations.of(context).points}';
|
||||
}
|
||||
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.kEmail,
|
||||
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.
|
||||
/// Returns true if the user confirms the deletion, false otherwise.
|
||||
/// [gameTitle] is the title of the game session to be deleted.
|
||||
Future<bool> _showDeleteGamePopup(
|
||||
BuildContext context, String gameTitle) async {
|
||||
return await showCupertinoDialog<bool>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return CupertinoAlertDialog(
|
||||
title: Text(
|
||||
AppLocalizations.of(context).delete_game_title,
|
||||
),
|
||||
content: Text(AppLocalizations.of(context)
|
||||
.delete_game_message(gameTitle)),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
child: Text(AppLocalizations.of(context).cancel),
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
isDestructiveAction: true,
|
||||
isDefaultAction: true,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
child: Text(
|
||||
AppLocalizations.of(context).delete,
|
||||
),
|
||||
)
|
||||
]);
|
||||
},
|
||||
) ??
|
||||
false;
|
||||
}
|
||||
|
||||
/// 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
|
||||
void dispose() {
|
||||
gameManager.removeListener(_updateView);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user