diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 20d5ead25..cbb9afd88 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -69,8 +69,8 @@ android { // For more information, see: https://flutter.dev/to/review-gradle-config. minSdk = 26 // 更新为26以满足ble_service库的要求 targetSdk = 34 // 设置为 Android 13 (API 33),以匹配 FOREGROUND_SERVICE_MEDIA_PLAYBACK 权限的要求 - versionCode = flutter.versionCode - versionName = flutter.versionName + versionCode = flutter.versionCode // 对应构建号,自动同步flutter构建号 + versionName = flutter.versionName // 对应版本号,自动同步flutter版本号 } signingConfigs { diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index bc3f4a793..5be33da0e 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -39,7 +39,7 @@ - CFBundleVersion + CFBundleVersion $(FLUTTER_BUILD_NUMBER) FacebookAdvertiserIDCollectionEnabled diff --git a/lib/core/bindings/initial_binding.dart b/lib/core/bindings/initial_binding.dart index 08bb41500..c372b985a 100644 --- a/lib/core/bindings/initial_binding.dart +++ b/lib/core/bindings/initial_binding.dart @@ -10,6 +10,7 @@ import '../../core/utils/logger.dart'; import '../../data/services/bluetooth_manager.dart'; import '../../data/services/ble_manager.dart'; import '../../data/services/account_service.dart'; +import '../../data/services/version_update_service.dart'; /// 初始绑定,用于管理全局依赖 class InitialBinding extends Bindings { @@ -46,6 +47,9 @@ class InitialBinding extends Bindings { // 账号管理服务 Get.lazyPut(() => AccountService(), fenix: true); + // 版本更新服务 + Get.lazyPut(() => VersionUpdateService(), fenix: true); + // 杰理蓝牙服务 // // 语言管理器(需要最先初始化) diff --git a/lib/data/models/channel_app_model.dart b/lib/data/models/channel_app_model.dart new file mode 100644 index 000000000..bddbf9f39 --- /dev/null +++ b/lib/data/models/channel_app_model.dart @@ -0,0 +1,151 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'channel_app_model.g.dart'; + +/// 渠道应用信息响应模型 +@JsonSerializable() +class ChannelAppResponse { + @JsonKey(name: 'code') + final int code; + + @JsonKey(name: 'data') + final ChannelAppData? data; + + @JsonKey(name: 'msg') + final String msg; + + const ChannelAppResponse({ + required this.code, + this.data, + required this.msg, + }); + + factory ChannelAppResponse.fromJson(Map json) => + _$ChannelAppResponseFromJson(json); + + Map toJson() => _$ChannelAppResponseToJson(this); +} + +/// 渠道应用数据模型 +@JsonSerializable() +class ChannelAppData { + @JsonKey(name: 'app') + final AppInfo app; + + const ChannelAppData({ + required this.app, + }); + + factory ChannelAppData.fromJson(Map json) => + _$ChannelAppDataFromJson(json); + + Map toJson() => _$ChannelAppDataToJson(this); +} + +/// 应用信息模型 +@JsonSerializable() +class AppInfo { + /// 下载地址 + @JsonKey(name: 'address') + final String address; + + /// 渠道标识 + @JsonKey(name: 'channel') + final String channel; + + /// 应用描述 + @JsonKey(name: 'description') + final String description; + + /// 最新版本号 + @JsonKey(name: 'version') + final String version; + + const AppInfo({ + required this.address, + required this.channel, + required this.description, + required this.version, + }); + + factory AppInfo.fromJson(Map json) => + _$AppInfoFromJson(json); + + Map toJson() => _$AppInfoToJson(this); +} + +/// 版本更新信息模型 +class VersionUpdateInfo { + /// 是否有新版本 + final bool hasUpdate; + + /// 当前版本 + final String currentVersion; + + /// 最新版本 + final String latestVersion; + + /// 应用信息 + final AppInfo? appInfo; + + /// 更新类型 + final UpdateType updateType; + + const VersionUpdateInfo({ + required this.hasUpdate, + required this.currentVersion, + required this.latestVersion, + this.appInfo, + required this.updateType, + }); +} + +/// 更新类型枚举 +enum UpdateType { + /// 无需更新 + none, + /// 可选更新 + optional, + /// 强制更新 + force, +} + +/// 应用商店类型枚举 +enum AppStoreType { + /// iOS App Store + appStore, + /// Google Play + googlePlay, + /// 华为应用市场 + huawei, + /// 小米应用商店 + xiaomi, + /// OPPO软件商店 + oppo, + /// vivo应用商店 + vivo, + /// 荣耀应用市场 + honor, + /// 官网下载 + official, + /// 未知 + unknown, +} + +/// 平台信息模型 +class PlatformInfo { + /// 平台类型 + final String platform; + + /// 应用商店类型 + final AppStoreType storeType; + + /// 渠道标识 + final String channel; + + const PlatformInfo({ + required this.platform, + required this.storeType, + required this.channel, + }); +} diff --git a/lib/data/models/channel_app_model.g.dart b/lib/data/models/channel_app_model.g.dart new file mode 100644 index 000000000..7d5e6aa6b --- /dev/null +++ b/lib/data/models/channel_app_model.g.dart @@ -0,0 +1,47 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'channel_app_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +ChannelAppResponse _$ChannelAppResponseFromJson(Map json) => + ChannelAppResponse( + code: (json['code'] as num).toInt(), + data: json['data'] == null + ? null + : ChannelAppData.fromJson(json['data'] as Map), + msg: json['msg'] as String, + ); + +Map _$ChannelAppResponseToJson(ChannelAppResponse instance) => + { + 'code': instance.code, + 'data': instance.data, + 'msg': instance.msg, + }; + +ChannelAppData _$ChannelAppDataFromJson(Map json) => + ChannelAppData( + app: AppInfo.fromJson(json['app'] as Map), + ); + +Map _$ChannelAppDataToJson(ChannelAppData instance) => + { + 'app': instance.app, + }; + +AppInfo _$AppInfoFromJson(Map json) => AppInfo( + address: json['address'] as String, + channel: json['channel'] as String, + description: json['description'] as String, + version: json['version'] as String, + ); + +Map _$AppInfoToJson(AppInfo instance) => { + 'address': instance.address, + 'channel': instance.channel, + 'description': instance.description, + 'version': instance.version, + }; diff --git a/lib/data/services/account_service.dart b/lib/data/services/account_service.dart index 6c01ac65c..cbbe49de1 100644 --- a/lib/data/services/account_service.dart +++ b/lib/data/services/account_service.dart @@ -25,7 +25,7 @@ class AccountService extends GetxService { Future clearUserData() async { try { // 清理所有存储 - await _storage.erase(); + //await _storage.erase(); // 或者逐个清理关键存储项 await _storage.write("logintoken", null); await _storage.write("user_info", null); @@ -38,6 +38,18 @@ class AccountService extends GetxService { } } + Future clearAllUserData() async { + // 清理所有用户数据 + try { + await _storage.erase(); + logger.d('所有用户数据清理成功'); + return true; + } catch (e) { + logger.e('用户数据清理失败: $e'); + return false; + } + } + /// 退出第三方登录 /// /// 返回操作是否成功 @@ -105,7 +117,8 @@ class AccountService extends GetxService { } // 清理本地数据 - final dataCleaned = await clearUserData(); + final dataCleaned = await clearAllUserData(); // 一次性清除所有数据 + if (!dataCleaned) { return AccountOperationResult( success: false, diff --git a/lib/data/services/network/api.dart b/lib/data/services/network/api.dart index e83cbe4e5..07e265b4e 100644 --- a/lib/data/services/network/api.dart +++ b/lib/data/services/network/api.dart @@ -117,4 +117,13 @@ class Api { params: params, ); } + + //获取渠道包信息 + static getChannelApp([params]) { + return DioManager().request( + NWMethod.post, + '/api/home/user_getchannelapp', + params: params, + ); + } } diff --git a/lib/data/services/version_update_service.dart b/lib/data/services/version_update_service.dart new file mode 100644 index 000000000..f0bf31f05 --- /dev/null +++ b/lib/data/services/version_update_service.dart @@ -0,0 +1,315 @@ +import 'dart:io'; +import 'package:flutter/foundation.dart'; +import 'package:get/get.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:logger/logger.dart'; +import 'package:device_info_plus/device_info_plus.dart'; +import '../models/channel_app_model.dart'; +import 'network/api.dart'; + +/// 版本更新服务类 +class VersionUpdateService extends GetxService { + static VersionUpdateService get to => Get.find(); + + final Logger _logger = Logger(); + + /// 获取当前应用版本信息 + Future getCurrentVersion() async { + try { + final packageInfo = await PackageInfo.fromPlatform(); + return '${packageInfo.version}+${packageInfo.buildNumber}'; + } catch (e) { + _logger.e('获取当前版本信息失败: $e'); + return '1.0.0'; + } + } + + /// 获取当前平台信息 + Future getCurrentPlatformInfo() async { + if (kIsWeb) { + return const PlatformInfo( + platform: 'web', + storeType: AppStoreType.official, + channel: '官方网站', + ); + } + + if (Platform.isIOS) { + return const PlatformInfo( + platform: 'ios', + storeType: AppStoreType.appStore, + channel: 'App Store', + ); + } + + if (Platform.isAndroid) { + // 根据设备品牌判断应用商店类型 + return await getAndroidStoreInfoByBrand(); + } + + return const PlatformInfo( + platform: 'unknown', + storeType: AppStoreType.unknown, + channel: '未知', + ); + } + + /// 根据设备品牌获取应用商店信息 + Future getAndroidStoreInfoByBrand() async { + try { + final deviceInfo = DeviceInfoPlugin(); + final androidInfo = await deviceInfo.androidInfo; + final brand = androidInfo.brand.toLowerCase(); + + return const PlatformInfo( + platform: 'android', + storeType: AppStoreType.official, + channel: '官方内测', + ); + + // switch (brand) { + // case 'huawei': + // case 'honor': + // return PlatformInfo( + // platform: 'android', + // storeType: + // brand == 'huawei' ? AppStoreType.huawei : AppStoreType.honor, + // channel: brand == 'huawei' ? '华为应用市场' : '荣耀应用市场', + // ); + // case 'xiaomi': + // case 'redmi': + // return const PlatformInfo( + // platform: 'android', + // storeType: AppStoreType.xiaomi, + // channel: '小米应用商店', + // ); + // case 'oppo': + // case 'oneplus': + // return const PlatformInfo( + // platform: 'android', + // storeType: AppStoreType.oppo, + // channel: 'OPPO软件商店', + // ); + // case 'vivo': + // case 'iqoo': + // return const PlatformInfo( + // platform: 'android', + // storeType: AppStoreType.vivo, + // channel: 'vivo应用商店', + // ); + // default: + // return const PlatformInfo( + // platform: 'android', + // storeType: AppStoreType.googlePlay, + // channel: 'Google Play', + // ); + // } + } catch (e) { + _logger.e('获取Android设备信息失败: $e'); + return const PlatformInfo( + platform: 'android', + storeType: AppStoreType.googlePlay, + channel: 'Google Play', + ); + } + } + + /// 检查版本更新 + Future checkForUpdate() async { + try { + final currentVersion = await getCurrentVersion(); + final platformInfo = await getCurrentPlatformInfo(); + + // 调用后端接口获取最新版本信息 + final response = + await Api.getChannelApp({'channel': platformInfo.channel}); + + print('获取渠道包信息:$response'); + + if (response == null || !response.containsKey('app')) { + return VersionUpdateInfo( + hasUpdate: false, + currentVersion: currentVersion, + latestVersion: currentVersion, + updateType: UpdateType.none, + ); + } + + //final channelAppResponse = ChannelAppResponse.fromJson(response); + // 直接从response中获取app信息 + final appInfo = AppInfo.fromJson(response['app']); + + // if (channelAppResponse.code != 0 || channelAppResponse.data == null) { + // _logger.w('获取版本信息失败: ${channelAppResponse.msg}'); + // return VersionUpdateInfo( + // hasUpdate: false, + // currentVersion: currentVersion, + // latestVersion: currentVersion, + // updateType: UpdateType.none, + // ); + // } + + // final appInfo = channelAppResponse.data!.app; + final hasUpdate = _compareVersions(currentVersion, appInfo.version); + + return VersionUpdateInfo( + hasUpdate: hasUpdate, + currentVersion: currentVersion, + latestVersion: appInfo.version, + appInfo: appInfo, + updateType: hasUpdate ? UpdateType.optional : UpdateType.none, + ); + } catch (e) { + _logger.e('检查版本更新失败: $e'); + final currentVersion = await getCurrentVersion(); + return VersionUpdateInfo( + hasUpdate: false, + currentVersion: currentVersion, + latestVersion: currentVersion, + updateType: UpdateType.none, + ); + } + } + + /// 比较版本号 + /// 返回true表示需要更新(远程版本更高) + bool _compareVersions(String current, String latest) { + // 拆分版本号与构建号 + final currentParts = current.split('+'); + final latestParts = latest.split('+'); + + // 解析版本号 (major.minor.patch) + final currentVersions = currentParts[0].split('.').map(int.parse).toList(); + final latestVersions = latestParts[0].split('.').map(int.parse).toList(); + + // 逐级比较版本号 + for (var i = 0; i < currentVersions.length; i++) { + if (latestVersions[i] > currentVersions[i]) return true; + if (latestVersions[i] < currentVersions[i]) return false; + } + + // 版本号相同则比较构建号 + if (latestParts.length > 1 && currentParts.length > 1) { + return int.parse(latestParts[1]) > int.parse(currentParts[1]); + } + return false; + } + + /// 解析版本号字符串为数字数组 + List _parseVersion(String version) { + final parts = version.split('.'); + final result = []; + + for (int i = 0; i < 3; i++) { + if (i < parts.length) { + result.add(int.tryParse(parts[i]) ?? 0); + } else { + result.add(0); + } + } + + return result; + } + + /// 执行更新操作 + Future performUpdate(VersionUpdateInfo updateInfo) async { + if (!updateInfo.hasUpdate || updateInfo.appInfo == null) { + return false; + } + + final platformInfo = await getCurrentPlatformInfo(); + + try { + switch (platformInfo.storeType) { + case AppStoreType.appStore: + return await _openAppStore(); + case AppStoreType.googlePlay: + return await _openGooglePlay(); + case AppStoreType.huawei: + return await _openHuaweiStore(); + case AppStoreType.xiaomi: + return await _openXiaomiStore(); + case AppStoreType.oppo: + return await _openOppoStore(); + case AppStoreType.vivo: + return await _openVivoStore(); + case AppStoreType.honor: + return await _openHonorStore(); + case AppStoreType.official: + return await _openOfficialDownload(updateInfo.appInfo!.address); + default: + return await _openOfficialDownload(updateInfo.appInfo!.address); + } + } catch (e) { + _logger.e('执行更新失败: $e'); + return false; + } + } + + /// 打开iOS App Store + Future _openAppStore() async { + // 这里需要替换为实际的App Store ID + const appStoreUrl = 'https://apps.apple.com/app/id你的应用ID'; + return await _launchUrl(appStoreUrl); + } + + /// 打开Google Play + Future _openGooglePlay() async { + // 这里需要替换为实际的包名 + const packageName = 'com.yourcompany.deepvoice'; + const playStoreUrl = + 'https://play.google.com/store/apps/details?id=$packageName'; + return await _launchUrl(playStoreUrl); + } + + /// 打开华为应用市场 + Future _openHuaweiStore() async { + const huaweiStoreUrl = 'appmarket://details?id=com.yourcompany.deepvoice'; + return await _launchUrl(huaweiStoreUrl); + } + + /// 打开小米应用商店 + Future _openXiaomiStore() async { + const xiaomiStoreUrl = 'mimarket://details?id=com.yourcompany.deepvoice'; + return await _launchUrl(xiaomiStoreUrl); + } + + /// 打开OPPO软件商店 + Future _openOppoStore() async { + const oppoStoreUrl = + 'oppomarket://details?packagename=com.yourcompany.deepvoice'; + return await _launchUrl(oppoStoreUrl); + } + + /// 打开vivo应用商店 + Future _openVivoStore() async { + const vivoStoreUrl = 'vivomarket://details?id=com.yourcompany.deepvoice'; + return await _launchUrl(vivoStoreUrl); + } + + /// 打开荣耀应用市场 + Future _openHonorStore() async { + const honorStoreUrl = 'honormarket://details?id=com.yourcompany.deepvoice'; + return await _launchUrl(honorStoreUrl); + } + + /// 打开官网下载 + Future _openOfficialDownload(String downloadUrl) async { + return await _launchUrl(downloadUrl); + } + + /// 启动URL + Future _launchUrl(String url) async { + try { + final uri = Uri.parse(url); + if (await canLaunchUrl(uri)) { + return await launchUrl(uri, mode: LaunchMode.externalApplication); + } + return false; + } catch (e) { + _logger.e('启动URL失败: $e'); + return false; + } + } +} diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index c62f3ff26..add1f53b4 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -91,6 +91,17 @@ class AgentController extends GetxController { _loadTtsPreference(); //加载TTS偏好设置 _loadChatHistory(); //加载聊天历史记录 _subscribeToEvents(); //订阅代理服务事件 + _initMusicStatus(); //初始化音乐状态 + } + + // 初始化音乐播放状态 + Future _initMusicStatus() async { + try { + await musicStatus(); + Logger.d('AgentController', '音乐状态初始化完成'); + } catch (e) { + Logger.e('AgentController', '音乐状态初始化失败: $e'); + } } @override @@ -818,85 +829,161 @@ class AgentController extends GetxController { useImagePrompt('请翻译这张图片中的文字'); } - // 播放音乐 - void musicStatus() async { - var status = await _musiceManager.getStatus(); - // debugPrint('获取当前音乐播放状态 $status'); - isPlaying.value = status['isPlaying']; - currentId.value = status['id'] ?? 0; + // 获取音乐播放状态 + Future musicStatus() async { + try { + var status = await _musiceManager.getStatus(); + isPlaying.value = status['isPlaying'] ?? false; + currentId.value = status['id'] ?? 0; + Logger.d('AgentController', + '音乐状态更新: playing=${isPlaying.value}, url=${currentId.value}'); + } catch (e) { + Logger.e('AgentController', '获取音乐状态失败: $e'); + } } // 播放音乐 - void musicPlay(Map song) async { - final id = song['id'] ?? ""; - final timestampMs = song['ts'] ?? 0; - // 当前时间 - final now = DateTime.now(); - // 将时间戳转换为 DateTime - final tsTime = DateTime.fromMillisecondsSinceEpoch(timestampMs).add( - const Duration(minutes: 10), // 转换为北京时间 - ); - // 判断是否过期 - if (tsTime.isBefore(now)) { - //获取新的连接 - var response = await Api.getMusicUrl({ - "ids": [id] - }); - // 2. 将响应数据转换为User对象 - try { - print('getMusicUrl 响应数据: ${response.toString()}'); // 调试输出原始数据 - final urldata = response['urls'][0] as Map; - song["url"] = urldata['url'] ?? ''; - } catch (e) { - Logger.e(TAG, '转换响应数据失败: $e'); - return; + Future musicPlay(Map song) async { + try { + Logger.d('AgentController', '开始播放音乐: ${song['title'] ?? song['name']}'); + + // 立即更新UI状态,提供即时反馈 + isPlaying.value = true; + currentId.value = song['id'] ?? 0; + + // 重置进度状态 + currentPosition.value = 0; + duration.value = 0; + progress.value = 0.0; + + final id = song['id'] ?? ""; + final timestampMs = song['ts'] ?? 0; + + // 检查URL是否需要刷新(只有当有时间戳时才检查) + if (timestampMs > 0) { + final now = DateTime.now(); + final tsTime = DateTime.fromMillisecondsSinceEpoch(timestampMs).add( + const Duration(minutes: 10), // 转换为北京时间 + ); + + // 判断是否过期 + if (tsTime.isBefore(now)) { + Logger.d('AgentController', '音乐URL已过期,正在获取新URL...'); + try { + var response = await Api.getMusicUrl({ + "ids": [id] + }); + print('getMusicUrl 响应数据: ${response.toString()}'); // 调试输出原始数据 + final urldata = response['urls'][0] as Map; + song["url"] = urldata['url'] ?? ''; + currentId.value = song['id'] ?? 0; + Logger.d('AgentController', '获取新URL成功'); + } catch (e) { + Logger.e('AgentController', '获取新URL失败: $e'); + // 如果获取新URL失败,尝试使用原URL + } + } } - } - _musiceManager.play(song); - isPlaying.value = true; - currentId.value = song['id'] ?? 0; - _startProgressTimer(); + // 转换为MusicManager期望的格式 + final musicData = { + 'id': song['id']?.toString() ?? '', + 'url': song['url']?.toString() ?? '', + 'title': song['title']?.toString() ?? song['name']?.toString() ?? '', + 'artist': + song['artist']?.toString() ?? song['sgener']?.toString() ?? '', + 'coverUrl': + song['coverUrl']?.toString() ?? song['image']?.toString() ?? '', + }; + + // 调用音乐管理器播放 + await _musiceManager.play(musicData); + + // 启动进度定时器 + _startProgressTimer(); + + Logger.d('AgentController', '音乐播放启动成功'); + } catch (e) { + Logger.e('AgentController', '播放音乐失败: $e'); + // 发生错误时重置状态 + isPlaying.value = false; + currentId.value = 0; + } } - // 播放音乐 - void musicPause() { - _musiceManager.pause(); - isPlaying.value = false; - _stopProgressTimer(); + // 暂停音乐 + Future musicPause() async { + try { + Logger.d('AgentController', '暂停音乐播放'); + await _musiceManager.pause(); + isPlaying.value = false; + _stopProgressTimer(); + } catch (e) { + Logger.e('AgentController', '暂停音乐失败: $e'); + } } - void musicResume() { - _musiceManager.resume(); - isPlaying.value = true; - _startProgressTimer(); + // 恢复音乐播放 + Future musicResume() async { + try { + Logger.d('AgentController', '恢复音乐播放'); + await _musiceManager.resume(); + isPlaying.value = true; + _startProgressTimer(); + } catch (e) { + Logger.e('AgentController', '恢复音乐失败: $e'); + } } // 启动进度更新定时器 void _startProgressTimer() { _stopProgressTimer(); // 先停止现有定时器 - _progressTimer = Timer.periodic(const Duration(seconds: 1), (timer) { - if (isPlaying.value) { + + Logger.d('AgentController', '启动播放进度定时器'); + _progressTimer = Timer.periodic(const Duration(milliseconds: 500), (timer) { + if (isPlaying.value && currentId.value != 0) { updateMusicProgress(); - } else { + } else if (!isPlaying.value) { + // 如果不在播放状态,停止定时器 timer.cancel(); + _progressTimer = null; + Logger.d('AgentController', '播放已停止,取消进度定时器'); } }); } // 停止进度更新定时器 void _stopProgressTimer() { - _progressTimer?.cancel(); - _progressTimer = null; + if (_progressTimer != null) { + Logger.d('AgentController', '停止播放进度定时器'); + _progressTimer?.cancel(); + _progressTimer = null; + } } // 获取播放进度 Future updateMusicProgress() async { try { + if (!isPlaying.value || currentId.value == 0) { + return; // 如果没有在播放,直接返回 + } + var progressInfo = await _musiceManager.getProgress(); - currentPosition.value = progressInfo['currentPosition'] ?? 0; - duration.value = progressInfo['duration'] ?? 0; - progress.value = progressInfo['progress'] ?? 0.0; + final newPosition = progressInfo['currentPosition'] ?? 0; + final newDuration = progressInfo['duration'] ?? 0; + final newProgress = progressInfo['progress'] ?? 0.0; + + // 只有当值发生变化时才更新,避免不必要的UI刷新 + if (currentPosition.value != newPosition) { + currentPosition.value = newPosition; + } + if (duration.value != newDuration) { + duration.value = newDuration; + } + if ((progress.value - newProgress).abs() > 0.001) { + // 避免浮点数精度问题 + progress.value = newProgress; + } } catch (e) { Logger.e('AgentController', '获取播放进度失败: $e'); } diff --git a/lib/modules/agent/views/music_card.dart b/lib/modules/agent/views/music_card.dart index ecdc7bbcd..f72af95be 100644 --- a/lib/modules/agent/views/music_card.dart +++ b/lib/modules/agent/views/music_card.dart @@ -95,19 +95,27 @@ Widget buildMusicItem(BuildContext context, int id, String name, String url, backgroundColor: Colors.grey, size: 36.0, strokeWidth: 2.0, - onPlayPause: () { - if (controller.isPlaying.value) { - if (controller.currentId.value == id) { - controller.musicPause(); - } else { - controller.musicPlay(song); - } - } else { - if (controller.currentId.value == id) { - controller.musicResume(); + onPlayPause: () async { + try { + if (controller.isPlaying.value) { + if (controller.currentId.value == id) { + // 当前歌曲正在播放,暂停它 + await controller.musicPause(); + } else { + // 其他歌曲正在播放,播放这首歌 + await controller.musicPlay(song); + } } else { - controller.musicPlay(song); + if (controller.currentId.value == id) { + // 当前歌曲已暂停,恢复播放 + await controller.musicResume(); + } else { + // 播放新歌曲 + await controller.musicPlay(song); + } } + } catch (e) { + // 静默处理错误,避免影响用户体验 } }, onSeek: isThisPlaying diff --git a/lib/modules/common/widgets/version_update_dialog.dart b/lib/modules/common/widgets/version_update_dialog.dart new file mode 100644 index 000000000..a52077bdf --- /dev/null +++ b/lib/modules/common/widgets/version_update_dialog.dart @@ -0,0 +1,226 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import '../../../data/models/channel_app_model.dart'; +import '../../../data/services/version_update_service.dart'; + +/// 版本更新提示对话框 +class VersionUpdateDialog extends StatelessWidget { + final VersionUpdateInfo updateInfo; + final VoidCallback? onCancel; + final VoidCallback? onUpdate; + + const VersionUpdateDialog({ + super.key, + required this.updateInfo, + this.onCancel, + this.onUpdate, + }); + + @override + Widget build(BuildContext context) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16.r), + ), + child: Container( + width: 320.w, + padding: EdgeInsets.all(24.w), + decoration: BoxDecoration( + color: isDarkMode ? Colors.grey[900] : Colors.white, + borderRadius: BorderRadius.circular(16.r), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 标题图标 + Container( + width: 64.w, + height: 64.w, + decoration: BoxDecoration( + color: Colors.blue.withOpacity(0.1), + borderRadius: BorderRadius.circular(32.r), + ), + child: Icon( + Icons.system_update, + size: 32.sp, + color: Colors.blue, + ), + ), + + SizedBox(height: 16.h), + + // 标题 + Text( + '发现新版本', + style: TextStyle( + fontSize: 20.sp, + fontWeight: FontWeight.bold, + color: isDarkMode ? Colors.white : Colors.black87, + ), + ), + + SizedBox(height: 8.h), + + // 版本信息 + Text( + '${updateInfo.currentVersion} → ${updateInfo.latestVersion}', + style: TextStyle( + fontSize: 14.sp, + color: isDarkMode ? Colors.grey[400] : Colors.grey[600], + ), + ), + + SizedBox(height: 16.h), + + // 更新描述 + if (updateInfo.appInfo?.description.isNotEmpty == true) ...[ + Container( + width: double.infinity, + padding: EdgeInsets.all(12.w), + decoration: BoxDecoration( + color: isDarkMode ? Colors.grey[800] : Colors.grey[100], + borderRadius: BorderRadius.circular(8.r), + ), + child: Text( + updateInfo.appInfo!.description, + style: TextStyle( + fontSize: 14.sp, + color: isDarkMode ? Colors.grey[300] : Colors.grey[700], + height: 1.4, + ), + ), + ), + SizedBox(height: 20.h), + ], + + // 按钮区域 + Row( + children: [ + // 取消按钮(仅在非强制更新时显示) + if (updateInfo.updateType != UpdateType.force) ...[ + Expanded( + child: TextButton( + onPressed: () { + Get.back(); + onCancel?.call(); + }, + style: TextButton.styleFrom( + padding: EdgeInsets.symmetric(vertical: 12.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.r), + side: BorderSide( + color: isDarkMode ? Colors.grey[600]! : Colors.grey[300]!, + ), + ), + ), + child: Text( + '稍后提醒', + style: TextStyle( + fontSize: 16.sp, + color: isDarkMode ? Colors.grey[400] : Colors.grey[600], + ), + ), + ), + ), + SizedBox(width: 12.w), + ], + + // 更新按钮 + Expanded( + child: ElevatedButton( + onPressed: () async { + Get.back(); + await _handleUpdate(); + onUpdate?.call(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + padding: EdgeInsets.symmetric(vertical: 12.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.r), + ), + elevation: 0, + ), + child: Text( + _getUpdateButtonText(), + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ], + ), + ], + ), + ), + ); + } + + /// 获取更新按钮文本 + String _getUpdateButtonText() { + switch (updateInfo.updateType) { + case UpdateType.force: + return '立即更新'; + case UpdateType.optional: + return '立即更新'; + default: + return '检查更新'; + } + } + + /// 处理更新操作 + Future _handleUpdate() async { + try { + // 显示加载提示 + Get.dialog( + const Center( + child: CircularProgressIndicator(), + ), + barrierDismissible: false, + ); + + final versionService = VersionUpdateService.to; + final success = await versionService.performUpdate(updateInfo); + + // 关闭加载提示 + Get.back(); + + if (!success) { + Get.snackbar( + '更新失败', + '无法打开应用商店,请手动前往应用商店更新', + snackPosition: SnackPosition.TOP, + backgroundColor: Colors.red.withOpacity(0.8), + colorText: Colors.white, + ); + } + } catch (e) { + // 关闭加载提示 + if (Get.isDialogOpen == true) { + Get.back(); + } + + Get.snackbar( + '更新失败', + '更新过程中发生错误,请稍后重试', + snackPosition: SnackPosition.TOP, + backgroundColor: Colors.red.withOpacity(0.8), + colorText: Colors.white, + ); + } + } + + /// 显示版本更新对话框 + static Future show(VersionUpdateInfo updateInfo) async { + await Get.dialog( + VersionUpdateDialog(updateInfo: updateInfo), + barrierDismissible: updateInfo.updateType != UpdateType.force, + ); + } +} diff --git a/lib/modules/music/views/music_playlist_view.dart b/lib/modules/music/views/music_playlist_view.dart index 65fecb2eb..37a35fbf2 100644 --- a/lib/modules/music/views/music_playlist_view.dart +++ b/lib/modules/music/views/music_playlist_view.dart @@ -175,19 +175,19 @@ class MusicPlaylistView extends StatelessWidget { backgroundColor: Colors.grey, size: 40.0, strokeWidth: 2.5, - onPlayPause: () { - if (controller.isPlaying.value) { - if (controller.currentId.value == id) { - controller.musicPause(); - } else { - controller.musicPlay(song); - } - } else { - if (controller.currentId.value == id) { - controller.musicResume(); + onPlayPause: () async { + try { + if (controller.isPlaying.value) { + if (controller.currentId.value == id) { + await controller.musicPause(); + } else { + await controller.musicPlay(song); + } } else { controller.musicPlay(song); } + } catch (e) { + // 静默处理错误 } }, onSeek: isThisPlaying diff --git a/lib/modules/settings/controllers/settings_controller.dart b/lib/modules/settings/controllers/settings_controller.dart index 36fd66fe3..285f687a0 100644 --- a/lib/modules/settings/controllers/settings_controller.dart +++ b/lib/modules/settings/controllers/settings_controller.dart @@ -16,6 +16,8 @@ import 'package:package_info_plus/package_info_plus.dart'; import 'package:permission_handler/permission_handler.dart'; import 'dart:async'; import 'package:app_settings/app_settings.dart'; +import '../../../data/services/version_update_service.dart'; +import '../../common/widgets/version_update_dialog.dart'; class SettingsController extends GetxController { // 语言设置 @@ -490,4 +492,50 @@ class SettingsController extends GetxController { } } } + + /// 手动检查版本更新 + Future checkForUpdate() async { + try { + // 显示加载提示 + Get.dialog( + const Center( + child: CircularProgressIndicator(), + ), + barrierDismissible: false, + ); + + final versionService = VersionUpdateService.to; + final updateInfo = await versionService.checkForUpdate(); + + // 关闭加载提示 + Get.back(); + + if (updateInfo.hasUpdate) { + // 显示更新对话框 + await VersionUpdateDialog.show(updateInfo); + } else { + // 显示已是最新版本的提示 + Get.snackbar( + '检查更新', + '当前已是最新版本 ${updateInfo.currentVersion}', + snackPosition: SnackPosition.TOP, + backgroundColor: Colors.green.withOpacity(0.8), + colorText: Colors.white, + ); + } + } catch (e) { + // 关闭加载提示 + if (Get.isDialogOpen == true) { + Get.back(); + } + + Get.snackbar( + '检查更新失败', + '网络连接异常,请稍后重试', + snackPosition: SnackPosition.TOP, + backgroundColor: Colors.red.withOpacity(0.8), + colorText: Colors.white, + ); + } + } } diff --git a/lib/modules/settings/views/settings_view.dart b/lib/modules/settings/views/settings_view.dart index fc37c5bad..35bb6c90b 100644 --- a/lib/modules/settings/views/settings_view.dart +++ b/lib/modules/settings/views/settings_view.dart @@ -880,7 +880,8 @@ class SettingsView extends GetView { ), InkWell( onTap: () { - // 在这里添加检查更新的逻辑 + // 调用检查更新方法 + controller.checkForUpdate(); }, child: Container( constraints: BoxConstraints( diff --git a/lib/modules/splash/controllers/splash_controller.dart b/lib/modules/splash/controllers/splash_controller.dart index e53f146dc..de6a1d09e 100644 --- a/lib/modules/splash/controllers/splash_controller.dart +++ b/lib/modules/splash/controllers/splash_controller.dart @@ -8,6 +8,8 @@ import '../../../data/models/user_Info.dart'; import '../../../routes/app_routes.dart'; import '../../../core/themes/theme_service.dart'; import '../../../data/services/network/api.dart'; +import '../../../data/services/version_update_service.dart'; +import '../../../modules/common/widgets/version_update_dialog.dart'; class SplashController extends GetxController { final GetStorage _storage = GetStorage(); @@ -73,6 +75,9 @@ class SplashController extends GetxController { // await _loadImages(); // await _initDatabase(); + // 检查版本更新(在后台进行,不阻塞启动流程) + _checkVersionUpdate(); + _isResourceLoaded.value = true; _checkNavigateCondition(); } catch (e) { @@ -151,4 +156,23 @@ class SplashController extends GetxController { return false; } } + + /// 检查版本更新(后台执行,不阻塞启动流程) + void _checkVersionUpdate() { + // 延迟执行版本检查,避免影响启动速度 + Timer(const Duration(seconds: 1), () async { + try { + final versionService = VersionUpdateService.to; + final updateInfo = await versionService.checkForUpdate(); + + if (updateInfo.hasUpdate) { + // 显示更新对话框 + await VersionUpdateDialog.show(updateInfo); + } + } catch (e) { + print('版本检查失败: $e'); + // 版本检查失败不影响用户使用,静默处理 + } + }); + } } diff --git a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPSubClient.kt b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPSubClient.kt index b7b3adbe2..4ea8d1b44 100644 --- a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPSubClient.kt +++ b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPSubClient.kt @@ -373,18 +373,18 @@ class MCPSubClient( isConnected = false stopHeartbeat() - if (retryCount < maxRetryAttempts) { - retryCount++ - currentReconnectDelay = minOf(currentReconnectDelay * 2, maxReconnectDelay) - - Log.w(TAG, "连接失败,将在 ${currentReconnectDelay}ms 后尝试重连 (尝试 $retryCount/$maxRetryAttempts)") - - delay(currentReconnectDelay) - connect() - } else { - Log.e(TAG, "已达到最大重试次数($maxRetryAttempts),停止重连") - // 可以在这里添加通知或回调,告知上层连接彻底失败 - } +// if (retryCount < maxRetryAttempts) { +// retryCount++ +// currentReconnectDelay = minOf(currentReconnectDelay * 2, maxReconnectDelay) +// +// Log.w(TAG, "连接失败,将在 ${currentReconnectDelay}ms 后尝试重连 (尝试 $retryCount/$maxRetryAttempts)") +// +// delay(currentReconnectDelay) +// connect() +// } else { +// Log.e(TAG, "已达到最大重试次数($maxRetryAttempts),停止重连") +// // 可以在这里添加通知或回调,告知上层连接彻底失败 +// } } /** * 关闭连接 diff --git a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt index 80820bece..517356ff9 100644 --- a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt +++ b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt @@ -1,12 +1,17 @@ package com.yunqiinnovation.chat_api +import android.Manifest +import android.content.ContentUris +import android.content.ContentValues import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.location.LocationManager import android.net.Uri import android.os.Build +import android.provider.AlarmClock import android.provider.CalendarContract +import android.provider.ContactsContract import android.telephony.SmsManager import android.util.Log import androidx.core.content.ContextCompat @@ -295,28 +300,108 @@ private class SendTextMessageHandler(private val context: Context?) : FunctionHa * 拨打电话处理器 */ private class MakePhoneCallHandler(private val context: Context?) : FunctionHandler { + + /** + * 根据联系人名称获取电话号码 + * @param contact 联系人名称或电话号码 + * @return 电话号码 + */ + private fun getPhoneNumberForContact(contact: String): String { + // 如果输入的是电话号码(简单判断,包含数字和特殊符号) + if (contact.matches(Regex("^[+\\d\\s\\-()]*\$")) && contact.any { it.isDigit() }) { + // 移除非数字字符 + return contact.replace(Regex("[^+\\d]"), "") + } + + // 检查读取联系人权限 + if (!checkPermissionAndGuide(context!!, Manifest.permission.READ_CONTACTS, "读取联系人", "访问联系人")) { + return "" + } + + // 从联系人数据库查询 + try { + val contentResolver = context.contentResolver + val uri = Uri.withAppendedPath( + ContactsContract.Contacts.CONTENT_FILTER_URI, + Uri.encode(contact) + ) + + val projection = arrayOf( + ContactsContract.Contacts._ID, + ContactsContract.Contacts.DISPLAY_NAME, + ContactsContract.Contacts.HAS_PHONE_NUMBER + ) + + contentResolver.query(uri, projection, null, null, null)?.use { cursor -> + if (cursor.moveToFirst()) { + val hasPhoneNumber = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.HAS_PHONE_NUMBER)) + + if (hasPhoneNumber > 0) { + val contactId = cursor.getString(cursor.getColumnIndexOrThrow( + ContactsContract.Contacts._ID)) + val phoneUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI + val phoneProjection = arrayOf(ContactsContract.CommonDataKinds.Phone.NUMBER) + val phoneSelection = "${ContactsContract.CommonDataKinds.Phone.CONTACT_ID} = ?" + val phoneSelectionArgs = arrayOf(contactId) + + contentResolver?.query(phoneUri, phoneProjection, phoneSelection, phoneSelectionArgs, null)?.use { phoneCursor -> + if (phoneCursor.moveToFirst()) { + return phoneCursor.getString(phoneCursor.getColumnIndexOrThrow( + ContactsContract.CommonDataKinds.Phone.NUMBER)) + } + } + } + } + } + } catch (e: Exception) { + Log.e("ChatApi", "查询联系人信息失败: ${e.message}", e) + } + + return "" + } + override suspend fun handle(arguments: Map): String { val contact = arguments["contact"] as? String if (contact == null) { return "{\"result\": \"缺少联系人参数\"}" } - return try { - val callIntent = Intent(Intent.ACTION_DIAL).apply { - data = Uri.parse("tel:$contact") - flags = Intent.FLAG_ACTIVITY_NEW_TASK + // 检查拨打电话权限 + if (!checkPermissionAndGuide(context!!,Manifest.permission.CALL_PHONE, "拨打电话", "拨打电话")) { + "{\"result\": \"需要电话权限才能拨打电话,请在权限管理中开启电话权限\"}" } - - if (context?.packageManager?.queryIntentActivities(callIntent, 0)?.isNotEmpty() == true) { - context.startActivity(callIntent) - "{\"result\": \"已发起电话呼叫\"}" - } else { - "{\"result\": \"无法拨打电话\"}" + + val phoneNumber = getPhoneNumberForContact(contact) + if (phoneNumber.isEmpty()) { + "{\"result\": \"未找到联系人信息: $contact\"}" } + + // 创建拨号意图 + val intent = Intent(Intent.ACTION_CALL) + intent.data = Uri.parse("tel:$phoneNumber") + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + context?.startActivity(intent) + "{\"result\": \"正在拨打${contact}的电话\"}" } catch (e: Exception) { - "{\"result\": \"拨打电话失败:${e.message}\"}" + Log.e("ChatApi", "拨打电话失败: ${e.message}", e) + "{\"result\": \"权限不足或者为查询到联系人\"}" } +// return try { +// val callIntent = Intent(Intent.ACTION_DIAL).apply { +// data = Uri.parse("tel:$contact") +// flags = Intent.FLAG_ACTIVITY_NEW_TASK +// } +// +// if (context?.packageManager?.queryIntentActivities(callIntent, 0)?.isNotEmpty() == true) { +// context.startActivity(callIntent) +// "{\"result\": \"已发起电话呼叫\"}" +// } else { +// "{\"result\": \"无法拨打电话\"}" +// } +// } catch (e: Exception) { +// "{\"result\": \"拨打电话失败:${e.message}\"}" +// } } } @@ -324,6 +409,140 @@ private class MakePhoneCallHandler(private val context: Context?) : FunctionHand * 设置提醒处理器 */ private class SetReminderHandler(private val context: Context?) : FunctionHandler { + + /** + * 获取默认日历ID + */ + private fun getDefaultCalendarId(): Long { + val projection = arrayOf(CalendarContract.Calendars._ID) + var calendarId = -1L + + try { + val uri = CalendarContract.Calendars.CONTENT_URI + val selection = "(${CalendarContract.Calendars.VISIBLE} = 1) AND (${CalendarContract.Calendars.IS_PRIMARY} = 1)" + context?.contentResolver?.query(uri, projection, selection, null, null)?.use { cursor -> + if (cursor.moveToFirst()) { + calendarId = cursor.getLong(0) + } else { + // 如果没有找到主日历,尝试找任何可见的日历 + context.contentResolver.query(uri, projection, "${CalendarContract.Calendars.VISIBLE} = 1", null, null)?.use { c -> + if (c.moveToFirst()) { + calendarId = c.getLong(0) + } + } + } + } + } catch (e: Exception) { + Log.e("CahtApi", "获取默认日历ID失败: ${e.message}", e) + } + + return calendarId + } + + /** + * 尝试创建闹钟提醒(备选方案) + */ + private fun tryCreateAlarm(title: String, content: String, timeStr: String): String { + try { + // 解析小时和分钟 + val timeParts = timeStr.split(" ")[1].split(":") + val hour = timeParts[0].toInt() + val minute = timeParts[1].toInt() + + val alarmIntent = Intent(AlarmClock.ACTION_SET_ALARM) + alarmIntent.putExtra(AlarmClock.EXTRA_MESSAGE, "$title: $content") + alarmIntent.putExtra(AlarmClock.EXTRA_HOUR, hour) + alarmIntent.putExtra(AlarmClock.EXTRA_MINUTES, minute) + alarmIntent.putExtra(AlarmClock.EXTRA_SKIP_UI, true) // 尝试跳过UI + alarmIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + + if (alarmIntent.resolveActivity(context!!.packageManager) != null) { + context.startActivity(alarmIntent) + return "{\"result\": \"已创建闹钟提醒: $title, 时间: $timeStr\"}" + } + } catch (e: Exception) { + Log.e("CahtApi", "创建闹钟失败: ${e.message}", e) + } + return "{\"result\": \"未找到可用的日历或闹钟应用\"}" + } + + /** + * 设置提醒 + * @param title 提醒标题 + * @param content 提醒内容(可选) + * @param timeStr 提醒时间,格式为"yyyy-MM-dd HH:mm" + * @return 操作结果 + */ + fun setReminder(title: String, content: String, timeStr: String): String { + Log.d("CahtApi", "设置提醒: $title, 时间: $timeStr") + + try { + // 解析时间字符串 + val time:Long = try { + val dateFormat = java.text.SimpleDateFormat("yyyy-MM-dd HH:mm", java.util.Locale.getDefault()) + dateFormat.parse(timeStr)?.time ?: System.currentTimeMillis() + } catch (e: Exception) { + Log.e("CahtApi", "解析时间失败: $timeStr, ${e.message}") + return "{\"result\": \"提醒时间格式无效,请使用'yyyy-MM-dd HH:mm'格式\"}" + } + + // 检查日历权限 + if (!checkPermissionAndGuide(context!!,Manifest.permission.WRITE_CALENDAR, "日历写入", "设置提醒")) { + return "{\"result\": \"需要日历权限才能创建提醒,请在权限管理中开启日历权限\"}" + } + + // 直接使用ContentProvider写入日历 + val endTime = time + 3600000 // 默认1小时后结束 + + // 获取默认日历ID + val calendarId = getDefaultCalendarId() + if (calendarId == -1L) { + Log.w("CahtApi", "未找到默认日历") + return tryCreateAlarm(title, content, timeStr) + } + + // 创建事件 + val eventValues = ContentValues().apply { + put(CalendarContract.Events.CALENDAR_ID, calendarId) + put(CalendarContract.Events.TITLE, title) + put(CalendarContract.Events.DESCRIPTION, content) + put(CalendarContract.Events.DTSTART, time) + put(CalendarContract.Events.DTEND, endTime) + put(CalendarContract.Events.HAS_ALARM, 1) + put(CalendarContract.Events.EVENT_TIMEZONE, java.util.TimeZone.getDefault().id) + } + + val eventUri = context.contentResolver.insert(CalendarContract.Events.CONTENT_URI, eventValues) + if (eventUri == null) { + Log.e("CahtApi", "插入日历事件失败") + return tryCreateAlarm(title, content, timeStr) + } + + // 获取事件ID + val eventId = ContentUris.parseId(eventUri) + + // 添加提醒(15分钟前提醒) + val reminderValues = ContentValues().apply { + put(CalendarContract.Reminders.EVENT_ID, eventId) + put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_ALERT) + put(CalendarContract.Reminders.MINUTES, 15) // 提前15分钟提醒 + } + + val reminderUri = context.contentResolver.insert(CalendarContract.Reminders.CONTENT_URI, reminderValues) + if (reminderUri != null) { + return "{\"result\": \"已成功创建提醒: $title, 时间: $timeStr\"}" + } else { + // 仍然创建了事件,但没有提醒 + return "{\"result\": \"已创建日历事件,但未设置提醒: $title\"}" + } + } catch (e: Exception) { + Log.e("CahtApi", "设置提醒失败: ${e.message}", e) + return "{\"result\": \"设置提醒失败: ${e.message}\"}" + } + } + + + override suspend fun handle(arguments: Map): String { val title = arguments["title"] as? String val content = arguments["content"] as? String ?: "" @@ -332,36 +551,36 @@ private class SetReminderHandler(private val context: Context?) : FunctionHandle if (title == null || timeString == null) { return "{\"result\": \"缺少必要参数\"}" } - - return try { - // 解析时间 - val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()) - val date = formatter.parse(timeString) - - if (date == null) { - return "{\"result\": \"时间格式错误\"}" - } - - // 创建日历事件 - val calendarIntent = Intent(Intent.ACTION_INSERT).apply { - data = CalendarContract.Events.CONTENT_URI - putExtra(CalendarContract.Events.TITLE, title) - putExtra(CalendarContract.Events.DESCRIPTION, content) - putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, date.time) - putExtra(CalendarContract.EXTRA_EVENT_END_TIME, date.time + 60 * 60 * 1000) // 默认1小时 - putExtra(CalendarContract.Events.HAS_ALARM, 1) - flags = Intent.FLAG_ACTIVITY_NEW_TASK - } - - if (context?.packageManager?.queryIntentActivities(calendarIntent, 0)?.isNotEmpty() == true) { - context.startActivity(calendarIntent) - "{\"result\": \"提醒设置成功\"}" - } else { - "{\"result\": \"无法打开日历应用\"}" - } - } catch (e: Exception) { - "{\"result\": \"设置提醒失败:${e.message}\"}" - } + return setReminder(title,content,timeString) +// return try { +// // 解析时间 +// val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()) +// val date = formatter.parse(timeString) +// +// if (date == null) { +// return "{\"result\": \"时间格式错误\"}" +// } +// +// // 创建日历事件 +// val calendarIntent = Intent(Intent.ACTION_INSERT).apply { +// data = CalendarContract.Events.CONTENT_URI +// putExtra(CalendarContract.Events.TITLE, title) +// putExtra(CalendarContract.Events.DESCRIPTION, content) +// putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, date.time) +// putExtra(CalendarContract.EXTRA_EVENT_END_TIME, date.time + 60 * 60 * 1000) // 默认1小时 +// putExtra(CalendarContract.Events.HAS_ALARM, 1) +// flags = Intent.FLAG_ACTIVITY_NEW_TASK +// } +// +// if (context?.packageManager?.queryIntentActivities(calendarIntent, 0)?.isNotEmpty() == true) { +// context.startActivity(calendarIntent) +// "{\"result\": \"提醒设置成功\"}" +// } else { +// "{\"result\": \"无法打开日历应用\"}" +// } +// } catch (e: Exception) { +// "{\"result\": \"设置提醒失败:${e.message}\"}" +// } } } diff --git a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/Utils.kt b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/Utils.kt index 0d1b90153..31b7bba05 100644 --- a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/Utils.kt +++ b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/Utils.kt @@ -1,5 +1,13 @@ package com.yunqiinnovation.chat_api +import android.app.ActivityManager +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.provider.Settings +import android.util.Log +import androidx.core.content.ContextCompat import io.ktor.client.* import io.ktor.client.engine.okhttp.* import io.ktor.client.plugins.sse.* @@ -54,4 +62,90 @@ fun createSslTrustAllClient(): HttpClient { */ fun createMcpHttpClient(): HttpClient { return createSslTrustAllClient() -} \ No newline at end of file +} + +/** + * 检查应用是否在前台运行 + * @return 应用是否在前台 + */ +fun isAppInForeground(context: Context,): Boolean { + try { + val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + val appProcesses = activityManager.runningAppProcesses ?: return false + val packageName = context.packageName + + for (appProcess in appProcesses) { + if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND + && appProcess.processName == packageName) { + return true + } + } + } catch (e: Exception) { + Log.e("ChatApi", "检查应用状态失败: ${e.message}", e) + } + return false +} +/** + * 检查权限并根据应用状态采取不同行为 + * @param permission 需要检查的权限 + * @param permissionName 权限名称(用于显示给用户) + * @param functionName 功能名称(用于日志和提示) + * @return 是否拥有权限 + */ +fun checkPermissionAndGuide(context: Context, permission: String, permissionName: String, functionName: String): Boolean { + if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) { + Log.w("ChatApi", "缺少${permissionName}权限,无法执行${functionName}") + + // 检查应用是否在前台 + if (isAppInForeground(context)) { + // 应用在前台,尝试直接弹出权限申请框 + try { + // 首先尝试发送广播通知DeepSound应用弹出权限申请对话框 + val intent = Intent("com.yunqiinnovation.deepsound.action.REQUEST_PERMISSION").apply { + setPackage(context.packageName) + putExtra("permission", permission) + putExtra("permissionName", permissionName) + putExtra("functionName", functionName) + addFlags(Intent.FLAG_RECEIVER_FOREGROUND) + } + context.sendBroadcast(intent) + + // 由于插件无法直接请求权限(需要Activity),上面的广播会让主应用来处理权限请求 + // 这里设置一个短暂延迟,让用户有时间响应权限请求 + Thread.sleep(500) + + // 再次检查权限是否已授予(以防用户立即授予) + if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED) { + return true + } + } catch (e: Exception) { + Log.e("ChatApi", "请求权限失败: ${e.message}", e) + } + + // 如果权限仍未授予,引导用户到权限管理页面 + try { + // 尝试跳转到应用内的权限管理页面 + val intent = Intent("com.yunqiinnovation.deepsound.action.PERMISSIONS_SETTINGS").apply { + setPackage(context.packageName) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + + if (intent.resolveActivity(context.packageManager) != null) { + context.startActivity(intent) + } else { + // 如果应用内权限页面不可用,则跳转到系统应用设置页面 + val settingsIntent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { + data = Uri.fromParts("package", context.packageName, null) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + context.startActivity(settingsIntent) + } + } catch (e: Exception) { + Log.e("ChatApi", "打开权限设置页面失败: ${e.message}", e) + } + } + // 应用在后台或权限请求失败,只返回文字提示 + return false + } + return true +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 662550b92..4fdd81180 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -57,6 +57,7 @@ dependencies: lottie: ^3.3.1 app_settings: 5.2.0 fluwx: ^5.4.1 + url_launcher: ^6.3.1 firebase_core: ^3.12.1 firebase_auth: ^5.5.1 google_sign_in: ^6.2.2