Compare commits

...

No commits in common. 'main' and 'newdev_shunjiawei' have entirely different histories.

  1. 220
      .gitignore
  2. 0
      .gradle/wrapper/dists/gradle-8.11.1-all/2qik7nd48slq1ooc2496ixf4i/gradle-8.11.1-all.zip.lck
  3. BIN
      .gradle/wrapper/dists/gradle-8.11.1-all/2qik7nd48slq1ooc2496ixf4i/gradle-8.11.1-all.zip.part
  4. 10
      .mcp.json
  5. 30
      .metadata
  6. 1
      .vercel/project.json
  7. 30
      GoogleService-Info.plist
  8. 51
      I18N_PLACEHOLDER_REMEDIATION.md
  9. 60
      README.md
  10. 27
      SpeexKit.xcframework/Info.plist
  11. 44
      SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Headers/OpusTools.h
  12. 100
      SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Headers/OpusUnit.h
  13. 21
      SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Headers/SpeexKit.h
  14. 40
      SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Headers/SpeexUnit.h
  15. BIN
      SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Info.plist
  16. 6
      SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Modules/module.modulemap
  17. BIN
      SpeexKit.xcframework/ios-arm64/SpeexKit.framework/SpeexKit
  18. 1
      analysis_options.yaml
  19. 14
      android/.gitignore
  20. 159
      android/app/build.gradle.kts
  21. 29
      android/app/google-services.json
  22. 15
      android/app/proguard-logging.pro
  23. 87
      android/app/proguard-rules.pro
  24. 4
      android/app/sign/Voitrans.properties
  25. 7
      android/app/src/debug/AndroidManifest.xml
  26. 181
      android/app/src/main/AndroidManifest.xml
  27. 533
      android/app/src/main/kotlin/com/saitong/voitrans/MainActivity.kt
  28. 196
      android/app/src/main/kotlin/com/saitong/voitrans/MyApplication.kt
  29. 12
      android/app/src/main/res/drawable-v21/launch_background.xml
  30. 12
      android/app/src/main/res/drawable/launch_background.xml
  31. 6
      android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  32. BIN
      android/app/src/main/res/mipmap-hdpi/ic_launcher.png
  33. BIN
      android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png
  34. BIN
      android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
  35. BIN
      android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png
  36. BIN
      android/app/src/main/res/mipmap-mdpi/ic_launcher.png
  37. BIN
      android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png
  38. BIN
      android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
  39. BIN
      android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png
  40. BIN
      android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
  41. BIN
      android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png
  42. BIN
      android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
  43. BIN
      android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png
  44. BIN
      android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  45. BIN
      android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png
  46. BIN
      android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
  47. BIN
      android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png
  48. BIN
      android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  49. BIN
      android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png
  50. BIN
      android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
  51. BIN
      android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png
  52. BIN
      android/app/src/main/res/raw/listening.mp3
  53. BIN
      android/app/src/main/res/raw/sample.mp3
  54. BIN
      android/app/src/main/res/raw/silence.mp3
  55. 18
      android/app/src/main/res/values-night/styles.xml
  56. 6
      android/app/src/main/res/values/strings.xml
  57. 18
      android/app/src/main/res/values/styles.xml
  58. 13
      android/app/src/main/res/xml/network_security_config.xml
  59. 7
      android/app/src/profile/AndroidManifest.xml
  60. 44
      android/build.gradle.kts
  61. 5
      android/gradle.properties
  62. 5
      android/gradle/wrapper/gradle-wrapper.properties
  63. 60
      android/settings.gradle.kts
  64. 30
      assets/config/GoogleService-Info.plist
  65. BIN
      assets/fonts/NotoSansSC-Regular.ttf
  66. BIN
      assets/images/DefaultPicture.jpg
  67. BIN
      assets/images/agents/assistant.png
  68. BIN
      assets/images/agents/doctor.png
  69. BIN
      assets/images/agents/girlfriend.png
  70. BIN
      assets/images/agents/interpreter.png
  71. BIN
      assets/images/agents/lily.png
  72. BIN
      assets/images/agents/love_guru.png
  73. BIN
      assets/images/agents/mei_hua.png
  74. BIN
      assets/images/agents/shopping.png
  75. BIN
      assets/images/agents/socrates.png
  76. BIN
      assets/images/agents/speaking_coach.png
  77. BIN
      assets/images/agents/storyteller.png
  78. BIN
      assets/images/agents/tarot.png
  79. BIN
      assets/images/defaultPicture.png
  80. BIN
      assets/images/explore.png
  81. BIN
      assets/images/explore1.png
  82. BIN
      assets/images/explore2.jpg
  83. BIN
      assets/images/glasses_light.png
  84. BIN
      assets/images/mine-background.png
  85. BIN
      assets/images/music.png
  86. BIN
      assets/images/qqmusic.png
  87. BIN
      assets/images/robot_light.png
  88. 2
      assets/js/d3@7.js
  89. 10
      assets/js/markmap-lib.js
  90. 9
      assets/js/markmap-toolbar.js
  91. 10
      assets/js/markmap-view.js
  92. 8
      assets/js/saveSvgAsPng.min.js
  93. BIN
      assets/listening.mp3
  94. 1
      assets/lottie/audio_wave.json
  95. 1
      assets/lottie/intro_foriday.json
  96. 1
      assets/lottie/meeting_wait.json
  97. 1
      assets/lottie/sound_effect.json
  98. 3
      assets/weather/100-fill.svg
  99. 3
      assets/weather/100.svg
  100. 3
      assets/weather/1001.svg

220
.gitignore

@ -1,25 +1,24 @@
# ---> Dart
# See https://www.dartlang.org/guides/libraries/private-files # See https://www.dartlang.org/guides/libraries/private-files
# Claude Code
.claude/
# Files and directories created by pub # Files and directories created by pub
.dart_tool/ .dart_tool/
.packages .packages
build/ build/
# If you're building an application, you may want to check-in your pubspec.lock
pubspec.lock
# Directory created by dartdoc # Directory created by dartdoc
# If you don't generate documentation locally you can remove this line. # If you don't generate documentation locally you can remove this line.
doc/api/ doc/api/
# dotenv environment variables file # dotenv environment variables file
.env* .env*
.env.example
CLAUDE.md
# Avoid committing generated Javascript files: # Avoid committing generated Javascript files:
*.dart.js *.dart.js
*.info.json # Produced by the --dump-info flag. *.info.json # Produced by the --dump-info flag.
*.js # When generated by dart2js. Don't specify *.js if your
# project includes source files written in JavaScript.
*.js_ *.js_
*.js.deps *.js.deps
*.js.map *.js.map
@ -27,3 +26,212 @@ doc/api/
.flutter-plugins .flutter-plugins
.flutter-plugins-dependencies .flutter-plugins-dependencies
# Dart/Flutter specific
.dart_tool/
.packages
build/
# local_plugins 是 Flutter plugin packages(library),不应提交 lock 文件
local_plugins/**/pubspec.lock
# IDE specific
.idea/
.vscode/
*.iml
*.iws
.DS_Store
# Flutter/Dart/Pub related
**/doc/api/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
.cxx/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/.last_build_id
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/ephemeral
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# macOS related
**/macos/Flutter/GeneratedPluginRegistrant.*
**/macos/Flutter/ephemeral
**/macos/**/*.mode1v3
**/macos/**/*.mode2v3
**/macos/**/*.moved-aside
**/macos/**/*.pbxuser
**/macos/**/*.perspectivev3
**/macos/**/*sync/
**/macos/**/.sconsign.dblite
**/macos/**/.tags*
**/macos/**/.vagrant/
**/macos/**/DerivedData/
**/macos/**/Icon?
**/macos/**/Pods/
**/macos/**/.symlinks/
**/macos/**/profile
**/macos/**/xcuserdata
**/macos/.generated/
**/macos/Flutter/Flutter.framework
**/macos/Flutter/App.framework
**/macos/Flutter/ephemeral/
**/macos/Flutter/GeneratedPluginRegistrant.swift
**/macos/Flutter/ephemeral
# Web related
lib/generated_plugin_registrant.dart
# Environment files
.env*
*.env.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Coverage
coverage/
# Dart compiled JS
*.dart.js
*.info.json
*.js_
*.js.deps
*.js.map
linux/flutter/generated_plugin_registrant.cc
linux/flutter/generated_plugin_registrant.h
linux/flutter/generated_plugins.cmake
windows/flutter/generated_plugin_registrant.cc
windows/flutter/generated_plugin_registrant.h
windows/flutter/generated_plugins.cmake
# Project only ships Android/iOS — ignore other platform scaffolding entirely
/linux/
/macos/
/windows/
/web/
**/linux/flutter/ephemeral/
**/windows/flutter/ephemeral/
# ================== Swift Package Manager (SPM) ==================
# SPM build directories
**/.build/
**/build/
.build/
build/
# SPM dependency resolution (controversial - some teams commit this)
Package.resolved
**/Package.resolved
# SPM working directory files
**/.swiftpm/
.swiftpm/
# SPM derived data and cache
**/DerivedData/
DerivedData/
# SPM temporary files
**/.DS_Store
.DS_Store
**/*.xcworkspace/xcuserdata/
**/*.xcodeproj/xcuserdata/
**/*.xcodeproj/project.xcworkspace/xcuserdata/
# SPM Xcode integration
**/*.xcodeproj/project.xcworkspace/
**/*.xcworkspace/
# SPM package pins (legacy)
Package.pins
**/Package.pins
# SPM manifest backup files
Package.swift.orig
**/Package.swift.orig
# ================== Local Plugins SPM Support ==================
# Local plugins build artifacts
local_plugins/**/.build/
local_plugins/**/build/
local_plugins/**/Package.resolved
local_plugins/**/.swiftpm/
local_plugins/**/DerivedData/
# Specifically for the problematic paths we saw
local_plugins/chat_api/ios/chat_api/.build/
local_plugins/chat_api/ios/chat_api/Package.resolved
local_plugins/ble_service/ios/ble_service/.build/
local_plugins/ble_service/ios/ble_service/Package.resolved
# ================== Additional iOS/Xcode Cleanup ==================
# Xcode user-specific files
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
*.xcuserstate
*.xcscmblueprint
*.xccheckout
# Xcode shared user data
xcshareddata/
**/xcshareddata/UserInterfaceState.xcuserstate
# CocoaPods artifacts (if mixed with SPM)
Pods/
**/Pods/
Podfile.lock
**/Podfile.lock
# FastLane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
fastlane/readme.md
.trae/rules/project_rules.md
.augment/rules/imported/project_rules.md
.kiro/
.qoder/
ios/Runner.xcodeproj/project.pbxproj
.superpowers/

