Bullet subheadings, font-accurate auto-fit, and small UX tweaks #3
4 changed files with 83 additions and 3 deletions
|
|
@ -550,7 +550,8 @@ class _AppTabBar extends StatelessWidget {
|
||||||
_TabChip(
|
_TabChip(
|
||||||
tab: tabsState.tabs[i],
|
tab: tabsState.tabs[i],
|
||||||
isActive: i == tabsState.clampedIndex,
|
isActive: i == tabsState.clampedIndex,
|
||||||
showClose: tabsState.tabs.length > 1,
|
showClose:
|
||||||
|
tabsState.tabs.length > 1 || tabsState.tabs[i].isOpen,
|
||||||
panelText: palette.panelText,
|
panelText: palette.panelText,
|
||||||
accent: Theme.of(context).colorScheme.secondary,
|
accent: Theme.of(context).colorScheme.secondary,
|
||||||
onTap: () => onSelect(i),
|
onTap: () => onSelect(i),
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,13 @@ class _PresentationInfoDialogState extends State<PresentationInfoDialog> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _setCurrentDate() {
|
||||||
|
final now = DateTime.now();
|
||||||
|
String twoDigits(int value) => value.toString().padLeft(2, '0');
|
||||||
|
_date.text = '${now.year}-${twoDigits(now.month)}-${twoDigits(now.day)}';
|
||||||
|
_date.selection = TextSelection.collapsed(offset: _date.text.length);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
|
|
@ -143,7 +150,12 @@ class _PresentationInfoDialogState extends State<PresentationInfoDialog> {
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 120,
|
width: 120,
|
||||||
child: _field(_date, 'Datum', 'Bijv. 2026-05-30'),
|
child: _field(
|
||||||
|
_date,
|
||||||
|
'Datum',
|
||||||
|
'Bijv. 2026-05-30',
|
||||||
|
onDoubleTap: _setCurrentDate,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -190,9 +202,10 @@ class _PresentationInfoDialogState extends State<PresentationInfoDialog> {
|
||||||
String label,
|
String label,
|
||||||
String hint, {
|
String hint, {
|
||||||
int maxLines = 1,
|
int maxLines = 1,
|
||||||
|
VoidCallback? onDoubleTap,
|
||||||
}) {
|
}) {
|
||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
return TextField(
|
final field = TextField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
maxLines: maxLines,
|
maxLines: maxLines,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
|
|
@ -202,5 +215,11 @@ class _PresentationInfoDialogState extends State<PresentationInfoDialog> {
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
if (onDoubleTap == null) return field;
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
onDoubleTap: onDoubleTap,
|
||||||
|
child: field,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
33
test/presentation_info_dialog_test.dart
Normal file
33
test/presentation_info_dialog_test.dart
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:ocideck/models/deck.dart';
|
||||||
|
import 'package:ocideck/widgets/dialogs/presentation_info_dialog.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('double-clicking date fills in the current date', (tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: PresentationInfoDialog(deck: Deck(title: 'Test')),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final dateField = find.byWidgetPredicate(
|
||||||
|
(widget) =>
|
||||||
|
widget is TextField && widget.decoration?.labelText == 'Datum',
|
||||||
|
);
|
||||||
|
final now = DateTime.now();
|
||||||
|
String twoDigits(int value) => value.toString().padLeft(2, '0');
|
||||||
|
final expected =
|
||||||
|
'${now.year}-${twoDigits(now.month)}-${twoDigits(now.day)}';
|
||||||
|
|
||||||
|
await tester.tap(dateField);
|
||||||
|
await tester.pump(const Duration(milliseconds: 50));
|
||||||
|
await tester.tap(dateField);
|
||||||
|
await tester.pump(const Duration(milliseconds: 100));
|
||||||
|
|
||||||
|
final field = tester.widget<TextField>(dateField);
|
||||||
|
expect(field.controller!.text, expected);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,10 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:ocideck/app.dart';
|
import 'package:ocideck/app.dart';
|
||||||
|
import 'package:ocideck/models/deck.dart';
|
||||||
|
import 'package:ocideck/models/slide.dart';
|
||||||
|
import 'package:ocideck/state/tabs_provider.dart';
|
||||||
|
import 'package:ocideck/widgets/app_shell.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Welcome screen shows startup logo', (WidgetTester tester) async {
|
testWidgets('Welcome screen shows startup logo', (WidgetTester tester) async {
|
||||||
|
|
@ -16,4 +20,27 @@ void main() {
|
||||||
await tester.pumpWidget(const ProviderScope(child: OciDeckApp()));
|
await tester.pumpWidget(const ProviderScope(child: OciDeckApp()));
|
||||||
expect(find.byIcon(Icons.settings_outlined), findsOneWidget);
|
expect(find.byIcon(Icons.settings_outlined), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('the only open presentation can be closed', (tester) async {
|
||||||
|
await tester.binding.setSurfaceSize(const Size(1600, 1000));
|
||||||
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||||
|
await tester.pumpWidget(const ProviderScope(child: OciDeckApp()));
|
||||||
|
final container = ProviderScope.containerOf(
|
||||||
|
tester.element(find.byType(AppShell)),
|
||||||
|
);
|
||||||
|
final tab = container.read(tabsProvider).current!;
|
||||||
|
tab.deckNotifier.loadDeck(
|
||||||
|
Deck(
|
||||||
|
title: 'Test',
|
||||||
|
slides: [Slide.create(SlideType.title).copyWith(title: 'Test')],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(find.byIcon(Icons.close), findsOneWidget);
|
||||||
|
await tester.tap(find.byIcon(Icons.close));
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(container.read(tabsProvider).current!.isOpen, isFalse);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue