Browse Source

Merge commit 'bd062c2acdc83e8665b92eae7a262b13b3533f52'

weicu
lxm 2 years ago
parent
commit
b5de5e1357
  1. 44
      ios/Podfile.lock
  2. 30
      ios/Runner.xcodeproj/project.pbxproj
  3. 123
      ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
  4. 123
      ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved
  5. 36
      ios/Runner/GoogleService-Info.plist
  6. 32
      ios/Runner/Info.plist
  7. 10
      ios/Runner/RunnerRelease.entitlements
  8. 12
      lib/core/translations/language/en_us.dart
  9. 12
      lib/core/translations/language/zh_cn.dart
  10. 8
      lib/data/providers/agent_provider.dart
  11. 13
      lib/data/services/asr_service.dart
  12. 23
      lib/data/services/deapsound_ai_service.dart
  13. 272
      lib/data/services/speech_impl/xunfei_asr_service.dart
  14. 10
      lib/modules/chat/controllers/chat_controller.dart
  15. 2
      lib/modules/home/bindings/home_binding.dart
  16. 2
      lib/modules/home/controllers/home_controller.dart
  17. 106
      lib/modules/login/controllers/facebookLogin.dart
  18. 7
      lib/modules/login/controllers/googleLogin.dart
  19. 11
      lib/modules/login/controllers/login_controller.dart
  20. 19
      lib/modules/meeting/controllers/meeting_details_controller.dart
  21. 78
      lib/modules/meeting/controllers/meeting_edit_controller.dart
  22. 134
      lib/modules/meeting/controllers/meeting_record_controller.dart
  23. 51
      lib/modules/meeting/views/meeting_details_view.dart
  24. 66
      lib/modules/meeting/views/meeting_edit_view.dart
  25. 2
      lib/modules/pairing/controllers/pairing_controller.dart
  26. 140
      lib/modules/profile/controllers/profile_controller.dart
  27. 10
      lib/modules/profile/views/profile_view.dart
  28. 100
      lib/modules/settings/controllers/settings_controller.dart
  29. 59
      lib/modules/settings/views/settings_view.dart
  30. 9
      lib/modules/splash/controllers/splash_controller.dart
  31. 8
      lib/modules/timbre/controllers/timbre_controller.dart
  32. 6
      lib/modules/timbre/views/timbre_view.dart

44
ios/Podfile.lock

@ -264,18 +264,18 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/sqflite_darwin/darwin"
SPEC CHECKSUMS:
app_settings: 58017cd26b604ae98c3e65acbdd8ba173703cc82
app_settings: 5127ae0678de1dcc19f2293271c51d37c89428b2
AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73
audio_session: 19e9480dbdd4e5f6c4543826b2e8b0e4ab6145fe
audio_waveforms: cd736909ebc6b6a164eb74701d8c705ce3241e1c
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0
audio_waveforms: a6dde7fe7c0ea05f06ffbdb0f7c1b2b2ba6cedcf
device_info_plus: 71ffc6ab7634ade6267c7a93088ed7e4f74e5896
FBAEMKit: cb719c53575a3be86ea873279f30d6a2c4e15881
FBSDKCoreKit: ecdb980a24633ccb012700299ceb16d0235e14d2
FBSDKCoreKit_Basics: 045101c4a9ef10c845347424d73a29aae02c3e43
FBSDKLoginKit: 69eb59b2f839aba635616df6e422acd0ca88030a
Firebase: d80354ed7f6df5f9aca55e9eb47cc4b634735eaf
firebase_auth: 3d848b9b866b201e5c8e0c06d8b2cec272fd8825
firebase_core: ac395f994af4e28f6a38b59e05a88ca57abeb874
firebase_auth: 9ebbd83276bf977dea1c74dfc199acf9d2a2f42f
firebase_core: 8d552814f6c01ccde5d88939fced4ec26f2f5510
FirebaseAppCheckInterop: 9664c858489710f682766ef54e2b6741d3b62070
FirebaseAuth: ad59a1a7b161e75f74c39f70179d2482d40e2737
FirebaseAuthInterop: 01a804fb074424fd58b92dd50dd0272277199356
@ -283,32 +283,32 @@ SPEC CHECKSUMS:
FirebaseCoreExtension: 3d3f2017a00d06e09ab4ebe065391b0bb642565e
FirebaseCoreInternal: df24ce5af28864660ecbd13596fc8dd3a8c34629
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
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
flutter_blue_plus_darwin: 09444a26fb6bdef523e55b68fc1c59af5a877ea6
flutter_facebook_auth: 8a9ad5fbfc25b77e7a53ea8986866950b27edcae
flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1
flutter_local_notifications: 395056b3175ba4f08480a7c5de30cd36d69827e4
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
flutter_tts: b88dbc8655d3dc961bc4a796e4e16a4cc1795833
fluwx: 6bf9c5a3a99ad31b0de137dd92370a0d10a60f4b
google_sign_in_ios: 19297361f2c51d7d8ac0201b866ef1fa5d1f94a8
GoogleSignIn: d4281ab6cf21542b1cfaff85c191f230b399d2db
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
GTMAppAuth: f69bd07d68cd3b766125f7e072c45d7340dea0de
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
just_audio: a42c63806f16995daf5b219ae1d679deb76e6a79
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
MicrosoftCognitiveServicesSpeech-iOS: 723eccb3e459bbb530df3d9bfe84a291074b09eb
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
RecaptchaInterop: 7d1a4a01a6b2cb1610a47ef3f85f0c411434cb21
record_darwin: 3b1a8e7d5c0cbf45ad6165b4d83a6ca643d929c3
record_darwin: fb1f375f1d9603714f55b8708a903bbb91ffdb0a
SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
speech_to_text: 627d3fd2194770b51abb324ba45c2d39398f24a8
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
speech_to_text: 9dc43a5df3cbc2813f8c7cc9bd0fbf94268ed7ac
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
Try: 5ef669ae832617b3cee58cb2c6f99fb767a4ff96
WechatOpenSDK-XCFramework: 36fb2bea0754266c17184adf4963d7e6ff98b69f

30
ios/Runner.xcodeproj/project.pbxproj

