initial commit

This commit is contained in:
michi 2025-06-21 08:41:23 +02:00
commit 1f00a55136
81 changed files with 7240 additions and 0 deletions

31
.github/copilot-instructions.md vendored Normal file
View File

@ -0,0 +1,31 @@
<!-- Use this file to provide workspace-specific custom instructions to Copilot. For more details, visit https://code.visualstudio.com/docs/copilot/copilot-customization#_use-a-githubcopilotinstructionsmd-file -->
# RoadTrip Kids App Instructions
This is a Flutter application for children aged 4-6 years, designed to provide entertainment during car trips. The app is fully speech-controlled and features both offline games and AI-powered premium games.
## App Structure
- `/lib/screens`: Contains the main UI screens of the app
- `/lib/models`: Data models for games, users, and content
- `/lib/services`: Services for speech recognition, text-to-speech, and OpenAI integration
- `/lib/widgets`: Reusable UI components like the animated character
- `/lib/utils`: Utility classes like the app theme
## Key Technologies
- Flutter for cross-platform mobile development
- Speech-to-Text for voice recognition
- Text-to-Speech for audio output
- OpenAI API for dynamic content generation
- Firebase for backend services and authentication
## Design Principles
- Child-friendly interface with large elements and vibrant colors
- Speech-first interaction to minimize need for manual input
- Engaging animated character for visual feedback
- Simple, intuitive navigation designed for children
- Safety features including parental controls
When making changes to the code, ensure they align with these design principles and maintain the child-friendly nature of the app.

162
.gitignore vendored Normal file
View File

@ -0,0 +1,162 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
# iOS related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/ephemeral/
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# macOS related
**/macos/Flutter/GeneratedPluginRegistrant.swift
# Environment files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Firebase related (if using Firebase)
firebase_options.dart
google-services.json
GoogleService-Info.plist
# API keys and sensitive configuration
lib/config/api_keys.dart
lib/config/secrets.dart
lib/config/openai_config.dart
# Build outputs
*.apk
*.ipa
*.aab
# Temporary files
*.tmp
*.temp
# Windows specific
Thumbs.db
ehthumbs.db
Desktop.ini
# Generated files
*.g.dart
*.freezed.dart
*.chopper.dart
*.gr.dart
# Asset files that might be generated
assets/fonts/generated/
assets/images/generated/
# Test related
test/coverage/
coverage/
lcov.info
# Documentation generated files
doc/api/
# Platform specific directories
linux/flutter/generated_plugin_registrant.cc
linux/flutter/generated_plugin_registrant.h
linux/flutter/generated_plugins.cmake
windows/flutter/generated_plugin_registrant.cc
windows/flutter/generated_plugin_registrant.h
windows/flutter/generated_plugins.cmake
web/flutter_service_worker.js
web/manifest.json
web/version.json
web/assets/AssetManifest.json
web/assets/FontManifest.json
# FVM (Flutter Version Management) files
.fvm/flutter_sdk
# Other
.flutter-devtools/
.metadata
# Speech and audio cache files
*.wav
*.mp3.tmp
audio_cache/
# RoadTrip Kids specific files
# Exclude user data and analytics
user_data/
analytics/
crash_reports/
# Exclude OpenAI API keys in any config files
*openai*key*
*api*key*

33
.metadata Normal file
View File

@ -0,0 +1,33 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "6fba2447e95c451518584c35e25f5433f14d888c"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 6fba2447e95c451518584c35e25f5433f14d888c
base_revision: 6fba2447e95c451518584c35e25f5433f14d888c
- platform: android
create_revision: 6fba2447e95c451518584c35e25f5433f14d888c
base_revision: 6fba2447e95c451518584c35e25f5433f14d888c
- platform: ios
create_revision: 6fba2447e95c451518584c35e25f5433f14d888c
base_revision: 6fba2447e95c451518584c35e25f5433f14d888c
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

60
README.md Normal file
View File

@ -0,0 +1,60 @@
# RoadTrip Kids
Eine sprachgesteuerte Spiele-App für Kinder (4-6 Jahre) auf Autofahrten, die Unterhaltung mit pädagogischem Wert verbindet.
## Projektübersicht
RoadTrip Kids ist eine innovative, vollständig sprachgesteuerte App, die speziell für Kinder im Alter von 4-6 Jahren entwickelt wurde, um Autofahrten unterhaltsamer zu gestalten. Die App bietet eine kostenlose Offline-Basis mit vorbereiteten Spielen und eine Premium-Version mit einer KI-gesteuerten dynamischen Spielerfahrung.
## Kernfunktionalitäten
### Vollständige Sprachsteuerung
- Präzise Spracherkennung, optimiert für Kinder im Alter von 4-6 Jahren
- Natürliche Sprachausgabe mit einer freundlichen, altersgerechten Stimme
- Keine Ablenkung durch manuelle Bedienung während der Fahrt
### Offline-Spiele (Kostenlos)
- Geräusche-Detektiv: Erkennen von Tiergeräuschen
- Ich sehe was: Klassisches "Ich sehe was, was du nicht siehst"-Spiel
- Reime-Reise: Reimspiele zur Sprachentwicklung
- Geschichtenzeit: Interaktive Kurzgeschichten
### Premium KI-Spiele (Abonnement)
- Dynamischer KI-Spielleiter mittels OpenAI
- Unbegrenzte Vielfalt an Spielinhalten
- Adaptive Reaktion auf die Antworten der Kinder
- Personalisierte Spielerfahrung
## Technischer Aufbau
### Frontend
- Entwickelt mit Flutter für iOS und Android
- Responsive und kinderfreundliche Benutzeroberfläche
- Animierter Spielleiter-Charakter
### Backend
- Firebase für Benutzerverwaltung und Datenbank
- Cloud Functions für die Anbindung an die OpenAI API
- Sichere Speicherung von Benutzereinstellungen
### Spracherkennung & Sprachausgabe
- Plattform-native Spracherkennung über Flutter-Plugins
- Hochwertige Text-to-Speech-Engine für natürliche Sprachausgabe
## Monetarisierungsmodell
- **Kostenlos**: Zugriff auf die Basisversion mit festgelegten Offline-Spielen
- **Premium (Abonnement)**: Freischaltung der KI-gesteuerten Spielerfahrung mit unbegrenzten dynamischen Inhalten
## Installation
Um das Projekt lokal zu entwickeln:
1. Stellen Sie sicher, dass Flutter SDK installiert ist
2. Klonen Sie das Repository
3. Führen Sie `flutter pub get` aus, um Abhängigkeiten zu installieren
4. Führen Sie `flutter run` aus, um die App zu starten
## Lizenz
© 2025 RoadTrip Kids. Alle Rechte vorbehalten.

28
analysis_options.yaml Normal file
View File

@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

14
android/.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

View File

@ -0,0 +1,44 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.example.road_trip_kids"
compileSdk = flutter.compileSdkVersion
ndkVersion = "27.0.12077973" // Updated to match required NDK version
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.road_trip_kids"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
flutter {
source = "../.."
}

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -0,0 +1,57 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <!-- Permissions for speech recognition and audio -->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.INTERNET"/>
<!-- Audio capture permissions for better speech recognition -->
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<!-- Bluetooth permissions - only needed if we explicitly use Bluetooth -->
<!-- If we don't need Bluetooth functionality, we can remove these permissions -->
<!--
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
-->
<application
android:label="road_trip_kids"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop" android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:enableOnBackInvokedCallback="true"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View File

@ -0,0 +1,5 @@
package com.example.road_trip_kids
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

21
android/build.gradle.kts Normal file
View File

@ -0,0 +1,21 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip

View File

@ -0,0 +1,25 @@
pluginManagement {
val flutterSdkPath = run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.7.3" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
}
include(":app")

34
ios/.gitignore vendored Normal file
View File

@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
</dict>
</plist>

View File

@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@ -0,0 +1,616 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.roadTripKids;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.roadTripKids.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.roadTripKids.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.roadTripKids.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.roadTripKids;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.roadTripKids;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@ -0,0 +1,13 @@
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

52
ios/Runner/Info.plist Normal file
View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Road Trip Kids</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>road_trip_kids</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>NSMicrophoneUsageDescription</key>
<string>This app uses the microphone for speech recognition to enable voice control</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>Speech recognition is used to understand your voice commands</string>
</dict>
</plist>

View File

@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"

View File

@ -0,0 +1,12 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

39
lib/main.dart Normal file
View File

@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'screens/splash_screen.dart';
import 'screens/home_screen.dart';
import 'services/speech_service.dart';
import 'services/tts_service.dart';
import 'utils/app_theme.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
// Firebase initialization would go here when Flutter SDK is installed
// await Firebase.initializeApp();
runApp(const RoadTripKidsApp());
}
class RoadTripKidsApp extends StatelessWidget {
const RoadTripKidsApp({super.key});
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => SpeechService()),
ChangeNotifierProvider(create: (_) => TTSService()),
// Add more providers as needed
],
child: MaterialApp(
title: 'RoadTrip Kids',
theme: AppTheme.lightTheme,
debugShowCheckedModeBanner: false,
home: const SplashScreen(),
routes: {
'/home': (context) => const HomeScreen(),
// Add more routes as needed
},
),
);
}
}

146
lib/models/game.dart Normal file
View File

