Skip to content

Commit

Permalink
feat: create ui for survey questions (#63)
Browse files Browse the repository at this point in the history
# Conflicts:
#	lib/modules/survey_detail/survey_detail_module.dart
  • Loading branch information
markgravity committed May 15, 2021
1 parent 7ec307c commit df896f1
Show file tree
Hide file tree
Showing 19 changed files with 569 additions and 4 deletions.
3 changes: 3 additions & 0 deletions assets/images/rounded-checkbox-normal.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions assets/images/rounded-checkbox-selected.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion assets/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@
"loginScreenPasswordTextFieldPlaceholderText": "Password",
"homeScreenTodayText": "Today",
"sideMenuLogoutButtonTitle": "Logout",
"surveyDetailScreenStartSurveyButtonTitle": "Start Survey"
"surveyDetailScreenStartSurveyButtonTitle": "Start Survey",
"surveyQuestionsScreenLowestScoreNPSText": "Not at all Likely",
"surveyQuestionsScreenHighestScoreNPSText": "Extremely Likely"
}
4 changes: 3 additions & 1 deletion assets/l10n/app_vi.arb
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@
"loginScreenPasswordTextFieldPlaceholderText": "Mật khẩu",
"homeScreenTodayText": "Hôm nay",
"sideMenuLogoutButtonTitle": "Đăng xuất",
"surveyDetailScreenStartSurveyButtonTitle": "Bắt đầu khảm sát"
"surveyDetailScreenStartSurveyButtonTitle": "Bắt đầu khảm sát",
"surveyQuestionsScreenLowestScoreNPSText": "Hoàn toàn không",
"surveyQuestionsScreenHighestScoreNPSText": "Rất có thể"
}
11 changes: 9 additions & 2 deletions lib/gen/assets.gen.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

118 changes: 118 additions & 0 deletions lib/modules/survey_questions/components/answers/nps_answer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
part of '../../survey_questions_module.dart';

class NPSAnswer extends StatefulWidget {
static const numberOfItems = 10;

const NPSAnswer({
Key? key,
this.score,
this.onSelect,
}) : super(key: key);

final int? score;
final ValueChanged<int?>? onSelect;

@override
_NPSAnswerState createState() => _NPSAnswerState();
}

class _NPSAnswerState extends State<NPSAnswer> {
late final selected =
BehaviorSubject<int?>.seeded(widget.score != null ? widget.score! - 1 : null);

@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: 56,
decoration: BoxDecoration(
border: Border.all(color: Colors.white),
borderRadius: BorderRadius.circular(10),
),
child: ListView.builder(
shrinkWrap: true,
itemCount: NPSAnswer.numberOfItems,
itemBuilder: _itemBuilder,
scrollDirection: Axis.horizontal,
),
),
const SizedBox(
height: 15,
),
Container(
margin: const EdgeInsets.symmetric(horizontal: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
StreamsSelector0<int?>.value(
stream: selected,
builder: (_, selected, __) => Text(
AppLocalizations.of(context)!.surveyQuestionsScreenLowestScoreNPSText,
style: TextStyle(
color: selected == 0 ? Colors.white : Colors.white.withOpacity(0.5),
fontSize: 17,
fontWeight: FontWeight.w800,
),
),
),
StreamsSelector0<int?>.value(
stream: selected,
builder: (_, selected, __) => Text(
AppLocalizations.of(context)!.surveyQuestionsScreenHighestScoreNPSText,
style: TextStyle(
color: selected == NPSAnswer.numberOfItems - 1
? Colors.white
: Colors.white.withOpacity(0.5),
fontSize: 17,
fontWeight: FontWeight.w800,
),
),
),
],
),
)
],
);
}

Widget _itemBuilder(BuildContext context, int i) {
return GestureDetector(
onTap: () {
selected.add(i);
if (widget.onSelect != null) widget.onSelect!(i + 1);
},
child: SizedBox(
width: 35,
child: Row(
children: [
if (i != 0)
Container(
margin: const EdgeInsets.symmetric(vertical: 1),
color: Colors.white,
width: 1,
),
Expanded(
child: StreamsSelector0<int?>.value(
stream: selected,
builder: (_, selected, __) => Text(
"${i + 1}",
textAlign: TextAlign.center,
style: TextStyle(
color: selected != null && i <= selected
? Colors.white
: Colors.white.withOpacity(0.5),
fontSize: 20,
fontWeight: FontWeight.w800,
),
),
),
),
],
),
),
);
}
}
63 changes: 63 additions & 0 deletions lib/modules/survey_questions/components/answers/rating_answer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
part of '../../survey_questions_module.dart';

