From 2b22e201bf9f9d094ff8b073ef0d64ddb59593f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Demk=C3=B3w?= Date: Sun, 11 Feb 2024 10:36:51 +0100 Subject: [PATCH] feature(recipients_app): Add overview list for surveys (#691) * Add new status and method for not filterred methods * Add surveys overview page * Add surveys overview card to dashboard * Make whole card clickable * Make whole dashboard scrollable * Add survey name to dashboard survey card * Make dateFormat to be based on date skeletons --- .../lib/core/cubits/survey/survey_cubit.dart | 62 +++++++---- .../lib/core/cubits/survey/survey_state.dart | 4 +- .../lib/data/models/survey/mapped_survey.dart | 3 + .../models/survey/survey_card_status.dart | 1 + recipients_app/lib/kri_intl.dart | 12 +-- recipients_app/lib/l10n/app_en.arb | 20 ++++ recipients_app/lib/l10n/app_kri.arb | 21 ++++ recipients_app/lib/ui/configs/app_theme.dart | 1 + .../icons/survey_status_icon_with_text.dart | 1 + .../lib/view/pages/account_page.dart | 23 ++-- .../lib/view/pages/dashboard_page.dart | 53 ++++----- .../lib/view/pages/payment_tile.dart | 4 +- .../lib/view/pages/surveys_page.dart | 57 ++++++++++ .../lib/view/widgets/empty_item.dart | 28 +++++ .../balance_card/balance_card_container.dart | 3 +- .../survey/survey_card_bottom_action.dart | 5 +- .../widgets/survey/survey_card_container.dart | 2 +- .../view/widgets/survey/survey_list_card.dart | 75 +++++++++++++ .../widgets/survey/survey_status_chip.dart | 97 +++++++++++++++++ .../widgets/survey/surveys_overview_card.dart | 101 ++++++++++++++++++ 20 files changed, 506 insertions(+), 67 deletions(-) create mode 100644 recipients_app/lib/view/pages/surveys_page.dart create mode 100644 recipients_app/lib/view/widgets/empty_item.dart create mode 100644 recipients_app/lib/view/widgets/survey/survey_list_card.dart create mode 100644 recipients_app/lib/view/widgets/survey/survey_status_chip.dart create mode 100644 recipients_app/lib/view/widgets/survey/surveys_overview_card.dart diff --git a/recipients_app/lib/core/cubits/survey/survey_cubit.dart b/recipients_app/lib/core/cubits/survey/survey_cubit.dart index 978333a4c..cf17eabdf 100644 --- a/recipients_app/lib/core/cubits/survey/survey_cubit.dart +++ b/recipients_app/lib/core/cubits/survey/survey_cubit.dart @@ -1,6 +1,8 @@ import "package:app/data/models/models.dart"; import "package:app/data/repositories/crash_reporting_repository.dart"; import "package:app/data/repositories/survey_repository.dart"; +import "package:cloud_firestore/cloud_firestore.dart"; +import "package:collection/collection.dart"; import "package:equatable/equatable.dart"; import "package:flutter_bloc/flutter_bloc.dart"; @@ -28,29 +30,17 @@ class SurveyCubit extends Cubit { Future getSurveys() async { try { - final surveys = - await surveyRepository.fetchSurveys(recipientId: recipient.userId); - - final mappedSurveys = surveys - .where((element) => _shouldShowSurveyCard(element)) - .map( - (survey) => MappedSurvey( - survey: survey, - surveyUrl: _getSurveyUrl( - survey, - recipient.userId, - ), - cardStatus: _getSurveyCardStatus(survey), - daysToOverdue: _getDaysToOverdue(survey), - daysAfterOverdue: _getDaysAfterOverdue(survey), - ), - ) + final mappedSurveys = await _getSurveys(); + + final dashboardSurveys = mappedSurveys + .where((element) => _shouldShowSurveyCard(element.survey)) .toList(); emit( SurveyState( status: SurveyStatus.updatedSuccess, mappedSurveys: mappedSurveys, + dashboardMappedSurveys: dashboardSurveys, ), ); } on Exception catch (ex, stackTrace) { @@ -59,6 +49,30 @@ class SurveyCubit extends Cubit { } } + Future> _getSurveys() async { + final surveys = + await surveyRepository.fetchSurveys(recipientId: recipient.userId); + + final mappedSurveys = surveys + .map( + (survey) => MappedSurvey( + name: _getReadableName(survey.id), + survey: survey, + surveyUrl: _getSurveyUrl( + survey, + recipient.userId, + ), + cardStatus: _getSurveyCardStatus(survey), + daysToOverdue: _getDaysToOverdue(survey), + daysAfterOverdue: _getDaysAfterOverdue(survey), + ), + ) + .sortedBy((element) => element.survey.dueDateAt ?? Timestamp.now()) + .toList(); + + return mappedSurveys; + } + String _getSurveyUrl(Survey survey, String recipientId) { final params = { "email": survey.accessEmail, @@ -103,7 +117,11 @@ class SurveyCubit extends Cubit { dateDifferenceInDays < _kOverdueEndDay) { return SurveyCardStatus.overdue; } else { - return SurveyCardStatus.missed; + if ((_getDaysAfterOverdue(survey) ?? 0) > 0) { + return SurveyCardStatus.missed; + } else { + return SurveyCardStatus.upcoming; + } } } else if (survey.status == SurveyServerStatus.completed) { return SurveyCardStatus.answered; @@ -113,6 +131,14 @@ class SurveyCubit extends Cubit { } } +String _getReadableName(String surveyId) { + return surveyId + .split("-") + .map((element) => + "${element[0].toUpperCase()}${element.substring(1).toLowerCase()}") + .join(" "); +} + int? _getDaysToOverdue(Survey survey) { final dueDateDaysDifference = _getSurveyDueDateAndNowDifferenceInDays(survey); if (dueDateDaysDifference == null) { diff --git a/recipients_app/lib/core/cubits/survey/survey_state.dart b/recipients_app/lib/core/cubits/survey/survey_state.dart index ffde3793f..c00da4701 100644 --- a/recipients_app/lib/core/cubits/survey/survey_state.dart +++ b/recipients_app/lib/core/cubits/survey/survey_state.dart @@ -5,12 +5,14 @@ enum SurveyStatus { initial, updatedSuccess, updatedFailure } class SurveyState extends Equatable { final SurveyStatus status; final List mappedSurveys; + final List dashboardMappedSurveys; const SurveyState({ this.status = SurveyStatus.initial, this.mappedSurveys = const [], + this.dashboardMappedSurveys = const [], }); @override - List get props => [status, mappedSurveys]; + List get props => [status, mappedSurveys, dashboardMappedSurveys]; } diff --git a/recipients_app/lib/data/models/survey/mapped_survey.dart b/recipients_app/lib/data/models/survey/mapped_survey.dart index 4bc2309c0..841a85706 100644 --- a/recipients_app/lib/data/models/survey/mapped_survey.dart +++ b/recipients_app/lib/data/models/survey/mapped_survey.dart @@ -3,6 +3,7 @@ import "package:app/data/models/survey/survey_card_status.dart"; import "package:equatable/equatable.dart"; class MappedSurvey extends Equatable { + final String name; final Survey survey; final String surveyUrl; final SurveyCardStatus cardStatus; @@ -10,6 +11,7 @@ class MappedSurvey extends Equatable { final int? daysAfterOverdue; const MappedSurvey({ + required this.name, required this.survey, required this.surveyUrl, required this.cardStatus, @@ -19,6 +21,7 @@ class MappedSurvey extends Equatable { @override List get props => [ + name, survey, surveyUrl, cardStatus, diff --git a/recipients_app/lib/data/models/survey/survey_card_status.dart b/recipients_app/lib/data/models/survey/survey_card_status.dart index b814ac8e8..84641302d 100644 --- a/recipients_app/lib/data/models/survey/survey_card_status.dart +++ b/recipients_app/lib/data/models/survey/survey_card_status.dart @@ -4,4 +4,5 @@ enum SurveyCardStatus { overdue, answered, missed, + upcoming, } diff --git a/recipients_app/lib/kri_intl.dart b/recipients_app/lib/kri_intl.dart index a224aca73..c88df68d4 100644 --- a/recipients_app/lib/kri_intl.dart +++ b/recipients_app/lib/kri_intl.dart @@ -45,12 +45,12 @@ const kriLocaleDatePatterns = { "H": "HH", "Hm": "HH:mm", "Hms": "HH:mm:ss", - "j": "HH", - "jm": "HH:mm", - "jms": "HH:mm:ss", - "jmv": "HH:mm v", - "jmz": "HH:mm z", - "jz": "HH z", + "j": "h a", + "jm": "h:mm a", + "jms": "h:mm:ss a", + "jmv": "h:mm a v", + "jmz": "h:mm a z", + "jz": "h a z", "m": "m", "ms": "mm:ss", "s": "s", diff --git a/recipients_app/lib/l10n/app_en.arb b/recipients_app/lib/l10n/app_en.arb index bfac35584..060cf9936 100644 --- a/recipients_app/lib/l10n/app_en.arb +++ b/recipients_app/lib/l10n/app_en.arb @@ -147,6 +147,26 @@ "continueText": "Continue", "phoneNumber": "Phone number", "appVersion": "App version:", + "surveysTitle": "Surveys", + "surveysEmpty": "No surveys.", + "surveyMissed": "Missed", + "surveyDue": "Due", + "surveyCompleted": "Completed", + "surveyUpcoming": "Upcoming", + "surveyInProgress": "In Progress", + "overview": "Overview", + "completedSurveysCount": "{done}/{all} completed", + "@completedSurveysCount": { + "placeholders": { + "done": { + "type": "int" + }, + "all": { + "type": "int" + } + } + }, + "mySurveysTitle": "My surveys", "invalidPhoneNumberError": "Invalid phone number. Please check your phone number and try again.", "invalidVerificationCodeError": "Invalid verification code. Please check provided SMS code and try again.", diff --git a/recipients_app/lib/l10n/app_kri.arb b/recipients_app/lib/l10n/app_kri.arb index 3f3062caf..b6f426d0d 100644 --- a/recipients_app/lib/l10n/app_kri.arb +++ b/recipients_app/lib/l10n/app_kri.arb @@ -149,6 +149,27 @@ "phoneNumber": "Fon nɔmba", "appVersion": "App version:", + "surveysTitle": "Surveys", + "surveysEmpty": "No surveys.", + "surveyMissed": "Missed", + "surveyDue": "Due", + "surveyCompleted": "Completed", + "surveyUpcoming": "Upcoming", + "surveyInProgress": "In Progress", + "overview": "Overview", + "completedSurveysCount": "{done}/{all} completed", + "@completedSurveysCount": { + "placeholders": { + "done": { + "type": "int" + }, + "all": { + "type": "int" + } + } + }, + "mySurveysTitle": "My surveys", + "invalidPhoneNumberError": "Fon nɔmba nɔ kɔrɛkt. Chɛk yu fon nɔmba ɛn tray bak ya.", "invalidVerificationCodeError": "Di spɛshal kod we wi sɛn yu nɔ kɔrɛkt. Duya chɛk di SMS kod ɛn tray bak.", "userDisabledError": "Wi dɔn lɔk yu akawnt. Rich awt to wi if yu want fɔ no mɔ.", diff --git a/recipients_app/lib/ui/configs/app_theme.dart b/recipients_app/lib/ui/configs/app_theme.dart index f66dc4f6d..2c5e34b55 100644 --- a/recipients_app/lib/ui/configs/app_theme.dart +++ b/recipients_app/lib/ui/configs/app_theme.dart @@ -7,6 +7,7 @@ abstract class AppTheme { static final ThemeData lightTheme = ThemeData( fontFamily: "Unica77LL", + fontFamilyFallback: ["sans-serif"], pageTransitionsTheme: const PageTransitionsTheme( builders: { TargetPlatform.android: SharedAxisPageTransitionsBuilder( diff --git a/recipients_app/lib/ui/icons/survey_status_icon_with_text.dart b/recipients_app/lib/ui/icons/survey_status_icon_with_text.dart index 3d2ef1c54..2721821c2 100644 --- a/recipients_app/lib/ui/icons/survey_status_icon_with_text.dart +++ b/recipients_app/lib/ui/icons/survey_status_icon_with_text.dart @@ -42,6 +42,7 @@ class SurveyStatusIconWithText extends StatelessWidget { case SurveyCardStatus.overdue: case SurveyCardStatus.firstReminder: case SurveyCardStatus.newSurvey: + case SurveyCardStatus.upcoming: // no impl for now. return Container(); } diff --git a/recipients_app/lib/view/pages/account_page.dart b/recipients_app/lib/view/pages/account_page.dart index 4f838bc5c..1cab982b3 100644 --- a/recipients_app/lib/view/pages/account_page.dart +++ b/recipients_app/lib/view/pages/account_page.dart @@ -62,13 +62,12 @@ class AccountPageState extends State { _callingNameController = TextEditingController( text: widget.recipient.callingName ?? "", ); - _birthDateController = TextEditingController( - text: getFormattedDate(widget.recipient.birthDate) ?? "", - ); _emailController = TextEditingController( text: widget.recipient.email ?? "", ); - + _birthDateController = TextEditingController( + text: "", + ); _paymentNumberController = TextEditingController( text: widget.recipient.mobileMoneyPhone?.phoneNumber.toString() ?? "", ); @@ -78,6 +77,12 @@ class AccountPageState extends State { ); _initAppVersionInfo(); + + WidgetsBinding.instance.addPostFrameCallback((_) { + final locale = Localizations.localeOf(context).toLanguageTag(); + _birthDateController.text = + getFormattedDate(widget.recipient.birthDate, locale) ?? ""; + }); } Future _initAppVersionInfo() async { @@ -103,6 +108,7 @@ class AccountPageState extends State { @override Widget build(BuildContext context) { final localizations = AppLocalizations.of(context)!; + final locale = Localizations.localeOf(context).toLanguageTag(); final recipient = context.watch().state.recipient ?? widget.recipient; @@ -238,7 +244,7 @@ class AccountPageState extends State { ), ); _birthDateController.text = - getFormattedDate(timestamp) ?? ""; + getFormattedDate(timestamp, locale) ?? ""; } return; }), @@ -444,8 +450,11 @@ class AccountPageState extends State { ); } - String? getFormattedDate(Timestamp? timestamp) { + String? getFormattedDate( + Timestamp? timestamp, + String locale, + ) { if (timestamp == null) return null; - return DateFormat("dd.MM.yyyy").format(timestamp.toDate()); + return DateFormat.yMd(locale).format(timestamp.toDate()); } } diff --git a/recipients_app/lib/view/pages/dashboard_page.dart b/recipients_app/lib/view/pages/dashboard_page.dart index 8c0806bec..5341e6803 100644 --- a/recipients_app/lib/view/pages/dashboard_page.dart +++ b/recipients_app/lib/view/pages/dashboard_page.dart @@ -5,11 +5,12 @@ import "package:app/core/cubits/survey/survey_cubit.dart"; import "package:app/data/repositories/repositories.dart"; import "package:app/ui/configs/configs.dart"; import "package:app/view/widgets/dashboard_item.dart"; +import "package:app/view/widgets/empty_item.dart"; import "package:app/view/widgets/income/balance_card/balance_card_container.dart"; import "package:app/view/widgets/survey/survey_card_container.dart"; +import "package:app/view/widgets/survey/surveys_overview_card.dart"; import "package:flutter/material.dart"; import "package:flutter_bloc/flutter_bloc.dart"; -import "package:flutter_gen/gen_l10n/app_localizations.dart"; class DashboardPage extends StatelessWidget { const DashboardPage({super.key}); @@ -51,7 +52,7 @@ class _DashboardView extends StatelessWidget { @override Widget build(BuildContext context) { - final localizations = AppLocalizations.of(context)!; + final surveys = context.watch().state.mappedSurveys; final List dashboardItems = context .watch() @@ -63,7 +64,7 @@ class _DashboardView extends StatelessWidget { final List surveysItems = context .watch() .state - .mappedSurveys + .dashboardMappedSurveys .map( (survey) => SurveyCardContainer( mappedSurvey: survey, @@ -71,38 +72,30 @@ class _DashboardView extends StatelessWidget { ) .toList(); - final items = dashboardItems + surveysItems; + final dynamicItemsCount = dashboardItems.length + surveysItems.length; + + final List headerItems = [ + const BalanceCardContainer(), + SurveysOverviewCard(mappedSurveys: surveys), + ]; + + List items; + + if (dynamicItemsCount > 0) { + items = headerItems + dashboardItems + surveysItems; + } else { + items = headerItems + [const EmptyItem()]; + } return BlocBuilder( builder: (context, state) { return Padding( padding: AppSpacings.h8, - child: Column( - children: [ - const BalanceCardContainer(), - const SizedBox(height: 8), - if (items.isEmpty) - Expanded( - child: Padding( - padding: AppSpacings.a8, - child: Center( - child: Text( - localizations.dashboardUp2Date, - textAlign: TextAlign.center, - ), - ), - ), - ) - else - Expanded( - child: ListView.separated( - separatorBuilder: (context, index) => - const SizedBox(height: 8), - itemCount: items.length, - itemBuilder: (context, index) => items[index], - ), - ), - ], + child: ListView.separated( + separatorBuilder: (context, index) => const SizedBox(height: 4), + itemCount: items.length, + itemBuilder: (context, index) => items[index], + physics: const BouncingScrollPhysics(), ), ); }, diff --git a/recipients_app/lib/view/pages/payment_tile.dart b/recipients_app/lib/view/pages/payment_tile.dart index 8255511fe..3a60d3661 100644 --- a/recipients_app/lib/view/pages/payment_tile.dart +++ b/recipients_app/lib/view/pages/payment_tile.dart @@ -65,10 +65,10 @@ class PaymentTile extends StatelessWidget { String dateFormat; String formattedDate = ""; if (mappedPayment.uiStatus == PaymentUiStatus.toBePaid) { - dateFormat = "dd MMMM yyyy"; + dateFormat = DateFormat.YEAR_MONTH_DAY; formattedDate = localizations.nextPayment + " "; } else { - dateFormat = "MMMM yyyy"; + dateFormat = DateFormat.YEAR_MONTH; } if (_reviewUiStatuses.contains(mappedPayment.uiStatus)) { formattedDate = localizations.review + " "; diff --git a/recipients_app/lib/view/pages/surveys_page.dart b/recipients_app/lib/view/pages/surveys_page.dart new file mode 100644 index 000000000..23f80f087 --- /dev/null +++ b/recipients_app/lib/view/pages/surveys_page.dart @@ -0,0 +1,57 @@ +import "package:app/core/cubits/survey/survey_cubit.dart"; +import "package:app/ui/configs/configs.dart"; +import "package:app/view/widgets/survey/survey_list_card.dart"; +import "package:flutter/material.dart"; +import "package:flutter_bloc/flutter_bloc.dart"; +import "package:flutter_gen/gen_l10n/app_localizations.dart"; + +class SurveysPage extends StatelessWidget { + const SurveysPage({super.key}); + + @override + Widget build(BuildContext context) { + final localizations = AppLocalizations.of(context)!; + + final mappedSurveys = context.watch().state.mappedSurveys; + + return Scaffold( + appBar: AppBar( + elevation: 0, + title: Text(localizations.surveysTitle), + leading: BackButton(onPressed: () { + context.read().getSurveys(); + Navigator.maybePop(context); + }), + centerTitle: true, + ), + body: Padding( + padding: AppSpacings.h8, + child: Column( + children: [ + if (mappedSurveys.isEmpty) + Expanded( + child: Padding( + padding: AppSpacings.a8, + child: Center( + child: Text( + localizations.surveysEmpty, + textAlign: TextAlign.center, + ), + ), + ), + ) + else + Expanded( + child: ListView.builder( + itemCount: mappedSurveys.length, + itemBuilder: (context, index) { + return SurveyListCard(mappedSurvey: mappedSurveys[index]); + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/recipients_app/lib/view/widgets/empty_item.dart b/recipients_app/lib/view/widgets/empty_item.dart new file mode 100644 index 000000000..7bd9844f6 --- /dev/null +++ b/recipients_app/lib/view/widgets/empty_item.dart @@ -0,0 +1,28 @@ +import "package:app/ui/configs/configs.dart"; +import "package:app/view/widgets/dashboard_item.dart"; +import "package:flutter/material.dart"; +import "package:flutter_gen/gen_l10n/app_localizations.dart"; + +class EmptyItem extends DashboardItem { + const EmptyItem({super.key}); + + @override + Widget build(BuildContext context) { + final localizations = AppLocalizations.of(context)!; + + return ConstrainedBox( + constraints: const BoxConstraints(minHeight: 200), + child: Container( + child: Padding( + padding: AppSpacings.a8, + child: Center( + child: Text( + localizations.dashboardUp2Date, + textAlign: TextAlign.center, + ), + ), + ), + ), + ); + } +} diff --git a/recipients_app/lib/view/widgets/income/balance_card/balance_card_container.dart b/recipients_app/lib/view/widgets/income/balance_card/balance_card_container.dart index 0d659e5e2..1688156e2 100644 --- a/recipients_app/lib/view/widgets/income/balance_card/balance_card_container.dart +++ b/recipients_app/lib/view/widgets/income/balance_card/balance_card_container.dart @@ -3,6 +3,7 @@ import "package:app/data/models/payment/payment.dart"; import "package:app/ui/configs/configs.dart"; import "package:app/view/pages/payment_tile.dart"; import "package:app/view/pages/payments_page.dart"; +import "package:app/view/widgets/dashboard_item.dart"; import "package:app/view/widgets/income/balance_card/balance_card_grid.dart"; import "package:app/view/widgets/income/balance_card/balance_card_header.dart"; import "package:app/view/widgets/income/balance_card/on_hold_bottom_card.dart"; @@ -16,7 +17,7 @@ const _kShowPaymentCardStatuses = [ PaymentUiStatus.toReview, ]; -class BalanceCardContainer extends StatelessWidget { +class BalanceCardContainer extends DashboardItem { const BalanceCardContainer({super.key}); @override diff --git a/recipients_app/lib/view/widgets/survey/survey_card_bottom_action.dart b/recipients_app/lib/view/widgets/survey/survey_card_bottom_action.dart index b05e85575..f3c8be614 100644 --- a/recipients_app/lib/view/widgets/survey/survey_card_bottom_action.dart +++ b/recipients_app/lib/view/widgets/survey/survey_card_bottom_action.dart @@ -94,6 +94,7 @@ class SurveyCardBottomAction extends StatelessWidget { case SurveyCardStatus.firstReminder: return AppColors.primaryColor; case SurveyCardStatus.newSurvey: + case SurveyCardStatus.upcoming: return Colors.white; } } @@ -108,6 +109,7 @@ class SurveyCardBottomAction extends StatelessWidget { case SurveyCardStatus.firstReminder: return Colors.white; case SurveyCardStatus.newSurvey: + case SurveyCardStatus.upcoming: return AppColors.fontColorDark; } } @@ -121,7 +123,7 @@ class SurveyCardBottomAction extends StatelessWidget { switch (mappedSurvey.cardStatus) { case SurveyCardStatus.answered: - return DateFormat("dd.MM.yyyy", locale).format( + return DateFormat.yMd(locale).format( mappedSurvey.survey.completedAt?.toDate() ?? DateTime.now()); case SurveyCardStatus.overdue: final daysAfterOverdue = mappedSurvey.daysAfterOverdue ?? 0; @@ -137,6 +139,7 @@ class SurveyCardBottomAction extends StatelessWidget { } return localizations.surveyDaysLeft(daysToOverdue, daysText); case SurveyCardStatus.missed: + case SurveyCardStatus.upcoming: return ""; } } diff --git a/recipients_app/lib/view/widgets/survey/survey_card_container.dart b/recipients_app/lib/view/widgets/survey/survey_card_container.dart index f8655a7e4..01e818c35 100644 --- a/recipients_app/lib/view/widgets/survey/survey_card_container.dart +++ b/recipients_app/lib/view/widgets/survey/survey_card_container.dart @@ -33,7 +33,7 @@ class SurveyCardContainer extends DashboardItem { mainAxisSize: MainAxisSize.min, children: [ Text( - localizations.surveyCardTitle, + "${mappedSurvey.name} ${localizations.surveyCardTitle}", style: const TextStyle( color: Colors.black, fontSize: 13.0, diff --git a/recipients_app/lib/view/widgets/survey/survey_list_card.dart b/recipients_app/lib/view/widgets/survey/survey_list_card.dart new file mode 100644 index 000000000..07abe069d --- /dev/null +++ b/recipients_app/lib/view/widgets/survey/survey_list_card.dart @@ -0,0 +1,75 @@ +import "package:app/data/models/models.dart"; +import "package:app/ui/configs/app_colors.dart"; +import "package:app/ui/configs/app_sizes.dart"; +import "package:app/view/widgets/survey/survey_status_chip.dart"; +import "package:flutter/material.dart"; +import "package:flutter_gen/gen_l10n/app_localizations.dart"; +import "package:intl/intl.dart"; + +class SurveyListCard extends StatelessWidget { + final MappedSurvey mappedSurvey; + + const SurveyListCard({super.key, required this.mappedSurvey}); + + @override + Widget build(BuildContext context) { + final localizations = AppLocalizations.of(context)!; + final locale = Localizations.localeOf(context).toLanguageTag(); + + return Card( + clipBehavior: Clip.antiAlias, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + mappedSurvey.name, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Colors.black, + ), + ), + const SizedBox(height: 4), + Text( + _formatDate( + mappedSurvey.survey.dueDateAt?.toDate(), + localizations, + locale, + ), + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: AppColors.darkGrey, + ), + ), + ], + ), + const SizedBox(width: AppSizes.small), + SurveyServerStatusChip( + status: mappedSurvey.cardStatus, + serverStatus: mappedSurvey.survey.status, + ), + ], + ), + ), + ], + ), + ); + } + + String _formatDate( + DateTime? dateTime, + AppLocalizations localizations, + String locale, + ) { + if (dateTime == null) return ""; + + return "${DateFormat.yMd(locale).format(dateTime)}"; + } +} diff --git a/recipients_app/lib/view/widgets/survey/survey_status_chip.dart b/recipients_app/lib/view/widgets/survey/survey_status_chip.dart new file mode 100644 index 000000000..eb8696a9c --- /dev/null +++ b/recipients_app/lib/view/widgets/survey/survey_status_chip.dart @@ -0,0 +1,97 @@ +import "package:app/data/models/survey/survey_card_status.dart"; +import "package:app/data/models/survey/survey.dart"; +import "package:app/ui/configs/configs.dart"; +import "package:flutter_gen/gen_l10n/app_localizations.dart"; +import "package:flutter/material.dart"; + +const statusIconHeight = 26.0; + +class SurveyServerStatusChip extends StatelessWidget { + final SurveyCardStatus status; + final SurveyServerStatus? serverStatus; + + const SurveyServerStatusChip({ + super.key, + required this.status, + required this.serverStatus, + }); + + @override + Widget build(BuildContext context) { + final localizations = AppLocalizations.of(context)!; + + late Color color; + late Color textColor; + + switch (status) { + case SurveyCardStatus.answered: + color = AppColors.primaryColor; + textColor = Colors.white; + break; + case SurveyCardStatus.missed: + color = AppColors.redColor; + textColor = AppColors.fontColorDark; + break; + case SurveyCardStatus.overdue: + case SurveyCardStatus.firstReminder: + case SurveyCardStatus.newSurvey: + color = AppColors.yellowColor; + textColor = AppColors.fontColorDark; + break; + case SurveyCardStatus.upcoming: + color = AppColors.backgroundColor; + textColor = AppColors.fontColorDark; + } + + return Container( + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(AppSizes.radiusMedium), + ), + height: statusIconHeight, + child: Padding( + padding: AppSpacings.h8v4, + child: Row( + children: [ + Text( + _getStatusName( + status, + serverStatus, + localizations, + ), + style: AppStyles.iconLabel.copyWith( + color: textColor, + ), + ), + ], + ), + ), + ); + } +} + +String _getStatusName( + SurveyCardStatus status, + SurveyServerStatus? serverStatus, + AppLocalizations localizations, +) { + final String statusName; + switch (status) { + case SurveyCardStatus.newSurvey: + case SurveyCardStatus.firstReminder: + case SurveyCardStatus.overdue: + if (serverStatus != SurveyServerStatus.inProgress) { + statusName = localizations.surveyDue; + } else { + statusName = localizations.surveyInProgress; + } + case SurveyCardStatus.answered: + statusName = localizations.surveyCompleted; + case SurveyCardStatus.missed: + statusName = localizations.surveyMissed; + case SurveyCardStatus.upcoming: + statusName = localizations.surveyUpcoming; + } + + return statusName; +} diff --git a/recipients_app/lib/view/widgets/survey/surveys_overview_card.dart b/recipients_app/lib/view/widgets/survey/surveys_overview_card.dart new file mode 100644 index 000000000..07a2930d6 --- /dev/null +++ b/recipients_app/lib/view/widgets/survey/surveys_overview_card.dart @@ -0,0 +1,101 @@ +import "package:app/core/cubits/survey/survey_cubit.dart"; +import "package:app/data/models/models.dart"; +import "package:app/ui/buttons/button_small.dart"; +import "package:app/ui/configs/app_colors.dart"; +import "package:app/ui/configs/app_sizes.dart"; +import "package:app/view/pages/surveys_page.dart"; +import "package:app/view/widgets/dashboard_item.dart"; +import "package:flutter/material.dart"; +import "package:flutter_bloc/flutter_bloc.dart"; +import "package:flutter_gen/gen_l10n/app_localizations.dart"; + +const _doneStatuses = [ + SurveyCardStatus.missed, + SurveyCardStatus.answered, +]; + +class SurveysOverviewCard extends DashboardItem { + final List mappedSurveys; + + const SurveysOverviewCard({super.key, required this.mappedSurveys}); + + @override + Widget build(BuildContext context) { + final localizations = AppLocalizations.of(context)!; + + return GestureDetector( + onTap: () => _navigateToSurveysPage(context), + child: Card( + clipBehavior: Clip.antiAlias, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + localizations.mySurveysTitle, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Colors.black, + ), + ), + const SizedBox(height: 4), + Text( + localizations.completedSurveysCount( + _getDoneSurveysCount(), + mappedSurveys.length, + ), + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: AppColors.darkGrey, + ), + ), + ], + ), + const SizedBox(width: AppSizes.small), + ButtonSmall( + onPressed: () => _navigateToSurveysPage(context), + label: localizations.overview, + buttonType: ButtonSmallType.outlined, + color: AppColors.fontColorDark, + ), + ], + ), + ), + ], + ), + ), + ); + } + + _navigateToSurveysPage(BuildContext context) { + final surveyCubit = context.read(); + + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => BlocProvider.value( + value: surveyCubit, + child: const SurveysPage(), + ), + ), + ); + } + + int _getDoneSurveysCount() { + return mappedSurveys.fold( + 0, + (acc, element) { + if (_doneStatuses.contains(element.cardStatus)) + return acc + 1; + else + return acc; + }, + ); + } +}