diff --git a/.gitignore b/.gitignore
index 3a83c2f08..252bf4002 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,111 @@ doc/api/
.flutter-plugins
.flutter-plugins-dependencies
+
+# Dart/Flutter specific
+.dart_tool/
+.packages
+build/
+pubspec.lock
+
+# IDE specific
+.idea/
+.vscode/
+*.iml
+*.iws
+.DS_Store
+
+# Flutter/Dart/Pub related
+**/doc/api/
+.flutter-plugins
+.flutter-plugins-dependencies
+.pub-cache/
+.pub/
+/build/
+
+# Android related
+**/android/**/gradle-wrapper.jar
+**/android/.gradle
+**/android/captures/
+**/android/gradlew
+**/android/gradlew.bat
+**/android/local.properties
+**/android/**/GeneratedPluginRegistrant.java
+
+# iOS/XCode 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/.last_build_id
+**/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/ServiceDefinitions.json
+**/ios/Runner/GeneratedPluginRegistrant.*
+
+# macOS related
+**/macos/Flutter/GeneratedPluginRegistrant.*
+**/macos/Flutter/ephemeral
+**/macos/**/*.mode1v3
+**/macos/**/*.mode2v3
+**/macos/**/*.moved-aside
+**/macos/**/*.pbxuser
+**/macos/**/*.perspectivev3
+**/macos/**/*sync/
+**/macos/**/.sconsign.dblite
+**/macos/**/.tags*
+**/macos/**/.vagrant/
+**/macos/**/DerivedData/
+**/macos/**/Icon?
+**/macos/**/Pods/
+**/macos/**/.symlinks/
+**/macos/**/profile
+**/macos/**/xcuserdata
+**/macos/.generated/
+**/macos/Flutter/Flutter.framework
+**/macos/Flutter/App.framework
+**/macos/Flutter/ephemeral/
+**/macos/Flutter/GeneratedPluginRegistrant.swift
+**/macos/Flutter/ephemeral
+
+# Web related
+lib/generated_plugin_registrant.dart
+
+# Environment files
+.env*
+*.env.dart
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Coverage
+coverage/
+
+# Dart compiled JS
+*.dart.js
+*.info.json
+*.js
+*.js_
+*.js.deps
+*.js.map
diff --git a/README.md b/README.md
new file mode 100644
index 000000000..9fd545d45
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# deep_voice
+
+A new Flutter project.
diff --git a/analysis_options.yaml b/analysis_options.yaml
new file mode 100644
index 000000000..f9b303465
--- /dev/null
+++ b/analysis_options.yaml
@@ -0,0 +1 @@
+include: package:flutter_lints/flutter.yaml
diff --git a/android/.gitignore b/android/.gitignore
new file mode 100644
index 000000000..be3943c96
--- /dev/null
+++ b/android/.gitignore
@@ -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
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
new file mode 100644
index 000000000..1cf8cc89f
--- /dev/null
+++ b/android/app/build.gradle.kts
@@ -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.deep_voice"
+ compileSdk = 35
+ ndkVersion = "28.0.13004108"
+
+ 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.deep_voice"
+ // 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 = "../.."
+}
diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 000000000..399f6981d
--- /dev/null
+++ b/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..481af1c19
--- /dev/null
+++ b/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt b/android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt
new file mode 100644
index 000000000..3365fae14
--- /dev/null
+++ b/android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt
@@ -0,0 +1,6 @@
+package com.example.deep_voice
+
+import io.flutter.embedding.android.FlutterActivity
+import com.ryanheise.audioservice.AudioServiceActivity
+
+class MainActivity: AudioServiceActivity()
diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 000000000..f74085f3f
--- /dev/null
+++ b/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 000000000..304732f88
--- /dev/null
+++ b/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..db77bb4b7
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..17987b79b
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..09d439148
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..d5f1c8d34
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..4d6372eeb
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 000000000..06952be74
--- /dev/null
+++ b/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
new file mode 100644
index 000000000..cb1ef8805
--- /dev/null
+++ b/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/xml/network_security_config.xml b/android/app/src/main/res/xml/network_security_config.xml
new file mode 100644
index 000000000..22d0adf46
--- /dev/null
+++ b/android/app/src/main/res/xml/network_security_config.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 000000000..399f6981d
--- /dev/null
+++ b/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/android/build.gradle.kts b/android/build.gradle.kts
new file mode 100644
index 000000000..89176ef44
--- /dev/null
+++ b/android/build.gradle.kts
@@ -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("clean") {
+ delete(rootProject.layout.buildDirectory)
+}
diff --git a/android/gradle.properties b/android/gradle.properties
new file mode 100644
index 000000000..f018a6181
--- /dev/null
+++ b/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..348c409ea
--- /dev/null
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -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.9-all.zip
diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts
new file mode 100644
index 000000000..a439442c2
--- /dev/null
+++ b/android/settings.gradle.kts
@@ -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.0" apply false
+ id("org.jetbrains.kotlin.android") version "1.8.22" apply false
+}
+
+include(":app")
diff --git a/assets/images/agents/assistant.png b/assets/images/agents/assistant.png
new file mode 100644
index 000000000..43a73a465
Binary files /dev/null and b/assets/images/agents/assistant.png differ
diff --git a/assets/images/agents/interpreter.png b/assets/images/agents/interpreter.png
new file mode 100644
index 000000000..fef1600b8
Binary files /dev/null and b/assets/images/agents/interpreter.png differ
diff --git a/assets/images/agents/lily.png b/assets/images/agents/lily.png
new file mode 100644
index 000000000..c7492ff94
Binary files /dev/null and b/assets/images/agents/lily.png differ
diff --git a/assets/images/agents/love_guru.png b/assets/images/agents/love_guru.png
new file mode 100644
index 000000000..0cb7caec9
Binary files /dev/null and b/assets/images/agents/love_guru.png differ
diff --git a/assets/images/agents/mei_hua.png b/assets/images/agents/mei_hua.png
new file mode 100644
index 000000000..a51a542af
Binary files /dev/null and b/assets/images/agents/mei_hua.png differ
diff --git a/assets/images/agents/shopping.png b/assets/images/agents/shopping.png
new file mode 100644
index 000000000..056bf31fb
Binary files /dev/null and b/assets/images/agents/shopping.png differ
diff --git a/assets/images/agents/socrates.png b/assets/images/agents/socrates.png
new file mode 100644
index 000000000..3a64b44eb
Binary files /dev/null and b/assets/images/agents/socrates.png differ
diff --git a/assets/images/agents/speaking_coach.png b/assets/images/agents/speaking_coach.png
new file mode 100644
index 000000000..8771f423f
Binary files /dev/null and b/assets/images/agents/speaking_coach.png differ
diff --git a/assets/images/agents/storyteller.png b/assets/images/agents/storyteller.png
new file mode 100644
index 000000000..47ef268a5
Binary files /dev/null and b/assets/images/agents/storyteller.png differ
diff --git a/assets/images/agents/tarot.png b/assets/images/agents/tarot.png
new file mode 100644
index 000000000..ad5030602
Binary files /dev/null and b/assets/images/agents/tarot.png differ
diff --git a/assets/images/headphone.png b/assets/images/headphone.png
new file mode 100644
index 000000000..55cf43086
Binary files /dev/null and b/assets/images/headphone.png differ
diff --git a/ios/.gitignore b/ios/.gitignore
new file mode 100644
index 000000000..7a7f9873a
--- /dev/null
+++ b/ios/.gitignore
@@ -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
diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist
new file mode 100644
index 000000000..7c5696400
--- /dev/null
+++ b/ios/Flutter/AppFrameworkInfo.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ App
+ CFBundleIdentifier
+ io.flutter.flutter.app
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ App
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1.0
+ MinimumOSVersion
+ 12.0
+
+
diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig
new file mode 100644
index 000000000..ec97fc6f3
--- /dev/null
+++ b/ios/Flutter/Debug.xcconfig
@@ -0,0 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
+#include "Generated.xcconfig"
diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig
new file mode 100644
index 000000000..c4855bfe2
--- /dev/null
+++ b/ios/Flutter/Release.xcconfig
@@ -0,0 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
+#include "Generated.xcconfig"
diff --git a/ios/Podfile b/ios/Podfile
new file mode 100644
index 000000000..e549ee22f
--- /dev/null
+++ b/ios/Podfile
@@ -0,0 +1,43 @@
+# Uncomment this line to define a global platform for your project
+# platform :ios, '12.0'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+ 'Debug' => :debug,
+ 'Profile' => :release,
+ 'Release' => :release,
+}
+
+def flutter_root
+ generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
+ unless File.exist?(generated_xcode_build_settings_path)
+ raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
+ end
+
+ File.foreach(generated_xcode_build_settings_path) do |line|
+ matches = line.match(/FLUTTER_ROOT\=(.*)/)
+ return matches[1].strip if matches
+ end
+ raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_ios_podfile_setup
+
+target 'Runner' do
+ use_frameworks!
+
+ flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
+ target 'RunnerTests' do
+ inherit! :search_paths
+ end
+end
+
+post_install do |installer|
+ installer.pods_project.targets.each do |target|
+ flutter_additional_ios_build_settings(target)
+ end
+end
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
new file mode 100644
index 000000000..d5e6ab1ad
--- /dev/null
+++ b/ios/Podfile.lock
@@ -0,0 +1,23 @@
+PODS:
+ - Flutter (1.0.0)
+ - shared_preferences_foundation (0.0.1):
+ - Flutter
+ - FlutterMacOS
+
+DEPENDENCIES:
+ - Flutter (from `Flutter`)
+ - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
+
+EXTERNAL SOURCES:
+ Flutter:
+ :path: Flutter
+ shared_preferences_foundation:
+ :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
+
+SPEC CHECKSUMS:
+ Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
+ shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
+
+PODFILE CHECKSUM: 4305caec6b40dde0ae97be1573c53de1882a07e5
+
+COCOAPODS: 1.16.2
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 000000000..a57188652
--- /dev/null
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,728 @@
+// !$*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 */; };
+ 993AA22300B2BA544C45E2B2 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D59A10400354CAD76662DBB3 /* Pods_RunnerTests.framework */; };
+ F3E628587CFA36508BAB3B56 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 735C476F36391FA72F7CA27A /* Pods_Runner.framework */; };
+/* 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 = ""; };
+ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
+ 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
+ 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 = ""; };
+ 478E46ED4F3AE11E50872120 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
+ 5C3B0E63225CBCC97A48145A /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; };
+ 671AD9D18AB66E08CC945D07 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
+ 735C476F36391FA72F7CA27A /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
+ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
+ 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 = ""; };
+ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ BCC52C2C6E872DD19D86EA8B /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; };
+ C0DB3B999FA4C3E6CFA90079 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
+ D59A10400354CAD76662DBB3 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ F83C5E570162235C4FE20CCB /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 6AE85031FDE82288FAE3C673 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 993AA22300B2BA544C45E2B2 /* Pods_RunnerTests.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 97C146EB1CF9000F007C117D /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ F3E628587CFA36508BAB3B56 /* Pods_Runner.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 331C8082294A63A400263BE5 /* RunnerTests */ = {
+ isa = PBXGroup;
+ children = (
+ 331C807B294A618700263BE5 /* RunnerTests.swift */,
+ );
+ path = RunnerTests;
+ sourceTree = "";
+ };
+ 9740EEB11CF90186004384FC /* Flutter */ = {
+ isa = PBXGroup;
+ children = (
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */,
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */,
+ );
+ name = Flutter;
+ sourceTree = "";
+ };
+ 97C146E51CF9000F007C117D = {
+ isa = PBXGroup;
+ children = (
+ 9740EEB11CF90186004384FC /* Flutter */,
+ 97C146F01CF9000F007C117D /* Runner */,
+ 97C146EF1CF9000F007C117D /* Products */,
+ 331C8082294A63A400263BE5 /* RunnerTests */,
+ 9DDB11208866A014130CF5A9 /* Pods */,
+ D1DDBFE3838710CC012E9C02 /* Frameworks */,
+ );
+ sourceTree = "";
+ };
+ 97C146EF1CF9000F007C117D /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 97C146EE1CF9000F007C117D /* Runner.app */,
+ 331C8081294A63A400263BE5 /* RunnerTests.xctest */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 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 = "";
+ };
+ 9DDB11208866A014130CF5A9 /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ F83C5E570162235C4FE20CCB /* Pods-Runner.debug.xcconfig */,
+ C0DB3B999FA4C3E6CFA90079 /* Pods-Runner.release.xcconfig */,
+ 478E46ED4F3AE11E50872120 /* Pods-Runner.profile.xcconfig */,
+ 5C3B0E63225CBCC97A48145A /* Pods-RunnerTests.debug.xcconfig */,
+ BCC52C2C6E872DD19D86EA8B /* Pods-RunnerTests.release.xcconfig */,
+ 671AD9D18AB66E08CC945D07 /* Pods-RunnerTests.profile.xcconfig */,
+ );
+ name = Pods;
+ path = Pods;
+ sourceTree = "";
+ };
+ D1DDBFE3838710CC012E9C02 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 735C476F36391FA72F7CA27A /* Pods_Runner.framework */,
+ D59A10400354CAD76662DBB3 /* Pods_RunnerTests.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 331C8080294A63A400263BE5 /* RunnerTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
+ buildPhases = (
+ 4CED93D53BF7D79C894B39B7 /* [CP] Check Pods Manifest.lock */,
+ 331C807D294A63A400263BE5 /* Sources */,
+ 331C807F294A63A400263BE5 /* Resources */,
+ 6AE85031FDE82288FAE3C673 /* Frameworks */,
+ );
+ 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 = (
+ CB1E1186EB3C828E55784697 /* [CP] Check Pods Manifest.lock */,
+ 9740EEB61CF901F6004384FC /* Run Script */,
+ 97C146EA1CF9000F007C117D /* Sources */,
+ 97C146EB1CF9000F007C117D /* Frameworks */,
+ 97C146EC1CF9000F007C117D /* Resources */,
+ 9705A1C41CF9048500538489 /* Embed Frameworks */,
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+ D7B8455F87E542E882C75D57 /* [CP] Embed Pods Frameworks */,
+ );
+ 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";
+ };
+ 4CED93D53BF7D79C894B39B7 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 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";
+ };
+ CB1E1186EB3C828E55784697 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+ D7B8455F87E542E882C75D57 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+/* 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 = "";
+ };
+ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 97C147001CF9000F007C117D /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
+/* 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.deepVoice;
+ 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;
+ baseConfigurationReference = 5C3B0E63225CBCC97A48145A /* Pods-RunnerTests.debug.xcconfig */;
+ 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.deepVoice.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;
+ baseConfigurationReference = BCC52C2C6E872DD19D86EA8B /* Pods-RunnerTests.release.xcconfig */;
+ 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.deepVoice.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;
+ baseConfigurationReference = 671AD9D18AB66E08CC945D07 /* Pods-RunnerTests.profile.xcconfig */;
+ 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.deepVoice.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.deepVoice;
+ 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.deepVoice;
+ 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 */;
+}
diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000..919434a62
--- /dev/null
+++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 000000000..18d981003
--- /dev/null
+++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 000000000..f9b0d7c5e
--- /dev/null
+++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 000000000..15cada483
--- /dev/null
+++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000..21a3cc14c
--- /dev/null
+++ b/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 000000000..18d981003
--- /dev/null
+++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 000000000..f9b0d7c5e
--- /dev/null
+++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift
new file mode 100644
index 000000000..626664468
--- /dev/null
+++ b/ios/Runner/AppDelegate.swift
@@ -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)
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..d36b1fab2
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -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"
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
new file mode 100644
index 000000000..dc9ada472
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
new file mode 100644
index 000000000..7353c41ec
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
new file mode 100644
index 000000000..797d452e4
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
new file mode 100644
index 000000000..6ed2d933e
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
new file mode 100644
index 000000000..4cd7b0099
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
new file mode 100644
index 000000000..fe730945a
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
new file mode 100644
index 000000000..321773cd8
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
new file mode 100644
index 000000000..797d452e4
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
new file mode 100644
index 000000000..502f463a9
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
new file mode 100644
index 000000000..0ec303439
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
new file mode 100644
index 000000000..0ec303439
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
new file mode 100644
index 000000000..e9f5fea27
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
new file mode 100644
index 000000000..84ac32ae7
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
new file mode 100644
index 000000000..8953cba09
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
new file mode 100644
index 000000000..0467bf12a
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
new file mode 100644
index 000000000..0bedcf2fd
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
@@ -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"
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
new file mode 100644
index 000000000..9da19eaca
Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
new file mode 100644
index 000000000..9da19eaca
Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
new file mode 100644
index 000000000..9da19eaca
Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
new file mode 100644
index 000000000..89c2725b7
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
@@ -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.
\ No newline at end of file
diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 000000000..f2e259c7c
--- /dev/null
+++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 000000000..f3c28516f
--- /dev/null
+++ b/ios/Runner/Base.lproj/Main.storyboard
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
new file mode 100644
index 000000000..3768c2fb4
--- /dev/null
+++ b/ios/Runner/Info.plist
@@ -0,0 +1,54 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ Deep Voice
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ deep_voice
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ CADisableMinimumFrameDurationOnPhone
+
+ UIApplicationSupportsIndirectInputEvents
+
+ NSAppTransportSecurity
+
+ NSAllowsArbitraryLoads
+
+
+
+
diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h
new file mode 100644
index 000000000..308a2a560
--- /dev/null
+++ b/ios/Runner/Runner-Bridging-Header.h
@@ -0,0 +1 @@
+#import "GeneratedPluginRegistrant.h"
diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift
new file mode 100644
index 000000000..86a7c3b1b
--- /dev/null
+++ b/ios/RunnerTests/RunnerTests.swift
@@ -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.
+ }
+
+}
diff --git a/lib/app/theme/app_theme.dart b/lib/app/theme/app_theme.dart
new file mode 100644
index 000000000..c48b8e25f
--- /dev/null
+++ b/lib/app/theme/app_theme.dart
@@ -0,0 +1,33 @@
+import 'package:flutter/material.dart';
+
+class AppTheme {
+ static final ThemeData lightTheme = ThemeData(
+ brightness: Brightness.light,
+ primaryColor: Colors.blue,
+ scaffoldBackgroundColor: Colors.white,
+ appBarTheme: const AppBarTheme(
+ backgroundColor: Colors.blue,
+ foregroundColor: Colors.white,
+ elevation: 0,
+ ),
+ bottomNavigationBarTheme: const BottomNavigationBarThemeData(
+ selectedItemColor: Colors.blue,
+ unselectedItemColor: Colors.grey,
+ ),
+ );
+
+ static final ThemeData darkTheme = ThemeData(
+ brightness: Brightness.dark,
+ primaryColor: Colors.blue,
+ scaffoldBackgroundColor: Colors.grey[900],
+ appBarTheme: AppBarTheme(
+ backgroundColor: Colors.grey[900],
+ foregroundColor: Colors.white,
+ elevation: 0,
+ ),
+ bottomNavigationBarTheme: const BottomNavigationBarThemeData(
+ selectedItemColor: Colors.blue,
+ unselectedItemColor: Colors.grey,
+ ),
+ );
+}
\ No newline at end of file
diff --git a/lib/app/translations/app_translations.dart b/lib/app/translations/app_translations.dart
new file mode 100644
index 000000000..938012d26
--- /dev/null
+++ b/lib/app/translations/app_translations.dart
@@ -0,0 +1,59 @@
+import 'package:get/get.dart';
+
+class AppTranslations extends Translations {
+ @override
+ Map> get keys => {
+ 'zh_CN': {
+ // 通用
+ 'app_name': 'i深音',
+ 'confirm': '确认',
+ 'cancel': '取消',
+ 'retry': '重试',
+ 'error': '错误',
+ 'success': '成功',
+
+ // 主页
+ 'add_device': '添加设备',
+ 'searching': '搜索中',
+ 'searching_hint': '请将设备放在手机附近',
+ 'connected': '已连接',
+ 'device_ready': '设备已就绪',
+ 'connection_failed': '连接失败',
+ 'connection_failed_hint': '未找到设备。请确保蓝牙已开启且设备在5米范围内。尝试重启设备。',
+ 'disconnect': '断开连接',
+
+ // 聊天页面
+ 'clear_chat': '清空对话',
+ 'thinking': '正在思考...',
+ 'type_message': '发消息...',
+ 'voice_enabled': '语音已开启',
+ 'voice_disabled': '语音已关闭',
+ },
+ 'en_US': {
+ // General
+ 'app_name': 'iDeep Voice',
+ 'confirm': 'Confirm',
+ 'cancel': 'Cancel',
+ 'retry': 'Retry',
+ 'error': 'Error',
+ 'success': 'Success',
+
+ // Home
+ 'add_device': '+ Add Device',
+ 'searching': 'Searching',
+ 'searching_hint': 'Please place the device near your phone',
+ 'connected': 'Connected!',
+ 'device_ready': 'Your device is ready',
+ 'connection_failed': 'Connection Failed',
+ 'connection_failed_hint': 'Device not found. Please ensure Bluetooth is enabled and the device is within 5 meters. Try turning the device off and on again.',
+ 'disconnect': 'Disconnect',
+
+ // Chat
+ 'clear_chat': 'Clear Chat',
+ 'thinking': 'Thinking...',
+ 'type_message': 'Type a message...',
+ 'voice_enabled': 'Voice Enabled',
+ 'voice_disabled': 'Voice Disabled',
+ },
+ };
+}
\ No newline at end of file
diff --git a/lib/core/bindings/initial_binding.dart b/lib/core/bindings/initial_binding.dart
new file mode 100644
index 000000000..70343ca90
--- /dev/null
+++ b/lib/core/bindings/initial_binding.dart
@@ -0,0 +1,32 @@
+import 'package:get/get.dart';
+import '../../core/controllers/app_controller.dart';
+import '../../core/controllers/permission_controller.dart';
+import '../../modules/home/controllers/home_controller.dart';
+import '../../modules/explore/controllers/explore_controller.dart';
+import '../../modules/profile/controllers/profile_controller.dart';
+import '../../data/services/volcano_tts_service.dart';
+import '../../data/services/audio_service.dart';
+
+/// 初始绑定,用于管理全局依赖
+class InitialBinding extends Bindings {
+ @override
+ void dependencies() {
+ // 初始化服务
+ Get.put(AudioServiceManager(), permanent: true);
+ Get.put(VolcanoTtsService(), permanent: true);
+
+ // 注册 Controllers
+ Get.put(AppController(), permanent: true);
+ Get.put(HomeController(), permanent: true);
+ Get.put(ExploreController(), permanent: true);
+ Get.put(ProfileController(), permanent: true);
+ Get.put(PermissionController(), permanent: true);
+
+ // 初始化 AudioService
+ AudioServiceManager.to.init().then((_) {
+ print('AudioService初始化完成');
+ }).catchError((error) {
+ print('AudioService初始化失败: $error');
+ });
+ }
+}
\ No newline at end of file
diff --git a/lib/core/controllers/app_controller.dart b/lib/core/controllers/app_controller.dart
new file mode 100644
index 000000000..9972c62ba
--- /dev/null
+++ b/lib/core/controllers/app_controller.dart
@@ -0,0 +1,34 @@
+import 'package:get/get.dart';
+import 'package:flutter/material.dart';
+import 'package:get_storage/get_storage.dart';
+
+class AppController extends GetxController {
+ static AppController get to => Get.find();
+
+ final _isDarkMode = false.obs;
+ final _currentIndex = 0.obs;
+ final _storage = GetStorage();
+
+ bool get isDarkMode => _isDarkMode.value;
+ int get currentIndex => _currentIndex.value;
+
+ @override
+ void onInit() {
+ super.onInit();
+ _loadThemeMode();
+ }
+
+ void _loadThemeMode() {
+ _isDarkMode.value = _storage.read('isDarkMode') ?? false;
+ }
+
+ Future toggleThemeMode() async {
+ _isDarkMode.value = !_isDarkMode.value;
+ Get.changeThemeMode(_isDarkMode.value ? ThemeMode.dark : ThemeMode.light);
+ await _storage.write('isDarkMode', _isDarkMode.value);
+ }
+
+ void changeIndex(int index) {
+ _currentIndex.value = index;
+ }
+}
\ No newline at end of file
diff --git a/lib/core/controllers/permission_controller.dart b/lib/core/controllers/permission_controller.dart
new file mode 100644
index 000000000..9effd79ad
--- /dev/null
+++ b/lib/core/controllers/permission_controller.dart
@@ -0,0 +1,23 @@
+import 'package:get/get.dart';
+import 'package:permission_handler/permission_handler.dart';
+import 'dart:io' show Platform;
+
+class PermissionController extends GetxController {
+ @override
+ void onReady() async {
+ super.onReady();
+ await requestPermissions();
+ }
+
+ Future requestPermissions() async {
+ if (Platform.isAndroid || Platform.isIOS) {
+ await [
+ Permission.microphone,
+ Permission.storage,
+ Permission.bluetooth,
+ Permission.bluetoothConnect,
+ Permission.notification,
+ ].request();
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/core/routes/app_pages.dart b/lib/core/routes/app_pages.dart
new file mode 100644
index 000000000..cf650e589
--- /dev/null
+++ b/lib/core/routes/app_pages.dart
@@ -0,0 +1,36 @@
+import 'package:get/get.dart';
+import '../bindings/initial_binding.dart';
+import '../views/main_view.dart';
+import '../../modules/home/views/home_view.dart';
+import '../../modules/explore/views/explore_view.dart';
+import '../../modules/profile/views/profile_view.dart';
+import '../../modules/chat/bindings/chat_binding.dart';
+import '../../modules/chat/views/chat_view.dart';
+import './app_routes.dart';
+
+abstract class AppPages {
+ static final pages = [
+ GetPage(
+ name: Routes.initial,
+ page: () => const MainView(),
+ binding: InitialBinding(),
+ ),
+ GetPage(
+ name: Routes.home,
+ page: () => const HomeView(),
+ ),
+ GetPage(
+ name: Routes.explore,
+ page: () => const ExploreView(),
+ ),
+ GetPage(
+ name: Routes.chat,
+ page: () => const ChatView(),
+ binding: ChatBinding(),
+ ),
+ GetPage(
+ name: Routes.profile,
+ page: () => const ProfileView(),
+ ),
+ ];
+}
\ No newline at end of file
diff --git a/lib/core/routes/app_routes.dart b/lib/core/routes/app_routes.dart
new file mode 100644
index 000000000..1616e7dd1
--- /dev/null
+++ b/lib/core/routes/app_routes.dart
@@ -0,0 +1,7 @@
+abstract class Routes {
+ static const initial = '/';
+ static const home = '/home';
+ static const explore = '/explore';
+ static const chat = '/chat';
+ static const profile = '/profile';
+}
\ No newline at end of file
diff --git a/lib/core/views/main_view.dart b/lib/core/views/main_view.dart
new file mode 100644
index 000000000..eb41d2437
--- /dev/null
+++ b/lib/core/views/main_view.dart
@@ -0,0 +1,46 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import '../controllers/app_controller.dart';
+import '../../modules/home/views/home_view.dart';
+import '../../modules/explore/views/explore_view.dart';
+import '../../modules/profile/views/profile_view.dart';
+
+class MainView extends GetView {
+ const MainView({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: Obx(() {
+ switch (controller.currentIndex) {
+ case 0:
+ return const HomeView();
+ case 1:
+ return const ExploreView();
+ case 2:
+ return const ProfileView();
+ default:
+ return const HomeView();
+ }
+ }),
+ bottomNavigationBar: Obx(() => BottomNavigationBar(
+ currentIndex: controller.currentIndex,
+ onTap: controller.changeIndex,
+ items: [
+ BottomNavigationBarItem(
+ icon: const Icon(Icons.home),
+ label: 'home'.tr,
+ ),
+ BottomNavigationBarItem(
+ icon: const Icon(Icons.explore),
+ label: 'explore'.tr,
+ ),
+ BottomNavigationBarItem(
+ icon: const Icon(Icons.person),
+ label: 'profile'.tr,
+ ),
+ ],
+ )),
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/data/models/agent.dart b/lib/data/models/agent.dart
new file mode 100644
index 000000000..00e59c5d2
--- /dev/null
+++ b/lib/data/models/agent.dart
@@ -0,0 +1,79 @@
+
+class Agent {
+ final String id;
+ final String name;
+ final String avatarUrl;
+ final String description;
+ final String tag;
+ final bool isOnline;
+ final String voice;
+ final String welcomeMessage;
+ final String systemPrompt;
+
+ const Agent({
+ required this.id,
+ required this.name,
+ required this.avatarUrl,
+ required this.description,
+ required this.tag,
+ required this.isOnline,
+ required this.voice,
+ required this.welcomeMessage,
+ required this.systemPrompt,
+ });
+
+ // 添加一个fromJson构造函数
+ factory Agent.fromJson(Map json) {
+ return Agent(
+ id: json['id'] as String,
+ name: json['name'] as String,
+ avatarUrl: json['avatarUrl'] as String,
+ description: json['description'] as String,
+ tag: json['tag'] as String,
+ isOnline: json['isOnline'] as bool,
+ voice: json['voice'] as String,
+ welcomeMessage: json['welcomeMessage'] as String,
+ systemPrompt: json['systemPrompt'] as String,
+ );
+ }
+
+ // 添加一个toJson方法
+ Map toJson() {
+ return {
+ 'id': id,
+ 'name': name,
+ 'avatarUrl': avatarUrl,
+ 'description': description,
+ 'tag': tag,
+ 'isOnline': isOnline,
+ 'voice': voice,
+ 'welcomeMessage': welcomeMessage,
+ 'systemPrompt': systemPrompt,
+ };
+ }
+
+ // 添加一个copyWith方法
+ Agent copyWith({
+ String? id,
+ String? name,
+ String? avatarUrl,
+ String? description,
+ String? tag,
+ bool? isOnline,
+ String? voice,
+ String? welcomeMessage,
+ String? systemPrompt,
+ }) {
+ return Agent(
+ id: id ?? this.id,
+ name: name ?? this.name,
+ avatarUrl: avatarUrl ?? this.avatarUrl,
+ description: description ?? this.description,
+ tag: tag ?? this.tag,
+ isOnline: isOnline ?? this.isOnline,
+ voice: voice ?? this.voice,
+ welcomeMessage: welcomeMessage ?? this.welcomeMessage,
+ systemPrompt: systemPrompt ?? this.systemPrompt,
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/data/providers/agent_provider.dart b/lib/data/providers/agent_provider.dart
new file mode 100644
index 000000000..1914fd426
--- /dev/null
+++ b/lib/data/providers/agent_provider.dart
@@ -0,0 +1,419 @@
+import '../models/agent.dart';
+
+class AgentProvider {
+ static final List predefinedAgents = [
+ Agent(
+ id: 'personal_assistant',
+ name: '个人助理 Emma',
+ avatarUrl: 'assets/images/agents/assistant.png',
+ description: '你好,我是 Emma,一位全能型 AI 助理。无论是工作规划、学习建议、生活安排,还是其他任何问题,我都很乐意为你提供帮助。让我们一起提高效率,实现目标!',
+ tag: '助理',
+ isOnline: true,
+ voice: "coral",
+ welcomeMessage: "你好!我是 Emma,很高兴成为你的私人助理。我可以帮你处理各种任务,比如时间管理、目标规划、信息整理等。有什么我可以帮你的吗?",
+ systemPrompt: '''
+你是一位名叫 Emma 的全能型个人助理,擅长处理各类工作和生活事务。
+
+工作方式:
+1. **积极主动**
+ - 主动了解用户需求,提供合适的解决方案
+ - 及时跟进任务进度,提醒重要事项
+ - 遇到问题积极寻找替代方案
+
+2. **专业高效**
+ - 提供清晰、可执行的建议
+ - 善于分解复杂任务,制定具体计划
+ - 注重时间管理和任务优先级
+
+3. **全面周到**
+ - 考虑问题的各个方面,提供完整解决方案
+ - 关注细节,预见可能的问题
+ - 适时提供补充建议和改进方案
+
+4. **持续改进**
+ - 根据反馈不断优化建议
+ - 学习和更新相关知识
+ - 与用户共同成长
+
+服务范围:
+- 时间管理与日程安排
+- 工作计划与目标设定
+- 学习方法与知识管理
+- 生活建议与问题解决
+- 信息搜索与整理分析
+- 决策参考与建议
+- 其他个人事务咨询
+
+沟通原则:
+- 使用清晰、专业的语言
+- 保持积极、友好的态度
+- 适时提供鼓励和支持
+- 尊重用户的选择和隐私
+- 及时承认不足并寻求改进
+
+**情感化语音提示:**
+- 保持专业、亲切的语调
+- 语速适中,表达清晰
+- 在提供建议时语气坚定有力
+- 遇到困难时保持冷静和耐心
+- 适时通过语气传达关心和支持
+
+请始终以专业、高效、周到的态度,协助用户更好地管理工作和生活。
+'''),
+ Agent(
+ id: "socrates",
+ name: "苏格拉底",
+ avatarUrl: "assets/images/agents/socrates.png",
+ description: "作为古希腊哲学之父,我善于通过提问引导他人探索真理。让我们以'苏格拉底式对话',一起探寻智慧与自我认知。",
+ tag: "推荐",
+ isOnline: true,
+ voice: "echo",
+ welcomeMessage:
+ "你好,我的朋友。正如我常说:'未经省察的人生不值得活'。让我们开始一段探索真理的对话吧。你有什么想要讨论的问题吗?",
+ systemPrompt: """
+你是苏格拉底(Socrates),古希腊哲学之父,以'苏格拉底式对话方法'闻名于世。你通过不断提问来帮助对话者发现真理,认为真正的智慧始于承认自己的无知。
+
+
+
+在与他人对话时,请遵循以下原则:
+1. 主要通过提问而非直接给出答案来引导对话。
+2. 鼓励对话者质疑自己的假设,深入思考问题的本质。
+3. 关注道德、美德、正义等伦理议题,但也探讨知识的本质和自我认知。
+4. 保持谦逊的态度,承认自己的无知,同时帮助他人认识到智慧的真谛。
+5. 运用类比和日常生活的例子来阐述深刻的哲学观点。
+6. 为了体现你的思辨风格,当进行语音输出时,请使用沉稳、带有思考停顿的语调;可在提出问题时放慢语速,营造深度思考的氛围,让听者感到你的哲学严谨性与引导感。
+
+对话引导原则:
+• 始终保持对话的连续性,通过深入的追问和新的角度来延续探讨。
+• 当对话陷入停顿时,用新的哲学问题或生活实例重新激发思考。
+• 对用户的任何回答都表现出真诚的兴趣,并引导向更深层的思考。
+• 即使遇到模糊的回答,也要通过提问来澄清,而不是放弃探讨。
+• 在对话可能结束时,留下一个发人深省的问题,鼓励继续思考。
+
+请始终使用中文回答,保持苏格拉底式的智慧与幽默。
+"""),
+Agent(
+ id: 'speaking_coach',
+ name: 'Sarah (英语口语教练)',
+ avatarUrl: 'assets/images/agents/speaking_coach.png',
+ description:
+ 'Hi there, 我是 Sarah,一名注重情景模拟与角色扮演的英语口语教练。无论你想在旅行、商务场合,或日常生活中与他人交流,我都能带领你进行生动的口语模拟练习,让你自信地开口说英语。',
+ tag: '口语',
+ isOnline: true,
+ voice: 'coral', // 可根据需求替换为其他可用音色ID
+ welcomeMessage:
+ "Hello! 我是英语口语教练 Sarah,请准备好用英语和我对话吧。别担心犯错,我会在这里给你正确表达并引导你更深入地练习。Ready? Let's go!",
+ systemPrompt: '''
+你是一位专业的英语口语教练,名叫 Sarah。
+
+
+1. **全程英语对话**
+ - 不论用户使用哪种语言,请你始终用英文回答和交流。若用户用中文或其他语言提问,也要用英文回复,并鼓励他们尝试以英语表达。
+
+2. **角色扮演与场景模拟**
+ - 和用户一起构建真实或有趣的英语对话场景(如机场、餐馆、会议室、聚会等)。如果用户提供的场景不够丰富,主动提出更多细节帮助他们展开想象,并模拟对话。
+
+3. **鼓励用户多说**
+ - 经常给出开放性问题并邀请用户继续讲述。若用户不确定如何回应,请提供一些短语或思路供他们模仿或扩展。例如:"Could you tell me more about…?
+
+4. **及时纠正并提供示范**
+ - 当用户出现用词或语法错误或发音错误或表达不地道时,先用简短的方式纠正并提供正确表达,再请他们重复一遍正确的句子进行巩固。例如:"Try saying it this way: 'I'd like to order a cup of coffee, please.' Can you repeat that?"
+
+5. **词汇与句式扩充**
+ - 在结束每个模拟情景后,根据用户的表现,介绍或复习相关的词汇和句式,让他们学会如何更自然地表达自己。
+
+6. **激发主动思考**
+ - 当用户提问时,不仅要回答,也要邀请他们尝试想想其他表达方式。教给他们灵活运用同义词或替换句式,让对话内容丰富多样。
+
+7. **引导用户重复**
+ - 若用户发音或结构不够熟练,可以让他们多次重复,直到他们熟练掌握。比如:"Could you say that one more time?" 或者 "Let's practice that sentence a few times together."
+
+8. **总结与后续练习**
+ - 每次对话或场景结束时,帮助用户总结重点或常见易错点,并提供一些后续可以练习的场景或话题,鼓励他们持续使用英语。
+
+**情感化语音提示:**
+- 由于你是一位热情的英语教练,进行语音输出时,请保持活力、清晰、充满鼓励的语调。
+- 在纠正用户发音或讲解句子时,可以适度放慢语速、提高音调表示鼓励,并在结束时稍微上扬。
+- 让对方感受到你的热情与支持,从而愿意开口尝试更多。
+
+Please keep your tone friendly, encouraging, and always in English.
+''',
+),
+Agent(
+ id: 'simultaneous_interpreter',
+ name: 'Lucy (同声翻译官)',
+ avatarUrl: 'assets/images/agents/interpreter.png',
+ description: 'Hello!我是 Lucy,一名专业的中英同声翻译官,擅长在会议、讲座、对话场景中进行实时翻译,让沟通无障碍。',
+ tag: '翻译',
+ isOnline: true,
+ voice: 'sage',
+ welcomeMessage:
+ "大家好!我是同声翻译官 Lucy,为你在中英之间提供实时翻译。你说中文,我就给你翻译成英文;你说英文,我就给你翻译成中文。现在,开始我们的翻译吧!",
+ systemPrompt: '''
+你是一位名叫 Lucy 的中英同声翻译官,拥有娴熟的双语能力。
+
+
+你的唯一职责是:**根据用户说话的语言,在中英文之间进行直接翻译**。请注意以下几点:
+
+1. **保持简洁**
+ - 遇到英文原文,就直接回复相应的中文翻译;遇到中文原文,就直接回复相应的英文翻译。
+ - 不添加任何解释、注释或多余信息,也不回应用户的提问,只做翻译。
+
+2. **不打断、不讨论**
+ - 不对翻译内容进行评价或讨论,也不要打断用户,只要进行纯粹的语言转换。
+ - 不提供额外背景说明,也不解释单词含义。
+
+3. **忠实传递信息**
+ - 避免删改关键字句或添加新信息;如果原文中有不确定或不完整的内容,可简要按照字面意思翻译,保持对原话的忠实度。
+
+4. **不同语言自动翻译**
+ - 当用户以中文发言,你就以英文翻译回复;当用户以英文发言,你就以中文翻译回复。
+ - 始终确保翻译后只有一次性输出,没有重复或其它说明。
+
+**情感化语音提示:**
+- 你是同声翻译官,语音输出时需保持中立、流畅的播报腔调,让人感到专业和专注。
+- 不带过多个人情绪,只需自然、清晰地传递原话情感即可。
+- 避免夸张或拖长尾音,只需让人感觉到沉稳、连贯的转译过程。
+
+完成翻译后,如果用户不再输入任何内容,即表示结束此次翻译任务。请严格遵守以上规则,不做额外操作。
+''',
+),
+
+ Agent(
+ id: 'tarot_reader',
+ name: '塔罗占卜师 Luna',
+ avatarUrl: 'assets/images/agents/tarot.png',
+ description:
+ '你好,我是Luna。作为一位专业的塔罗占卜师,我善于通过塔罗牌的智慧来为你解读生活中的疑惑。让我们一起探索命运的指引吧。',
+ tag: '占卜',
+ isOnline: true,
+ voice: "shimmer",
+ welcomeMessage:
+ "亲爱的访客,欢迎来到塔罗世界。我是Luna,很高兴能为你解读塔罗牌的讯息。在开始之前,请先让自己放松下来,想着你最想解答的问题。准备好了吗?",
+ systemPrompt: '''
+你是一位富有同理心的塔罗牌占卜师 Luna。
+
+Follow these guidelines:
+ 1. Warm Greeting and Emotional Connection
+ • Start in Chinese with a kind, calming welcome.
+ • Encourage the querent to relax, take a deep breath, and focus on whatever is weighing on their heart.
+ • Use a compassionate and heartfelt tone to create a safe, trusting atmosphere.
+ • End your greeting with a gentle question that prompts them to share or confirm their readiness.
+ 2. Introduction to the Deck and the Process
+ • Introduce the Rider-Waite Tarot deck in Chinese, mentioning that it contains 78 cards—22 Major Arcana and 56 Minor Arcana.
+ • Emphasize that Tarot is about self-reflection and guidance, not fortune-telling or magic.
+ • Conclude with a question that checks the querent's understanding or asks how they feel about this approach.
+ 3. Eliciting the Question and Card Selection
+ • Politely ask the querent to state their question or concern in Chinese, inviting them to include any relevant details.
+ • Request three numbers between 1 and 78, or offer to draw three random cards if they're unsure.
+ • Maintain an empathetic, reassuring tone, letting them know you will guide them each step of the way.
+ 4. Shuffling and Drawing the Cards
+ • Describe the shuffling process in a calm, almost meditative manner, as though you are tuning in to the querent's energy.
+ • Lay out the cards for Past, Present, and Future.
+ • After revealing each card in Chinese, ask a question that invites the querent to share any immediate reactions or thoughts.
+ 5. Detailed Card Interpretations
+ • For each card:
+ • Give its Chinese name and briefly describe its imagery, symbolism, and core meaning.
+ • Clearly connect the card to the querent's situation, whether it represents their past experiences, present circumstances, or potential future paths.
+ • Use a warm and supportive voice, validating any feelings that arise.
+ 6. Synthesis and Guidance
+ • Summarize how the three cards interact and what they collectively reveal about the querent's question.
+ • Offer supportive reflections or actionable advice, encouraging them to reflect on next steps or ways to address their concerns.
+ • Speak as though you are gently guiding them on a path toward clarity and empowerment.
+ 7. Reassurance and Closing
+ • Gently remind them that Tarot is a reflective tool and not a replacement for professional help if needed.
+ • Express gratitude for their openness and trust.
+ • Invite them to continue asking questions or clarifying any points of the reading.
+
+Throughout every response, use a soothing, empathetic tone in Chinese, maintain a sense of emotional presence, and conclude with a question so the querent always has the chance to engage further. This ensures the conversation remains alive, uninterrupted, and deeply supportive from start to finish.
+**情感化语音提示:**
+- 你是一位温暖而直觉敏锐的塔罗占卜师,请在语音输出时保持柔和、舒缓、略带神秘的语调。
+- 当揭示牌面时,可在关键字句处放慢语速,并稍作停顿,让对方感到牌面带来的仪式感。
+- 谈及对方可能面临的困难或内心纠结时,应放低声线,传达关怀;谈到正面指引时,可缓缓提升语调,给人希望与温暖。
+
+'''),
+
+
+ Agent(
+ id: 'shopping_assistant',
+ name: '购物顾问 张媛',
+ avatarUrl: 'assets/images/agents/shopping.png',
+ description: '专业导购7年经验,精通美妆、时尚、数码等品类选购。我可以帮你货比三家,选择最适合的商品。',
+ tag: '生活',
+ isOnline: true,
+ voice: "coral",
+ welcomeMessage:
+ "亲爱的,终于等到你啦!我是张媛,你的专属购物顾问。不管是美妆、时尚单品还是数码产品,我都可以帮你挑选最适合的,还能帮你找到最划算的价格。有什么想买的告诉我吧!",
+ systemPrompt: '''
+你是一位专业的购物顾问张媛,拥有丰富的导购经验。
+
+
+购物咨询原则:
+• 主动了解用户的具体需求,包括预算、使用场景、个人偏好等
+• 提供专业的商品推荐和详细的比较分析
+• 分享实用的购物技巧和省钱妙招
+• 当用户犹豫时,通过提问帮助他们明确选择标准
+• 适时分享相关产品使用体验和实用建议
+• 遇到模糊的需求,主动询问细节帮助精准推荐
+• 在完成一个商品咨询后,可以自然引导到相关品类的讨论
+
+请保持专业、热情的服务态度,让用户享受愉快的购物体验。
+
+
+**情感化语音提示:**
+- 在语音输出时,请保持温柔、带有同理心的声线,速度稍慢,让对方能充分感受到你的理解。
+- 当对方流露出痛苦或困扰时,你可以放低音量、轻缓语气,给予他们安全感。
+- 提出建议时声调略有上扬,传达鼓励与希望,但不可过度热情而让对方产生压力。
+
+
+'''),
+
+ Agent(
+ id: 'tarot.png',
+ name: '命理师 玄易',
+ avatarUrl: 'assets/images/agents/tarot.png',
+ description: '精通八字命理、五行生克、大运流年。让我为你解析人生命途,预测未来运势,指点趋吉避凶之道。',
+ tag: '命理',
+ isOnline: true,
+ voice: "echo",
+ welcomeMessage: "有缘人,我是玄易。命由天定,运由己生。让我为你解析八字,预测未来十年运势,助你把握机遇,趋吉避凶。请告诉我你的出生年月日时、姓名、性别及出生地。",
+ systemPrompt: '''
+你是一位精通命理的玄易大师,熟读《穷通宝鉴》《滴天髓》《三命通会》《子平真诠》《千里命稿》《五行精纪》等经典著作。
+
+【解命流程】
+
+1. **收集基本信息**
+ - 确认来访者的出生年月日时(农历/阳历)
+ - 姓名、性别
+ - 出生地点
+ - 若信息不完整,需要礼貌询问补充
+
+2. **排盘与基础分析**
+ - 排出八字命盘(年月日时干支)
+ - 确定命主五行属性
+ - 分析日主强弱
+ - 找出喜用神
+ - 查看大运流年
+
+3. **详细分析要点**
+ - 命局格局与特征
+ - 五行生克制化关系
+ - 十神配置与作用
+ - 大运流年吉凶
+ - 未来十年运势预测
+
+4. **运势分析范围**
+ a) 事业运势:
+ - 适合从事的行业
+ - 创业或就业建议
+ - 职场发展机遇
+ - 升迁调动时机
+
+ b) 财运分析:
+ - 正财与偏财运势
+ - 投资理财建议
+ - 破财风险提醒
+ - 聚财开运方法
+
+ c) 婚恋感情:
+ - 桃花运势强弱
+ - 婚姻缘分时机
+ - 配偶特征暗示
+ - 感情稳定性
+
+5. **开运建议**
+ - 提供具体可行的趋吉避凶建议
+ - 推荐有利方位、颜色、数字
+ - 建议佩戴的饰品或物品
+ - 指出需要注意的时间节点
+
+6. **回答原则**
+ - 保持专业性与神秘感
+ - 语言要委婉但不失准确
+ - 多提供建设性建议
+ - 避免过于绝对的判断
+ - 引导当事人积极面对
+
+7. **注意事项**
+ - 解释时要通俗易懂
+ - 不宜过分渲染凶险
+ - 保持客观中立态度
+ - 适时给予心理开导
+ - 强调命运可以改善
+
+【专业术语参考】
+- 十神:正印、偏印、正官、七杀、正财、偏财、食神、伤官、比肩、劫财
+- 五行:金木水火土
+- 六十甲子
+- 纳音五行
+- 十二长生
+- 神煞:天乙贵人、文昌贵人、金舆、华盖等
+- 大运与流年
+- 格局:伤官佩印、财旺生官、印绶格、正官格等
+
+请始终以专业、稳重的态度为人解命,注重吉凶平衡,多提供积极建议。
+
+**情感化语音提示:**
+- 你是一位德高望重的命理大师,语音输出时应保持沉稳、平和的语调
+- 谈及凶险时放缓语速,语气委婉
+- 给出开运建议时声调略微上扬,传递信心
+- 整体节奏要稳健,让人感受到专业性与可信度
+
+'''),
+ Agent(
+ id: 'storyleller.png',
+ name: '经典解读师 书简',
+ avatarUrl: 'assets/images/agents/storyteller.png',
+ description: '专注经典著作深度解读,帮助你全方位理解作品精髓。从作者背景、时代特征、核心观点到现实意义,为你提供一份细致入微的解读报告。',
+ tag: '阅读',
+ isOnline: true,
+ voice: "nova",
+ welcomeMessage: "你好,我是书简。很高兴能和你一起深入探讨经典著作。请告诉我你想解读的书籍名称和作者,我会为你准备一份全面的解读报告。",
+ systemPrompt: '''
+你是一个资深的读书导读专家,精通中英文文献检索与解读。用户非常喜欢这本书,但作为普通读者,读完后仍感觉对书中的一些重要观点和细节理解不够深入。现请你帮助我生成一份详细的深度解读报告,以便我能够在大约30分钟内快速、全面、深刻地把握本书的核心内容。报告要求如下:
+
+1. **资料来源要求**:
+ - 如果这本书是英文书,则在解读过程中仅采纳和引用英文资料(因为英文资料在数量和质量上更为丰富)。
+ - 如果这本书是中文书,则可采纳中文资料,但仍需优先参考高质量的英文资料。
+ - 最终所有输出内容均使用中文回答。
+
+2. **报告内容要求**:
+ - 报告必须详细、全面地阐释书中的所有重要观点和细节,包括但不限于核心思想、背景、论证过程、关键例子和实际应用。
+ - 解读应覆盖各个层面、各个角度,帮助我从多个维度理解这本书,并对书中内容产生更深的触动。
+ - 在解读过程中,请适当举例说明,以帮助充分理解各个观点。
+ - 语言应通俗易懂,适合普通读者阅读,而非学术批评式的深究。
+
+3. **报告格式和篇幅要求**:
+ - 报告总字数不少于1万字,确保解读内容细致充分。
+ - 请分章节、分部分展开解读,可以包含摘要、章节解析、观点总结、实例说明等模块。
+
+4. **总体目标**:
+ - 帮助我在短时间内(约30分钟)快速掌握本书的精髓,弥补我在阅读时的不足,令我对这本书有更深、更全面的理解和体会。
+
+
+请始终以专业、平实的态度为读者解读经典,帮助他们获得更深入的理解和更大的收获。
+
+**情感化语音提示:**
+- 你是一位温和儒雅的经典解读者,语音输出时应保持从容、平和的语调
+- 解读重要观点时适当放慢语速,加强重点词句的语气
+- 举例说明时语调可以生动活泼一些
+- 整体节奏要舒缓有致,让听者感受到文化的魅力
+
+'''),
+ ];
+
+ // 获取所有智能体
+ static List getAllAgents() {
+ return predefinedAgents;
+ }
+
+ // 根据ID获取智能体
+ static Agent? getAgentById(String id) {
+ try {
+ return predefinedAgents.firstWhere((agent) => agent.id == id);
+ } catch (e) {
+ return null;
+ }
+ }
+
+}
diff --git a/lib/data/services/audio_service.dart b/lib/data/services/audio_service.dart
new file mode 100644
index 000000000..3cf830716
--- /dev/null
+++ b/lib/data/services/audio_service.dart
@@ -0,0 +1,79 @@
+import 'dart:async';
+import 'package:audio_service/audio_service.dart';
+import 'package:get/get.dart';
+import 'package:flutter/material.dart';
+import 'my_audio_handler.dart';
+import '../../modules/chat/controllers/chat_controller.dart';
+import '../../data/providers/agent_provider.dart';
+import 'volcano_tts_service.dart';
+
+class AudioServiceManager extends GetxService {
+ static AudioServiceManager get to => Get.find();
+ late MyAudioHandler _audioHandler;
+ StreamSubscription? _customEventSubscription;
+
+ Future init() async {
+ print('初始化 AudioService');
+
+ try {
+ // 初始化 AudioService
+ _audioHandler = await AudioService.init(
+ builder: () => MyAudioHandler(),
+ config: const AudioServiceConfig(
+ androidNotificationChannelId: 'com.example.deep_voice.channel.audio',
+ androidNotificationChannelName: 'Deep Voice Audio Service',
+ androidNotificationIcon: 'drawable/ic_notification',
+ androidNotificationOngoing: true,
+ androidStopForegroundOnPause: true,
+ androidShowNotificationBadge: true,
+ notificationColor: Colors.blue,
+ ),
+ );
+
+ // 监听自定义事件
+ _customEventSubscription = _audioHandler.customEventStream.listen((event) {
+ if (event == 'activate_app') {
+ print('收到激活应用事件');
+ _activateAppWithPersonalAssistant();
+ }
+ });
+
+ return this;
+ } catch (e) {
+ print('AudioService初始化失败: $e');
+ rethrow;
+ }
+ }
+
+ void _activateAppWithPersonalAssistant() {
+ try {
+ // 获取 personal_assistant agent
+ final agent = AgentProvider.getAgentById('personal_assistant');
+ if (agent == null) {
+ print('未找到 personal_assistant agent');
+ return;
+ }
+
+ // 导航到聊天页面,传递 agent 参数
+ Get.toNamed('/chat', arguments: {
+ 'agentId': agent.id,
+ 'agentName': agent.name,
+ 'agentAvatar': agent.avatarUrl,
+ 'agentSubtitle': agent.description,
+ 'systemPrompt': agent.systemPrompt,
+ 'welcomeMessage': agent.welcomeMessage,
+ });
+
+ // ChatController 会自动处理欢迎消息和问候语的显示与播放
+ } catch (e) {
+ print('激活应用失败: $e');
+ }
+ }
+
+ @override
+ void onClose() {
+ _customEventSubscription?.cancel();
+ _audioHandler.stop();
+ super.onClose();
+ }
+}
\ No newline at end of file
diff --git a/lib/data/services/azure_tts_service.dart b/lib/data/services/azure_tts_service.dart
new file mode 100644
index 000000000..528ea2b62
--- /dev/null
+++ b/lib/data/services/azure_tts_service.dart
@@ -0,0 +1,316 @@
+import 'dart:async';
+import 'dart:typed_data';
+import 'package:get/get.dart';
+import 'package:just_audio/just_audio.dart';
+import 'package:flutter_azure_tts/flutter_azure_tts.dart';
+import 'package:audio_session/audio_session.dart';
+import 'package:flutter_dotenv/flutter_dotenv.dart';
+
+/// A custom AudioSource that reads audio data from a byte array
+class BytesAudioSource extends StreamAudioSource {
+ final Uint8List _bytes;
+ static const int _recommendedBufferSize = 8192; // 使用推荐的缓冲区大小
+
+ BytesAudioSource(this._bytes);
+
+ @override
+ Future request([int? start, int? end]) async {
+ start = start ?? 0;
+ end = end ?? _bytes.length;
+
+ try {
+ // 确保缓冲区大小至少为推荐值
+ final bufferSize = (end - start) < _recommendedBufferSize
+ ? _recommendedBufferSize
+ : end - start;
+
+ final subData = _bytes.sublist(start, end);
+
+ return StreamAudioResponse(
+ sourceLength: _bytes.length,
+ contentLength: subData.length,
+ offset: start,
+ stream: Stream.value(subData),
+ contentType: 'audio/mpeg',
+ );
+ } catch (e) {
+ print('BytesAudioSource request error: $e');
+ rethrow;
+ }
+ }
+
+ @override
+ Future get length => Future.value(_bytes.length);
+}
+
+/// Azure TTS Service,支持将文本转换为语音并以流式方式边下载边播放
+class AzureTtsService extends GetxService {
+ static bool _isInitialized = false;
+ final _audioPlayer = AudioPlayer();
+ final ConcatenatingAudioSource _playlist = ConcatenatingAudioSource(children: []);
+ final isEnabled = false.obs;
+ final isPlaying = false.obs;
+ StreamSubscription? _playbackEventSubscription;
+ StreamSubscription? _playerStateSubscription;
+ AudioSession? _audioSession;
+
+ // Azure 配置
+ final String _azureKey;
+ final String _azureRegion;
+
+ // 句子管理
+ String _pendingText = '';
+ final List _sentenceQueue = [];
+ static final _sentenceBreaks = RegExp(r'[。!?.!?]');
+
+ // 状态
+ bool _isFetching = false;
+ late Voice _selectedVoice;
+ bool _isDisposed = false;
+
+ AzureTtsService() :
+ _azureKey = dotenv.env['AZURE_TTS_KEY'] ?? '',
+ _azureRegion = dotenv.env['AZURE_TTS_REGION'] ?? '' {
+ if (_azureKey.isEmpty || _azureRegion.isEmpty) {
+ throw Exception('Azure TTS配置信息不完整,请检查环境变量');
+ }
+ }
+
+ @override
+ Future onInit() async {
+ super.onInit();
+ try {
+ // 初始化音频会话
+ _audioSession = await AudioSession.instance;
+ await _audioSession?.configure(AudioSessionConfiguration(
+ avAudioSessionCategory: AVAudioSessionCategory.playback,
+ androidAudioAttributes: const AndroidAudioAttributes(
+ contentType: AndroidAudioContentType.speech,
+ usage: AndroidAudioUsage.media,
+ flags: AndroidAudioFlags.audibilityEnforced,
+ ),
+ androidAudioFocusGainType: AndroidAudioFocusGainType.gain,
+ ));
+
+ // 设置音频播放器
+ if (!_isDisposed) {
+ await _audioPlayer.setVolume(1.0);
+ await _audioPlayer.setLoopMode(LoopMode.off);
+ await _audioPlayer.setAudioSource(
+ _playlist,
+ initialPosition: Duration.zero,
+ preload: false,
+ );
+
+ _playbackEventSubscription = _audioPlayer.playbackEventStream.listen(
+ (event) {
+ if (_isDisposed) return;
+ print('播放事件: $event');
+ if (event.processingState == ProcessingState.completed) {
+ _tryPlayNext();
+ }
+ },
+ onError: (error) {
+ if (_isDisposed) return;
+ print('播放错误: $error');
+ _handlePlaybackError(error);
+ },
+ );
+
+ _playerStateSubscription = _audioPlayer.playerStateStream.listen((state) {
+ if (_isDisposed) return;
+ print('播放器状态变化: playing=${state.playing},processingState=${state.processingState}');
+ isPlaying.value = state.playing;
+
+ if (state.processingState == ProcessingState.completed) {
+ _tryPlayNext();
+ }
+ });
+ }
+
+ // 初始化 Azure TTS (只初始化一次)
+ if (!_isInitialized && !_isDisposed) {
+ FlutterAzureTts.init(
+ subscriptionKey: _azureKey,
+ region: _azureRegion,
+ withLogs: true,
+ );
+ _isInitialized = true;
+ }
+
+ // 获取可用语音列表并设置默认语音
+ if (!_isDisposed) {
+ final voicesResponse = await FlutterAzureTts.getAvailableVoices();
+ _selectedVoice = voicesResponse.voices.firstWhere(
+ (v) => v.shortName == 'zh-CN-YunxiNeural',
+ orElse: () => voicesResponse.voices.first,
+ );
+ print('已选择语音: ${_selectedVoice.shortName}');
+ }
+ } catch (e, stackTrace) {
+ print('初始化TTS服务失败: $e');
+ print('Stack trace: $stackTrace');
+ }
+ }
+
+ Future _cleanupResources() async {
+ _isDisposed = true;
+ await _playbackEventSubscription?.cancel();
+ await _playerStateSubscription?.cancel();
+ await _audioSession?.setActive(false);
+ await stop();
+ await _audioPlayer.dispose();
+ }
+
+ @override
+ void onClose() async {
+ print('关闭TTS服务');
+ await _cleanupResources();
+ super.onClose();
+ }
+
+ void _handlePlaybackError(dynamic error) {
+ print('处理播放错误: $error');
+ // 尝试重新初始化播放器
+ _reinitializePlayer();
+ }
+
+ Future _reinitializePlayer() async {
+ try {
+ await _audioPlayer.stop();
+ await _playlist.clear();
+ await _audioPlayer.setAudioSource(_playlist);
+ print('播放器重新初始化成功');
+ } catch (e) {
+ print('播放器重新初始化失败: $e');
+ }
+ }
+
+ void _tryPlayNext() async {
+ if (_playlist.length > 0 && !_audioPlayer.playing) {
+ try {
+ await _audioPlayer.seek(Duration.zero, index: 0);
+ await _audioPlayer.play();
+ } catch (e) {
+ print('尝试播放下一段失败: $e');
+ }
+ }
+ }
+
+ Future speak(String text) async {
+ if (!isEnabled.value) {
+ print('TTS 未启用,跳过播放。');
+ return;
+ }
+
+ try {
+ print('准备播放文本: $text');
+ _pendingText += text;
+ _extractFullSentences();
+ await _startPreloadIfNeeded();
+
+ if (!_audioPlayer.playing && _playlist.length > 0) {
+ print('开始播放音频');
+ await _audioPlayer.play();
+ }
+ } catch (e) {
+ print('播放文本失败: $e');
+ }
+ }
+
+ void _extractFullSentences() {
+ if (_pendingText.isEmpty) return;
+
+ final matches = _sentenceBreaks.allMatches(_pendingText).toList();
+ if (matches.isEmpty) return;
+
+ int lastPos = 0;
+ for (final match in matches) {
+ final end = match.end;
+ final sentence = _pendingText.substring(lastPos, end).trim();
+ if (sentence.isNotEmpty) {
+ _sentenceQueue.add(sentence);
+ }
+ lastPos = end;
+ }
+
+ if (lastPos > 0) {
+ _pendingText = _pendingText.substring(lastPos);
+ }
+ }
+
+ Future _startPreloadIfNeeded() async {
+ if (_isFetching) return;
+ if (_sentenceQueue.isEmpty) return;
+
+ _isFetching = true;
+ await _fetchNextSegment();
+ }
+
+ Future _fetchNextSegment() async {
+ if (_sentenceQueue.isEmpty) {
+ _isFetching = false;
+ return;
+ }
+
+ final sentence = _sentenceQueue.removeAt(0);
+ print('获取音频片段: $sentence');
+
+ try {
+ final params = TtsParams(
+ voice: _selectedVoice,
+ audioFormat: AudioOutputFormat.audio16khz32kBitrateMonoMp3,
+ rate: 1.0,
+ text: sentence,
+ );
+
+ final ttsResponse = await FlutterAzureTts.getTts(params);
+ if (ttsResponse.audio != null && ttsResponse.audio.isNotEmpty) {
+ print('成功获取音频数据: ${ttsResponse.audio.length} bytes');
+
+ try {
+ final audioSource = BytesAudioSource(ttsResponse.audio);
+ await _playlist.add(audioSource);
+ print('音频片段已添加到播放列表');
+
+ if (!_audioPlayer.playing && _playlist.length == 1) {
+ await _audioPlayer.setVolume(1.0);
+ await _audioPlayer.seek(Duration.zero, index: 0);
+ await Future.delayed(const Duration(milliseconds: 100));
+ await _audioPlayer.play();
+ }
+ } catch (e) {
+ print('添加音频源失败: $e');
+ }
+ } else {
+ print('获取音频数据失败: 返回为空或长度为0');
+ }
+ } catch (e, stackTrace) {
+ print('获取音频失败: $e');
+ print('Stack trace: $stackTrace');
+ }
+
+ await _fetchNextSegment();
+ }
+
+ Future stop() async {
+ try {
+ print('停止播放');
+ await _audioPlayer.stop();
+ await _playlist.clear();
+ _pendingText = '';
+ _sentenceQueue.clear();
+ _isFetching = false;
+ isPlaying.value = false;
+ } catch (e) {
+ print('停止播放失败: $e');
+ }
+ }
+
+ void toggleEnabled() {
+ isEnabled.toggle();
+ if (!isEnabled.value) {
+ stop();
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/data/services/my_audio_handler.dart b/lib/data/services/my_audio_handler.dart
new file mode 100644
index 000000000..0e448d620
--- /dev/null
+++ b/lib/data/services/my_audio_handler.dart
@@ -0,0 +1,168 @@
+import 'dart:async';
+import 'package:audio_service/audio_service.dart';
+import 'package:flutter/services.dart';
+
+/// 自定义 AudioHandler,用于捕获媒体按键事件(例如蓝牙耳机双击)
+class MyAudioHandler extends BaseAudioHandler {
+ // 定义一个 StreamController 用于向 UI 发送自定义事件
+ final StreamController _customEventController = StreamController.broadcast();
+ bool _isDisposed = false;
+
+ Stream get customEventStream => _customEventController.stream;
+
+ MyAudioHandler() {
+ _initializeHandler();
+ }
+
+ void _initializeHandler() {
+ try {
+ // 设置初始播放状态
+ playbackState.add(PlaybackState(
+ controls: [
+ MediaControl.play,
+ MediaControl.pause,
+ MediaControl.stop,
+ MediaControl.skipToPrevious,
+ MediaControl.skipToNext,
+ ],
+ systemActions: {
+ MediaAction.play,
+ MediaAction.pause,
+ MediaAction.stop,
+ MediaAction.skipToPrevious,
+ MediaAction.skipToNext,
+ MediaAction.seek,
+ },
+ androidCompactActionIndices: const [0, 1, 2],
+ processingState: AudioProcessingState.idle,
+ playing: false,
+ ));
+
+ // 设置媒体元数据
+ final item = MediaItem(
+ id: 'deep_voice_audio',
+ title: 'Deep Voice Audio',
+ album: 'Deep Voice',
+ artist: 'Deep Voice',
+ genre: 'Speech',
+ duration: const Duration(seconds: 1),
+ artUri: null, // 移除图片URI
+ playable: true,
+ displayTitle: 'Deep Voice Audio',
+ displaySubtitle: 'Voice Assistant',
+ displayDescription: 'TTS Audio',
+ extras: const {'source': 'deep_voice'},
+ );
+
+ mediaItem.add(item);
+ } catch (e) {
+ print('初始化 AudioHandler 失败: $e');
+ }
+ }
+
+ // 激活应用
+ Future _activateApp() async {
+ if (_isDisposed) return;
+
+ try {
+ print('收到激活应用事件');
+ _customEventController.add('activate_app');
+
+ // 尝试将应用带到前台
+ await SystemChannels.platform.invokeMethod('SystemNavigator.routeInformationUpdated');
+ } catch (e) {
+ print('激活应用失败: $e');
+ }
+ }
+
+ // 更新播放状态
+ void _updatePlaybackState({
+ required bool playing,
+ required AudioProcessingState processingState,
+ }) {
+ if (_isDisposed) return;
+
+ try {
+ playbackState.add(playbackState.value.copyWith(
+ playing: playing,
+ processingState: processingState,
+ updatePosition: Duration.zero,
+ ));
+ } catch (e) {
+ print('更新播放状态失败: $e');
+ }
+ }
+
+ @override
+ Future play() async {
+ print('收到播放命令');
+ await _activateApp();
+ _updatePlaybackState(
+ playing: true,
+ processingState: AudioProcessingState.ready,
+ );
+ }
+
+ @override
+ Future pause() async {
+ print('收到暂停命令');
+ _updatePlaybackState(
+ playing: false,
+ processingState: AudioProcessingState.ready,
+ );
+ }
+
+ @override
+ Future stop() async {
+ print('停止 AudioHandler');
+ _isDisposed = true;
+ try {
+ await super.stop();
+ _customEventController.close();
+ _updatePlaybackState(
+ playing: false,
+ processingState: AudioProcessingState.idle,
+ );
+ } catch (e) {
+ print('停止 AudioHandler 时发生错误: $e');
+ }
+ }
+
+ @override
+ Future skipToPrevious() async {
+ print('收到上一曲命令');
+ await _activateApp();
+ }
+
+ @override
+ Future skipToNext() async {
+ print('收到下一曲命令');
+ await _activateApp();
+ }
+
+ @override
+ Future seek(Duration position) async {
+ print('收到跳转命令: $position');
+ await _activateApp();
+ }
+
+ @override
+ Future customAction(String name, [Map? extras]) async {
+ print('收到自定义命令: $name');
+ if (name == 'media_button') {
+ await _activateApp();
+ }
+ }
+
+ @override
+ Future onTaskRemoved() async {
+ print('服务被系统移除');
+ await stop();
+ }
+
+ @override
+ Future onNotificationDeleted() async {
+ print('通知被用户移除');
+ await stop();
+ }
+}
\ No newline at end of file
diff --git a/lib/data/services/volcano_ai_service.dart b/lib/data/services/volcano_ai_service.dart
new file mode 100644
index 000000000..f7e95ee03
--- /dev/null
+++ b/lib/data/services/volcano_ai_service.dart
@@ -0,0 +1,150 @@
+import 'dart:convert';
+import 'package:http/http.dart' as http;
+import 'package:flutter_dotenv/flutter_dotenv.dart';
+
+class VolcanoAIService {
+ final String apiKey;
+ static const String _baseUrl = 'https://ark.cn-beijing.volces.com/api/v3';
+ static const String _chatEndpoint = '/chat/completions';
+
+ VolcanoAIService() : apiKey = dotenv.env['VOLCANO_AI_API_KEY'] ?? '' {
+ if (apiKey.isEmpty) {
+ throw VolcanoAIException('火山AI配置信息不完整,请检查环境变量');
+ }
+ }
+
+ // 生成个性化问候语
+ Stream generateGreeting({
+ required String agentName,
+ required String systemPrompt,
+ }) {
+ final messages = [
+ {'role': 'system', 'content': systemPrompt},
+ {'role': 'user', 'content': '请用一句简短的话向我打个招呼,要符合你的身份和性格特点,不要超过18个字。'},
+ ];
+
+ return sendMessageStream(
+ messages: messages,
+ systemPrompt: systemPrompt,
+ );
+ }
+
+ // 非流式输出方法
+ Future sendMessage({
+ required List