class RatingAnswer extends StatefulWidget {
const RatingAnswer({
Key? key,
required this.symbol,
this.selected,
this.onSelect,
}) : super(key: key);

final String symbol;
final int? selected;
final ValueChanged<int?>? onSelect;

@override
_RatingAnswerState createState() => _RatingAnswerState();
}

class _RatingAnswerState extends State<RatingAnswer> {
late final BehaviorSubject<int?> selected = BehaviorSubject.seeded(widget.selected);

@override
Widget build(BuildContext context) {
return SizedBox(
height: 34,
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemBuilder: _itemBuilder,
itemCount: 5,
),
);
}

Widget _itemBuilder(BuildContext context, int i) => StreamsSelector0<int?>.value(
stream: selected,
builder: (_, selected, child) {
return Opacity(
opacity: selected != null && i <= selected ? 1 : 0.5,
child: child,
);
},
child: GestureDetector(
onTap: () {
selected.add(i);

if (widget.onSelect != null) {
widget.onSelect!(i + 1);
}
},
child: Container(
padding: EdgeInsets.fromLTRB(i == 0 ? 0 : 16, 0, 0, 0),
child: Text(
widget.symbol,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.w800,
),
),
),
),
);
}
141 changes: 141 additions & 0 deletions lib/modules/survey_questions/components/answers/select_answer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
part of '../../survey_questions_module.dart';

class SelectAnswer extends StatefulWidget {
static const itemHeight = 50.0;

const SelectAnswer({
Key? key,
required this.options,
this.isMultiSelection = false,
this.selectedIndexes = const [],
this.onSelect,
}) : super(key: key);

final bool isMultiSelection;
final List<int> selectedIndexes;
final List<SelectOptionAnswer> options;
final ValueChanged<List<SelectOptionAnswer>>? onSelect;

@override
_SelectAnswerState createState() => _SelectAnswerState();
}

class _SelectAnswerState extends State<SelectAnswer> {
late final BehaviorSubject<List<int>> selectedIndexes;
final disposeBag = CompositeSubscription();

@override
void didChangeDependencies() {
selectedIndexes = BehaviorSubject<List<int>>.seeded(widget.selectedIndexes);
selectedIndexes.listen(_onSelectedIndexesUpdate).addTo(disposeBag);

super.didChangeDependencies();
}

@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 60),
height: SelectAnswer.itemHeight * 3,
child: Stack(
children: [
ScrollSnapList(
scrollDirection: Axis.vertical,
curve: Curves.decelerate,
duration: 1,
itemSize: SelectAnswer.itemHeight,
itemCount: widget.options.length,
onItemFocus: _onItemFocus,
itemBuilder: _itemBuilder,
dynamicItemOpacity: widget.isMultiSelection ? 1 : 0.5,
),
Align(
child: SizedBox(
height: SelectAnswer.itemHeight,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
height: 1,
color: Colors.white,
),
Container(
height: 1,
color: Colors.white,
),
],
),
),
)
],
),
);
}

Widget _itemBuilder(BuildContext context, int i) {
return SizedBox(
height: SelectAnswer.itemHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
StreamsSelector0<List<int>>.value(
stream: selectedIndexes,
builder: (_, selectedIndexes, __) {
return Text(
widget.options[i].name,
textAlign: TextAlign.left,
style: TextStyle(
color: Colors.white,
fontWeight: selectedIndexes.contains(i) ? FontWeight.w800 : FontWeight.normal,
fontSize: 20,
),
);
},
),
if (widget.isMultiSelection)
GestureDetector(
onTap: () => _onCheckboxTap(i),
child: StreamsSelector0<List<int>>.value(
stream: selectedIndexes,
builder: (_, selectedIndexes, __) => selectedIndexes.contains(i)
? Assets.images.roundedCheckboxSelected.svg()
: Assets.images.roundedCheckboxNormal.svg(),
),
),
],
),
);
}

void _onCheckboxTap(int i) {
final indexes = selectedIndexes.value.toList(growable: true);
indexes.contains(i) ? indexes.remove(i) : indexes.add(i);
selectedIndexes.add(indexes);
}

void _onItemFocus(int i) {
if (widget.isMultiSelection) return;
selectedIndexes.add([i]);
}

void _onSelectedIndexesUpdate(List<int> indexes) {
if (widget.onSelect == null) return;
widget.onSelect!(indexes.map((e) => widget.options[e]).toList());
}

@override
void dispose() {
disposeBag.dispose();
super.dispose();
}
}

class SelectOptionAnswer {
const SelectOptionAnswer({
required this.id,
required this.name,
});

final String id;
final String name;
}
Loading

0 comments on commit df896f1

Please sign in to comment.