0
.gradle/wrapper/dists/gradle-8.11.1-all/2qik7nd48slq1ooc2496ixf4i/gradle-8.11.1-all.zip.lck

BIN
.gradle/wrapper/dists/gradle-8.11.1-all/2qik7nd48slq1ooc2496ixf4i/gradle-8.11.1-all.zip.part

Binary file not shown.

10
.mcp.json

@ -0,0 +1,10 @@
{
"mcpServers": {
"web-search": {
"url": "http://mcp.ideapsound.com:8000/sse"
},
"mp3": {
"url": "https://api.deapsound.com/sse"
}
}
}

30
.metadata

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

1
.vercel/project.json

@ -0,0 +1 @@
{"projectName":"trae_deep_voice_k3cu"}

30
GoogleService-Info.plist

@ -0,0 +1,30 @@
<?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>API_KEY</key>
<string>AIzaSyBEQPL-2Qh6fERxrxLP9Pzcm95mTmTpBxE</string>
<key>GCM_SENDER_ID</key>
<string>733281995296</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.saitong.voitrans</string>
<key>PROJECT_ID</key>
<string>voitrans</string>
<key>STORAGE_BUCKET</key>
<string>voitrans.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:733281995296:ios:6a8351691312100bd70a45</string>
</dict>
</plist>

51
I18N_PLACEHOLDER_REMEDIATION.md

@ -0,0 +1,51 @@
# I18N Placeholder Remediation
This document records the repeatable steps used to detect and fix leftover English placeholder text and invalid-quote issues in translation map files under `lib/core/translations/language/`.
## Problem Patterns
### 1) English placeholders inside non-English locales
Symptoms:
- UI shows English strings in a non-English locale.
- `tools/i18n_audit.py --mode englishy` reports items for a locale file.
Fix:
- Replace English values with accurate localized text.
- Keep placeholder syntax intact (examples: `@minutes`, `{count}`, `{filePath}`, `{error}`).
### 2) Invalid “smart quotes” used as Dart string delimiters
Symptoms:
- `flutter analyze` reports `illegal_character`, `expected_token`, `expression_in_map`, `undefined_identifier` in a language file.
Root cause:
- Curly quotes like `‘ ’` are not valid Dart string delimiters, so map entries stop being parsed as strings.
Fix:
- Replace `‘` and `’` with ASCII `'` and ensure every entry is in the form `'key': 'value',`.
## Verification Checklist
Run these commands from the repo root:
```bash
python3 tools/i18n_audit.py --mode englishy
flutter analyze lib/core/translations/language/<locale>.dart
dart format lib/core/translations/language/<locale>.dart
flutter test test/<locale>_translations_test.dart
```
Recommended additional checks:
```bash
rg "[‘’]" lib/core/translations/language/<locale>.dart
```
## Test Convention
For each locale file, add a `test/<locale>_translations_test.dart` test that asserts:
- The map can be imported and accessed.
- Its key set matches `enUS`.
- It contains no empty string values.

60
README.md

@ -1,2 +1,60 @@
# deepvoice_client # Deep Voice
Flutter 应用程序,使用 GetX 状态管理和依赖注入。
## 项目目录结构
```
lib/
├─ core/
│ ├─ bindings/ # 全局或通用的 Bindings
│ ├─ constants/ # 常量、枚举、Key、全局配置等
│ ├─ errors/ # 异常/错误处理相关
│ ├─ themes/ # 主题(Color, TextStyle 等)
│ ├─ translations/ # 国际化、语言相关
│ ├─ utils/ # 通用工具类、Helper、扩展函数等
│ └─ widgets/ # 通用的无业务逻辑的Widgets
│
├─ data/
│ ├─ models/ # 数据模型(可与后端DTO对应)
│ ├─ repositories/ # 数据仓库(实际操作数据 CRUD, 远程或本地)
│ ├─ services/ # 第三方或原生平台封装,如网络请求、缓存、数据库等
│ └─ providers/ # 适配器层,比如把网络、数据库等转换为 Repository 可调用的接口
│
├─ modules/
│ ├─ login/
│ │ ├─ controllers/ # GetX Controller
│ │ ├─ views/ # 页面、Widget
│ │ ├─ bindings/ # 该模块的 Bindings
│ │ └─ ... # 其他文件
│ ├─ home/
│ │ ├─ controllers/
│ │ ├─ views/
│ │ ├─ bindings/
│ │ └─ ...
│ └─ ...
│
├─ routes/
│ ├─ app_pages.dart # 定义所有页面的 Route 列表
│ └─ app_routes.dart # 定义路由常量或路由字符串
│
└─ main.dart # 程序入口
```
### 本地调试内购(IAP)流程
不需要真实购买即可在本地跑通前端内购流程:
- 在项目根目录的环境变量文件(如 `.env`)里添加:
```
APP_IAP_MOCK=1
```
- 作用:在 `PayService` 中启用“本地内购模拟”,调用 `processGooglePayment` 或 `processApplePayment` 会:
- 立即触发“已发起内购(本地模拟)”;
- 短暂延时后触发“支付处理中…(本地模拟)”;
- 再触发“支付成功(本地模拟)”。
- 注意:该模式不依赖 Google Play / App Store 安装来源,也不会进行服务器收据校验,仅用于前端 UI 与状态机验证。
- 关闭模拟:将 `APP_IAP_MOCK` 移除或设为 `0/false`,恢复真实内购流程。

27
SpeexKit.xcframework/Info.plist

@ -0,0 +1,27 @@
<?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>AvailableLibraries</key>
<array>
<dict>
<key>BinaryPath</key>
<string>SpeexKit.framework/SpeexKit</string>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>SpeexKit.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>

44
SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Headers/OpusTools.h

@ -0,0 +1,44 @@
//
// OpusTools.h
// SpeexKit
//
// Created by EzioChan on 2024/1/17.
// Copyright © 2024 www.zh-jieli.com. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol OpusToolsDelegate <NSObject>
-(void)opusToolsPcmData:(NSData*)data;
-(void)opusToolsPcmFile:(NSData *)data;
-(void)opusToolsOpusData:(NSData *)data;
-(void)opusToolsOpusFile:(NSData *)data;
@end
@interface OpusTools : NSObject
@property(nonatomic,weak)id<OpusToolsDelegate> delegate;
-(void)opusDecodeInfo:(int)sampleRate Channel:(int)channel frameSize:(int) fs isHasHead:(BOOL)head;
-(void)opusEncodeInfo:(int)sampleRate Channel:(int)channel frameSize:(int)fs isHasHead:(BOOL)head;
-(void)decodeData:(NSData*)data;
-(void)decodeFile:(NSString*)path;
-(void)encodeData:(NSData*)data;
-(void)encodeFile:(NSString*)path;
@end
NS_ASSUME_NONNULL_END

