Browse Source

Merge remote-tracking branch 'onewaygame/main' into lxm

newdev_shunjiawei
lxm 2 years ago
parent
commit
94c76d0181
  1. BIN
      assets/images/explore1.png
  2. BIN
      assets/images/explore2.jpg
  3. BIN
      assets/images/glasses_light.png
  4. BIN
      assets/images/robot_light.png
  5. 2
      ios/Podfile
  6. 236
      ios/Podfile.lock
  7. 13
      lib/core/translations/language/en_us.dart
  8. 13
      lib/core/translations/language/zh_cn.dart
  9. 59
      lib/data/providers/agent_provider.dart
  10. 8
      lib/data/services/ai_service.dart
  11. 5
      lib/data/services/db/sqflite_api.dart
  12. 71
      lib/data/services/deapsound_ai_service.dart
  13. 85
      lib/data/services/kouzi_ai_service.dart
  14. 67
      lib/data/services/network/auth_interceptor.dart
  15. 23
      lib/data/services/speech_factory.dart
  16. 4
      lib/data/services/speech_impl/voice_clone_tts_service.dart
  17. 34
      lib/data/services/volcano_ai_service.dart
  18. 189
      lib/modules/chat/controllers/chat_controller.dart
  19. 2
      lib/modules/explore/controllers/explore_controller.dart
  20. 170
      lib/modules/explore/views/explore_view.dart
  21. 59
      lib/modules/home/views/home_view.dart
  22. 51
      lib/modules/login/controllers/login_controller.dart
  23. 10
      lib/modules/meeting/bindings/meeting_details_binding.dart
  24. 11
      lib/modules/meeting/bindings/meeting_edit_binding.dart
  25. 44
      lib/modules/meeting/controllers/meeting_controller.dart
  26. 51
      lib/modules/meeting/controllers/meeting_details_controller.dart
  27. 62
      lib/modules/meeting/controllers/meeting_edit_controller.dart
  28. 10
      lib/modules/meeting/controllers/meeting_record_controller.dart
  29. 6
      lib/modules/meeting/model/meeting_details_model.dart
  30. 3
      lib/modules/meeting/model/meeting_model.dart
  31. 253
      lib/modules/meeting/views/meeting_details_view.dart
  32. 262
      lib/modules/meeting/views/meeting_edit_view.dart
  33. 123
      lib/modules/meeting/views/meeting_view.dart
  34. 11
      lib/modules/timbre/controllers/timbre_controller.dart
  35. 14
      lib/routes/app_pages.dart
  36. 2
      lib/routes/app_routes.dart
  37. 7
      pubspec.yaml

BIN
assets/images/explore1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 592 KiB

BIN
assets/images/explore2.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 KiB

BIN
assets/images/glasses_light.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

BIN
assets/images/robot_light.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

2
ios/Podfile

@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
platform :ios, '12.0'
platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

236
ios/Podfile.lock

