Merge pull request #154 from flixcoo/feature/129-implement-aboutview-for-stable-version

Implement AboutView for stable version
This commit is contained in:
2025-08-05 14:49:22 +02:00
committed by GitHub
22 changed files with 5764 additions and 127 deletions

View File

@@ -12,6 +12,7 @@ class Constants {
static const String kEmail = 'cabocounter@felixkirchner.de'; static const String kEmail = 'cabocounter@felixkirchner.de';
static const String kPrivacyPolicyLink = static const String kPrivacyPolicyLink =
'https://www.privacypolicies.com/live/1b3759d4-b2f1-4511-8e3b-21bb1626be68'; 'https://www.privacypolicies.com/live/1b3759d4-b2f1-4511-8e3b-21bb1626be68';
static const String kImprintLink = 'https://imprint.felixkirchner.de';
static RateMyApp rateMyApp = RateMyApp( static RateMyApp rateMyApp = RateMyApp(
appStoreIdentifier: '6747105718', appStoreIdentifier: '6747105718',

View File

@@ -17,7 +17,12 @@
"back": "Zurück", "back": "Zurück",
"home": "Home", "home": "Home",
"about": "Über", "about": "Über",
"licenses": "Lizenzen",
"license_details": "Lizenzdetails",
"no_license_text": "Keine Lizenz verfügbar",
"imprint": "Impressum",
"empty_text_1": "Ganz schön leer hier...", "empty_text_1": "Ganz schön leer hier...",
"empty_text_2": "Füge über den Button oben rechts eine neue Runde hinzu", "empty_text_2": "Füge über den Button oben rechts eine neue Runde hinzu",
@@ -155,8 +160,6 @@
"wiki": "Wiki", "wiki": "Wiki",
"app_version": "App-Version", "app_version": "App-Version",
"privacy_policy": "Datenschutzerklärung", "privacy_policy": "Datenschutzerklärung",
"build": "Build-Nr.",
"loading": "Lädt...", "loading": "Lädt...",
"build": "Build-Nr."
"about_text": "Hey :) Danke, dass du als eine:r der ersten User meiner ersten eigenen App dabei bist! Ich hab sehr viel Arbeit in dieses Projekt gesteckt und auch, wenn ich (hoffentlich) an vieles Gedacht hab, wird auf jeden Fall noch nicht alles 100% funktionieren. Solltest du also irgendwelche Fehler entdecken oder Feedback zum Design oder der Benutzerfreundlichkeit haben, teile Sie mir gern über die Testflight App oder auf den dir bekannten Wegen mit. Danke! "
} }

View File

@@ -17,7 +17,12 @@
"back": "Back", "back": "Back",
"home": "Home", "home": "Home",
"about": "About", "about": "About",
"licenses": "Licenses",
"license_details": "License Details",
"imprint": "Imprint",
"no_license_text": "No license available",
"empty_text_1": "Pretty empty here...", "empty_text_1": "Pretty empty here...",
"empty_text_2": "Create a new game using the button in the top right.", "empty_text_2": "Create a new game using the button in the top right.",
@@ -156,7 +161,5 @@
"app_version": "App Version", "app_version": "App Version",
"privacy_policy": "Privacy Policy", "privacy_policy": "Privacy Policy",
"loading": "Loading...", "loading": "Loading...",
"build": "Build No.", "build": "Build No."
"about_text": "Hey :) Thanks for being one of the first users of my app! Ive put a lot of work into this project, and even though I tried to think of everything, it might not work perfectly just yet. So if you discover any bugs or have feedback on the design or usability, please let me know via the TestFlight app or by sending me a message or email. Thank you very much!"
} }

View File