100
SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Headers/OpusUnit.h

@ -0,0 +1,100 @@
//
// OpusUnit.h
// QCY_Demo
//
// Created by 杰理科技 on 2021/7/5.
// Copyright © 2021 杰理科技. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* 通知:流式编码回调Opus数据
* 通知数据类型:NSData(Opus)
*/
extern NSString *kOPUS_ENCODE_DATA;
/**
* 通知:流式解码回调PCM数据
* 通知数据类型:NSData(PCM)
*/
extern NSString *kOPUS_DECODE_DATA;
@interface OpusUnit : NSObject
+ (void)opusIsLog:(BOOL)log;
#pragma mark - Opus参数
/**
* 设置参数
* @param rate 采样率 例如:48000、24000、16000、12000、8000(默认 16000)
* @param frameRate 帧长度
* 16k :40
* 32K :80
* 64k :160
* 96k :320
* 128k:640
* @param channel 声道 例如:1、2 (默认 1)
* @param isHeader 是否带头
*/
+(void)opusSetSampleRate:(int)rate frameRate:(int)frameRate Channels:(int)channel isHeader:(BOOL)isHeader;
#pragma mark - Opus解码
/**
* 直接【opus文件】转换成【pcm文件】
* @param path_opus opus文件路径
* @param path_pcm pcm文件路径
*/
+ (int)opusDecodeOPUS:(NSString *)path_opus PCM:(NSString *)path_pcm;
/**
* 流式解码【开启】
* 注意:需要异步调用,放在工程单利类中,切勿频繁开启。
*/
+ (int)opusDecoderRun;
/**
* 输入Opus的数据
* 通知监听“kOPUS_DECODE_DATA”获得解码后数据
* 通知数据类型:NSNotification.object => NSData(PCM格式)
* @param data opus数据流(长度:1024)
*/
+ (void)opusWriteData:(NSData*)data;
/**
* 流式解码【关闭】
*/
+ (int)opusDecoderStop;
#pragma mark - Opus编码
/**
* 【pcm文件】转换成【opus文件】
*/
+ (int)opusEncodePCM:(NSString *)path_pcm OPUS:(NSString *)path_opus;
/**
* 流式编码【开启】
* 注意:需要异步调用,放在工程单利类中,切勿频繁开启。
*/
+ (int)opusEncoderRun;
/**
* 输入pcm的数据
* 通知监听“kOPUS_DECODE_DATA”获得解码后数据
* 通知数据类型:NSNotification.object => NSData(opus格式)
* @param data opus数据流(长度:1024)
*/
+ (void)pcmWriteData:(NSData*)data;
/**
* 流式编码【关闭】
*/
+ (int)opusEncoderStop;
@end
NS_ASSUME_NONNULL_END

21
SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Headers/SpeexKit.h

@ -0,0 +1,21 @@
//
// SpeexKit.h
// SpeexKit
//
// Created by zhihui liang on 2018/8/20.
// Copyright © 2018年 www.zh-jieli.com. All rights reserved.
//
#import <UIKit/UIKit.h>
//! Project version number for SpeexKit.
FOUNDATION_EXPORT double SpeexKitVersionNumber;
//! Project version string for SpeexKit.
FOUNDATION_EXPORT const unsigned char SpeexKitVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <SpeexKit/PublicHeader.h>
#import <SpeexKit/SpeexUnit.h>
#import <SpeexKit/OpusUnit.h>
#import <SpeexKit/OpusTools.h>

40
SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Headers/SpeexUnit.h

@ -0,0 +1,40 @@
//
// SpeexUnit.h
// SpeexKit
//
// Created by zhihui liang on 2018/8/20.
// Copyright © 2018年 www.zh-jieli.com. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* 流式解码回调PCM数据
*/
extern NSString *kSPEEX_DECODE_DATA; //speex decode
@interface SpeexUnit : NSObject
+(void)speexIsLog:(BOOL)log;
/**
* 直接【speex文件】转换成【pcm文件】
*/
+(int)speexDecodeSPX:(NSString *)path_spx PCM:(NSString *)path_pcm;
/**
* 流式解码【开启】
*/
+(int)speexDecoderRun;
/**
* 输入Speex的数据
*/
+(void)speexWriteData:(NSData*)data;
/**
* 流式解码【关闭】
*/
+(int)speexDecoderStop;
@end

BIN
SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Info.plist

Binary file not shown.

6
SpeexKit.xcframework/ios-arm64/SpeexKit.framework/Modules/module.modulemap

@ -0,0 +1,6 @@
framework module SpeexKit {
umbrella header "SpeexKit.h"
export *
module * { export * }
}

BIN
SpeexKit.xcframework/ios-arm64/SpeexKit.framework/SpeexKit

Binary file not shown.

1
analysis_options.yaml

@ -0,0 +1 @@
include: package:flutter_lints/flutter.yaml

14
android/.gitignore

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

159
android/app/build.gradle.kts

@ -0,0 +1,159 @@
import java.util.Properties
import java.io.FileInputStream
repositories {
google()
mavenCentral()
flatDir {
dirs(project(":spotify_sdk").projectDir.resolve("libs"))
dirs(project(":qq_music").projectDir.resolve("libs")) // 添加这行
}
}
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
id("com.google.gms.google-services")version "4.3.13"
}
val keystorePropertiesFile = rootProject.file("app/sign/voitrans.properties")
val keystoreProperties =Properties().apply {
load(FileInputStream(keystorePropertiesFile)) // 使用 Kotlin 扩展方法
}
android {
namespace = "com.saitong.voitrans"
compileSdk = 36
ndkVersion = "28.2.13676358"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
// Enable core library desugaring
isCoreLibraryDesugaringEnabled = true
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
// 添加lint选项
lintOptions {
isCheckReleaseBuilds = false
}
// 添加packaging配置,排除冲突文件
packaging {
resources {
excludes.add("META-INF/DEPENDENCIES")
excludes.add("META-INF/LICENSE")
excludes.add("META-INF/LICENSE.txt")
excludes.add("META-INF/license.txt")
excludes.add("META-INF/NOTICE")
excludes.add("META-INF/NOTICE.txt")
excludes.add("META-INF/notice.txt")
excludes.add("META-INF/*.kotlin_module")
}
jniLibs {
useLegacyPackaging = true
// Google Play 渠道排除高德地图 .so(尚未支持 16KB 页面对齐)
val envFile = rootProject.file("../.env")
if (envFile.exists()) {
val channel = envFile.readLines()
.firstOrNull { it.trim().startsWith("Channel=") }
?.substringAfter("=")?.trim()
if (channel == "GLOBAL_GOOGLE") {
excludes += setOf("**/libAMapSDK*.so")
}
}
}
}
defaultConfig {
manifestPlaceholders.putAll(
mapOf(
"redirectSchemeName" to "spotify-sdk",
"redirectHostName" to "auth",
"applicationName" to "com.saitong.voitrans.MyApp" // 新增:声明自定义 Application
)
)
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.saitong.voitrans"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = 26 // 更新为26以满足ble_service库的要求
targetSdk = 35 // 设置为 Android 15 (API 35),以满足应用市场要求
versionCode = flutter.versionCode // 对应构建号,自动同步flutter构建号
versionName = flutter.versionName // 对应版本号,自动同步flutter版本号
}
signingConfigs {
create("release") {
keyAlias = keystoreProperties.getProperty("keyAlias")
keyPassword = keystoreProperties.getProperty("keyPassword")
storeFile = file(keystoreProperties.getProperty("storeFile"))
storePassword = keystoreProperties.getProperty("storePassword")
enableV1Signing = true
enableV2Signing = true
enableV3Signing = false
enableV4Signing = false
}
getByName("debug") {
// debug 使用与 release 相同的签名配置
keyAlias = keystoreProperties.getProperty("keyAlias")
keyPassword = keystoreProperties.getProperty("keyPassword")
storeFile = file(keystoreProperties.getProperty("storeFile"))
storePassword = keystoreProperties.getProperty("storePassword")
enableV1Signing = true
enableV2Signing = true
enableV3Signing = false
enableV4Signing = false
}
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
isMinifyEnabled = false
isShrinkResources = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
signingConfig = signingConfigs.getByName("release")
// Release 版本只保留 arm64-v8a 和 armeabi-v7a(移除 x86/x86_64)
ndk {
abiFilters += listOf("armeabi-v7a", "arm64-v8a")
}
}
debug {
isMinifyEnabled = false
isShrinkResources = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
signingConfig = signingConfigs.getByName("debug")
// Debug 版本保留所有 ABI(包括 x86/x86_64,用于模拟器)
ndk {
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
}
}
}
}
dependencies {
// 添加核心库反糖化
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3")
// 添加 CardView 依赖
implementation("androidx.cardview:cardview:1.0.0")
}
flutter {
source = "../.."
}