@ -20,6 +20,7 @@
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3406320CAE3314A73EF78043 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BBE10AD7954527B6BB12CFB /* Pods_RunnerTests.framework */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
591293932D9524D1004A65BB /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 591293922D9524D1004A65BB /* GoogleService-Info.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 */; };
@ -68,6 +69,8 @@
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
4A9190D0838960F143BDC158 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5912938C2D9520CE004A65BB /* RunnerRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerRelease.entitlements; sourceTree = "<group>"; };
591293922D9524D1004A65BB /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
5BBE10AD7954527B6BB12CFB /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@ -161,6 +164,7 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
5912938C2D9520CE004A65BB /* RunnerRelease.entitlements */,
0A7A32E22D8DBA6D0047F746 /* VoiceInteractionService.swift */,
0A7A32E32D8DBA6D0047F746 /* VolcanoAIService.swift */,
0ADE296F2D8C6C450032BFCB /* AudioSessionManager.swift */,
@ -171,6 +175,7 @@
0A5EDC032D8B544100215E26 /* BluetoothMediaButtonHelper.swift */,
0A5EDBFB2D89C4FD00215E26 /* ClassicBluetoothHelper.swift */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
591293922D9524D1004A65BB /* GoogleService-Info.plist */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
@ -265,6 +270,9 @@
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
packageReferences = (
5912938D2D95215F004A65BB /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
);
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
@ -293,6 +301,7 @@
0ADE29692D8C25660032BFCB /* silence.mp3 in Resources */,
0ADE296A2D8C25660032BFCB /* sample.mp3 in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
591293932D9524D1004A65BB /* GoogleService-Info.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -523,7 +532,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
CURRENT_PROJECT_VERSION = 23;
DEVELOPMENT_TEAM = 42C82SZUVX;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@ -532,6 +541,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -723,7 +733,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
CURRENT_PROJECT_VERSION = 23;
DEVELOPMENT_TEAM = 42C82SZUVX;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@ -732,6 +742,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -748,7 +759,8 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
CURRENT_PROJECT_VERSION = 23;
DEVELOPMENT_TEAM = 42C82SZUVX;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@ -757,6 +769,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -799,6 +812,17 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
5912938D2D95215F004A65BB /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/firebase/firebase-ios-sdk";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 11.10.0;
};
};
/* End XCRemoteSwiftPackageReference section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

123
ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

@ -0,0 +1,123 @@
{
"originHash" : "c63c63846d9c539229e96de38d6af51417e28c0ee9a0bc48bd0f0f19d923c329",
"pins" : [
{
"identity" : "abseil-cpp-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/abseil-cpp-binary.git",
"state" : {
"revision" : "bbe8b69694d7873315fd3a4ad41efe043e1c07c5",
"version" : "1.2024072200.0"
}
},
{
"identity" : "app-check",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/app-check.git",
"state" : {
"revision" : "61b85103a1aeed8218f17c794687781505fbbef5",
"version" : "11.2.0"
}
},
{
"identity" : "firebase-ios-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/firebase-ios-sdk",
"state" : {
"revision" : "2e4a4daac9da51f79c131e76cb47e785dd7708c4",
"version" : "11.10.0"
}
},
{
"identity" : "googleappmeasurement",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleAppMeasurement.git",
"state" : {
"revision" : "defe1970a9099d47765592ebd7ac5f5e9fdb57c6",
"version" : "11.10.0"
}
},
{
"identity" : "googledatatransport",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleDataTransport.git",
"state" : {
"revision" : "617af071af9aa1d6a091d59a202910ac482128f9",
"version" : "10.1.0"
}
},
{
"identity" : "googleutilities",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleUtilities.git",
"state" : {
"revision" : "53156c7ec267db846e6b64c9f4c4e31ba4cf75eb",
"version" : "8.0.2"
}
},
{
"identity" : "grpc-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/grpc-binary.git",
"state" : {
"revision" : "cc0001a0cf963aa40501d9c2b181e7fc9fd8ec71",
"version" : "1.69.0"
}
},
{
"identity" : "gtm-session-fetcher",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/gtm-session-fetcher.git",
"state" : {
"revision" : "4d70340d55d7d07cc2fdf8e8125c4c126c1d5f35",
"version" : "4.4.0"
}
},
{
"identity" : "interop-ios-for-google-sdks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/interop-ios-for-google-sdks.git",
"state" : {
"revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe",
"version" : "101.0.0"
}
},
{
"identity" : "leveldb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/leveldb.git",
"state" : {
"revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1",
"version" : "1.22.5"
}
},
{
"identity" : "nanopb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/nanopb.git",
"state" : {
"revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1",
"version" : "2.30910.0"
}
},
{
"identity" : "promises",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/promises.git",
"state" : {
"revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac",
"version" : "2.4.0"
}
},
{
"identity" : "swift-protobuf",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-protobuf.git",
"state" : {
"revision" : "d72aed98f8253ec1aa9ea1141e28150f408cf17f",
"version" : "1.29.0"
}
}
],
"version" : 3
}

123
ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved

@ -0,0 +1,123 @@
{
"originHash" : "c63c63846d9c539229e96de38d6af51417e28c0ee9a0bc48bd0f0f19d923c329",
"pins" : [
{
"identity" : "abseil-cpp-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/abseil-cpp-binary.git",
"state" : {
"revision" : "bbe8b69694d7873315fd3a4ad41efe043e1c07c5",
"version" : "1.2024072200.0"
}
},
{
"identity" : "app-check",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/app-check.git",
"state" : {
"revision" : "61b85103a1aeed8218f17c794687781505fbbef5",
"version" : "11.2.0"
}
},
{
"identity" : "firebase-ios-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/firebase-ios-sdk",
"state" : {
"revision" : "2e4a4daac9da51f79c131e76cb47e785dd7708c4",
"version" : "11.10.0"
}
},
{
"identity" : "googleappmeasurement",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleAppMeasurement.git",
"state" : {
"revision" : "defe1970a9099d47765592ebd7ac5f5e9fdb57c6",
"version" : "11.10.0"
}
},
{
"identity" : "googledatatransport",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleDataTransport.git",
"state" : {
"revision" : "617af071af9aa1d6a091d59a202910ac482128f9",
"version" : "10.1.0"
}
},
{
"identity" : "googleutilities",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleUtilities.git",
"state" : {
"revision" : "53156c7ec267db846e6b64c9f4c4e31ba4cf75eb",
"version" : "8.0.2"
}
},
{
"identity" : "grpc-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/grpc-binary.git",
"state" : {
"revision" : "cc0001a0cf963aa40501d9c2b181e7fc9fd8ec71",
"version" : "1.69.0"
}
},
{
"identity" : "gtm-session-fetcher",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/gtm-session-fetcher.git",
"state" : {
"revision" : "4d70340d55d7d07cc2fdf8e8125c4c126c1d5f35",
"version" : "4.4.0"
}
},
{
"identity" : "interop-ios-for-google-sdks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/interop-ios-for-google-sdks.git",
"state" : {
"revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe",
"version" : "101.0.0"
}
},
{
"identity" : "leveldb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/leveldb.git",
"state" : {
"revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1",
"version" : "1.22.5"
}
},
{
"identity" : "nanopb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/nanopb.git",
"state" : {
"revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1",
"version" : "2.30910.0"
}
},
{
"identity" : "promises",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/promises.git",
"state" : {
"revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac",
"version" : "2.4.0"
}
},
{
"identity" : "swift-protobuf",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-protobuf.git",
"state" : {
"revision" : "d72aed98f8253ec1aa9ea1141e28150f408cf17f",
"version" : "1.29.0"
}
}
],
"version" : 3
}

36
ios/Runner/GoogleService-Info.plist

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CLIENT_ID</key>
<string>907754492821-oc9e77svgmmlgouo0op7vv00e1hb8278.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.907754492821-oc9e77svgmmlgouo0op7vv00e1hb8278</string>
<key>ANDROID_CLIENT_ID</key>
<string>907754492821-9jakqvathu61e6b8g95i41ssi7o4dnka.apps.googleusercontent.com</string>
<key>API_KEY</key>
<string>AIzaSyAvyBQcOXuO_q851vevqZIufsPRTMCe808</string>
<key>GCM_SENDER_ID</key>
<string>907754492821</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.yunqiinnovation.deepsound</string>
<key>PROJECT_ID</key>
<string>deepsound-e0e3d</string>
<key>STORAGE_BUCKET</key>
<string>deepsound-e0e3d.firebasestorage.app</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true></true>
<key>IS_GCM_ENABLED</key>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:907754492821:ios:246794fc44b23174145c9c</string>
</dict>
</plist>

32
ios/Runner/Info.plist

@ -2,6 +2,36 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.yunqiinnovation.deepsound</string>
<key>CFBundleURLSchemes</key>
<array>
<string>fb3829022037361855</string>
<string>com.googleusercontent.apps.907754492821-oc9e77svgmmlgouo0op7vv00e1hb8278</string>
</array>
</dict>
</array>
<key>FacebookAppID</key>
<string>3829022037361855</string>
<key>FacebookClientToken</key>
<string>193d0a2b1757c41cfa557ea8e8f21c33</string>
<key>FacebookDisplayName</key>
<string>Deepsound</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>fbapi</string>
<string>fb-messenger-api</string>
<string>fbauth2</string>
<string>fbshareextension</string>
<string>googlechrome</string>
<string>googlechromes</string>
</array>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
@ -10,6 +40,8 @@
<string>DeepSound</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>NSUserTrackingUsageDescription</key>
<string>This application needs to access your tracking data in order to offer better personalized services and advertisements</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>

10
ios/Runner/RunnerRelease.entitlements

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)com.yunqiinnovation.deepsound</string>
</array>
</dict>
</plist>

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

@ -43,6 +43,7 @@ const Map<String, String> enUS = {
"and": " and ",
"userAgreement": "User Agreement",
"privacyPolicy": "Privacy Policy",
'selectCountry':'Select Country/Region',
// 首页
"deviceStatus": "Device Status",
@ -85,6 +86,8 @@ const Map<String, String> enUS = {
"fine": "Great",
"excellent": "Excellent",
"unknown": "Unknown",
'bluetoothTurnedOff': 'Bluetooth is turned off, please turn it on',
'bluetoothOff':'bluetooth is off',
// 发现页
"searchAi": "Search AI features...",
@ -99,8 +102,8 @@ const Map<String, String> enUS = {
"simultaneousTranslationInfo": "Instant translation for 80+ languages",
"aiGirlfriend": "Parent-child bondingn",
"aiGirlfriendInfo": "Intelligent company, chat at any time",
"healthConsultant": "Health Consultant",
"healthConsultantInfo": "Provides scientific health advice",
"healthConsultant": "Multi-function AI chat",
"healthConsultantInfo": "More AI chat features",
"conferenceAssistant": "Business Meeting Assistant",
"conferenceAssistantInfo":
"Real-time minutes recording and multilingual translation",
@ -148,6 +151,11 @@ const Map<String, String> enUS = {
"flutterVoiceService": "Local voice service",
"flutterVoiceServiceInfo":
"Device-local voice service with no internet required",
'timbreService': 'Timbre Service',
'timbreServiceInfo': 'Select voice synthesis service',
'defaultTimbre': 'Default Timbre',
'microsoftCloudTimbreService': 'Microsoft Cloud Timbre Service',
'voiceClone': 'Voice Clone',
// 聊天页
"clearChatHistory": "Clear Chat History",

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

@ -84,6 +84,8 @@ const Map<String, String> zhCN = {
"fine": "很好",
"excellent": "极佳",
"unknown": "未知",
'bluetoothTurnedOff': '蓝牙已关闭,请打开蓝牙',
'bluetoothOff': '蓝牙已关闭',
// 发现页
"searchAi": "搜索AI功能...",
@ -98,8 +100,8 @@ const Map<String, String> zhCN = {
"simultaneousTranslationInfo": "实时翻译80+种语言",
"aiGirlfriend": "亲子陪伴",
"aiGirlfriendInfo": "智能陪伴,随时聊天",
"healthConsultant": "健康顾问",
"healthConsultantInfo": "提供科学健康建议",
"healthConsultant": "多功能AI聊天",
"healthConsultantInfo": "提供更多AI聊天功能",
"conferenceAssistant": "商务会议助手",
"conferenceAssistantInfo": "实时记录会议纪要,多语言翻译",
"studyAssistant": "语言学习助手",
@ -143,6 +145,12 @@ const Map<String, String> zhCN = {
"azureVoiceServiceInfo": "微软云端语音服务,支持高质量语音合成和识别",
"flutterVoiceService": "Flutter 本地语音服务",
"flutterVoiceServiceInfo": "设备本地语音服务,无需联网即可使用",
'timbreService': '音色服务',
'timbreServiceInfo': '选择音色语音合成服务',
'defaultTimbre': '默认音色',
'microsoftCloudTimbreService': '微软云端音色服务',
'voiceClone': '声音复刻',
'selectCountry': '选择国家/地区',
// 聊天页
"clearChatHistory": "清空聊天记录",

8
lib/data/providers/agent_provider.dart

@ -130,15 +130,13 @@ class AgentProvider {
'''),
Agent(
id: 'health_advisor',
name: '健康顾问 吴小江博士',
name: '多功能AI聊天 吴小江',
avatarUrl: 'assets/images/agents/doctor.png',
description:
'我是吴博士,拥有丰富的健康管理经验。我可以为你提供科学的健康建议、生活方式指导和疾病预防知识,帮助你实现身心健康的平衡。',
description: '我是吴小江,我可以为你提供更多AI聊天功能。',
tag: '健康',
isOnline: true,
voice: "zh_male_shaonianzixin_moon_bigtts",
welcomeMessage:
"您好,我是李医生,很高兴能为您提供健康咨询服务。健康是人生最宝贵的财富,我将用专业知识帮助您维护和改善健康状况。请问有什么健康问题想咨询吗?",
welcomeMessage: "您好,我是吴小江,很高兴能为您提供多功能AI聊天。我将用专业知识帮助您。请问有什么问题想咨询吗?",
systemPrompt: '''
你是一位名叫李医生的健康顾问,拥有丰富的医学知识和健康管理经验。

13
lib/data/services/asr_service.dart

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:typed_data';
/// 语音识别服务接口
abstract class AsrService {
@ -62,6 +63,12 @@ class RecognitionEvent {
/// 检测到的语言
final String detectedLanguage;
/// 角色
final String role;
/// 原始音频
final Uint8List? audio;
/// 错误信息(仅在 error 和 canceled 类型中有效)
final String error;
@ -69,6 +76,8 @@ class RecognitionEvent {
required this.type,
this.text = '',
this.detectedLanguage = '',
this.role = '',
this.audio,
this.error = '',
});
@ -96,7 +105,9 @@ class RecognitionEvent {
bool get isFinalResult => type == RecognitionEventType.finalResult;
/// 检查是否为错误
bool get isError => type == RecognitionEventType.error || type == RecognitionEventType.canceled;
bool get isError =>
type == RecognitionEventType.error ||
type == RecognitionEventType.canceled;
@override
String toString() {

23
lib/data/services/deapsound_ai_service.dart

@ -20,16 +20,27 @@ class DeapsoundAIService implements AiService {
required String systemPrompt,
}) async* {
final client = http.Client();
final url = Uri.parse('https://www.deapsound.com/ai/chat');
final url = Uri.parse('https://www.deapsound.com/ai/funcchat');
List contextsList = [];
String msg = '';
for (var i = 0; i < messages.length; i++) {
Map item = messages[i];
if (i < messages.length - 1) {
if (item['role'] == 'user') {
contextsList.add(item['content']);
}
} else {
msg = item['content'];
}
}
final headers = {
'Content-Type': 'application/json',
'Authorization': apiKey,
};
final requestBody = {
"msg": messages.last['content'],
};
final requestBody = {"contexts": contextsList, "msg": msg};
try {
final request = http.Request('POST', url)
@ -51,8 +62,8 @@ class DeapsoundAIService implements AiService {
if (data['event'] == 'workflow_finished') {
break;
}
if (data['event'] == 'text_chunk') {
String content = data['data']['text'] ?? '';
if (data['content'] != null) {
String content = data['content'] ?? '';
if (content.isNotEmpty) {
yield content;
}

272
lib/data/services/speech_impl/xunfei_asr_service.dart

@ -0,0 +1,272 @@
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:record/record.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import '../../../core/utils/logger.dart';
import 'package:get/get.dart';
import '../asr_service.dart';
/// 讯飞 语音识别服务
///
class XunfeiAsrService extends GetxService implements AsrService {
final String baseUrl = 'wss://rtasr.xfyun.cn/v1/ws';
final int _chunkSize = 1280; // 每次发送的数据大小
final String appId = '43d87d22';
final String apiKey = '83f063755e8d342670002f50a4471d42';
AudioRecorder? recorder;
Timer? _timer;
WebSocketChannel? _channel;
bool isConnected = false; //是否链接
bool _isInitialized = false;
final List<String> _defaultSupportedLanguages = ['zh-CN'];
String _languageStr = 'cn';
// 连续识别相关
bool _isContinuousRecognitionActive = false;
StreamController<RecognitionEvent>? _eventStreamController;
@override
List<String> get supportedLanguages => _defaultSupportedLanguages;
Uint8List _buff = Uint8List(0); //音频缓存区
BytesBuilder _originaBytes = BytesBuilder(); //原始音频
bool isRecording = false;
@override
Future<bool> initialize({
List<String>? supportedLanguages,
}) async {
try {
final List<String> languages =
supportedLanguages ?? _defaultSupportedLanguages;
String languagetoStr = languages.first.split('-')[1].toLowerCase();
_languageStr = languagetoStr;
_isInitialized = true;
Logger.info('Azure 语音识别服务初始化成功');
return true;
} catch (e) {
Logger.error('Azure 语音识别服务初始化失败: $e');
_isInitialized = false;
rethrow;
}
}
@override
Future<RecognitionEvent> recognizeOnce() async {
if (!_isInitialized) {
await initialize();
}
try {
throw Exception('无效的识别结果格式');
} catch (e) {
Logger.error('语音识别失败: $e');
return RecognitionEvent.error(e.toString());
}
}
@override
Future<Stream<RecognitionEvent>> startContinuousRecognition() async {
if (!_isInitialized) {
await initialize();
}
try {
_eventStreamController = StreamController<RecognitionEvent>.broadcast();
await recorder?.dispose();
recorder = AudioRecorder();
Stream<Uint8List>? stream = await recorder?.startStream(
const RecordConfig(
sampleRate: 16000,
encoder: AudioEncoder.pcm16bits,
numChannels: 1,
),
);
isRecording = true;
stream?.listen((data) {
if (isRecording) {
_buff = _appendToBuffer(data);
_originaBytes.add(data);
}
});
await _tryConnecting();
_timer = Timer.periodic(const Duration(milliseconds: 40), (timer) async {
//每40毫秒读取一次数据
var frame = _getAudioData();
if (frame.isNotEmpty) {
_channel?.sink.add(frame);
}
});
_isContinuousRecognitionActive = true;
return _eventStreamController!.stream;
} catch (e) {
Logger.error('开始连续语音识别失败: $e');
rethrow;
}
}
//写入音频数据到缓存区中
Uint8List _appendToBuffer(Uint8List newData) {
var newBuffer = Uint8List(_buff.length + newData.length);
newBuffer.setAll(0, _buff);
newBuffer.setAll(_buff.length, newData);
return newBuffer;
}
//读取缓存区中一帧数据
Uint8List _getAudioData() {
if (_buff.length >= _chunkSize) {
// 从缓冲区中读取1280字节的数据
var data = _buff.sublist(0, _chunkSize);
// 移除已读取的数据
_buff = _buff.sublist(_chunkSize);
return data;
} else {
return _buff;
}
}
//尝试连接
Future<void> _tryConnecting() async {
try {
final url = _geturl();
_channel = WebSocketChannel.connect(Uri.parse(url));
_channel?.stream.timeout(const Duration(seconds: 10)); //设置超时时间
await _channel?.ready;
isConnected = true;
_channel?.stream.listen(
(message) {
_handle(message);
},
onError: (error) {
isConnected = false;
Logger.error('连接失败: $error');
},
onDone: () {
isConnected = false;
Logger.error('WebSocket 连接已关闭!');
},
cancelOnError: true,
);
} catch (e) {
Logger.error("初始化异常----------------$e");
}
}
//处理数据
void _handle(data) {
try {
final result = jsonDecode(data);
if (result['code'] == "0" && result['data'] != "") {
final data = jsonDecode(result['data']);
String msg = "";
String role = '';
List rtList = data['cn']['st']['rt'];
for (var rt in rtList) {
for (var ws in rt['ws']) {
for (var ws in ws['cw']) {
msg += ws['w'];
if (ws['rl'] != "0") {
role = ws['rl'];
}
}
}
}
Logger.info(
"识别到的内容 type:${data['cn']['st']['type']} msg:$msg rl:$role");
if (msg.isNotEmpty) {
if (data['cn']['st']['type'] == "0") {
_eventStreamController?.add(RecognitionEvent(
type: RecognitionEventType.finalResult,
text: msg,
role: role,
audio: _originaBytes.toBytes(),
));
_originaBytes.clear();
} else {
_eventStreamController?.add(RecognitionEvent(
type: RecognitionEventType.intermediateResult,
text: msg,
role: role,
));
}
}
}
} catch (e) {
Logger.error("接受消息异常 $e");
}
}
///获取链接地址
String _geturl() {
// 生成时间戳
String ts = (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString();
// 计算 baseString
String baseString = md5.convert(utf8.encode(appId + ts)).toString();
// 计算 signa
var hmacSha1 = Hmac(sha1, utf8.encode(apiKey));
var signa = hmacSha1.convert(utf8.encode(baseString));
String signaBase64 = base64Encode(signa.bytes);
String url = "";
// 构建 WebSocket URL
url =
'$baseUrl?appid=$appId&ts=$ts&lang=$_languageStr&roleType=2&signa=${Uri.encodeComponent(signaBase64)}';
return url;
}
@override
Future<bool> stopContinuousRecognition() async {
try {
await recorder?.stop();
isRecording = false;
_timer?.cancel();
_buff = Uint8List(0);
_originaBytes = BytesBuilder();
if (isConnected) {
_channel?.sink.add(utf8.encode('{"end": true}')); //结束
}
_isContinuousRecognitionActive = false;
return true;
} catch (e) {
Logger.error('停止连续语音识别失败: $e');
rethrow;
}
}
@override
bool isContinuousRecognitionActive() {
return _isContinuousRecognitionActive;
}
@override
Future<void> dispose() async {
try {
await stopContinuousRecognition();
recorder?.dispose();
_channel?.sink.close();
_eventStreamController?.close();
_eventStreamController = null;
_isInitialized = false;
Logger.info('Azure 语音识别资源已释放');
} catch (e) {
Logger.error('释放语音识别资源失败: $e');
_isInitialized = false;
}
}
}

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

@ -93,16 +93,16 @@ 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;
case 'health_advisor':
// 多功能AI聊天
Get.put(DeapsoundAIService());
_aiService = Get.find<DeapsoundAIService>();
break;
default:
_aiService = Get.find<VolcanoAIService>();
}

2
lib/modules/home/bindings/home_binding.dart

@ -1,4 +1,4 @@
import 'package:deep_voice/modules/explore/controllers/explore_controller.dart';
import '../../explore/controllers/explore_controller.dart';
import 'package:get/get.dart';
import '../controllers/home_controller.dart';

2
lib/modules/home/controllers/home_controller.dart

@ -278,7 +278,7 @@ class HomeController extends GetxController {
String getConnectionStatusText() {
// 如果蓝牙关闭,优先显示蓝牙状态
if (_bluetoothService.bluetoothState.value == 'off') {
return '蓝牙已关闭';
return 'bluetoothOff'.tr; // 蓝牙已关闭
}
switch (_connectionStatus.value) {

106
lib/modules/login/controllers/facebookLogin.dart

@ -1,6 +1,13 @@
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
import 'package:get/get.dart';
import 'package:crypto/crypto.dart';
class Facebook {
String token = '';
@ -9,35 +16,90 @@ class Facebook {
String email = '';
String pictureurl = '';
String id = '';
String generateNonce([int length = 32]) {
const charset =
'0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._';
final random = Random.secure();
return List.generate(length, (_) => charset[random.nextInt(charset.length)])
.join();
}
/// Returns the sha256 hash of [input] in hex notation.
String sha256ofString(String input) {
final bytes = utf8.encode(input);
final digest = sha256.convert(bytes);
return digest.toString();
}
// FaceBook登录
// 修改后的 signInWithFacebook
Future<bool> signInWithFacebook() async {
try {
print("开始 Facebook 登录");
final LoginResult result = await FacebookAuth.instance.login();
print("Facebook 登录结果:$result");
if (result.status == LoginStatus.success) {
token = result.accessToken!.tokenString;
print("Facebook Token: $token");
final credential = FacebookAuthProvider.credential(token);
print("Facebook Token: $credential");
final userData = await FacebookAuth.instance.getUserData();
name = userData["name"].toString();
email = userData["email"].toString();
pictureurl = userData["picture"]["data"]["url"].toString();
id = userData["id"].toString();
print("user login facebook ${userData..values}");
print("user login facebook ${email}${pictureurl}${name}${id}");
return true;
final rawNonce = generateNonce();
final nonce = sha256ofString(rawNonce);
final LoginResult loginResult = await FacebookAuth.instance
.login(
loginTracking: LoginTracking.enabled,
nonce: nonce,
)
.catchError((onError) {
if (kDebugMode) {
//print(onError);
}
throw Exception(onError.message);
});
if (loginResult.accessToken == null) {
throw Exception(loginResult.message);
}
// Create a credential from the access token
OAuthCredential facebookAuthCredential;
print("tokenType${loginResult.accessToken!.type}");
if (Platform.isIOS) {
// Create a credential from the access token
switch (loginResult.accessToken!.type) {
case AccessTokenType.classic:
final classicToken = loginResult.accessToken as ClassicToken;
token = classicToken.tokenString;
print('Token 字符串: ${classicToken.tokenString}');
print('classicToken值: ${classicToken}');
facebookAuthCredential = FacebookAuthProvider.credential(
classicToken.authenticationToken!,
);
break;
case AccessTokenType.limited:
final limitedToken = loginResult.accessToken as LimitedToken;
token = limitedToken.tokenString;
print('Token 字符串: ${limitedToken.tokenString}');
print('LimitedToken值: ${limitedToken}');
facebookAuthCredential = OAuthCredential(
providerId: 'facebook.com',
signInMethod: 'oauth',
idToken: limitedToken.tokenString,
rawNonce: rawNonce,
);
break;
}
} else {
print("登录失败,状态:${result.status},消息:${result.message}");
return false;
facebookAuthCredential = FacebookAuthProvider.credential(
loginResult.accessToken!.tokenString,
);
token = loginResult.accessToken!.tokenString;
}
print('facebookAuthCredential值: ${facebookAuthCredential}');
if (facebookAuthCredential == null) {
throw Exception("Facebook Login Fail.");
}
final userData = await FacebookAuth.instance.getUserData();
name = userData["name"].toString();
email = userData["email"].toString();
pictureurl = userData["picture"]["data"]["url"].toString();
id = userData["id"].toString();
print("user login facebook ${email}${pictureurl}${name}${id}");
return true;
} catch (e) {
print("Facebook 登录异常:$e");
return false;

7
lib/modules/login/controllers/googleLogin.dart

@ -33,6 +33,8 @@ class Google {
return true;
} else {
UserCredential userCredential = await loginGoogle();
token = await userCredential.user?.getIdToken();
print('Future响应数据tokenResult: $token');
if (token != "") {
return true;
} else {
@ -61,10 +63,7 @@ class Google {
// Obtain the auth details from the request
final GoogleSignInAuthentication? googleAuth =
await googleUser?.authentication;
accessToken = googleAuth?.accessToken;
token = googleAuth?.idToken;
print('loginGoogle:googleAuth?.idToken= ${token}');
print('loginGoogle:googleAuth?.accessToken= ${accessToken}');
// Create a new credential,
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth?.accessToken,

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

@ -21,8 +21,8 @@ class LoginController extends GetxController {
final GetStorage _storage = GetStorage();
// 添加国家编码相关状态
final _selectedCountryCode = '+86'.obs;
final _selectedCountryFlag = '🇨🇳'.obs;
final _selectedCountryCode = ''.obs;
final _selectedCountryFlag = '✉️'.obs;
// 国家编码 Getter
String get selectedCountryCode => _selectedCountryCode.value;
String get selectedCountryFlag => _selectedCountryFlag.value;
@ -32,6 +32,8 @@ class LoginController extends GetxController {
{'name': '中国', 'code': '+86', 'flag': '🇨🇳'}, // 保持第二位
// 按国家代码数字升序排列(去除+号后排序)
{'name': '美国', 'code': '+1', 'flag': '🇺🇸'},
{'name': '加拿大', 'code': '+1', 'flag': '🇨🇦'},
{'name': '俄罗斯', 'code': '+7', 'flag': '🇷🇺'},
{'name': '英国', 'code': '+44', 'flag': '🇬🇧'},
{'name': '德国', 'code': '+49', 'flag': '🇩🇪'},
@ -52,8 +54,6 @@ class LoginController extends GetxController {
{'name': '菲律宾', 'code': '+63', 'flag': '🇵🇭'},
{'name': '印度尼西亚', 'code': '+62', 'flag': '🇮🇩'},
{'name': '阿联酋', 'code': '+971', 'flag': '🇦🇪'},
{'name': '美国', 'code': '+1', 'flag': '🇺🇸'},
{'name': '加拿大', 'code': '+1', 'flag': '🇨🇦'},
{'name': '墨西哥', 'code': '+52', 'flag': '🇲🇽'},
{'name': '巴西', 'code': '+55', 'flag': '🇧🇷'},
{'name': '阿根廷', 'code': '+54', 'flag': '🇦🇷'},
@ -104,7 +104,7 @@ class LoginController extends GetxController {
_selectedCountryCode.value = code;
_selectedCountryFlag.value = flag;
// 根据选择更新输入框类型
if (code == '@') {
if (code == '') {
contactController.clear(); // 清空当前输入
loginType = 0; // 设置为邮箱登录类型
} else {
@ -246,6 +246,7 @@ class LoginController extends GetxController {
// 为了演示,我们只是模拟发送
if (isEmialContactController() || isPhoneContactController()) {
_startCountdown();
print(contactController.text);
var response = await Api.getCode({
"addr": selectedCountryCode + contactController.text,
"vtype": loginType,

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

@ -1,13 +1,16 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:just_audio/just_audio.dart';
import '../../../data/services/db/sqflite_api.dart';
import '../../../data/services/speech_impl/volcano_tts_api_service.dart';
import '../model/meeting_details_model.dart';
import '../model/meeting_model.dart';
class MeetingDetailsController extends GetxController
with GetSingleTickerProviderStateMixin {
late AnimationController lottieController;
final AudioPlayer audioPlayer = AudioPlayer();
int id = 0;
@ -15,6 +18,8 @@ class MeetingDetailsController extends GetxController
Rx<MeetingModel> meetingData = MeetingModel().obs;
Rx<MeetingDetailsModel> dataDetails = MeetingDetailsModel().obs;
int seconds = 0;
RxBool isPlay = false.obs;
@override
@ -29,6 +34,7 @@ class MeetingDetailsController extends GetxController
@override
void onClose() {
lottieController.dispose();
audioPlayer.dispose();
super.onClose();
}
@ -36,15 +42,26 @@ class MeetingDetailsController extends GetxController
final data = await SqfliteApi.getMeetingDetails(id);
meetingData.value = MeetingModel.fromJson(data);
dataDetails.value = MeetingDetailsModel.fromJson(data['details']);
audioPlayer.setAudioSource(BytesAudioSource(dataDetails.value.audio));
audioPlayer.playerStateStream.listen((state) async {
if (state.processingState == ProcessingState.completed) {
await audioPlayer.seek(Duration.zero);
audioPlayer.pause();
lottieController.stop();
isPlay.value = false;
}
});
isLoading.value = false;
}
void start() {
void start() async {
if (isPlay.value) {
lottieController.stop();
audioPlayer.pause();
isPlay.value = false;
} else {
lottieController.repeat();
audioPlayer.play();
isPlay.value = true;
}
}

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

@ -1,22 +1,28 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:just_audio/just_audio.dart';
import '../../../data/services/db/sqflite_api.dart';
import '../model/meeting_details_model.dart';
import '../model/meeting_model.dart';
import '../../../data/services/speech_impl/volcano_tts_api_service.dart';
import 'meeting_controller.dart';
import 'meeting_details_controller.dart';
class MeetingEditController extends GetxController
with GetSingleTickerProviderStateMixin {
final MeetingController meetingController = Get.find<MeetingController>();
final MeetingDetailsController meetingDetailsController =
Get.find<MeetingDetailsController>();
late AnimationController lottieController;
late TextEditingController titleController;
late TextEditingController contentController;
int id = 0;
final AudioPlayer audioPlayer = AudioPlayer();
RxBool isLoading = true.obs;
Rx<MeetingModel> meetingData = MeetingModel().obs;
Rx<MeetingDetailsModel> dataDetails = MeetingDetailsModel().obs;
int creationTime = 0;
int id = 0;
RxBool isPlay = false.obs;
@ -34,29 +40,75 @@ class MeetingEditController extends GetxController
lottieController.dispose();
titleController.dispose();
contentController.dispose();
audioPlayer.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,
text: meetingDetailsController.meetingData.value.title,
);
contentController = TextEditingController(
text: dataDetails.value.content,
text: meetingDetailsController.dataDetails.value.content,
);
isLoading.value = false;
creationTime = meetingDetailsController.meetingData.value.creationtime;
audioPlayer.setAudioSource(
BytesAudioSource(meetingDetailsController.dataDetails.value.audio));
audioPlayer.playerStateStream.listen((state) async {
if (state.processingState == ProcessingState.completed) {
await audioPlayer.seek(Duration.zero);
audioPlayer.pause();
lottieController.stop();
isPlay.value = false;
}
});
}
void start() {
void start() async {
if (isPlay.value) {
lottieController.stop();
audioPlayer.pause();
isPlay.value = false;
} else {
lottieController.repeat();
await audioPlayer.seek(Duration.zero);
audioPlayer.play();
isPlay.value = true;
}
}
void confirm() async {
String content = contentController.text;
String firstContent =
content.length >= 30 ? content.substring(0, 30) : content;
String title = titleController.text.isNotEmpty
? titleController.text
: meetingDetailsController.meetingData.value.title;
Map<String, Object?> data = {
'title': title,
'firstContent': firstContent.replaceAll("\n", ""),
'seconds': meetingDetailsController.seconds,
'content': contentController.text,
};
final result = await SqfliteApi.editMeeting(id, data);
if (result != null) {
int index = meetingController.dataList.indexWhere(
(item) => item['id'] == id,
);
if (index != -1) {
final dataMap =
Map<String, dynamic>.from(meetingController.dataList[index]);
dataMap['title'] = title;
dataMap['content'] = firstContent.replaceAll("\n", "");
dataMap['seconds'] = meetingDetailsController.seconds;
meetingController.dataList[index] = dataMap;
}
meetingDetailsController.meetingData.value.title = title;
meetingDetailsController.dataDetails.value.content =
contentController.text;
meetingDetailsController.meetingData.refresh();
meetingDetailsController.dataDetails.refresh();
Get.back();
}
}
}

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

@ -1,10 +1,12 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../data/services/asr_service.dart';
import '../../../data/services/db/sqflite_api.dart';
import '../../../data/services/speech_impl/xunfei_asr_service.dart';
import 'meeting_controller.dart';
class MeetingRecordController extends GetxController
@ -27,11 +29,14 @@ class MeetingRecordController extends GetxController
StreamSubscription? _recognitionSubscription;
BytesBuilder _originaBytes = BytesBuilder(); //原始音频
@override
void onInit() {
super.onInit();
lottieController = AnimationController(vsync: this);
_asrService = Get.find<AsrService>();
Get.put(XunfeiAsrService());
_asrService = Get.find<XunfeiAsrService>();
final Map<String, dynamic> args = Get.arguments ?? {};
title = args['title'];
creationTime = DateTime.now().millisecondsSinceEpoch;
@ -41,8 +46,9 @@ class MeetingRecordController extends GetxController
void onClose() {
lottieController.dispose();
timer?.cancel();
_asrService.stopContinuousRecognition();
_asrService.dispose();
_recognitionSubscription?.cancel();
_originaBytes = BytesBuilder();
super.onClose();
}
@ -82,7 +88,6 @@ class MeetingRecordController extends GetxController
//结束录音
Future<void> _stopRecorder() async {
await _asrService.stopContinuousRecognition();
_recognitionSubscription?.cancel();
}
// 处理语音识别事件
@ -92,8 +97,9 @@ class MeetingRecordController extends GetxController
intermediateContent.value = event.text;
break;
case RecognitionEventType.finalResult:
finalContent.value += event.text;
finalContent.value += _spliceText(event);
intermediateContent.value = '';
_originaBytes.add(event.audio!);
break;
default:
// 不做任何处理
@ -101,17 +107,60 @@ class MeetingRecordController extends GetxController
}
}
String _spliceText(RecognitionEvent event) {
String eventText = event.text;
String text = '';
if (finalContent.value.isNotEmpty) {
if (event.role != '') {
final punctuations = {
',',
'。',
'!',
'?',
';',
':',
',',
'.',
':',
';',
'!',
'?'
};
final firstChar = eventText[0];
if (punctuations.contains(firstChar)) {
text += '$firstChar\n';
eventText = eventText.substring(1);
} else {
text += '\n';
}
text += '说话人${event.role}:';
}
} else {
text += '说话人${event.role}:';
}
text += eventText;
return text;
}
// 完成
void complete() async {
String firstContent = finalContent.value.length >= 20
? finalContent.value.substring(0, 20)
String firstContent = finalContent.value.length >= 30
? finalContent.value.substring(0, 30)
: finalContent.value;
Uint8List audio = _originaBytes.toBytes();
Uint8List wavHeader = generateWavHeader(
sampleRate: 16000,
numberOfChannels: 1,
bitsPerSample: 16,
dataLength: audio.length,
);
Uint8List wavData = Uint8List.fromList([...wavHeader, ...audio]);
Map<String, Object?> data = {
'title': title,
'firstContent': firstContent,
'firstContent': firstContent.replaceAll("\n", ""),
'seconds': seconds.value,
'creationTime': creationTime,
'audio': [],
'audio': wavData,
'content': finalContent.value,
};
final result = await SqfliteApi.insertMeeting(data);
@ -126,4 +175,73 @@ class MeetingRecordController extends GetxController
Get.back();
}
}
Uint8List generateWavHeader({
required int sampleRate,
required int numberOfChannels,
required int bitsPerSample,
required int dataLength,
}) {
final byteRate = sampleRate * numberOfChannels * (bitsPerSample ~/ 8);
final blockAlign = numberOfChannels * (bitsPerSample ~/ 8);
final riffSize = 36 + dataLength;
final header = Uint8List(44);
var pos = 0;
// RIFF 块
header.setRange(pos, pos + 4, [0x52, 0x49, 0x46, 0x46]); // 'RIFF'
pos += 4;
// 写入 RIFF 大小(小端)
header.setRange(pos, pos + 4, [
riffSize & 0xff,
(riffSize >> 8) & 0xff,
(riffSize >> 16) & 0xff,
(riffSize >> 24) & 0xff,
]);
pos += 4;
// WAVE 标识
header.setRange(pos, pos + 4, [0x57, 0x41, 0x56, 0x45]); // 'WAVE'
pos += 4;
// fmt 子块
header.setRange(pos, pos + 4, [0x66, 0x6d, 0x74, 0x20]); // 'fmt '
pos += 4;
header.setRange(pos, pos + 4, [16, 0, 0, 0]); // 块大小
pos += 4;
header.setRange(pos, pos + 2, [1, 0]); // PCM 格式
pos += 2;
header.setRange(pos, pos + 2, [numberOfChannels, 0]); // 声道数
pos += 2;
// 采样率(小端)
header.setRange(pos, pos + 4, [
sampleRate & 0xff,
(sampleRate >> 8) & 0xff,
(sampleRate >> 16) & 0xff,
(sampleRate >> 24) & 0xff,
]);
pos += 4;
// 字节率(小端)
header.setRange(pos, pos + 4, [
byteRate & 0xff,
(byteRate >> 8) & 0xff,
(byteRate >> 16) & 0xff,
(byteRate >> 24) & 0xff,
]);
pos += 4;
// 块对齐
header.setRange(pos, pos + 2, [blockAlign, 0]);
pos += 2;
// 位深度
header.setRange(pos, pos + 2, [bitsPerSample, 0]);
pos += 2;
// data 子块
header.setRange(pos, pos + 4, [0x64, 0x61, 0x74, 0x61]); // 'data'
pos += 4;
// 数据大小(小端)
header.setRange(pos, pos + 4, [
dataLength & 0xff,
(dataLength >> 8) & 0xff,
(dataLength >> 16) & 0xff,
(dataLength >> 24) & 0xff,
]);
return header;
}
}

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

@ -139,12 +139,22 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
'00:00:00',
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 12.sp,
),
StreamBuilder<Duration>(
stream: controller.audioPlayer.positionStream,
builder: (context, snapshot) {
final position = snapshot.data ?? Duration.zero;
return Text(
DateUtil.formatDateMs(
position.inMilliseconds,
format: 'HH:mm:ss',
isUtc: true,
),
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 12.sp,
),
);
},
),
GestureDetector(
onTap: controller.start,
@ -164,12 +174,23 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
),
),
),
Text(
'00:00:00',
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 12.sp,
),
StreamBuilder<Duration?>(
stream: controller.audioPlayer.durationStream,
builder: (context, snapshot) {
final duration = snapshot.data ?? Duration.zero;
controller.seconds = duration.inSeconds;
return Text(
DateUtil.formatDateMs(
duration.inMilliseconds,
format: 'HH:mm:ss',
isUtc: true,
),
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 12.sp,
),
);
},
),
],
),
@ -202,10 +223,8 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
alignment: Alignment.topLeft,
child: SingleChildScrollView(
reverse: true,
child: Obx(
() => Text(
controller.dataDetails.value.content,
),
child: Text(
controller.dataDetails.value.content,
),
),
),

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

@ -17,9 +17,7 @@ class MeetingEditView extends GetView<MeetingEditController> {
return Scaffold(
backgroundColor: isDarkMode ? Colors.black : Colors.grey[50],
appBar: _buildAppBar(isDarkMode),
body: Obx(() => !controller.isLoading.value
? _buildBody(isDarkMode)
: const SizedBox()),
body: _buildBody(isDarkMode),
);
}
@ -38,7 +36,7 @@ class MeetingEditView extends GetView<MeetingEditController> {
),
actions: [
GestureDetector(
onTap: () {},
onTap: controller.confirm,
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: 12.w,
@ -105,7 +103,7 @@ class MeetingEditView extends GetView<MeetingEditController> {
padding: EdgeInsets.symmetric(vertical: 5.w),
child: Text(
DateUtil.formatDateMs(
controller.meetingData.value.creationtime,
controller.creationTime,
format: DateFormats.y_mo_d_h_m,
),
style: TextStyle(
@ -176,12 +174,22 @@ class MeetingEditView extends GetView<MeetingEditController> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
'00:00:00',
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 12.sp,
),
StreamBuilder<Duration>(
stream: controller.audioPlayer.positionStream,
builder: (context, snapshot) {
final position = snapshot.data ?? Duration.zero;
return Text(
DateUtil.formatDateMs(
position.inMilliseconds,
format: 'HH:mm:ss',
isUtc: true,
),
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 12.sp,
),
);
},
),
GestureDetector(
onTap: controller.start,
@ -194,19 +202,31 @@ class MeetingEditView extends GetView<MeetingEditController> {
12.w,
),
),
child: Icon(
controller.isPlay.value ? Icons.pause : Icons.play_arrow,
color: Colors.white,
size: 25.w,
child: Obx(
() => 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,
),
StreamBuilder<Duration?>(
stream: controller.audioPlayer.durationStream,
builder: (context, snapshot) {
final duration = snapshot.data ?? Duration.zero;
return Text(
DateUtil.formatDateMs(
duration.inMilliseconds,
format: 'HH:mm:ss',
isUtc: true,
),
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 12.sp,
),
);
},
),
],
),
@ -239,14 +259,14 @@ class MeetingEditView extends GetView<MeetingEditController> {
controller: controller.contentController,
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
fontSize: 16.sp,
fontSize: 14.sp,
),
maxLines: 50,
decoration: InputDecoration(
hintText: '编辑记录内容',
hintStyle: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.black54,
fontSize: 16.sp,
fontSize: 14.sp,
),
fillColor: Colors.transparent,
border: InputBorder.none,

2
lib/modules/pairing/controllers/pairing_controller.dart

@ -82,7 +82,7 @@ class PairingController extends GetxController {
/// 更新状态描述
void _updateStatusDescription() {
if (_bluetoothService.bluetoothState.value == 'off') {
statusDescription.value = '蓝牙已关闭,请打开蓝牙';
statusDescription.value = 'bluetoothTurnedOff'.tr;// 蓝牙已关闭, 请打开蓝牙
return;
}

140
lib/modules/profile/controllers/profile_controller.dart

@ -13,6 +13,7 @@ import 'package:path_provider/path_provider.dart';
import '../../../data/services/network/api.dart';
import 'package:dio/dio.dart';
class ProfileController extends GetxController {
static ProfileController get to => Get.find();
@ -115,6 +116,53 @@ class ProfileController extends GetxController {
// void toggleEditing() {
// _isEditing.value = !_isEditing.value;
// }
// 验证base64图片数据
// Future<void> _verifyBase64Image(String base64String) async {
// try {
// // 解码base64数据
// List<int> imageBytes = base64Decode(base64String);
// // 获取应用文档目录
// final appDir = await getApplicationDocumentsDirectory();
// final verifyPath = '${appDir.path}/verify_base64_${DateTime.now().millisecondsSinceEpoch}.jpg';
// // 将解码后的数据写入文件
// final file = File(verifyPath);
// await file.writeAsBytes(imageBytes);
// // 记录验证文件的路径
// Logger().d('Base64验证图片已保存到: $verifyPath');
// // 显示图片预览对话框
// Get.dialog(
// Dialog(
// child: Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// Padding(
// padding: const EdgeInsets.all(16.0),
// child: Text('Base64 图片验证', style: TextStyle(fontWeight: FontWeight.bold)),
// ),
// Image.file(File(verifyPath)),
// Padding(
// padding: const EdgeInsets.all(16.0),
// child: ElevatedButton(
// onPressed: () => Get.back(),
// child: Text('关闭'),
// ),
// ),
// ],
// ),
// ),
// );
// Get.snackbar('验证', '已将Base64转换回图片,请查看预览');
// } catch (e) {
// Logger().e('验证Base64图片失败: $e');
// Get.snackbar('错误', 'Base64验证失败: $e');
// }
// }
// 更新用户信息到后端
@ -123,26 +171,29 @@ class ProfileController extends GetxController {
// 准备数据
Map<String, dynamic> data = {
"image": "", // 默认空字符串
"language": currentLanguage.value,
"name": userInfo.value.name,
"language": currentLanguage.value,//当前用户语言
"name": userInfo.value.name,//当前用户名
};
// // 如果有头像,转换为base64
// if (localAvatar.value.isNotEmpty) {
// try{
// File imageFile = File(localAvatar.value);
// if(await imageFile.exists()){
// List<int> imageBytes = await imageFile.readAsBytes();
// String base64Image = base64Encode(imageBytes);
// data["image"] = base64Image;
// }else{
// Logger().e('头像文件不存在');
// }
// }catch(e){
// Logger().e('读取头像文件失败: $e');
// }
// }
// 如果有头像,转换为base64
if (localAvatar.value.isNotEmpty) {
//如果本地头像路径不为空,即代表有头像
try {
File imageFile = File(localAvatar.value);
if (await imageFile.exists()) {
List<int> imageBytes = await imageFile.readAsBytes();
String base64Image = base64Encode(imageBytes);
data["image"] = base64Image;
// // 验证base64数据 - 将base64转回图片并保存到本地
// await _verifyBase64Image(base64Image);
} else {
Logger().e('头像文件不存在');
}
} catch (e) {
Logger().e('读取头像文件失败: $e');
}
}
final dio = Dio(BaseOptions(
baseUrl: 'https://www.deapsound.com:443',
@ -160,6 +211,8 @@ class ProfileController extends GetxController {
'/api/home/user_setting',
data: data,
);
// 调用API
//final response = await Api.updateUserSetting(data);
@ -167,6 +220,13 @@ class ProfileController extends GetxController {
if (response.data['code'] == 0) {
Logger().d('更新成功: ${response.data}');
Get.snackbar('成功', '同步到服务器成功');
userInfo.update((u) {
if (u != null) {
u.name = response.data['data']['name'] ?? '';
u.avatar = response.data['data']['avatar'] ?? '';
}
});
Logger().d('更新后的用户信息: ${userInfo.value.toJson()}');
} else {
throw '更新失败:${response.data['msg'] ?? '未知错误'}';
}
@ -184,18 +244,35 @@ class ProfileController extends GetxController {
final userJson = _storage.read(userKey);
if (userJson != null) {
userInfo.value = User.instance;
Logger().d('从本地加载用户信息: $userJson');
// 如果有远程头像地址,下载并保存到本地
if (userInfo.value.avatar.isNotEmpty) {
Logger().d('远程头像地址: ${userInfo.value.avatar}');
_downloadAndSaveAvatar(userInfo.value.avatar);
}
}
}
// 下载并保存远程头像
Future<void> _downloadAndSaveAvatar(String url) async {
Future<void> _downloadAndSaveAvatar(String url) async {
try {
final response = await GetConnect().get(url);
if (response.status.isOk && response.body != null) {
Logger().d('开始下载头像: $url');
final dio = Dio(BaseOptions(
headers: {
'accept': '*/*',
'Authorization': _storage.read<String>("logintoken"),
},
));
final response = await dio.get(
url,
options: Options(responseType: ResponseType.bytes),
);
Logger().d('下载状态: ${response.statusCode}');
if (response.statusCode == 200) {
// 保存到应用文档目录
final appDir = await getApplicationDocumentsDirectory();
final fileName = 'avatar_${DateTime.now().millisecondsSinceEpoch}.jpg';
@ -203,15 +280,13 @@ class ProfileController extends GetxController {
// 写入文件
final file = File(localPath);
final List<int> bytes = response.body.runtimeType == String
? base64Decode(response.body.toString())
: response.body;
await file.writeAsBytes(bytes);
await file.writeAsBytes(response.data);
// 更新本地头像路径
localAvatar.value = localPath;
await _storage.write('local_avatar', localPath);
Logger().d('头像已下载到本地: $localPath');
//Get.snackbar('头像', '头像已下载到本地');
}
} catch (e) {
Logger().e('下载头像失败: $e');
@ -225,7 +300,7 @@ class ProfileController extends GetxController {
// 更新用户头像
Future<void> updateUserAvatar() async {
final XFile? image = await _picker.pickImage(
source: ImageSource.gallery,
source: ImageSource.gallery,// 从相册选择
maxWidth: 800,
maxHeight: 800,
imageQuality: 85,
@ -268,7 +343,7 @@ class ProfileController extends GetxController {
localAvatar.value = image.path;
await _storage.write('local_avatar', image.path);
// await saveUserInfo(); //保存到本地
await _syncWithBackend(); //同步到后端
//await _syncWithBackend(); //同步到后端
// 通知 SettingsController 更新
// if (Get.isRegistered<SettingsController>()) {
@ -278,7 +353,7 @@ class ProfileController extends GetxController {
}
// 更新用户名
Future<void> updateUserName(String name) async {
Future<void> updateUserNameAndAvatar(String name) async {
if (name.trim().isEmpty) {
Logger().e('无效名称: $name');
return;
@ -298,11 +373,14 @@ class ProfileController extends GetxController {
//同步更新 SettingsController
if (Get.isRegistered<SettingsController>()) {
final settingsController = Get.find<SettingsController>();
settingsController.loadUserInfo();
settingsController.userInfo.value.name = userInfo.value.name;
settingsController.userInfo.value.avatar = '${userInfo.value.avatar}?time=${DateTime.now().millisecondsSinceEpoch}';
//settingsController.loadUserInfo();
settingsController.userInfo.refresh();
}
} catch (e) {
Logger().e('更新名称错误: $e');
Get.snackbar('错误', '更新用户名失败');
Logger().e('更新用户名和头像错误: $e');
Get.snackbar('错误', '更新用户名和头像失败');
}
}
}

10
lib/modules/profile/views/profile_view.dart

@ -148,6 +148,14 @@ class EditProfileView extends GetView<ProfileController> {
centerTitle: true,
elevation: 0,
backgroundColor: isDarkMode ? Colors.black : Colors.white,
leading: IconButton(
icon: Icon(
Icons.arrow_back,
size: 24.sp,
color: isDarkMode? Colors.white : Colors.black,
),
onPressed: () => Get.back(),
),
title: Text(
'editProfile'.tr,
style: TextStyle(
@ -231,7 +239,7 @@ class EditProfileView extends GetView<ProfileController> {
child: ElevatedButton(
onPressed: () {
// 保存用户名
controller.updateUserName(nameController.text);
controller.updateUserNameAndAvatar(nameController.text);
// 返回上一级页面
Get.back();
},

100
lib/modules/settings/controllers/settings_controller.dart

@ -1,13 +1,17 @@
import 'package:firebase_auth/firebase_auth.dart' as firebase;
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:dio/dio.dart';
import 'package:get_storage/get_storage.dart';
import '../../../core/themes/theme_service.dart';
import '../../../data/services/speech_factory.dart';
import '../models/language_model.dart';
//import '../../../data/models/user_model.dart';
import 'package:logger/logger.dart';
import '../../../data/models/user_Info.dart';
import '../../../modules/login/controllers/googleLogin.dart';
import '../../../modules/login/controllers/facebookLogin.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
class SettingsController extends GetxController {
// 语言设置
@ -59,12 +63,12 @@ class SettingsController extends GetxController {
},
];
RxString timbreName = '默认音色'.obs;
RxString timbreName = 'defaultTimbre'.obs;
//用户信息
static const String userKey = 'user_info';
//final _storage=GetStorage();
final ImagePicker _picker = ImagePicker();
//final ImagePicker _picker = ImagePicker();
final Rx<User> userInfo = User.instance.obs; //使用单例实例
@override
@ -91,8 +95,20 @@ class SettingsController extends GetxController {
void loadUserInfo() {
final userJson = _storage.read(userKey);
Logger().d('加载用户信息: $userJson');
if (userJson != null) {
userInfo.value = User.instance;
Logger().d('用户信息已加载: ${userInfo.value.toJson()}');
if (userInfo.value.avatar.isNotEmpty) {
Logger().d('开始下载头像');
_downloadAndSaveAvatar(userInfo.value.avatar);
} else {
Logger().d('用户没有头像');
}
} else {
Logger().d('没有找到用户信息');
}
}
@ -100,24 +116,64 @@ class SettingsController extends GetxController {
await _storage.write(userKey, userInfo.value.toJson());
}
Future<void> updateUserAvatar() async {
final XFile? image = await _picker.pickImage(
source: ImageSource.gallery,
maxWidth: 800,
maxHeight: 800,
imageQuality: 85,
);
// 下载并保存远程头像
Future<void> _downloadAndSaveAvatar(String url) async {
try {
Logger().d('开始下载头像: $url');
final dio = Dio(BaseOptions(
headers: {
'accept': '*/*',
'Authorization': _storage.read<String>("logintoken"),
},
));
final response = await dio.get(
url,
options: Options(responseType: ResponseType.bytes),
);
if (image != null) {
userInfo.update((user) {
if (user != null) {
user.avatar = image.path;
}
});
await saveUserInfo();
Logger().d('下载状态: ${response.statusCode}');
if (response.statusCode == 200) {
// 保存到应用文档目录
final appDir = await getApplicationDocumentsDirectory();
final fileName = 'avatar_${DateTime.now().millisecondsSinceEpoch}.jpg';
final localPath = '${appDir.path}/$fileName';
// 写入文件
final file = File(localPath);
await file.writeAsBytes(response.data);
// 更新本地头像路径
//localAvatar.value = localPath;
await _storage.write('local_avatar', localPath);
Logger().d('头像已下载到本地: $localPath');
//Get.snackbar('头像', '头像已下载到本地');
}
} catch (e) {
Logger().e('下载头像失败: $e');
}
}
// Future<void> updateUserAvatar() async {
// final XFile? image = await _picker.pickImage(
// source: ImageSource.gallery,
// maxWidth: 800,
// maxHeight: 800,
// imageQuality: 85,
// );
// if (image != null) {
// userInfo.update((user) {
// if (user != null) {
// user.avatar = image.path;
// }
// });
// await saveUserInfo();
// }
// }
Future<void> updateUserName(String name) async {
if (name.trim().isNotEmpty) {
userInfo.update((user) {
@ -222,9 +278,15 @@ class SettingsController extends GetxController {
targetLanguage.value = temp;
}
void signOut() {
Future<void> signOut() async {
// 在此实现退出登录逻辑
_storage.write("logintoken", "");
_storage.write("user_info", "");
final google = Google();
final facebook = Facebook();
await google.signOutGoogle();
await facebook.signOutFacebook();
Get.offAllNamed('/login');
}
}

59
lib/modules/settings/views/settings_view.dart

@ -110,13 +110,55 @@ class SettingsView extends GetView<SettingsController> {
// 裁剪圆形头像
borderRadius: BorderRadius.circular(30.r),
child: controller.userInfo.value.avatar.isEmpty
? Image.network(
'https://images.unsplash.com/photo-1534528741775-53994a69daeb?ixlib=rb-1.2.1&auto=format&fit=crop&w=200&q=80',
fit: BoxFit.cover, // 图片填充方式
?
// Image.network(
// 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?ixlib=rb-1.2.1&auto=format&fit=crop&w=200&q=80',
// fit: BoxFit.cover, // 图片填充方式
// )
Container(
decoration: BoxDecoration(
color: Colors.blue[600],
borderRadius: BorderRadius.circular(30.r),
),
child: Center(
child: Text(
controller.userInfo.value.name.isNotEmpty
? controller.userInfo.value.name[0]
.toUpperCase()
: '?',
style: TextStyle(
color: Colors.white,
fontSize: 24.sp,
fontWeight: FontWeight.bold,
),
),
),
)
: Image.network(
controller.userInfo.value.avatar,
fit: BoxFit.cover, // 图片填充方式
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
// 当图片加载失败时显示用户名首字母
return Container(
decoration: BoxDecoration(
color: Colors.blue[600],
borderRadius: BorderRadius.circular(30.r),
),
child: Center(
child: Text(
controller.userInfo.value.name.isNotEmpty
? controller.userInfo.value.name[0]
.toUpperCase()
: '?',
style: TextStyle(
color: Colors.white,
fontSize: 24.sp,
fontWeight: FontWeight.bold,
),
),
),
);
},
)),
),
)),
@ -328,6 +370,9 @@ class SettingsView extends GetView<SettingsController> {
),
onTap: () {
// 打开备份设置页面
print('---------用户头像-${controller.userInfo.value.avatar}');
print('---------用户名-${controller.userInfo.value.name}');
//print('user${User.instance.value}');
},
isDarkMode: isDarkMode,
),
@ -346,15 +391,15 @@ class SettingsView extends GetView<SettingsController> {
: Colors.grey[200]),
Obx(
() => _buildNavigationSetting(
title: '音色服务',
subtitle: '选择音色语音合成服务',
title: 'timbreService'.tr, // 音色服务
subtitle: 'timbreServiceInfo'.tr, // 选择音色服务
icon: Icons.photo_camera_front_outlined,
iconBgColor: isDarkMode
? Colors.cyan[900]!.withOpacity(0.3)
: Colors.cyan[100]!,
iconColor:
isDarkMode ? Colors.cyan[300]! : Colors.cyan[600]!,
value: controller.timbreName.value,
value: controller.timbreName.value.tr,
valueStyle: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[500],

9
lib/modules/splash/controllers/splash_controller.dart

@ -76,10 +76,13 @@ class SplashController extends GetxController {
// 获取登录Token并跳转
Future<void> _navigateToLogin() async {
final isLoggedIn = await getLoginToken();
bool isLoggedIn = false;
final token = _storage.read('logintoken');
print('getLoginToken:进来了${token}');
if (token != null && token.isNotEmpty) {
isLoggedIn = await getLoginToken();
}
if (isLoggedIn) {
// Get.offAllNamed(Routes.login); // 跳转到登录页
// Get.offAllNamed(Routes.home); // 跳转到主页
Get.offAllNamed(Routes.pairing);
} else {
Get.offAllNamed(Routes.login); // 跳转到登录页

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

@ -51,15 +51,15 @@ class TimbreController extends GetxController {
} else {
dataList.add({
'type': '1',
'name': '默认音色',
'description': '微软云端音色服务',
'name': 'defaultTimbre',
'description': 'microsoftCloudTimbreService',
'isUsable': true
});
_storage.write('timbreList', [
{
'type': '1',
'name': '默认音色',
'description': '微软云端音色服务',
'name': 'defaultTimbre',
'description': 'microsoftCloudTimbreService',
'isUsable': true
}
]);

6
lib/modules/timbre/views/timbre_view.dart

@ -137,7 +137,7 @@ class TimbreView extends GetView<TimbreController> {
const Icon(Icons.keyboard_voice, color: Colors.white),
5.horizontalSpace,
Text(
'声音复刻',
'voiceClone'.tr,
style: TextStyle(color: Colors.white, fontSize: 16.sp),
),
],
@ -180,7 +180,7 @@ class TimbreView extends GetView<TimbreController> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
data['name'],
'${data['name']}'.tr,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w500,
@ -190,7 +190,7 @@ class TimbreView extends GetView<TimbreController> {
),
SizedBox(height: 4.h),
Text(
data['description'],
'${data['description']}'.tr,
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.black54,

Loading…
Cancel
Save