@ -0,0 +1,146 @@
class Game {
final String id;
final String name;
final String description;
final String iconPath;
final String type;
final bool requiresInternet;
final bool isPremium;
final List<String> ageGroups;
Game({
required this.id,
required this.name,
required this.description,
required this.iconPath,
required this.type,
required this.requiresInternet,
required this.isPremium,
required this.ageGroups,
});
// Factory method to create Game from JSON
factory Game.fromJson(Map<String, dynamic> json) {
return Game(
id: json['id'],
name: json['name'],
description: json['description'],
iconPath: json['iconPath'],
type: json['type'],
requiresInternet: json['requiresInternet'],
isPremium: json['isPremium'],
ageGroups: List<String>.from(json['ageGroups']),
);
}
// Convert Game to JSON
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'description': description,
'iconPath': iconPath,
'type': type,
'requiresInternet': requiresInternet,
'isPremium': isPremium,
'ageGroups': ageGroups,
};
}
}
// Sample game data
List<Game> offlineGames = [
Game(
id: 'animal_sounds',
name: 'Geräusche-Detektiv',
description: 'Erkenne Tiergeräusche und finde heraus, welches Tier sie macht.',
iconPath: 'assets/images/animal_sounds_icon.png',
type: 'animal_sounds',
requiresInternet: false,
isPremium: false,
ageGroups: ['4-6'],
),
Game(
id: 'i_spy',
name: 'Ich sehe was',
description: 'Ich sehe was, was du nicht siehst - finde Gegenstände im Auto und draußen!',
iconPath: 'assets/images/i_spy_icon.png',
type: 'i_spy',
requiresInternet: false,
isPremium: false,
ageGroups: ['4-6'],
),
Game(
id: 'rhyme_game',
name: 'Reime-Reise',
description: 'Finde Wörter, die sich reimen und erweitere deinen Wortschatz.',
iconPath: 'assets/images/rhyme_icon.png',
type: 'rhyme_game',
requiresInternet: false,
isPremium: false,
ageGroups: ['4-6'],
),
Game(
id: 'story_time',
name: 'Geschichtenzeit',
description: 'Höre spannende Geschichten und entscheide, wie sie weitergehen.',
iconPath: 'assets/images/story_icon.png',
type: 'story_time',
requiresInternet: false,
isPremium: false,
ageGroups: ['4-6'],
),
];
List<Game> onlineGames = [
Game(
id: 'ai_animal_sounds',
name: 'KI Geräusche-Detektiv',
description: 'Unser KI-Freund denkt sich immer neue Tiere und ihre Geräusche aus!',
iconPath: 'assets/images/ai_animal_sounds_icon.png',
type: 'animal_sounds',
requiresInternet: true,
isPremium: true,
ageGroups: ['4-6'],
),
Game(
id: 'ai_i_spy',
name: 'KI Ich sehe was',
description: 'Unser KI-Freund spielt mit dir "Ich sehe was" mit unendlich vielen Ideen!',
iconPath: 'assets/images/ai_i_spy_icon.png',
type: 'i_spy',
requiresInternet: true,
isPremium: true,
ageGroups: ['4-6'],
),
Game(
id: 'ai_rhyme_game',
name: 'KI Reime-Reise',
description: 'Unser KI-Freund kennt tausende Reimwörter und bringt dir viele bei!',
iconPath: 'assets/images/ai_rhyme_icon.png',
type: 'rhyme_game',
requiresInternet: true,
isPremium: true,
ageGroups: ['4-6'],
),
Game(
id: 'ai_story_time',
name: 'KI Geschichtenzeit',
description: 'Unser KI-Freund erzählt dir aufregende, neue Geschichten, die du mitgestalten kannst!',
iconPath: 'assets/images/ai_story_icon.png',
type: 'story_time',
requiresInternet: true,
isPremium: true,
ageGroups: ['4-6'],
),
Game(
id: 'ai_guess_object',
name: 'KI Rätselspiel',
description: 'Unser KI-Freund gibt dir Hinweise zu einem geheimen Objekt. Kannst du es erraten?',
iconPath: 'assets/images/ai_guess_icon.png',
type: 'guess_object',
requiresInternet: true,
isPremium: true,
ageGroups: ['4-6'],
),
];

View File

@ -0,0 +1,186 @@
class GameContent {
final String id;
final String gameType;
final String text;
final String question;
final String answer;
final List<String> hints;
final List<String> options;
final String? audioPath;
final String? imagePath;
GameContent({
required this.id,
required this.gameType,
required this.text,
required this.question,
required this.answer,
required this.hints,
required this.options,
this.audioPath,
this.imagePath,
});
// Factory method to create GameContent from JSON
factory GameContent.fromJson(Map<String, dynamic> json) {
return GameContent(
id: json['id'],
gameType: json['gameType'],
text: json['text'],
question: json['question'],
answer: json['answer'],
hints: List<String>.from(json['hints']),
options: List<String>.from(json['options']),
audioPath: json['audioPath'],
imagePath: json['imagePath'],
);
}
// Convert GameContent to JSON
Map<String, dynamic> toJson() {
return {
'id': id,
'gameType': gameType,
'text': text,
'question': question,
'answer': answer,
'hints': hints,
'options': options,
'audioPath': audioPath,
'imagePath': imagePath,
};
}
}
// Sample content for offline games
List<GameContent> animalSoundsContent = [ GameContent(
id: 'as_1',
gameType: 'animal_sounds',
text: 'Ich denke an ein Tier, das "Miau" macht und gerne schnurrt. Welches Tier ist das?',
question: 'Welches Tier macht "Miau"?',
answer: 'Katze',
hints: ['Es hat einen langen Schwanz', 'Es hat Whisker', 'Es ist ein Haustier'],
options: ['Katze', 'Hund', 'Vogel'],
audioPath: 'assets/sounds/cat.mp3',
imagePath: 'assets/images/cat.png',
),
GameContent(
id: 'as_2',
gameType: 'animal_sounds',
text: 'Ich denke an ein Tier, das "Wau Wau" macht und gerne mit dem Schwanz wedelt. Welches Tier ist das?',
question: 'Welches Tier macht "Wau Wau"?',
answer: 'Hund',
hints: ['Es ist ein treuer Freund', 'Manche bringen Stöcke zurück', 'Es geht gerne spazieren'],
options: ['Katze', 'Hund', 'Pferd'],
audioPath: 'assets/sounds/dog.mp3',
imagePath: 'assets/images/dog.png',
),
GameContent(
id: 'as_3',
gameType: 'animal_sounds',
text: 'Ich denke an ein Tier, das "Muh" macht und uns Milch gibt. Welches Tier ist das?',
question: 'Welches Tier macht "Muh"?',
answer: 'Kuh',
hints: ['Es lebt auf dem Bauernhof', 'Es hat schwarze und weiße Flecken', 'Es frisst Gras'],
options: ['Schwein', 'Kuh', 'Schaf'],
audioPath: 'assets/sounds/cow.mp3',
imagePath: 'assets/images/cow.png',
),
];
List<GameContent> iSpyContent = [ GameContent(
id: 'is_1',
gameType: 'i_spy',
text: 'Ich sehe was, was du nicht siehst, und das ist blau und sehr groß über uns. Was könnte das sein?',
question: 'Was könnte das sein?',
answer: 'Himmel',
hints: ['Es ist über uns', 'Es ist sehr groß', 'Vögel fliegen dort'],
options: ['der Himmel', 'Himmel', 'See', 'Auto'],
imagePath: 'assets/images/sky.png',
), GameContent(
id: 'is_2',
gameType: 'i_spy',
text: 'Ich sehe was, was du nicht siehst, und das ist rund und hilft uns beim Fahren. Was könnte das sein?',
question: 'Was könnte das sein?',
answer: 'Rad',
hints: ['Es ist am Auto', 'Es dreht sich', 'Ohne es können wir nicht fahren'],
options: ['das Rad', 'Rad', 'Lenkrad', 'Ball'],
imagePath: 'assets/images/wheel.png',
),
GameContent(
id: 'is_3',
gameType: 'i_spy',
text: 'Ich sehe was, was du nicht siehst, und das ist grün und wächst draußen. Was könnte das sein?',
question: 'Was könnte das sein?',
answer: 'Baum',
hints: ['Es wächst draußen', 'Es hat Blätter', 'Vögel bauen Nester darin'],
options: ['der Baum', 'Baum', 'Gras', 'Busch'],
imagePath: 'assets/images/tree.png',
),
];
List<GameContent> rhymeGameContent = [
GameContent(
id: 'rg_1',
gameType: 'rhyme_game',
text: 'Finde ein Wort, das sich auf "Haus" reimt. Was könnte das sein?',
question: 'Was reimt sich auf "Haus"?',
answer: 'Maus', // Hauptantwort für Hints
hints: ['Es ist ein kleines Tier', 'Es hat einen langen Schwanz', 'Katzen jagen es'],
options: ['Maus', 'Klaus', 'raus', 'Applaus'], // Alle möglichen Reimwörter
imagePath: 'assets/images/mouse.png',
),
GameContent(
id: 'rg_2',
gameType: 'rhyme_game',
text: 'Finde ein Wort, das sich auf "Hund" reimt. Was könnte das sein?',
question: 'Was reimt sich auf "Hund"?',
answer: 'rund', // Hauptantwort für Hints
hints: ['Es beschreibt eine Form', 'Wie ein Kreis', 'Wie ein Ball'],
options: ['bunt', 'rund', 'Mund', 'gesund'], // Alle möglichen Reimwörter
imagePath: 'assets/images/round.png',
),
GameContent(
id: 'rg_3',
gameType: 'rhyme_game',
text: 'Finde ein Wort, das sich auf "Ball" reimt. Was könnte das sein?',
question: 'Was reimt sich auf "Ball"?',
answer: 'Knall', // Hauptantwort für Hints
hints: ['Es ist ein lautes Geräusch', 'Wie ein Ballon, der platzt', 'Es kann erschrecken'],
options: ['Fall', 'Knall', 'Hall', 'all'], // Alle möglichen Reimwörter
imagePath: 'assets/images/bang.png',
),
];
List<GameContent> storyTimeContent = [
GameContent(
id: 'st_1',
gameType: 'story_time',
text: 'Der kleine Bär ging im Wald spazieren. Er sah einen großen, alten Baum mit einem Loch darin. Was denkst du, was in dem Loch war?',
question: 'Was war in dem Loch im Baum?',
answer: '',
hints: ['Vielleicht war es ein Tier?', 'Oder etwas Überraschendes?', 'Was könnte in einem Baumloch leben?'],
options: ['Ein Eichhörnchen', 'Ein Schatz', 'Eine Fee'],
imagePath: 'assets/images/bear_tree.png',
),
GameContent(
id: 'st_2',
gameType: 'story_time',
text: 'Die kleine Maus wollte über den Fluss, aber es gab keine Brücke. Sie brauchte Hilfe. Wer könnte ihr helfen?',
question: 'Wer könnte der kleinen Maus helfen?',
answer: '',
hints: ['Vielleicht ein anderes Tier?', 'Jemand, der schwimmen kann?', 'Oder jemand, der etwas bauen kann?'],
options: ['Ein Frosch', 'Ein Biber', 'Eine Ente'],
imagePath: 'assets/images/mouse_river.png',
),
GameContent(
id: 'st_3',
gameType: 'story_time',
text: 'Die Sonne ging unter und der kleine Igel suchte einen Platz zum Schlafen. Er war müde von seinem langen Tag. Wo könnte er schlafen?',
question: 'Wo könnte der Igel schlafen?',
answer: '',
hints: ['Ein gemütlicher Ort?', 'Ein geschützter Ort?', 'Irgendwo in der Natur?'],
options: ['Unter einem Busch', 'In einem Laubhaufen', 'In einer Höhle'],
imagePath: 'assets/images/hedgehog.png',
),
];

View File

