diff --git a/README.md b/README.md index fdcb28a..2576ca0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # TPSchedule -学生课表 APP,Flutter 实现,目前接入电子科技大学研究生系统。Android 0.5.2+10,Android 7.0 及以上。 +学生课表 APP,Flutter 实现,目前接入电子科技大学研究生系统。Android 0.5.3+11,Android 7.0 及以上。 使用用户提供的原始 SVG 图标,蓝色渐变、黄色日历框及白色笔画保持一致;启动画面使用相同配色。0.5.1修复重复启动页闪烁:初始化期间保留原生启动屏,数据就绪后直接显示课表首帧,不再叠加Flutter品牌页或淡出动画。Android12+ 原生启动屏使用同色纯蓝背景及图标,旧版采用蓝色渐变。 @@ -10,6 +10,8 @@ 0.5.2按新的Apple Music参考图调整Dock:底座保持通透,增强边缘折射并收窄柔化亮边;静止滑块为明确的半透明中性灰,按住时过渡回原有通透玻璃。滑动时的放大比例、弹簧参数和点击切换逻辑不变。蓝色实心图标保留。详见[0.5.2验证记录](docs/tpschedule-0.5.2-validation.md)。 +0.5.3为蓝色图标增加轻微柔光,下方显示“课表、考试、我的”。文字可随系统字号放大,并在Dock固定尺寸内自动适配。灰色静止滑块、按压放大和回弹保持。详见[0.5.3验证记录](docs/tpschedule-0.5.3-validation.md)。 + ## 安装与学校登录 安装 `dist/TPSchedule.apk`(release运行模式),或使用 `dist/TPSchedule-debug.apk` 测试。Android包名已按用户确认改为 `com.tpschedule.app`,显示名称为TPSchedule。它与旧 `com.example.hepan_everyday` 是两个独立应用,不能覆盖安装旧RSchedule;旧应用与数据保留,新应用需要重新导入课程并设置日期。 diff --git a/docs/tpschedule-0.5.3-validation.md b/docs/tpschedule-0.5.3-validation.md new file mode 100644 index 0000000..ffc62b4 --- /dev/null +++ b/docs/tpschedule-0.5.3-validation.md @@ -0,0 +1,17 @@ +# TPSchedule Android 0.5.3+11:Dock名称与微光 + +日期:2026-10-14。 + +蓝色填充图标增加零偏移、6px模糊半径的轻微柔光,选中图标光晕透明度20%,其他图标8%。图标下方新增“课表、考试、我的”,字号11,单行显示;在固定尺寸内适配系统文字放大。个人页的无障碍说明仍为“个人设置”,触摸区域覆盖图标及名称。 + +Dock尺寸264×68、灰色静止滑块、通透按压效果、放大比例、弹簧参数与点击平滑切换保持。 + +142项完整Flutter测试通过,使用--enable-impeller,静态分析无问题。复用既有导航测试确认点击“我的”、双向拖动、页面不横向切换、滚动隐藏和放大字体;页面标题断言限定到页面内,避免与新增Dock文字混淆。玻璃像素采样避开名称和图标光晕区域,以继续检查玻璃材质。独立源码审查未发现问题。 + +应用名称及ID仍为TPSchedule/com.tpschedule.app,签名配置不变。 + +安卓API36模拟器覆盖安装通过,原课程保留。实际截图确认三项名称和轻微蓝色光晕可见,点击“我的”文字可进入个人页;按压截图保留放大玻璃效果。截图位于dist/screenshots/tpschedule-week.png、tpschedule-profile.png、tpschedule-dock-held.png。模拟器采用软件兼容渲染。 + +发布包versionName0.5.3/versionCode11,无debuggable标记。原证书指纹检查和新旧证书DER比较均通过,公钥与证书未变,私钥未更换或导出。 + +发布APK SHA256:391FBE08BD0DEC1671FB2AD4BF6C7C962B2FDE94422025817A93A644BFBE48E9。 diff --git a/mobile/lib/widgets/glass_dock.dart b/mobile/lib/widgets/glass_dock.dart index 0a4f612..37c0eff 100644 --- a/mobile/lib/widgets/glass_dock.dart +++ b/mobile/lib/widgets/glass_dock.dart @@ -216,7 +216,7 @@ class _GlassDockState extends State with TickerProviderStateMixin { children: [ _item(0, Icons.calendar_month_rounded, '课表'), _item(1, Icons.assignment_rounded, '考试'), - _item(2, Icons.person_rounded, '个人设置'), + _item(2, Icons.person_rounded, '个人设置', caption: '我的'), ], ), ), @@ -226,7 +226,12 @@ class _GlassDockState extends State with TickerProviderStateMixin { ); }, ); - Widget _item(int index, IconData icon, String label) => Expanded( + Widget _item( + int index, + IconData icon, + String label, { + String? caption, + }) => Expanded( child: Semantics( label: label, button: true, @@ -279,18 +284,51 @@ class _GlassDockState extends State with TickerProviderStateMixin { } : null, child: Center( - child: AnimatedScale( - scale: index == _position.round().clamp(0, 2) - ? 1 + _pressure * 0.08 - : 1, - duration: const Duration(milliseconds: 130), - child: Icon( - icon, - size: 27, - color: index == _position.round().clamp(0, 2) - ? _blue - : _blue.withValues(alpha: 0.48), - ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + AnimatedScale( + scale: index == _position.round().clamp(0, 2) + ? 1 + _pressure * 0.08 + : 1, + duration: const Duration(milliseconds: 130), + child: Icon( + icon, + size: 27, + color: index == _position.round().clamp(0, 2) + ? _blue + : _blue.withValues(alpha: 0.48), + shadows: [ + Shadow( + color: _blue.withValues( + alpha: index == _position.round().clamp(0, 2) + ? 0.20 + : 0.08, + ), + blurRadius: 6, + ), + ], + ), + ), + const SizedBox(height: 3), + Flexible( + child: FittedBox( + fit: BoxFit.scaleDown, + child: Text( + caption ?? label, + maxLines: 1, + style: TextStyle( + fontSize: 11, + height: 1.1, + fontWeight: FontWeight.w600, + color: index == _position.round().clamp(0, 2) + ? _blue + : _blue.withValues(alpha: 0.62), + ), + ), + ), + ), + ], ), ), ), diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 914a630..f94c2ff 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -1,7 +1,7 @@ name: hepan_everyday description: TPSchedule 学生课表 publish_to: none -version: 0.5.2+10 +version: 0.5.3+11 environment: sdk: ^3.13.5 dependencies: diff --git a/mobile/test/widgets/glass_dock_test.dart b/mobile/test/widgets/glass_dock_test.dart index db0e0be..8eff167 100644 --- a/mobile/test/widgets/glass_dock_test.dart +++ b/mobile/test/widgets/glass_dock_test.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:hepan_everyday/widgets/glass_dock.dart'; import 'package:hepan_everyday/app/hepan_app.dart'; +import 'package:hepan_everyday/features/timetable/timetable_page.dart'; import 'package:timezone/data/latest.dart' as tzdata; import 'navigation_test.dart' show uiController; @@ -16,7 +17,13 @@ void main() { await c.showDemo(); await tester.pumpWidget(HepanApp(controller: c)); await tester.pumpAndSettle(); - expect(find.text('课表'), findsOneWidget); + expect( + find.descendant( + of: find.byType(TimetablePage), + matching: find.text('课表'), + ), + findsOneWidget, + ); expect(tester.getRect(find.text('周日')).right, lessThanOrEqualTo(800)); await tester.drag(find.text('周三'), const Offset(-250, 0)); await tester.pumpAndSettle(); @@ -36,7 +43,7 @@ void main() { ); await tester.pumpAndSettle(); expect(find.text('示例课表'), findsOneWidget); - await tester.tap(find.bySemanticsLabel('个人设置')); + await tester.tap(find.text('我的')); await tester.pumpAndSettle(); expect(find.text('登录学校账户'), findsOneWidget); c.dispose(); diff --git a/mobile/test/widgets/glass_material_test.dart b/mobile/test/widgets/glass_material_test.dart index 7e9f278..0140c38 100644 --- a/mobile/test/widgets/glass_material_test.dart +++ b/mobile/test/widgets/glass_material_test.dart @@ -18,10 +18,11 @@ void main() { backdropColor: Colors.white, preview: true, ); - final selected = _channel(resting, 46, 52, 0); + // Sample beside the visible label, away from icon glow and glass rims. + final selected = _channel(resting, 22, 52, 0); final base = _channel(resting, 160, 52, 0); expect(base - selected, greaterThan(20)); - expect((_channel(resting, 46, 52, 2) - selected).abs(), lessThan(12)); + expect((_channel(resting, 22, 52, 2) - selected).abs(), lessThan(12)); final held = await _render( tester, GlassDock(selectedIndex: 0, onSelected: (_) {}), @@ -29,7 +30,7 @@ void main() { held: true, preview: true, ); - expect(_channel(held, 46, 52, 0) - selected, greaterThan(18)); + expect(_channel(held, 22, 52, 0) - selected, greaterThan(18)); }); testWidgets( 'glass softens fine background detail without whitening its colour', @@ -60,9 +61,9 @@ void main() { ); // Below the symbol and away from the rim, the selected lens should retain // this saturated cyan backdrop while applying the new resting grey tint. - final red = _channel(pixels, 46, 52, 0); - final green = _channel(pixels, 46, 52, 1); - final blue = _channel(pixels, 46, 52, 2); + final red = _channel(pixels, 22, 52, 0); + final green = _channel(pixels, 22, 52, 1); + final blue = _channel(pixels, 22, 52, 2); expect(red, lessThan(85)); expect(green - red, greaterThan(95)); expect(blue - red, greaterThan(140)); @@ -80,7 +81,7 @@ void main() { ); for (final start in [32, 160]) { final reds = [ - for (var x = start; x < start + 20; x++) _channel(pixels, x, 52, 0), + for (var x = start; x < start + 20; x++) _channel(pixels, x, 60, 0), ]; final contrast = reds.reduce((a, b) => a > b ? a : b) - @@ -103,7 +104,7 @@ void main() { ); for (final start in [24, 160]) { final reds = [ - for (var x = start; x < start + 40; x++) _channel(pixels, x, 52, 0), + for (var x = start; x < start + 40; x++) _channel(pixels, x, 60, 0), ]; final contrast = reds.reduce((a, b) => a > b ? a : b) - diff --git a/mobile/test/widgets/rschedule_navigation_test.dart b/mobile/test/widgets/rschedule_navigation_test.dart index fef12da..03660fe 100644 --- a/mobile/test/widgets/rschedule_navigation_test.dart +++ b/mobile/test/widgets/rschedule_navigation_test.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:hepan_everyday/app/hepan_app.dart'; +import 'package:hepan_everyday/features/timetable/timetable_page.dart'; import 'package:timezone/data/latest.dart' as tzdata; import 'navigation_test.dart' show uiController; @@ -19,7 +20,13 @@ void main() { await c.showDemo(); await tester.pumpWidget(HepanApp(controller: c)); await tester.pumpAndSettle(); - expect(find.text('课表'), findsOneWidget); + expect( + find.descendant( + of: find.byType(TimetablePage), + matching: find.text('课表'), + ), + findsOneWidget, + ); expect(find.bySemanticsLabel('考试'), findsOneWidget); await tester.drag(find.text('周三'), const Offset(0, -200)); await tester.pumpAndSettle(); diff --git a/mobile/test/widgets/term_prompt_test.dart b/mobile/test/widgets/term_prompt_test.dart index f180339..d3b040b 100644 --- a/mobile/test/widgets/term_prompt_test.dart +++ b/mobile/test/widgets/term_prompt_test.dart @@ -67,7 +67,8 @@ void main() { await tester.pumpAndSettle(); await tester.tap(find.bySemanticsLabel('考试')); await tester.pumpAndSettle(); - expect(find.text('考试'), findsOneWidget); + // The page heading and Dock caption both show the active tab name. + expect(find.text('考试'), findsNWidgets(2)); expect(find.text('考试安排'), findsOneWidget); await tester.tap(find.bySemanticsLabel('个人设置')); await tester.pumpAndSettle();