diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 127288e55..ebb128fb5 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -50,6 +50,8 @@
+
+
diff --git a/lib/core/translations/language/en_us.dart b/lib/core/translations/language/en_us.dart
index 7dca392ef..45071ebdd 100644
--- a/lib/core/translations/language/en_us.dart
+++ b/lib/core/translations/language/en_us.dart
@@ -487,15 +487,19 @@ const Map enUS = {
// 权限管理页面
'getPermissionStatusFailed': 'Failed to get permission status',
'communicationPermissions': 'Communication Permissions',
+ 'alarmPermissions': 'Alarm Permissions',
'calendarPermissions': 'Calendar Permissions',
'locationPermissions': 'Location Permissions',
'phone': 'Phone',
'sms': 'SMS',
+ 'alarm': 'Alarm',
'calendarWrite': 'Calendar Write',
'calendarFullAccess': 'Calendar Full Access',
'locationInfo': 'Location Info',
'phonePermissionDesc': 'Used for phone call functionality',
'smsPermissionDesc': 'Used for SMS functionality',
+ 'alarmPermissionDesc':
+ 'Used to set exact alarms to ensure reminders trigger on time',
'calendarWritePermissionDesc':
'Used for creating calendar events and reminders',
'calendarFullAccessPermissionDesc':
@@ -512,6 +516,11 @@ const Map enUS = {
'phonePermissionDetailDesc':
'Phone call functionality requires phone permission',
'smsPermissionDetailDesc': 'SMS functionality requires SMS permission',
+ 'alarmPermissionDetailDesc':
+ 'This permission is required to set exact alarms and ensure reminders trigger on time',
+ 'contactsPermissionDesc': 'Used for accessing contact information',
+ 'contactsPermissionDetailDesc':
+ 'Phone call and SMS functionality requires contacts permission to select contacts',
'calendarWritePermissionDetailDesc':
'Creating calendar events requires calendar write permission',
'calendarFullAccessPermissionDetailDesc':
diff --git a/lib/core/translations/language/zh_cn.dart b/lib/core/translations/language/zh_cn.dart
index d73546416..986e8cd14 100644
--- a/lib/core/translations/language/zh_cn.dart
+++ b/lib/core/translations/language/zh_cn.dart
@@ -455,15 +455,18 @@ const Map zhCN = {
// 权限管理页面
'getPermissionStatusFailed': '获取权限状态失败',
'communicationPermissions': '通讯权限',
+ 'alarmPermissions': '闹钟权限',
'calendarPermissions': '日历权限',
'locationPermissions': '位置权限',
'phone': '电话',
'sms': '短信',
+ 'alarm': '闹钟',
'calendarWrite': '日历写入',
'calendarFullAccess': '日历完整访问',
'locationInfo': '位置信息',
'phonePermissionDesc': '用于拨打电话功能',
'smsPermissionDesc': '用于发送短信功能',
+ 'alarmPermissionDesc': '用于设置精确闹钟以确保提醒按时触发',
'calendarWritePermissionDesc': '用于创建日历事件和提醒',
'calendarFullAccessPermissionDesc': '用于读取和管理日历事件及提醒',
'locationPermissionDesc': '用于获取当前位置信息',
@@ -477,9 +480,12 @@ const Map zhCN = {
'permissionRequestError': '请求权限时出错',
'phonePermissionDetailDesc': '拨打电话功能需要电话权限',
'smsPermissionDetailDesc': '发送短信功能需要短信权限',
+ 'alarmPermissionDetailDesc': '设置精确闹钟需要此权限,以确保提醒按时触发',
'calendarWritePermissionDetailDesc': '创建日历事件需要日历写入权限',
'calendarFullAccessPermissionDetailDesc': '管理日历事件需要完整日历权限',
'locationPermissionDetailDesc': '获取位置信息需要位置权限',
+ 'contactsPermissionDesc': '用于访问联系人信息',
+ 'contactsPermissionDetailDesc': '拨打电话和发送短信功能需要联系人权限以选择联系人',
'appNeedsPermissionForFullFunction': '应用需要此权限以提供完整功能',
'permanentDenialText': '您已永久拒绝{permission}权限,请前往应用设置开启',
'goToSettings': '去设置',
diff --git a/lib/core/widgets/intro_overlay_builder.dart b/lib/core/widgets/intro_overlay_builder.dart
index 920737ae2..fec06693e 100644
--- a/lib/core/widgets/intro_overlay_builder.dart
+++ b/lib/core/widgets/intro_overlay_builder.dart
@@ -7,6 +7,7 @@ class IntroOverlayBuilder {
static Widget homeBuilder(
StepWidgetParams params,
bool isWakeup, {
+ String title = '对着麦克风说:“小言小言”',
Function? onNext,
}) {
return Column(
@@ -21,7 +22,7 @@ class IntroOverlayBuilder {
Padding(
padding: EdgeInsets.only(bottom: 20.w),
child: Text(
- isWakeup ? '我在呢~' : '对着麦克风说:“小言小言”',
+ isWakeup ? '我在呢~' : title,
style: TextStyle(
fontSize: 15.sp,
color: Colors.white,
@@ -30,12 +31,27 @@ class IntroOverlayBuilder {
),
),
if (onNext != null)
- _button(
- '下一步',
- () {
- params.onNext!();
- onNext();
- },
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Padding(
+ padding: EdgeInsets.only(right: 50.w),
+ child: _button(
+ '下一步',
+ () {
+ params.onNext!();
+ onNext();
+ },
+ ),
+ ),
+ _button(
+ '跳过',
+ () {
+ params.onFinish();
+ },
+ isSkip: true,
+ ),
+ ],
),
],
);
@@ -82,17 +98,43 @@ class IntroOverlayBuilder {
onFinish();
},
),
+ if (onNext != null || onFinish != null) const Spacer(),
+ _button(
+ '跳过',
+ () {
+ params.onFinish();
+ },
+ isSkip: true,
+ ),
],
),
],
);
}
- static Widget _button(String text, Function() onTap) {
- return _AnimatedBorderButton(
- text: text,
- onTap: onTap,
- );
+ static Widget _button(String text, Function() onTap, {bool isSkip = false}) {
+ return isSkip
+ ? GestureDetector(
+ onTap: onTap,
+ child: Container(
+ padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 5.w),
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(20.r),
+ border: Border.all(color: Colors.white),
+ ),
+ child: Text(
+ text,
+ style: TextStyle(
+ fontSize: 14.sp,
+ color: Colors.white,
+ ),
+ ),
+ ),
+ )
+ : _AnimatedBorderButton(
+ text: text,
+ onTap: onTap,
+ );
}
}
@@ -207,7 +249,7 @@ class _AnimatedBorderButtonState extends State<_AnimatedBorderButton>
borderRadius: 20.r,
),
child: Container(
- padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 5.w),
+ padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 5.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.r),
border: Border.all(color: Colors.transparent),
diff --git a/lib/data/services/novice_guide_service.dart b/lib/data/services/novice_guide_service.dart
index d32be2368..c58b1a312 100644
--- a/lib/data/services/novice_guide_service.dart
+++ b/lib/data/services/novice_guide_service.dart
@@ -4,6 +4,8 @@ import 'package:ble_service/ble_service.dart';
import 'package:flutter_intro/flutter_intro.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
+import 'package:permission_handler/permission_handler.dart';
+import '../../core/utils/permission_util.dart';
class NoviceGuideService extends GetxService {
final GetStorage _storage = GetStorage();
@@ -24,44 +26,109 @@ class NoviceGuideService extends GetxService {
'input': '今天天气怎么样',
'send': '小言会为你实时为你播报天气',
'end': '小言能实时为你播报天气,试试其他指令吧',
+ 'method': () async {
+ // 天气查询需要位置权限
+ await PermissionUtil.instance.requestPermission(
+ permissionType: Permission.location,
+ permissionName: 'locationInfo'.tr, // 位置信息
+ explanationText: 'locationPermissionDesc'.tr, // 用于获取当前位置信息
+ permanentDenialText:
+ 'locationPermissionDetailDesc'.tr, // 获取位置信息需要位置权限
+ );
+ },
},
{
'start': '对小言说:“请播放一组摇滚乐”',
'input': '请播放一组摇滚乐',
'send': '小言会为你搜寻到一组音乐',
'end': '小言已成功为您搜寻到一组音乐,试试其他指令吧',
+ 'method': () async {},
},
{
'start': '对小言说:“导航到深圳机场”',
'input': '导航到深圳机场',
'send': '小言会为你规划路线',
'end': '小言已成功为您导航到深圳机场,试试其他指令吧',
+ 'method': () async {
+ // 导航功能需要位置权限
+ await PermissionUtil.instance.requestPermission(
+ permissionType: Permission.location,
+ permissionName: 'locationInfo'.tr, // 位置信息
+ explanationText: 'locationPermissionDesc'.tr, // 用于获取当前位置信息
+ permanentDenialText:
+ 'locationPermissionDetailDesc'.tr, // 获取位置信息需要位置权限
+ );
+ },
},
{
'start': '对小言说:“明天下午3点创建会议提醒”',
'input': '明天下午3点创建会议提醒',
'send': '小言会为你创建会议提醒',
'end': '小言已成功为您创建会议提醒,试试其他指令吧',
+ 'method': () async {
+ //创建会议提醒需要日历权限
+ await PermissionUtil.instance.requestPermission(
+ permissionType: Permission.calendarWriteOnly,
+ permissionName: 'calendarWrite'.tr, // 日历写入
+ explanationText: 'calendarWritePermissionDesc'.tr, // 用于创建日历事件和提醒
+ permanentDenialText:
+ 'calendarWritePermissionDetailDesc'.tr, // 创建日历事件需要日历写入权限
+ );
+ },
},
{
'start': '对小言说:“拨打妈妈的电话”',
'input': '拨打妈妈的电话',
'send': '小言会为你拨打电话',
'end': '小言已成功为您拨打电话,试试其他指令吧',
+ 'method': () async {
+ // 拨打电话需要电话权限
+ await PermissionUtil.instance.requestPermission(
+ permissionType: Permission.phone,
+ permissionName: 'phone'.tr, // 电话
+ explanationText: 'phonePermissionDesc'.tr, // 用于拨打电话功能
+ permanentDenialText: 'phonePermissionDetailDesc'.tr, // 拨打电话功能需要电话权限
+ );
+ //拨打电话还需要联系人权限
+ await PermissionUtil.instance.requestPermission(
+ permissionType: Permission.contacts,
+ permissionName: 'contacts'.tr, // 联系人
+ explanationText: 'contactsPermissionDesc'.tr, // 用于访问联系人信息
+ permanentDenialText:
+ 'contactsPermissionDetailDesc'.tr, // 拨打电话功能需要联系人权限
+ );
+ },
},
{
'start': '对小言说:“给爸爸发短信说晚上回家吃饭’”',
'input': '给爸爸发短信说晚上回家吃饭’',
'send': '小言会为你发送短信',
'end': '短信已发送,语音操控更便捷',
+ 'method': () async {
+ // 发送短信需要短信权限
+ await PermissionUtil.instance.requestPermission(
+ permissionType: Permission.sms,
+ permissionName: 'sms'.tr, // 短信
+ explanationText: 'smsPermissionDesc'.tr, // 用于发送短信功能
+ permanentDenialText: 'smsPermissionDetailDesc'.tr, // 发送短信功能需要短信权限
+ );
+ //发送短信还需要联系人权限
+ await PermissionUtil.instance.requestPermission(
+ permissionType: Permission.contacts,
+ permissionName: 'contacts'.tr, // 联系人
+ explanationText: 'contactsPermissionDesc'.tr, // 用于访问联系人信息
+ permanentDenialText:
+ 'contactsPermissionDetailDesc'.tr, // 发送短信功能需要联系人权限
+ );
+ },
},
];
@override
void onInit() {
super.onInit();
- // isHomeGuide = _storage.read('home_guide') ?? false;
- // isAIGuide = _storage.read('ai_guide') ?? false;
+ isHomeGuide = _storage.read('home_guide') ?? false;
+ isAIGuide = _storage.read('ai_guide') ?? false;
}
@override
diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart
index 82e7696e1..71d4be63b 100644
--- a/lib/modules/agent/controllers/agent_controller.dart
+++ b/lib/modules/agent/controllers/agent_controller.dart
@@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:ffi';
import 'dart:io';
import 'package:amap_service/amap_service.dart';
+import 'package:flutter_intro/flutter_intro.dart';
import '../../../data/services/ble_manager.dart';
import '../../../data/services/bluetooth_manager.dart';
import '../../../data/services/novice_guide_service.dart';
@@ -123,6 +124,7 @@ class AgentController extends GetxController {
Timer? _progressTimer;
// 用于跟踪AI回复状态
bool _isNewAssistantResponse = true;
+ RxBool newAIReplyCompleted = true.obs;
String _currentResponseId = '';
final TAG = 'AgentController';
@@ -158,6 +160,7 @@ class AgentController extends GetxController {
_setupScrollListener(); //设置滚动监听器
_loadChatHistory(); //加载聊天历史记录
_subscribeToEvents(); //订阅代理服务事件
+ // _subscribeToBleEvents(); //订阅BLE事件
_initMusicStatus(); //初始化音乐状态
_setupMediaSessionCallbacks(); //设置媒体会话回调
_setupTextInputListener(); //设置文本输入监听器
@@ -207,6 +210,7 @@ class AgentController extends GetxController {
_eventSubscription?.cancel();
_amapeventSubscription?.cancel();
_bleEventSubscription?.cancel();
+ Intro.of(Get.context!).dispose();
super.onClose();
}
@@ -635,6 +639,7 @@ class AgentController extends GetxController {
isImageProcessing.value = false;
// 标记当前回复完成
_isNewAssistantResponse = true;
+ newAIReplyCompleted.value = true;
break;
case AgentServiceEventType.error:
@@ -892,6 +897,7 @@ class AgentController extends GetxController {
isProcessing.value = true;
// 标记为新的AI回复
_isNewAssistantResponse = true;
+ newAIReplyCompleted.value = false;
// 根据TTS状态决定是否朗读
await AgentService.processTextInput(text,
speakResponse: isTtsEnabled.value);
@@ -1336,7 +1342,7 @@ class AgentController extends GetxController {
// 打电话模式
isTextInputMode.value = false;
Logger.i(TAG, '打电话模式已启动,开始语音输入');
- await AgentService.startConversation();
+ await AgentService.startConversation(mode: 'phone_call');
}
// 结束打电话模式
@@ -1388,7 +1394,7 @@ class AgentController extends GetxController {
} else {
// 当前未录音,开始录音
Logger.i(TAG, '开始打电话模式录音');
- final result = await AgentService.startConversation();
+ final result = await AgentService.startConversation(mode: 'phone_call');
// 手动更新状态,确保UI同步
if (result) {
@@ -1492,7 +1498,7 @@ class AgentController extends GetxController {
isPushToTalkActive.value = true;
//await AgentService.recognizeCallback();
// 开始语音识别
- await AgentService.startConversation();
+ await AgentService.startConversation(mode: 'push_to_talk');
// await startVoiceInput();
}
@@ -1756,21 +1762,52 @@ class AgentController extends GetxController {
// 打开图片查看器
void openImageViewer(BuildContext context, String imagePath) {
+ // 构建完整的图片路径
+ String fullImagePath;
+ if (imagePath.startsWith('/')) {
+ // 如果是相对路径(以/开头),需要拼接AppDir.value
+ fullImagePath = '${AppDir.value}$imagePath';
+ } else {
+ // 如果已经是完整路径,直接使用
+ fullImagePath = imagePath;
+ }
+
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Scaffold(
- appBar: AppBar(
- backgroundColor: Colors.black,
- iconTheme: IconThemeData(color: Colors.white),
- ),
+ // appBar: AppBar(
+ // backgroundColor: Colors.black,
+ // leading: IconButton(
+ // icon: Icon(Icons.arrow_back_ios, color: Colors.white),
+ // onPressed: () => Navigator.of(context).pop(),
+ // ),
+ // iconTheme: IconThemeData(color: Colors.white),
+ // //title: Text('图片预览', style: TextStyle(color: Colors.white)),
+ // ),
body: Container(
color: Colors.black,
- child: PhotoView(
- imageProvider: FileImage(File(imagePath)),
- minScale: PhotoViewComputedScale.contained,
- maxScale: PhotoViewComputedScale.covered * 2,
- backgroundDecoration: BoxDecoration(color: Colors.black),
+ child: GestureDetector(
+ onTap: () => Navigator.of(context).pop(), // 点击退出
+
+ child: PhotoView(
+ imageProvider: FileImage(File(fullImagePath)),
+ minScale: PhotoViewComputedScale.contained,
+ maxScale: PhotoViewComputedScale.covered * 2,
+ backgroundDecoration: BoxDecoration(color: Colors.black),
+ // errorBuilder: (context, error, stackTrace) {
+ // return const Center(
+ // child: Column(
+ // mainAxisAlignment: MainAxisAlignment.center,
+ // children: [
+ // Icon(Icons.error, color: Colors.white, size: 64),
+ // SizedBox(height: 16),
+ // Text('图片加载失败', style: TextStyle(color: Colors.white)),
+ // ],
+ // ),
+ // );
+ // },
+ ),
),
),
),
diff --git a/lib/modules/agent/views/agent_view.dart b/lib/modules/agent/views/agent_view.dart
index 849baa4cc..c78eb62d3 100644
--- a/lib/modules/agent/views/agent_view.dart
+++ b/lib/modules/agent/views/agent_view.dart
@@ -193,62 +193,108 @@ class _AgentViewState extends State with WidgetsBindingObserver {
),
body: Stack(
children: [
+ IntroStepBuilder(
+ group: 'ai',
+ order: 3,
+ getOverlayPosition: ({
+ required Offset offset,
+ required Size screenSize,
+ required Size size,
+ }) {
+ return OverlayPosition(
+ top: 200.h,
+ width: screenSize.width,
+ crossAxisAlignment: CrossAxisAlignment.end,
+ );
+ },
+ overlayBuilder: (params) {
+ return Obx(
+ () => IntroOverlayBuilder.homeBuilder(
+ params,
+ false,
+ title: controller.newAIReplyCompleted.value
+ ? '小言已经完成了回答!'
+ : '小言正在思考中~',
+ onNext: controller.newAIReplyCompleted.value ? () {} : null,
+ ),
+ );
+ },
+ builder: (BuildContext context, GlobalKey key) => SizedBox(
+ key: key,
+ ),
+ ),
// 主要聊天界面
Column(
children: [
// 聊天内容
Expanded(
- child: Stack(
- children: [
- IntroStepBuilder(
- group: 'ai',
- order: 3,
- overlayBuilder: (params) {
- return IntroOverlayBuilder.aiBuilder(
- params,
- controller.noviceGuideService.aiStepsList[
- controller.noviceGuideService.aiStep]['end'],
- onFinish: () async {
- if (controller.noviceGuideService.aiStep < 5) {
- await Future.delayed(
- const Duration(milliseconds: 300));
- controller.noviceGuideService.aiStep++;
- Intro.of(Get.context!)
- .start(group: 'ai', reset: true);
- }
- },
- );
- },
- builder: (BuildContext context, GlobalKey key) => Align(
- alignment: Alignment.bottomCenter,
- child: SizedBox(
- key: key,
- width: double.infinity,
- height: 430.w,
- ),
- ),
- ),
- Obx(
- () => Align(
- alignment: Alignment.topCenter,
- child: ListView.builder(
- controller: controller.scrollController,
- shrinkWrap: true,
- reverse: true,
- padding: EdgeInsets.symmetric(
- vertical: 16.h, horizontal: 16.w),
- itemCount: controller.messages.length +
- (controller.hasMoreMessages.value ? 1 : 0),
- itemBuilder: (context, index) {
- // 如果是最后一项且有更多消息,显示加载指示器
- if (index == controller.messages.length &&
- controller.hasMoreMessages.value) {
- return _buildLoadMoreIndicator();
- }
+ child: Obx(() {
+ return Align(
+ alignment: Alignment.topCenter,
+ child: ListView.builder(
+ controller: controller.scrollController,
+ shrinkWrap: true,
+ reverse: true,
+ padding: EdgeInsets.symmetric(
+ vertical: 16.h, horizontal: 16.w),
+ itemCount: controller.messages.length +
+ (controller.hasMoreMessages.value ? 1 : 0),
+ itemBuilder: (context, index) {
+ // 如果是最后一项且有更多消息,显示加载指示器
+ if (index == controller.messages.length &&
+ controller.hasMoreMessages.value) {
+ return _buildLoadMoreIndicator();
+ }
- final message = controller.messages[
- controller.messages.length - 1 - index];
- return Padding(
+ final message = controller
+ .messages[controller.messages.length - 1 - index];
+ return index == 0
+ ? IntroStepBuilder(
+ group: 'ai',
+ order: 4,
+ overlayBuilder: (params) {
+ return IntroOverlayBuilder.aiBuilder(
+ params,
+ controller.noviceGuideService.aiStepsList[
+ controller
+ .noviceGuideService.aiStep]['end'],
+ onFinish: () async {
+ if (controller.noviceGuideService.aiStep <
+ 5) {
+ await Future.delayed(
+ const Duration(milliseconds: 300));
+ controller.noviceGuideService.aiStep++;
+ Intro.of(Get.context!)
+ .start(group: 'ai', reset: true);
+ }
+ },
+ );
+ },
+ builder:
+ (BuildContext context, GlobalKey key) =>
+ Padding(
+ key: key,
+ padding: EdgeInsets.only(bottom: 8.h),
+ child: GestureDetector(
+ onLongPressStart:
+ (LongPressStartDetails details) {
+ messageActionController
+ .onLongPressMessage(context,
+ isDarkMode, details, message);
+ },
+ child: MessageBubble(
+ isUser: message.isUser,
+ message: message.text,
+ timestamp: message.timestamp,
+ isRecognizing: message.isRecognizing,
+ hasImage: message.hasImage,
+ imagePath: message.imagePath,
+ meta: message.meta,
+ ),
+ ),
+ ),
+ )
+ : Padding(
padding: EdgeInsets.only(bottom: 8.h),
child: GestureDetector(
onLongPressStart:
@@ -265,13 +311,12 @@ class _AgentViewState extends State with WidgetsBindingObserver {
imagePath: message.imagePath,
meta: message.meta,
),
- ));
- },
- ),
- ),
+ ),
+ );
+ },
),
- ],
- ),
+ );
+ }),
),
// 输入区域
@@ -685,7 +730,11 @@ class _AgentViewState extends State with WidgetsBindingObserver {
return IntroStepBuilder(
group: 'ai',
order: 2,
- onHighlightWidgetTap: () {
+ onHighlightWidgetTap: () async {
+ await controller
+ .noviceGuideService.aiStepsList[
+ controller.noviceGuideService
+ .aiStep]['method']();
widgetParams!.onNext!();
controller.sendMessage();
},
diff --git a/lib/modules/agent/views/weather_card.dart b/lib/modules/agent/views/weather_card.dart
index d06f7d97a..ce5026993 100644
--- a/lib/modules/agent/views/weather_card.dart
+++ b/lib/modules/agent/views/weather_card.dart
@@ -25,17 +25,7 @@ Widget buildWeatherContent(BuildContext context, String city,
Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
- gradient: isDarkMode
- ? const LinearGradient(
- begin: Alignment.topLeft,
- end: Alignment.bottomRight,
- colors: [Color(0xFF1E40AF), Color(0xFF1E3A8A)],
- )
- : const LinearGradient(
- begin: Alignment.topLeft,
- end: Alignment.bottomRight,
- colors: [Color(0xFF60A5FA), Color(0xFF3B82F6)],
- ),
+ gradient: _getWeatherGradient(now['dayweather'] ?? '', isDarkMode),
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
@@ -63,7 +53,7 @@ Widget buildWeatherContent(BuildContext context, String city,
color: Colors.white,
),
),
- SizedBox(height: 4),
+ SizedBox(height: 4.w),
Text(
_getWeatherDescriptionTranslation(now["dayweather"]),
style: TextStyle(
@@ -483,3 +473,90 @@ IconData getWeatherIcon(String description) {
// 默认或未知天气
return Icons.help_outline;
}
+
+/// 根据天气类型获取背景渐变颜色
+LinearGradient _getWeatherGradient(String weather, bool isDarkMode) {
+ final weatherType = weather.trim();
+
+ // 晴天 - 温暖的橙黄色
+ if (['晴', '少云', '晴间多云'].any((sunny) => weatherType.contains(sunny))) {
+ return LinearGradient(
+ begin: Alignment.topLeft,
+ end: Alignment.bottomRight,
+ colors: isDarkMode
+ ? [Color(0xFFFF8A00), Color(0xFFE65100)] // 深色模式:深橙色
+ : [Color(0xFFFFB74D), Color(0xFFFF9800)], // 浅色模式:亮橙色
+ );
+ }
+
+ // 多云/阴天 - 灰蓝色
+ if (['多云', '阴'].any((cloudy) => weatherType.contains(cloudy))) {
+ return LinearGradient(
+ begin: Alignment.topLeft,
+ end: Alignment.bottomRight,
+ colors: isDarkMode
+ ? [Color(0xFF455A64), Color(0xFF37474F)] // 深色模式:深灰蓝
+ : [Color(0xFF78909C), Color(0xFF607D8B)], // 浅色模式:灰蓝色
+ );
+ }
+
+ // 雨天 - 深蓝色
+ if (weatherType.contains('雨') ||
+ ['阵雨', '雷阵雨', '小雨', '中雨', '大雨', '暴雨']
+ .any((rain) => weatherType.contains(rain))) {
+ return LinearGradient(
+ begin: Alignment.topLeft,
+ end: Alignment.bottomRight,
+ colors: isDarkMode
+ ? [Color(0xFF1565C0), Color(0xFF0D47A1)] // 深色模式:深蓝色
+ : [Color(0xFF42A5F5), Color(0xFF1976D2)], // 浅色模式:亮蓝色
+ );
+ }
+
+ // 雪天 - 冷色调蓝白色
+ if (weatherType.contains('雪') ||
+ ['阵雪', '小雪', '中雪', '大雪', '暴雪']
+ .any((snow) => weatherType.contains(snow))) {
+ return LinearGradient(
+ begin: Alignment.topLeft,
+ end: Alignment.bottomRight,
+ colors: isDarkMode
+ ? [Color(0xFF0277BD), Color(0xFF01579B)] // 深色模式:深冷蓝
+ : [Color(0xFF81D4FA), Color(0xFF29B6F6)], // 浅色模式:浅蓝色
+ );
+ }
+
+ // 雾霾天 - 暗黄灰色
+ if (weatherType.contains('雾') ||
+ weatherType.contains('霾') ||
+ ['雾', '浓雾', '霾', '中度霾', '重度霾'].any((fog) => weatherType.contains(fog))) {
+ return LinearGradient(
+ begin: Alignment.topLeft,
+ end: Alignment.bottomRight,
+ colors: isDarkMode
+ ? [Color(0xFF5D4037), Color(0xFF3E2723)] // 深色模式:深棕色
+ : [Color(0xFF8D6E63), Color(0xFF6D4C41)], // 浅色模式:棕灰色
+ );
+ }
+
+ // 大风天 - 青绿色
+ if (weatherType.contains('风') ||
+ ['大风', '强风', '疾风', '烈风'].any((wind) => weatherType.contains(wind))) {
+ return LinearGradient(
+ begin: Alignment.topLeft,
+ end: Alignment.bottomRight,
+ colors: isDarkMode
+ ? [Color(0xFF00695C), Color(0xFF004D40)] // 深色模式:深青色
+ : [Color(0xFF26A69A), Color(0xFF00796B)], // 浅色模式:青绿色
+ );
+ }
+
+ // 默认 - 原来的蓝色
+ return LinearGradient(
+ begin: Alignment.topLeft,
+ end: Alignment.bottomRight,
+ colors: isDarkMode
+ ? [Color(0xFF1E40AF), Color(0xFF1E3A8A)]
+ : [Color(0xFF60A5FA), Color(0xFF3B82F6)],
+ );
+}
diff --git a/lib/modules/login/controllers/login_controller.dart b/lib/modules/login/controllers/login_controller.dart
index 3b0c7f590..b605931ca 100644
--- a/lib/modules/login/controllers/login_controller.dart
+++ b/lib/modules/login/controllers/login_controller.dart
@@ -26,22 +26,6 @@ enum LoginViewState {
verification // 验证码页面
}
-// 用户渠道枚举
-// enum UserChannel {
-// UNKNOWN(0), // 未知渠道
-// CHINA_HUAWEI(1), // 中国-华为
-// CHINA_XIAOMI(2), // 中国-小米
-// CHINA_OPPO(3), // 中国-OPPO
-// CHINA_VIVO(4), // 中国-VIVO
-// CHINA_HONOR(5), // 中国-荣耀
-// OFFICIAL(100), // 官方渠道
-// GLOBAL_APPLE(101), // 全球-苹果
-// GLOBAL_GOOGLE(102); // 全球-谷歌
-
-// const UserChannel(this.value);
-// final int value;
-// }
-
class LoginController extends GetxController {
static LoginController get to => Get.find();
final GetStorage _storage = GetStorage();
@@ -354,9 +338,32 @@ class LoginController extends GetxController {
// 获取验证码
void getVerificationCode() {
if (contactController.text.isEmpty) {
+ Get.snackbar(
+ 'tip'.tr, //提示
+ 'pleaseEnterContactInfo'.tr, //请输入手机号或邮箱
+ snackPosition: SnackPosition.TOP,
+ backgroundColor: Colors.red.withOpacity(0.1),
+ colorText: Colors.red,
+ );
+ return;
+ }
+
+ // 根据登录类型验证格式
+ if (loginType == 0 && !isEmailContactController()) {
Get.snackbar(
'tip'.tr,
- 'pleaseEnterContactInfo'.tr,
+ 'invalidEmailFormat'.tr,
+ snackPosition: SnackPosition.TOP,
+ backgroundColor: Colors.red.withOpacity(0.1),
+ colorText: Colors.red,
+ );
+ return;
+ }
+
+ if (loginType == 1 && !isPhoneContactController()) {
+ Get.snackbar(
+ 'tip'.tr,
+ 'invalidPhoneFormat'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
@@ -371,40 +378,6 @@ class LoginController extends GetxController {
// 使用验证码直接登录
void loginWithVerificationCode() {
- //测试期间暂时关闭验证码登录
- // if (contactController.text.isEmpty) {
- // Get.snackbar(
- // '提示',
- // '请输入手机号码或邮箱',
- // snackPosition: SnackPosition.TOP,
- // backgroundColor: Colors.red.withOpacity(0.1),
- // colorText: Colors.red,
- // );
- // return;
- // }
-
- // if (verificationCodeController.text.isEmpty) {
- // Get.snackbar(
- // '提示',
- // '请输入验证码',
- // snackPosition: SnackPosition.TOP,
- // backgroundColor: Colors.red.withOpacity(0.1),
- // colorText: Colors.red,
- // );
- // return;
- // }
-
- // if (verificationCodeController.text.length < 6) {
- // Get.snackbar(
- // '提示',
- // '请输入完整的验证码',
- // snackPosition: SnackPosition.TOP,
- // backgroundColor: Colors.red.withOpacity(0.1),
- // colorText: Colors.red,
- // );
- // return;
- // }
-
_isLoading.value = true;
// 模拟验证过程
@@ -481,9 +454,8 @@ class LoginController extends GetxController {
// 发送验证码
Future _sendVerificationCode() async {
- // 这里应该是实际发送验证码的逻辑
- // 为了演示,我们只是模拟发送
- if (isEmialContactController() || isPhoneContactController()) {
+ // 实际发送验证码的逻辑
+ if (isEmailContactController() || isPhoneContactController()) {
_startCountdown();
Logger().d(contactController.text);
var response = await Api.getCode({
@@ -497,8 +469,8 @@ class LoginController extends GetxController {
}
} else {
Get.snackbar(
- 'tip'.tr,
- 'pleaseEnterContactInfo'.tr,
+ 'tip'.tr, //提示
+ 'pleaseEnterContactInfo'.tr, //请输入手机号或邮箱
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
@@ -530,19 +502,39 @@ class LoginController extends GetxController {
}
// 检查邮箱格式
- bool isEmialContactController() {
+ bool isEmailContactController() {
// 检查每个元素是否为单个字母或数字字符
- final text = contactController.text;
- return (text.length > 5 &&
- RegExp(r'^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$')
- .hasMatch(text));
+ final text = contactController.text.trim();
+ if (text.length < 6) return false;
+
+ // 更严格的邮箱验证
+ final emailRegex =
+ RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$');
+ return emailRegex.hasMatch(text);
}
-// 检查手机号码格式
+ // 检查手机号码格式
bool isPhoneContactController() {
// 检查每个元素是否为单个字母或数字字符
- final text = contactController.text;
- return RegExp(r'^[0-9]+$').hasMatch(text);
+ final text = contactController.text.trim();
+ if (text.isEmpty) return false;
+ // 根据国家代码验证手机号长度
+ // 长度检查
+ if (text.length < 7 || text.length > 15) return false;
+
+ // 必须是纯数字
+ if (!RegExp(r'^[0-9]+$').hasMatch(text)) return false;
+ // 特定国家的特殊验证
+ switch (_selectedCountryCode.value) {
+ case '+86': // 中国
+ return text.length == 11 && text.startsWith('1');
+ case '+1': // 美国/加拿大
+ return text.length == 10;
+ case '+81': // 日本
+ return text.length == 10 || text.length == 11;
+ default:
+ return true; // 其他国家只做基本验证
+ }
}
// 检查验证码是否完整
@@ -570,7 +562,7 @@ class LoginController extends GetxController {
}
// 验证邮箱格式
- if (loginType == 0 && !isEmialContactController()) {
+ if (loginType == 0 && !isEmailContactController()) {
Get.snackbar(
'error'.tr,
'invalidEmailFormat'.tr,
diff --git a/lib/modules/settings/controllers/settings_controller.dart b/lib/modules/settings/controllers/settings_controller.dart
index 4dcecf302..496e405e3 100644
--- a/lib/modules/settings/controllers/settings_controller.dart
+++ b/lib/modules/settings/controllers/settings_controller.dart
@@ -192,7 +192,18 @@ class SettingsController extends GetxController {
},
{'type': Permission.bluetooth, 'name': '蓝牙', 'description': '用于连接蓝牙设备'}
]
- }
+ },
+ {
+ 'name': '闹钟权限',
+ 'icon': Icons.alarm,
+ 'permissions': [
+ {
+ 'type': Permission.scheduleExactAlarm,
+ 'name': '闹钟',
+ 'description': '用于设置精确闹钟'
+ }
+ ]
+ },
];
// 检查单个权限状态
@@ -453,7 +464,8 @@ class SettingsController extends GetxController {
Logger().i('更新agent服务语音识别语言: ${supportedLanguages.join(', ')}');
// 调用agent服务更新语言设置
- final success = await AgentService.setSupportedLanguages(supportedLanguages);
+ final success =
+ await AgentService.setSupportedLanguages(supportedLanguages);
if (success) {
Logger().i('agent服务语言设置更新成功');
} else {
diff --git a/lib/modules/settings/views/permissions_view.dart b/lib/modules/settings/views/permissions_view.dart
index c6eebac8c..2b0ceaf80 100644
--- a/lib/modules/settings/views/permissions_view.dart
+++ b/lib/modules/settings/views/permissions_view.dart
@@ -4,6 +4,8 @@ import 'package:permission_handler/permission_handler.dart';
import '../controllers/settings_controller.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'dart:ui';
+import 'dart:io' show Platform;
+
import '../../../core/utils/permission_util.dart'; // 引入PermissionUtil
class PermissionsView extends GetView {
@@ -37,7 +39,7 @@ class PermissionsView extends GetView {
centerTitle: true,
),
body: FutureBuilder