@ -0,0 +1,76 @@
class UserProfile {
final String id;
final String childName;
final String childAge;
final bool isPremium;
final DateTime? premiumExpiryDate;
final Map<String, int> gameStats;
UserProfile({
required this.id,
required this.childName,
required this.childAge,
required this.isPremium,
this.premiumExpiryDate,
required this.gameStats,
});
// Factory method to create UserProfile from JSON
factory UserProfile.fromJson(Map<String, dynamic> json) {
return UserProfile(
id: json['id'],
childName: json['childName'],
childAge: json['childAge'],
isPremium: json['isPremium'],
premiumExpiryDate: json['premiumExpiryDate'] != null
? DateTime.parse(json['premiumExpiryDate'])
: null,
gameStats: Map<String, int>.from(json['gameStats']),
);
}
// Convert UserProfile to JSON
Map<String, dynamic> toJson() {
return {
'id': id,
'childName': childName,
'childAge': childAge,
'isPremium': isPremium,
'premiumExpiryDate': premiumExpiryDate?.toIso8601String(),
'gameStats': gameStats,
};
}
// Create a copy with updated fields
UserProfile copyWith({
String? id,
String? childName,
String? childAge,
bool? isPremium,
DateTime? premiumExpiryDate,
Map<String, int>? gameStats,
}) {
return UserProfile(
id: id ?? this.id,
childName: childName ?? this.childName,
childAge: childAge ?? this.childAge,
isPremium: isPremium ?? this.isPremium,
premiumExpiryDate: premiumExpiryDate ?? this.premiumExpiryDate,
gameStats: gameStats ?? this.gameStats,
);
}
}
// Sample user profile for demonstration
UserProfile sampleUserProfile = UserProfile(
id: 'user123',
childName: 'Max',
childAge: '5',
isPremium: false,
gameStats: {
'animal_sounds': 10,
'i_spy': 5,
'rhyme_game': 3,
'story_time': 7,
},
);

1075
lib/screens/game_screen.dart Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,498 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/game.dart';
import '../services/tts_service.dart';
import '../utils/app_theme.dart';
import 'game_screen.dart';
import 'parent_dashboard.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateMixin {
late TabController _tabController;
bool _isPremium = false; // This would be set from user profile
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
// Welcome message when app opens
WidgetsBinding.instance.addPostFrameCallback((_) {
final ttsService = Provider.of<TTSService>(context, listen: false);
ttsService.speak('Willkommen bei RoadTrip Kids! Wähle ein Spiel aus!');
});
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('RoadTrip Kids'),
elevation: 0,
actions: [
// Parent settings button
IconButton(
icon: const Icon(Icons.settings),
onPressed: () {
// Show PIN entry dialog before opening parent dashboard
_showPinEntryDialog();
},
),
],
bottom: TabBar(
controller: _tabController,
indicatorColor: Colors.white,
labelStyle: const TextStyle(
fontFamily: 'Bubblegum',
fontSize: 16,
fontWeight: FontWeight.bold,
),
tabs: const [
Tab(text: 'Offline Spiele'),
Tab(text: 'KI Spiele'),
],
),
),
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
AppTheme.primaryColor.withOpacity(0.7),
AppTheme.backgroundColor,
],
),
),
child: TabBarView(
controller: _tabController,
children: [
// Offline Games Tab
_buildOfflineGamesGrid(),
// Online (Premium) Games Tab
_buildOnlineGamesGrid(),
],
),
),
);
}
Widget _buildOfflineGamesGrid() {
return Padding(
padding: const EdgeInsets.all(16.0),
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: 0.8,
),
itemCount: offlineGames.length,
itemBuilder: (context, index) {
final game = offlineGames[index];
return _buildGameCard(game);
},
),
);
}
Widget _buildOnlineGamesGrid() {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// Premium banner if not subscribed
if (!_isPremium)
Card(
color: AppTheme.secondaryColor,
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
const Text(
'Premium Spiele',
style: TextStyle(
fontFamily: 'Bubblegum',
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 8),
const Text(
'Erwecke unseren KI-Spielleiter zum Leben und erlebe unendlich viele neue Spiele!',
style: TextStyle(
fontFamily: 'Bubblegum',
fontSize: 14,
color: Colors.white,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: AppTheme.secondaryColor,
),
child: const Text('Premium freischalten'),
onPressed: () {
// Show subscription dialog
_showSubscriptionDialog();
},
),
],
),
),
),
const SizedBox(height: 16),
// Online games grid
Expanded(
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: 0.8,
),
itemCount: onlineGames.length,
itemBuilder: (context, index) {
final game = onlineGames[index];
return _buildGameCard(game);
},
),
),
],
),
);
}
Widget _buildGameCard(Game game) {
final bool isLocked = game.isPremium && !_isPremium;
return GestureDetector(
onTap: () { if (isLocked) {
// Show subscription dialog if locked
_showSubscriptionDialog();
} else {
// Don't speak the game name here anymore - let the GameScreen handle it
// Navigate to game screen immediately
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => GameScreen(game: game),
),
);
}
},
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
elevation: 5,
child: Stack(
fit: StackFit.expand,
children: [
// Game content
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Game image - placeholder for now
Expanded(
flex: 3,
child: Container(
decoration: BoxDecoration(
color: AppTheme.gameColors[offlineGames.indexOf(game) % AppTheme.gameColors.length],
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
),
child: Center(
child: Icon(
_getIconForGame(game.type),
size: 50,
color: Colors.white,
),
),
),
),
// Game info
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
game.name,
style: const TextStyle(
fontFamily: 'Bubblegum',
fontSize: 16,
fontWeight: FontWeight.bold,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
game.description,
style: const TextStyle(
fontSize: 12,
color: AppTheme.secondaryTextColor,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
),
],
),
// Lock overlay for premium games
if (isLocked)
Container(
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.5),
borderRadius: BorderRadius.circular(16),
),
child: const Center(
child: Icon(
Icons.lock,
color: Colors.white,
size: 40,
),
),
),
],
),
),
);
}
IconData _getIconForGame(String gameType) {
switch (gameType) {
case 'animal_sounds':
return Icons.pets;
case 'i_spy':
return Icons.visibility;
case 'rhyme_game':
return Icons.format_quote;
case 'story_time':
return Icons.book;
case 'guess_object':
return Icons.help_outline;
default:
return Icons.games;
}
}
// Show PIN entry dialog for parent area
void _showPinEntryDialog() {
final TextEditingController pinController = TextEditingController();
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text(
'Elternbereich',
style: TextStyle(fontFamily: 'Bubblegum'),
),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'Bitte gib den PIN ein:',
style: TextStyle(fontFamily: 'Bubblegum'),
),
const SizedBox(height: 16),
TextField(
controller: pinController,
keyboardType: TextInputType.number,
maxLength: 4,
obscureText: true,
textAlign: TextAlign.center,
decoration: const InputDecoration(
border: OutlineInputBorder(),
counterText: '',
),
),
],
),
actions: [
TextButton(
child: const Text('Abbrechen'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: const Text('Bestätigen'),
onPressed: () {
// Default PIN is 1234 for demo
if (pinController.text == '1234') {
Navigator.of(context).pop();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ParentDashboard(),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Falscher PIN'),
),
);
}
},
),
],
);
},
);
}
// Show subscription dialog
void _showSubscriptionDialog() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
title: const Text(
'Premium freischalten',
style: TextStyle(
fontFamily: 'Bubblegum',
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Premium icon
const Icon(
Icons.stars,
size: 60,
color: AppTheme.secondaryColor,
),
const SizedBox(height: 16),
// Premium description
const Text(
'Mit Premium erhältst du:',
style: TextStyle(
fontFamily: 'Bubblegum',
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
// Premium features
_buildPremiumFeature(
'Unendlich viele neue Spiele durch unsere KI',
Icons.computer,
),
_buildPremiumFeature(
'Dynamisch reagierender Spielleiter',
Icons.chat,
),
_buildPremiumFeature(
'Personalisierte Spiele',
Icons.person,
),
_buildPremiumFeature(
'Keine Werbung',
Icons.block,
),
const SizedBox(height: 16),
// Subscription price
const Text(
'Nur 4,99€ pro Monat',
style: TextStyle(
fontFamily: 'Bubblegum',
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppTheme.secondaryColor,
),
),
const Text(
'Jederzeit kündbar',
style: TextStyle(
fontSize: 12,
),
),
],
),
actions: [
TextButton(
child: const Text('Später'),
onPressed: () {
Navigator.of(context).pop();
},
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.secondaryColor,
),
child: const Text('Jetzt abonnieren'),
onPressed: () {
// In a real app, this would trigger the subscription flow
Navigator.of(context).pop();
// For demo purposes, just show a success message
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Dies ist eine Demo - Kein Abonnement abgeschlossen'),
),
);
},
),
],
);
},
);
}
Widget _buildPremiumFeature(String text, IconData icon) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
children: [
Icon(
icon,
size: 20,
color: AppTheme.secondaryColor,
),
const SizedBox(width: 8),
Expanded(
child: Text(text),
),
],
),
);
}
}

View File

