reader pin UX: push-pin icon, toggle, left/right split
Per user feedback on the reader action bar: use the push-pin glyph (not the chain/link), make the pin button toggle the pinned state (tap to pin current, tap again to unpin) via ReaderNav.togglePin, and split the layout so the pin/unpin toggle sits on the left while jump-to-pin joins the navigator (back/forward) on the right — left toggles, right navigates. The action button gains an active (accent) state for the pinned indicator. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -725,7 +725,7 @@ void main() {
|
||||
|
||||
await tester.tap(find
|
||||
.byWidgetPredicate(
|
||||
(w) => w is Semantics && w.properties.label == 'Pin current',
|
||||
(w) => w is Semantics && w.properties.label == 'Pin',
|
||||
)
|
||||
.first);
|
||||
await pumpAsync(tester);
|
||||
@@ -742,7 +742,7 @@ void main() {
|
||||
// Pin D-1.
|
||||
await tester.tap(find
|
||||
.byWidgetPredicate(
|
||||
(w) => w is Semantics && w.properties.label == 'Pin current',
|
||||
(w) => w is Semantics && w.properties.label == 'Pin',
|
||||
)
|
||||
.first);
|
||||
await pumpAsync(tester);
|
||||
@@ -763,41 +763,32 @@ void main() {
|
||||
expect(find.text('Decision D-1'), findsWidgets);
|
||||
});
|
||||
|
||||
testWidgets('pin replaces previous pin', (tester) async {
|
||||
testWidgets('pin toggles off on a second tap (unpin)', (tester) async {
|
||||
await pumpView(tester, initialId: 'D-1');
|
||||
|
||||
// Pin D-1.
|
||||
// Pin D-1 → the jump-to-pin button appears in the navigator.
|
||||
await tester.tap(find
|
||||
.byWidgetPredicate(
|
||||
(w) => w is Semantics && w.properties.label == 'Pin current',
|
||||
(w) => w is Semantics && w.properties.label == 'Pin',
|
||||
)
|
||||
.first);
|
||||
await pumpAsync(tester);
|
||||
expect(
|
||||
find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Jump to pin'),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
// Navigate to D-2.
|
||||
await open(tester, 'D-2');
|
||||
|
||||
// Replace pin with D-2.
|
||||
// Tapping the toggle again (now 'Unpin') clears the pin.
|
||||
await tester.tap(find
|
||||
.byWidgetPredicate(
|
||||
(w) => w is Semantics && w.properties.label == 'Replace pin',
|
||||
(w) => w is Semantics && w.properties.label == 'Unpin',
|
||||
)
|
||||
.first);
|
||||
await pumpAsync(tester);
|
||||
|
||||
// Navigate to D-3.
|
||||
await open(tester, 'D-3');
|
||||
|
||||
// Jump to pin — should go to D-2 (replaced), not D-1.
|
||||
await tester.tap(find
|
||||
.byWidgetPredicate(
|
||||
(w) => w is Semantics && w.properties.label == 'Jump to pin',
|
||||
)
|
||||
.first);
|
||||
await pumpAsync(tester);
|
||||
|
||||
// Title appears in pane header subtitle + body card.
|
||||
expect(find.text('Decision D-2'), findsWidgets);
|
||||
expect(
|
||||
find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Jump to pin'),
|
||||
findsNothing,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -326,7 +326,7 @@ void main() {
|
||||
await loadFile(tester, f, 'a.md');
|
||||
|
||||
expect(
|
||||
find.byWidgetPredicate((w) => w is Semantics && (w.properties.label == 'Pin current' || w.properties.label == 'Replace pin')),
|
||||
find.byWidgetPredicate((w) => w is Semantics && (w.properties.label == 'Pin' || w.properties.label == 'Unpin')),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
@@ -350,7 +350,7 @@ void main() {
|
||||
// Tap Pin current.
|
||||
await tester.tap(find
|
||||
.byWidgetPredicate(
|
||||
(w) => w is Semantics && w.properties.label == 'Pin current',
|
||||
(w) => w is Semantics && w.properties.label == 'Pin',
|
||||
)
|
||||
.first);
|
||||
await pumpAsync(tester);
|
||||
@@ -370,7 +370,7 @@ void main() {
|
||||
// Pin a.md.
|
||||
await tester.tap(find
|
||||
.byWidgetPredicate(
|
||||
(w) => w is Semantics && w.properties.label == 'Pin current',
|
||||
(w) => w is Semantics && w.properties.label == 'Pin',
|
||||
)
|
||||
.first);
|
||||
await pumpAsync(tester);
|
||||
@@ -391,42 +391,34 @@ void main() {
|
||||
expect(find.text('a.md'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('pin replaces previous pin', (tester) async {
|
||||
_stubReadMap(f, {'a.md': 'A', 'b.md': 'B', 'c.md': 'C'});
|
||||
testWidgets('pin toggles off on a second tap (unpin)', (tester) async {
|
||||
_stubReadMap(f, {'a.md': 'A', 'b.md': 'B'});
|
||||
await pumpView(tester, f);
|
||||
await loadFile(tester, f, 'a.md');
|
||||
|
||||
// Pin a.md.
|
||||
// Pin a.md → jump-to-pin appears.
|
||||
await tester.tap(find
|
||||
.byWidgetPredicate(
|
||||
(w) => w is Semantics && w.properties.label == 'Pin current',
|
||||
(w) => w is Semantics && w.properties.label == 'Pin',
|
||||
)
|
||||
.first);
|
||||
await pumpAsync(tester);
|
||||
expect(
|
||||
find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Jump to pin'),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
// Navigate to b.md.
|
||||
await loadFile(tester, f, 'b.md');
|
||||
|
||||
// Replace pin with b.md.
|
||||
// Tap the toggle again (now 'Unpin') → pin cleared.
|
||||
await tester.tap(find
|
||||
.byWidgetPredicate(
|
||||
(w) => w is Semantics && w.properties.label == 'Replace pin',
|
||||
(w) => w is Semantics && w.properties.label == 'Unpin',
|
||||
)
|
||||
.first);
|
||||
await pumpAsync(tester);
|
||||
|
||||
// Navigate to c.md.
|
||||
await loadFile(tester, f, 'c.md');
|
||||
|
||||
// Jump to pin should go to b.md now (replaced), not a.md.
|
||||
await tester.tap(find
|
||||
.byWidgetPredicate(
|
||||
(w) => w is Semantics && w.properties.label == 'Jump to pin',
|
||||
)
|
||||
.first);
|
||||
await pumpAsync(tester);
|
||||
|
||||
expect(find.text('b.md'), findsOneWidget);
|
||||
expect(
|
||||
find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Jump to pin'),
|
||||
findsNothing,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -115,6 +115,15 @@ void main() {
|
||||
expect(loads.last, 'D-1');
|
||||
});
|
||||
|
||||
test('togglePin pins the current entry, then clears it', () {
|
||||
nav.open('D-1');
|
||||
expect(nav.hasPinned, isFalse);
|
||||
nav.togglePin();
|
||||
expect(nav.hasPinned, isTrue);
|
||||
nav.togglePin();
|
||||
expect(nav.hasPinned, isFalse);
|
||||
});
|
||||
|
||||
test('pin replaces the previous pin', () {
|
||||
nav.open('D-1');
|
||||
nav.pin();
|
||||
|
||||
Reference in New Issue
Block a user