-
Notifications
You must be signed in to change notification settings - Fork 38
refactor(capture_page): cubit usage adjustments #52
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,34 +60,21 @@ class CaptureView extends StatelessWidget { | |
const SizedBox(height: 16), | ||
const CaptureScoreSelectorLabels(), | ||
const SizedBox(height: 32), | ||
AnimatedOpacity( | ||
duration: const Duration(milliseconds: 1500), | ||
opacity: | ||
context.watch<CaptureCubit>().isScoreSelected ? 1.0 : 0.0, | ||
child: Column( | ||
children: [ | ||
const AnswerChips(), | ||
const SizedBox(height: 32), | ||
ElevatedButton( | ||
key: const Key('capturePage_submit_elevatedButton'), | ||
onPressed: context.watch<CaptureCubit>().canSubmit | ||
? context.read<CaptureCubit>().submitResult | ||
: null, | ||
child: Padding( | ||
padding: const EdgeInsets.symmetric( | ||
horizontal: 72, | ||
vertical: 12, | ||
), | ||
child: Text( | ||
context.l10n.submit, | ||
style: const TextStyle( | ||
fontWeight: FontWeight.bold, | ||
), | ||
), | ||
), | ||
BlocSelector<CaptureCubit, CaptureCubitState, bool>( | ||
selector: (state) => state.isScoreSelected, | ||
builder: (context, isScoreSelected) { | ||
return AnimatedOpacity( | ||
duration: const Duration(milliseconds: 1500), | ||
opacity: isScoreSelected ? 1.0 : 0.0, | ||
child: Column( | ||
children: const [ | ||
AnswerChips(), | ||
SizedBox(height: 32), | ||
_SubmitButton(), | ||
], | ||
), | ||
], | ||
), | ||
); | ||
}, | ||
), | ||
const SizedBox(height: 55), | ||
// TODO(Jan-Stepien): implement Need Help button | ||
|
@@ -109,51 +96,81 @@ class CaptureView extends StatelessWidget { | |
} | ||
} | ||
|
||
class _SubmitButton extends StatelessWidget { | ||
const _SubmitButton({Key? key}) : super(key: key); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final canSubmit = context.select( | ||
(CaptureCubit cubit) => cubit.state.canSubmit, | ||
); | ||
return ElevatedButton( | ||
onPressed: | ||
canSubmit ? () => context.read<CaptureCubit>().submitResult() : null, | ||
child: const Padding( | ||
padding: EdgeInsets.symmetric( | ||
horizontal: 72, | ||
vertical: 12, | ||
), | ||
child: Text( | ||
Texts.submit, | ||
style: TextStyle( | ||
fontWeight: FontWeight.bold, | ||
), | ||
), | ||
), | ||
); | ||
} | ||
} | ||
|
||
class CaptureScoreSelector extends StatelessWidget { | ||
const CaptureScoreSelector({Key? key, this.radius = 30}) : super(key: key); | ||
|
||
final double radius; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final theme = Theme.of(context); | ||
final maxScore = context.select( | ||
(CaptureCubit cubit) => cubit.state.maxScore, | ||
); | ||
return Row( | ||
mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||
children: List.generate( | ||
context.read<CaptureCubit>().state.maxScore, | ||
(index) => AnimatedContainer( | ||
duration: const Duration(milliseconds: 500), | ||
height: radius * 2, | ||
decoration: BoxDecoration( | ||
shape: BoxShape.circle, | ||
color: | ||
context.select((CaptureCubit cubit) => cubit.state.score) - 1 == | ||
index | ||
? NpsColors.colorSecondary | ||
: NpsColors.colorGrey5, | ||
), | ||
child: TextButton( | ||
onPressed: () => | ||
context.read<CaptureCubit>().selectScore(score: index + 1), | ||
style: ButtonStyle( | ||
shape: MaterialStateProperty.all<CircleBorder>( | ||
const CircleBorder(), | ||
), | ||
alignment: Alignment.center, | ||
maxScore, | ||
(index) { | ||
final isSelected = context.select( | ||
(CaptureCubit cubit) => cubit.state.score - 1 == index, | ||
); | ||
return AnimatedContainer( | ||
duration: const Duration(milliseconds: 500), | ||
height: radius * 2, | ||
decoration: BoxDecoration( | ||
shape: BoxShape.circle, | ||
color: | ||
isSelected ? NpsColors.colorSecondary : NpsColors.colorGrey5, | ||
), | ||
child: Text( | ||
(index + 1).toString(), | ||
style: Theme.of(context).textTheme.subtitle1?.copyWith( | ||
color: context.select( | ||
(CaptureCubit cubit) => cubit.state.score, | ||
) - | ||
1 == | ||
index | ||
? NpsColors.colorWhite | ||
: NpsColors.colorPrimary1, | ||
), | ||
child: TextButton( | ||
onPressed: () { | ||
context.read<CaptureCubit>().selectScore(score: index + 1); | ||
}, | ||
style: ButtonStyle( | ||
shape: MaterialStateProperty.all<CircleBorder>( | ||
const CircleBorder(), | ||
), | ||
alignment: Alignment.center, | ||
), | ||
child: Text( | ||
(index + 1).toString(), | ||
style: theme.textTheme.subtitle1?.copyWith( | ||
color: isSelected | ||
? NpsColors.colorWhite | ||
: NpsColors.colorPrimary1, | ||
), | ||
), | ||
), | ||
), | ||
), | ||
); | ||
}, | ||
), | ||
); | ||
} | ||
|
@@ -214,37 +231,37 @@ class AnswerChips extends StatelessWidget { | |
alignment: WrapAlignment.center, | ||
children: List<List<Widget>>.generate( | ||
chips.length, | ||
(index) => <Widget>[ | ||
ActionChip( | ||
onPressed: () => | ||
context.read<CaptureCubit>().chipToogled(index: index), | ||
padding: const EdgeInsets.symmetric( | ||
horizontal: 16, | ||
vertical: 12, | ||
), | ||
label: Text( | ||
chips[index], | ||
style: TextStyle( | ||
fontWeight: FontWeight.bold, | ||
color: context | ||
.watch<CaptureCubit>() | ||
.state | ||
.chipIndexes | ||
.contains(index) | ||
? NpsColors.colorWhite | ||
: NpsColors.colorPrimary1, | ||
(index) { | ||
final isSelected = context.select( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is to reduce of 'watch rebuilds' ? from 2 to 1? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I apologize if I was unclear when I said scope the watches as low as possible. I meant scope them to the lowest BuildContext. Having multiple watches on the same context is less efficient than a single top level watch. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It makes sense, |
||
(CaptureCubit cubit) => cubit.state.chipIndexes.contains(index), | ||
); | ||
return <Widget>[ | ||
ActionChip( | ||
onPressed: () { | ||
context.read<CaptureCubit>().chipToogled(index: index); | ||
}, | ||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), | ||
label: Text( | ||
chips[index], | ||
style: TextStyle( | ||
fontWeight: FontWeight.bold, | ||
color: isSelected | ||
? NpsColors.colorWhite | ||
: NpsColors.colorPrimary1, | ||
), | ||
), | ||
backgroundColor: | ||
isSelected ? NpsColors.colorSecondary : NpsColors.colorGrey5, | ||
), | ||
backgroundColor: | ||
context.watch<CaptureCubit>().state.chipIndexes.contains(index) | ||
? NpsColors.colorSecondary | ||
: NpsColors.colorGrey5, | ||
), | ||
const SizedBox(width: 8), | ||
], | ||
).reduce( | ||
(value, element) => [...value, ...element], | ||
), | ||
const SizedBox(width: 8), | ||
]; | ||
}, | ||
).reduce((value, element) => [...value, ...element]), | ||
); | ||
} | ||
} | ||
|
||
extension on CaptureCubitState { | ||
bool get isScoreSelected => score != -1; | ||
bool get canSubmit => score != -1 && chipIndexes.isNotEmpty; | ||
} | ||
Comment on lines
+264
to
+267
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hows this better than getters on cubit? i dont really get it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Extension in the UI indicates that this is purely presentation logic that is "syntactic sugar". I recommend avoiding exposing public methods to query state information from the cubit directly because ideally the cubit should only be accessed in order notify of changes in the UI. Getters on the cubit are error prone because they don't trigger rebuilds when the computed value changes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, it wont trigger changes and we want to keep the state as a complete definition, i see that, thanks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using
BlocSelector
here to scope rebuildsThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i get it, all fine