feat: 长辈模式(大字大按钮 + 偏好按账号隔离)+ 语音输入(按住说话 + 实时转写 + 后端代理 DashScope ASR + 患者端专用)+ 健康仪表盘背景渐变

This commit is contained in:
MingNian
2026-07-21 10:53:16 +08:00
parent 0cb5b8e85a
commit 28f704c98e
25 changed files with 2214 additions and 131 deletions

View File

@@ -53,8 +53,8 @@ void main() {
source.indexOf('class _HeaderIconButton'),
);
expect(buttonSource, contains('width: 44'));
expect(buttonSource, contains('height: 44'));
expect(buttonSource, contains('width: elderMode ? 54 : 44'));
expect(buttonSource, contains('height: elderMode ? 54 : 44'));
});
test('settings logout action is white with dark text', () {

View File

@@ -0,0 +1,113 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:health_app/core/app_theme.dart';
import 'package:health_app/core/elder_mode_scope.dart';
import 'package:health_app/pages/settings/elder_mode_page.dart';
import 'package:health_app/providers/elder_mode_provider.dart';
class _FakeElderModeNotifier extends ElderModeNotifier {
final bool initiallyEnabled;
_FakeElderModeNotifier(this.initiallyEnabled);
@override
ElderModeState build() => ElderModeState(
preferences: ElderModePreferences(enabled: initiallyEnabled),
isLoaded: true,
);
@override
Future<bool> enable() async {
state = state.copyWith(
preferences: state.preferences.copyWith(enabled: true),
);
return true;
}
@override
Future<bool> disable() async {
state = state.copyWith(
preferences: state.preferences.copyWith(enabled: false),
);
return true;
}
@override
Future<bool> setLargeTextEnabled(bool enabled) async {
state = state.copyWith(
preferences: state.preferences.copyWith(largeTextEnabled: enabled),
);
return true;
}
@override
Future<bool> setVoiceInputEnabled(bool enabled) async {
state = state.copyWith(
preferences: state.preferences.copyWith(voiceInputEnabled: enabled),
);
return true;
}
}
class _ElderModeHarness extends ConsumerWidget {
const _ElderModeHarness();
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(elderModeProvider);
final baseTheme = state.isEnabled
? AppTheme.elderLightTheme
: AppTheme.lightTheme;
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: baseTheme,
home: ElderModeScope(
enabled: state.isEnabled,
largeTextEnabled: state.isEnabled && state.preferences.largeTextEnabled,
child: const ElderModePage(),
),
);
}
}
Future<void> _pumpPage(WidgetTester tester, {required bool enabled}) async {
tester.view.physicalSize = const Size(390, 844);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
await tester.pumpWidget(
ProviderScope(
overrides: [
elderModeProvider.overrideWith(() => _FakeElderModeNotifier(enabled)),
],
child: const _ElderModeHarness(),
),
);
await tester.pumpAndSettle();
}
void main() {
testWidgets('长辈模式可立即开启并显示已开启状态', (tester) async {
await _pumpPage(tester, enabled: false);
expect(find.text('开启长辈模式,操作更清晰'), findsOneWidget);
expect(find.text('立即开启'), findsOneWidget);
await tester.tap(find.text('立即开启'));
await tester.pumpAndSettle();
expect(find.text('长辈模式已开启'), findsOneWidget);
expect(find.text('大图标和大按钮已启用'), findsOneWidget);
expect(tester.takeException(), isNull);
});
testWidgets('长辈模式页面在 390x844 下无布局溢出', (tester) async {
await _pumpPage(tester, enabled: true);
expect(find.text('关闭长辈模式'), findsOneWidget);
expect(find.byType(Switch), findsNWidgets(2));
expect(tester.takeException(), isNull);
});
}

View File

@@ -139,11 +139,11 @@ void main() {
).readAsStringSync();
final drawer = File('lib/widgets/health_drawer.dart').readAsStringSync();
expect(source, contains('width: 18'));
expect(source, contains('height: 18'));
expect(source, contains('width: elderMode ? 24 : 18'));
expect(source, contains('height: elderMode ? 24 : 18'));
expect(source, contains('visual.icon,'));
expect(source, contains('size: 13'));
expect(source, contains('fontSize: 15'));
expect(source, contains('size: elderMode ? 17 : 13'));
expect(source, contains('fontSize: elderMode ? 17 : 15'));
expect(source, contains('gradient: selected'));
expect(source, contains('? AppColors.actionOutlineGradient'));
expect(source, contains('selected ? null : Colors.transparent'));
@@ -165,9 +165,10 @@ void main() {
expect(input, contains('LucideIcons.send'));
expect(input, contains('border: InputBorder.none'));
expect(input, contains('crossAxisAlignment: CrossAxisAlignment.center'));
// 文字模式:附件 / 麦克风 / 发送;语音模式:附件 / 键盘。全部用主渐变。
expect(
RegExp('gradient: AppColors.primaryGradient').allMatches(input).length,
2,
5,
);
expect(input, isNot(contains('Border.all(color: AppColors.borderLight)')));
expect(source, isNot(contains('class _RoundToolButton')));

View File

@@ -0,0 +1,39 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:health_app/providers/elder_mode_provider.dart';
import 'package:health_app/services/realtime_speech_service.dart';
void main() {
test('实时语音地址保留后端基础路径并切换为 WebSocket', () {
expect(
buildRealtimeSpeechEndpoint('http://192.168.1.34:5000').toString(),
'ws://192.168.1.34:5000/api/speech/realtime',
);
expect(
buildRealtimeSpeechEndpoint(
'https://erpapi.datalumina.cn/xiaomai/',
).toString(),
'wss://erpapi.datalumina.cn/xiaomai/api/speech/realtime',
);
});
test('旧版长辈设置升级后默认启用语音输入', () {
final preferences = ElderModePreferences.fromJson({
'schemaVersion': 1,
'enabled': true,
'largeTextEnabled': true,
});
expect(preferences.voiceInputEnabled, isTrue);
});
test('长辈模式语音输入开关可以持久化', () {
const preferences = ElderModePreferences(
enabled: true,
voiceInputEnabled: false,
);
final restored = ElderModePreferences.fromJson(preferences.toJson());
expect(restored.enabled, isTrue);
expect(restored.voiceInputEnabled, isFalse);
});
}