diff --git a/README.md b/README.md index 1bf9e28..bbbe68c 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # TPSchedule -当前Android版本0.7.8+26:本科和研究生课表/考试均可选附带HTML反馈,保留原签名,个人页展示蜀ICP备2025169276号-5A。详见[0.7.8验证记录](docs/tpschedule-0.7.8-validation.md)及[反馈服务更新说明](server/feedback/html-diagnostic-update.md)。0.7.6起支持启动/手动检查更新与浏览器下载,附带[独立发布后台](server/updates/README.md)。 +当前Android版本0.7.8+27:Dock增加乳白透光层、弧形反光和边缘折射,见[材质优化记录](docs/tpschedule-0.7.8-dock-refinement.md)。本科和研究生课表/考试均可选附带HTML反馈,保留原签名,个人页展示蜀ICP备2025169276号-5A。详见[0.7.8功能记录](docs/tpschedule-0.7.8-validation.md)及[反馈服务更新说明](server/feedback/html-diagnostic-update.md)。0.7.6起支持启动/手动检查更新与浏览器下载,附带[独立发布后台](server/updates/README.md)。 0.7.0补充:考试页查询类型与卡片保持16像素间距;反馈问题和建议可自选附带最近10分钟的登录/读取/提醒诊断日志。默认不发送,不含密码、Cookie或课表内容,单独作为邮件文本附件。服务端需按[日志附件更新说明](server/feedback/diagnostic-log-update.md)手动更新;真实学校登录及SMTP未由本次离线测试代替验证。玻璃取样偏移仍在调查,未纳入本次修复结论。 -学生课表 APP,Flutter 实现,支持电子科技大学研究生和本科生系统。Android 0.7.8+26,Android 7.0 及以上。 +学生课表 APP,Flutter 实现,支持电子科技大学研究生和本科生系统。Android 0.7.8+27,Android 7.0 及以上。 使用用户提供的原始 SVG 图标,蓝色渐变、黄色日历框及白色笔画保持一致;启动画面使用相同配色。0.5.1修复重复启动页闪烁:初始化期间保留原生启动屏,数据就绪后直接显示课表首帧,不再叠加Flutter品牌页或淡出动画。Android12+ 原生启动屏使用同色纯蓝背景及图标,旧版采用蓝色渐变。 diff --git a/docs/android-mvp-validation.md b/docs/android-mvp-validation.md index b95a6ee..74e82d3 100644 --- a/docs/android-mvp-validation.md +++ b/docs/android-mvp-validation.md @@ -1,6 +1,6 @@ # Android 首版验证记录 -当前版本为0.7.8+26,本科/研究生HTML反馈及签名、备案信息见[0.7.8记录](tpschedule-0.7.8-validation.md)。应用更新检查与后台说明见[0.7.6记录](tpschedule-0.7.6-updates.md)。本科考试兼容的验证见[0.7.5记录](tpschedule-0.7.5-validation.md)。两例真实课表缺失字段问题待新页面定位。本科接入记录见[0.7.0记录](tpschedule-0.7.0-validation.md)。下文为首版历史记录。 +当前版本为0.7.8+27,Dock乳白透光材质见[优化记录](tpschedule-0.7.8-dock-refinement.md)。本科/研究生HTML反馈及签名、备案信息见[0.7.8功能记录](tpschedule-0.7.8-validation.md)。应用更新检查与后台说明见[0.7.6记录](tpschedule-0.7.6-updates.md)。本科考试兼容的验证见[0.7.5记录](tpschedule-0.7.5-validation.md)。两例真实课表缺失字段问题待新页面定位。本科接入记录见[0.7.0记录](tpschedule-0.7.0-validation.md)。下文为首版历史记录。 日期:2026-10-13。测试环境:Flutter 3.47.7 / Dart 3.13.5 / JDK 17;Android 16 API 36,Pixel 7 模拟器,1080×2400。首版版本 0.1.0+1,包 ID com.example.hepan_everyday,最低 Android 7.0(API 24)。 diff --git a/docs/tpschedule-0.7.8-dock-refinement.md b/docs/tpschedule-0.7.8-dock-refinement.md new file mode 100644 index 0000000..55ca941 --- /dev/null +++ b/docs/tpschedule-0.7.8-dock-refinement.md @@ -0,0 +1,25 @@ +# TPSchedule 0.7.8+27:乳白透光液态玻璃Dock + +2026-10-24。用户指出参考图中的液态玻璃带白色材质与折射,要求优化现有Dock。此次只调整材质、着色器和发布构建号,沿用0.7.8版本名及原包名、签名、备案号。 + +## 视觉调整 + +- Dock基底由近乎透明的5%/0.8%/1.8%叠色改为24%/12%/18%的白色渐变,带柔和乳白透光层。 +- 加强弧形内外亮边及方向反光,优化柔和悬浮阴影,表现边缘厚度;避免叠加的阴影被玻璃取样后使白色主体变灰。 +- 折射边缘位移系数由0.20调至0.23,并加入掠射角白色反射;背景仍实时取样。保留原坐标、纹理方向和像素密度处理,不改折射定位。 +- 基底轻度模糊1.5逻辑像素,滑块仍随按压减少模糊。静止灰色滑块、蓝色图标、白光晕和原放大/回弹保留。 +- Dock尺寸264×58、常驻、图标文字及点击/拖动规则不变。当前页面导航、学校导入、通知与反馈服务未改。 + +## 验证范围 + +增加深色背景上的实际像素测试,要求白色材质能提亮背景且继续透出原色。调整旧透明材质的颜色预期为乳白材质,要求饱和背景至少保留一半色差;原细纹柔化、大轮廓透出、移动后折射对齐、点按不抖、放大回弹、取消拖动与减少动画测试继续执行。修改前深色背景测试红通道27,不满足乳白提亮要求;修改后通过。 + +着色器支持时使用实时背景折射;不支持的渲染方式沿用原轻度模糊、乳白叠色和边缘绘制,不伪称支持真实折射。 + +最终303项Flutter测试通过、静态分析无问题,15项材质/运动/导航测试通过,正式APK构建成功。Android16/API36模拟器使用软件渲染启动参数覆盖安装及冷启动成功,TotalTime2899ms,检查了实际首页与Dock,未见AndroidRuntime致命错误。本机默认GPU试启动时模拟器进程退出;此轮原生截图属于模糊回退路径,不能替代真机折射效果验证。支持着色器的像素测试覆盖动态取样位置、大轮廓透出和细纹柔化。独立代码审查未发现需修正的问题。 + +新APK包名com.tpschedule.app,versionName0.7.8、versionCode27;apksigner验证签名有效,证书MD5为78DE9119AD14727FA1C5E5E4E68145EE,与原构建26证书逐字节一致,RSA公钥与用户提供值一致。密钥与0.5.4登记冻结材料不改动。 + +## 发布 + +版本号0.7.8,构建号27;保留构建26的安装包。更新后台可使用相同版本名与更高构建号发布,链接须对应本次真实APK。反馈服务器不因此次Dock优化产生新部署步骤。 diff --git a/mobile/lib/app/app_version.dart b/mobile/lib/app/app_version.dart index 70c6d4b..ae0aab7 100644 --- a/mobile/lib/app/app_version.dart +++ b/mobile/lib/app/app_version.dart @@ -1,3 +1,3 @@ const appVersion = '0.7.8'; -const appBuildNumber = 26; +const appBuildNumber = 27; const appFilingNumber = '蜀ICP备2025169276号-5A'; diff --git a/mobile/lib/widgets/glass_dock.dart b/mobile/lib/widgets/glass_dock.dart index de70a20..5c75334 100644 --- a/mobile/lib/widgets/glass_dock.dart +++ b/mobile/lib/widgets/glass_dock.dart @@ -142,9 +142,9 @@ class _GlassDockState extends State with TickerProviderStateMixin { )!, ] : [ - Colors.white.withValues(alpha: 0.05), - _blue.withValues(alpha: 0.008), - Colors.white.withValues(alpha: 0.018), + Colors.white.withValues(alpha: 0.24), + const Color(0xFFF9FBFF).withValues(alpha: 0.12), + Colors.white.withValues(alpha: 0.18), ], ), ), @@ -154,8 +154,8 @@ class _GlassDockState extends State with TickerProviderStateMixin { return ClipRRect( borderRadius: BorderRadius.circular(40), child: GlassRefraction( - strength: lens ? 0.70 + pressure * 0.70 : 0.95, - blurSigma: lens ? 1.6 - pressure * 0.6 : 1.4, + strength: lens ? 0.78 + pressure * 0.70 : 1.05, + blurSigma: lens ? 1.6 - pressure * 0.6 : 1.5, child: content, ), ); @@ -188,8 +188,8 @@ class _GlassDockState extends State with TickerProviderStateMixin { boxShadow: [ BoxShadow( color: const Color(0xFF4C6078).withValues(alpha: 0.075), - blurRadius: 16, - offset: const Offset(0, 5), + blurRadius: 18, + offset: const Offset(0, 6), ), ], ), @@ -352,15 +352,15 @@ class _GlassDockState extends State with TickerProviderStateMixin { ); } -/// A diffuse silver bevel and thin directional glints show optical thickness. -/// The painted highlights stay on the edge so background colour passes through. +/// A white, rounded bevel catches light around a translucent milky body. +/// Highlights stay at the edge; the backdrop remains visible beneath the glass. class _GlassRimPainter extends CustomPainter { const _GlassRimPainter({required this.lens, required this.pressure}); final bool lens; final double pressure; @override void paint(Canvas canvas, Size size) { - final edge = lens ? const Color(0xFF7B838F) : const Color(0xFFAFBECF); + final edge = lens ? const Color(0xFF7B838F) : const Color(0xFF9CAABD); // A quiet grey selection at rest; the glossy lens rim returns on touch. final opacity = lens ? 0.25 + pressure * 0.75 : 1.0; final bounds = Offset.zero & size; @@ -376,15 +376,15 @@ class _GlassRimPainter extends CustomPainter { outer, Paint() ..style = PaintingStyle.stroke - ..strokeWidth = lens ? 1.8 : 2.2 - ..maskFilter = MaskFilter.blur(BlurStyle.normal, lens ? 0.9 : 0.45) + ..strokeWidth = lens ? 1.8 : 2.8 + ..maskFilter = MaskFilter.blur(BlurStyle.normal, lens ? 0.9 : 0.65) ..shader = ui.Gradient.linear( bounds.topLeft, bounds.bottomRight, [ - Colors.white.withValues(alpha: 0.24 * opacity), + Colors.white.withValues(alpha: (lens ? 0.24 : 0.42) * opacity), edge.withValues(alpha: 0.04 * opacity), - Colors.white.withValues(alpha: 0.14 * opacity), + Colors.white.withValues(alpha: (lens ? 0.14 : 0.30) * opacity), ], [0.0, 0.55, 1.0], ), @@ -419,9 +419,9 @@ class _GlassRimPainter extends CustomPainter { bounds.topLeft, bounds.bottomRight, [ - Colors.white.withValues(alpha: 0.20 * opacity), + Colors.white.withValues(alpha: (lens ? 0.20 : 0.34) * opacity), edge.withValues(alpha: (lens ? 0.13 : 0.07) * opacity), - Colors.white.withValues(alpha: 0.28 * opacity), + Colors.white.withValues(alpha: (lens ? 0.28 : 0.40) * opacity), ], [0.0, 0.5, 1.0], ), diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index f474a98..97466a5 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -1,7 +1,7 @@ name: hepan_everyday description: TPSchedule 学生课表 publish_to: none -version: 0.7.8+26 +version: 0.7.8+27 environment: sdk: ^3.13.5 dependencies: diff --git a/mobile/shaders/liquid_glass.frag b/mobile/shaders/liquid_glass.frag index 2a460cb..b3609c6 100644 --- a/mobile/shaders/liquid_glass.frag +++ b/mobile/shaders/liquid_glass.frag @@ -29,7 +29,7 @@ void main() { float rim = 1.0 - smoothstep(0.0, radius * 0.46, depth); // Curved glass bends rays most at its edge, with subtle centre magnification. vec2 ray = centre + local / (1.0 + u_strength * 0.075) - - normal * rim * rim * radius * u_strength * 0.20; + - normal * rim * rim * radius * u_strength * 0.23; vec4 colour = texture(u_background, sampleUV(ray)); float dispersion = u_strength * rim * 0.65; colour.r = texture(u_background, sampleUV(ray + normal * dispersion)).r; @@ -41,6 +41,9 @@ void main() { vec4 reflected = texture(u_background, sampleUV(point + normal * radius * 0.40)); colour = mix(colour, reflected, min(edge_light * 0.18, 0.22)); + // A grazing-angle white reflection gives the curved rim visible thickness; + // the broad centre continues to transmit the live, lightly softened scene. + colour.rgb = mix(colour.rgb, vec3(colour.a), min(edge_light * 0.12, 0.16)); float reflection = edge_light * 0.07; colour.rgb = min(colour.rgb + reflection * colour.a, vec3(colour.a)); frag_color = colour; diff --git a/mobile/test/widgets/glass_material_test.dart b/mobile/test/widgets/glass_material_test.dart index 4820f0c..3697dbc 100644 --- a/mobile/test/widgets/glass_material_test.dart +++ b/mobile/test/widgets/glass_material_test.dart @@ -9,6 +9,25 @@ import 'package:hepan_everyday/widgets/glass_dock.dart'; import 'package:hepan_everyday/widgets/glass_refraction.dart'; void main() { + testWidgets( + 'Dock has a milky white body while transmitting a dark backdrop', + (tester) async { + final pixels = await _render( + tester, + GlassDock(selectedIndex: 0, onSelected: (_) {}), + backdropColor: const Color(0xFF162536), + preview: true, + previewName: 'liquid-glass-milky-dark.png', + ); + // Away from icons and the bevel: white glass lifts the dark background, + // while its original cool colour remains visible through the material. + final red = _channel(pixels, 160, 43, 0); + final green = _channel(pixels, 160, 43, 1); + expect(red, greaterThan(45)); + expect(red, lessThan(90)); + expect(green - red, closeTo(13, 6)); + }, + ); testWidgets('resting cursor is neutral grey and clears while held', ( tester, ) async { @@ -21,6 +40,8 @@ void main() { // Sample beside the visible label, away from icon glow and glass rims. final selected = _channel(resting, 16, 43, 0); final base = _channel(resting, 160, 43, 0); + // On a white page the white glass must not turn into a grey veil. + expect(base, greaterThanOrEqualTo(240)); expect(base - selected, greaterThan(20)); expect((_channel(resting, 16, 43, 2) - selected).abs(), lessThan(12)); final held = await _render( @@ -64,11 +85,11 @@ void main() { final red = _channel(pixels, 16, 40, 0); final green = _channel(pixels, 16, 40, 1); final blue = _channel(pixels, 16, 40, 2); - expect(red, lessThan(85)); - expect(green - red, greaterThan(95)); - // The strongest resting tint (34%) and base veil (5%) retain about 63% - // of the source's 210-level blue/red contrast, including the shorter rim. - expect(blue - red, greaterThan(130)); + expect(red, lessThan(115)); + expect(green - red, greaterThanOrEqualTo(80)); + // The milky base and resting grey lens still transmit over half of the + // cyan source's 210-level blue/red contrast rather than becoming opaque. + expect(blue - red, greaterThan(105)); }, ); @@ -166,6 +187,7 @@ Future _render( double stripeWidth = 2, bool gradient = false, bool preview = false, + String? previewName, Offset offset = Offset.zero, Color backdropColor = const Color(0xFF14B4E6), bool held = false, @@ -246,11 +268,13 @@ Future _render( final png = (await previewImage.toByteData( format: ui.ImageByteFormat.png, ))!; - final name = backdropColor == Colors.white - ? held - ? 'liquid-glass-held.png' - : 'liquid-glass-rest.png' - : 'liquid-glass-detail.png'; + final name = + previewName ?? + (backdropColor == Colors.white + ? held + ? 'liquid-glass-held.png' + : 'liquid-glass-rest.png' + : 'liquid-glass-detail.png'); final output = File('../dist/screenshots/$name'); await output.parent.create(recursive: true); await output.writeAsBytes(png.buffer.asUint8List());