Browse Source

Merge branch 'new_dev' of https://github.com/deepcloud2048/deep_voice into new_dev

weicu
tanlongsheng 1 year ago
parent
commit
21b957d3e2
  1. 4
      android/app/build.gradle.kts
  2. 2
      ios/Runner/Info.plist
  3. 4
      lib/core/bindings/initial_binding.dart
  4. 151
      lib/data/models/channel_app_model.dart
  5. 47
      lib/data/models/channel_app_model.g.dart
  6. 17
      lib/data/services/account_service.dart
  7. 9
      lib/data/services/network/api.dart
  8. 315
      lib/data/services/version_update_service.dart
  9. 189
      lib/modules/agent/controllers/agent_controller.dart
  10. 30
      lib/modules/agent/views/music_card.dart
  11. 226
      lib/modules/common/widgets/version_update_dialog.dart
  12. 20
      lib/modules/music/views/music_playlist_view.dart
  13. 48
      lib/modules/settings/controllers/settings_controller.dart
  14. 3
      lib/modules/settings/views/settings_view.dart
  15. 24
      lib/modules/splash/controllers/splash_controller.dart
  16. 24
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPSubClient.kt
  17. 301
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt
  18. 96
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/Utils.kt
  19. 1
      pubspec.yaml

4
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 {

2
ios/Runner/Info.plist

@ -39,7 +39,7 @@
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<key>CFBundleVersion</key> <!-- 构建号 -->
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>FacebookAdvertiserIDCollectionEnabled</key>
<false/>

4
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>(() => AccountService(), fenix: true);
// 版本更新服务
Get.lazyPut<VersionUpdateService>(() => VersionUpdateService(), fenix: true);
// 杰理蓝牙服务
// // 语言管理器(需要最先初始化)

151
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<String, dynamic> json) =>
_$ChannelAppResponseFromJson(json);
Map<String, dynamic> toJson() => _$ChannelAppResponseToJson(this);
}
/// 渠道应用数据模型
@JsonSerializable()
class ChannelAppData {
@JsonKey(name: 'app')
final AppInfo app;
const ChannelAppData({
required this.app,
});
factory ChannelAppData.fromJson(Map<String, dynamic> json) =>
_$ChannelAppDataFromJson(json);
Map<String, dynamic> 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<String, dynamic> json) =>
_$AppInfoFromJson(json);
Map<String, dynamic> 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,
});
}

47
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<String, dynamic> json) =>
ChannelAppResponse(
code: (json['code'] as num).toInt(),
data: json['data'] == null
? null
: ChannelAppData.fromJson(json['data'] as Map<String, dynamic>),
msg: json['msg'] as String,
);
Map<String, dynamic> _$ChannelAppResponseToJson(ChannelAppResponse instance) =>
<String, dynamic>{
'code': instance.code,
'data': instance.data,
'msg': instance.msg,
};
ChannelAppData _$ChannelAppDataFromJson(Map<String, dynamic> json) =>
ChannelAppData(
app: AppInfo.fromJson(json['app'] as Map<String, dynamic>),
);
Map<String, dynamic> _$ChannelAppDataToJson(ChannelAppData instance) =>
<String, dynamic>{
'app': instance.app,
};
AppInfo _$AppInfoFromJson(Map<String, dynamic> json) => AppInfo(
address: json['address'] as String,
channel: json['channel'] as String,
description: json['description'] as String,
version: json['version'] as String,
);
Map<String, dynamic> _$AppInfoToJson(AppInfo instance) => <String, dynamic>{
'address': instance.address,
'channel': instance.channel,
'description': instance.description,
'version': instance.version,
};

17
lib/data/services/account_service.dart

@ -25,7 +25,7 @@ class AccountService extends GetxService {
Future<bool> 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<bool> 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,

9
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,
);
}
}