@@ -194,6 +194,30 @@ abstract class AppLocalizations {
/// **'Über'** /// **'Über'**
String get about; String get about;
/// No description provided for @licenses.
///
/// In de, this message translates to:
/// **'Lizenzen'**
String get licenses;
/// No description provided for @license_details.
///
/// In de, this message translates to:
/// **'Lizenzdetails'**
String get license_details;
/// No description provided for @no_license_text.
///
/// In de, this message translates to:
/// **'Keine Lizenz verfügbar'**
String get no_license_text;
/// No description provided for @imprint.
///
/// In de, this message translates to:
/// **'Impressum'**
String get imprint;
/// No description provided for @empty_text_1. /// No description provided for @empty_text_1.
/// ///
/// In de, this message translates to: /// In de, this message translates to:
@@ -699,23 +723,17 @@ abstract class AppLocalizations {
/// **'Datenschutzerklärung'** /// **'Datenschutzerklärung'**
String get privacy_policy; String get privacy_policy;
/// No description provided for @build.
///
/// In de, this message translates to:
/// **'Build-Nr.'**
String get build;
/// No description provided for @loading. /// No description provided for @loading.
/// ///
/// In de, this message translates to: /// In de, this message translates to:
/// **'Lädt...'** /// **'Lädt...'**
String get loading; String get loading;
/// No description provided for @about_text. /// No description provided for @build.
/// ///
/// In de, this message translates to: /// In de, this message translates to:
/// **'Hey :) Danke, dass du als eine:r der ersten User meiner ersten eigenen App dabei bist! Ich hab sehr viel Arbeit in dieses Projekt gesteckt und auch, wenn ich (hoffentlich) an vieles Gedacht hab, wird auf jeden Fall noch nicht alles 100% funktionieren. Solltest du also irgendwelche Fehler entdecken oder Feedback zum Design oder der Benutzerfreundlichkeit haben, teile Sie mir gern über die Testflight App oder auf den dir bekannten Wegen mit. Danke! '** /// **'Build-Nr.'**
String get about_text; String get build;
} }
class _AppLocalizationsDelegate class _AppLocalizationsDelegate

View File

@@ -56,6 +56,18 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get about => 'Über'; String get about => 'Über';
@override
String get licenses => 'Lizenzen';
@override
String get license_details => 'Lizenzdetails';
@override
String get no_license_text => 'Keine Lizenz verfügbar';
@override
String get imprint => 'Impressum';
@override @override
String get empty_text_1 => 'Ganz schön leer hier...'; String get empty_text_1 => 'Ganz schön leer hier...';
@@ -347,13 +359,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get privacy_policy => 'Datenschutzerklärung'; String get privacy_policy => 'Datenschutzerklärung';
@override
String get build => 'Build-Nr.';
@override @override
String get loading => 'Lädt...'; String get loading => 'Lädt...';
@override @override
String get about_text => String get build => 'Build-Nr.';
'Hey :) Danke, dass du als eine:r der ersten User meiner ersten eigenen App dabei bist! Ich hab sehr viel Arbeit in dieses Projekt gesteckt und auch, wenn ich (hoffentlich) an vieles Gedacht hab, wird auf jeden Fall noch nicht alles 100% funktionieren. Solltest du also irgendwelche Fehler entdecken oder Feedback zum Design oder der Benutzerfreundlichkeit haben, teile Sie mir gern über die Testflight App oder auf den dir bekannten Wegen mit. Danke! ';
} }

View File

@@ -56,6 +56,18 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get about => 'About'; String get about => 'About';
@override
String get licenses => 'Licenses';
@override
String get license_details => 'License Details';
@override
String get no_license_text => 'No license available';
@override
String get imprint => 'Imprint';
@override @override
String get empty_text_1 => 'Pretty empty here...'; String get empty_text_1 => 'Pretty empty here...';
@@ -344,13 +356,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get privacy_policy => 'Privacy Policy'; String get privacy_policy => 'Privacy Policy';
@override
String get build => 'Build No.';
@override @override
String get loading => 'Loading...'; String get loading => 'Loading...';
@override @override
String get about_text => String get build => 'Build No.';
'Hey :) Thanks for being one of the first users of my app! Ive put a lot of work into this project, and even though I tried to think of everything, it might not work perfectly just yet. So if you discover any bugs or have feedback on the design or usability, please let me know via the TestFlight app or by sending me a message or email. Thank you very much!';
} }