@ -0,0 +1,633 @@
import 'package:flutter/material.dart';
import '../models/user_profile.dart';
import '../utils/app_theme.dart';
class ParentDashboard extends StatefulWidget {
const ParentDashboard({super.key});
@override
State<ParentDashboard> createState() => _ParentDashboardState();
}
class _ParentDashboardState extends State<ParentDashboard> with SingleTickerProviderStateMixin {
late TabController _tabController;
UserProfile _userProfile = sampleUserProfile; // Use the sample profile for demo
final TextEditingController _childNameController = TextEditingController();
final TextEditingController _childAgeController = TextEditingController();
String _selectedPin = '1234'; // Default PIN
bool _isChangingPin = false;
final TextEditingController _newPinController = TextEditingController();
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
// Initialize controllers with user data
_childNameController.text = _userProfile.childName;
_childAgeController.text = _userProfile.childAge;
}
@override
void dispose() {
_tabController.dispose();
_childNameController.dispose();
_childAgeController.dispose();
_newPinController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Elternbereich'),
bottom: TabBar(
controller: _tabController,
indicatorColor: Colors.white,
tabs: const [
Tab(text: 'Profil'),
Tab(text: 'Abonnement'),
Tab(text: 'Einstellungen'),
],
),
),
body: TabBarView(
controller: _tabController,
children: [
// Profile Tab
_buildProfileTab(),
// Subscription Tab
_buildSubscriptionTab(),
// Settings Tab
_buildSettingsTab(),
],
),
);
}
Widget _buildProfileTab() {
return SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Profile card
Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Profile header
const Text(
'Kinderprofil',
style: TextStyle(
fontFamily: 'Bubblegum',
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
// Child name
TextFormField(
controller: _childNameController,
decoration: const InputDecoration(
labelText: 'Name des Kindes',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.person),
),
),
const SizedBox(height: 16),
// Child age
TextFormField(
controller: _childAgeController,
decoration: const InputDecoration(
labelText: 'Alter des Kindes',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.cake),
),
keyboardType: TextInputType.number,
),
const SizedBox(height: 24),
// Save button
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _saveProfile,
child: const Text('Speichern'),
),
),
],
),
),
),
const SizedBox(height: 24),
// Statistics card
Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Statistics header
const Text(
'Spielstatistik',
style: TextStyle(
fontFamily: 'Bubblegum',
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
// Statistics
_buildStatisticsItem('Geräusche-Detektiv', _userProfile.gameStats['animal_sounds'] ?? 0),
_buildStatisticsItem('Ich sehe was', _userProfile.gameStats['i_spy'] ?? 0),
_buildStatisticsItem('Reime-Reise', _userProfile.gameStats['rhyme_game'] ?? 0),
_buildStatisticsItem('Geschichtenzeit', _userProfile.gameStats['story_time'] ?? 0),
],
),
),
),
],
),
);
}
Widget _buildSubscriptionTab() {
return SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Current plan card
Card(
elevation: 4,
color: _userProfile.isPremium ? AppTheme.secondaryColor : null,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Plan header
Text(
'Aktueller Plan',
style: TextStyle(
fontFamily: 'Bubblegum',
fontSize: 22,
fontWeight: FontWeight.bold,
color: _userProfile.isPremium ? Colors.white : null,
),
),
const SizedBox(height: 16),
// Plan details
Row(
children: [
Icon(
_userProfile.isPremium ? Icons.star : Icons.star_border,
size: 48,
color: _userProfile.isPremium ? Colors.white : AppTheme.secondaryColor,
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_userProfile.isPremium ? 'Premium' : 'Kostenlos',
style: TextStyle(
fontFamily: 'Bubblegum',
fontSize: 24,
fontWeight: FontWeight.bold,
color: _userProfile.isPremium ? Colors.white : null,
),
),
const SizedBox(height: 4),
Text(
_userProfile.isPremium
? 'Aktiv bis: ${_userProfile.premiumExpiryDate?.toString().split(' ')[0] ?? 'Unbekannt'}'
: 'Nur Offline-Spiele verfügbar',
style: TextStyle(
fontSize: 14,
color: _userProfile.isPremium ? Colors.white70 : AppTheme.secondaryTextColor,
),
),
],
),
),
],
),
const SizedBox(height: 24),
// Subscription button
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: _userProfile.isPremium ? Colors.white : null,
foregroundColor: _userProfile.isPremium ? AppTheme.secondaryColor : null,
),
onPressed: () {
setState(() {
// Toggle premium status (demo only)
_userProfile = _userProfile.copyWith(
isPremium: !_userProfile.isPremium,
premiumExpiryDate: _userProfile.isPremium
? null
: DateTime.now().add(const Duration(days: 30)),
);
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(_userProfile.isPremium
? 'Premium aktiviert (Demo)'
: 'Premium deaktiviert (Demo)'),
),
);
},
child: Text(_userProfile.isPremium
? 'Abonnement kündigen'
: 'Jetzt Premium werden'),
),
),
],
),
),
),
const SizedBox(height: 24),
// Subscription benefits
if (!_userProfile.isPremium)
Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Premium-Vorteile',
style: TextStyle(
fontFamily: 'Bubblegum',
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
// Benefits
_buildBenefitItem('Unbegrenzt neue Spiele durch AI'),
_buildBenefitItem('Dynamisch reagierender Spielleiter'),
_buildBenefitItem('Personalisierte Spiele'),
_buildBenefitItem('Keine Werbung'),
const SizedBox(height: 16),
// Price
const Text(
'Nur 4,99€ pro Monat',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppTheme.secondaryColor,
),
textAlign: TextAlign.center,
),
const Text(
'Jederzeit kündbar',
style: TextStyle(
fontSize: 14,
),
textAlign: TextAlign.center,
),
],
),
),
),
],
),
);
}
Widget _buildSettingsTab() {
return SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Security card
Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Sicherheit',
style: TextStyle(
fontFamily: 'Bubblegum',
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
// PIN settings
if (_isChangingPin)
Column(
children: [
TextFormField(
controller: _newPinController,
decoration: const InputDecoration(
labelText: 'Neuer PIN',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.lock_outline),
),
obscureText: true,
keyboardType: TextInputType.number,
maxLength: 4,
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () {
setState(() {
_isChangingPin = false;
_newPinController.clear();
});
},
child: const Text('Abbrechen'),
),
),
const SizedBox(width: 16),
Expanded(
child: ElevatedButton(
onPressed: () {
if (_newPinController.text.length == 4) {
setState(() {
_selectedPin = _newPinController.text;
_isChangingPin = false;
_newPinController.clear();
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('PIN geändert'),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('PIN muss 4 Zahlen haben'),
),
);
}
},
child: const Text('Speichern'),
),
),
],
),
],
)
else
ListTile(
title: const Text('PIN für Elternbereich ändern'),
subtitle: const Text('Aktueller PIN: ****'),
leading: const Icon(Icons.lock_outline),
trailing: const Icon(Icons.edit),
onTap: () {
setState(() {
_isChangingPin = true;
});
},
),
],
),
),
),
const SizedBox(height: 24),
// App settings card
Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'App-Einstellungen',
style: TextStyle(
fontFamily: 'Bubblegum',
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
// Sound settings
SwitchListTile(
title: const Text('Hintergrundmusik'),
subtitle: const Text('Musik im Hintergrund abspielen'),
value: true,
onChanged: (value) {
// In a real app, this would toggle background music
},
),
SwitchListTile(
title: const Text('Soundeffekte'),
subtitle: const Text('Soundeffekte während des Spiels'),
value: true,
onChanged: (value) {
// In a real app, this would toggle sound effects
},
),
// Voice settings
ListTile(
title: const Text('Sprachgeschwindigkeit'),
subtitle: const Text('Mittel'),
leading: const Icon(Icons.speed),
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
onTap: () {
// In a real app, this would open voice speed settings
},
),
// Language settings
ListTile(
title: const Text('Sprache'),
subtitle: const Text('Deutsch'),
leading: const Icon(Icons.language),
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
onTap: () {
// In a real app, this would open language settings
},
),
],
),
),
),
const SizedBox(height: 24),
// About card
Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Über die App',
style: TextStyle(
fontFamily: 'Bubblegum',
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
// About items
ListTile(
title: const Text('Version'),
subtitle: const Text('1.0.0'),
leading: const Icon(Icons.info_outline),
),
ListTile(
title: const Text('Datenschutz'),
leading: const Icon(Icons.privacy_tip_outlined),
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
onTap: () {
// In a real app, this would open privacy policy
},
),
ListTile(
title: const Text('Nutzungsbedingungen'),
leading: const Icon(Icons.description_outlined),
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
onTap: () {
// In a real app, this would open terms of service
},
),
ListTile(
title: const Text('Support kontaktieren'),
leading: const Icon(Icons.support_agent),
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
onTap: () {
// In a real app, this would open support contact options
},
),
],
),
),
),
],
),
);
}
Widget _buildStatisticsItem(String name, int count) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
children: [
Expanded(
child: Text(name),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: AppTheme.primaryColor,
borderRadius: BorderRadius.circular(16),
),
child: Text(
count.toString(),
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
],
),
);
}
Widget _buildBenefitItem(String text) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
children: [
const Icon(
Icons.check_circle,
color: AppTheme.secondaryColor,
),
const SizedBox(width: 12),
Expanded(
child: Text(text),
),
],
),
);
}
void _saveProfile() {
setState(() {
_userProfile = _userProfile.copyWith(
childName: _childNameController.text,
childAge: _childAgeController.text,
);
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Profil gespeichert'),
),
);
}
}

View File

@ -0,0 +1,166 @@
import 'dart:async';
import 'package:flutter/material.dart';
import '../utils/app_theme.dart';
import 'home_screen.dart';
class SplashScreen extends StatefulWidget {
const SplashScreen({super.key});
@override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _scaleAnimation;
late Animation<double> _rotationAnimation;
@override
void initState() {
super.initState();
// Initialize animation controller
_animationController = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
);
// Create scale animation
_scaleAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.elasticOut,
),
);
// Create rotation animation
_rotationAnimation = Tween<double>(
begin: 0.0,
end: 0.1,
).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.elasticInOut,
),
);
// Start the animation
_animationController.forward();
// Navigate to home screen after 3 seconds
Timer(const Duration(seconds: 3), () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const HomeScreen()),
);
});
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
AppTheme.primaryColor,
AppTheme.accentColor,
],
),
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Animated Logo
AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: Transform.rotate(
angle: _rotationAnimation.value,
child: Container(
width: 150,
height: 150,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(75),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 10,
spreadRadius: 5,
),
],
),
child: const Center(
child: Icon(
Icons.directions_car,
size: 80,
color: AppTheme.primaryColor,
),
),
),
),
);
},
),
const SizedBox(height: 30), // App Name
const Text(
'RoadTrip Kids',
style: TextStyle(
fontFamily: 'Comic Sans MS', // Changed from 'Bubblegum'
fontSize: 36,
fontWeight: FontWeight.bold,
color: Colors.white,
shadows: [
Shadow(
blurRadius: 10.0,
color: Colors.black26,
offset: Offset(2.0, 2.0),
),
],
),
),
const SizedBox(height: 10),
// Tagline
const Text(
'Spaß auf jeder Fahrt!',
style: TextStyle(
fontFamily: 'Comic Sans MS', // Changed from 'Bubblegum'
fontSize: 18,
color: Colors.white,
shadows: [
Shadow(
blurRadius: 5.0,
color: Colors.black26,
offset: Offset(1.0, 1.0),
),
],
),
),
const SizedBox(height: 50),
// Loading indicator
const CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
],
),
),
),
);
}
}

View File