29
android/app/google-services.json

@ -0,0 +1,29 @@
{
"project_info": {
"project_number": "733281995296",
"project_id": "voitrans",
"storage_bucket": "voitrans.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:733281995296:android:fd51ec5604787ab7d70a45",
"android_client_info": {
"package_name": "com.saitong.voitrans"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyDPvThoWXxZ-LhE0gS9KH51jRE3f8YTmOE"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}

15
android/app/proguard-logging.pro

@ -0,0 +1,15 @@
# 移除系统组件的日志调用
-assumenosideeffects class android.util.Log {
public static int v(...);
public static int d(...);
public static int i(...);
}
# 针对特定类的日志移除
-assumenosideeffects class android.media.AudioTrack {
private static void log*(...);
}
-assumenosideeffects class android.app.AppOpsManager {
private static void log*(...);
}

87
android/app/proguard-rules.pro

@ -0,0 +1,87 @@
# Flutter Framework
# 这些规则保留了 Flutter 引擎和插件系统正常运行所需的类。
-ignorewarnings
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.** { *; }
-keep class io.flutter.util.** { *; }
-keep class io.flutter.view.** { *; }
-keep class io.flutter.embedding.android.** { *; }
-keep class io.flutter.embedding.engine.** { *; }
-keep class io.flutter.embedding.engine.plugins.** { *; }
-keep class io.flutter.plugin.common.** { *; }
-keep class io.flutter.plugins.** { *; }
-keep class io.flutter.plugin.platform.** { *; }
# 保留你的应用程序的主 Activity。
# !!! 请将 com.example.deep_voice.MainActivity 替换为你的实际包名和 Activity 类名 !!!
-keep class com.saitong.voitrans.MainActivity { *; }
# 保留 open_ai_service 包下的所有类
-keep class com.yunqiinnovation.open_ai_service.** { *; }
# 保留 MCPClient 相关类
-keep class com.yunqiinnovation.open_ai_service.MCPClient { *; }
-keep class com.yunqiinnovation.open_ai_service.MCPSubClient { *; }
-keep class com.yunqiinnovation.open_ai_service.FunctionHandler { *; }
-keep class com.yunqiinnovation.open_ai_service.ToolType { *; }
-keep class com.yunqiinnovation.open_ai_service.mcp.** { *; }
-keep class com.yunqiinnovation.open_ai_service.mcp.CustomSseClientTransport { *; }
-keep class com.yunqiinnovation.open_ai_service.SystemFunctionHandler { *; }
-keep class com.yunqiinnovation.open_ai_service.OpenAIService { *; }
-keep class com.yunqiinnovation.open_ai_service.OpenAIService$* { *; }
# 保留 OpenAIServicePlugin 类
-keep class com.yunqiinnovation.open_ai_service.OpenAIServicePlugin { *; }
# 保留 Jackson 相关类
-keep class com.fasterxml.jackson.** { *; }
-keep class com.fasterxml.jackson.databind.** { *; }
-keep class com.fasterxml.jackson.core.** { *; }
# 保留 Ktor 相关类
-keep class io.ktor.** { *; }
-keep class kotlinx.coroutines.** { *; }
-keep class kotlinx.serialization.** { *; }
# 如果你的应用直接使用了 AndroidX 的某些特定类(例如,通过原生代码或插件),可能需要保留它们。
# 例如,保留所有 Parcelable 实现类,如果它们被跨进程使用或序列化。
# -keep class * implements android.os.Parcelable {
# public static final android.os.Parcelable$Creator *;
# }
# 保留 MCP 相关依赖库
-keep class io.modelcontextprotocol.** { *; }
-keep class com.anthropic.** { *; }
# 保留 OkHttp 相关类
-keep class okhttp3.** { *; }
-keep class okio.** { *; }
# 如果你使用了特定的第三方库,并且这些库需要特定的 ProGuard 规则,请在此处添加。
# 例如,OkHttp/Retrofit, Gson, Glide 等通常需要保留模型类或特定方法。
# 查看库的官方文档通常会提供所需的 ProGuard 规则。
# -keepclasseswithmembers class * {
# @android.webkit.JavascriptInterface <methods>;
# }
# -keepattributes Signature
# -keepattributes *Annotation*
# 如果你有自定义的原生插件,需要保留它们的类。
# !!! 将 com.yourcompany.yourplugin.** 替换为你的插件的实际包名 !!!
# -keep class com.yourcompany.yourplugin.** { *; }
# 保留枚举类,防止 switch 语句因混淆出错
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# 保留 R 类及其内部类,防止资源访问出错
-keep class **.R$* { *; }
# 保留 native 方法
-keepclasseswithmembernames class * {
native <methods>;
}

4
android/app/sign/Voitrans.properties

@ -0,0 +1,4 @@
storePassword=saitong_voitrans1122
keyPassword=saitong_voitrans1122
keyAlias=voitrans
storeFile=sign/voitrans.keystore

7
android/app/src/debug/AndroidManifest.xml

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

181
android/app/src/main/AndroidManifest.xml

@ -0,0 +1,181 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- 添加必要的权限 -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<!-- Android 13+ 前台服务特定类型权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
<!-- Android 14+ 蓝牙设备连接前台服务权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"/>
<!-- Android 15+ 数据同步前台服务权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
<!-- Android 14+ 麦克风前台服务权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!-- Android 13+ 通知权限 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<!-- 添加存储权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<!-- 添加Android 11+的存储权限 -->
<!-- <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" /> -->
<!-- Android 13+ 媒体权限 -->
<!-- <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> -->
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
<!-- 添加悬浮窗权限,可能有助于解决前台服务启动问题 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<!-- 添加发送短信权限 -->
<!-- <uses-permission android:name="android.permission.SEND_SMS" /> -->
<!-- 添加读取联系人权限 -->
<uses-permission android:name="android.permission.READ_CONTACTS" />
<!-- 添加拨打电话权限 -->
<uses-permission android:name="android.permission.CALL_PHONE" />
<!-- 添加查询应用包权限 -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<!--允许获取网络状态,用于网络定位(无gps情况下的定位),若需网络定位功能则必选-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!--允许获取wifi网络信息,用于网络定位(无gps情况下的定位),若需网络定位功能则必选-->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!--允许获取wifi状态改变,用于网络定位(无gps情况下的定位),若需网络定位功能则必选-->
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<!--后台获取位置信息,若需后台定位则必选-->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<!--用于申请调用A-GPS模块,卫星定位加速-->
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
<!--用于申请调用精确闹钟权限-->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<!--允许写设备缓存,用于问题排查-->
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission
android:name="android.permission.RECEIVE_BOOT_COMPLETED"
tools:node="remove" />
<application
android:label="Voitrans"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true"
android:extractNativeLibs="true"
android:enableOnBackInvokedCallback="true"
tools:replace="android:label">
<!-- 支持16KB内存页面大小,满足Android 15要求 -->
<property android:name="android.allow_16kb_memory_page" android:value="true" />
<receiver
android:name="androidx.work.impl.background.systemalarm.RescheduleReceiver"
tools:node="remove" />
<meta-data android:name="com.facebook.sdk.ApplicationId" android:value="@string/facebook_app_id"/>
<meta-data android:name="com.facebook.sdk.ClientToken" android:value="@string/facebook_client_token"/>
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTask"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="voitrans"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="content" android:mimeType="audio/*" />
<data android:scheme="file" android:mimeType="audio/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="audio/*" />
</intent-filter>
<!-- PayPal 网页回跳的深链:deepvoiceclient://paypal/return 与 /cancel -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="deepvoiceclient" android:host="paypal" android:pathPrefix="/return" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="deepvoiceclient" android:host="paypal" android:pathPrefix="/cancel" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<meta-data
android:name="com.amap.api.v2.apikey"
android:value="78e7946dd602778df82c6ae143a88184"/>
<service android:name="com.amap.api.location.APSService"></service>
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
<queries>
<!-- 声明百度地图包名 -->
<package android:name="com.baidu.BaiduMap" />
<!-- 声明要处理的URL Scheme -->
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="baidumap" />
</intent>
</queries>
</manifest>

533
android/app/src/main/kotlin/com/saitong/voitrans/MainActivity.kt

@ -0,0 +1,533 @@
package com.saitong.voitrans
import android.app.PendingIntent
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanSettings
import android.content.Intent
import android.os.Build
import android.os.Bundle
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.embedding.android.FlutterActivity
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Log
import io.flutter.embedding.engine.FlutterEngineCache
import io.flutter.plugin.common.MethodChannel // 添加这行导入
import android.net.Uri
import android.provider.OpenableColumns
import java.io.File
import java.io.FileOutputStream
import org.json.JSONObject
class MainActivity: FlutterActivity() {
private val TAG = "MainActivity"
private var pendingDeepLink: Intent? = null
private var pendingNavigationIntent: Intent? = null
private var pendingShareIntent: Intent? = null
private var pendingSharedFilePath: String? = null
private var pendingDeepLinkUrl: String? = null
private val EXTRA_NAVIGATE_TO = "com.saitong.voitrans.extra.NAVIGATE_TO"
private val NAVIGATE_TO_MEETING_RECORD = "meetingRecord"
private val ROUTE_MEETING_RECORD = "/meeting/record"
private val DEEPLINK_CHANNEL = "com.saitong.voitrans/deeplink"
private val DELIVERY_RETRY_DELAY_MS = 300L
private val MAX_DELIVERY_RETRIES = 12
private val NATIVE_LOCALIZATION_CHANNEL = "com.saitong.voitrans/native_localization"
private val NATIVE_LOCALIZATION_PREF_NAME = "native_localization"
private val NATIVE_LOCALIZATION_PREF_KEY_LOCALE = "locale"
private val NATIVE_LOCALIZATION_PREF_KEY_STRINGS_JSON = "strings_json"
override fun onCreate(savedInstanceState: Bundle?) {
Log.d(TAG, "🚀 MainActivity onCreate 开始")
super.onCreate(savedInstanceState)
// 检查 Intent 信息
intent?.let {
Log.d(TAG, "Intent action: ${it.action}")
Log.d(TAG, "Intent data: ${it.data}")
Log.d(TAG, "Intent extras: ${it.extras}")
}
intent?.let {
if (it.action == Intent.ACTION_SEND) {
pendingShareIntent = it
Log.d(TAG, "📎 暂存分享文件意图,等待Flutter引擎准备完成")
} else if (it.action == Intent.ACTION_VIEW && it.data != null) {
val uri = it.data
if (uri != null && isLikelyAudio(it, uri)) {
pendingShareIntent = it
Log.d(TAG, "📎 暂存打开文件意图,等待Flutter引擎准备完成")
} else {
pendingDeepLink = it
Log.d(TAG, "🔗 暂存深链接,等待Flutter引擎准备完成")
}
}
}
if (intent?.hasExtra(EXTRA_NAVIGATE_TO) == true) {
pendingNavigationIntent = intent
Log.d(TAG, "🧭 暂存导航意图,等待Flutter引擎准备完成")
}
// 设置日志级别,减少系统级日志
suppressSystemLogs()
Log.d(TAG, "✅ MainActivity onCreate 完成")
}
// 添加 onNewIntent 方法处理深链接
override fun onNewIntent(intent: Intent) {
Log.d(TAG, "🔗 onNewIntent 被调用")
super.onNewIntent(intent)
setIntent(intent) // 更新当前 Intent
// 检查新的 Intent 信息
Log.d(TAG, "New Intent action: ${intent.action}")
Log.d(TAG, "New Intent data: ${intent.data}")
Log.d(TAG, "New Intent extras: ${intent.extras}")
intent.let {
if (it.action == Intent.ACTION_SEND) {
pendingShareIntent = it
Log.d(TAG, "📎 暂存分享文件意图,等待Flutter引擎准备完成")
} else if (it.action == Intent.ACTION_VIEW && it.data != null) {
val uri = it.data
if (uri != null && isLikelyAudio(it, uri)) {
pendingShareIntent = it
Log.d(TAG, "📎 暂存打开文件意图,等待Flutter引擎准备完成")
} else {
pendingDeepLink = it
Log.d(TAG, "🔗 暂存深链接,等待Flutter引擎准备完成")
}
}
}
if (intent.hasExtra(EXTRA_NAVIGATE_TO)) {
pendingNavigationIntent = intent
Log.d(TAG, "🧭 暂存导航意图,等待Flutter引擎准备完成")
}
deliverOrQueueIntent(intent)
}
override fun provideFlutterEngine(context: Context): FlutterEngine? {
Log.d(TAG, "🔍 provideFlutterEngine 被调用")
val engine = FlutterEngineCache.getInstance().get("main_engine")
if (engine == null) {
Log.w(TAG, "❌ FlutterEngine 缓存为空,这可能导致问题")
// 如果缓存为空,让 Flutter 框架创建默认 Engine
return null
}
// 检查 Engine 状态 - 使用更兼容的方式
try {
// 检查 Dart 执行器是否正在运行
val isExecutorRunning = engine.dartExecutor.isExecutingDart
Log.d(TAG, "FlutterEngine Dart 执行状态: $isExecutorRunning")
// 如果需要,可以检查其他状态
if (!isExecutorRunning) {
Log.w(TAG, "⚠️ FlutterEngine Dart 执行器未运行,可能需要重新初始化")
}
} catch (e: Exception) {
Log.w(TAG, "❌ 检查 FlutterEngine 状态时出错", e)
}
Log.d(TAG, "✅ 使用缓存的 FlutterEngine")
return engine
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
Log.d(TAG, "🔧 configureFlutterEngine 开始")
// 检查插件是否已经注册,避免重复注册
val pluginRegistry = flutterEngine.plugins
// 只有在插件未注册时才调用 GeneratedPluginRegistrant
if (!pluginRegistry.has(io.flutter.plugins.firebase.core.FlutterFirebaseCorePlugin::class.java)) {
Log.d(TAG, "📦 注册 Flutter 插件")
super.configureFlutterEngine(flutterEngine)
GeneratedPluginRegistrant.registerWith(flutterEngine)
} else {
Log.d(TAG, "⚠️ 插件已注册,跳过重复注册")
}
setupNativeLocalizationChannel(flutterEngine)
// Flutter引擎配置完成后,处理待处理的深链接
Handler(Looper.getMainLooper()).postDelayed({
pendingShareIntent?.let { intent ->
Log.d(TAG, "📎 处理待处理的分享/打开文件意图")
deliverOrQueueIntent(intent)
pendingShareIntent = null
}
pendingDeepLink?.let { intent ->
Log.d(TAG, "🔗 处理待处理的深链接")
deliverOrQueueIntent(intent)
pendingDeepLink = null
}
pendingNavigationIntent?.let { intent ->
Log.d(TAG, "🧭 处理待处理的导航意图")
deliverOrQueueIntent(intent)
pendingNavigationIntent = null
}
}, 1000) // 延迟1秒确保Flutter完全准备好
Log.d(TAG, "✅ configureFlutterEngine 完成")
}
private fun setupNativeLocalizationChannel(flutterEngine: FlutterEngine) {
val channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, NATIVE_LOCALIZATION_CHANNEL)
channel.setMethodCallHandler { call, result ->
when (call.method) {
"updateNativeStrings" -> {
try {
val args = call.arguments as? Map<*, *>
val locale = args?.get("locale") as? String
val strings = args?.get("strings") as? Map<*, *>
val obj = JSONObject()
strings?.forEach { (k, v) ->
val key = k as? String ?: return@forEach
val value = v as? String ?: return@forEach
obj.put(key, value)
}
val prefs = getSharedPreferences(NATIVE_LOCALIZATION_PREF_NAME, Context.MODE_PRIVATE)
prefs.edit()
.putString(NATIVE_LOCALIZATION_PREF_KEY_LOCALE, locale)
.putString(NATIVE_LOCALIZATION_PREF_KEY_STRINGS_JSON, obj.toString())
.apply()
result.success(null)
} catch (e: Exception) {
result.error("NATIVE_LOCALIZATION_ERROR", e.message, null)
}
}
else -> result.notImplemented()
}
}
}
private fun deliverOrQueueIntent(intent: Intent?) {
intent ?: return
val navigateTo = intent.getStringExtra(EXTRA_NAVIGATE_TO)
if (navigateTo == NAVIGATE_TO_MEETING_RECORD) {
pushRouteToFlutter(ROUTE_MEETING_RECORD)
return
}
if (intent.action == Intent.ACTION_SEND ||
intent.action == Intent.ACTION_SEND_MULTIPLE ||
intent.action == Intent.ACTION_VIEW
) {
try {
val path = extractAudioPathFromIntent(intent)
if (path != null) {
pendingSharedFilePath = path
deliverPendingSharedFile()
return
}
} catch (e: Exception) {
Log.e(TAG, "❌ 处理分享/打开文件失败", e)
}
}
if (intent.action == Intent.ACTION_VIEW && intent.data != null) {
val uri = intent.data
Log.d(TAG, "🔗 处理深链接: $uri")
pendingDeepLinkUrl = uri.toString()
deliverPendingDeepLink()
return
}
Log.d(TAG, "🔗 Intent 不是深链接或数据为空")
}
private fun getEngineForMessaging(): FlutterEngine? {
return flutterEngine ?: FlutterEngineCache.getInstance().get("main_engine")
}
private fun deliverPendingSharedFile(attempt: Int = 0) {
val filePath = pendingSharedFilePath ?: return
val engine = getEngineForMessaging()
val messenger = engine?.dartExecutor?.binaryMessenger
if (messenger == null) {
if (attempt < MAX_DELIVERY_RETRIES) {
Handler(Looper.getMainLooper()).postDelayed(
{ deliverPendingSharedFile(attempt + 1) },
DELIVERY_RETRY_DELAY_MS
)
} else {
Log.w(TAG, "❌ FlutterEngine 未就绪,放弃发送分享文件:$filePath")
}
return
}
val channel = MethodChannel(messenger, DEEPLINK_CHANNEL)
channel.invokeMethod(
"handleSharedFile",
mapOf("filePath" to filePath),
object : MethodChannel.Result {
override fun success(result: Any?) {
pendingSharedFilePath = null
}
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
Log.e(TAG, "❌ Flutter 处理分享文件返回错误:$errorCode, $errorMessage")
}
override fun notImplemented() {
if (attempt < MAX_DELIVERY_RETRIES) {
Handler(Looper.getMainLooper()).postDelayed(
{ deliverPendingSharedFile(attempt + 1) },
DELIVERY_RETRY_DELAY_MS
)
} else {
Log.w(TAG, "❌ Flutter 未注册分享处理器,放弃发送分享文件:$filePath")
}
}
}
)
}
private fun deliverPendingDeepLink(attempt: Int = 0) {
val url = pendingDeepLinkUrl ?: return
val engine = getEngineForMessaging()
val messenger = engine?.dartExecutor?.binaryMessenger
if (messenger == null) {
if (attempt < MAX_DELIVERY_RETRIES) {
Handler(Looper.getMainLooper()).postDelayed(
{ deliverPendingDeepLink(attempt + 1) },
DELIVERY_RETRY_DELAY_MS
)
} else {
Log.w(TAG, "❌ FlutterEngine 未就绪,放弃发送深链接:$url")
}
return
}
val channel = MethodChannel(messenger, DEEPLINK_CHANNEL)
val arguments = buildUrlArguments(url)
channel.invokeMethod(
"handleDeepLink",
arguments,
object : MethodChannel.Result {
override fun success(result: Any?) {
pendingDeepLinkUrl = null
}
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
Log.e(TAG, "❌ Flutter 处理深链接返回错误:$errorCode, $errorMessage")
}
override fun notImplemented() {
if (attempt < MAX_DELIVERY_RETRIES) {
Handler(Looper.getMainLooper()).postDelayed(
{ deliverPendingDeepLink(attempt + 1) },
DELIVERY_RETRY_DELAY_MS
)
} else {
Log.w(TAG, "❌ Flutter 未注册深链接处理器,放弃发送深链接:$url")
}
}
}
)
}
private fun extractAudioPathFromIntent(intent: Intent): String? {
val uri: Uri? = when (intent.action) {
Intent.ACTION_SEND -> {
val extraStream = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
extraStream ?: intent.clipData?.takeIf { it.itemCount > 0 }?.getItemAt(0)?.uri
}
Intent.ACTION_SEND_MULTIPLE -> {
val items = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
items?.firstOrNull() ?: intent.clipData?.takeIf { it.itemCount > 0 }?.getItemAt(0)?.uri
}
Intent.ACTION_VIEW -> intent.data
else -> null
}
if (uri == null) return null
if (!isLikelyAudio(intent, uri)) return null
return copyUriToCache(uri)
}
private fun isLikelyAudio(intent: Intent, uri: Uri): Boolean {
val type = intent.type ?: ""
if (type.startsWith("audio/")) return true
val scheme = uri.scheme ?: ""
if (scheme == "content" || scheme == "file") return true
val path = uri.toString().lowercase()
return path.endsWith(".wav") || path.endsWith(".mp3") || path.endsWith(".m4a") || path.endsWith(".aac")
}
private fun copyUriToCache(uri: Uri): String? {
return try {
val resolver = applicationContext.contentResolver
val cursor = resolver.query(uri, null, null, null, null)
var displayName = "shared_audio"
cursor?.use {
val nameIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
if (nameIndex >= 0 && it.moveToFirst()) {
displayName = it.getString(nameIndex) ?: displayName
}
}
val ext = when {
displayName.endsWith(".wav", true) -> "wav"
displayName.endsWith(".mp3", true) -> "mp3"
displayName.endsWith(".m4a", true) -> "m4a"
displayName.endsWith(".aac", true) -> "aac"
else -> displayName.substringAfterLast('.', "audio")
}
val sharedDir = File(applicationContext.filesDir, "shared_audio")
if (!sharedDir.exists()) sharedDir.mkdirs()
val outFile = File(sharedDir, "audio_${System.currentTimeMillis()}.$ext")
val inStream = resolver.openInputStream(uri)
if (inStream == null) {
outFile.delete()
return null
}
inStream.use {
FileOutputStream(outFile).use { out ->
val buffer = ByteArray(8192)
var len: Int
while (true) {
len = it.read(buffer)
if (len <= 0) break
out.write(buffer, 0, len)
}
}
}
if (!outFile.exists() || outFile.length() <= 0) {
outFile.delete()
return null
}
outFile.absolutePath
} catch (e: Exception) {
Log.e(TAG, "复制分享文件到缓存失败: ${e.message}", e)
null
}
}
private fun pushRouteToFlutter(route: String) {
try {
val engine = flutterEngine
if (engine == null) {
Log.w(TAG, "❌ FlutterEngine 为空,无法 pushRoute")
return
}
Handler(Looper.getMainLooper()).post {
try {
engine.navigationChannel.pushRoute(route)
} catch (e: Exception) {
Log.e(TAG, "❌ pushRoute 失败", e)
}
}
} catch (e: Exception) {
Log.e(TAG, "❌ pushRouteToFlutter 出错", e)
}
}
private fun buildUrlArguments(url: String): Map<String, Any?> {
return try {
val uri = android.net.Uri.parse(url)
mapOf(
"url" to url,
"scheme" to uri.scheme,
"host" to uri.host,
"path" to uri.path,
"query" to uri.query,
"queryParameters" to uri.queryParameterNames.associateWith { uri.getQueryParameter(it) }
)
} catch (e: Exception) {
mapOf("url" to url)
}
}
override fun onDestroy() {
super.onDestroy()
}
/**
* 抑制系统级组件的日志输出
*/
private fun suppressSystemLogs() {
// 更全面的系统标签列表
val systemTags = arrayOf(
"MediaCodec",
"CCodec",
"CCodecBufferChannel",
"OpusManager",
"MediaCodecList",
"MediaPlayer",
"AudioTrack",
"AudioManager",
"AudioFlinger",
"AudioService",
"AudioEffect",
"AppOpsManager",
"AudioRecord",
"OpenGLRenderer",
"Choreographer",
"InputMethodManager",
"ViewRootImpl",
"Surface",
"BufferQueueProducer",
"ActivityManager",
"WindowManager",
"SensorManager"
)
// 尝试多种方式设置日志级别
systemTags.forEach { tag ->
try {
// 方式1:标准属性设置
System.setProperty("log.tag.$tag", "ERROR")
// 方式2:尝试使用反射设置(针对某些特殊标签)
setLogLevelViaReflection(tag, Log.ERROR)
} catch (e: Exception) {
// 忽略设置失败的情况
}
}
// 方式3:尝试设置全局日志级别
try {
// 设置更高的阈值,减少verbose和debug日志
System.setProperty("log.tag", "WARN")
} catch (e: Exception) {
// 忽略
}
}
/**
* 通过反射尝试设置日志级别
*/
private fun setLogLevelViaReflection(tag: String, level: Int) {
try {
val logClass = Class.forName("android.util.Log")
val isLoggableMethod = logClass.getDeclaredMethod(
"isLoggable",
String::class.java,
Int::class.javaPrimitiveType
)
isLoggableMethod.isAccessible = true
// 某些系统可能支持直接设置
val setLogLevelMethod = logClass.getDeclaredMethod(
"setLogLevel",
String::class.java,
Int::class.javaPrimitiveType
)
setLogLevelMethod.isAccessible = true
setLogLevelMethod.invoke(null, tag, level)
} catch (e: Exception) {
// 反射方法可能不存在,忽略
}
}
}

196
android/app/src/main/kotlin/com/saitong/voitrans/MyApplication.kt

@ -0,0 +1,196 @@
package com.saitong.voitrans
import android.app.ActivityManager
import android.content.Context
import android.util.Log
import io.flutter.app.FlutterApplication
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.FlutterEngineCache
import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.plugins.GeneratedPluginRegistrant
import java.io.OutputStream
import java.io.PrintStream
// Application.kt
class MyApp : FlutterApplication() {
companion object {
private const val TAG = "MyApp"
private const val ENGINE_ID = "main_engine"
// 标记 Engine 是否已经执行过 Dart 入口点
@Volatile
private var isDartEntrypointExecuted = false
// 记录应用启动时间,用于判断是否为新启动
@Volatile
private var appStartTime = 0L
}
override fun onCreate() {
super.onCreate()
appStartTime = System.currentTimeMillis()
Log.d(TAG, "MyApp onCreate - 进程启动")
// 屏蔽高德定位 SDK(CgiManager) 的 NumberFormatException 噪音日志
installAmapStderrFilter()
val cache = FlutterEngineCache.getInstance()
val existingEngine = cache.get(ENGINE_ID)
if (existingEngine == null) {
Log.d(TAG, "创建新的 FlutterEngine")
createAndCacheEngine(cache)
} else {
Log.d(TAG, "FlutterEngine 已存在,检查状态")
// 检查现有 Engine 的状态
try {
val isExecuting = existingEngine.dartExecutor.isExecutingDart
Log.d(TAG, "Engine Dart执行状态: $isExecuting")
if (!isExecuting && isMainProcess()) {
Log.d(TAG, "Engine 存在但Dart未执行,重新执行")
existingEngine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
)
isDartEntrypointExecuted = true
}
} catch (e: Exception) {
Log.e(TAG, "检查或重新执行 Dart 入口点失败", e)
// 如果失败,重新创建 Engine
cache.remove(ENGINE_ID)
createAndCacheEngine(cache)
}
}
}
private fun createAndCacheEngine(cache: FlutterEngineCache) {
// 在主进程中总是创建并执行Dart入口点
if (isMainProcess()) {
Log.d(TAG, "✅ 在主进程创建 FlutterEngine 并执行 Dart")
val flutterEngine = FlutterEngine(this)
// 注册插件(只在这里注册一次)
Log.d(TAG, "📦 注册 Flutter 插件")
GeneratedPluginRegistrant.registerWith(flutterEngine)
try {
Log.d(TAG, "🚀 开始执行 Dart 入口点")
flutterEngine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
)
isDartEntrypointExecuted = true
cache.put(ENGINE_ID, flutterEngine)
Log.d(TAG, "✅ FlutterEngine 创建并缓存成功")
} catch (e: Exception) {
Log.e(TAG, "❌ 执行 Dart 入口点失败", e)
// 如果执行失败,仍然缓存 Engine,但标记为未执行
isDartEntrypointExecuted = false
cache.put(ENGINE_ID, flutterEngine)
}
} else {
Log.d(TAG, "⚠️ 在非主进程,仅创建 FlutterEngine")
// 非主进程只创建 Engine,不执行 Dart 入口点
val flutterEngine = FlutterEngine(this)
cache.put(ENGINE_ID, flutterEngine)
}
}
private fun isMainProcess(): Boolean {
val am = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val processes = am.runningAppProcesses ?: return true
val mainProcessName = packageName
val myPid = android.os.Process.myPid()
for (process in processes) {
if (process.pid == myPid) {
return process.processName == mainProcessName
}
}
return true
}
/**
* 过滤 System.err 中来自高德 SDK 的 NumberFormatException 栈(com.autonavi.aps.amapapi / CgiManager / ApsManager)。
* 仅丢弃这类已知噪音,其他 stderr 输出原样打印。
*/
private fun installAmapStderrFilter() {
val origin = System.err ?: return
val noiseKeywords = arrayOf(
"com.autonavi.aps.amapapi",
"CgiManager",
"ApsManager",
"onCellLocationChanged",
"PhoneStateListener",
)
val bufferRef = StringBuilder()
var suppressing = false
val filtered = PrintStream(object : OutputStream() {
override fun write(b: Int) {
val ch = b.toChar()
bufferRef.append(ch)
if (ch == '\n') {
val line = bufferRef.toString()
bufferRef.setLength(0)
val trimmed = line.trimStart()
if (trimmed.startsWith("java.lang.NumberFormatException")) {
suppressing = true
return
}
if (suppressing) {
// 只要是栈帧 (at xxx) 或 caused by,就继续丢弃
if (trimmed.startsWith("at ") ||
trimmed.startsWith("Caused by") ||
trimmed.startsWith("... ") ||
noiseKeywords.any { trimmed.contains(it) }
) {
return
}
suppressing = false
}
origin.print(line)
}
}
}, true)
System.setErr(filtered)
Log.d(TAG, "已安装高德 SDK stderr 过滤器")
}
private fun isAppInForeground(): Boolean {
val am = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val processes = am.runningAppProcesses ?: return false
val pkg = packageName
for (p in processes) {
if (p.processName == pkg &&
p.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
return true
}
}
return false
}
private fun isAppBeingLaunched(): Boolean {
// 改进的启动检测逻辑
val currentTime = System.currentTimeMillis()
val isRecentStart = (currentTime - appStartTime) < 3000 // 3秒内认为是新启动
if (isRecentStart) {
Log.d(TAG, "检测到应用新启动")
return true
}
// 备用检测方法(兼容旧版本)
return try {
val am = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val tasks = am.getRunningTasks(1)
val result = tasks.isNotEmpty() && tasks[0].topActivity?.packageName == packageName
Log.d(TAG, "通过 RunningTasks 检测启动状态: $result")
result
} catch (e: Exception) {
Log.w(TAG, "无法获取 RunningTasks,可能缺少权限", e)
// 如果无法获取任务信息,默认认为是启动状态
true
}
}
}

