feat: 长辈模式(大字大按钮 + 偏好按账号隔离)+ 语音输入(按住说话 + 实时转写 + 后端代理 DashScope ASR + 患者端专用)+ 健康仪表盘背景渐变
This commit is contained in:
162
health_app/lib/providers/elder_mode_provider.dart
Normal file
162
health_app/lib/providers/elder_mode_provider.dart
Normal file
@@ -0,0 +1,162 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'auth_provider.dart';
|
||||
|
||||
class ElderModePreferences {
|
||||
final bool enabled;
|
||||
final bool largeTextEnabled;
|
||||
final bool voiceInputEnabled;
|
||||
|
||||
const ElderModePreferences({
|
||||
this.enabled = false,
|
||||
this.largeTextEnabled = true,
|
||||
this.voiceInputEnabled = true,
|
||||
});
|
||||
|
||||
ElderModePreferences copyWith({
|
||||
bool? enabled,
|
||||
bool? largeTextEnabled,
|
||||
bool? voiceInputEnabled,
|
||||
}) {
|
||||
return ElderModePreferences(
|
||||
enabled: enabled ?? this.enabled,
|
||||
largeTextEnabled: largeTextEnabled ?? this.largeTextEnabled,
|
||||
voiceInputEnabled: voiceInputEnabled ?? this.voiceInputEnabled,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'schemaVersion': 1,
|
||||
'enabled': enabled,
|
||||
'largeTextEnabled': largeTextEnabled,
|
||||
'voiceInputEnabled': voiceInputEnabled,
|
||||
};
|
||||
|
||||
factory ElderModePreferences.fromJson(Map<String, dynamic> json) {
|
||||
return ElderModePreferences(
|
||||
enabled: json['enabled'] == true,
|
||||
largeTextEnabled: json['largeTextEnabled'] != false,
|
||||
voiceInputEnabled: json['voiceInputEnabled'] != false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ElderModeState {
|
||||
final ElderModePreferences preferences;
|
||||
final bool isLoaded;
|
||||
final bool isSaving;
|
||||
final String? error;
|
||||
|
||||
const ElderModeState({
|
||||
this.preferences = const ElderModePreferences(),
|
||||
this.isLoaded = false,
|
||||
this.isSaving = false,
|
||||
this.error,
|
||||
});
|
||||
|
||||
bool get isEnabled => preferences.enabled;
|
||||
bool get useLargeText => isEnabled && preferences.largeTextEnabled;
|
||||
|
||||
ElderModeState copyWith({
|
||||
ElderModePreferences? preferences,
|
||||
bool? isLoaded,
|
||||
bool? isSaving,
|
||||
String? error,
|
||||
bool clearError = false,
|
||||
}) {
|
||||
return ElderModeState(
|
||||
preferences: preferences ?? this.preferences,
|
||||
isLoaded: isLoaded ?? this.isLoaded,
|
||||
isSaving: isSaving ?? this.isSaving,
|
||||
error: clearError ? null : error ?? this.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final elderModeProvider = NotifierProvider<ElderModeNotifier, ElderModeState>(
|
||||
ElderModeNotifier.new,
|
||||
);
|
||||
|
||||
class ElderModeNotifier extends Notifier<ElderModeState> {
|
||||
static const _storageKeyPrefix = 'accessibility_preferences_v1:user_';
|
||||
String? _currentUserId;
|
||||
|
||||
@override
|
||||
ElderModeState build() {
|
||||
final userId = ref.watch(
|
||||
authProvider.select((state) => state.user?.id),
|
||||
);
|
||||
_currentUserId = userId;
|
||||
if (userId == null || userId.isEmpty) {
|
||||
return const ElderModeState(isLoaded: true);
|
||||
}
|
||||
Future<void>.microtask(_load);
|
||||
return const ElderModeState();
|
||||
}
|
||||
|
||||
String get _storageKey => '$_storageKeyPrefix$_currentUserId';
|
||||
|
||||
Future<void> _load() async {
|
||||
if (_currentUserId == null || _currentUserId!.isEmpty) return;
|
||||
try {
|
||||
final raw = await ref.read(localDbProvider).read(_storageKey);
|
||||
if (raw == null || raw.trim().isEmpty) {
|
||||
state = state.copyWith(isLoaded: true, clearError: true);
|
||||
return;
|
||||
}
|
||||
final decoded = jsonDecode(raw);
|
||||
if (decoded is! Map) throw const FormatException('invalid preferences');
|
||||
state = state.copyWith(
|
||||
preferences: ElderModePreferences.fromJson(
|
||||
Map<String, dynamic>.from(decoded),
|
||||
),
|
||||
isLoaded: true,
|
||||
clearError: true,
|
||||
);
|
||||
} catch (_) {
|
||||
state = state.copyWith(isLoaded: true, error: '长辈模式设置读取失败,已使用默认设置');
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> enable() =>
|
||||
_save(state.preferences.copyWith(enabled: true, largeTextEnabled: true));
|
||||
|
||||
Future<bool> disable() => _save(state.preferences.copyWith(enabled: false));
|
||||
|
||||
Future<bool> setLargeTextEnabled(bool enabled) =>
|
||||
_save(state.preferences.copyWith(largeTextEnabled: enabled));
|
||||
|
||||
Future<bool> setVoiceInputEnabled(bool enabled) =>
|
||||
_save(state.preferences.copyWith(voiceInputEnabled: enabled));
|
||||
|
||||
Future<bool> _save(ElderModePreferences next) async {
|
||||
if (_currentUserId == null || _currentUserId!.isEmpty) {
|
||||
state = state.copyWith(
|
||||
isLoaded: true,
|
||||
error: '请先登录后再调整长辈模式',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
final previous = state.preferences;
|
||||
state = state.copyWith(
|
||||
preferences: next,
|
||||
isLoaded: true,
|
||||
isSaving: true,
|
||||
clearError: true,
|
||||
);
|
||||
try {
|
||||
await ref.read(localDbProvider).write(_storageKey, jsonEncode(next));
|
||||
state = state.copyWith(isSaving: false, clearError: true);
|
||||
return true;
|
||||
} catch (_) {
|
||||
state = state.copyWith(
|
||||
preferences: previous,
|
||||
isSaving: false,
|
||||
error: '保存失败,请稍后重试',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user