fix(train): the Record sheet's music choice on a row of its own, each label on one line
The segmented choice of Music, Metronome and Off sat in the music tile's subtitle, beside its icon, where a phone of 360 dp wrapped "Metronome" into "Metronom/e". It takes the list's whole width now, under its title, each label on one line and scaled down where larger text still leaves too little room. ADR 0030, D65. Whats-New: The Record sheet's Music, Metronome and Off each fit on one line. Whats-New-De: Musik, Metronom und Aus passen im Aufzeichnen-Blatt jeweils auf eine Zeile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
d305725bb5
commit
0c2d4ffecc
3 changed files with 87 additions and 26 deletions
|
|
@ -307,33 +307,41 @@ class _RecordSheetState() extends State<_RecordSheet> {
|
||||||
final chosen = _music == MusicChoice.music && !hasMusic
|
final chosen = _music == MusicChoice.music && !hasMusic
|
||||||
? MusicChoice.off
|
? MusicChoice.off
|
||||||
: _music;
|
: _music;
|
||||||
return ListTile(
|
// The choice on a row of its own, as wide as the list, each label on
|
||||||
leading: const Icon(LucideIcons.music),
|
// one line, smaller where it still doesn't fit: beside the icon, a
|
||||||
title: Text(l10n.recordMusic),
|
// phone wrapped "Metronome" in two (D65).
|
||||||
subtitle: Padding(
|
return Column(
|
||||||
padding: const EdgeInsets.only(top: 8),
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
child: SegmentedButton<MusicChoice>(
|
children: [
|
||||||
showSelectedIcon: false,
|
ListTile(
|
||||||
segments: [
|
leading: const Icon(LucideIcons.music),
|
||||||
ButtonSegment(
|
title: Text(l10n.recordMusic),
|
||||||
value: MusicChoice.music,
|
|
||||||
label: Text(l10n.recordMusicOn),
|
|
||||||
enabled: hasMusic,
|
|
||||||
),
|
|
||||||
ButtonSegment(
|
|
||||||
value: MusicChoice.metronome,
|
|
||||||
label: Text(l10n.recordMetronome),
|
|
||||||
),
|
|
||||||
ButtonSegment(
|
|
||||||
value: MusicChoice.off,
|
|
||||||
label: Text(l10n.recordMusicOff),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
selected: {chosen},
|
|
||||||
onSelectionChanged: (choice) =>
|
|
||||||
setState(() => _music = choice.single),
|
|
||||||
),
|
),
|
||||||
),
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
|
||||||
|
child: SegmentedButton<MusicChoice>(
|
||||||
|
showSelectedIcon: false,
|
||||||
|
segments: [
|
||||||
|
ButtonSegment(
|
||||||
|
value: MusicChoice.music,
|
||||||
|
label: _OneLine(l10n.recordMusicOn),
|
||||||
|
enabled: hasMusic,
|
||||||
|
),
|
||||||
|
ButtonSegment(
|
||||||
|
value: MusicChoice.metronome,
|
||||||
|
label: _OneLine(l10n.recordMetronome),
|
||||||
|
),
|
||||||
|
ButtonSegment(
|
||||||
|
value: MusicChoice.off,
|
||||||
|
label: _OneLine(l10n.recordMusicOff),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
selected: {chosen},
|
||||||
|
onSelectionChanged: (choice) =>
|
||||||
|
setState(() => _music = choice.single),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -478,3 +486,12 @@ Future<RecordingState> startedOrRefused(RecordingBloc bloc) =>
|
||||||
state.bluetoothRefused ||
|
state.bluetoothRefused ||
|
||||||
state.rowerMissing,
|
state.rowerMissing,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// A segment's label on one line, scaled down where it doesn't fit.
|
||||||
|
class const _OneLine(final String text) extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Text(text, maxLines: 1, softWrap: false),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import 'dart:io';
|
||||||
|
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:flutter/gestures.dart' show kLongPressTimeout;
|
import 'package:flutter/gestures.dart' show kLongPressTimeout;
|
||||||
|
import 'package:flutter/rendering.dart' show RenderParagraph;
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:halkyon_app/halkyon_app.dart';
|
import 'package:halkyon_app/halkyon_app.dart';
|
||||||
|
|
@ -69,6 +70,7 @@ import 'package:halkyon_ui/halkyon_ui.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:http/testing.dart';
|
import 'package:http/testing.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
|
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||||
import 'package:material_ui/material_ui.dart';
|
import 'package:material_ui/material_ui.dart';
|
||||||
|
|
||||||
import '../support/fake_firmware_relay.dart';
|
import '../support/fake_firmware_relay.dart';
|
||||||
|
|
@ -2193,6 +2195,43 @@ void main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets("the music's choice keeps each label on one line on a phone, "
|
||||||
|
'its text larger (D65)', (tester) async {
|
||||||
|
await withClock(Clock(() => at), () async {
|
||||||
|
await startRecording(tester);
|
||||||
|
tester.view.physicalSize = const Size(360, 800);
|
||||||
|
tester.platformDispatcher.textScaleFactorTestValue = 1.3;
|
||||||
|
addTearDown(tester.platformDispatcher.clearTextScaleFactorTestValue);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(startFor());
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.ensureVisible(find.text('Metronome'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
for (final label in ['Music', 'Metronome', 'Off']) {
|
||||||
|
final text = find.descendant(
|
||||||
|
of: find.byType(SegmentedButton<MusicChoice>),
|
||||||
|
matching: find.text(label),
|
||||||
|
);
|
||||||
|
final paragraph = tester.renderObject<RenderParagraph>(text);
|
||||||
|
final tops = paragraph
|
||||||
|
.getBoxesForSelection(
|
||||||
|
TextSelection(baseOffset: 0, extentOffset: label.length),
|
||||||
|
)
|
||||||
|
.map((box) => box.top)
|
||||||
|
.toSet();
|
||||||
|
expect(tops, hasLength(1), reason: '$label on one line');
|
||||||
|
}
|
||||||
|
// On a row of its own, as wide as the sheet's list less its margins.
|
||||||
|
final segments = tester.getRect(
|
||||||
|
find.byType(SegmentedButton<MusicChoice>),
|
||||||
|
);
|
||||||
|
final icon = tester.getRect(find.byIcon(LucideIcons.music));
|
||||||
|
expect(segments.left, lessThanOrEqualTo(icon.left));
|
||||||
|
expect(segments.top, greaterThan(icon.bottom));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets("a ride offers the bike's sensors, found nearby and chosen; "
|
testWidgets("a ride offers the bike's sensors, found nearby and chosen; "
|
||||||
"a run doesn't (D49)", (tester) async {
|
"a run doesn't (D49)", (tester) async {
|
||||||
platform.nearbyBike = [
|
platform.nearbyBike = [
|
||||||
|
|
|
||||||
|
|
@ -1714,6 +1714,11 @@ Strava. What was found, 27 Oct:
|
||||||
is on and Train allowed the precise one (Android's approximate location gives no
|
is on and Train allowed the precise one (Android's approximate location gives no
|
||||||
GPS fixes).
|
GPS fixes).
|
||||||
|
|
||||||
|
- **The Record sheet's music on one line**: its choice (Music, Metronome, Off) sat in
|
||||||
|
the tile's subtitle beside the icon, where a phone wrapped "Metronome" in two. It
|
||||||
|
has a row of its own now, as wide as the list, each label on one line and scaled
|
||||||
|
down where it still doesn't fit (larger text), rather than shortened labels or a
|
||||||
|
menu, which would hide the choice behind a tap.
|
||||||
## Consequences
|
## Consequences
|
||||||
|
|
||||||
- Train's sensors are one pattern: open profiles read by the app itself, on the phone;
|
- Train's sensors are one pattern: open profiles read by the app itself, on the phone;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue