Skip to content

Commit 3993fb0

Browse files
authored
feat: use async steps results (#164)
* feat: use async step results * feat: update changelog + version * fix: update color opacity method and improve image answer view state management * feat: update flutter deps
1 parent 436151a commit 3993fb0

File tree

11 files changed

+195
-129
lines changed

11 files changed

+195
-129
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
# 0.3.3
2+
3+
- INFO: Updated dependencies (Flutter 3.35.6)
4+
- INFO: Steps are now return a Future, so you can do async operations before returning the step
5+
- INFO: onResult is now called after onNextStep has finished
6+
- FIX: Replaced deprecated `groupValue` parameter in Radio widget with RadioGroup wrapper (Flutter 3.32.0+)
7+
- FIX: Fixed `InputDecorationThemeData` type error in time_picker_widget.dart for compatibility with newer Flutter versions
8+
- FIX: Replaced deprecated `withOpacity` method with `withValues` for Color in input_decoration.dart
9+
10+
# 0.3.2
11+
12+
- INFO: Updated dependencies (Flutter 3.33.0)
13+
114
# 0.3.1
215

316
- INFO: Updated dependencies (Flutter 3.32.1)

example/lib/main.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class _MyAppState extends State<MyApp> {
4747
themeData: Theme.of(context).copyWith(
4848
primaryColor: Colors.cyan,
4949
appBarTheme: const AppBarTheme(
50-
color: Colors.white,
50+
backgroundColor: Colors.white,
5151
iconTheme: IconThemeData(
5252
color: Colors.cyan,
5353
),

lib/src/controller/survey_controller.dart

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:async';
2+
13
import 'package:flutter/widgets.dart';
24
import 'package:flutter_bloc/flutter_bloc.dart';
35
import 'package:survey_kit/src/presenter/survey_event.dart';
@@ -15,7 +17,7 @@ class SurveyController {
1517
///
1618
/// Returns:
1719
/// - `true` if the default behavior should still be executed after this function, `false` otherwise.
18-
final bool Function(
20+
final FutureOr<bool> Function(
1921
BuildContext context,
2022
QuestionResult Function() resultFunction,
2123
)? onNextStep;
@@ -56,12 +58,12 @@ class SurveyController {
5658
this.onCloseSurvey,
5759
});
5860

59-
void nextStep(
61+
Future<void> nextStep(
6062
BuildContext context,
6163
QuestionResult Function() resultFunction,
62-
) {
64+
) async {
6365
if (onNextStep != null) {
64-
if (!onNextStep!(context, resultFunction)) return;
66+
if (!await onNextStep!(context, resultFunction)) return;
6567
}
6668
BlocProvider.of<SurveyPresenter>(context).add(
6769
NextStep(

lib/src/presenter/survey_presenter.dart

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:async';
2+
13
import 'package:collection/collection.dart' show IterableExtension;
24
import 'package:flutter_bloc/flutter_bloc.dart';
35
import 'package:survey_kit/src/configuration/app_bar_configuration.dart';
@@ -13,7 +15,7 @@ import 'package:survey_kit/src/steps/identifier/step_identifier.dart';
1315
//TO DO: Extract gathering of the results into another class
1416
class SurveyPresenter extends Bloc<SurveyEvent, SurveyState> {
1517
final TaskNavigator taskNavigator;
16-
final Function(SurveyResult) onResult;
18+
final FutureOr<void> Function(SurveyResult) onResult;
1719

1820
Set<QuestionResult> results = {};
1921
late final DateTime startDate;
@@ -22,40 +24,44 @@ class SurveyPresenter extends Bloc<SurveyEvent, SurveyState> {
2224
required this.taskNavigator,
2325
required this.onResult,
2426
}) : super(LoadingSurveyState()) {
25-
26-
on<StartSurvey>((event, emit){
27-
emit(
28-
_handleInitialStep()
29-
);
27+
on<StartSurvey>((event, emit) {
28+
emit(_handleInitialStep());
3029
});
3130

32-
on<NextStep>((event, emit){
33-
if (state is PresentingSurveyState){
34-
emit(_handleNextStep(event, state as PresentingSurveyState));
31+
on<NextStep>((event, emit) async {
32+
if (state is PresentingSurveyState) {
33+
final newState = _handleNextStep(event, state as PresentingSurveyState);
34+
emit(newState);
35+
36+
// Call onResult after emitting the final state
37+
if (newState is SurveyResultState) {
38+
await onResult(newState.result);
39+
}
3540
}
3641
});
3742

38-
on<StepBack>((event, emit){
39-
if (state is PresentingSurveyState){
40-
emit(
41-
_handleStepBack(event, state as PresentingSurveyState)
42-
);
43+
on<StepBack>((event, emit) {
44+
if (state is PresentingSurveyState) {
45+
emit(_handleStepBack(event, state as PresentingSurveyState));
4346
}
4447
});
4548

46-
on<CloseSurvey>((event, emit){
47-
if (state is PresentingSurveyState){
48-
emit(
49-
_handleClose(event, state as PresentingSurveyState)
50-
);
49+
on<CloseSurvey>((event, emit) async {
50+
if (state is PresentingSurveyState) {
51+
final newState = _handleClose(event, state as PresentingSurveyState);
52+
emit(newState);
53+
54+
// Call onResult after emitting the final state
55+
if (newState is SurveyResultState) {
56+
await onResult(newState.result);
57+
}
5158
}
5259
});
5360

5461
this.startDate = DateTime.now();
5562
add(StartSurvey());
5663
}
5764

58-
5965
SurveyState _handleInitialStep() {
6066
Step? step = taskNavigator.firstStep();
6167
if (step != null) {

lib/src/survey_kit.dart

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:async';
2+
13
import 'package:collection/collection.dart' show IterableExtension;
24
import 'package:flutter/material.dart';
35
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -24,7 +26,7 @@ class SurveyKit extends StatefulWidget {
2426
final ThemeData? themeData;
2527

2628
/// Function which is called after the results are collected
27-
final Function(SurveyResult) onResult;
29+
final FutureOr<void> Function(SurveyResult) onResult;
2830

2931
/// [SurveyController] to override the navigation methods
3032
/// onNextStep, onBackStep, onCloseSurvey
@@ -116,7 +118,7 @@ class _SurveyKitState extends State<SurveyKit> {
116118
class SurveyPage extends StatefulWidget {
117119
final int length;
118120
final Widget Function(AppBarConfiguration appBarConfiguration)? appBar;
119-
final Function(SurveyResult) onResult;
121+
final FutureOr<void> Function(SurveyResult) onResult;
120122

121123
const SurveyPage({
122124
required this.length,
@@ -149,9 +151,6 @@ class _SurveyPageState extends State<SurveyPage>
149151
return BlocConsumer<SurveyPresenter, SurveyState>(
150152
listenWhen: (previous, current) => previous != current,
151153
listener: (context, state) async {
152-
if (state is SurveyResultState) {
153-
widget.onResult.call(state.result);
154-
}
155154
if (state is PresentingSurveyState) {
156155
tabController.animateTo(state.currentStepIndex);
157156
}

lib/src/views/agreement_answer_view.dart

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -86,42 +86,43 @@ class _AgreementAnswerViewState extends State<AgreementAnswerView> {
8686
href != null ? launchUrl(Uri.parse(href)) : null,
8787
),
8888
),
89-
Row(
90-
crossAxisAlignment: CrossAxisAlignment.center,
91-
children: [
92-
Radio<BooleanResult>(
93-
groupValue: _result,
94-
value: BooleanResult.POSITIVE,
95-
onChanged: (v) {
89+
RadioGroup<BooleanResult>(
90+
groupValue: _result,
91+
onChanged: (v) {
92+
setState(() {
93+
_result = v;
94+
});
95+
},
96+
child: Row(
97+
crossAxisAlignment: CrossAxisAlignment.center,
98+
children: [
99+
Radio<BooleanResult>(value: BooleanResult.POSITIVE),
100+
SizedBox(
101+
width: 16,
102+
),
103+
Expanded(
104+
child: GestureDetector(
105+
onTap: () {
96106
setState(() {
97-
_result = v;
107+
if (_result == BooleanResult.POSITIVE) {
108+
_result = BooleanResult.NEGATIVE;
109+
} else {
110+
_result = BooleanResult.POSITIVE;
111+
}
98112
});
99-
}),
100-
SizedBox(
101-
width: 16,
102-
),
103-
Expanded(
104-
child: GestureDetector(
105-
onTap: () {
106-
setState(() {
107-
if (_result == BooleanResult.POSITIVE) {
108-
_result = BooleanResult.NEGATIVE;
109-
} else {
110-
_result = BooleanResult.POSITIVE;
111-
}
112-
});
113-
},
114-
child: MarkdownBody(
115-
styleSheet: markDownStyleSheet.copyWith(
116-
p: theme.textTheme.bodySmall,
113+
},
114+
child: MarkdownBody(
115+
styleSheet: markDownStyleSheet.copyWith(
116+
p: theme.textTheme.bodySmall,
117+
),
118+
data: _agreementAnswerFormat.markdownAgreementText ??
119+
'',
120+
onTapLink: (text, href, title) =>
121+
href != null ? launchUrl(Uri.parse(href)) : null,
117122
),
118-
data:
119-
_agreementAnswerFormat.markdownAgreementText ?? '',
120-
onTapLink: (text, href, title) =>
121-
href != null ? launchUrl(Uri.parse(href)) : null,
122-
),
123-
)),
124-
],
123+
)),
124+
],
125+
),
125126
)
126127
],
127128
),

lib/src/views/decoration/input_decoration.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ InputDecoration textFieldInputDecoration({String hint = ''}) => InputDecoration(
1212
Radius.zero,
1313
),
1414
borderSide: BorderSide(
15-
color: Colors.black.withOpacity(0.2),
15+
color: Colors.black.withValues(alpha: 0.2),
1616
),
1717
),
1818
enabledBorder: OutlineInputBorder(
1919
borderRadius: BorderRadius.all(
2020
Radius.zero,
2121
),
2222
borderSide: BorderSide(
23-
color: Colors.black.withOpacity(0.2),
23+
color: Colors.black.withValues(alpha: 0.2),
2424
),
2525
),
2626
hintText: hint,

lib/src/views/widget/time_picker_widget.dart

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,9 @@ class _HourMinuteControl extends StatelessWidget {
270270
final Color backgroundColor = timePickerTheme.hourMinuteColor ??
271271
WidgetStateColor.resolveWith((Set<WidgetState> states) {
272272
return states.contains(WidgetState.selected)
273-
? themeData.colorScheme.primary.withOpacity(isDark ? 0.24 : 0.12)
274-
: themeData.colorScheme.onSurface.withOpacity(0.12);
273+
? themeData.colorScheme.primary
274+
.withValues(alpha: isDark ? 0.24 : 0.12)
275+
: themeData.colorScheme.onSurface.withValues(alpha: 0.12);
275276
});
276277
final TextStyle style = timePickerTheme.hourMinuteTextStyle ??
277278
themeData.textTheme.displayMedium!;
@@ -542,7 +543,7 @@ class _DayPeriodControl extends StatelessWidget {
542543
WidgetStateColor.resolveWith((Set<WidgetState> states) {
543544
return states.contains(WidgetState.selected)
544545
? colorScheme.primary
545-
: colorScheme.onSurface.withOpacity(0.60);
546+
: colorScheme.onSurface.withValues(alpha: 0.60);
546547
});
547548
final Color backgroundColor = timePickerTheme.dayPeriodColor ??
548549
WidgetStateColor.resolveWith((Set<WidgetState> states) {
@@ -551,7 +552,7 @@ class _DayPeriodControl extends StatelessWidget {
551552
// and allows the optional elevation overlay for dark mode to be
552553
// visible.
553554
return states.contains(WidgetState.selected)
554-
? colorScheme.primary.withOpacity(isDark ? 0.24 : 0.12)
555+
? colorScheme.primary.withValues(alpha: isDark ? 0.24 : 0.12)
555556
: Colors.transparent;
556557
});
557558
final bool amSelected = selectedTime.period == DayPeriod.am;
@@ -572,8 +573,8 @@ class _DayPeriodControl extends StatelessWidget {
572573
const RoundedRectangleBorder(borderRadius: _kDefaultBorderRadius);
573574
final BorderSide borderSide = timePickerTheme.dayPeriodBorderSide ??
574575
BorderSide(
575-
color: Color.alphaBlend(
576-
colorScheme.onSurface.withOpacity(0.38), colorScheme.surface),
576+
color: Color.alphaBlend(colorScheme.onSurface.withValues(alpha: 0.38),
577+
colorScheme.surface),
577578
);
578579
// Apply the custom borderSide.
579580
shape = shape.copyWith(
@@ -1270,7 +1271,7 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
12701271
final ThemeData theme = Theme.of(context);
12711272
final TimePickerThemeData pickerTheme = TimePickerTheme.of(context);
12721273
final Color backgroundColor = pickerTheme.dialBackgroundColor ??
1273-
themeData.colorScheme.onSurface.withOpacity(0.12);
1274+
themeData.colorScheme.onSurface.withValues(alpha: 0.12);
12741275
final Color accentColor =
12751276
pickerTheme.dialHandColor ?? themeData.colorScheme.primary;
12761277
final Color primaryLabelColor = WidgetStateProperty.resolveAs(
@@ -1715,8 +1716,7 @@ class _HourMinuteTextFieldState extends State<_HourMinuteTextField> {
17151716
final TimePickerThemeData timePickerTheme = TimePickerTheme.of(context);
17161717
final ColorScheme colorScheme = theme.colorScheme;
17171718

1718-
final InputDecorationThemeData? inputDecorationTheme =
1719-
timePickerTheme.inputDecorationTheme;
1719+
final inputDecorationTheme = timePickerTheme.inputDecorationTheme;
17201720
InputDecoration inputDecoration;
17211721
if (inputDecorationTheme != null) {
17221722
inputDecoration =
@@ -1738,14 +1738,14 @@ class _HourMinuteTextFieldState extends State<_HourMinuteTextField> {
17381738
borderSide: BorderSide(color: colorScheme.error, width: 2.0),
17391739
),
17401740
hintStyle: widget.style
1741-
.copyWith(color: colorScheme.onSurface.withOpacity(0.36)),
1741+
.copyWith(color: colorScheme.onSurface.withValues(alpha: 0.36)),
17421742
errorStyle: const TextStyle(
17431743
fontSize: 0.0,
17441744
height: 0.0), // Prevent the error text from appearing.
17451745
);
17461746
}
17471747
final Color unfocusedFillColor = timePickerTheme.hourMinuteColor ??
1748-
colorScheme.onSurface.withOpacity(0.12);
1748+
colorScheme.onSurface.withValues(alpha: 0.12);
17491749
// If screen reader is in use, make the hint text say hours/minutes.
17501750
// Otherwise, remove the hint text when focused because the centered cursor
17511751
// appears odd above the hint text.
@@ -2031,8 +2031,9 @@ class _TimePickerWidgetState extends State<TimePickerWidget> {
20312031
const SizedBox(width: 10.0),
20322032
IconButton(
20332033
color: TimePickerTheme.of(context).entryModeIconColor ??
2034-
theme.colorScheme.onSurface.withOpacity(
2035-
theme.colorScheme.brightness == Brightness.dark ? 1.0 : 0.6,
2034+
theme.colorScheme.onSurface.withValues(
2035+
alpha:
2036+
theme.colorScheme.brightness == Brightness.dark ? 1.0 : 0.6,
20362037
),
20372038
onPressed: _handleEntryModeToggle,
20382039
icon: Icon(_entryMode == TimePickerEntryMode.dial

packages/survey_kit_image_answer/lib/src/view/image_answer_view.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ class _ImageAnswerViewState extends State<ImageAnswerView> {
158158
source: ImageSource.camera,
159159
);
160160

161+
if (!mounted) return;
162+
161163
Navigator.pop(context);
162164

163165
picture?.readAsBytes().then((value) {
@@ -172,6 +174,8 @@ class _ImageAnswerViewState extends State<ImageAnswerView> {
172174
source: ImageSource.gallery,
173175
);
174176

177+
if (!mounted) return;
178+
175179
Navigator.pop(context);
176180

177181
picture?.readAsBytes().then((value) {

0 commit comments

Comments
 (0)