Browse Source

Merge commit '2d45dfe476dde12830ef3e2f6c15689c647b4251' into lxm

newdev_shunjiawei
lxm 2 years ago
parent
commit
e0c2e99332
  1. 9
      ios/Runner.xcodeproj/project.pbxproj
  2. 4
      ios/Runner/BluetoothMediaButtonHelper.swift
  3. 4
      ios/Runner/Info.plist
  4. 4
      lib/core/translations/language/en_us.dart
  5. 4
      lib/core/translations/language/zh_cn.dart
  6. 8
      lib/data/providers/agent_provider.dart
  7. 6
      lib/data/services/deapsound_ai_service.dart
  8. 285
      lib/data/services/speech_impl/volcano_asr_api_service.dart
  9. 2
      lib/main.dart
  10. 10
      lib/modules/chat/controllers/chat_controller.dart
  11. 2
      lib/modules/chat/views/chat_view.dart
  12. 3
      lib/modules/meeting/controllers/meeting_details_controller.dart
  13. 58
      lib/modules/meeting/controllers/meeting_edit_controller.dart
  14. 8
      lib/modules/meeting/views/meeting_edit_view.dart
  15. 6
      macos/Runner.xcodeproj/project.pbxproj
  16. 2
      macos/Runner/Configs/AppInfo.xcconfig

9
ios/Runner.xcodeproj/project.pbxproj

@ -523,7 +523,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = 23;
DEVELOPMENT_TEAM = 42C82SZUVX; DEVELOPMENT_TEAM = 42C82SZUVX;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
@ -532,6 +532,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound; PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -723,7 +724,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = 23;
DEVELOPMENT_TEAM = 42C82SZUVX; DEVELOPMENT_TEAM = 42C82SZUVX;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
@ -732,6 +733,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound; PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -748,7 +750,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = 23;
DEVELOPMENT_TEAM = 42C82SZUVX; DEVELOPMENT_TEAM = 42C82SZUVX;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
@ -757,6 +759,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound; PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";

4
ios/Runner/BluetoothMediaButtonHelper.swift