12
android/app/src/main/res/drawable-v21/launch_background.xml

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

12
android/app/src/main/res/drawable/launch_background.xml

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

6
android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
</adaptive-icon>

BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
android/app/src/main/res/raw/listening.mp3

Binary file not shown.

BIN
android/app/src/main/res/raw/sample.mp3

Binary file not shown.

BIN
android/app/src/main/res/raw/silence.mp3

Binary file not shown.

18
android/app/src/main/res/values-night/styles.xml

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

6
android/app/src/main/res/values/strings.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="facebook_app_id">3829022037361855</string>
<string name="fb_login_protocol_scheme">fb3829022037361855</string>
<string name="facebook_client_token">193d0a2b1757c41cfa557ea8e8f21c33</string>
</resources>

18
android/app/src/main/res/values/styles.xml

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

13
android/app/src/main/res/xml/network_security_config.xml

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">127.0.0.1</domain>
<domain includeSubdomains="true">localhost</domain>
</domain-config>
</network-security-config>

7
android/app/src/profile/AndroidManifest.xml

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

44
android/build.gradle.kts

@ -0,0 +1,44 @@
allprojects {
repositories {
mavenLocal()
google()
mavenCentral()
maven { url = uri("https://storage.googleapis.com/download.flutter.io") }
// 添加火山引擎Maven仓库
maven {
url = uri("https://artifact.bytedance.com/repository/Volcengine/")
}
maven { url = uri("https://maven.pkg.jetbrains.space/public/p/ktor/eap") }
// 修改为绝对路径,避免相对路径计算错误
flatDir {
dirs(file("${rootProject.projectDir}/../local_plugins/qq_music/android/libs"))
}
}
}
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20")
// 其他依赖...
}
}

