Skip to content

Commit a0dee4b

Browse files
authored
Merge pull request #374 from DevKor-github/codex/spare-time-minimum-bubble
[codex] Add spare time minimum warning
2 parents aea0826 + 7f32715 commit a0dee4b

9 files changed

Lines changed: 151 additions & 30 deletions

File tree

lib/l10n/app_en.arb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
"@setSpareTimeWarning": {
2424
"description": "Warning for setting spare time during onboarding"
2525
},
26+
"spareTimeMinimumWarning": "Minimum spare time is 10 minutes",
27+
"@spareTimeMinimumWarning": {
28+
"description": "Warning shown when onboarding spare time is at the minimum value"
29+
},
2630
"todaysAppointments": "Today's Appointments",
2731
"@todaysAppointments": {
2832
"description": "Title for today's appointments section on the home screen"

lib/l10n/app_ko.arb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"setSpareTimeTitle": "여유시간을 설정해주세요",
66
"setSpareTimeDescription": "설정한 여유시간만큼 일찍 도착할 수 있어요.",
77
"setSpareTimeWarning": "여유시간은 혹시 모를 상황을 위해 꼭 설정해야 돼요.",
8+
"spareTimeMinimumWarning": "여유시간은 10분 아래로 설정할 수 없어요",
89
"todaysAppointments": "오늘의 약속",
910
"slogan": "작은 준비가\n큰 여유를 만들어요!",
1011
"noAppointments": "약속이 없는 날이에요",

lib/l10n/app_localizations.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ abstract class AppLocalizations {
134134
/// **'You must set a spare time in case of unexpected situations.'**
135135
String get setSpareTimeWarning;
136136

137+
/// Warning shown when onboarding spare time is at the minimum value
138+
///
139+
/// In en, this message translates to:
140+
/// **'Minimum spare time is 10 minutes'**
141+
String get spareTimeMinimumWarning;
142+
137143
/// Title for today's appointments section on the home screen
138144
///
139145
/// In en, this message translates to:

lib/l10n/app_localizations_en.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ class AppLocalizationsEn extends AppLocalizations {
2828
String get setSpareTimeWarning =>
2929
'You must set a spare time in case of unexpected situations.';
3030

31+
@override
32+
String get spareTimeMinimumWarning => 'Minimum spare time is 10 minutes';
33+
3134
@override
3235
String get todaysAppointments => 'Today\'s Appointments';
3336

lib/l10n/app_localizations_ko.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ class AppLocalizationsKo extends AppLocalizations {
2626
@override
2727
String get setSpareTimeWarning => '여유시간은 혹시 모를 상황을 위해 꼭 설정해야 돼요.';
2828

29+
@override
30+
String get spareTimeMinimumWarning => '여유시간은 10분 아래로 설정할 수 없어요';
31+
2932
@override
3033
String get todaysAppointments => '오늘의 약속';
3134

lib/presentation/onboarding/schedule_spare_time/components/shcedule_spare_time_field.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
11
import 'package:flutter/widgets.dart';
2+
import 'package:on_time_front/presentation/shared/components/error_message_bubble.dart';
23
import 'package:on_time_front/presentation/shared/components/time_stepper.dart';
34

45
class ScheduleSpareTimeField extends StatelessWidget {
56
const ScheduleSpareTimeField({
67
super.key,
78
required this.lowerBound,
89
required this.spareTime,
10+
required this.minimumWarningMessage,
911
required this.onSpareTimeDecreased,
1012
required this.onSpareTimeIncreased,
1113
});
1214

1315
final Duration spareTime;
1416
final Duration lowerBound;
17+
final String minimumWarningMessage;
1518
final VoidCallback onSpareTimeIncreased;
1619
final VoidCallback onSpareTimeDecreased;
1720

1821
@override
1922
Widget build(BuildContext context) {
23+
final isAtMinimum = spareTime <= lowerBound;
24+
2025
return Padding(
2126
padding: EdgeInsets.only(top: 90.0),
2227
child: Column(
@@ -27,6 +32,16 @@ class ScheduleSpareTimeField extends StatelessWidget {
2732
lowerBound: lowerBound,
2833
value: spareTime,
2934
),
35+
if (isAtMinimum)
36+
Padding(
37+
padding: const EdgeInsets.only(top: 8),
38+
child: ErrorMessageBubble(
39+
width: 300,
40+
padding: const EdgeInsets.only(left: 72),
41+
tailPosition: TailPosition.top,
42+
errorMessage: Text(minimumWarningMessage),
43+
),
44+
),
3045
Expanded(child: SizedBox()),
3146
],
3247
),

lib/presentation/onboarding/schedule_spare_time/screens/schedule_spare_time_form.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class ScheduleSpareTimeForm extends StatefulWidget {
1414

1515
class _ScheduleSpareTimeFormState extends State<ScheduleSpareTimeForm> {
1616
Duration spareTime = Duration(minutes: 30);
17-
final Duration lowerBound = Duration(minutes: 0);
17+
final Duration lowerBound = Duration(minutes: 10);
1818

1919
@override
2020
Widget build(BuildContext context) {
@@ -42,9 +42,14 @@ class _ScheduleSpareTimeFormState extends State<ScheduleSpareTimeForm> {
4242
child: ScheduleSpareTimeField(
4343
lowerBound: lowerBound,
4444
spareTime: spareTime,
45+
minimumWarningMessage:
46+
AppLocalizations.of(context)!.spareTimeMinimumWarning,
4547
onSpareTimeDecreased: () {
4648
setState(() {
47-
spareTime -= Duration(minutes: 10);
49+
final updatedSpareTime = spareTime - Duration(minutes: 10);
50+
if (updatedSpareTime >= lowerBound) {
51+
spareTime = updatedSpareTime;
52+
}
4853
});
4954
},
5055
onSpareTimeIncreased: () {

lib/presentation/shared/components/error_message_bubble.dart

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,11 @@ class ErrorMessageBubble extends StatelessWidget {
4444
this.action,
4545
this.tailPosition = TailPosition.bottom,
4646
this.padding = const EdgeInsets.only(left: 72.0),
47+
this.width,
4748
});
4849

4950
final EdgeInsets padding;
51+
final double? width;
5052

5153
final Widget errorMessage;
5254
final TextButton? action;
@@ -65,10 +67,12 @@ class ErrorMessageBubble extends StatelessWidget {
6567
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
6668
color: Theme.of(context).colorScheme.error,
6769
decorationColor: Theme.of(context).colorScheme.error,
70+
letterSpacing: 0,
6871
),
6972
child: _MessageBubbleBody(
7073
errorMessage: errorMessage,
7174
action: action,
75+
width: width,
7276
),
7377
);
7478

@@ -144,49 +148,55 @@ class _MessageBubbleBody extends StatelessWidget {
144148
const _MessageBubbleBody({
145149
required this.errorMessage,
146150
required this.action,
151+
required this.width,
147152
});
148153

149154
final Widget errorMessage;
150155
final Widget? action;
156+
final double? width;
151157

152158
@override
153159
Widget build(BuildContext context) {
154160
final colorScheme = Theme.of(context).colorScheme;
155161
return Container(
162+
width: width,
156163
padding: const EdgeInsets.symmetric(horizontal: 21, vertical: 13.5),
157164
decoration: BoxDecoration(
158165
borderRadius: BorderRadius.circular(16),
159166
color: colorScheme.errorContainer,
160167
),
161-
child: Row(
162-
mainAxisAlignment: MainAxisAlignment.spaceBetween,
163-
children: [
164-
errorMessage,
165-
TextButtonTheme(
166-
data: TextButtonThemeData(
167-
style: ButtonStyle(
168-
backgroundColor: WidgetStateProperty.all(
169-
Colors.transparent,
168+
child: action == null
169+
? errorMessage
170+
: Row(
171+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
172+
children: [
173+
Flexible(child: errorMessage),
174+
TextButtonTheme(
175+
data: TextButtonThemeData(
176+
style: ButtonStyle(
177+
backgroundColor: WidgetStateProperty.all(
178+
Colors.transparent,
179+
),
180+
overlayColor: WidgetStateProperty.all(
181+
Colors.transparent,
182+
),
183+
elevation: WidgetStateProperty.all(0),
184+
foregroundColor: WidgetStateProperty.all(
185+
Theme.of(context).colorScheme.error,
186+
),
187+
textStyle: WidgetStateProperty.all(
188+
DefaultTextStyle.of(context).style,
189+
),
190+
padding: WidgetStateProperty.all(
191+
const EdgeInsets.symmetric(horizontal: 0),
192+
),
193+
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
194+
),
170195
),
171-
overlayColor: WidgetStateProperty.all(
172-
Colors.transparent,
173-
),
174-
elevation: WidgetStateProperty.all(0),
175-
foregroundColor: WidgetStateProperty.all(
176-
Theme.of(context).colorScheme.error,
177-
),
178-
textStyle: WidgetStateProperty.all(
179-
DefaultTextStyle.of(context).style,
180-
),
181-
padding: WidgetStateProperty.all(
182-
const EdgeInsets.symmetric(horizontal: 0),
183-
),
184-
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
185-
),
186-
),
187-
child: action ?? const SizedBox.shrink())
188-
],
189-
),
196+
child: action!,
197+
)
198+
],
199+
),
190200
);
191201
}
192202
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:on_time_front/presentation/onboarding/schedule_spare_time/components/shcedule_spare_time_field.dart';
3+
import 'package:widgetbook/widgetbook.dart';
4+
import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;
5+
6+
@widgetbook.UseCase(
7+
name: 'default',
8+
type: ScheduleSpareTimeField,
9+
)
10+
Widget useCaseScheduleSpareTimeField(BuildContext context) {
11+
final initialMinutes = context.knobs.int.input(
12+
label: 'Initial Minutes',
13+
initialValue: 10,
14+
);
15+
16+
return _ScheduleSpareTimeFieldUseCase(initialMinutes: initialMinutes);
17+
}
18+
19+
class _ScheduleSpareTimeFieldUseCase extends StatefulWidget {
20+
const _ScheduleSpareTimeFieldUseCase({required this.initialMinutes});
21+
22+
final int initialMinutes;
23+
24+
@override
25+
State<_ScheduleSpareTimeFieldUseCase> createState() =>
26+
_ScheduleSpareTimeFieldUseCaseState();
27+
}
28+
29+
class _ScheduleSpareTimeFieldUseCaseState
30+
extends State<_ScheduleSpareTimeFieldUseCase> {
31+
static const _lowerBound = Duration(minutes: 10);
32+
late Duration _spareTime;
33+
34+
@override
35+
void initState() {
36+
super.initState();
37+
_spareTime = _normalizedInitialSpareTime;
38+
}
39+
40+
@override
41+
void didUpdateWidget(covariant _ScheduleSpareTimeFieldUseCase oldWidget) {
42+
super.didUpdateWidget(oldWidget);
43+
if (oldWidget.initialMinutes != widget.initialMinutes) {
44+
_spareTime = _normalizedInitialSpareTime;
45+
}
46+
}
47+
48+
Duration get _normalizedInitialSpareTime {
49+
final initialSpareTime = Duration(minutes: widget.initialMinutes);
50+
return initialSpareTime < _lowerBound ? _lowerBound : initialSpareTime;
51+
}
52+
53+
@override
54+
Widget build(BuildContext context) {
55+
return ScheduleSpareTimeField(
56+
lowerBound: _lowerBound,
57+
spareTime: _spareTime,
58+
minimumWarningMessage: '여유시간은 10분 아래로 설정할 수 없어요',
59+
onSpareTimeDecreased: () {
60+
setState(() {
61+
final updatedSpareTime = _spareTime - const Duration(minutes: 10);
62+
if (updatedSpareTime >= _lowerBound) {
63+
_spareTime = updatedSpareTime;
64+
}
65+
});
66+
},
67+
onSpareTimeIncreased: () {
68+
setState(() {
69+
_spareTime += const Duration(minutes: 10);
70+
});
71+
},
72+
);
73+
}
74+
}

0 commit comments

Comments
 (0)