@ -230,8 +230,8 @@ class BluetoothMediaButtonHelper: NSObject, AVAudioPlayerDelegate {
// 创建音频专辑图片 // 创建音频专辑图片
let albumArt = UIImage(named: "AppIcon") // 使用应用图标作为专辑图片 let albumArt = UIImage(named: "AppIcon") // 使用应用图标作为专辑图片
var nowPlayingInfo: [String: Any] = [ var nowPlayingInfo: [String: Any] = [
MPMediaItemPropertyTitle: "DeepVoice 语音助手", MPMediaItemPropertyTitle: "DeepSound 语音助手",
MPMediaItemPropertyArtist: "DeepVoice", MPMediaItemPropertyArtist: "DeepSound",
MPNowPlayingInfoPropertyPlaybackRate: isPlaying ? 1.0 : 0.0, // 1.0表示正在播放 MPNowPlayingInfoPropertyPlaybackRate: isPlaying ? 1.0 : 0.0, // 1.0表示正在播放
MPNowPlayingInfoPropertyElapsedPlaybackTime: currentPlaybackTime, MPNowPlayingInfoPropertyElapsedPlaybackTime: currentPlaybackTime,
MPMediaItemPropertyPlaybackDuration: totalDuration MPMediaItemPropertyPlaybackDuration: totalDuration

4
ios/Runner/Info.plist

@ -2,12 +2,14 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>Deep Voice</string> <string>DeepSound</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>

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

@ -99,8 +99,8 @@ const Map<String, String> enUS = {
"simultaneousTranslationInfo": "Instant translation for 80+ languages", "simultaneousTranslationInfo": "Instant translation for 80+ languages",
"aiGirlfriend": "Parent-child bondingn", "aiGirlfriend": "Parent-child bondingn",
"aiGirlfriendInfo": "Intelligent company, chat at any time", "aiGirlfriendInfo": "Intelligent company, chat at any time",
"healthConsultant": "Health Consultant", "healthConsultant": "Multi-function AI chat",
"healthConsultantInfo": "Provides scientific health advice", "healthConsultantInfo": "More AI chat features",
"conferenceAssistant": "Business Meeting Assistant", "conferenceAssistant": "Business Meeting Assistant",
"conferenceAssistantInfo": "conferenceAssistantInfo":
"Real-time minutes recording and multilingual translation", "Real-time minutes recording and multilingual translation",

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

@ -98,8 +98,8 @@ const Map<String, String> zhCN = {
"simultaneousTranslationInfo": "实时翻译80+种语言", "simultaneousTranslationInfo": "实时翻译80+种语言",
"aiGirlfriend": "亲子陪伴", "aiGirlfriend": "亲子陪伴",
"aiGirlfriendInfo": "智能陪伴,随时聊天", "aiGirlfriendInfo": "智能陪伴,随时聊天",
"healthConsultant": "健康顾问", "healthConsultant": "多功能AI聊天",
"healthConsultantInfo": "提供科学健康建议", "healthConsultantInfo": "提供更多AI聊天功能",
"conferenceAssistant": "商务会议助手", "conferenceAssistant": "商务会议助手",
"conferenceAssistantInfo": "实时记录会议纪要,多语言翻译", "conferenceAssistantInfo": "实时记录会议纪要,多语言翻译",
"studyAssistant": "语言学习助手", "studyAssistant": "语言学习助手",

8
lib/data/providers/agent_provider.dart

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

6
lib/data/services/deapsound_ai_service.dart

@ -20,7 +20,7 @@ class DeapsoundAIService implements AiService {
required String systemPrompt, required String systemPrompt,
}) async* { }) async* {
final client = http.Client(); final client = http.Client();
final url = Uri.parse('https://www.deapsound.com/ai/chat'); final url = Uri.parse('https://www.deapsound.com/ai/funcchat');
final headers = { final headers = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -51,8 +51,8 @@ class DeapsoundAIService implements AiService {
if (data['event'] == 'workflow_finished') { if (data['event'] == 'workflow_finished') {
break; break;
} }
if (data['event'] == 'text_chunk') { if (data['content'] != null) {
String content = data['data']['text'] ?? ''; String content = data['content'] ?? '';
if (content.isNotEmpty) { if (content.isNotEmpty) {
yield content; yield content;
} }

285
lib/data/services/speech_impl/volcano_asr_api_service.dart

@ -15,61 +15,61 @@ import 'package:record/record.dart';
import '../asr_service.dart'; import '../asr_service.dart';
/// Volcano语音识别服务 /// Volcano语音识别服务
/// ///
/// 负责与火山引擎实时语音识别服务的通信 /// 负责与火山引擎实时语音识别服务的通信
class VolcanoAsrApiService implements AsrService { class VolcanoAsrApiService implements AsrService {
static final VolcanoAsrApiService to = Get.find<VolcanoAsrApiService>(); static final VolcanoAsrApiService to = Get.find<VolcanoAsrApiService>();
// API配置 // API配置
late final String _appKey; late final String _appKey;
late final String _secretKey; late final String _secretKey;
late final String _serviceId; late final String _serviceId;
late final String _hostUrl; late final String _hostUrl;
final String _path = '/ws/v1/asr'; final String _path = '/ws/v1/asr';
// 请求相关参数 // 请求相关参数
final String _algorithm = 'hmac-sha1'; final String _algorithm = 'hmac-sha1';
final Uuid _uuid = const Uuid(); final Uuid _uuid = const Uuid();
// 录音配置 // 录音配置
final int _sampleRate = 16000; final int _sampleRate = 16000;
final int _bitsPerSample = 16; final int _bitsPerSample = 16;
final int _channels = 1; final int _channels = 1;
final String _encoding = 'pcm'; final String _encoding = 'pcm';
// 连接相关 // 连接相关
WebSocketChannel? _channel; WebSocketChannel? _channel;
Completer<void>? _connectionCompleter; Completer<void>? _connectionCompleter;
StreamSubscription? _connectionSubscription; StreamSubscription? _connectionSubscription;
Timer? _heartbeatTimer; Timer? _heartbeatTimer;
// 语音录音 // 语音录音
final Record? _recorder = null; // TODO: 实现正确的Record实例化 final Record? _recorder = null; // TODO: 实现正确的Record实例化
Timer? _audioTimer; Timer? _audioTimer;
bool _isRecording = false; bool _isRecording = false;
// 状态控制 // 状态控制
bool _isInitialized = false; bool _isInitialized = false;
bool _isConnecting = false; bool _isConnecting = false;
bool _isContinuousRecognitionActive = false; bool _isContinuousRecognitionActive = false;
// 识别结果控制 // 识别结果控制
bool _isRecognitionEndRequested = false; bool _isRecognitionEndRequested = false;
String _currentSessionId = ''; String _currentSessionId = '';
final List<String> _supportedLanguages = ['zh-CN', 'en-US']; final List<String> _supportedLanguages = ['zh-CN', 'en-US'];
String _selectedLanguage = 'zh-CN'; String _selectedLanguage = 'zh-CN';
// 结果流 // 结果流
StreamController<RecognitionEvent>? _recognitionController; StreamController<RecognitionEvent>? _recognitionController;
// 事件计数器(用于调试) // 事件计数器(用于调试)
int _sendPacketCount = 0; int _sendPacketCount = 0;
int _receivePacketCount = 0; int _receivePacketCount = 0;
// AsrService接口实现 // AsrService接口实现
@override @override
List<String> get supportedLanguages => _supportedLanguages; List<String> get supportedLanguages => _supportedLanguages;
@override @override
Future<bool> initialize({ Future<bool> initialize({
List<String>? supportedLanguages, List<String>? supportedLanguages,
@ -79,21 +79,21 @@ class VolcanoAsrApiService implements AsrService {
if (_isInitialized) { if (_isInitialized) {
await dispose(); await dispose();
} }
_isInitialized = false; _isInitialized = false;
// 从环境变量加载配置 // 从环境变量加载配置
_loadConfig(); _loadConfig();
// 设置支持的语言 // 设置支持的语言
if (supportedLanguages != null && supportedLanguages.isNotEmpty) { if (supportedLanguages != null && supportedLanguages.isNotEmpty) {
_supportedLanguages.clear(); _supportedLanguages.clear();
_supportedLanguages.addAll(supportedLanguages); _supportedLanguages.addAll(supportedLanguages);
} }
// 初始化录音机 // 初始化录音机
await _initRecorder(); await _initRecorder();
_isInitialized = true; _isInitialized = true;
Logger.info('火山语音API已初始化'); Logger.info('火山语音API已初始化');
return true; return true;
@ -102,19 +102,19 @@ class VolcanoAsrApiService implements AsrService {
return false; return false;
} }
} }
@override @override
Future<RecognitionEvent> recognizeOnce() async { Future<RecognitionEvent> recognizeOnce() async {
try { try {
if (!_isInitialized) { if (!_isInitialized) {
await initialize(); await initialize();
} }
final resultCompleter = Completer<RecognitionEvent>(); final resultCompleter = Completer<RecognitionEvent>();
// 开始连续识别流程 // 开始连续识别流程
final stream = await startContinuousRecognition(); final stream = await startContinuousRecognition();
// 监听结果 // 监听结果
StreamSubscription? subscription; StreamSubscription? subscription;
subscription = stream.listen((event) { subscription = stream.listen((event) {
@ -138,7 +138,7 @@ class VolcanoAsrApiService implements AsrService {
resultCompleter.complete(RecognitionEvent.error('未收到识别结果')); resultCompleter.complete(RecognitionEvent.error('未收到识别结果'));
} }
}); });
// 设置超时 // 设置超时
Timer(const Duration(seconds: 30), () { Timer(const Duration(seconds: 30), () {
if (!resultCompleter.isCompleted) { if (!resultCompleter.isCompleted) {
@ -147,104 +147,104 @@ class VolcanoAsrApiService implements AsrService {
stopContinuousRecognition(); stopContinuousRecognition();
} }
}); });
return resultCompleter.future; return resultCompleter.future;
} catch (e) { } catch (e) {
Logger.error('单次语音识别失败: $e'); Logger.error('单次语音识别失败: $e');
return RecognitionEvent.error(e.toString()); return RecognitionEvent.error(e.toString());
} }
} }
@override @override
Future<Stream<RecognitionEvent>> startContinuousRecognition() async { Future<Stream<RecognitionEvent>> startContinuousRecognition() async {
if (!_isInitialized) { if (!_isInitialized) {
await initialize(); await initialize();
} }
if (_isContinuousRecognitionActive) { if (_isContinuousRecognitionActive) {
await stopContinuousRecognition(); await stopContinuousRecognition();
} }
try { try {
// 创建结果控制器 // 创建结果控制器
_recognitionController = StreamController<RecognitionEvent>.broadcast(); _recognitionController = StreamController<RecognitionEvent>.broadcast();
// 连接WebSocket // 连接WebSocket
await _connectWebSocket(); await _connectWebSocket();
// 开始录音 // 开始录音
await _startRecording(); await _startRecording();
_isContinuousRecognitionActive = true; _isContinuousRecognitionActive = true;
_isRecognitionEndRequested = false; _isRecognitionEndRequested = false;
Logger.info('连续语音识别已开始'); Logger.info('连续语音识别已开始');
return _recognitionController!.stream; return _recognitionController!.stream;
} catch (e) { } catch (e) {
Logger.error('开始连续语音识别失败: $e'); Logger.error('开始连续语音识别失败: $e');
await _cleanupResources(); await _cleanupResources();
// 返回一个包含错误的流 // 返回一个包含错误的流
final errorController = StreamController<RecognitionEvent>.broadcast(); final errorController = StreamController<RecognitionEvent>.broadcast();
errorController.add(RecognitionEvent.error(e.toString())); errorController.add(RecognitionEvent.error(e.toString()));
errorController.close(); errorController.close();
return errorController.stream; return errorController.stream;
} }
} }
@override @override
Future<bool> stopContinuousRecognition() async { Future<bool> stopContinuousRecognition() async {
if (!_isContinuousRecognitionActive) { if (!_isContinuousRecognitionActive) {
return true; return true;
} }
try { try {
_isRecognitionEndRequested = true; _isRecognitionEndRequested = true;
// 先停止录音,以确保不会再发送新的音频数据 // 先停止录音,以确保不会再发送新的音频数据
await _stopRecording(); await _stopRecording();
// 发送结束指令 // 发送结束指令
await _sendEndSignal(); await _sendEndSignal();
// 等待服务器处理完所有已发送的数据 // 等待服务器处理完所有已发送的数据
await Future.delayed(const Duration(milliseconds: 500)); await Future.delayed(const Duration(milliseconds: 500));
// 清理资源 // 清理资源
await _cleanupResources(); await _cleanupResources();
_isContinuousRecognitionActive = false; _isContinuousRecognitionActive = false;
Logger.info('连续语音识别已停止'); Logger.info('连续语音识别已停止');
return true; return true;
} catch (e) { } catch (e) {
Logger.error('停止连续语音识别失败: $e'); Logger.error('停止连续语音识别失败: $e');
// 确保资源被清理 // 确保资源被清理
await _cleanupResources(); await _cleanupResources();
_isContinuousRecognitionActive = false; _isContinuousRecognitionActive = false;
return false; return false;
} }
} }
@override @override
bool isContinuousRecognitionActive() { bool isContinuousRecognitionActive() {
return _isContinuousRecognitionActive; return _isContinuousRecognitionActive;
} }
@override @override
Future<void> dispose() async { Future<void> dispose() async {
try { try {
if (_isContinuousRecognitionActive) { if (_isContinuousRecognitionActive) {
await stopContinuousRecognition(); await stopContinuousRecognition();
} }
// 确保资源被清理 // 确保资源被清理
await _cleanupResources(); await _cleanupResources();
_isInitialized = false; _isInitialized = false;
Logger.info('火山语音API资源已释放'); Logger.info('火山语音API资源已释放');
} catch (e) { } catch (e) {
@ -253,31 +253,32 @@ class VolcanoAsrApiService implements AsrService {
_isInitialized = false; _isInitialized = false;
} }
} }
/// 从环境变量加载配置 /// 从环境变量加载配置
void _loadConfig() { void _loadConfig() {
_appKey = dotenv.env['VOLCANO_APP_KEY'] ?? ''; _appKey = dotenv.env['VOLCANO_APP_KEY'] ?? '';
_secretKey = dotenv.env['VOLCANO_SECRET_KEY'] ?? ''; _secretKey = dotenv.env['VOLCANO_SECRET_KEY'] ?? '';
_serviceId = dotenv.env['VOLCANO_SERVICE_ID'] ?? ''; _serviceId = dotenv.env['VOLCANO_SERVICE_ID'] ?? '';
_hostUrl = dotenv.env['VOLCANO_HOST_URL'] ?? 'ws-api-asr.volces.com'; _hostUrl = dotenv.env['VOLCANO_HOST_URL'] ?? 'ws-api-asr.volces.com';
if (_appKey.isEmpty || _secretKey.isEmpty || _serviceId.isEmpty) { if (_appKey.isEmpty || _secretKey.isEmpty || _serviceId.isEmpty) {
throw Exception('未找到火山语音API配置,请在.env文件中设置VOLCANO_APP_KEY, VOLCANO_SECRET_KEY和VOLCANO_SERVICE_ID'); throw Exception(
'未找到火山语音API配置,请在.env文件中设置VOLCANO_APP_KEY, VOLCANO_SECRET_KEY和VOLCANO_SERVICE_ID');
} }
} }
/// 初始化录音机 /// 初始化录音机
Future<void> _initRecorder() async { Future<void> _initRecorder() async {
try { try {
// TODO: 正确实现录音机初始化 // TODO: 正确实现录音机初始化
// _recorder = Record(); // _recorder = Record();
// 检查和请求权限 // 检查和请求权限
// final hasPermission = await _recorder!.hasPermission(); // final hasPermission = await _recorder!.hasPermission();
// if (!hasPermission) { // if (!hasPermission) {
// throw Exception('没有录音权限'); // throw Exception('没有录音权限');
// } // }
Logger.info('录音机初始化成功'); Logger.info('录音机初始化成功');
} catch (e) { } catch (e) {
// _recorder = null; // _recorder = null;
@ -285,7 +286,7 @@ class VolcanoAsrApiService implements AsrService {
rethrow; rethrow;
} }
} }
/// 连接WebSocket /// 连接WebSocket
Future<void> _connectWebSocket() async { Future<void> _connectWebSocket() async {
if (_isConnecting) { if (_isConnecting) {
@ -294,20 +295,20 @@ class VolcanoAsrApiService implements AsrService {
return _connectionCompleter!.future; return _connectionCompleter!.future;
} }
} }
_connectionCompleter = Completer<void>(); _connectionCompleter = Completer<void>();
_isConnecting = true; _isConnecting = true;
try { try {
// 关闭现有连接 // 关闭现有连接
await _closeWebSocket(); await _closeWebSocket();
// 生成新的会话ID // 生成新的会话ID
_currentSessionId = _uuid.v4(); _currentSessionId = _uuid.v4();
// 构建URL和请求头 // 构建URL和请求头
final (url, headers) = _buildConnectionParams(); final (url, headers) = _buildConnectionParams();
// 创建WebSocket连接 // 创建WebSocket连接
if (kIsWeb) { if (kIsWeb) {
_channel = WebSocketChannel.connect(Uri.parse(url)); _channel = WebSocketChannel.connect(Uri.parse(url));
@ -317,42 +318,42 @@ class VolcanoAsrApiService implements AsrService {
headers: headers, headers: headers,
); );
} }
// 处理WebSocket消息 // 处理WebSocket消息
_connectionSubscription = _channel!.stream.listen( _connectionSubscription = _channel!.stream.listen(
_handleWebSocketMessage, _handleWebSocketMessage,
onError: _handleWebSocketError, onError: _handleWebSocketError,
onDone: _handleWebSocketDone, onDone: _handleWebSocketDone,
); );
// 开始心跳 // 开始心跳
_startHeartbeat(); _startHeartbeat();
// 发送开始识别指令 // 发送开始识别指令
await _sendStartCommand(); await _sendStartCommand();
_isConnecting = false; _isConnecting = false;
_connectionCompleter!.complete(); _connectionCompleter!.complete();
Logger.info('WebSocket连接成功'); Logger.info('WebSocket连接成功');
} catch (e) { } catch (e) {
_isConnecting = false; _isConnecting = false;
if (_connectionCompleter != null && !_connectionCompleter!.isCompleted) { if (_connectionCompleter != null && !_connectionCompleter!.isCompleted) {
_connectionCompleter!.completeError(e); _connectionCompleter!.completeError(e);
} }
Logger.error('连接WebSocket失败: $e'); Logger.error('连接WebSocket失败: $e');
rethrow; rethrow;
} }
return _connectionCompleter!.future; return _connectionCompleter!.future;
} }
/// 构建连接参数 /// 构建连接参数
(String, Map<String, String>?) _buildConnectionParams() { (String, Map<String, String>?) _buildConnectionParams() {
final timestamp = (DateTime.now().millisecondsSinceEpoch / 1000).floor(); final timestamp = (DateTime.now().millisecondsSinceEpoch / 1000).floor();
final nonce = _generateNonce(); final nonce = _generateNonce();
// 构建表单(用于签名和查询参数) // 构建表单(用于签名和查询参数)
final form = { final form = {
'algorithm': _algorithm, 'algorithm': _algorithm,
@ -362,54 +363,54 @@ class VolcanoAsrApiService implements AsrService {
'session_id': _currentSessionId, 'session_id': _currentSessionId,
'timestamp': timestamp.toString(), 'timestamp': timestamp.toString(),
}; };
// 计算签名 // 计算签名
final signature = _calculateSignature(form); final signature = _calculateSignature(form);
// 添加签名到表单 // 添加签名到表单
form['signature'] = signature; form['signature'] = signature;
// 构建URL查询参数 // 构建URL查询参数
final queryString = form.entries final queryString = form.entries
.map((e) => '${e.key}=${Uri.encodeComponent(e.value)}') .map((e) => '${e.key}=${Uri.encodeComponent(e.value)}')
.join('&'); .join('&');
// 构建完整URL // 构建完整URL
final url = 'wss://$_hostUrl$_path?$queryString'; final url = 'wss://$_hostUrl$_path?$queryString';
// 构建请求头 // 构建请求头
final headers = <String, String>{ final headers = <String, String>{
'User-Agent': 'Deep Voice App/1.0.0', 'User-Agent': 'DeepSound App/1.0.0',
}; };
return (url, kIsWeb ? null : headers); return (url, kIsWeb ? null : headers);
} }
/// 生成随机数 /// 生成随机数
String _generateNonce() { String _generateNonce() {
final random = Random(); final random = Random();
final randoms = List.generate(16, (_) => random.nextInt(256)); final randoms = List.generate(16, (_) => random.nextInt(256));
return base64Url.encode(randoms).substring(0, 16); return base64Url.encode(randoms).substring(0, 16);
} }
/// 计算签名 /// 计算签名
String _calculateSignature(Map<String, String> form) { String _calculateSignature(Map<String, String> form) {
// 按键排序 // 按键排序
final sortedKeys = form.keys.toList()..sort(); final sortedKeys = form.keys.toList()..sort();
// 构建待签名字符串 // 构建待签名字符串
final signString = sortedKeys.map((key) => '$key=${form[key]}').join(''); final signString = sortedKeys.map((key) => '$key=${form[key]}').join('');
// 使用HMAC-SHA1进行签名 // 使用HMAC-SHA1进行签名
final key = utf8.encode(_secretKey); final key = utf8.encode(_secretKey);
final bytes = utf8.encode(signString); final bytes = utf8.encode(signString);
final hmacSha1 = Hmac(sha1, key); final hmacSha1 = Hmac(sha1, key);
final digest = hmacSha1.convert(bytes); final digest = hmacSha1.convert(bytes);
// 返回Base64编码的签名 // 返回Base64编码的签名
return base64.encode(digest.bytes); return base64.encode(digest.bytes);
} }
/// 开始心跳定时 /// 开始心跳定时
void _startHeartbeat() { void _startHeartbeat() {
_heartbeatTimer?.cancel(); _heartbeatTimer?.cancel();
@ -417,7 +418,7 @@ class VolcanoAsrApiService implements AsrService {
_sendHeartbeat(); _sendHeartbeat();
}); });
} }
/// 发送心跳包 /// 发送心跳包
void _sendHeartbeat() { void _sendHeartbeat() {
if (_channel != null && _isContinuousRecognitionActive) { if (_channel != null && _isContinuousRecognitionActive) {
@ -426,18 +427,18 @@ class VolcanoAsrApiService implements AsrService {
'type': 'heartbeat', 'type': 'heartbeat',
'sequence': _sendPacketCount++, 'sequence': _sendPacketCount++,
}; };
_channel!.sink.add(jsonEncode(heartbeatMsg)); _channel!.sink.add(jsonEncode(heartbeatMsg));
} catch (e) { } catch (e) {
Logger.error('发送心跳包失败: $e'); Logger.error('发送心跳包失败: $e');
} }
} }
} }
/// 发送开始识别指令 /// 发送开始识别指令
Future<void> _sendStartCommand() async { Future<void> _sendStartCommand() async {
if (_channel == null) return; if (_channel == null) return;
try { try {
final startCommand = { final startCommand = {
'type': 'start', 'type': 'start',
@ -454,7 +455,7 @@ class VolcanoAsrApiService implements AsrService {
'max_seconds': 60, 'max_seconds': 60,
}, },
}; };
_channel!.sink.add(jsonEncode(startCommand)); _channel!.sink.add(jsonEncode(startCommand));
Logger.info('已发送开始识别指令'); Logger.info('已发送开始识别指令');
} catch (e) { } catch (e) {
@ -462,7 +463,7 @@ class VolcanoAsrApiService implements AsrService {
rethrow; rethrow;
} }
} }
/// 开始录音 /// 开始录音
Future<void> _startRecording() async { Future<void> _startRecording() async {
// TODO: 实现正确的录音开始逻辑 // TODO: 实现正确的录音开始逻辑
@ -497,12 +498,12 @@ class VolcanoAsrApiService implements AsrService {
rethrow; rethrow;
} }
*/ */
// 临时实现,防止错误 // 临时实现,防止错误
_isRecording = true; _isRecording = true;
Logger.info('录音已开始(模拟)'); Logger.info('录音已开始(模拟)');
} }
/// 捕获并发送音频数据 /// 捕获并发送音频数据
Future<void> _captureAndSendAudio() async { Future<void> _captureAndSendAudio() async {
// TODO: 实现正确的音频捕获逻辑 // TODO: 实现正确的音频捕获逻辑
@ -545,46 +546,46 @@ class VolcanoAsrApiService implements AsrService {
} }
*/ */
} }
/// 发送音频数据 /// 发送音频数据
Future<void> _sendAudioData(Uint8List data) async { Future<void> _sendAudioData(Uint8List data) async {
if (_channel == null || data.isEmpty) return; if (_channel == null || data.isEmpty) return;
try { try {
final audioMessage = { final audioMessage = {
'type': 'audio', 'type': 'audio',
'sequence': _sendPacketCount++, 'sequence': _sendPacketCount++,
'audio': base64.encode(data), 'audio': base64.encode(data),
}; };
_channel!.sink.add(jsonEncode(audioMessage)); _channel!.sink.add(jsonEncode(audioMessage));
} catch (e) { } catch (e) {
Logger.error('发送音频数据失败: $e'); Logger.error('发送音频数据失败: $e');
} }
} }
/// 发送结束信号 /// 发送结束信号
Future<void> _sendEndSignal() async { Future<void> _sendEndSignal() async {
if (_channel == null) return; if (_channel == null) return;
try { try {
final endCommand = { final endCommand = {
'type': 'end', 'type': 'end',
'sequence': _sendPacketCount++, 'sequence': _sendPacketCount++,
}; };
_channel!.sink.add(jsonEncode(endCommand)); _channel!.sink.add(jsonEncode(endCommand));
Logger.info('已发送结束指令'); Logger.info('已发送结束指令');
} catch (e) { } catch (e) {
Logger.error('发送结束指令失败: $e'); Logger.error('发送结束指令失败: $e');
} }
} }
/// 停止录音 /// 停止录音
Future<void> _stopRecording() async { Future<void> _stopRecording() async {
_audioTimer?.cancel(); _audioTimer?.cancel();
_audioTimer = null; _audioTimer = null;
// TODO: 实现正确的录音停止逻辑 // TODO: 实现正确的录音停止逻辑
/* /*
if (_isRecording && _recorder != null) { if (_isRecording && _recorder != null) {
@ -597,64 +598,65 @@ class VolcanoAsrApiService implements AsrService {
} }
} }
*/ */
// 临时实现,防止错误 // 临时实现,防止错误
_isRecording = false; _isRecording = false;
Logger.info('录音已停止(模拟)'); Logger.info('录音已停止(模拟)');
} }
/// 处理WebSocket消息 /// 处理WebSocket消息
void _handleWebSocketMessage(dynamic message) { void _handleWebSocketMessage(dynamic message) {
try { try {
_receivePacketCount++; _receivePacketCount++;
if (message is String) { if (message is String) {
final Map<String, dynamic> data = jsonDecode(message); final Map<String, dynamic> data = jsonDecode(message);
final String type = data['type'] as String? ?? ''; final String type = data['type'] as String? ?? '';
switch (type) { switch (type) {
case 'result': case 'result':
_handleResultMessage(data); _handleResultMessage(data);
break; break;
case 'start_result': case 'start_result':
_handleStartResultMessage(data); _handleStartResultMessage(data);
break; break;
case 'end_result': case 'end_result':
_handleEndResultMessage(data); _handleEndResultMessage(data);
break; break;
case 'error': case 'error':
_handleErrorMessage(data); _handleErrorMessage(data);
break; break;
case 'heartbeat_result': case 'heartbeat_result':
// 心跳响应,不需要特殊处理 // 心跳响应,不需要特殊处理
break; break;
default: default:
Logger.info('收到未知类型的消息: $type'); Logger.info('收到未知类型的消息: $type');
} }
} }
} catch (e) { } catch (e) {
Logger.error('处理WebSocket消息失败: $e'); Logger.error('处理WebSocket消息失败: $e');
if (_recognitionController != null && !_recognitionController!.isClosed) { if (_recognitionController != null && !_recognitionController!.isClosed) {
_recognitionController!.addError('处理消息失败: $e'); _recognitionController!.addError('处理消息失败: $e');
} }
} }
} }
/// 处理结果消息 /// 处理结果消息
void _handleResultMessage(Map<String, dynamic> data) { void _handleResultMessage(Map<String, dynamic> data) {
try { try {
final Map<String, dynamic>? result = data['result'] as Map<String, dynamic>?; final Map<String, dynamic>? result =
data['result'] as Map<String, dynamic>?;
if (result == null) return; if (result == null) return;
final bool isEnd = result['is_end'] as bool? ?? false; final bool isEnd = result['is_end'] as bool? ?? false;
final String text = result['text'] as String? ?? ''; final String text = result['text'] as String? ?? '';
if (_recognitionController != null && !_recognitionController!.isClosed) { if (_recognitionController != null && !_recognitionController!.isClosed) {
if (isEnd) { if (isEnd) {
// 最终结果 // 最终结果
@ -674,11 +676,11 @@ class VolcanoAsrApiService implements AsrService {
Logger.error('处理结果消息失败: $e'); Logger.error('处理结果消息失败: $e');
} }
} }
/// 处理开始结果消息 /// 处理开始结果消息
void _handleStartResultMessage(Map<String, dynamic> data) { void _handleStartResultMessage(Map<String, dynamic> data) {
final int code = data['code'] as int? ?? -1; final int code = data['code'] as int? ?? -1;
if (code == 0) { if (code == 0) {
// 开始成功 // 开始成功
if (_recognitionController != null && !_recognitionController!.isClosed) { if (_recognitionController != null && !_recognitionController!.isClosed) {
@ -689,19 +691,20 @@ class VolcanoAsrApiService implements AsrService {
} else { } else {
// 开始失败 // 开始失败
final String message = data['message'] as String? ?? '未知错误'; final String message = data['message'] as String? ?? '未知错误';
Logger.error('语音识别开始失败: [$code] $message'); Logger.error('语音识别开始失败: [$code] $message');
if (_recognitionController != null && !_recognitionController!.isClosed) { if (_recognitionController != null && !_recognitionController!.isClosed) {
_recognitionController!.add(RecognitionEvent.error('开始失败: [$code] $message')); _recognitionController!
.add(RecognitionEvent.error('开始失败: [$code] $message'));
} }
} }
} }
/// 处理结束结果消息 /// 处理结束结果消息
void _handleEndResultMessage(Map<String, dynamic> data) { void _handleEndResultMessage(Map<String, dynamic> data) {
final int code = data['code'] as int? ?? -1; final int code = data['code'] as int? ?? -1;
if (code == 0) { if (code == 0) {
// 结束成功 // 结束成功
Logger.info('语音识别成功结束'); Logger.info('语音识别成功结束');
@ -710,73 +713,75 @@ class VolcanoAsrApiService implements AsrService {
final String message = data['message'] as String? ?? '未知错误'; final String message = data['message'] as String? ?? '未知错误';
Logger.error('语音识别结束失败: [$code] $message'); Logger.error('语音识别结束失败: [$code] $message');
} }
// 无论成功还是失败,通知会话已结束 // 无论成功还是失败,通知会话已结束
if (_recognitionController != null && !_recognitionController!.isClosed) { if (_recognitionController != null && !_recognitionController!.isClosed) {
_recognitionController!.add(RecognitionEvent( _recognitionController!.add(RecognitionEvent(
type: RecognitionEventType.sessionStopped, type: RecognitionEventType.sessionStopped,
)); ));
} }
// 清理资源 // 清理资源
_cleanupResources(); _cleanupResources();
} }
/// 处理错误消息 /// 处理错误消息
void _handleErrorMessage(Map<String, dynamic> data) { void _handleErrorMessage(Map<String, dynamic> data) {
final int code = data['code'] as int? ?? -1; final int code = data['code'] as int? ?? -1;
final String message = data['message'] as String? ?? '未知错误'; final String message = data['message'] as String? ?? '未知错误';
Logger.error('收到错误消息: [$code] $message'); Logger.error('收到错误消息: [$code] $message');
if (_recognitionController != null && !_recognitionController!.isClosed) { if (_recognitionController != null && !_recognitionController!.isClosed) {
_recognitionController!.add(RecognitionEvent.error('[$code] $message')); _recognitionController!.add(RecognitionEvent.error('[$code] $message'));
} }
} }
/// 处理WebSocket错误 /// 处理WebSocket错误
void _handleWebSocketError(dynamic error) { void _handleWebSocketError(dynamic error) {
Logger.error('WebSocket错误: $error'); Logger.error('WebSocket错误: $error');
if (_connectionCompleter != null && !_connectionCompleter!.isCompleted) { if (_connectionCompleter != null && !_connectionCompleter!.isCompleted) {
_connectionCompleter!.completeError(error); _connectionCompleter!.completeError(error);
} }
if (_recognitionController != null && !_recognitionController!.isClosed) { if (_recognitionController != null && !_recognitionController!.isClosed) {
_recognitionController!.addError(error); _recognitionController!.addError(error);
} }
// 清理资源 // 清理资源
_cleanupResources(); _cleanupResources();
} }
/// 处理WebSocket连接关闭 /// 处理WebSocket连接关闭
void _handleWebSocketDone() { void _handleWebSocketDone() {
Logger.info('WebSocket连接已关闭'); Logger.info('WebSocket连接已关闭');
if (_connectionCompleter != null && !_connectionCompleter!.isCompleted) { if (_connectionCompleter != null && !_connectionCompleter!.isCompleted) {
_connectionCompleter!.completeError('WebSocket连接已关闭'); _connectionCompleter!.completeError('WebSocket连接已关闭');
} }
// 通知会话已结束 // 通知会话已结束
if (_recognitionController != null && !_recognitionController!.isClosed && !_isRecognitionEndRequested) { if (_recognitionController != null &&
!_recognitionController!.isClosed &&
!_isRecognitionEndRequested) {
_recognitionController!.add(RecognitionEvent( _recognitionController!.add(RecognitionEvent(
type: RecognitionEventType.sessionStopped, type: RecognitionEventType.sessionStopped,
)); ));
} }
// 清理资源 // 清理资源
_cleanupResources(); _cleanupResources();
} }
/// 关闭WebSocket连接 /// 关闭WebSocket连接
Future<void> _closeWebSocket() async { Future<void> _closeWebSocket() async {
_connectionSubscription?.cancel(); _connectionSubscription?.cancel();
_connectionSubscription = null; _connectionSubscription = null;
_heartbeatTimer?.cancel(); _heartbeatTimer?.cancel();
_heartbeatTimer = null; _heartbeatTimer = null;
if (_channel != null) { if (_channel != null) {
try { try {
_channel!.sink.close(); _channel!.sink.close();
@ -786,22 +791,22 @@ class VolcanoAsrApiService implements AsrService {
} }
} }
} }
/// 清理所有资源 /// 清理所有资源
Future<void> _cleanupResources() async { Future<void> _cleanupResources() async {
// 停止录音 // 停止录音
await _stopRecording(); await _stopRecording();
// 关闭WebSocket // 关闭WebSocket
await _closeWebSocket(); await _closeWebSocket();
// 关闭结果控制器 // 关闭结果控制器
if (_recognitionController != null && !_recognitionController!.isClosed) { if (_recognitionController != null && !_recognitionController!.isClosed) {
await _recognitionController!.close(); await _recognitionController!.close();
_recognitionController = null; _recognitionController = null;
} }
_isContinuousRecognitionActive = false; _isContinuousRecognitionActive = false;
_isRecognitionEndRequested = false; _isRecognitionEndRequested = false;
} }
} }

2
lib/main.dart

@ -30,7 +30,7 @@ void main() async {
const double width = 375.0; const double width = 375.0;
const double height = 812.0; const double height = 812.0;
setWindowTitle('Deep Voice'); setWindowTitle('DeepSound');
setWindowMinSize(const Size(width, height)); setWindowMinSize(const Size(width, height));
setWindowMaxSize(const Size(width, height)); setWindowMaxSize(const Size(width, height));

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

@ -93,16 +93,16 @@ class ChatController extends GetxController {
agent = foundAgent; agent = foundAgent;
switch (agent.id) { switch (agent.id) {
case 'personal_assistant':
// 全能助手
Get.put(DeapsoundAIService());
_aiService = Get.find<DeapsoundAIService>();
break;
case 'cyber_girlfriend': case 'cyber_girlfriend':
// 亲子陪伴 // 亲子陪伴
Get.put(KouziAIService()); Get.put(KouziAIService());
_aiService = Get.find<KouziAIService>(); _aiService = Get.find<KouziAIService>();
break; break;
case 'health_advisor':
// 多功能AI聊天
Get.put(DeapsoundAIService());
_aiService = Get.find<DeapsoundAIService>();
break;
default: default:
_aiService = Get.find<VolcanoAIService>(); _aiService = Get.find<VolcanoAIService>();
} }

2
lib/modules/chat/views/chat_view.dart

@ -2,11 +2,9 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'dart:math'; import 'dart:math';
import 'package:audio_waveforms/audio_waveforms.dart';
import '../controllers/chat_controller.dart'; import '../controllers/chat_controller.dart';
import '../models/message_model.dart'; import '../models/message_model.dart';
import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_colors.dart';
import '../../../core/widgets/message_bubble.dart';
class ChatView extends GetView<ChatController> { class ChatView extends GetView<ChatController> {
const ChatView({Key? key}) : super(key: key); const ChatView({Key? key}) : super(key: key);

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

@ -15,6 +15,8 @@ class MeetingDetailsController extends GetxController
Rx<MeetingModel> meetingData = MeetingModel().obs; Rx<MeetingModel> meetingData = MeetingModel().obs;
Rx<MeetingDetailsModel> dataDetails = MeetingDetailsModel().obs; Rx<MeetingDetailsModel> dataDetails = MeetingDetailsModel().obs;
int seconds = 0;
RxBool isPlay = false.obs; RxBool isPlay = false.obs;
@override @override
@ -36,6 +38,7 @@ class MeetingDetailsController extends GetxController
final data = await SqfliteApi.getMeetingDetails(id); final data = await SqfliteApi.getMeetingDetails(id);
meetingData.value = MeetingModel.fromJson(data); meetingData.value = MeetingModel.fromJson(data);
dataDetails.value = MeetingDetailsModel.fromJson(data['details']); dataDetails.value = MeetingDetailsModel.fromJson(data['details']);
seconds = meetingData.value.seconds;
isLoading.value = false; isLoading.value = false;
} }

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

@ -2,21 +2,23 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../../data/services/db/sqflite_api.dart'; import '../../../data/services/db/sqflite_api.dart';
import '../model/meeting_details_model.dart'; import 'meeting_controller.dart';
import '../model/meeting_model.dart'; import 'meeting_details_controller.dart';
class MeetingEditController extends GetxController class MeetingEditController extends GetxController
with GetSingleTickerProviderStateMixin { with GetSingleTickerProviderStateMixin {
final MeetingController meetingController = Get.find<MeetingController>();
final MeetingDetailsController meetingDetailsController =
Get.find<MeetingDetailsController>();
late AnimationController lottieController; late AnimationController lottieController;
late TextEditingController titleController; late TextEditingController titleController;
late TextEditingController contentController; late TextEditingController contentController;
int id = 0; int creationTime = 0;
RxBool isLoading = true.obs; int id = 0;
Rx<MeetingModel> meetingData = MeetingModel().obs;
Rx<MeetingDetailsModel> dataDetails = MeetingDetailsModel().obs;
RxBool isPlay = false.obs; RxBool isPlay = false.obs;
@ -38,16 +40,13 @@ class MeetingEditController extends GetxController
} }
void _getDataDetails() async { void _getDataDetails() async {
final data = await SqfliteApi.getMeetingDetails(id);
meetingData.value = MeetingModel.fromJson(data);
dataDetails.value = MeetingDetailsModel.fromJson(data['details']);
titleController = TextEditingController( titleController = TextEditingController(
text: meetingData.value.title, text: meetingDetailsController.meetingData.value.title,
); );
contentController = TextEditingController( contentController = TextEditingController(
text: dataDetails.value.content, text: meetingDetailsController.dataDetails.value.content,
); );
isLoading.value = false; creationTime = meetingDetailsController.meetingData.value.creationtime;
} }
void start() { void start() {
@ -59,4 +58,39 @@ class MeetingEditController extends GetxController
isPlay.value = true; isPlay.value = true;
} }
} }
void confirm() async {
String content = contentController.text;
String firstContent =
content.length >= 20 ? content.substring(0, 20) : content;
String title = titleController.text.isNotEmpty
? titleController.text
: meetingDetailsController.meetingData.value.title;
Map<String, Object?> data = {
'title': title,
'firstContent': firstContent,
'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;
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();
}
}
} }

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

@ -17,9 +17,7 @@ class MeetingEditView extends GetView<MeetingEditController> {
return Scaffold( return Scaffold(
backgroundColor: isDarkMode ? Colors.black : Colors.grey[50], backgroundColor: isDarkMode ? Colors.black : Colors.grey[50],
appBar: _buildAppBar(isDarkMode), appBar: _buildAppBar(isDarkMode),
body: Obx(() => !controller.isLoading.value body: _buildBody(isDarkMode),
? _buildBody(isDarkMode)
: const SizedBox()),
); );
} }
@ -38,7 +36,7 @@ class MeetingEditView extends GetView<MeetingEditController> {
), ),
actions: [ actions: [
GestureDetector( GestureDetector(
onTap: () {}, onTap: controller.confirm,
child: Padding( child: Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: 12.w, horizontal: 12.w,
@ -105,7 +103,7 @@ class MeetingEditView extends GetView<MeetingEditController> {
padding: EdgeInsets.symmetric(vertical: 5.w), padding: EdgeInsets.symmetric(vertical: 5.w),
child: Text( child: Text(
DateUtil.formatDateMs( DateUtil.formatDateMs(
controller.meetingData.value.creationtime, controller.creationTime,
format: DateFormats.y_mo_d_h_m, format: DateFormats.y_mo_d_h_m,
), ),
style: TextStyle( style: TextStyle(

6
macos/Runner.xcodeproj/project.pbxproj

@ -479,7 +479,7 @@
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.deepVoice.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/deep_voice.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/deep_voice"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/deep_voice.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/deep_voice";
@ -494,7 +494,7 @@
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.deepVoice.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/deep_voice.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/deep_voice"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/deep_voice.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/deep_voice";
@ -509,7 +509,7 @@
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.deepVoice.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/deep_voice.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/deep_voice"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/deep_voice.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/deep_voice";

2
macos/Runner/Configs/AppInfo.xcconfig

@ -8,7 +8,7 @@
PRODUCT_NAME = deep_voice PRODUCT_NAME = deep_voice
// The application's bundle identifier // The application's bundle identifier
PRODUCT_BUNDLE_IDENTIFIER = com.example.deepVoice PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound
// The copyright displayed in application information // The copyright displayed in application information
PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved.

Loading…
Cancel
Save