diff --git a/apps/android/app/src/main/AndroidManifest.xml b/apps/android/app/src/main/AndroidManifest.xml
index 9ebc80d..b8713dc 100644
--- a/apps/android/app/src/main/AndroidManifest.xml
+++ b/apps/android/app/src/main/AndroidManifest.xml
@@ -15,6 +15,19 @@
android:label="灵可析"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
+
+
+
+
+
+
+
+
+
Bool {
GeneratedPluginRegistrant.register(with: self)
+ if #available(iOS 10.0, *) {
+ UNUserNotificationCenter.current().delegate = self
+ }
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
diff --git a/apps/lib/core/api/api_exception.dart b/apps/lib/core/api/api_exception.dart
index d306560..d85b8d7 100644
--- a/apps/lib/core/api/api_exception.dart
+++ b/apps/lib/core/api/api_exception.dart
@@ -1,3 +1,5 @@
+import 'dart:convert';
+
import 'package:dio/dio.dart';
abstract class ApiException implements Exception {
@@ -17,9 +19,14 @@ abstract class ApiException implements Exception {
final data = response?.data;
String detail;
- if (data is Map) {
+ final decodedData = _normalizeErrorData(data);
+
+ if (decodedData is Map) {
detail =
- (data['detail'] ?? data['message'] ?? data['error'])?.toString() ??
+ (decodedData['detail'] ??
+ decodedData['message'] ??
+ decodedData['error'])
+ ?.toString() ??
'请求失败';
} else {
detail = _networkErrorMessage(error);
@@ -42,6 +49,29 @@ abstract class ApiException implements Exception {
return const ServerException('网络错误');
}
+ static Map? _normalizeErrorData(dynamic data) {
+ if (data is Map) {
+ return data;
+ }
+ if (data is Map) {
+ return data.map((key, value) => MapEntry(key.toString(), value));
+ }
+ if (data is String && data.trim().isNotEmpty) {
+ try {
+ final decoded = jsonDecode(data);
+ if (decoded is Map) {
+ return decoded;
+ }
+ if (decoded is Map) {
+ return decoded.map((key, value) => MapEntry(key.toString(), value));
+ }
+ } catch (_) {
+ return null;
+ }
+ }
+ return null;
+ }
+
static String _localizeError(String detail, int? statusCode) {
if (statusCode == 403) {
return '没有权限执行此操作';
@@ -50,7 +80,11 @@ abstract class ApiException implements Exception {
return '请求的资源不存在';
}
if (statusCode == 429) {
- return '请求过于频繁,请稍后再试';
+ final normalized = detail.trim();
+ if (normalized.isEmpty || normalized == '请求失败') {
+ return '请求过于频繁,请稍后再试';
+ }
+ return detail;
}
if (statusCode != null && statusCode >= 500) {
return '服务器错误,请稍后再试';
diff --git a/apps/lib/core/notifications/local_notification_service.dart b/apps/lib/core/notifications/local_notification_service.dart
index 62f92ba..7f6df15 100644
--- a/apps/lib/core/notifications/local_notification_service.dart
+++ b/apps/lib/core/notifications/local_notification_service.dart
@@ -1,12 +1,23 @@
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
+import 'package:flutter/foundation.dart';
import 'package:timezone/data/latest.dart' as tz_data;
import 'package:timezone/timezone.dart' as tz;
import '../../features/calendar/data/models/schedule_item_model.dart';
+class NotificationScheduleException implements Exception {
+ final String message;
+
+ NotificationScheduleException(this.message);
+
+ @override
+ String toString() => message;
+}
+
class LocalNotificationService {
final FlutterLocalNotificationsPlugin _plugin;
bool _initialized = false;
+ bool _exactAlarmPermissionRequested = false;
LocalNotificationService({FlutterLocalNotificationsPlugin? plugin})
: _plugin = plugin ?? FlutterLocalNotificationsPlugin();
@@ -27,17 +38,17 @@ class LocalNotificationService {
await _plugin.initialize(settings);
- await _plugin
+ final androidImpl = _plugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin
- >()
- ?.requestNotificationsPermission();
+ >();
+ await androidImpl?.requestNotificationsPermission();
- await _plugin
+ final iosImpl = _plugin
.resolvePlatformSpecificImplementation<
IOSFlutterLocalNotificationsPlugin
- >()
- ?.requestPermissions(alert: true, badge: true, sound: true);
+ >();
+ await iosImpl?.requestPermissions(alert: true, badge: true, sound: true);
_initialized = true;
}
@@ -59,6 +70,41 @@ class LocalNotificationService {
final notificationId = _notificationIdForEvent(event.id);
final scheduledAt = tz.TZDateTime.from(fireAt, tz.local);
+ final androidImpl = _plugin
+ .resolvePlatformSpecificImplementation<
+ AndroidFlutterLocalNotificationsPlugin
+ >();
+
+ var androidScheduleMode = AndroidScheduleMode.exactAllowWhileIdle;
+ if (defaultTargetPlatform == TargetPlatform.android &&
+ androidImpl != null) {
+ var notificationsEnabled =
+ await androidImpl.areNotificationsEnabled() ?? false;
+ if (!notificationsEnabled) {
+ await androidImpl.requestNotificationsPermission();
+ notificationsEnabled =
+ await androidImpl.areNotificationsEnabled() ?? false;
+ }
+ if (!notificationsEnabled) {
+ throw NotificationScheduleException('系统通知权限未开启,无法创建提醒');
+ }
+
+ try {
+ var canScheduleExact =
+ await androidImpl.canScheduleExactNotifications() ?? false;
+ if (!canScheduleExact && !_exactAlarmPermissionRequested) {
+ _exactAlarmPermissionRequested = true;
+ await androidImpl.requestExactAlarmsPermission();
+ canScheduleExact =
+ await androidImpl.canScheduleExactNotifications() ?? false;
+ }
+ if (!canScheduleExact) {
+ androidScheduleMode = AndroidScheduleMode.inexactAllowWhileIdle;
+ }
+ } catch (_) {
+ androidScheduleMode = AndroidScheduleMode.inexactAllowWhileIdle;
+ }
+ }
final details = NotificationDetails(
android: AndroidNotificationDetails(
@@ -83,11 +129,20 @@ class LocalNotificationService {
_buildReminderBody(event, reminderMinutes),
scheduledAt,
details,
- androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
+ androidScheduleMode: androidScheduleMode,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
);
- } catch (_) {
+
+ final pending = await _plugin.pendingNotificationRequests();
+ final scheduled = pending.any((item) => item.id == notificationId);
+ if (!scheduled) {
+ throw NotificationScheduleException('提醒未被系统接受,请检查系统通知和电池优化设置');
+ }
+ } catch (error) {
+ if (error is NotificationScheduleException) {
+ rethrow;
+ }
await _plugin.zonedSchedule(
notificationId,
event.title,
@@ -98,6 +153,12 @@ class LocalNotificationService {
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
);
+
+ final pending = await _plugin.pendingNotificationRequests();
+ final scheduled = pending.any((item) => item.id == notificationId);
+ if (!scheduled) {
+ throw NotificationScheduleException('提醒创建失败,请检查系统设置后重试');
+ }
}
}
diff --git a/apps/lib/features/calendar/ui/widgets/create_event_sheet.dart b/apps/lib/features/calendar/ui/widgets/create_event_sheet.dart
index fbb5760..c8c1907 100644
--- a/apps/lib/features/calendar/ui/widgets/create_event_sheet.dart
+++ b/apps/lib/features/calendar/ui/widgets/create_event_sheet.dart
@@ -669,9 +669,9 @@ class _CreateEventSheetState extends State
try {
final notificationService = sl();
await notificationService.upsertEventReminder(saved);
- } catch (_) {
+ } catch (e) {
if (mounted) {
- Toast.show(context, '提醒创建失败,请检查通知权限', type: ToastType.warning);
+ Toast.show(context, '提醒创建失败:$e', type: ToastType.warning);
}
}
diff --git a/apps/lib/features/chat/ui/widgets/ui_schema_renderer.dart b/apps/lib/features/chat/ui/widgets/ui_schema_renderer.dart
index 0821cc9..d644835 100644
--- a/apps/lib/features/chat/ui/widgets/ui_schema_renderer.dart
+++ b/apps/lib/features/chat/ui/widgets/ui_schema_renderer.dart
@@ -75,7 +75,6 @@ class UiSchemaRenderer {
).whereType