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
35 changes: 25 additions & 10 deletions lib/ui2/screens/cycle_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -479,14 +479,24 @@ class _CycleTabState extends State<CycleTab> with RevisionReload {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
// A Wrap, not a Row with a Spacer: at large text sizes or in a
// longer language the label and the phase pill do not fit one
// line, and the row overflowed. Side by side when they fit, the
// pill on its own line when they don't.
Wrap(
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
alignment: WrapAlignment.spaceBetween,
crossAxisAlignment: WrapCrossAlignment.center,
spacing: S.x2,
runSpacing: S.x2,
children: [
Text(
l?.cycleDayInThisCycle ?? 'DAY IN THIS CYCLE',
style: F.over.copyWith(color: p.ink3),
),
const Spacer(),
if (d.phase != 'unknown') Pill(_phaseLabel(l, d.phase), C.pink),
if (d.phase != 'unknown')
// Wraps at large text rather than cutting a long phase name
// short with an ellipsis; one line at default sizes.
Pill(_phaseLabel(l, d.phase), C.pink, wrap: bigText(c)),
],
),
const SizedBox(height: S.x2),
Expand All @@ -496,13 +506,18 @@ class _CycleTabState extends State<CycleTab> with RevisionReload {
children: [
Text('${d.cycleDay}', style: F.n34.copyWith(color: p.ink)),
const SizedBox(width: S.x2),
Text(
d.medianLength == null
? (l?.cycleCountedFromLastStart ??
'counted from your last logged start')
: (l?.cycleOfAboutDays(d.medianLength!.round()) ??
'of about ${d.medianLength!.round()}'),
style: F.cap.copyWith(color: p.ink3),
// Wraps under itself rather than running off the card:
// "counted from your last logged start" is most of a line at
// the default size already.
Flexible(
child: Text(
d.medianLength == null
? (l?.cycleCountedFromLastStart ??
'counted from your last logged start')
: (l?.cycleOfAboutDays(d.medianLength!.round()) ??
'of about ${d.medianLength!.round()}'),
style: F.cap.copyWith(color: p.ink3),
),
),
],
),
Expand Down
73 changes: 73 additions & 0 deletions test/cycle_day_row_overflow_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// The cycle screen's "day in this cycle" card must fit a phone at any text
// size.
//
// The headline row put the day number and "counted from your last logged
// start" side by side with nothing allowed to wrap, and the header row put
// its label before a Spacer and the phase pill. At a 390 pt width that
// overflowed by 147 pt in the test font, and by less, but still, with larger
// system text (iOS goes to 3.1x) or a longer language.

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';

import 'package:openstrap_edge/data/local_repository.dart';
import 'package:openstrap_edge/state/app_state.dart';
import 'package:openstrap_edge/ui2/screens/cycle_screen.dart';
import 'package:openstrap_edge/ui2/ui2.dart';

class _Repo extends LocalRepository {
_Repo({this.median});
final num? median;

@override
Future<Map<String, dynamic>> getCycle() async => {
'enabled': true,
'phase': 'follicular',
'cycle_day': 5,
'median_length': ?median,
'logs': [
{'date': '2026-11-09', 'kind': 'start'},
],
};

@override
Future<Map<String, List<String>>> getCycleSymptoms() async => const {};
}

Future<void> _pump(WidgetTester t, {num? median, double scale = 1}) async {
t.view.physicalSize = const Size(390 * 3, 4000 * 3);
t.view.devicePixelRatio = 3;
addTearDown(t.view.reset);
final app = AppState.forTesting()..repo = _Repo(median: median);
addTearDown(app.dispose);
await t.pumpWidget(ChangeNotifierProvider<AppState>.value(
value: app,
child: MediaQuery(
data: MediaQueryData(textScaler: TextScaler.linear(scale)),
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
Resolved
child: MaterialApp(
theme: buildTheme(Brightness.light),
home: const Scaffold(body: CycleTab()),
),
),
));
await t.pumpAndSettle();
}

void main() {
for (final scale in const [1.0, 3.1]) {
testWidgets('no median yet, ${scale}x text', (t) async {
// The pump is the assertion: a RenderFlex overflow fails it.
await _pump(t, scale: scale);
expect(find.textContaining('counted from your last logged start'),
findsOneWidget);
});

testWidgets('with a median, ${scale}x text', (t) async {
await _pump(t, median: 29, scale: scale);
expect(find.textContaining('of about 29'), findsOneWidget);
// The phase name wraps at large text instead of ending in an ellipsis.
expect(t.widget<Pill>(find.byType(Pill).first).wrap, scale > 1.3);
});
}
}
Loading