View File

@@ -0,0 +1,95 @@
import 'package:cabo_counter/core/constants.dart';
import 'package:cabo_counter/l10n/generated/app_localizations.dart';
import 'package:cabo_counter/presentation/views/about/licenses/license_view.dart';
import 'package:cabo_counter/services/version_service.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:url_launcher/url_launcher.dart';
/// A view that displays information about the app, including its name, version,
/// privacy policy, imprint, and licenses.
class AboutView extends StatelessWidget {
const AboutView({super.key});
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
resizeToAvoidBottomInset: false,
navigationBar: CupertinoNavigationBar(
middle: Text(AppLocalizations.of(context).about),
),
child: SafeArea(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(0, 10, 0, 0),
child: Text(
AppLocalizations.of(context).app_name,
style: const TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
),
Text(
'${AppLocalizations.of(context).app_version} ${VersionService.getVersionWithBuild()}',
style: TextStyle(fontSize: 15, color: Colors.grey[300]),
),
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
child: SizedBox(
height: 200,
child: Image.asset('assets/cabo_counter-logo_rounded.png'),
)),
CupertinoButton(
sizeStyle: CupertinoButtonSize.medium,
padding: EdgeInsets.zero,
child: Text(AppLocalizations.of(context).privacy_policy),
onPressed: () =>
launchUrl(Uri.parse(Constants.kPrivacyPolicyLink)),
),
CupertinoButton(
sizeStyle: CupertinoButtonSize.medium,
padding: EdgeInsets.zero,
child: Text(AppLocalizations.of(context).imprint),
onPressed: () => launchUrl(Uri.parse(Constants.kImprintLink)),
),
CupertinoButton(
sizeStyle: CupertinoButtonSize.medium,
padding: EdgeInsets.zero,
child: Text(AppLocalizations.of(context).licenses),
onPressed: () => Navigator.push(context,
CupertinoPageRoute(builder: (_) => const LicenseView()))),
const SizedBox(
height: 10,
),
const Text(
'\u00A9 Felix Kirchner',
style: TextStyle(fontSize: 16),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
onPressed: () =>
launchUrl(Uri.parse(Constants.kInstagramLink)),
icon: const Icon(FontAwesomeIcons.instagram)),
IconButton(
onPressed: () =>
launchUrl(Uri.parse('mailto:${Constants.kEmail}')),
icon: const Icon(CupertinoIcons.envelope)),
IconButton(
onPressed: () =>
launchUrl(Uri.parse(Constants.kGithubLink)),
icon: const Icon(FontAwesomeIcons.github)),
],
),
],
),
)));
}
}

View File

@@ -0,0 +1,59 @@
import 'package:cabo_counter/core/custom_theme.dart';
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.
class LicenseDetailView extends StatelessWidget {
final String title, license;
const LicenseDetailView(
{super.key, required this.title, required this.license});
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(
AppLocalizations.of(context).license_details,
),
),
child: SafeArea(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Column(
children: [
Padding(
padding:
const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
child: FittedBox(
fit: BoxFit.fill,
child: Text(
title,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
),
Container(
margin: const EdgeInsets.all(8),
padding:
const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
decoration: BoxDecoration(
color: CustomTheme.buttonBackgroundColor,
borderRadius: BorderRadius.circular(16)),
child: Text(
license,
style: const TextStyle(fontSize: 15),
),
)
],
),
),
),
);
}
}

View File