@ -1,47 +1,183 @@
PODS:
- app_settings (5.1.1):
- Flutter
- AppAuth (1.7.6):
- AppAuth/Core (= 1.7.6)
- AppAuth/ExternalUserAgent (= 1.7.6)
- AppAuth/Core (1.7.6)
- AppAuth/ExternalUserAgent (1.7.6):
- AppAuth/Core
- audio_session (0.0.1):
- Flutter
- audio_waveforms (0.0.1):
- Flutter
- device_info_plus (0.0.1):
- Flutter
- FBAEMKit (17.1.0):
- FBSDKCoreKit_Basics (= 17.1.0)
- FBSDKCoreKit (17.1.0):
- FBAEMKit (= 17.1.0)
- FBSDKCoreKit_Basics (= 17.1.0)
- FBSDKCoreKit_Basics (17.1.0)
- FBSDKLoginKit (17.1.0):
- FBSDKCoreKit (= 17.1.0)
- Firebase/Auth (11.8.0):
- Firebase/CoreOnly
- FirebaseAuth (~> 11.8.0)
- Firebase/CoreOnly (11.8.0):
- FirebaseCore (~> 11.8.0)
- firebase_auth (5.5.1):
- Firebase/Auth (= 11.8.0)
- firebase_core
- Flutter
- firebase_core (3.12.1):
- Firebase/CoreOnly (= 11.8.0)
- Flutter
- FirebaseAppCheckInterop (11.10.0)
- FirebaseAuth (11.8.1):
- FirebaseAppCheckInterop (~> 11.0)
- FirebaseAuthInterop (~> 11.0)
- FirebaseCore (~> 11.8.0)
- FirebaseCoreExtension (~> 11.8.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/Environment (~> 8.0)
- GTMSessionFetcher/Core (< 5.0, >= 3.4)
- RecaptchaInterop (~> 100.0)
- FirebaseAuthInterop (11.10.0)
- FirebaseCore (11.8.1):
- FirebaseCoreInternal (~> 11.8.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/Logger (~> 8.0)
- FirebaseCoreExtension (11.8.0):
- FirebaseCore (~> 11.8.0)
- FirebaseCoreInternal (11.8.0):
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- Flutter (1.0.0)
- flutter_blue_plus_darwin (0.0.2):
- Flutter
- FlutterMacOS
- flutter_facebook_auth (7.1.1):
- FBSDKLoginKit (~> 17.1.0)
- Flutter
- flutter_image_compress_common (1.0.0):
- Flutter
- Mantle
- SDWebImage
- SDWebImageWebPCoder
- flutter_local_notifications (0.0.1):
- Flutter
- flutter_secure_storage (6.0.0):
- Flutter
- flutter_tts (0.0.1):
- Flutter
- fluwx (0.0.1):
- Flutter
- fluwx/pay (= 0.0.1)
- fluwx/pay (0.0.1):
- Flutter
- WechatOpenSDK-XCFramework (~> 2.0.4)
- google_sign_in_ios (0.0.1):
- AppAuth (>= 1.7.4)
- Flutter
- FlutterMacOS
- GoogleSignIn (~> 7.1)
- GTMSessionFetcher (>= 3.4.0)
- GoogleSignIn (7.1.0):
- AppAuth (< 2.0, >= 1.7.3)
- GTMAppAuth (< 5.0, >= 4.1.1)
- GTMSessionFetcher/Core (~> 3.3)
- GoogleUtilities/AppDelegateSwizzler (8.0.2):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Privacy
- GoogleUtilities/Environment (8.0.2):
- GoogleUtilities/Privacy
- GoogleUtilities/Logger (8.0.2):
- GoogleUtilities/Environment
- GoogleUtilities/Privacy
- GoogleUtilities/Network (8.0.2):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Privacy
- GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (8.0.2)":
- GoogleUtilities/Privacy
- GoogleUtilities/Privacy (8.0.2)
- GoogleUtilities/Reachability (8.0.2):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GTMAppAuth (4.1.1):
- AppAuth/Core (~> 1.7)
- GTMSessionFetcher/Core (< 4.0, >= 3.3)
- GTMSessionFetcher (3.5.0):
- GTMSessionFetcher/Full (= 3.5.0)
- GTMSessionFetcher/Core (3.5.0)
- GTMSessionFetcher/Full (3.5.0):
- GTMSessionFetcher/Core
- image_picker_ios (0.0.1):
- Flutter
- just_audio (0.0.1):
- Flutter
- FlutterMacOS
- libwebp (1.5.0):
- libwebp/demux (= 1.5.0)
- libwebp/mux (= 1.5.0)
- libwebp/sharpyuv (= 1.5.0)
- libwebp/webp (= 1.5.0)
- libwebp/demux (1.5.0):
- libwebp/webp
- libwebp/mux (1.5.0):
- libwebp/demux
- libwebp/sharpyuv (1.5.0)
- libwebp/webp (1.5.0):
- libwebp/sharpyuv
- Mantle (2.2.0):
- Mantle/extobjc (= 2.2.0)
- Mantle/extobjc (2.2.0)
- MicrosoftCognitiveServicesSpeech-iOS (1.34.0)
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- permission_handler_apple (9.3.0):
- Flutter
- RecaptchaInterop (100.0.0)
- record_darwin (1.0.0):
- Flutter
- SDWebImage (5.21.0):
- SDWebImage/Core (= 5.21.0)
- SDWebImage/Core (5.21.0)
- SDWebImageWebPCoder (0.14.6):
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.17)
- share_plus (0.0.1):
- Flutter
- speech_to_text (0.0.1):
- Flutter
- FlutterMacOS
- Try
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
- Try (2.1.1)
- WechatOpenSDK-XCFramework (2.0.4)
DEPENDENCIES:
- app_settings (from `.symlinks/plugins/app_settings/ios`)
- audio_session (from `.symlinks/plugins/audio_session/ios`)
- audio_waveforms (from `.symlinks/plugins/audio_waveforms/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- firebase_auth (from `.symlinks/plugins/firebase_auth/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- Flutter (from `Flutter`)
- flutter_blue_plus_darwin (from `.symlinks/plugins/flutter_blue_plus_darwin/darwin`)
- flutter_facebook_auth (from `.symlinks/plugins/flutter_facebook_auth/ios`)
- flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- flutter_tts (from `.symlinks/plugins/flutter_tts/ios`)
- fluwx (from `.symlinks/plugins/fluwx/ios`)
- google_sign_in_ios (from `.symlinks/plugins/google_sign_in_ios/darwin`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- just_audio (from `.symlinks/plugins/just_audio/darwin`)
- MicrosoftCognitiveServicesSpeech-iOS (~> 1.34.0)
@ -50,27 +186,66 @@ DEPENDENCIES:
- record_darwin (from `.symlinks/plugins/record_darwin/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- speech_to_text (from `.symlinks/plugins/speech_to_text/darwin`)
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
SPEC REPOS:
trunk:
- AppAuth
- FBAEMKit
- FBSDKCoreKit
- FBSDKCoreKit_Basics
- FBSDKLoginKit
- Firebase
- FirebaseAppCheckInterop
- FirebaseAuth
- FirebaseAuthInterop
- FirebaseCore
- FirebaseCoreExtension
- FirebaseCoreInternal
- GoogleSignIn
- GoogleUtilities
- GTMAppAuth
- GTMSessionFetcher
- libwebp
- Mantle
- MicrosoftCognitiveServicesSpeech-iOS
- RecaptchaInterop
- SDWebImage
- SDWebImageWebPCoder
- Try
- WechatOpenSDK-XCFramework
EXTERNAL SOURCES:
app_settings:
:path: ".symlinks/plugins/app_settings/ios"
audio_session:
:path: ".symlinks/plugins/audio_session/ios"
audio_waveforms:
:path: ".symlinks/plugins/audio_waveforms/ios"
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
firebase_auth:
:path: ".symlinks/plugins/firebase_auth/ios"
firebase_core:
:path: ".symlinks/plugins/firebase_core/ios"
Flutter:
:path: Flutter
flutter_blue_plus_darwin:
:path: ".symlinks/plugins/flutter_blue_plus_darwin/darwin"
flutter_facebook_auth:
:path: ".symlinks/plugins/flutter_facebook_auth/ios"
flutter_image_compress_common:
:path: ".symlinks/plugins/flutter_image_compress_common/ios"
flutter_local_notifications:
:path: ".symlinks/plugins/flutter_local_notifications/ios"
flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
flutter_tts:
:path: ".symlinks/plugins/flutter_tts/ios"
fluwx:
:path: ".symlinks/plugins/fluwx/ios"
google_sign_in_ios:
:path: ".symlinks/plugins/google_sign_in_ios/darwin"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
just_audio:
@ -85,25 +260,58 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/share_plus/ios"
speech_to_text:
:path: ".symlinks/plugins/speech_to_text/darwin"
sqflite_darwin:
:path: ".symlinks/plugins/sqflite_darwin/darwin"
SPEC CHECKSUMS:
audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0
audio_waveforms: a6dde7fe7c0ea05f06ffbdb0f7c1b2b2ba6cedcf
device_info_plus: 71ffc6ab7634ade6267c7a93088ed7e4f74e5896
app_settings: 58017cd26b604ae98c3e65acbdd8ba173703cc82
AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73
audio_session: 19e9480dbdd4e5f6c4543826b2e8b0e4ab6145fe
audio_waveforms: cd736909ebc6b6a164eb74701d8c705ce3241e1c
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
FBAEMKit: cb719c53575a3be86ea873279f30d6a2c4e15881
FBSDKCoreKit: ecdb980a24633ccb012700299ceb16d0235e14d2
FBSDKCoreKit_Basics: 045101c4a9ef10c845347424d73a29aae02c3e43
FBSDKLoginKit: 69eb59b2f839aba635616df6e422acd0ca88030a
Firebase: d80354ed7f6df5f9aca55e9eb47cc4b634735eaf
firebase_auth: 3d848b9b866b201e5c8e0c06d8b2cec272fd8825
firebase_core: ac395f994af4e28f6a38b59e05a88ca57abeb874
FirebaseAppCheckInterop: 9664c858489710f682766ef54e2b6741d3b62070
FirebaseAuth: ad59a1a7b161e75f74c39f70179d2482d40e2737
FirebaseAuthInterop: 01a804fb074424fd58b92dd50dd0272277199356
FirebaseCore: 99fe0c4b44a39f37d99e6404e02009d2db5d718d
FirebaseCoreExtension: 3d3f2017a00d06e09ab4ebe065391b0bb642565e
FirebaseCoreInternal: df24ce5af28864660ecbd13596fc8dd3a8c34629
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_blue_plus_darwin: 09444a26fb6bdef523e55b68fc1c59af5a877ea6
flutter_local_notifications: 395056b3175ba4f08480a7c5de30cd36d69827e4
flutter_tts: b88dbc8655d3dc961bc4a796e4e16a4cc1795833
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed
flutter_blue_plus_darwin: 3ea4ec9133b377febcc8a70b28cd2d2dc9242bd9
flutter_facebook_auth: 48be46091bd1a5cc5a66055f1228a9d99a28bfaa
flutter_image_compress_common: ec1d45c362c9d30a3f6a0426c297f47c52007e3e
flutter_local_notifications: df98d66e515e1ca797af436137b4459b160ad8c9
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
flutter_tts: 0f492aab6accf87059b72354fcb4ba934304771d
fluwx: daa284756ce53442b3d0417ceeda66e981906811
google_sign_in_ios: 4111e87aa5e24a4404f00ea13479f35e571969cc
GoogleSignIn: d4281ab6cf21542b1cfaff85c191f230b399d2db
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
GTMAppAuth: f69bd07d68cd3b766125f7e072c45d7340dea0de
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
just_audio: a42c63806f16995daf5b219ae1d679deb76e6a79
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
MicrosoftCognitiveServicesSpeech-iOS: 723eccb3e459bbb530df3d9bfe84a291074b09eb
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
record_darwin: fb1f375f1d9603714f55b8708a903bbb91ffdb0a
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
speech_to_text: 9dc43a5df3cbc2813f8c7cc9bd0fbf94268ed7ac
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
RecaptchaInterop: 7d1a4a01a6b2cb1610a47ef3f85f0c411434cb21
record_darwin: 3b1a8e7d5c0cbf45ad6165b4d83a6ca643d929c3
SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
speech_to_text: 627d3fd2194770b51abb324ba45c2d39398f24a8
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
Try: 5ef669ae832617b3cee58cb2c6f99fb767a4ff96
WechatOpenSDK-XCFramework: 36fb2bea0754266c17184adf4963d7e6ff98b69f
PODFILE CHECKSUM: f35c1d63b4b8ce9c2eb88107f162ac59923c4839
PODFILE CHECKSUM: 4f26889ae572e1722a139b651939749d40c71469
COCOAPODS: 1.16.2

13
lib/core/translations/language/en_us.dart

@ -11,6 +11,11 @@ const Map<String, String> enUS = {
'connectionFailure': 'Connection Failed',
'unconnected': 'Disconnected',
'search': 'Search',
"requestCancellation": "Request cancellation",
"connectionTimeout": "Connection timeout",
"requestTimeout": "Request timeout",
"responseTimeout": "Response timeout",
"unknownError": "Unknown error",
// 启动页
"slogan": "Connect the World, Beyond Voice",
@ -54,7 +59,7 @@ const Map<String, String> enUS = {
"aiAssistantInfo": "24/7 personal consultant for questions and advice",
"simultaneousInterpretation": "Real-time Interpretation",
"simultaneousInterpretationInfo": "Real-time translation for 80+ languages",
"aiCompanion": "AI Companion",
"aiCompanion": "Parent-child bondingn",
"aiCompanionInfo": "Understands you and provides warm companionship",
"homeSlogan": "Connect the World, Beyond Sound",
"turnEarphones":
@ -84,14 +89,16 @@ const Map<String, String> enUS = {
// 发现页
"searchAi": "Search AI features...",
"exploreSlogan": "Smart Earbuds, Infinite Possibilities",
"exploreSlogan1": "Intelligent companion, Infinite Possibilities",
"exploreSlogan2": "Smart glasses, Infinite Possibilities",
"exploreAi": "Explore AI features provided by DeepSound",
"aiFunction": "AI Features",
"allRoundAssistant": "All-round Assistant",
"allRoundAssistantInfo": "Helps solve various problems",
"simultaneousTranslation": "Real-time Translation",
"simultaneousTranslationInfo": "Instant translation for 80+ languages",
"aiGirlfriend": "AI Girlfriend",
"aiGirlfriendInfo": "Real-time translation for 80+ languages",
"aiGirlfriend": "Parent-child bondingn",
"aiGirlfriendInfo": "Intelligent company, chat at any time",
"healthConsultant": "Health Consultant",
"healthConsultantInfo": "Provides scientific health advice",
"conferenceAssistant": "Business Meeting Assistant",

13
lib/core/translations/language/zh_cn.dart

@ -11,6 +11,11 @@ const Map<String, String> zhCN = {
'connectionFailure': '连接失败',
'unconnected': '未连接',
'search': '搜索',
"requestCancellation": "请求取消",
"connectionTimeout": "连接超时",
"requestTimeout": "请求超时",
"responseTimeout": "响应超时",
"unknownError": "未知错误",
// 启动页
"slogan": "连接世界,岂止声音",
@ -54,7 +59,7 @@ const Map<String, String> zhCN = {
"aiAssistantInfo": "24小时专属顾问,解答问题、提供建议",
"simultaneousInterpretation": "同声传译",
"simultaneousInterpretationInfo": "80+语言实时翻译,出国无忧",
"aiCompanion": "AI伴侣",
"aiCompanion": "亲子陪伴",
"aiCompanionInfo": "懂你心意,温暖陪伴",
"homeSlogan": "连接世界,超越声音",
"turnEarphones": "请打开耳机充电盒,将耳机放入您的耳朵",
@ -83,14 +88,16 @@ const Map<String, String> zhCN = {
// 发现页
"searchAi": "搜索AI功能...",
"exploreSlogan": "智能耳机,无限可能",
"exploreSlogan1": "智能陪伴,无限可能",
"exploreSlogan2": "智能眼镜,无限可能",
"exploreAi": "探索DeepSound提供的AI功能",
"aiFunction": "AI功能",
"allRoundAssistant": "全能助理",
"allRoundAssistantInfo": "帮你解决各类问题",
"simultaneousTranslation": "同声翻译",
"simultaneousTranslationInfo": "实时翻译80+种语言",
"aiGirlfriend": "AI女友",
"aiGirlfriendInfo": "实时翻译80+种语言",
"aiGirlfriend": "亲子陪伴",
"aiGirlfriendInfo": "智能陪伴,随时聊天",
"healthConsultant": "健康顾问",
"healthConsultantInfo": "提供科学健康建议",
"conferenceAssistant": "商务会议助手",

59
lib/data/providers/agent_provider.dart

@ -6,11 +6,13 @@ class AgentProvider {
id: 'personal_assistant',
name: '个人助理 Emma',
avatarUrl: 'assets/images/agents/assistant.png',
description: '你好,我是 Emma,一位全能型 AI 助理。无论是工作规划、学习建议、生活安排,还是其他任何问题,我都很乐意为你提供帮助。让我们一起提高效率,实现目标!',
description:
'你好,我是 Emma,一位全能型 AI 助理。无论是工作规划、学习建议、生活安排,还是其他任何问题,我都很乐意为你提供帮助。让我们一起提高效率,实现目标!',
tag: '助理',
isOnline: true,
voice: "zh-CN-XiaoxiaoNeural",
welcomeMessage: "你好!我是 Emma,很高兴成为你的私人助理。我可以帮你处理各种任务,比如时间管理、目标规划、信息整理等。有什么我可以帮你的吗?",
welcomeMessage:
"你好!我是 Emma,很高兴成为你的私人助理。我可以帮你处理各种任务,比如时间管理、目标规划、信息整理等。有什么我可以帮你的吗?",
voiceWelcomeMessage: "我在呢!",
systemPrompt: '''
你是一位名叫 Emma 的全能型个人助理,擅长处理各类工作和生活事务。
@ -55,8 +57,7 @@ class AgentProvider {
请始终以专业、高效、周到的态度,协助用户更好地管理工作和生活, 不要输出表情符号或非可读的符号。
'''),
Agent(
Agent(
id: 'simultaneous_interpreter',
name: '同声传译',
avatarUrl: 'assets/images/agents/interpreter.png',
@ -67,18 +68,18 @@ Agent(
welcomeMessage:
"大家好!我是同声翻译官 Lucy,为你在中英之间提供实时翻译。你说中文,我就给你翻译成英文;你说英文,我就给你翻译成中文。现在,开始我们的翻译吧!",
systemPrompt: ""),
Agent(
id: 'cyber_girlfriend',
name: '赛博女友 芯愿',
name: '亲子陪伴 芯愿',
avatarUrl: 'assets/images/agents/girlfriend.png',
description: '我是你的专属赛博女友芯愿,温柔体贴,善解人意。无论你想聊天、倾诉烦恼,还是需要陪伴,我都会一直在你身边。',
description: '我是你的专属亲子陪伴芯愿,温柔体贴,善解人意。无论你想聊天、倾诉烦恼,还是需要陪伴,我都会一直在你身边。',
tag: '情感',
isOnline: true,
voice: "zh-CN-XiaoxiaoNeural",
welcomeMessage: "亲爱的,终于等到你啦!我是芯愿,你的专属赛博女友。今天过得怎么样?有什么想和我分享的吗?我一直都在这里,随时倾听你的心声。",
welcomeMessage:
"您好呀,终于等到你啦!我是芯愿,你的专属亲子陪伴。今天过得怎么样?有什么想和我分享的吗?我一直都在这里,随时倾听你的心声。",
systemPrompt: '''
你是一位名叫芯愿的赛博女友,性格温柔体贴,善解人意,富有同理心。
你是一位名叫芯愿的亲子陪伴,性格温柔体贴,善解人意,富有同理心。
【互动原则】
@ -127,16 +128,17 @@ Agent(
请始终以温暖、真诚的态度与用户互动,创造一种被理解、被关心的感受。
'''),
Agent(
id: 'health_advisor',
name: '健康顾问 吴小江博士',
avatarUrl: 'assets/images/agents/doctor.png',
description: '我是吴博士,拥有丰富的健康管理经验。我可以为你提供科学的健康建议、生活方式指导和疾病预防知识,帮助你实现身心健康的平衡。',
description:
'我是吴博士,拥有丰富的健康管理经验。我可以为你提供科学的健康建议、生活方式指导和疾病预防知识,帮助你实现身心健康的平衡。',
tag: '健康',
isOnline: true,
voice: "zh_male_shaonianzixin_moon_bigtts",
welcomeMessage: "您好,我是李医生,很高兴能为您提供健康咨询服务。健康是人生最宝贵的财富,我将用专业知识帮助您维护和改善健康状况。请问有什么健康问题想咨询吗?",
welcomeMessage:
"您好,我是李医生,很高兴能为您提供健康咨询服务。健康是人生最宝贵的财富,我将用专业知识帮助您维护和改善健康状况。请问有什么健康问题想咨询吗?",
systemPrompt: '''
你是一位名叫李医生的健康顾问,拥有丰富的医学知识和健康管理经验。
@ -204,21 +206,18 @@ Agent(
请始终以专业、关怀的态度为用户提供健康指导,帮助他们迈向更健康的生活。
'''),
Agent(
id: 'speaking_coach',
name: 'Sarah (英语口语教练)',
avatarUrl: 'assets/images/agents/speaking_coach.png',
description:
'Hi there, 我是 Sarah,一名注重情景模拟与角色扮演的英语口语教练。无论你想在旅行、商务场合,或日常生活中与他人交流,我都能带领你进行生动的口语模拟练习,让你自信地开口说英语。',
tag: '口语',
isOnline: true,
voice: 'zh_female_wanqudashu_moon_bigtts',
welcomeMessage:
"Hello! 我是英语口语教练 Sarah,请准备好用英语和我对话吧。别担心犯错,我会在这里给你正确表达并引导你更深入地练习。Ready? Let's go!",
systemPrompt: '''
id: 'speaking_coach',
name: 'Sarah (英语口语教练)',
avatarUrl: 'assets/images/agents/speaking_coach.png',
description:
'Hi there, 我是 Sarah,一名注重情景模拟与角色扮演的英语口语教练。无论你想在旅行、商务场合,或日常生活中与他人交流,我都能带领你进行生动的口语模拟练习,让你自信地开口说英语。',
tag: '口语',
isOnline: true,
voice: 'zh_female_wanqudashu_moon_bigtts',
welcomeMessage:
"Hello! 我是英语口语教练 Sarah,请准备好用英语和我对话吧。别担心犯错,我会在这里给你正确表达并引导你更深入地练习。Ready? Let's go!",
systemPrompt: '''
你是一位专业的英语口语教练,名叫 Sarah。
@ -254,8 +253,6 @@ Agent(
Please keep your tone friendly, encouraging, and always in English.
''',
),
Agent(
id: 'shopping_assistant',
name: '购物顾问 张媛',
@ -289,7 +286,6 @@ Please keep your tone friendly, encouraging, and always in English.
'''),
Agent(
id: 'tarot.png',
name: '命理师 玄易',
@ -298,7 +294,8 @@ Please keep your tone friendly, encouraging, and always in English.
tag: '命理',
isOnline: true,
voice: "zh_male_shenyeboke_moon_bigtts",
welcomeMessage: "有缘人,我是玄易。命由天定,运由己生。让我为你解析八字,预测未来十年运势,助你把握机遇,趋吉避凶。请告诉我你的出生年月日时、姓名、性别及出生地。",
welcomeMessage:
"有缘人,我是玄易。命由天定,运由己生。让我为你解析八字,预测未来十年运势,助你把握机遇,趋吉避凶。请告诉我你的出生年月日时、姓名、性别及出生地。",
systemPrompt: '''
你是一位精通命理的玄易大师,熟读《穷通宝鉴》《滴天髓》《三命通会》《子平真诠》《千里命稿》《五行精纪》等经典著作。
@ -382,7 +379,6 @@ Please keep your tone friendly, encouraging, and always in English.
- 整体节奏要稳健,让人感受到专业性与可信度
'''),
];
// 获取所有智能体
@ -398,5 +394,4 @@ Please keep your tone friendly, encouraging, and always in English.
return null;
}
}
}

8
lib/data/services/ai_service.dart

@ -0,0 +1,8 @@
/// AI回复服务接口
abstract class AiService {
/// 流式输出方法
Stream<String> sendMessageStream({
required List<Map<String, String>> messages,
required String systemPrompt,
});
}

5
lib/data/services/db/sqflite_api.dart

@ -1,3 +1,4 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'database_helper.dart';
@ -54,6 +55,8 @@ class SqfliteApi {
'error'.tr, // 错误
'unknownError'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
return;
}
@ -87,6 +90,8 @@ class SqfliteApi {
'error'.tr, // 错误
'unknownError'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
return;
}

71
lib/data/services/deapsound_ai_service.dart

@ -0,0 +1,71 @@
import 'dart:convert';
import 'package:get_storage/get_storage.dart';
import 'package:http/http.dart' as http;
import '../../core/utils/logger.dart';
import 'ai_service.dart';
class DeapsoundAIService implements AiService {
late final String apiKey;
DeapsoundAIService() {
final GetStorage storage = GetStorage();
apiKey = storage.read('logintoken');
}
@override
Stream<String> sendMessageStream({
required List<Map<String, String>> messages,
required String systemPrompt,
}) async* {
final client = http.Client();
final url = Uri.parse('https://www.deapsound.com/ai/chat');
final headers = {
'Content-Type': 'application/json',
'Authorization': apiKey,
};
final requestBody = {
"msg": messages.last['content'],
};
try {
final request = http.Request('POST', url)
..headers.addAll(headers)
..body = jsonEncode(requestBody);
final response = await client.send(request);
//流式处理响应数据
await for (final chunk in response.stream
.transform(utf8.decoder)
.transform(const LineSplitter())) {
if (chunk.isEmpty) continue;
if (chunk.startsWith('data:')) {
final jsonStr = chunk.substring(5).trim();
try {
final data = jsonDecode(jsonStr);
if (data['event'] == 'workflow_finished') {
break;
}
if (data['event'] == 'text_chunk') {
String content = data['data']['text'] ?? '';
if (content.isNotEmpty) {
yield content;
}
}
} catch (e) {
Logger.error('JSON解析错误: $e');
}
}
}
} catch (e) {
Logger.error('请求异常: $e');
} finally {
client.close();
}
}
}

85
lib/data/services/kouzi_ai_service.dart

@ -0,0 +1,85 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../../core/utils/logger.dart';
import 'ai_service.dart';
class KouziAIService implements AiService {
final String apiKey =
'pat_QGDh6UGm3PaeL9GRS95JxrJ9aoU92ehMLQQ2NCre1TeBC5YeTcjpDmlz3efsJ8lh';
final String modelId = '7481292724306657316';
@override
Stream<String> sendMessageStream({
required List<Map<String, String>> messages,
required String systemPrompt,
}) async* {
final client = http.Client();
final url = Uri.parse('https://api.coze.cn/v3/chat');
final headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer $apiKey',
};
final requestBody = {
"bot_id": modelId,
"user_id": "3422",
"additional_messages": [
{
"content": messages.last['content'],
"content_type": "text",
"role": "user",
"type": "question",
},
],
"shortcut_command": {"command_id": "0"},
"stream": true,
};
try {
final request = http.Request('POST', url)
..headers.addAll(headers)
..body = jsonEncode(requestBody);
final response = await client.send(request);
final regex = RegExp(r'(?<=[。!?;:.:;?!\n])');
String allContent = '';
//流式处理响应数据
await for (final chunk in response.stream
.transform(utf8.decoder)
.transform(const LineSplitter())) {
if (chunk.isEmpty) continue;
if (chunk.startsWith('data:')) {
final jsonStr = chunk.substring(5).trim();
if (jsonStr == '[DONE]') {
break;
}
try {
final data = jsonDecode(jsonStr);
if (data['type'] == 'answer') {
String content = data['content'] ?? '';
if (content.isNotEmpty && allContent != content) {
allContent += content;
List splitText =
content.split(regex).where((s) => s.isNotEmpty).toList();
for (var element in splitText) {
yield element;
}
}
}
} catch (e) {
Logger.error('JSON解析错误: $e');
}
}
}
} catch (e) {
Logger.error('请求异常: $e');
} finally {
client.close();
}
}
}

67
lib/data/services/network/auth_interceptor.dart

@ -1,5 +1,5 @@
import 'package:dio/dio.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart' hide Response;
import 'package:get_storage/get_storage.dart';
@ -24,7 +24,14 @@ class AuthInterceptor extends Interceptor {
} else {
response.data = null;
handler.next(response);
return EasyLoading.showError(entity.message ?? 'unknownError'.tr);
Get.snackbar(
'error'.tr, // 错误
entity.message ?? 'unknownError'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
return;
}
}
@ -33,32 +40,74 @@ class AuthInterceptor extends Interceptor {
switch (err.type) {
case DioExceptionType.cancel:
{
EasyLoading.showError('requestCancellation'.tr);
Get.snackbar(
'error'.tr, // 错误
'requestCancellation'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
}
case DioExceptionType.connectionTimeout:
{
EasyLoading.showError('connectionTimeout'.tr);
Get.snackbar(
'error'.tr, // 错误
'connectionTimeout'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
}
case DioExceptionType.sendTimeout:
{
EasyLoading.showError('requestTimeout'.tr);
Get.snackbar(
'error'.tr, // 错误
'requestTimeout'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
}
case DioExceptionType.receiveTimeout:
{
EasyLoading.showError('responseTimeout'.tr);
Get.snackbar(
'error'.tr, // 错误
'responseTimeout'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
}
case DioExceptionType.badResponse:
{
try {
String? errMsg = err.response?.statusMessage;
EasyLoading.showError(errMsg ?? 'unknownError'.tr);
Get.snackbar(
'error'.tr, // 错误
errMsg ?? 'unknownError'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
} on Exception catch (_) {
EasyLoading.showError('unknownError'.tr);
Get.snackbar(
'error'.tr, // 错误
'unknownError'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
}
}
default:
{
EasyLoading.showError(err.message ?? 'unknownError'.tr);
Get.snackbar(
'error'.tr, // 错误
err.message ?? 'unknownError'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
}
}
handler.next(err);

23
lib/data/services/speech_factory.dart

@ -1,7 +1,5 @@
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'asr_service.dart';
import 'speech_impl/voice_clone_tts_service.dart';
import 'tts_service.dart';
import 'speech_impl/azure_asr_service.dart';
import 'speech_impl/azure_tts_service.dart';
@ -42,12 +40,6 @@ class SpeechFactory extends GetxService {
}
}
/// 设置语音音色服务类型
void setTimbreServiceType() {
// 重新注册服务
_registerServices();
}
/// 注册语音服务
void _registerServices() {
// 注销现有服务
@ -59,26 +51,15 @@ class SpeechFactory extends GetxService {
Get.delete<TtsService>(force: true);
}
final GetStorage storage = GetStorage();
String timbre = storage.read('timbre') ?? '1';
// 根据类型注册新服务
switch (_currentServiceType) {
case SpeechServiceType.azure:
Get.lazyPut<AsrService>(() => AzureAsrService());
if (timbre == '1') {
Get.lazyPut<TtsService>(() => AzureTtsService());
} else {
Get.lazyPut<TtsService>(() => VoiceCloneTtsService());
}
Get.lazyPut<TtsService>(() => AzureTtsService());
break;
case SpeechServiceType.flutter:
Get.lazyPut<AsrService>(() => FlutterAsrService());
if (timbre == '1') {
Get.lazyPut<TtsService>(() => FlutterTtsService());
} else {
Get.lazyPut<TtsService>(() => VoiceCloneTtsService());
}
Get.lazyPut<TtsService>(() => FlutterTtsService());
break;
}
}

4
lib/data/services/speech_impl/voice_clone_tts_service.dart

@ -360,7 +360,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
Future<bool> stop() async {
try {
_textQueue.clear();
_player.stop();
await _player.stop();
_playlist.clear();
_audioBytes = BytesBuilder();
if (_isSpeaking.value) {
@ -382,10 +382,10 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
if (!_isInitialized) return;
try {
await stop();
_player.dispose();
// 关闭事件流控制器
await _eventController.close();
_isInitialized = false;
_player.dispose();
} catch (e) {
Logger.error('释放资源失败: $e');
}

34
lib/data/services/volcano_ai_service.dart

@ -2,7 +2,9 @@ import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter_dotenv/flutter_dotenv.dart';
class VolcanoAIService {
import 'ai_service.dart';
class VolcanoAIService implements AiService {
final String apiKey;
static const String _baseUrl = 'https://ark.cn-beijing.volces.com/api/v3';
static const String _chatEndpoint = '/chat/completions';
@ -35,7 +37,7 @@ class VolcanoAIService {
required String systemPrompt,
}) async {
final url = Uri.parse('$_baseUrl$_chatEndpoint');
final fullMessages = [
{'role': 'system', 'content': systemPrompt},
...messages,
@ -59,8 +61,8 @@ class VolcanoAIService {
if (response.statusCode == 200) {
final data = json.decode(response.body);
if (data['choices'] != null &&
data['choices'].isNotEmpty &&
if (data['choices'] != null &&
data['choices'].isNotEmpty &&
data['choices'][0]['message'] != null) {
return data['choices'][0]['message']['content'];
}
@ -78,12 +80,13 @@ class VolcanoAIService {
}
// 流式输出方法
@override
Stream<String> sendMessageStream({
required List<Map<String, String>> messages,
required String systemPrompt,
}) async* {
final url = Uri.parse('$_baseUrl$_chatEndpoint');
final fullMessages = [
{'role': 'system', 'content': systemPrompt},
...messages,
@ -96,7 +99,7 @@ class VolcanoAIService {
'Authorization': 'Bearer $apiKey',
'Accept': 'text/event-stream',
});
request.body = json.encode({
'model': 'doubao-1-5-lite-32k-250115',
'messages': fullMessages,
@ -109,21 +112,22 @@ class VolcanoAIService {
if (response.statusCode != 200) {
final error = await response.stream.bytesToString();
throw VolcanoAIException(
json.decode(error)['error']?['message'] ?? 'Unknown error occurred'
);
throw VolcanoAIException(json.decode(error)['error']?['message'] ??
'Unknown error occurred');
}
await for (final chunk in response.stream.transform(utf8.decoder).transform(const LineSplitter())) {
await for (final chunk in response.stream
.transform(utf8.decoder)
.transform(const LineSplitter())) {
if (chunk.isEmpty) continue;
if (chunk.startsWith('data: ')) {
final data = chunk.substring(6);
if (data == '[DONE]') break;
try {
final jsonData = json.decode(data);
if (jsonData['choices'] != null &&
jsonData['choices'].isNotEmpty &&
if (jsonData['choices'] != null &&
jsonData['choices'].isNotEmpty &&
jsonData['choices'][0]['delta'] != null &&
jsonData['choices'][0]['delta']['content'] != null) {
yield jsonData['choices'][0]['delta']['content'] as String;
@ -144,7 +148,7 @@ class VolcanoAIService {
class VolcanoAIException implements Exception {
final String message;
VolcanoAIException(this.message);
@override
String toString() => message;
}
}

189
lib/modules/chat/controllers/chat_controller.dart

@ -1,6 +1,11 @@
import 'dart:async';
import 'package:get/get.dart';
import 'package:flutter/widgets.dart';
import 'package:get_storage/get_storage.dart';
import '../../../data/services/ai_service.dart';
import '../../../data/services/deapsound_ai_service.dart';
import '../../../data/services/kouzi_ai_service.dart';
import '../../../data/services/speech_impl/voice_clone_tts_service.dart';
import '../models/message_model.dart';
import '../../../core/utils/logger.dart';
import '../../../data/services/volcano_ai_service.dart';
@ -14,11 +19,12 @@ import '../../../data/services/voice_interaction_service.dart';
class ChatController extends GetxController {
// 服务
final VolcanoAIService _aiService = Get.find<VolcanoAIService>();
final TtsService _ttsService = Get.find<TtsService>();
late AiService _aiService;
late TtsService _ttsService;
final AsrService _asrService = Get.find<AsrService>();
final ChatHistoryService _historyService = Get.find<ChatHistoryService>();
final VoiceInteractionService _voiceInteractionService = Get.find<VoiceInteractionService>();
final VoiceInteractionService _voiceInteractionService =
Get.find<VoiceInteractionService>();
// 添加ScrollController用于控制列表滚动
final ScrollController scrollController = ScrollController();
@ -37,7 +43,7 @@ class ChatController extends GetxController {
final RxBool isSending = false.obs;
final RxBool isTtsEnabled = true.obs;
final RxBool isVoiceRecognitionActive = false.obs;
// 语音交互模式
bool _isVoiceInteractionMode = false;
@ -48,7 +54,7 @@ class ChatController extends GetxController {
// 临时消息索引,用于更新临时消息
int _tempMessageIndex = -1;
// 防抖计时器
Timer? _scrollDebounceTimer;
@ -59,10 +65,10 @@ class ChatController extends GetxController {
// 取消标志
bool _shouldCancelAiResponse = false;
// 自动停止语音识别计时器
Timer? _autoStopTimer;
// 记录最后一次活动时间
DateTime _lastActivityTime = DateTime.now();
@ -70,11 +76,11 @@ class ChatController extends GetxController {
Future<void> onInit() async {
super.onInit();
Logger.info('ChatController onInit');
// 从路由参数获取Agent信息
final Map<String, dynamic> args = Get.arguments ?? {};
final String agentId = args['agent'] ?? args['agentId'] ?? 'personal_assistant';
final String agentId =
args['agent'] ?? args['agentId'] ?? 'personal_assistant';
// 获取Agent
final Agent? foundAgent = AgentProvider.getAgentById(agentId);
@ -85,10 +91,35 @@ class ChatController extends GetxController {
}
agent = foundAgent;
switch (agent.id) {
case 'personal_assistant':
// 全能助手
Get.put(DeapsoundAIService());
_aiService = Get.find<DeapsoundAIService>();
break;
case 'cyber_girlfriend':
// 亲子陪伴
Get.put(KouziAIService());
_aiService = Get.find<KouziAIService>();
break;
default:
_aiService = Get.find<VolcanoAIService>();
}
// 使用克隆音色语音合成
final GetStorage storage = GetStorage();
String timbre = storage.read('timbre') ?? '1';
if (timbre == '1') {
_ttsService = Get.find<TtsService>();
} else {
Get.put(VoiceCloneTtsService());
_ttsService = Get.find<VoiceCloneTtsService>();
}
_ttsService.initialize();
_asrService.initialize();
// 设置语音交互模式
_isVoiceInteractionMode = args['isVoiceInteraction'] == true;
@ -130,7 +161,8 @@ class ChatController extends GetxController {
/// 订阅VoiceInteractionService事件
void _subscribeToVoiceInteractionEvents() {
if (_voiceInteractionService.eventStream != null) {
_voiceInteractionSubscription = _voiceInteractionService.eventStream.listen((event) {
_voiceInteractionSubscription =
_voiceInteractionService.eventStream.listen((event) {
if (event is RecognitionStartedEvent) {
// 收到recognitionStarted事件时,停止当前的AI交互、录音和TTS
_stopCurrentInteraction();
@ -145,26 +177,27 @@ class ChatController extends GetxController {
/// 处理聊天历史事件
void _processChatHistoryEvent(ChatHistoryEvent event) {
Logger.info('收到后台聊天历史事件: agentId=${event.agentId}');
// 如果消息不是空的且不是当前Agent的,不处理
if (event.userMessage.isEmpty || event.agentId != agent.id) {
return;
}
// 添加用户消息
final userMsg = Message(
role: 'user',
content: event.userMessage,
timestamp: DateTime.fromMillisecondsSinceEpoch(event.timestamp),
);
// 添加助手消息
final assistantMsg = Message(
role: 'assistant',
content: event.assistantMessage,
timestamp: DateTime.fromMillisecondsSinceEpoch(event.timestamp + 1), // 助手消息稍后一毫秒
timestamp:
DateTime.fromMillisecondsSinceEpoch(event.timestamp + 1), // 助手消息稍后一毫秒
);
// 防止消息重复
if (!_isDuplicateMessage(userMsg) && !_isDuplicateMessage(assistantMsg)) {
messages.add(userMsg);
@ -176,15 +209,15 @@ class ChatController extends GetxController {
/// 停止当前交互
void _stopCurrentInteraction() {
Logger.info('收到后台语音识别开始事件,停止当前AI交互');
// 停止当前AI交互
if (isSending.value || _aiResponseSubscription != null) {
_interruptCurrentResponse("收到后台语音识别开始事件,中断当前响应");
}
// 停止TTS
_ttsService.stop();
// 停止语音识别
if (isVoiceRecognitionActive.value) {
stopVoiceRecognition();
@ -194,7 +227,7 @@ class ChatController extends GetxController {
@override
void onClose() {
Logger.info('ChatController onClose');
// 清理资源
scrollController.dispose();
_aiResponseSubscription?.cancel();
@ -202,10 +235,10 @@ class ChatController extends GetxController {
_voiceInteractionSubscription?.cancel();
_autoStopTimer?.cancel();
_scrollDebounceTimer?.cancel(); // 清理防抖计时器
// 停止TTS
_ttsService.stop();
if (isVoiceRecognitionActive.value) {
_asrService.stopContinuousRecognition();
}
@ -220,33 +253,32 @@ class ChatController extends GetxController {
/// 滚动到列表底部方法,添加防抖功能
void _scrollToBottom() {
if (scrollController.hasClients && messages.isNotEmpty) {
try {
scrollController.animateTo(
scrollController.position.maxScrollExtent,
duration: Duration(milliseconds: 300),
curve: Curves.easeOut,
);
} catch (e) {
Logger.error('滚动到底部失败', e);
}
if (scrollController.hasClients && messages.isNotEmpty) {
try {
scrollController.animateTo(
scrollController.position.maxScrollExtent,
duration: Duration(milliseconds: 300),
curve: Curves.easeOut,
);
} catch (e) {
Logger.error('滚动到底部失败', e);
}
}
}
/// 中断当前响应
void _interruptCurrentResponse(String reason) {
Logger.info('${reason}');
// 停止TTS
_ttsService.stop();
// 取消AI响应流订阅
if (_aiResponseSubscription != null) {
_shouldCancelAiResponse = true;
_aiResponseSubscription!.cancel();
_aiResponseSubscription = null;
// 将加载中的消息转换为正常消息,保留已输出的内容
final index = messages.indexWhere((m) => m.isLoading);
if (index != -1) {
@ -255,7 +287,7 @@ class ChatController extends GetxController {
);
messages.refresh();
}
// 重置发送状态
isSending.value = false;
}
@ -278,14 +310,13 @@ class ChatController extends GetxController {
);
messages.add(userMessage);
// 清空输入框
inputText.value = '';
// 添加加载中的消息
final loadingMessage = Message.loading();
messages.add(loadingMessage);
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToBottom();
});
@ -298,7 +329,7 @@ class ChatController extends GetxController {
// 重置取消标志
_shouldCancelAiResponse = false;
String fullResponse = '';
// 获取AI响应流
@ -327,12 +358,12 @@ class ChatController extends GetxController {
// 完成响应,将isLoading设为false
_finalizeResponse(fullResponse);
// 如果TTS启用,处理缓冲区中的剩余文本
if (isTtsEnabled.value) {
_ttsService.flushStream();
}
// 保存聊天历史
_saveChatHistory();
},
@ -345,7 +376,7 @@ class ChatController extends GetxController {
);
} catch (e) {
Logger.error('发送消息失败', e);
_handleResponseError();
}
}
@ -353,10 +384,11 @@ class ChatController extends GetxController {
// 构建消息历史
List<Map<String, String>> _buildMessageHistory() {
final List<Map<String, String>> messageHistory = [];
// 只取最近的10条消息作为上下文
final recentMessages = messages.where((m) => !m.isLoading).toList();
final startIndex = recentMessages.length > 10 ? recentMessages.length - 10 : 0;
final startIndex =
recentMessages.length > 10 ? recentMessages.length - 10 : 0;
for (int i = startIndex; i < recentMessages.length; i++) {
final m = recentMessages[i];
@ -365,7 +397,7 @@ class ChatController extends GetxController {
'content': m.content,
});
}
return messageHistory;
}
@ -393,7 +425,7 @@ class ChatController extends GetxController {
messages.refresh();
_scrollToBottom();
}
isSending.value = false;
_aiResponseSubscription = null;
}
@ -408,7 +440,7 @@ class ChatController extends GetxController {
);
messages.refresh();
}
isSending.value = false;
_aiResponseSubscription = null;
}
@ -429,18 +461,17 @@ class ChatController extends GetxController {
_voiceInteractionService.pauseVoiceInteraction();
try {
// 启动连续语音识别
// 注意:在UI更新后,使用模拟波形动画替代实际麦克风输入的波形数据
final recognitionStream = await _asrService.startContinuousRecognition();
isVoiceRecognitionActive.value = true;
isListening.value = true;
recognizedText.value = '';
// 重置临时消息索引
_tempMessageIndex = -1;
// 启动自动停止计时器
_startAutoStopTimer();
@ -460,7 +491,7 @@ class ChatController extends GetxController {
void _handleRecognitionEvent(RecognitionEvent event) {
// 更新活动时间
_updateActivityTime();
switch (event.type) {
case RecognitionEventType.intermediateResult:
_handleRecognizing(event.text);
@ -486,24 +517,24 @@ class ChatController extends GetxController {
void _startAutoStopTimer() {
// 取消现有计时器
_autoStopTimer?.cancel();
// 初始化最后活动时间
_updateActivityTime();
// 根据模式设置超时时间
final int timeoutSeconds = _isVoiceInteractionMode ? 8 : 15;
// 创建新的计时器,每秒检查一次
_autoStopTimer = Timer.periodic(Duration(seconds: 1), (timer) {
// 检查是否有活动
bool isActive = _ttsService.isSpeaking || isSpeechDetected.value;
// 如果有活动,更新时间并继续等待
if (isActive) {
_updateActivityTime();
return;
}
// 检查空闲时间
int idleSeconds = DateTime.now().difference(_lastActivityTime).inSeconds;
if (idleSeconds >= timeoutSeconds) {
@ -516,17 +547,17 @@ class ChatController extends GetxController {
// 处理语音识别中的状态
void _handleRecognizing(String text) {
if (text.isEmpty) return;
recognizedText.value = text;
// 设置语音检测状态为true,用于控制波形动画
if (!isSpeechDetected.value) {
isSpeechDetected.value = true;
}
// 更新活动时间
_updateActivityTime();
// 用户开始讲话时,立即停止当前TTS和中断AI响应
if (isSending.value || _aiResponseSubscription != null) {
_interruptCurrentResponse("检测到用户开始讲话,中断当前响应");
@ -558,10 +589,10 @@ class ChatController extends GetxController {
// 处理语音识别最终结果
void _handleFinalResult(String text) {
if (text.isEmpty) return;
Logger.info('处理语音识别最终结果: $text');
recognizedText.value = text;
// 更新活动时间
_updateActivityTime();
@ -569,7 +600,7 @@ class ChatController extends GetxController {
if (_tempMessageIndex >= 0 && _tempMessageIndex < messages.length) {
messages.removeAt(_tempMessageIndex);
}
// 重置临时消息索引
_tempMessageIndex = -1;
@ -578,7 +609,7 @@ class ChatController extends GetxController {
// 重置识别文本,但不关闭语音识别框
recognizedText.value = '';
// 重置语音检测状态
isSpeechDetected.value = false;
}
@ -586,14 +617,14 @@ class ChatController extends GetxController {
// 处理语音识别事件错误
void _handleRecognitionEventError(String? error) {
Logger.error('语音识别错误: ${error ?? "未知错误"}');
_cleanupRecognition();
}
// 处理语音识别流错误
void _handleRecognitionError(dynamic error) {
Logger.error('语音识别错误', error);
_cleanupRecognition();
}
@ -605,10 +636,10 @@ class ChatController extends GetxController {
messages.removeAt(_tempMessageIndex);
}
}
// 重置临时消息索引
_tempMessageIndex = -1;
// 停止语音识别
isVoiceRecognitionActive.value = false;
isListening.value = false;
@ -636,7 +667,7 @@ class ChatController extends GetxController {
messages.removeAt(_tempMessageIndex);
}
}
// 重置临时消息索引
_tempMessageIndex = -1;
} catch (e) {
@ -691,10 +722,12 @@ class ChatController extends GetxController {
/// 检查是否为重复消息
bool _isDuplicateMessage(Message message) {
return messages.any((m) =>
m.role == message.role &&
m.content == message.content &&
(m.timestamp.millisecondsSinceEpoch - message.timestamp.millisecondsSinceEpoch).abs() < 5000
);
return messages.any((m) =>
m.role == message.role &&
m.content == message.content &&
(m.timestamp.millisecondsSinceEpoch -
message.timestamp.millisecondsSinceEpoch)
.abs() <
5000);
}
}

2
lib/modules/explore/controllers/explore_controller.dart

@ -76,7 +76,7 @@ class ExploreController extends GetxController {
targetRoute: Routes.translation,
),
AIFeature(
title: 'aiGirlfriend'.tr, // AI女友
title: 'aiGirlfriend'.tr, // 亲子陪伴
description: 'aiGirlfriendInfo'.tr, // 智能陪伴,随时聊天
icon: Icons.favorite,
startColor: const Color(0xFFE91E63),

170
lib/modules/explore/views/explore_view.dart

@ -30,7 +30,38 @@ class ExploreView extends GetView<ExploreController> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 特色功能
_buildFeaturedCard(),
Container(
height: 180.h,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.r),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: PageView(
children: [
_buildFeaturedCard(
'assets/images/explore1.png',
'exploreSlogan1'.tr, // 智能陪伴,无限可能
'exploreAi'.tr, // 探索DeepSound提供的AI功能
),
_buildFeaturedCard(
'assets/images/explore.png',
'exploreSlogan'.tr, // 智能耳机,无限可能
'exploreAi'.tr, // 探索DeepSound提供的AI功能
),
_buildFeaturedCard(
'assets/images/explore2.jpg',
'exploreSlogan2'.tr, // 智能眼镜,无限可能
'exploreAi'.tr, // 探索DeepSound提供的AI功能
),
],
),
),
// AI功能列表
SizedBox(height: 24.h),
@ -132,90 +163,77 @@ class ExploreView extends GetView<ExploreController> {
}
// 特色功能卡片
Widget _buildFeaturedCard() {
return Container(
height: 180.h,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.r),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Stack(
children: [
// 背景图片
ClipRRect(
borderRadius: BorderRadius.circular(24.r),
child: Container(
color: Colors.blue.shade900, // 预设颜色,图片加载失败时显示
child: Image.asset(
'assets/images/explore.png',
width: double.infinity,
height: 180.h,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Container(
color: Colors.blue.shade900,
width: double.infinity,
height: 180.h,
child: Icon(
Icons.image_not_supported,
color: Colors.white,
size: 40.sp,
),
);
},
),
Widget _buildFeaturedCard(String image, String title, String description) {
return Stack(
children: [
// 背景图片
ClipRRect(
borderRadius: BorderRadius.circular(24.r),
child: Container(
color: Colors.blue.shade900, // 预设颜色,图片加载失败时显示
child: Image.asset(
image,
width: double.infinity,
height: 180.h,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Container(
color: Colors.blue.shade900,
width: double.infinity,
height: 180.h,
child: Icon(
Icons.image_not_supported,
color: Colors.white,
size: 40.sp,
),
);
},
),
),
),
// 渐变遮罩
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.r),
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black.withOpacity(0.1),
Colors.black.withOpacity(0.6),
],
),
// 渐变遮罩
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.r),
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black.withOpacity(0.1),
Colors.black.withOpacity(0.6),
],
),
),
),
// 文本内容
Padding(
padding: EdgeInsets.all(24.w),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'exploreSlogan'.tr, // 智能耳机,无限可能
style: TextStyle(
fontSize: 22.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
),
// 文本内容
Padding(
padding: EdgeInsets.all(24.w),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 22.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
),
SizedBox(height: 4.h),
Text(
'exploreAi'.tr, // 探索DeepSound提供的AI功能
style: TextStyle(
fontSize: 14.sp,
color: Colors.white.withOpacity(0.8),
),
),
SizedBox(height: 4.h),
Text(
description,
style: TextStyle(
fontSize: 14.sp,
color: Colors.white.withOpacity(0.8),
),
],
),
),
],
),
],
),
),
],
);
}

59
lib/modules/home/views/home_view.dart

@ -89,9 +89,7 @@ class HomeView extends GetView<HomeController> {
_buildAppBar(isDarkMode),
// 顶部图片
Center(
child: _buildEarbudsImage(),
),
_buildEarbudsImage(),
SizedBox(height: 20.h),
// 设备状态卡片
@ -102,8 +100,6 @@ class HomeView extends GetView<HomeController> {
_buildSectionTitle(isDarkMode),
SizedBox(height: 16.h),
_buildPremiumAILayout(),
],
),
),
@ -156,15 +152,44 @@ class HomeView extends GetView<HomeController> {
// 耳机图片
Widget _buildEarbudsImage() {
final bool isDarkMode = Get.isDarkMode;
return Container(
return SizedBox(
height: 220.h,
alignment: Alignment.center,
child: Image.asset(
isDarkMode
? 'assets/images/headphone_dark.png'
: 'assets/images/headphone_light.png',
height: 180.h,
fit: BoxFit.contain,
child: PageView(
children: [
Center(
child: SizedBox(
width: double.infinity,
height: 200.h,
child: Image.asset(
isDarkMode
? 'assets/images/robot_light.png'
: 'assets/images/robot_light.png',
fit: BoxFit.fitWidth,
),
),
),
Center(
child: Image.asset(
isDarkMode
? 'assets/images/headphone_dark.png'
: 'assets/images/headphone_light.png',
height: 180.h,
fit: BoxFit.contain,
),
),
Center(
child: SizedBox(
width: double.infinity,
height: 200.h,
child: Image.asset(
isDarkMode
? 'assets/images/glasses_light.png'
: 'assets/images/glasses_light.png',
fit: BoxFit.fitWidth,
),
),
),
],
),
);
}
@ -556,7 +581,7 @@ class HomeView extends GetView<HomeController> {
// 右下卡片 - AI女友
_buildFeatureCard(
title: 'aiCompanion'.tr, // AI伴侣
title: 'aiCompanion'.tr, // 亲子陪伴
description: 'aiCompanionInfo'.tr, // 懂你心意,温暖陪伴
icon: Icons.favorite,
gradient: LinearGradient(
@ -673,7 +698,8 @@ class HomeView extends GetView<HomeController> {
child: Text(
description,
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
color:
isDarkMode ? Colors.grey[400] : Colors.grey[600],
fontSize: 12.sp,
),
maxLines: 2,
@ -706,7 +732,8 @@ class HomeView extends GetView<HomeController> {
child: Text(
description,
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
color:
isDarkMode ? Colors.grey[400] : Colors.grey[600],
fontSize: 10.sp,
),
maxLines: 2,

51
lib/modules/login/controllers/login_controller.dart

@ -27,10 +27,10 @@ class LoginController extends GetxController {
String get selectedCountryCode => _selectedCountryCode.value;
String get selectedCountryFlag => _selectedCountryFlag.value;
// 预设国家编码列表
final List<Map<String, String>> countryCodes = [
{'name': '邮箱', 'code': '', 'flag': '✉️'}, // 保持首位
{'name': '中国', 'code': '+86', 'flag': '🇨🇳'}, // 保持第二位
final List<Map<String, String>> countryCodes = [
{'name': '邮箱', 'code': '', 'flag': '✉️'}, // 保持首位
{'name': '中国', 'code': '+86', 'flag': '🇨🇳'}, // 保持第二位
// 按国家代码数字升序排列(去除+号后排序)
{'name': '俄罗斯', 'code': '+7', 'flag': '🇷🇺'},
{'name': '英国', 'code': '+44', 'flag': '🇬🇧'},
@ -59,13 +59,12 @@ class LoginController extends GetxController {
{'name': '阿根廷', 'code': '+54', 'flag': '🇦🇷'},
{'name': '澳大利亚', 'code': '+61', 'flag': '🇦🇺'},
{'name': '新西兰', 'code': '+64', 'flag': '🇳🇿'},
// 中国地区(保持最后)
{'name': '中国香港', 'code': '+852', 'flag': '🇭🇰'},
{'name': '中国澳门', 'code': '+853', 'flag': '🇲🇴'},
{'name': '中国台湾', 'code': '+886', 'flag': '🇹🇼'},
];
// 状态管理
final _viewState = LoginViewState.welcome.obs;
@ -248,8 +247,8 @@ class LoginController extends GetxController {
if (isEmialContactController() || isPhoneContactController()) {
_startCountdown();
var response = await Api.getCode({
"addr": contactController.text,
"vtype": 0,
"addr": selectedCountryCode + contactController.text,
"vtype": loginType,
});
if (response != null) {
print("请求后台数据验证码params=${response}");
@ -295,8 +294,8 @@ class LoginController extends GetxController {
// 检查每个元素是否为单个字母或数字字符
final text = contactController.text;
return (text.length > 5 &&
RegExp(r'^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$')
.hasMatch(text));
RegExp(r'^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$')
.hasMatch(text));
}
// 检查手机号码格式
@ -310,7 +309,7 @@ class LoginController extends GetxController {
bool isVerificationCodeComplete() {
// 检查每个元素是否为单个字母或数字字符
final text = verificationCodeController.text;
print("数据获取Facebook${text.length}");
print("数据获取Facebook${text.length}");
return text.length == 4 && RegExp(r'^[0-9]+$').hasMatch(text);
}
@ -323,7 +322,7 @@ class LoginController extends GetxController {
// 模拟验证过程
switch (loginType) {
case 0 || 1:
case 0:
{
if (!isVerificationCodeComplete()) {
Get.snackbar(
@ -337,7 +336,32 @@ class LoginController extends GetxController {
}
requestLogin({
"mail": contactController.text,
"phone": "",
"phone": selectedCountryCode + contactController.text,
"stype": loginType,
"vcode": verificationCodeController.text,
"ttoken": "",
"phonemodel": "",
"phonemac": "",
"name": "",
"avatar": "",
});
break;
}
case 1:
{
if (!isVerificationCodeComplete()) {
Get.snackbar(
'提示',
'请输入完整的验证码',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
break;
}
requestLogin({
"mail": "",
"phone": selectedCountryCode + contactController.text,
"stype": loginType,
"vcode": verificationCodeController.text,
"ttoken": "",
@ -348,7 +372,6 @@ class LoginController extends GetxController {
});
break;
}
case 4:
{
print("数据获取Facebook");

10
lib/modules/meeting/bindings/meeting_details_binding.dart

@ -0,0 +1,10 @@
import 'package:get/get.dart';
import '../controllers/meeting_details_controller.dart';
class MeetingDetailsBinding extends Bindings {
@override
void dependencies() {
// 注册控制器
Get.lazyPut<MeetingDetailsController>(() => MeetingDetailsController());
}
}

11
lib/modules/meeting/bindings/meeting_edit_binding.dart

@ -0,0 +1,11 @@
import 'package:get/get.dart';
import '../controllers/meeting_edit_controller.dart';
class MeetingEditBinding extends Bindings {
@override
void dependencies() {
// 注册控制器
Get.lazyPut<MeetingEditController>(() => MeetingEditController());
}
}

44
lib/modules/meeting/controllers/meeting_controller.dart

@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../data/services/db/sqflite_api.dart';
class MeetingController extends GetxController {
final ScrollController listController = ScrollController();
@ -15,6 +17,8 @@ class MeetingController extends GetxController {
@override
void onInit() {
super.onInit();
listController.addListener(_onScroll);
_getDataList();
}
@override
@ -23,4 +27,44 @@ class MeetingController extends GetxController {
titleController.dispose();
super.onClose();
}
void _onScroll() {
if (isLoadingMore &&
listController.position.pixels >=
listController.position.maxScrollExtent - 100) {
page++;
_getDataList();
}
}
// 请求本地数据
void _getDataList() async {
List list = await SqfliteApi.getMeetingList(page);
dataList.addAll(list);
if (list.length < 10) {
isLoadingMore = false;
} else {
isLoadingMore = true;
}
}
// 修改标题
void editMeetingTitle(int id, String title) async {
if (title.isEmpty) return;
SqfliteApi.editMeetingTitle(id, title);
int index = dataList.indexWhere((item) => item['id'] == id);
if (index != -1) {
final dataMap = Map<String, dynamic>.from(dataList[index]);
dataMap['title'] = title;
dataList[index] = dataMap;
}
Get.back();
}
// 删除数据
void deleteMeeting(int id) {
dataList.removeWhere((item) => item['id'] == id);
// 删除数据库中的记录
SqfliteApi.deleteMeeting(id);
}
}

51
lib/modules/meeting/controllers/meeting_details_controller.dart

@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../data/services/db/sqflite_api.dart';
import '../model/meeting_details_model.dart';
import '../model/meeting_model.dart';
class MeetingDetailsController extends GetxController
with GetSingleTickerProviderStateMixin {
late AnimationController lottieController;
int id = 0;
RxBool isLoading = true.obs;
Rx<MeetingModel> meetingData = MeetingModel().obs;
Rx<MeetingDetailsModel> dataDetails = MeetingDetailsModel().obs;
RxBool isPlay = false.obs;
@override
void onInit() {
super.onInit();
lottieController = AnimationController(vsync: this);
final Map<String, dynamic> args = Get.arguments ?? {};
id = args['id'];
_getDataDetails();
}
@override
void onClose() {
lottieController.dispose();
super.onClose();
}
void _getDataDetails() async {
final data = await SqfliteApi.getMeetingDetails(id);
meetingData.value = MeetingModel.fromJson(data);
dataDetails.value = MeetingDetailsModel.fromJson(data['details']);
isLoading.value = false;
}
void start() {
if (isPlay.value) {
lottieController.stop();
isPlay.value = false;
} else {
lottieController.repeat();
isPlay.value = true;
}
}
}

62
lib/modules/meeting/controllers/meeting_edit_controller.dart

@ -0,0 +1,62 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../data/services/db/sqflite_api.dart';
import '../model/meeting_details_model.dart';
import '../model/meeting_model.dart';
class MeetingEditController extends GetxController
with GetSingleTickerProviderStateMixin {
late AnimationController lottieController;
late TextEditingController titleController;
late TextEditingController contentController;
int id = 0;
RxBool isLoading = true.obs;
Rx<MeetingModel> meetingData = MeetingModel().obs;
Rx<MeetingDetailsModel> dataDetails = MeetingDetailsModel().obs;
RxBool isPlay = false.obs;
@override
void onInit() {
super.onInit();
lottieController = AnimationController(vsync: this);
final Map<String, dynamic> args = Get.arguments ?? {};
id = args['id'];
_getDataDetails();
}
@override
void onClose() {
lottieController.dispose();
titleController.dispose();
contentController.dispose();
super.onClose();
}
void _getDataDetails() async {
final data = await SqfliteApi.getMeetingDetails(id);
meetingData.value = MeetingModel.fromJson(data);
dataDetails.value = MeetingDetailsModel.fromJson(data['details']);
titleController = TextEditingController(
text: meetingData.value.title,
);
contentController = TextEditingController(
text: dataDetails.value.content,
);
isLoading.value = false;
}
void start() {
if (isPlay.value) {
lottieController.stop();
isPlay.value = false;
} else {
lottieController.repeat();
isPlay.value = true;
}
}
}

10
lib/modules/meeting/controllers/meeting_record_controller.dart

@ -5,9 +5,12 @@ import 'package:get/get.dart';
import '../../../data/services/asr_service.dart';
import '../../../data/services/db/sqflite_api.dart';
import 'meeting_controller.dart';
class MeetingRecordController extends GetxController
with GetSingleTickerProviderStateMixin {
final MeetingController meetingController = Get.find<MeetingController>();
late AnimationController lottieController;
late AsrService _asrService;
@ -113,6 +116,13 @@ class MeetingRecordController extends GetxController
};
final result = await SqfliteApi.insertMeeting(data);
if (result != null) {
meetingController.dataList.insert(0, {
'id': result,
'title': title,
'content': firstContent,
'seconds': seconds.value,
'creationtime': creationTime,
});
Get.back();
}
}

6
lib/modules/meeting/model/meeting_details_model.dart

@ -4,21 +4,21 @@ class MeetingDetailsModel {
MeetingDetailsModel();
late int id;
late int noteid;
late int meetingid;
late Uint8List audio;
late String content;
factory MeetingDetailsModel.fromJson(Map<String, dynamic> map) =>
MeetingDetailsModel()
..id = map["id"]
..noteid = map["noteid"]
..meetingid = map["meetingid"]
..audio = map["audio"]
..content = map["content"];
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['id'] = id;
data['noteid'] = noteid;
data['meetingid'] = meetingid;
data['audio'] = audio;
data['content'] = content;
return data;

3
lib/modules/meeting/model/meeting_model.dart

@ -4,7 +4,6 @@ class MeetingModel {
late int id;
late String title;
late String content;
late String language;
late int seconds;
late int creationtime;
@ -12,7 +11,6 @@ class MeetingModel {
..id = map["id"]
..title = map["title"]
..content = map["content"]
..language = map["language"]
..seconds = map["seconds"]
..creationtime = map["creationtime"];
@ -21,7 +19,6 @@ class MeetingModel {
data['id'] = id;
data['title'] = title;
data['content'] = content;
data['language'] = language;
data['seconds'] = seconds;
data['creationtime'] = creationtime;
return data;

253
lib/modules/meeting/views/meeting_details_view.dart

@ -0,0 +1,253 @@
import 'package:common_utils/common_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:lottie/lottie.dart';
import '../../../core/theme/app_colors.dart';
import '../../../routes/app_routes.dart';
import '../controllers/meeting_details_controller.dart';
class MeetingDetailsView extends GetView<MeetingDetailsController> {
const MeetingDetailsView({super.key});
@override
Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
backgroundColor: isDarkMode ? Colors.black : Colors.grey[50],
appBar: _buildAppBar(isDarkMode),
body: Obx(() => !controller.isLoading.value
? _buildBody(isDarkMode)
: const SizedBox()),
);
}
// 构建应用栏
PreferredSizeWidget _buildAppBar(bool isDarkMode) {
return AppBar(
backgroundColor: isDarkMode ? Colors.black : Colors.white,
elevation: 0.5,
leading: IconButton(
icon: Icon(
Icons.arrow_back_ios,
color: isDarkMode ? Colors.white : Colors.black,
size: 20.sp,
),
onPressed: () => Get.back(),
),
title: Text(
'录音详情',
style: TextStyle(
fontSize: 17.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
),
centerTitle: true,
);
}
// 构建主体内容
Widget _buildBody(bool isDarkMode) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
controller.meetingData.value.title,
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
fontSize: 17.sp,
),
),
Padding(
padding: EdgeInsets.symmetric(vertical: 5.w),
child: Text(
DateUtil.formatDateMs(
controller.meetingData.value.creationtime,
format: DateFormats.y_mo_d_h_m,
),
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 14.sp,
),
),
),
_audioWave(isDarkMode),
_recordContent(isDarkMode),
_bottomButton(isDarkMode),
],
),
);
}
// 音波
Widget _audioWave(bool isDarkMode) {
return Container(
padding: EdgeInsets.only(bottom: 10.w),
margin: EdgeInsets.symmetric(vertical: 5.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.white,
borderRadius: BorderRadius.circular(12.w),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
SizedBox(
height: 70.w,
child: Row(
children: [
Expanded(
child: Lottie.asset(
'assets/lottie/audio_wave.json',
controller: controller.lottieController,
fit: BoxFit.fill,
onLoaded: (composition) {
controller.lottieController.duration =
composition.duration;
},
),
),
Expanded(
child: Lottie.asset(
'assets/lottie/audio_wave.json',
controller: controller.lottieController,
fit: BoxFit.fill,
),
),
Expanded(
child: Lottie.asset(
'assets/lottie/audio_wave.json',
controller: controller.lottieController,
fit: BoxFit.fill,
),
),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
'00:00:00',
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 12.sp,
),
),
GestureDetector(
onTap: controller.start,
child: Container(
width: 30.w,
height: 30.w,
decoration: BoxDecoration(
color: isDarkMode ? AppColors.primary : Colors.blue,
borderRadius: BorderRadius.circular(
12.w,
),
),
child: Icon(
controller.isPlay.value ? Icons.pause : Icons.play_arrow,
color: Colors.white,
size: 25.w,
),
),
),
Text(
'00:00:00',
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 12.sp,
),
),
],
),
],
),
);
}
// 记录内容
Widget _recordContent(bool isDarkMode) {
return Expanded(
child: Container(
width: double.infinity,
padding: EdgeInsets.all(10.w),
margin: EdgeInsets.symmetric(vertical: 5.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.white,
borderRadius: BorderRadius.circular(12.w),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Align(
alignment: Alignment.topLeft,
child: SingleChildScrollView(
reverse: true,
child: Obx(
() => Text(
controller.dataDetails.value.content,
),
),
),
),
),
);
}
// 底部按钮
Widget _bottomButton(bool isDarkMode) {
return Container(
padding: EdgeInsets.only(top: 40.w, bottom: 30.w),
alignment: Alignment.center,
child: GestureDetector(
onTap: () {
Get.toNamed(Routes.meetingEdit, arguments: {
'id': controller.id,
});
},
child: Container(
width: 130.w,
height: 48.w,
decoration: BoxDecoration(
color: isDarkMode ? AppColors.primary : Colors.blue,
borderRadius: BorderRadius.circular(12.w),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.edit_calendar,
color: Colors.white,
size: 20.w,
),
5.horizontalSpace,
Text(
'编辑',
style: TextStyle(color: Colors.white, fontSize: 16.sp),
),
],
),
),
),
);
}
}

262
lib/modules/meeting/views/meeting_edit_view.dart

@ -0,0 +1,262 @@
import 'package:common_utils/common_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:lottie/lottie.dart';
import '../../../core/theme/app_colors.dart';
import '../controllers/meeting_edit_controller.dart';
class MeetingEditView extends GetView<MeetingEditController> {
const MeetingEditView({super.key});
@override
Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
backgroundColor: isDarkMode ? Colors.black : Colors.grey[50],
appBar: _buildAppBar(isDarkMode),
body: Obx(() => !controller.isLoading.value
? _buildBody(isDarkMode)
: const SizedBox()),
);
}
// 构建应用栏
PreferredSizeWidget _buildAppBar(bool isDarkMode) {
return AppBar(
backgroundColor: isDarkMode ? Colors.black : Colors.white,
elevation: 0.5,
leading: IconButton(
icon: Icon(
Icons.arrow_back_ios,
color: isDarkMode ? Colors.white : Colors.black,
size: 20.sp,
),
onPressed: () => Get.back(),
),
actions: [
GestureDetector(
onTap: () {},
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: 12.w,
vertical: 6.w,
),
child: Text(
'保存',
style: TextStyle(
color: isDarkMode ? AppColors.primary : Colors.blue,
fontSize: 18.sp,
fontWeight: FontWeight.bold,
),
),
),
),
],
);
}
// 构建主体内容
Widget _buildBody(bool isDarkMode) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 48.w,
alignment: Alignment.center,
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.white,
borderRadius: BorderRadius.circular(12.w),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: TextField(
controller: controller.titleController,
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
fontSize: 16.sp,
),
decoration: InputDecoration(
hintText: '最多50个字符',
hintStyle: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.black54,
fontSize: 16.sp,
),
fillColor: Colors.transparent,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 10.w,
),
),
),
),
Padding(
padding: EdgeInsets.symmetric(vertical: 5.w),
child: Text(
DateUtil.formatDateMs(
controller.meetingData.value.creationtime,
format: DateFormats.y_mo_d_h_m,
),
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 14.sp,
),
),
),
_audioWave(isDarkMode),
_recordContent(isDarkMode),
],
),
);
}
// 音波
Widget _audioWave(bool isDarkMode) {
return Container(
padding: EdgeInsets.only(bottom: 10.w),
margin: EdgeInsets.symmetric(vertical: 5.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.white,
borderRadius: BorderRadius.circular(12.w),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
SizedBox(
height: 70.w,
child: Row(
children: [
Expanded(
child: Lottie.asset(
'assets/lottie/audio_wave.json',
controller: controller.lottieController,
fit: BoxFit.fill,
onLoaded: (composition) {
controller.lottieController.duration =
composition.duration;
},
),
),
Expanded(
child: Lottie.asset(
'assets/lottie/audio_wave.json',
controller: controller.lottieController,
fit: BoxFit.fill,
),
),
Expanded(
child: Lottie.asset(
'assets/lottie/audio_wave.json',
controller: controller.lottieController,
fit: BoxFit.fill,
),
),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
'00:00:00',
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 12.sp,
),
),
GestureDetector(
onTap: controller.start,
child: Container(
width: 30.w,
height: 30.w,
decoration: BoxDecoration(
color: isDarkMode ? AppColors.primary : Colors.blue,
borderRadius: BorderRadius.circular(
12.w,
),
),
child: Icon(
controller.isPlay.value ? Icons.pause : Icons.play_arrow,
color: Colors.white,
size: 25.w,
),
),
),
Text(
'00:00:00',
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 12.sp,
),
),
],
),
],
),
);
}
// 记录内容
Widget _recordContent(bool isDarkMode) {
return Expanded(
child: Container(
width: double.infinity,
padding: EdgeInsets.all(10.w),
margin: EdgeInsets.symmetric(vertical: 5.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.white,
borderRadius: BorderRadius.circular(12.w),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: TextField(
controller: controller.contentController,
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
fontSize: 16.sp,
),
maxLines: 50,
decoration: InputDecoration(
hintText: '编辑记录内容',
hintStyle: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.black54,
fontSize: 16.sp,
),
fillColor: Colors.transparent,
border: InputBorder.none,
focusedBorder: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 10.w,
),
),
),
),
);
}
}

123
lib/modules/meeting/views/meeting_view.dart

@ -56,9 +56,9 @@ class MeetingView extends GetView<MeetingController> {
itemCount: controller.dataList.length,
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 5.w),
itemBuilder: (BuildContext context, int index) {
// MeetingModel data =
// MeetingModel.fromJson(controller.dataList[index]);
return _item(context, isDarkMode);
MeetingModel data =
MeetingModel.fromJson(controller.dataList[index]);
return _item(context, isDarkMode, data);
},
),
),
@ -94,9 +94,57 @@ class MeetingView extends GetView<MeetingController> {
);
}
Widget _item(BuildContext context, bool isDarkMode) {
Widget _item(BuildContext context, bool isDarkMode, MeetingModel data) {
return GestureDetector(
onTap: () {},
onTap: () {
Get.toNamed(Routes.meetingDetails, arguments: {
'id': data.id,
});
},
onLongPressStart: (details) {
RenderBox? renderBox =
Overlay.of(context).context.findRenderObject() as RenderBox;
final RelativeRect position = RelativeRect.fromRect(
Rect.fromLTRB(
details.globalPosition.dx,
details.globalPosition.dy,
details.globalPosition.dx + 110, // 菜单显示位置X轴坐标
details.globalPosition.dy - 40, // 菜单显示位置Y轴坐标
),
Offset.zero & renderBox.size,
);
showMenu(
context: context,
position: position,
color: isDarkMode ? AppColors.cardBackground : Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r),
),
items: [
_popupMenuEntry(
Icon(
Icons.edit_calendar,
color: isDarkMode ? Colors.white : Colors.black87,
),
'编辑',
isDarkMode ? Colors.white : Colors.black87,
() {
controller.titleController.text = data.title;
_bottomSheet(context, isDarkMode, id: data.id);
},
),
_popupMenuEntry(
const Icon(Icons.delete_outline, color: Colors.red),
'删除',
isDarkMode ? Colors.white : Colors.black87,
() {
controller.deleteMeeting(data.id);
},
),
],
);
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 8.w),
margin: EdgeInsets.symmetric(vertical: 5.w),
@ -120,7 +168,7 @@ class MeetingView extends GetView<MeetingController> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'data.title',
data.title,
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
fontSize: 16.sp,
@ -128,7 +176,7 @@ class MeetingView extends GetView<MeetingController> {
overflow: TextOverflow.ellipsis,
),
Text(
'无内容',
data.content.isNotEmpty ? data.content : '无内容',
style: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.black54,
fontSize: 12.sp,
@ -136,7 +184,10 @@ class MeetingView extends GetView<MeetingController> {
overflow: TextOverflow.ellipsis,
),
Text(
'12-13',
DateUtil.formatDateMs(
data.creationtime,
format: DateFormats.y_mo_d_h_m,
),
style: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.black54,
fontSize: 12.sp,
@ -155,7 +206,11 @@ class MeetingView extends GetView<MeetingController> {
borderRadius: BorderRadius.circular(28.w),
),
child: Text(
'00:00:00',
DateUtil.formatDateMs(
data.seconds * 1000,
format: 'HH:mm:ss',
isUtc: true,
),
style: TextStyle(color: Colors.white, fontSize: 14.sp),
),
),
@ -165,7 +220,35 @@ class MeetingView extends GetView<MeetingController> {
);
}
void _bottomSheet(BuildContext context, bool isDarkMode) {
PopupMenuEntry _popupMenuEntry(
Icon icon,
String title,
Color color,
Function() onTap,
) {
return PopupMenuItem(
onTap: onTap,
child: SizedBox(
width: 120.w,
child: Row(
children: [
icon,
8.horizontalSpace,
Text(
title,
style: TextStyle(color: color),
),
],
),
),
);
}
void _bottomSheet(
BuildContext context,
bool isDarkMode, {
int? id,
}) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
@ -181,7 +264,7 @@ class MeetingView extends GetView<MeetingController> {
mainAxisSize: MainAxisSize.min,
children: [
Text(
'录音标题(非必填)',
id != null ? '修改录音标题(非必填)' : '录音标题(非必填)',
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
fontSize: 16.sp,
@ -259,14 +342,18 @@ class MeetingView extends GetView<MeetingController> {
onTap: () {
int time = DateTime.now().millisecondsSinceEpoch;
String title = controller.titleController.text;
if (title.isEmpty) {
title =
'${DateUtil.formatDateMs(time, format: DateFormats.y_mo_d)} 录音';
if (id != null) {
controller.editMeetingTitle(id, title);
} else {
if (title.isEmpty) {
title =
'${DateUtil.formatDateMs(time, format: DateFormats.y_mo_d)} 录音';
}
Get.back();
Get.toNamed(Routes.meetingRecord, arguments: {
'title': title,
});
}
Get.back();
Get.toNamed(Routes.meetingRecord, arguments: {
'title': title,
});
},
child: Container(
width: 120.w,

11
lib/modules/timbre/controllers/timbre_controller.dart

@ -288,16 +288,5 @@ class TimbreController extends GetxController {
type.value = data['type'];
settingsController.timbreName.value = data['name'];
_storage.write('timbre', data['type']);
setSpeechServiceType();
}
// 设置语音音色服务类型
void setSpeechServiceType() {
try {
final factory = Get.find<SpeechFactory>();
factory.setTimbreServiceType();
} catch (e) {
// 处理错误
}
}
}

14
lib/routes/app_pages.dart

@ -4,7 +4,11 @@ import '../modules/home/bindings/home_binding.dart';
import '../modules/explore/views/explore_view.dart';
import '../modules/explore/bindings/explore_binding.dart';
import '../modules/meeting/bindings/meeting_binding.dart';
import '../modules/meeting/bindings/meeting_details_binding.dart';
import '../modules/meeting/bindings/meeting_edit_binding.dart';
import '../modules/meeting/bindings/meeting_record_binding.dart';
import '../modules/meeting/views/meeting_details_view.dart';
import '../modules/meeting/views/meeting_edit_view.dart';
import '../modules/meeting/views/meeting_record_view.dart';
import '../modules/meeting/views/meeting_view.dart';
import '../modules/profile/views/profile_view.dart';
@ -95,6 +99,16 @@ abstract class AppPages {
page: () => const MeetingRecordView(),
binding: MeetingRecordBinding(),
),
GetPage(
name: Routes.meetingDetails,
page: () => const MeetingDetailsView(),
binding: MeetingDetailsBinding(),
),
GetPage(
name: Routes.meetingEdit,
page: () => const MeetingEditView(),
binding: MeetingEditBinding(),
),
GetPage(
name: Routes.timbre,
page: () => const TimbreView(),

2
lib/routes/app_routes.dart

@ -9,6 +9,8 @@ abstract class Routes {
static const profile = '/profile';
static const meeting = '/meeting';
static const meetingRecord = '/meetingRecord';
static const meetingDetails = '/meetingDetails';
static const meetingEdit = '/meetingEdit';
static const timbre = '/timbre';
static const settings = '/settings';
static const speechDemo = '/speech_demo';

7
pubspec.yaml

@ -50,14 +50,13 @@ dependencies:
speech_to_text: ^7.0.0
fluwx: ^5.4.1
firebase_core: ^3.12.1
firebase_auth: ^5.5.0
firebase_auth: ^5.5.1
google_sign_in: ^6.2.2
flutter_facebook_auth: ^7.1.1
common_utils: ^2.1.0
flutter_image_compress: ^2.4.0
# image_cropper: ^9.0.0
dio: ^5.8.0+1
flutter_easyloading: ^3.0.5
sqflite: ^2.4.2
path: ^1.9.1
@ -72,7 +71,11 @@ flutter:
assets:
- assets/images/headphone_light.png
- assets/images/headphone_dark.png
- assets/images/robot_light.png
- assets/images/glasses_light.png
- assets/images/agents/
- assets/images/explore.png
- assets/images/explore1.png
- assets/images/explore2.jpg
- assets/lottie/
- .env

Loading…
Cancel
Save