Add Dock captions and subtle blue icon glow
This commit is contained in:
@@ -216,7 +216,7 @@ class _GlassDockState extends State<GlassDock> 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<GlassDock> 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<GlassDock> 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),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
+1
-1
@@ -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:
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) -
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user