@@ -0,0 +1,60 @@
import 'package:cabo_counter/core/custom_theme.dart';
import 'package:cabo_counter/l10n/generated/app_localizations.dart';
import 'package:cabo_counter/presentation/views/about/licenses/license_detail_view.dart';
import 'package:cabo_counter/presentation/views/about/licenses/oss_licenses.dart';
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.
class LicenseView extends StatelessWidget {
const LicenseView({super.key});
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(AppLocalizations.of(context).licenses),
),
child: SafeArea(
child: ListView.builder(
physics: const BouncingScrollPhysics(),
itemCount: ossLicenses.length,
itemBuilder: (_, index) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 4),
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(8),
),
child: CupertinoListTile(
backgroundColor: CustomTheme.backgroundColor,
onTap: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (_) => LicenseDetailView(
title: ossLicenses[index].name,
license: ossLicenses[index].license ??
AppLocalizations.of(context).no_license_text,
),
),
);
},
trailing: const CupertinoListTileChevron(),
title: Text(
ossLicenses[index].name,
style: GoogleFonts.roboto(),
),
subtitle: Text(ossLicenses[index].description),
),
),
);
},
),
),
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,78 +0,0 @@
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/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:url_launcher/url_launcher.dart';
class AboutView extends StatelessWidget {
const AboutView({super.key});
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
resizeToAvoidBottomInset: false,
navigationBar: CupertinoNavigationBar(
middle: Text(AppLocalizations.of(context).about),
),
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(0, 10, 0, 0),
child: Text(
AppLocalizations.of(context).app_name,
style: const TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
),
Text(
'${AppLocalizations.of(context).app_version} ${VersionService.getVersionWithBuild()}',
style: TextStyle(fontSize: 15, color: Colors.grey[300]),
),
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
child: SizedBox(
height: 200,
child: Image.asset('assets/cabo_counter-logo_rounded.png'),
)),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 30),
child: Text(
AppLocalizations.of(context).about_text,
textAlign: TextAlign.center,
softWrap: true,
)),
const SizedBox(
height: 30,
),
const Text(
'\u00A9 Felix Kirchner',
style: TextStyle(fontSize: 16),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
onPressed: () =>
launchUrl(Uri.parse(Constants.kInstagramLink)),
icon: const Icon(FontAwesomeIcons.instagram)),
IconButton(
onPressed: () =>
launchUrl(Uri.parse('mailto:${Constants.kEmail}')),
icon: const Icon(CupertinoIcons.envelope)),
IconButton(
onPressed: () =>
launchUrl(Uri.parse(Constants.kGithubLink)),
icon: const Icon(FontAwesomeIcons.github)),
],
),
],
)));
}
}

View File

@@ -3,11 +3,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/generated/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/home/active_game/graph_view.dart';
import 'package:cabo_counter/presentation/views/graph_view.dart'; import 'package:cabo_counter/presentation/views/home/active_game/mode_selection_view.dart';
import 'package:cabo_counter/presentation/views/mode_selection_view.dart'; import 'package:cabo_counter/presentation/views/home/active_game/points_view.dart';
import 'package:cabo_counter/presentation/views/points_view.dart'; import 'package:cabo_counter/presentation/views/home/active_game/round_view.dart';
import 'package:cabo_counter/presentation/views/round_view.dart'; import 'package:cabo_counter/presentation/views/home/create_game_view.dart';
import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:confetti/confetti.dart'; import 'package:confetti/confetti.dart';

View File