5
android/gradle.properties

@ -0,0 +1,5 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
org.gradle.java.home=/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home
android.useAndroidX=true
android.enableJetifier=true
android.max.page.size=16384

5
android/gradle/wrapper/gradle-wrapper.properties

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

60
android/settings.gradle.kts

@ -0,0 +1,60 @@
// This file configures the Flutter project
// Used by the Flutter tool to generate GeneratedPluginRegistrant.java
pluginManagement {
val flutterSdkPath = run {
val properties = java.util.Properties()
val localPropertiesFile = file("local.properties")
if (localPropertiesFile.exists()) {
localPropertiesFile.inputStream().use { properties.load(it) }
}
val flutterSdkPath = properties.getProperty("flutter.sdk")
requireNotNull(flutterSdkPath) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
mavenLocal()
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader")
id("com.android.application") version "8.9.1" apply false
id("com.android.library") version "8.9.1" apply false
id("org.jetbrains.kotlin.android") version "2.1.10" apply false
id("com.google.protobuf") version "0.9.4" apply false
}
include(":app")
include(":azure_speech")
include(":chat_storage")
include(":jieli")
include(":spotify_sdk")
include(":ble_service")
include(":classic_bluetooth")
include(":agent_service")
include(":speech")
include(":chat_api")
include(":bytedance_speech")
include(":qq_music") // 添加这行
include(":jl_opus")
// 设置项目路径
project(":azure_speech").projectDir = file("../local_plugins/azure_speech/android")
project(":chat_storage").projectDir = file("../local_plugins/chat_storage/android")
project(":jieli").projectDir = file("../local_plugins/jieli/android")
project(":spotify_sdk").projectDir = file("../local_plugins/spotify_sdk/android")
project(":ble_service").projectDir = file("../local_plugins/ble_service/android")
project(":classic_bluetooth").projectDir = file("../local_plugins/classic_bluetooth/android")
project(":agent_service").projectDir = file("../local_plugins/agent_service/android")
project(":speech").projectDir = file("../local_plugins/speech/android")
project(":chat_api").projectDir = file("../local_plugins/chat_api/android")
project(":bytedance_speech").projectDir = file("../local_plugins/bytedance_speech/android")
project(":qq_music").projectDir = file("../local_plugins/qq_music/android") // 添加这行
project(":jl_opus").projectDir = file("../local_plugins/jl_opus/android")

30
assets/config/GoogleService-Info.plist

@ -0,0 +1,30 @@
<?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>API_KEY</key>
<string>AIzaSyBEQPL-2Qh6fERxrxLP9Pzcm95mTmTpBxE</string>
<key>GCM_SENDER_ID</key>
<string>733281995296</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.saitong.voitrans</string>
<key>PROJECT_ID</key>
<string>voitrans</string>
<key>STORAGE_BUCKET</key>
<string>voitrans.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:733281995296:ios:6a8351691312100bd70a45</string>
</dict>
</plist>

BIN
assets/fonts/NotoSansSC-Regular.ttf

Binary file not shown.

BIN
assets/images/DefaultPicture.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

BIN
assets/images/agents/assistant.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
assets/images/agents/doctor.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
assets/images/agents/girlfriend.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 KiB

BIN
assets/images/agents/interpreter.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
assets/images/agents/lily.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
assets/images/agents/love_guru.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
assets/images/agents/mei_hua.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

BIN
assets/images/agents/shopping.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
assets/images/agents/socrates.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

BIN
assets/images/agents/speaking_coach.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
assets/images/agents/storyteller.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
assets/images/agents/tarot.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
assets/images/defaultPicture.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 KiB

BIN
assets/images/explore.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 KiB

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/mine-background.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

BIN
assets/images/music.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
assets/images/qqmusic.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
assets/images/robot_light.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

2
assets/js/d3@7.js

File diff suppressed because one or more lines are too long

10
assets/js/markmap-lib.js

File diff suppressed because one or more lines are too long

9
assets/js/markmap-toolbar.js

File diff suppressed because one or more lines are too long

10
assets/js/markmap-view.js

File diff suppressed because one or more lines are too long

8
assets/js/saveSvgAsPng.min.js

File diff suppressed because one or more lines are too long

BIN
assets/listening.mp3

Binary file not shown.

1
assets/lottie/audio_wave.json

File diff suppressed because one or more lines are too long

1
assets/lottie/intro_foriday.json

File diff suppressed because one or more lines are too long

1
assets/lottie/meeting_wait.json

File diff suppressed because one or more lines are too long

1
assets/lottie/sound_effect.json

File diff suppressed because one or more lines are too long

3
assets/weather/100-fill.svg

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="qi-100-fill" viewBox="0 0 16 16">
<path d="M8.005 3.5a4.5 4.5 0 1 0 0 9 4.5 4.5 0 0 0 0-9Zm.004-.997a.5.5 0 0 1-.5-.5v-1.5a.5.5 0 0 1 1 0v1.5a.5.5 0 0 1-.5.5ZM3.766 4.255a.498.498 0 0 1-.353-.147l-1.062-1.06a.5.5 0 0 1 .707-.707L4.122 3.4a.5.5 0 0 1-.355.854v.001ZM2.004 8.493h-1.5a.5.5 0 1 1 0-1h1.5a.5.5 0 1 1 0 1Zm.691 5.303a.5.5 0 0 1-.354-.854l1.062-1.06a.5.5 0 0 1 .708.707l-1.063 1.06a.497.497 0 0 1-.353.147Zm5.301 2.201a.5.5 0 0 1-.5-.5v-1.5a.5.5 0 0 1 1 0v1.5a.5.5 0 0 1-.5.5Zm5.304-2.191a.496.496 0 0 1-.353-.147l-1.06-1.06a.5.5 0 1 1 .706-.707l1.06 1.06a.5.5 0 0 1-.353.854Zm2.203-5.299h-1.5a.5.5 0 0 1 0-1h1.5a.5.5 0 1 1 0 1ZM12.25 4.265a.5.5 0 0 1-.354-.854l1.06-1.06a.5.5 0 1 1 .708.707l-1.06 1.06a.498.498 0 0 1-.354.147Z"/>
</svg>

After

Width:  |  Height:  |  Size: 839 B

3
assets/weather/100.svg

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="qi-100" viewBox="0 0 16 16">
<path d="M7.655 2.357a.5.5 0 0 0 .854-.353v-1.5a.5.5 0 0 0-1 0v1.5a.5.5 0 0 0 .146.353Zm-4.08 1.861c.06.026.126.039.191.039l.001-.001a.5.5 0 0 0 .355-.855l-1.064-1.06a.5.5 0 0 0-.707.708l1.062 1.06a.498.498 0 0 0 .162.11ZM.503 8.496h1.5a.5.5 0 1 0 0-1h-1.5a.5.5 0 0 0 0 1Zm1.914 5.221a.501.501 0 0 0 .631-.063l1.063-1.06a.5.5 0 0 0-.708-.707l-1.062 1.06a.5.5 0 0 0 .076.77Zm5.225 2.14a.5.5 0 0 0 .854-.354v-1.5a.5.5 0 0 0-1 0v1.5a.5.5 0 0 0 .146.354Zm5.467-2.084a.5.5 0 0 0 .544-.816l-1.06-1.06a.498.498 0 0 0-.832.152.5.5 0 0 0 .126.555l1.06 1.06a.496.496 0 0 0 .162.109Zm.893-5.263h1.5a.5.5 0 1 0 0-1h-1.5a.5.5 0 0 0 0 1Zm-2.031-4.327a.5.5 0 0 0 .633-.063l1.06-1.06a.5.5 0 1 0-.708-.708l-1.06 1.06a.5.5 0 0 0 .075.77Zm-6.466.075a4.5 4.5 0 1 1 5 7.484 4.5 4.5 0 0 1-5-7.484Zm4.445.832a3.5 3.5 0 1 0-3.89 5.82 3.5 3.5 0 0 0 3.89-5.82Z"/>
</svg>

After

Width:  |  Height:  |  Size: 965 B

3
assets/weather/1001.svg

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="qi-1001" viewBox="0 0 16 16">
<path d="M9.21 1.491c-3.757-.066-6.613 2.537-6.88 5.705-.292 3.494 2.107 4.947 2.428 5.232C2.853 12.4.585 10.28.204 8.296a.104.104 0 0 0-.1-.085.103.103 0 0 0-.103.114c.403 3.526 3.405 6.2 6.79 6.186 3.604-.016 6.518-2.147 6.89-5.646.35-3.295-2.108-5.008-2.424-5.292 2.023.02 4.162 2.15 4.54 4.133.008.048.05.084.098.085a.102.102 0 0 0 .1-.071.103.103 0 0 0 .004-.043c-.406-3.521-3.405-6.126-6.788-6.185ZM8 9.503A1.502 1.502 0 1 1 8 6.5a1.502 1.502 0 0 1 0 3.004Z"/>
</svg>

After

Width:  |  Height:  |  Size: 595 B

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save