@ -0,0 +1,157 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
class OpenAIService extends ChangeNotifier {
static const String _baseUrl = 'https://api.openai.com/v1';
String _apiKey = ''; // API key will be set through Firebase Cloud Functions
bool _isLoading = false;
bool get isLoading => _isLoading;
// This would be set through Firebase to keep the API key secure
void setApiKey(String apiKey) {
_apiKey = apiKey;
}
// Generate game content using OpenAI API
Future<Map<String, dynamic>?> generateGameContent({
required String gameType,
String? childAge,
String? location,
String? previousResponse,
int maxTokens = 150,
}) async {
if (_apiKey.isEmpty) {
debugPrint('API key not set');
return null;
}
_isLoading = true;
notifyListeners();
try {
// Create a prompt based on the game type
String prompt = _createGamePrompt(
gameType: gameType,
childAge: childAge,
location: location,
previousResponse: previousResponse,
);
final response = await http.post(
Uri.parse('$_baseUrl/chat/completions'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $_apiKey',
},
body: jsonEncode({
'model': 'gpt-4-turbo', // Use the appropriate model
'messages': [ {
'role': 'system',
'content': 'You are a friendly game host designed to entertain children aged 4-6 during car trips. '
'Keep your responses simple, engaging, and appropriate for young children. '
'Use short sentences and friendly language. Answer in German.'
},
{
'role': 'user',
'content': prompt
}
],
'max_tokens': maxTokens,
'temperature': 0.7,
}),
);
_isLoading = false;
notifyListeners();
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
final content = data['choices'][0]['message']['content'];
// Parse the content based on the game type
return _parseGameResponse(gameType, content);
} else {
debugPrint('Error: ${response.statusCode}, ${response.body}');
return null;
}
} catch (e) {
debugPrint('Exception: $e');
_isLoading = false;
notifyListeners();
return null;
}
}
// Create game-specific prompts
String _createGamePrompt({
required String gameType,
String? childAge,
String? location,
String? previousResponse,
}) {
switch (gameType) { case 'animal_sounds':
return 'Denke dir ein Tier aus und beschreibe das Geräusch, das es macht. '
'Stelle eine Frage an das Kind, welches Tier dieses Geräusch macht. '
'Halte es einfach und kindgerecht für ${childAge ?? '4-6'}-jährige.';
case 'i_spy':
return 'Spiele "Ich sehe was, was du nicht siehst" mit einem Kind im Alter von ${childAge ?? '4-6'} Jahren. '
'Beschreibe ein Objekt, das typischerweise in einem Auto oder auf einer Straße zu sehen ist. '
'Beginne mit "Ich sehe was, was du nicht siehst, und das ist..." '
'${location != null ? "Wir befinden uns gerade in der Nähe von $location." : ""}';
case 'rhyme_game':
return 'Denke dir ein einfaches Wort aus und frage das Kind nach einem Reimwort. '
'Das Wort sollte einfach sein und viele mögliche Reimwörter haben. '
'Für Kinder im Alter von ${childAge ?? '4-6'} Jahren.';
case 'story_time':
return 'Erzähle den Anfang einer kurzen, kindgerechten Geschichte für ein ${childAge ?? '4-6'}-jähriges Kind. '
'Die Geschichte sollte interaktiv sein, mit einer einfachen Frage am Ende, '
'damit das Kind entscheiden kann, wie die Geschichte weitergehen soll.';
case 'guess_object':
return 'Denke dir ein Objekt aus, das Kinder kennen. '
'Gib 3 einfache Hinweise und frage ein Kind im Alter von ${childAge ?? '4-6'} Jahren, was es sein könnte. '
'${previousResponse != null ? "Das Kind hat geantwortet: \"$previousResponse\". Reagiere darauf." : ""}';
default:
return 'Schlage ein einfaches, interaktives Spiel für ein Kind im Alter von ${childAge ?? '4-6'} Jahren vor, '
'das während einer Autofahrt gespielt werden kann. Erkläre kurz die Regeln.';
}
}
// Parse the AI response based on game type
Map<String, dynamic> _parseGameResponse(String gameType, String content) {
// Default structure for game responses
final response = {
'text': content,
'question': '',
'answer': '',
'hints': <String>[],
'options': <String>[],
};
// Extract specific parts based on game type
switch (gameType) {
case 'animal_sounds':
// Try to extract the animal name as answer
final animalRegex = RegExp(r'(?:ist|ein|der|die|das)\s+([A-Za-zäöüÄÖÜß]+)', caseSensitive: false);
final match = animalRegex.firstMatch(content);
if (match != null && match.groupCount >= 1) {
response['answer'] = match.group(1)!;
}
break;
case 'i_spy':
// Try to extract color or property and object
final propertyRegex = RegExp(r'das ist\s+([A-Za-zäöüÄÖÜß]+)', caseSensitive: false);
final match = propertyRegex.firstMatch(content);
if (match != null && match.groupCount >= 1) {
response['answer'] = match.group(1)!;
}
break;
// Add more game-specific parsing as needed
}
return response;
}
}

View File

@ -0,0 +1,244 @@
import 'package:flutter/material.dart';
import 'package:speech_to_text/speech_to_text.dart' as stt;
class SpeechService extends ChangeNotifier {
final stt.SpeechToText _speech = stt.SpeechToText();
bool _isListening = false;
String _lastRecognizedWords = '';
bool _isAvailable = false;
bool _isInitialized = false;
bool get isListening => _isListening;
String get lastRecognizedWords => _lastRecognizedWords;
bool get isAvailable => _isAvailable;
bool get isInitialized => _isInitialized;
SpeechService() {
_initSpeech();
} // Initialize speech recognition with optimized settings
Future<void> _initSpeech() async {
try {
_isAvailable = await _speech.initialize(
onError: (error) {
debugPrint('Speech recognition error: $error');
// Try to recover from errors by reinitializing
if (error.errorMsg.contains('error_audio_error') ||
error.errorMsg.contains('error_no_match')) {
debugPrint('Attempting to recover from speech error...');
Future.delayed(const Duration(milliseconds: 1000), () {
if (!_isInitialized) {
_initSpeech();
}
});
}
},
onStatus: (status) {
debugPrint('Speech recognition status: $status');
if (status == 'listening') {
debugPrint('Speech recognition is now actively listening');
}
},
);
_isInitialized = true;
debugPrint('Speech recognition initialized, available: $_isAvailable');
notifyListeners();
} catch (e) {
debugPrint('Error initializing speech recognition: $e');
_isAvailable = false;
_isInitialized = false;
notifyListeners();
}
}// Start listening for speech input with improved reliability and sensitivity
Future<bool> startListening({
String? localeId,
Function(String text)? onResult,
Function()? onComplete,
int listenTimeoutSeconds = 60, // Longer listening time
int pauseTimeoutSeconds = 2, // Shorter pause timeout for better responsiveness
}) async {
if (!_isInitialized) {
debugPrint('Speech service not initialized, trying to initialize...');
await _initSpeech();
if (!_isInitialized) {
// Try one more time after a delay
await Future.delayed(const Duration(milliseconds: 500));
await _initSpeech();
if (!_isInitialized) {
debugPrint('Could not initialize speech service after retry');
return false;
}
}
}
// If already listening, stop first
if (_isListening) {
await stopListening();
// Add short delay to make sure previous session is fully stopped
await Future.delayed(const Duration(milliseconds: 300));
}
if (_isAvailable) {
_isListening = true;
_lastRecognizedWords = '';
notifyListeners();
debugPrint('Starting to listen for speech input');
try {
// Cancel any Bluetooth headset connections if they're confusing the app
// This is a workaround for the repeated BluetoothHeadset logs
try {
await _speech.stop();
await Future.delayed(const Duration(milliseconds: 200));
} catch (e) {
// Ignore errors here, just trying to clean up
}
await _speech.listen(
localeId: localeId ?? 'de_DE', // Set German as default, can be changed
listenFor: Duration(seconds: listenTimeoutSeconds),
pauseFor: Duration(seconds: pauseTimeoutSeconds),
partialResults: true,
// Make speech recognition more sensitive
onSoundLevelChange: (level) {
// Optional: could use this to show visual feedback of sound level
},
onResult: (result) {
// Only update if we have actual words
if (result.recognizedWords.isNotEmpty) {
_lastRecognizedWords = result.recognizedWords;
debugPrint('Speech recognized: $_lastRecognizedWords');
if (onResult != null) {
try {
onResult(_lastRecognizedWords);
} catch (e) {
debugPrint('Error in onResult callback: $e');
}
}
notifyListeners();
}
},
);
// Handle completion with more reliability
_speech.statusListener = (status) {
debugPrint('Speech recognition status: $status');
if (status == 'done' || status == 'notListening') {
_isListening = false;
// Delay the onComplete callback slightly to ensure all results are processed
Future.delayed(const Duration(milliseconds: 300), () {
if (onComplete != null) {
// Use try-catch to prevent setState errors after dispose
try {
onComplete();
} catch (e) {
debugPrint('Error calling onComplete: $e');
}
}
});
notifyListeners();
}
};
// Add error handler explicitly
_speech.errorListener = (error) {
debugPrint('Speech recognition error: $error');
// If we get a timeout error, we should consider whether to retry
if (error.errorMsg == 'error_speech_timeout' && _isListening) {
_isListening = false;
notifyListeners();
// Let the caller know there was a timeout so they can restart if needed
if (onComplete != null) {
try {
onComplete();
} catch (e) {
debugPrint('Error calling onComplete after timeout: $e');
}
}
}
};
return true;
} catch (e) {
debugPrint('Error starting speech recognition: $e');
_isListening = false;
notifyListeners();
return false;
}
} else {
debugPrint('Speech recognition not available');
return false;
}
}
// Stop listening with improved reliability
Future<void> stopListening() async {
if (_isListening) {
try {
// First try to cancel any Bluetooth connections
try {
await _speech.cancel();
await Future.delayed(const Duration(milliseconds: 100));
} catch (e) {
// Ignore errors here
}
// Then properly stop
await _speech.stop();
} catch (e) {
debugPrint('Error stopping speech recognition: $e');
// If stop fails, try to force a reset
try {
await _speech.cancel();
} catch (_) {
// Ignore nested errors
}
} finally {
_isListening = false;
notifyListeners();
}
}
}
// Cancel listening with improved reliability
Future<void> cancelListening() async {
if (_isListening) {
try {
await _speech.cancel();
} catch (e) {
debugPrint('Error cancelling speech recognition: $e');
// Try more forcefully
try {
await _speech.stop();
} catch (_) {
// Ignore nested errors
}
// If still failing, try to reset the speech recognition
if (_isListening) {
_isInitialized = false;
await _initSpeech();
}
} finally {
_isListening = false;
_lastRecognizedWords = '';
notifyListeners();
}
}
}
// Reset recognized words
void resetRecognizedWords() {
_lastRecognizedWords = '';
notifyListeners();
}
// Check if speech matches expected keywords/phrases
bool checkSpeechMatch(List<String> keywords) {
if (_lastRecognizedWords.isEmpty) return false;
final String normalizedSpeech = _lastRecognizedWords.toLowerCase();
return keywords.any((keyword) => normalizedSpeech.contains(keyword.toLowerCase()));
}
}

View File