@@ -23,9 +23,9 @@ class _PointsViewState extends State<PointsView> {
), ),
child: SafeArea(child: LayoutBuilder(builder: (context, constraints) { child: SafeArea(child: LayoutBuilder(builder: (context, constraints) {
const double caboFieldWidthFactor = 0.2; const double caboFieldWidthFactor = 0.2;
const double roundColWidth = 35;
const double tablePadding = 8; const double tablePadding = 8;
final int playerCount = widget.gameSession.players.length; final int playerCount = widget.gameSession.players.length;
const double roundColWidth = 35;
final double playerColWidth = final double playerColWidth =
(constraints.maxWidth - roundColWidth - (tablePadding)) / (constraints.maxWidth - roundColWidth - (tablePadding)) /
playerCount; playerCount;

View File

@@ -3,8 +3,8 @@ 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/generated/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/home/active_game/active_game_view.dart';
import 'package:cabo_counter/presentation/views/mode_selection_view.dart'; import 'package:cabo_counter/presentation/views/home/active_game/mode_selection_view.dart';
import 'package:cabo_counter/presentation/widgets/custom_button.dart'; import 'package:cabo_counter/presentation/widgets/custom_button.dart';
import 'package:cabo_counter/services/config_service.dart'; import 'package:cabo_counter/services/config_service.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';

View File

@@ -3,9 +3,9 @@ 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/generated/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/home/active_game/active_game_view.dart';
import 'package:cabo_counter/presentation/views/create_game_view.dart'; import 'package:cabo_counter/presentation/views/home/create_game_view.dart';
import 'package:cabo_counter/presentation/views/settings_view.dart'; import 'package:cabo_counter/presentation/views/home/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:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';

View File

@@ -1,7 +1,7 @@
import 'package:cabo_counter/core/constants.dart'; import 'package:cabo_counter/core/constants.dart';
import 'package:cabo_counter/core/custom_theme.dart'; import 'package:cabo_counter/core/custom_theme.dart';
import 'package:cabo_counter/l10n/generated/app_localizations.dart'; import 'package:cabo_counter/l10n/generated/app_localizations.dart';
import 'package:cabo_counter/presentation/views/mode_selection_view.dart'; import 'package:cabo_counter/presentation/views/home/active_game/mode_selection_view.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/custom_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';
@@ -187,13 +187,6 @@ class _SettingsViewState extends State<SettingsView> {
launchUrl(Uri.parse(Constants.kGithubWikiLink)), launchUrl(Uri.parse(Constants.kGithubWikiLink)),
suffixWidget: const CupertinoListTileChevron(), suffixWidget: const CupertinoListTileChevron(),
), ),
CustomFormRow(
prefixText: AppLocalizations.of(context).privacy_policy,
prefixIcon: CupertinoIcons.doc_append,
onPressed: () =>
launchUrl(Uri.parse(Constants.kPrivacyPolicyLink)),
suffixWidget: const CupertinoListTileChevron(),
),
CustomFormRow( CustomFormRow(
prefixText: AppLocalizations.of(context).error_found, prefixText: AppLocalizations.of(context).error_found,
prefixIcon: FontAwesomeIcons.github, prefixIcon: FontAwesomeIcons.github,

View File

@@ -1,9 +1,10 @@
import 'package:cabo_counter/core/custom_theme.dart'; import 'package:cabo_counter/core/custom_theme.dart';
import 'package:cabo_counter/l10n/generated/app_localizations.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/about/about_view.dart';
import 'package:cabo_counter/presentation/views/main_menu_view.dart'; import 'package:cabo_counter/presentation/views/home/main_menu_view.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
/// A view that provides a tabbed interface for navigating between the main menu and the about section.
class TabView extends StatefulWidget { class TabView extends StatefulWidget {
const TabView({super.key}); const TabView({super.key});

View File

@@ -1,4 +1,4 @@
import 'package:cabo_counter/presentation/views/mode_selection_view.dart'; import 'package:cabo_counter/presentation/views/home/active_game/mode_selection_view.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
/// This class handles the configuration settings for the app. /// This class handles the configuration settings for the app.

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.5.5+639 version: 0.5.6+661
environment: environment:
sdk: ^3.5.4 sdk: ^3.5.4
@@ -31,6 +31,8 @@ dependencies:
reorderables: ^0.4.2 reorderables: ^0.4.2
collection: ^1.18.0 collection: ^1.18.0
confetti: ^0.6.0 confetti: ^0.6.0
flutter_oss_licenses: ^2.0.1
google_fonts: ^6.3.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: