Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions lib/ble/band_status_l10n.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Localized [BandStatus] copy, kept out of `ble_state.dart` on purpose: that
// file is pure transport-layer logic (no Flutter, no BuildContext) and is
// unit-tested directly against its literal English text in
// `test/ble_state_test.dart`. This is the render-time wrapper every UI call
// site (devices.dart, pairing.dart, pair_sensor.dart) should use instead of
// reading `.title`/`.reason`/`.fix` straight off a `BandStatus` — same split
// as `sourceState`/`_localizedSourceState` in devices.dart.

import 'package:flutter/widgets.dart' show BuildContext;

import '../l10n/app_localizations.dart';
import 'ble_state.dart' show BandCondition, BandStatus;

BandStatus localizedBandStatus(BuildContext c, BandStatus s) {
final l = AppLocalizations.of(c);
switch (s.condition) {
case BandCondition.bluetoothDenied:
return BandStatus(
s.condition,
l?.bandStatusBluetoothDeniedTitle ?? s.title,
l?.bandStatusBluetoothDeniedReason ?? s.reason,
fix: l?.bandStatusBluetoothDeniedFix ?? s.fix,
);
case BandCondition.bluetoothOff:
return BandStatus(
s.condition,
l?.bandStatusBluetoothOffTitle ?? s.title,
l?.bandStatusBluetoothOffReason ?? s.reason,
fix: l?.bandStatusBluetoothOffFix ?? s.fix,
);
case BandCondition.bluetoothUnsupported:
return BandStatus(
s.condition,
l?.bandStatusBluetoothUnsupportedTitle ?? s.title,
l?.bandStatusBluetoothUnsupportedReason ?? s.reason,
);
case BandCondition.reconnectPaused:
return BandStatus(
s.condition,
l?.bandStatusReconnectPausedTitle ?? s.title,
l?.bandStatusReconnectPausedReason(s.bondRefusals ?? 0) ?? s.reason,
fix: l?.bandStatusRepairFix ?? s.fix,
bondRefusals: s.bondRefusals,
);
case BandCondition.repairNeeded:
return BandStatus(
s.condition,
l?.bandStatusRepairNeededTitle ?? s.title,
l?.bandStatusRepairNeededReason ?? s.reason,
fix: l?.bandStatusRepairFix ?? s.fix,
);
case BandCondition.syncStuck:
return BandStatus(
s.condition,
l?.bandStatusSyncStuckTitle ?? s.title,
l?.bandStatusSyncStuckReason ?? s.reason,
fix: l?.bandStatusSyncStuckFix ?? s.fix,
);
case BandCondition.strapUnresponsive:
return BandStatus(
s.condition,
l?.bandStatusStrapUnresponsiveTitle ?? s.title,
l?.bandStatusStrapUnresponsiveReason ?? s.reason,
fix: l?.bandStatusStrapUnresponsiveFix ?? s.fix,
);
case BandCondition.clockLost:
return BandStatus(
s.condition,
l?.bandStatusClockLostTitle ?? s.title,
l?.bandStatusClockLostReason ?? s.reason,
fix: l?.bandStatusClockLostFix ?? s.fix,
);
case BandCondition.connected:
return BandStatus(
s.condition,
l?.devicesConnected ?? s.title,
l?.bandStatusConnectedReason ?? s.reason,
);
case BandCondition.connecting:
return BandStatus(
s.condition,
l?.bandStatusConnectingTitle ?? s.title,
l?.bandStatusConnectingReason ?? s.reason,
);
case BandCondition.scanning:
return BandStatus(
s.condition,
l?.bandStatusScanningTitle ?? s.title,
l?.bandStatusScanningReason ?? s.reason,
);
case BandCondition.disconnected:
return BandStatus(
s.condition,
l?.devicesNotConnected ?? s.title,
l?.bandStatusDisconnectedReason ?? s.reason,
fix: l?.bandStatusDisconnectedFix ?? s.fix,
);
}
}
9 changes: 8 additions & 1 deletion lib/ble/ble_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,13 @@ class BandStatus {
/// The way forward, or null when there is genuinely nothing to do.
final String? fix;

const BandStatus(this.condition, this.title, this.reason, {this.fix});
/// Set only for [BandCondition.reconnectPaused] — the count already baked
/// into [reason]'s English text, carried separately so a UI layer can
/// re-render the reason in another language without re-parsing it.
final int? bondRefusals;

const BandStatus(this.condition, this.title, this.reason,
{this.fix, this.bondRefusals});

/// True for the states that need to be shown. The four ordinary link states
/// (connected/connecting/scanning/disconnected) are the app's normal
Expand Down Expand Up @@ -271,6 +277,7 @@ BandStatus bandStatusFor({
'batteries on a link that will not open. Nothing is reconnecting '
'until you act.',
fix: repairFix,
bondRefusals: bondRefusals,
);
}
if (needsRepairGuide) {
Expand Down
69 changes: 69 additions & 0 deletions lib/gestures/device_action.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
// product decision): answer/reject call (Android ANSWER_PHONE_CALLS; impossible on
// iOS), workout lap.

import 'package:flutter/widgets.dart' show BuildContext;

import '../l10n/app_localizations.dart';

enum DeviceAction {
none,
mediaPlayPause,
Expand Down Expand Up @@ -127,6 +131,71 @@ extension DeviceActionX on DeviceAction {
}
}

/// Localized [label], falling back to the English string above when there is
/// no [AppLocalizations] in scope (e.g. a widget test with no delegate
/// wired). `.label`/`.blurb` stay pure and untouched — same split as
/// `sourceTierLabel`/`sourceTierDetail` in devices.dart.
String localizedLabel(BuildContext c) {
final l = AppLocalizations.of(c);
switch (this) {
case DeviceAction.none:
return l?.deviceActionNoneLabel ?? label;
case DeviceAction.mediaPlayPause:
return l?.deviceActionMediaPlayPauseLabel ?? label;
case DeviceAction.mediaNext:
return l?.deviceActionMediaNextLabel ?? label;
case DeviceAction.mediaPrev:
return l?.deviceActionMediaPrevLabel ?? label;
case DeviceAction.volumeUp:
return l?.deviceActionVolumeUpLabel ?? label;
case DeviceAction.volumeDown:
return l?.deviceActionVolumeDownLabel ?? label;
case DeviceAction.ringPhone:
return l?.deviceActionRingPhoneLabel ?? label;
case DeviceAction.torch:
return l?.deviceActionTorchLabel ?? label;
case DeviceAction.markMoment:
return l?.deviceActionMarkMomentLabel ?? label;
case DeviceAction.workoutToggle:
return l?.deviceActionWorkoutToggleLabel ?? label;
case DeviceAction.logWater:
return l?.deviceActionLogWaterLabel ?? label;
case DeviceAction.broadcastToTasker:
return l?.deviceActionBroadcastToTaskerLabel ?? label;
}
}

/// Localized [blurb] — see [localizedLabel].
String localizedBlurb(BuildContext c) {
final l = AppLocalizations.of(c);
switch (this) {
case DeviceAction.none:
return l?.deviceActionNoneBlurb ?? blurb;
case DeviceAction.mediaPlayPause:
return l?.deviceActionMediaPlayPauseBlurb ?? blurb;
case DeviceAction.mediaNext:
return l?.deviceActionMediaNextBlurb ?? blurb;
case DeviceAction.mediaPrev:
return l?.deviceActionMediaPrevBlurb ?? blurb;
case DeviceAction.volumeUp:
return l?.deviceActionVolumeUpBlurb ?? blurb;
case DeviceAction.volumeDown:
return l?.deviceActionVolumeDownBlurb ?? blurb;
case DeviceAction.ringPhone:
return l?.deviceActionRingPhoneBlurb ?? blurb;
case DeviceAction.torch:
return l?.deviceActionTorchBlurb ?? blurb;
case DeviceAction.markMoment:
return l?.deviceActionMarkMomentBlurb ?? blurb;
case DeviceAction.workoutToggle:
return l?.deviceActionWorkoutToggleBlurb ?? blurb;
case DeviceAction.logWater:
return l?.deviceActionLogWaterBlurb ?? blurb;
case DeviceAction.broadcastToTasker:
return l?.deviceActionBroadcastToTaskerBlurb ?? blurb;
}
}

/// In-app actions act on our own app/backend (handled in Dart, no native call,
/// available on every platform). Everything else (except `none`) is native.
bool get isInApp =>
Expand Down
Loading
Loading