@ -0,0 +1,200 @@
import 'package:flutter/material.dart';
import 'package:flutter_tts/flutter_tts.dart';
class TTSService extends ChangeNotifier {
final FlutterTts _flutterTts = FlutterTts();
bool _isSpeaking = false; String _currentUtterance = ''; double _volume = 1.0;
double _pitch = 1.0; // Neutral pitch for more natural sound
double _rate = 0.5; // More natural speech rate (less sloth-like)
bool get isSpeaking => _isSpeaking;
String get currentUtterance => _currentUtterance;
TTSService() {
_initTts();
}
// Initialize text-to-speech with better quality settings
Future<void> _initTts() async {
await _flutterTts.setLanguage('de-DE'); // Set German as default language await _flutterTts.setVolume(_volume);
await _flutterTts.setPitch(_pitch);
// Use a more natural speech rate that doesn't sound too slow
await _flutterTts.setSpeechRate(_rate);
// Keep configuration simpler to avoid potential TTS issues
debugPrint('TTS initialized with standard parameters');
// Try to improve quality and reliability
try {
// Try to get available engines
final engines = await _flutterTts.getEngines;
debugPrint('Available TTS engines: $engines');
// Try to use Google's TTS engine if available
if (engines != null && engines.contains('com.google.android.tts')) {
await _flutterTts.setEngine('com.google.android.tts');
debugPrint('Using Google TTS engine');
}
// Enable awaiting speak completion (safely)
try {
await _flutterTts.awaitSpeakCompletion(true);
} catch (e) {
debugPrint('awaitSpeakCompletion not supported: $e');
}
// Get available voices to check if everything is working
try {
final voices = await _flutterTts.getVoices;
debugPrint('Available voices: ${voices?.length ?? 0}');
} catch (e) {
debugPrint('Error getting voices: $e');
}
} catch (e) {
debugPrint('Setting TTS options failed: $e');
}
_flutterTts.setStartHandler(() {
_isSpeaking = true;
notifyListeners();
});
_flutterTts.setCompletionHandler(() {
_isSpeaking = false;
notifyListeners();
});
_flutterTts.setErrorHandler((error) {
_isSpeaking = false;
debugPrint('TTS Error: $error');
notifyListeners();
});
_flutterTts.setCancelHandler(() {
_isSpeaking = false;
notifyListeners();
});
debugPrint('TTS initialized successfully with improved quality settings');
} // Speak text with improved clarity
Future<void> speak(String text, {Function? onComplete}) async {
if (text.isEmpty) return;
// Make sure any previous speech is stopped
if (_isSpeaking) {
await stop();
// Wait a moment to ensure the TTS engine has fully stopped
await Future.delayed(const Duration(milliseconds: 200));
}
_currentUtterance = text;
debugPrint('TTS speaking: $text');
// No segmentation - speak the entire text at once to prevent stuttering
// This ensures game titles don't get split and repeated
// Set completion handler to call onComplete callback
_flutterTts.setCompletionHandler(() {
_isSpeaking = false;
// Add a short delay before calling onComplete
Future.delayed(const Duration(milliseconds: 300), () {
// Call the provided completion callback if any
if (onComplete != null) {
try {
onComplete();
} catch (e) {
debugPrint('Error in onComplete callback: $e');
}
}
});
notifyListeners();
});
// Speak the text - simple and direct approach to prevent stuttering
try {
// Just speak the entire text at once
_isSpeaking = true;
notifyListeners();
await _flutterTts.speak(text);
} catch (e) {
debugPrint('Error speaking text: $e');
_isSpeaking = false;
notifyListeners();
}
}
// Stop speaking
Future<void> stop() async {
await _flutterTts.stop();
_isSpeaking = false;
notifyListeners();
}
// Set volume
Future<void> setVolume(double volume) async {
if (volume >= 0.0 && volume <= 1.0) {
_volume = volume;
await _flutterTts.setVolume(volume);
notifyListeners();
}
}
// Set pitch
Future<void> setPitch(double pitch) async {
if (pitch >= 0.5 && pitch <= 2.0) {
_pitch = pitch;
await _flutterTts.setPitch(pitch);
notifyListeners();
}
}
// Set speech rate
Future<void> setRate(double rate) async {
if (rate >= 0.1 && rate <= 1.0) {
_rate = rate;
await _flutterTts.setSpeechRate(rate);
notifyListeners();
}
}
// Set language
Future<void> setLanguage(String language) async {
await _flutterTts.setLanguage(language);
notifyListeners();
}
// Method to say a short acknowledgement phrase
// This can be used to give quick feedback without interrupting the flow
Future<void> sayFeedback(String feedback) async {
if (feedback.isEmpty) return;
// Don't interrupt existing speech for feedback
if (_isSpeaking) {
debugPrint('Not giving feedback - already speaking');
return;
}
_currentUtterance = feedback;
debugPrint('TTS feedback: $feedback');
// Use a faster rate for quick feedback
final originalRate = _rate;
await _flutterTts.setSpeechRate(0.45); // Slightly faster for feedback
try {
// Speak the feedback directly without pauses
await _flutterTts.speak(feedback);
} catch (e) {
debugPrint('Error speaking feedback: $e');
} finally {
// Reset to original rate
await _flutterTts.setSpeechRate(originalRate);
}
}
// Dispose
@override
void dispose() {
_flutterTts.stop();
super.dispose();
}
}

113
lib/utils/app_theme.dart Normal file
View File

@ -0,0 +1,113 @@
import 'package:flutter/material.dart';
class AppTheme {
// Primary Colors
static const Color primaryColor = Color(0xFF4CAF50); // Vibrant Green
static const Color secondaryColor = Color(0xFFFF9800); // Playful Orange
static const Color accentColor = Color(0xFF03A9F4); // Sky Blue
// Background Colors
static const Color backgroundColor = Color(0xFFF5F5F5); // Light Grey
static const Color cardColor = Colors.white;
// Text Colors
static const Color primaryTextColor = Color(0xFF212121); // Dark Grey
static const Color secondaryTextColor = Color(0xFF757575); // Medium Grey
static const Color buttonTextColor = Colors.white;
// Game-specific Colors
static const Color correctAnswerColor = Color(0xFF4CAF50); // Green
static const Color wrongAnswerColor = Color(0xFFF44336); // Red
static const Color hintColor = Color(0xFFFFEB3B); // Yellow
// Fun Colors for Different Game Categories
static const Color animalCategoryColor = Color(0xFFE91E63); // Pink
static const Color natureCategoryColor = Color(0xFF009688); // Teal
static const Color vehicleCategoryColor = Color(0xFF673AB7); // Purple
static const Color foodCategoryColor = Color(0xFFFF5722); // Deep Orange
// Child-friendly color palette
static const List<Color> gameColors = [
Color(0xFFFF5252), // Red
Color(0xFF536DFE), // Blue
Color(0xFFFFD600), // Yellow
Color(0xFF00E676), // Green
Color(0xFFFF6D00), // Orange
Color(0xFFD500F9), // Purple
Color(0xFF00B0FF), // Light Blue
Color(0xFFFFAB00), // Amber
];
// Theme data
static ThemeData lightTheme = ThemeData(
primaryColor: primaryColor,
colorScheme: ColorScheme.light(
primary: primaryColor,
secondary: secondaryColor,
),
scaffoldBackgroundColor: backgroundColor,
cardTheme: const CardThemeData(
color: cardColor,
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
),
textTheme: const TextTheme( displayLarge: TextStyle(
// Using system font instead of 'Bubblegum'
fontFamily: 'Comic Sans MS',
fontSize: 32,
fontWeight: FontWeight.bold,
color: primaryTextColor,
),
displayMedium: TextStyle(
// Using system font instead of 'Bubblegum'
fontFamily: 'Comic Sans MS',
fontSize: 24,
fontWeight: FontWeight.bold,
color: primaryTextColor,
),
bodyLarge: TextStyle(
// Using system font instead of 'Bubblegum'
fontFamily: 'Comic Sans MS',
fontSize: 18,
color: primaryTextColor,
),
bodyMedium: TextStyle(
// Using system font instead of 'Bubblegum'
fontFamily: 'Comic Sans MS',
fontSize: 16,
color: secondaryTextColor,
),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: primaryColor,
foregroundColor: buttonTextColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 24), textStyle: const TextStyle(
// Using system font instead of 'Bubblegum'
fontFamily: 'Comic Sans MS',
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
appBarTheme: const AppBarTheme(
backgroundColor: primaryColor,
foregroundColor: Colors.white,
centerTitle: true,
elevation: 0, titleTextStyle: TextStyle(
// Using system font instead of 'Bubblegum'
fontFamily: 'Comic Sans MS',
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
iconTheme: const IconThemeData(
color: primaryColor,
size: 28,
),
);
}

View File

@ -0,0 +1,376 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../services/tts_service.dart';
class AnimatedCharacter extends StatefulWidget {
final String mood;
const AnimatedCharacter({
this.mood = 'happy',
super.key,
});
@override
State<AnimatedCharacter> createState() => _AnimatedCharacterState();
}
class _AnimatedCharacterState extends State<AnimatedCharacter> with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _bounceAnimation;
late Animation<double> _mouthAnimation;
bool _isSpeaking = false;
@override
void initState() {
super.initState();
// Initialize animation controller
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
// Create bounce animation
_bounceAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
),
);
// Create mouth animation
_mouthAnimation = Tween<double>(
begin: 0.2,
end: 0.5,
).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
),
);
// Start bounce animation
_animationController.repeat(reverse: true);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// Listen to TTS service to animate mouth when speaking
final ttsService = Provider.of<TTSService>(context);
_isSpeaking = ttsService.isSpeaking;
// If speaking, make animation faster
if (_isSpeaking && _animationController.duration?.inMilliseconds != 200) {
_animationController.stop();
_animationController.duration = const Duration(milliseconds: 200);
_animationController.repeat(reverse: true);
} else if (!_isSpeaking && _animationController.duration?.inMilliseconds != 500) {
_animationController.stop();
_animationController.duration = const Duration(milliseconds: 500);
_animationController.repeat(reverse: true);
}
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Transform.translate(
offset: Offset(0, _bounceAnimation.value * 5 - 2.5),
child: SizedBox(
height: 200,
width: 200,
child: CustomPaint(
painter: CharacterPainter(
mood: widget.mood,
mouthOpenRatio: _isSpeaking ? _mouthAnimation.value : 0.2,
blinkEyes: _bounceAnimation.value > 0.8,
),
),
),
);
},
);
}
}
class CharacterPainter extends CustomPainter {
final String mood;
final double mouthOpenRatio;
final bool blinkEyes;
CharacterPainter({
required this.mood,
required this.mouthOpenRatio,
required this.blinkEyes,
});
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final radius = size.width / 2;
// Define colors based on mood
Color faceColor;
switch (mood) {
case 'happy':
faceColor = Colors.amber;
break;
case 'sad':
faceColor = Colors.lightBlue;
break;
case 'excited':
faceColor = Colors.orange;
break;
case 'thinking':
faceColor = Colors.purple;
break;
default:
faceColor = Colors.amber;
}
// Paint face
final facePaint = Paint()
..color = faceColor
..style = PaintingStyle.fill;
canvas.drawCircle(center, radius * 0.9, facePaint);
// Draw shadow
final shadowPaint = Paint()
..color = Colors.black26
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 8);
canvas.drawCircle(center + const Offset(5, 5), radius * 0.9, shadowPaint);
// Draw face border
final borderPaint = Paint()
..color = faceColor.darker()
..style = PaintingStyle.stroke
..strokeWidth = 8;
canvas.drawCircle(center, radius * 0.9, borderPaint);
// Draw eyes
_drawEyes(canvas, size);
// Draw mouth
_drawMouth(canvas, size);
// Draw additional features based on mood
if (mood == 'thinking') {
_drawThinkingBubble(canvas, size);
} else if (mood == 'excited') {
_drawExcitedFeatures(canvas, size);
}
}
void _drawEyes(Canvas canvas, Size size) {
final eyeRadius = size.width * 0.1;
final leftEyeCenter = Offset(size.width * 0.35, size.height * 0.4);
final rightEyeCenter = Offset(size.width * 0.65, size.height * 0.4);
final whitePaint = Paint()
..color = Colors.white
..style = PaintingStyle.fill;
final irisColor = mood == 'sad' ? Colors.blue : Colors.brown;
final irisPaint = Paint()
..color = irisColor
..style = PaintingStyle.fill;
final pupilPaint = Paint()
..color = Colors.black
..style = PaintingStyle.fill;
// Draw eye whites
canvas.drawCircle(leftEyeCenter, eyeRadius, whitePaint);
canvas.drawCircle(rightEyeCenter, eyeRadius, whitePaint);
// Draw iris
if (!blinkEyes) {
canvas.drawCircle(leftEyeCenter, eyeRadius * 0.7, irisPaint);
canvas.drawCircle(rightEyeCenter, eyeRadius * 0.7, irisPaint);
// Draw pupils
canvas.drawCircle(leftEyeCenter, eyeRadius * 0.3, pupilPaint);
canvas.drawCircle(rightEyeCenter, eyeRadius * 0.3, pupilPaint);
// Draw eye highlights
final highlightPaint = Paint()
..color = Colors.white
..style = PaintingStyle.fill;
canvas.drawCircle(leftEyeCenter + Offset(eyeRadius * 0.2, -eyeRadius * 0.2), eyeRadius * 0.15, highlightPaint);
canvas.drawCircle(rightEyeCenter + Offset(eyeRadius * 0.2, -eyeRadius * 0.2), eyeRadius * 0.15, highlightPaint);
} else {
// Draw blinking eyes (just a line)
final blinkPaint = Paint()
..color = Colors.black
..style = PaintingStyle.stroke
..strokeWidth = 3;
canvas.drawLine(
leftEyeCenter - Offset(eyeRadius * 0.8, 0),
leftEyeCenter + Offset(eyeRadius * 0.8, 0),
blinkPaint,
);
canvas.drawLine(
rightEyeCenter - Offset(eyeRadius * 0.8, 0),
rightEyeCenter + Offset(eyeRadius * 0.8, 0),
blinkPaint,
);
}
}
void _drawMouth(Canvas canvas, Size size) {
final mouthPaint = Paint()
..color = Colors.black
..style = PaintingStyle.fill;
final innerMouthPaint = Paint()
..color = mood == 'sad' ? Colors.blue.shade900 : Colors.red.shade800
..style = PaintingStyle.fill;
final mouthWidth = size.width * 0.5;
final mouthHeight = size.height * mouthOpenRatio;
final mouthRect = Rect.fromCenter(
center: Offset(size.width / 2, size.height * 0.65),
width: mouthWidth,
height: mouthHeight,
);
if (mood == 'sad') {
// Sad mouth (frown)
canvas.drawArc(
mouthRect,
0,
3.14,
false,
Paint()
..color = Colors.black
..style = PaintingStyle.stroke
..strokeWidth = 5,
);
} else {
// Draw mouth background
canvas.drawOval(mouthRect, innerMouthPaint);
// Draw teeth
if (mouthOpenRatio > 0.3) {
final teethPaint = Paint()
..color = Colors.white
..style = PaintingStyle.fill;
final teethRect = Rect.fromLTWH(
mouthRect.left + 5,
mouthRect.top + 5,
mouthRect.width - 10,
mouthRect.height / 3,
);
canvas.drawRRect(
RRect.fromRectAndRadius(teethRect, const Radius.circular(3)),
teethPaint,
);
}
// Draw mouth border
canvas.drawOval(
mouthRect,
Paint()
..color = Colors.black
..style = PaintingStyle.stroke
..strokeWidth = 3,
);
}
}
void _drawThinkingBubble(Canvas canvas, Size size) {
final bubblePaint = Paint()
..color = Colors.white
..style = PaintingStyle.fill;
final bubbleBorderPaint = Paint()
..color = Colors.black
..style = PaintingStyle.stroke
..strokeWidth = 2;
// Draw thinking bubble
final bubbleCenter = Offset(size.width * 0.8, size.height * 0.2);
canvas.drawCircle(bubbleCenter, size.width * 0.1, bubblePaint);
canvas.drawCircle(bubbleCenter, size.width * 0.1, bubbleBorderPaint);
// Draw smaller bubbles leading to the character
final smallBubble1Center = Offset(size.width * 0.7, size.height * 0.3);
canvas.drawCircle(smallBubble1Center, size.width * 0.05, bubblePaint);
canvas.drawCircle(smallBubble1Center, size.width * 0.05, bubbleBorderPaint);
final smallBubble2Center = Offset(size.width * 0.6, size.height * 0.35);
canvas.drawCircle(smallBubble2Center, size.width * 0.03, bubblePaint);
canvas.drawCircle(smallBubble2Center, size.width * 0.03, bubbleBorderPaint);
}
void _drawExcitedFeatures(Canvas canvas, Size size) {
final sparkPaint = Paint()
..color = Colors.yellow
..style = PaintingStyle.fill;
// Draw sparks around the character
for (int i = 0; i < 8; i++) {
final angle = i * 3.14 / 4;
final sparkCenter = Offset(
size.width / 2 + cos(angle) * size.width * 0.6,
size.height / 2 + sin(angle) * size.width * 0.6,
);
_drawStar(canvas, sparkCenter, size.width * 0.08, sparkPaint);
}
}
void _drawStar(Canvas canvas, Offset center, double radius, Paint paint) {
final path = Path();
final outerRadius = radius;
final innerRadius = radius * 0.4;
for (int i = 0; i < 10; i++) {
final angle = i * 3.14 / 5;
final r = i % 2 == 0 ? outerRadius : innerRadius;
final x = center.dx + cos(angle) * r;
final y = center.dy + sin(angle) * r;
if (i == 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
}
path.close();
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(covariant CharacterPainter oldDelegate) {
return oldDelegate.mood != mood ||
oldDelegate.mouthOpenRatio != mouthOpenRatio ||
oldDelegate.blinkEyes != blinkEyes;
}
}
extension ColorExtension on Color {
Color darker() {
return Color.fromARGB(
alpha,
(red * 0.7).round(),
(green * 0.7).round(),
(blue * 0.7).round(),
);
}
}

View File

@ -0,0 +1,89 @@
import 'dart:math';
import 'package:flutter/material.dart';
import '../utils/app_theme.dart';
class SpeechWave extends StatefulWidget {
const SpeechWave({super.key});
@override
State<SpeechWave> createState() => _SpeechWaveState();
}
class _SpeechWaveState extends State<SpeechWave> with TickerProviderStateMixin {
late List<AnimationController> _controllers;
late List<Animation<double>> _animations;
final int _barCount = 5;
@override
void initState() {
super.initState();
// Initialize controllers and animations
_controllers = List.generate(
_barCount,
(index) => AnimationController(
vsync: this,
duration: Duration(milliseconds: 300 + Random().nextInt(700)),
),
);
_animations = _controllers.map((controller) {
return Tween<double>(begin: 0.1, end: 1.0).animate(
CurvedAnimation(
parent: controller,
curve: Curves.easeInOut,
),
);
}).toList();
// Start animations with different delays
for (int i = 0; i < _controllers.length; i++) {
Future.delayed(Duration(milliseconds: i * 50), () {
if (mounted) {
_controllers[i].repeat(reverse: true);
}
});
}
}
@override
void dispose() {
for (var controller in _controllers) {
controller.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: 120,
height: 60,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(_barCount, (index) {
return AnimatedBuilder(
animation: _animations[index],
builder: (context, child) {
return _buildBar(_animations[index].value);
},
);
}),
),
);
}
Widget _buildBar(double height) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: Container(
width: 6,
height: 40 * height,
decoration: BoxDecoration(
color: AppTheme.primaryColor,
borderRadius: BorderRadius.circular(3),
),
),
);
}
}

826
pubspec.lock Normal file
View File

