diff --git a/lib/core/constants.dart b/lib/core/constants.dart index aa453c2..c7f7589 100644 --- a/lib/core/constants.dart +++ b/lib/core/constants.dart @@ -1,19 +1,41 @@ import 'package:rate_my_app/rate_my_app.dart'; +/// A utility class that holds constant values and configuration settings +/// used throughout the application, such as external links, email addresses, +/// and timing parameters for UI elements. +/// +/// This class also provides an instance of [RateMyApp] for managing +/// in-app rating prompts. class Constants { + /// Indicates the current development phase of the app static const String appDevPhase = 'Beta'; + /// Links to various social media profiles and resources related to the app. + /// URL to my Instagram profile static const String kInstagramLink = 'https://instagram.felixkirchner.de'; + + /// URL to my GitHub profile static const String kGithubLink = 'https://github.felixkirchner.de'; + + /// URL to the GitHub issues page for reporting bugs or requesting features. static const String kGithubIssuesLink = 'https://cabo-counter-issues.felixkirchner.de'; + + /// URL to the GitHub wiki for additional documentation and guides. static const String kGithubWikiLink = 'https://cabo-counter-wiki.felixkirchner.de'; + + /// Official email address for user inquiries and support. static const String kEmail = 'cabocounter@felixkirchner.de'; + + /// URL to the app's privacy policy page. static const String kPrivacyPolicyLink = 'https://cabo-counter-datenschutz.felixkirchner.de'; - static const String kImprintLink = 'https://impressum.felixkirchner.de'; + /// URL to the app's imprint page, containing legal information. + static const String kLegalLink = 'https://impressum.felixkirchner.de'; + + /// Instance of [RateMyApp] configured to prompt users for app store ratings. static RateMyApp rateMyApp = RateMyApp( appStoreIdentifier: '6747105718', minDays: 15, diff --git a/lib/core/custom_theme.dart b/lib/core/custom_theme.dart index 74b735f..62d0afb 100644 --- a/lib/core/custom_theme.dart +++ b/lib/core/custom_theme.dart @@ -1,13 +1,29 @@ import 'package:flutter/cupertino.dart'; class CustomTheme { + /// Main Theme of the App + /// Primary white color mainly used for text static Color white = CupertinoColors.white; + + /// Red color, typically used for destructive actions or error states static Color red = CupertinoColors.destructiveRed; + + /// Primary color of the app, used for buttons, highlights, and interactive elements static Color primaryColor = CupertinoColors.systemGreen; + + /// Background color for the main app scaffold and views static Color backgroundColor = const Color(0xFF101010); + + /// Background color for main UI elements like cards or containers. static Color mainElementBackgroundColor = CupertinoColors.darkBackgroundGray; + + /// Background color for player tiles in lists. static Color playerTileColor = CupertinoColors.secondaryLabel; + + /// Background color for buttons and interactive controls. static Color buttonBackgroundColor = const Color(0xFF202020); + + /// Color used to highlight the kamikaze button and players static Color kamikazeColor = CupertinoColors.systemYellow; // Line Colors for GraphView @@ -18,25 +34,33 @@ class CustomTheme { static final Color graphColor5 = primaryColor; // Colors for PointsView + /// Color used to indicate a loss of points in the UI. static Color pointLossColor = primaryColor; + + /// Color used to indicate a gain of points in the UI. static const Color pointGainColor = Color(0xFFF44336); + // Text Styles + /// Text style for mode titles, typically used in headers or section titles. static TextStyle modeTitle = TextStyle( color: primaryColor, fontSize: 20, fontWeight: FontWeight.bold, ); + /// Default text style for mode descriptions. static const TextStyle modeDescription = TextStyle( fontSize: 16, ); + /// Text style for titles of sections of [CupertinoListTile]. static TextStyle rowTitle = TextStyle( fontSize: 20, color: primaryColor, fontWeight: FontWeight.bold, ); + /// Text style for round titles, used for prominent display of the round title static TextStyle roundTitle = TextStyle( fontSize: 60, color: white, diff --git a/lib/data/game_session.dart b/lib/data/game_session.dart index ddc3104..c0a3347 100644 --- a/lib/data/game_session.dart +++ b/lib/data/game_session.dart @@ -77,17 +77,6 @@ class GameSession extends ChangeNotifier { roundList = (json['roundList'] as List).map((e) => Round.fromJson(e)).toList(); - /// Returns the length of the longest player name. - int getMaxLengthOfPlayerNames() { - int length = 0; - for (String player in players) { - if (player.length >= length) { - length = player.length; - } - } - return length; - } - /// Assigns 50 points to all players except the kamikaze player. /// [kamikazePlayerIndex] is the index of the kamikaze player. void applyKamikaze(int roundNum, int kamikazePlayerIndex) { @@ -242,8 +231,8 @@ class GameSession extends ChangeNotifier { /// It then checks if any player has exceeded 100 points. If so, it sets /// isGameFinished to true and calls the _setWinner() method to determine /// the winner. - /// It returns a list of players indices who reached 100 points in the current - /// round for the [RoundView] to show a popup + /// It returns a list of players indices who reached 100 points (bonus player) + /// in the current round for the [RoundView] to show a popup List updatePoints() { List bonusPlayers = []; _sumPoints(); diff --git a/lib/main.dart b/lib/main.dart index 8b45434..adaffdb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,8 +9,11 @@ import 'package:flutter/services.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); + // Ensure the app runs in portrait mode only await SystemChrome.setPreferredOrientations( [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); + + // Initialize services await ConfigService.initConfig(); await VersionService.init(); runApp(const App()); @@ -38,6 +41,9 @@ class _AppState extends State with WidgetsBindingObserver { } @override + + /// Every time the app goes into the background or is closed, + /// save the current game sessions to local storage. void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.paused || state == AppLifecycleState.detached) { diff --git a/lib/presentation/views/about/about_view.dart b/lib/presentation/views/about/about_view.dart index 25431ec..5ed90c6 100644 --- a/lib/presentation/views/about/about_view.dart +++ b/lib/presentation/views/about/about_view.dart @@ -56,7 +56,7 @@ class AboutView extends StatelessWidget { sizeStyle: CupertinoButtonSize.medium, padding: EdgeInsets.zero, child: Text(AppLocalizations.of(context).imprint), - onPressed: () => launchUrl(Uri.parse(Constants.kImprintLink)), + onPressed: () => launchUrl(Uri.parse(Constants.kLegalLink)), ), CupertinoButton( sizeStyle: CupertinoButtonSize.medium, diff --git a/lib/presentation/views/about/licenses/license_detail_view.dart b/lib/presentation/views/about/licenses/license_detail_view.dart index 787c686..16b75e3 100644 --- a/lib/presentation/views/about/licenses/license_detail_view.dart +++ b/lib/presentation/views/about/licenses/license_detail_view.dart @@ -3,8 +3,13 @@ import 'package:cabo_counter/l10n/generated/app_localizations.dart' show AppLocalizations; import 'package:flutter/cupertino.dart'; -/// A view that displays the details of a specific open source software license. -/// It shows the title and the full license text in a scrollable view. +/// Displays the details of a specific open source software license in a Cupertino-style view. +/// +/// This view presents the license title and its full text in a scrollable layout. +/// +/// Required parameters: +/// - [title]: The name of the license. +/// - [license]: The full license text to display. class LicenseDetailView extends StatelessWidget { final String title, license; const LicenseDetailView( diff --git a/lib/presentation/views/about/licenses/license_view.dart b/lib/presentation/views/about/licenses/license_view.dart index 0b54f76..6fecbde 100644 --- a/lib/presentation/views/about/licenses/license_view.dart +++ b/lib/presentation/views/about/licenses/license_view.dart @@ -6,8 +6,14 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; -/// A view that displays a list of the open source software licenses used in the app. -/// It allows users to tap on a license to view its details in a separate screen. +/// Displays a list of open source software licenses used in the app. +/// +/// Users can tap on a license to view its details on a separate screen. +/// This view uses a Cupertino design and supports localization. +/// +/// See also: +/// - [LicenseDetailView] for displaying license details. +/// - [ossLicenses] for the list of licenses. class LicenseView extends StatelessWidget { const LicenseView({super.key}); diff --git a/lib/presentation/views/home/active_game/active_game_view.dart b/lib/presentation/views/home/active_game/active_game_view.dart index 07209f8..3a4c0e5 100644 --- a/lib/presentation/views/home/active_game/active_game_view.dart +++ b/lib/presentation/views/home/active_game/active_game_view.dart @@ -15,6 +15,13 @@ import 'package:confetti/confetti.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +/// Displays the active game view, showing game details, player rankings, rounds, and statistics. +/// +/// This view allows users to interact with an ongoing game session, including viewing player scores, +/// navigating through rounds, ending or deleting the game, exporting game data, and starting a new game +/// with the same settings. It also provides visual feedback such as confetti animation when the game ends. +/// +/// The widget listens to changes in the provided [GameSession] and updates the UI accordingly. class ActiveGameView extends StatefulWidget { final GameSession gameSession; const ActiveGameView({super.key, required this.gameSession}); diff --git a/lib/presentation/views/home/active_game/graph_view.dart b/lib/presentation/views/home/active_game/graph_view.dart index f23be15..b7251d6 100644 --- a/lib/presentation/views/home/active_game/graph_view.dart +++ b/lib/presentation/views/home/active_game/graph_view.dart @@ -4,6 +4,11 @@ import 'package:cabo_counter/l10n/generated/app_localizations.dart'; import 'package:flutter/cupertino.dart'; import 'package:syncfusion_flutter_charts/charts.dart'; +/// A widget that displays the cumulative scoring history of a game session as a line graph. +/// +/// The [GraphView] visualizes the progression of each player's score over multiple rounds +/// using a line chart. It supports dynamic coloring for each player, axis formatting, +/// and handles cases where insufficient data is available to render the graph. class GraphView extends StatefulWidget { final GameSession gameSession; diff --git a/lib/presentation/views/home/active_game/mode_selection_view.dart b/lib/presentation/views/home/active_game/mode_selection_view.dart index 86db5ff..34777cb 100644 --- a/lib/presentation/views/home/active_game/mode_selection_view.dart +++ b/lib/presentation/views/home/active_game/mode_selection_view.dart @@ -8,6 +8,12 @@ enum GameMode { unlimited, } +/// A stateless widget that displays a menu for selecting the game mode. +/// +/// The [ModeSelectionMenu] allows the user to choose between different game modes: +/// - Point limit mode with a specified [pointLimit] +/// - Unlimited mode +/// - Optionally, no default mode if [showDeselection] is true class ModeSelectionMenu extends StatelessWidget { final int pointLimit; final bool showDeselection; diff --git a/lib/presentation/views/home/active_game/points_view.dart b/lib/presentation/views/home/active_game/points_view.dart index 7844872..eb541e8 100644 --- a/lib/presentation/views/home/active_game/points_view.dart +++ b/lib/presentation/views/home/active_game/points_view.dart @@ -4,6 +4,13 @@ import 'package:cabo_counter/l10n/generated/app_localizations.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +/// Displays an overview of points for each player and round in the current game session. +/// +/// The [PointsView] widget shows a table with all rounds and player scores, +/// including score updates and highlights for players who said "Cabo". +/// It uses a Cupertino-style layout and adapts to the number of players. +/// +/// Requires a [GameSession] to provide player and round data. class PointsView extends StatefulWidget { final GameSession gameSession; diff --git a/lib/presentation/views/home/active_game/round_view.dart b/lib/presentation/views/home/active_game/round_view.dart index 14b1f94..35b85d6 100644 --- a/lib/presentation/views/home/active_game/round_view.dart +++ b/lib/presentation/views/home/active_game/round_view.dart @@ -8,6 +8,19 @@ import 'package:flutter/services.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +/// A view for displaying and managing a single round +/// +/// This widget allows users to input and review scores for each player in a round, +/// select the player who called CABO, and handle special cases such as Kamikaze rounds. +/// It manages the round state, validates input, and coordinates navigation between rounds. +/// +/// Features: +/// - Rotates player order based on the previous round's winner. +/// - Supports Kamikaze rounds with dedicated UI and logic. +/// - Handles score input, validation, and updates to the game session. +/// - Displays bonus point popups when applicable. +/// +/// Requires a [GameSession] and the current [roundNumber]. class RoundView extends StatefulWidget { final GameSession gameSession; final int roundNumber; diff --git a/lib/presentation/views/home/create_game_view.dart b/lib/presentation/views/home/create_game_view.dart index 2a3f3d8..f181721 100644 --- a/lib/presentation/views/home/create_game_view.dart +++ b/lib/presentation/views/home/create_game_view.dart @@ -20,6 +20,12 @@ enum CreateStatus { noPlayerName, } +/// A view for creating a new game session in the Cabo Counter app. +/// +/// The [CreateGameView] allows users to input a game title, select a game mode, +/// add and reorder player names, and validate all required fields before +/// starting a new game. It provides feedback dialogs for missing or invalid +/// input and navigates to the active game view upon successful creation. class CreateGameView extends StatefulWidget { final GameMode gameMode; final String? gameTitle; diff --git a/lib/presentation/views/home/main_menu_view.dart b/lib/presentation/views/home/main_menu_view.dart index 3363511..240783a 100644 --- a/lib/presentation/views/home/main_menu_view.dart +++ b/lib/presentation/views/home/main_menu_view.dart @@ -16,6 +16,11 @@ enum PreRatingDialogDecision { yes, no, cancel } enum BadRatingDialogDecision { email, cancel } +/// Home screen of the app that displays a list of game sessions. +/// +/// The [MainMenuView] is the main entry point for the app's home screen. +/// It displays a list of existing game sessions, allows users to create new games, +/// access settings, and handles user feedback dialogs for app rating and support. class MainMenuView extends StatefulWidget { const MainMenuView({super.key}); diff --git a/lib/presentation/views/home/settings_view.dart b/lib/presentation/views/home/settings_view.dart index d8e8d6c..802663c 100644 --- a/lib/presentation/views/home/settings_view.dart +++ b/lib/presentation/views/home/settings_view.dart @@ -11,6 +11,10 @@ import 'package:flutter/cupertino.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:url_launcher/url_launcher.dart'; +/// Settings and information page for the app. +/// +/// [SettingsView] is a settings page for the app, allowing users to configure game options, +/// manage game data (import, export, delete), and view app information. class SettingsView extends StatefulWidget { const SettingsView({super.key}); diff --git a/lib/presentation/views/tab_view.dart b/lib/presentation/views/tab_view.dart index e8469f7..d24e411 100644 --- a/lib/presentation/views/tab_view.dart +++ b/lib/presentation/views/tab_view.dart @@ -4,7 +4,15 @@ import 'package:cabo_counter/presentation/views/about/about_view.dart'; import 'package:cabo_counter/presentation/views/home/main_menu_view.dart'; import 'package:flutter/cupertino.dart'; -/// A view that provides a tabbed interface for navigating between the main menu and the about section. +/// TabBar for navigating between the main menu and about section. +/// +/// [TabView] is a [StatefulWidget] that provides a tabbed interface for navigating +/// between the main menu and the about section of the app. It uses a +/// [CupertinoTabScaffold] with two tabs: +/// - Home (MainMenuView) +/// - About (AboutView) +/// +/// The tab labels are provided via localization. class TabView extends StatefulWidget { const TabView({super.key}); diff --git a/lib/presentation/widgets/custom_button.dart b/lib/presentation/widgets/custom_button.dart index 0feb799..2b68b44 100644 --- a/lib/presentation/widgets/custom_button.dart +++ b/lib/presentation/widgets/custom_button.dart @@ -1,6 +1,10 @@ import 'package:cabo_counter/core/custom_theme.dart'; import 'package:flutter/cupertino.dart'; +/// A customizable button widget using Cupertino style. +/// +/// Displays a button with a child widget and optional callback. +/// The button uses a medium size, rounded corners, and a custom background color. class CustomButton extends StatelessWidget { final Widget child; final VoidCallback? onPressed; diff --git a/lib/presentation/widgets/custom_form_row.dart b/lib/presentation/widgets/custom_form_row.dart index 7a2526b..751e487 100644 --- a/lib/presentation/widgets/custom_form_row.dart +++ b/lib/presentation/widgets/custom_form_row.dart @@ -2,6 +2,11 @@ import 'package:cabo_counter/core/custom_theme.dart'; import 'package:cabo_counter/presentation/widgets/custom_stepper.dart'; import 'package:flutter/cupertino.dart'; +/// A customizable form row widget with a prefix icon, text, and optional suffix widget. +/// +/// Displays a row with an icon and text on the left side. +/// Optionally, a suffix widget (e.g. a stepper) can be shown on the right side. +/// The row is styled as a [CupertinoButton] and can react to taps. class CustomFormRow extends StatefulWidget { final String prefixText; final IconData prefixIcon; diff --git a/lib/presentation/widgets/custom_stepper.dart b/lib/presentation/widgets/custom_stepper.dart index a05a4cb..be41048 100644 --- a/lib/presentation/widgets/custom_stepper.dart +++ b/lib/presentation/widgets/custom_stepper.dart @@ -1,6 +1,17 @@ import 'package:cabo_counter/core/custom_theme.dart'; import 'package:flutter/cupertino.dart'; // Für iOS-Style +/// A custom stepper widget for incrementing and decrementing a value. +/// +/// The [CustomStepper] widget allows increasing and decreasing a value +/// within a defined range ([minValue] to [maxValue]) in fixed steps. +/// +/// Properties: +/// - [minValue]: The minimum value. +/// - [maxValue]: The maximum value. +/// - [initialValue]: The initial value (optional, defaults to [minValue]). +/// - [step]: The step size. +/// - [onChanged]: Callback triggered when the value changes. class CustomStepper extends StatefulWidget { final int minValue; final int maxValue; diff --git a/lib/services/config_service.dart b/lib/services/config_service.dart index 4ff68d9..419e461 100644 --- a/lib/services/config_service.dart +++ b/lib/services/config_service.dart @@ -1,10 +1,12 @@ import 'package:cabo_counter/presentation/views/home/active_game/mode_selection_view.dart'; import 'package:shared_preferences/shared_preferences.dart'; -/// This class handles the configuration settings for the app. -/// It uses SharedPreferences to store and retrieve the personal configuration of the app. -/// Currently it provides methods to initialize, get, and set the point limit and cabo penalty. +/// A service class for managing and persisting app configuration settings using `SharedPreferences`. +/// +/// Provides methods to initialize, retrieve, update, and reset configuration values such as point limit, +/// cabo penalty, and game mode. Ensures that user preferences are stored locally and persist across app restarts. class ConfigService { + // Keys for the stored values static const String _keyPointLimit = 'pointLimit'; static const String _keyCaboPenalty = 'caboPenalty'; static const String _keyGameMode = 'gameMode'; diff --git a/lib/services/local_storage_service.dart b/lib/services/local_storage_service.dart index 29ac58b..2c99d36 100644 --- a/lib/services/local_storage_service.dart +++ b/lib/services/local_storage_service.dart @@ -55,6 +55,7 @@ class LocalStorageService { try { final file = await _getFilePath(); + // Check if the file exists if (!await file.exists()) { print( '[local_storage_service.dart] Es existiert noch keine Datei mit Spieldaten'); @@ -65,11 +66,13 @@ class LocalStorageService { '[local_storage_service.dart] Es existiert bereits eine Datei mit Spieldaten'); final jsonString = await file.readAsString(); + // Check if the file is empty if (jsonString.isEmpty) { print('[local_storage_service.dart] Die gefundene Datei ist leer'); return false; } + // Validate the JSON schema if (!await _validateJsonSchema(jsonString, true)) { print( '[local_storage_service.dart] Die Datei konnte nicht validiert werden'); @@ -148,7 +151,6 @@ class LocalStorageService { /// Opens the file picker to import a JSON file and loads the game data from it. static Future importJsonFile() async { final path = await FilePicker.platform.pickFiles( - dialogTitle: 'Wähle eine Datei mit Spieldaten aus', type: FileType.custom, allowedExtensions: ['json'], ); @@ -162,9 +164,8 @@ class LocalStorageService { try { final jsonString = await _readFileContent(path.files.single); + // Checks if the JSON String is in the gameList format if (await _validateJsonSchema(jsonString, true)) { - // Checks if the JSON String is in the gameList format - final jsonData = json.decode(jsonString) as List; List importedList = jsonData .map((jsonItem) => diff --git a/pubspec.yaml b/pubspec.yaml index d1e8929..0264645 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.5.8+670 +version: 0.5.8+671 environment: sdk: ^3.5.4 diff --git a/test/data/game_session_test.dart b/test/data/game_session_test.dart index 9654bad..8e16d2a 100644 --- a/test/data/game_session_test.dart +++ b/test/data/game_session_test.dart @@ -62,10 +62,6 @@ void main() { }); group('Helper Functions', () { - test('getMaxLengthOfPlayerNames', () { - expect(session.getMaxLengthOfPlayerNames(), equals(7)); // Charlie (7) - }); - test('increaseRound', () { expect(session.roundNumber, 1); session.increaseRound();