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