@ -0,0 +1,826 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_flutterfire_internals:
dependency: transitive
description:
name: _flutterfire_internals
sha256: "37a42d06068e2fe3deddb2da079a8c4d105f241225ba27b7122b37e9865fd8f7"
url: "https://pub.dev"
source: hosted
version: "1.3.35"
archive:
dependency: transitive
description:
name: archive
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
url: "https://pub.dev"
source: hosted
version: "3.6.1"
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.7.0"
async:
dependency: transitive
description:
name: async
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev"
source: hosted
version: "2.13.0"
audioplayers:
dependency: "direct main"
description:
name: audioplayers
sha256: c05c6147124cd63e725e861335a8b4d57300b80e6e92cea7c145c739223bbaef
url: "https://pub.dev"
source: hosted
version: "5.2.1"
audioplayers_android:
dependency: transitive
description:
name: audioplayers_android
sha256: b00e1a0e11365d88576320ec2d8c192bc21f1afb6c0e5995d1c57ae63156acb5
url: "https://pub.dev"
source: hosted
version: "4.0.3"
audioplayers_darwin:
dependency: transitive
description:
name: audioplayers_darwin
sha256: "3034e99a6df8d101da0f5082dcca0a2a99db62ab1d4ddb3277bed3f6f81afe08"
url: "https://pub.dev"
source: hosted
version: "5.0.2"
audioplayers_linux:
dependency: transitive
description:
name: audioplayers_linux
sha256: "60787e73fefc4d2e0b9c02c69885402177e818e4e27ef087074cf27c02246c9e"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
audioplayers_platform_interface:
dependency: transitive
description:
name: audioplayers_platform_interface
sha256: "365c547f1bb9e77d94dd1687903a668d8f7ac3409e48e6e6a3668a1ac2982adb"
url: "https://pub.dev"
source: hosted
version: "6.1.0"
audioplayers_web:
dependency: transitive
description:
name: audioplayers_web
sha256: "22cd0173e54d92bd9b2c80b1204eb1eb159ece87475ab58c9788a70ec43c2a62"
url: "https://pub.dev"
source: hosted
version: "4.1.0"
audioplayers_windows:
dependency: transitive
description:
name: audioplayers_windows
sha256: "9536812c9103563644ada2ef45ae523806b0745f7a78e89d1b5fb1951de90e1a"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
characters:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
source: hosted
version: "1.4.0"
clock:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.2"
cloud_firestore:
dependency: "direct main"
description:
name: cloud_firestore
sha256: a0f161b92610e078b4962d7e6ebeb66dc9cce0ada3514aeee442f68165d78185
url: "https://pub.dev"
source: hosted
version: "4.17.5"
cloud_firestore_platform_interface:
dependency: transitive
description:
name: cloud_firestore_platform_interface
sha256: "6a55b319f8d33c307396b9104512e8130a61904528ab7bd8b5402678fca54b81"
url: "https://pub.dev"
source: hosted
version: "6.2.5"
cloud_firestore_web:
dependency: transitive
description:
name: cloud_firestore_web
sha256: "89dfa1304d3da48b3039abbb2865e3d30896ef858e569a16804a99f4362283a9"
url: "https://pub.dev"
source: hosted
version: "3.12.5"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
connectivity_plus:
dependency: "direct main"
description:
name: connectivity_plus
sha256: "77a180d6938f78ca7d2382d2240eb626c0f6a735d0bfdce227d8ffb80f95c48b"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
connectivity_plus_platform_interface:
dependency: transitive
description:
name: connectivity_plus_platform_interface
sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a
url: "https://pub.dev"
source: hosted
version: "1.2.4"
crypto:
dependency: transitive
description:
name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
url: "https://pub.dev"
source: hosted
version: "3.0.6"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
url: "https://pub.dev"
source: hosted
version: "1.0.8"
dbus:
dependency: transitive
description:
name: dbus
sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c"
url: "https://pub.dev"
source: hosted
version: "0.7.11"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
ffi:
dependency: transitive
description:
name: ffi
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.1"
firebase_auth:
dependency: "direct main"
description:
name: firebase_auth
sha256: "279b2773ff61afd9763202cb5582e2b995ee57419d826b9af6517302a59b672f"
url: "https://pub.dev"
source: hosted
version: "4.16.0"
firebase_auth_platform_interface:
dependency: transitive
description:
name: firebase_auth_platform_interface
sha256: a0270e1db3b2098a14cb2a2342b3cd2e7e458e0c391b1f64f6f78b14296ec093
url: "https://pub.dev"
source: hosted
version: "7.3.0"
firebase_auth_web:
dependency: transitive
description:
name: firebase_auth_web
sha256: c7b1379ccef7abf4b6816eede67a868c44142198e42350f51c01d8fc03f95a7d
url: "https://pub.dev"
source: hosted
version: "5.8.13"
firebase_core:
dependency: "direct main"
description:
name: firebase_core
sha256: "26de145bb9688a90962faec6f838247377b0b0d32cc0abecd9a4e43525fc856c"
url: "https://pub.dev"
source: hosted
version: "2.32.0"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
sha256: d7253d255ff10f85cfd2adaba9ac17bae878fa3ba577462451163bd9f1d1f0bf
url: "https://pub.dev"
source: hosted
version: "5.4.0"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
sha256: ddd72baa6f727e5b23f32d9af23d7d453d67946f380bd9c21daf474ee0f7326e
url: "https://pub.dev"
source: hosted
version: "2.23.0"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04
url: "https://pub.dev"
source: hosted
version: "2.0.3"
flutter_secure_storage:
dependency: "direct main"
description:
name: flutter_secure_storage
sha256: "22dbf16f23a4bcf9d35e51be1c84ad5bb6f627750565edd70dab70f3ff5fff8f"
url: "https://pub.dev"
source: hosted
version: "8.1.0"
flutter_secure_storage_linux:
dependency: transitive
description:
name: flutter_secure_storage_linux
sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688
url: "https://pub.dev"
source: hosted
version: "1.2.3"
flutter_secure_storage_macos:
dependency: transitive
description:
name: flutter_secure_storage_macos
sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247"
url: "https://pub.dev"
source: hosted
version: "3.1.3"
flutter_secure_storage_platform_interface:
dependency: transitive
description:
name: flutter_secure_storage_platform_interface
sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8
url: "https://pub.dev"
source: hosted
version: "1.1.2"
flutter_secure_storage_web:
dependency: transitive
description:
name: flutter_secure_storage_web
sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9
url: "https://pub.dev"
source: hosted
version: "1.2.1"
flutter_secure_storage_windows:
dependency: transitive
description:
name: flutter_secure_storage_windows
sha256: "38f9501c7cb6f38961ef0e1eacacee2b2d4715c63cc83fe56449c4d3d0b47255"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
flutter_svg:
dependency: "direct main"
description:
name: flutter_svg
sha256: cd57f7969b4679317c17af6fd16ee233c1e60a82ed209d8a475c54fd6fd6f845
url: "https://pub.dev"
source: hosted
version: "2.2.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_tts:
dependency: "direct main"
description:
name: flutter_tts
sha256: bdf2fc4483e74450dc9fc6fe6a9b6a5663e108d4d0dad3324a22c8e26bf48af4
url: "https://pub.dev"
source: hosted
version: "4.2.3"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
http:
dependency: "direct main"
description:
name: http
sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
in_app_purchase:
dependency: "direct main"
description:
name: in_app_purchase
sha256: "5cddd7f463f3bddb1d37a72b95066e840d5822d66291331d7f8f05ce32c24b6c"
url: "https://pub.dev"
source: hosted
version: "3.2.3"
in_app_purchase_android:
dependency: transitive
description:
name: in_app_purchase_android
sha256: fd76e5612da6facadcfe8a3477da092908227260a9f6ec7db9a66dd989c69b02
url: "https://pub.dev"
source: hosted
version: "0.4.0+2"
in_app_purchase_platform_interface:
dependency: transitive
description:
name: in_app_purchase_platform_interface
sha256: "1d353d38251da5b9fea6635c0ebfc6bb17a2d28d0e86ea5e083bf64244f1fb4c"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
in_app_purchase_storekit:
dependency: transitive
description:
name: in_app_purchase_storekit
sha256: ceddd5a70d268f762d29993ed470054bc2baf8793e41800bc82cde05110260d0
url: "https://pub.dev"
source: hosted
version: "0.4.2"
js:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
version: "0.6.7"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev"
source: hosted
version: "4.9.0"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
url: "https://pub.dev"
source: hosted
version: "10.0.9"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
url: "https://pub.dev"
source: hosted
version: "3.0.9"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
lints:
dependency: transitive
description:
name: lints
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
lottie:
dependency: "direct main"
description:
name: lottie
sha256: a93542cc2d60a7057255405f62252533f8e8956e7e06754955669fd32fb4b216
url: "https://pub.dev"
source: hosted
version: "2.7.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.dev"
source: hosted
version: "1.16.0"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
nm:
dependency: transitive
description:
name: nm
sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
path_parsing:
dependency: transitive
description:
name: path_parsing
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
path_provider:
dependency: transitive
description:
name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
url: "https://pub.dev"
source: hosted
version: "2.1.5"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
url: "https://pub.dev"
source: hosted
version: "2.2.17"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
pedantic:
dependency: transitive
description:
name: pedantic
sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602"
url: "https://pub.dev"
source: hosted
version: "1.11.1"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
url: "https://pub.dev"
source: hosted
version: "6.1.0"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
provider:
dependency: "direct main"
description:
name: provider
sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84"
url: "https://pub.dev"
source: hosted
version: "6.1.5"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
url: "https://pub.dev"
source: hosted
version: "2.5.3"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac"
url: "https://pub.dev"
source: hosted
version: "2.4.10"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev"
source: hosted
version: "2.4.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
source_span:
dependency: transitive
description:
name: source_span
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
url: "https://pub.dev"
source: hosted
version: "1.10.1"
speech_to_text:
dependency: "direct main"
description:
name: speech_to_text
sha256: "6cf8f284997490ebef1d68f8707bef57dcf083f43c0f915cc285428520bfe6be"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
speech_to_text_platform_interface:
dependency: transitive
description:
name: speech_to_text_platform_interface
sha256: a1935847704e41ee468aad83181ddd2423d0833abe55d769c59afca07adb5114
url: "https://pub.dev"
source: hosted
version: "2.3.0"
sprintf:
dependency: transitive
description:
name: sprintf
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
synchronized:
dependency: transitive
description:
name: synchronized
sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6"
url: "https://pub.dev"
source: hosted
version: "3.3.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
url: "https://pub.dev"
source: hosted
version: "0.7.4"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.4.0"
uuid:
dependency: transitive
description:
name: uuid
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
url: "https://pub.dev"
source: hosted
version: "4.5.1"
vector_graphics:
dependency: transitive
description:
name: vector_graphics
sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6
url: "https://pub.dev"
source: hosted
version: "1.1.19"
vector_graphics_codec:
dependency: transitive
description:
name: vector_graphics_codec
sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146"
url: "https://pub.dev"
source: hosted
version: "1.1.13"
vector_graphics_compiler:
dependency: transitive
description:
name: vector_graphics_compiler
sha256: "557a315b7d2a6dbb0aaaff84d857967ce6bdc96a63dc6ee2a57ce5a6ee5d3331"
url: "https://pub.dev"
source: hosted
version: "1.1.17"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
url: "https://pub.dev"
source: hosted
version: "15.0.0"
web:
dependency: transitive
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.5.0"
sdks:
dart: ">=3.7.0 <4.0.0"
flutter: ">=3.27.0"

54
pubspec.yaml Normal file
View File

@ -0,0 +1,54 @@
name: road_trip_kids
description: A speech-controlled game app for kids during road trips
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: ">=2.19.0 <4.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.5 # Speech recognition
speech_to_text: ^7.0.0
# Text to speech
flutter_tts: ^4.2.2
# State management
provider: ^6.0.5
# Firebase dependencies
firebase_core: ^2.14.0
firebase_auth: ^4.6.3
cloud_firestore: ^4.8.1
# For animations
lottie: ^2.6.0
# For HTTP requests to OpenAI API
http: ^1.1.0
# For secure storage (parental controls)
flutter_secure_storage: ^8.0.0
# For local storage
shared_preferences: ^2.2.0
# Audio files
audioplayers: ^5.1.0
# SVG rendering
flutter_svg: ^2.0.7
# Connectivity checking
connectivity_plus: ^4.0.2
# For in-app purchases (subscription)
in_app_purchase: ^3.1.7
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.2
flutter:
uses-material-design: true
assets:
- assets/images/
- assets/sounds/
# Using system fonts instead to resolve build issue
# fonts:
# - family: Bubblegum
# fonts:
# - asset: assets/fonts/BubblegumSans-Regular.ttf

21
test/widget_test.dart Normal file
View File

@ -0,0 +1,21 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:road_trip_kids/main.dart';
void main() {
testWidgets('App launches smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const RoadTripKidsApp());
// Verify that our app starts with the splash screen or home screen
expect(find.byType(MaterialApp), findsOneWidget);
});
}