315
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<String> getCurrentVersion() async {
try {
final packageInfo = await PackageInfo.fromPlatform();
return '${packageInfo.version}+${packageInfo.buildNumber}';
} catch (e) {
_logger.e('获取当前版本信息失败: $e');
return '1.0.0';
}
}
/// 获取当前平台信息
Future<PlatformInfo> 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<PlatformInfo> 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<VersionUpdateInfo> 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<int> _parseVersion(String version) {
final parts = version.split('.');
final result = <int>[];
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<bool> 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<bool> _openAppStore() async {
// 这里需要替换为实际的App Store ID
const appStoreUrl = 'https://apps.apple.com/app/id你的应用ID';
return await _launchUrl(appStoreUrl);
}
/// 打开Google Play
Future<bool> _openGooglePlay() async {
// 这里需要替换为实际的包名
const packageName = 'com.yourcompany.deepvoice';
const playStoreUrl =
'https://play.google.com/store/apps/details?id=$packageName';
return await _launchUrl(playStoreUrl);
}
/// 打开华为应用市场
Future<bool> _openHuaweiStore() async {
const huaweiStoreUrl = 'appmarket://details?id=com.yourcompany.deepvoice';
return await _launchUrl(huaweiStoreUrl);
}
/// 打开小米应用商店
Future<bool> _openXiaomiStore() async {
const xiaomiStoreUrl = 'mimarket://details?id=com.yourcompany.deepvoice';
return await _launchUrl(xiaomiStoreUrl);
}
/// 打开OPPO软件商店
Future<bool> _openOppoStore() async {
const oppoStoreUrl =
'oppomarket://details?packagename=com.yourcompany.deepvoice';
return await _launchUrl(oppoStoreUrl);
}
/// 打开vivo应用商店
Future<bool> _openVivoStore() async {
const vivoStoreUrl = 'vivomarket://details?id=com.yourcompany.deepvoice';
return await _launchUrl(vivoStoreUrl);
}
/// 打开荣耀应用市场
Future<bool> _openHonorStore() async {
const honorStoreUrl = 'honormarket://details?id=com.yourcompany.deepvoice';
return await _launchUrl(honorStoreUrl);
}
/// 打开官网下载
Future<bool> _openOfficialDownload(String downloadUrl) async {
return await _launchUrl(downloadUrl);
}
/// 启动URL
Future<bool> _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;
}
}
}

189
lib/modules/agent/controllers/agent_controller.dart

@ -91,6 +91,17 @@ class AgentController extends GetxController {
_loadTtsPreference(); //加载TTS偏好设置
_loadChatHistory(); //加载聊天历史记录
_subscribeToEvents(); //订阅代理服务事件
_initMusicStatus(); //初始化音乐状态
}
// 初始化音乐播放状态
Future<void> _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<void> 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<String, dynamic> 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<String, dynamic>;
song["url"] = urldata['url'] ?? '';
} catch (e) {
Logger.e(TAG, '转换响应数据失败: $e');
return;
Future<void> musicPlay(Map<String, dynamic> 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<String, dynamic>;
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 = <String, String>{
'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<void> 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<void> 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<void> 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');
}

30
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

226
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<void> _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<void> show(VersionUpdateInfo updateInfo) async {
await Get.dialog(
VersionUpdateDialog(updateInfo: updateInfo),
barrierDismissible: updateInfo.updateType != UpdateType.force,
);
}
}

20
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

48
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<void> 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,
);
}
}
}

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

@ -880,7 +880,8 @@ class SettingsView extends GetView<SettingsController> {
),
InkWell(
onTap: () {
// 在这里添加检查更新的逻辑
// 调用检查更新方法
controller.checkForUpdate();
},
child: Container(
constraints: BoxConstraints(

24
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');
// 版本检查失败不影响用户使用,静默处理
}
});
}
}

24
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),停止重连")
// // 可以在这里添加通知或回调,告知上层连接彻底失败
// }
}
/**
* 关闭连接

301
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, Any>): 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, Any>): 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}\"}"
// }
}
}

96
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()
}
}
/**
* 检查应用是否在前台运行
* @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
}

1
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

Loading…
Cancel
Save