Skip to content

Commit

Permalink
feat: show card conditionally
Browse files Browse the repository at this point in the history
  • Loading branch information
borgoat committed Dec 31, 2024
1 parent 2d07da2 commit 7e64dc1
Show file tree
Hide file tree
Showing 15 changed files with 163 additions and 21 deletions.
1 change: 1 addition & 0 deletions lib/actions/actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export 'auth.dart';
export 'date.dart';
export 'deeplinks.dart';
export 'default_rules.dart';
export 'feedback.dart';
export 'groups.dart';
export 'invites.dart';
export 'locale.dart';
Expand Down
2 changes: 2 additions & 0 deletions lib/actions/feedback.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/// An action to indicate that the user has interacted with the feedback card
class InteractedWithFeedback {}
6 changes: 6 additions & 0 deletions lib/l10n/app_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@
"eventName": "Name des Events",
"events": "Events",
"eventsEmpty": "Es ist Zeit, deiner Gruppe Leben einzuhauchen!<br/>Tippe auf die Schaltfläche <newevent/>, um deine erste Aktivität zu planen.<br/>Wähle ein Datum, setze eine Uhrzeit und füge Details hinzu, um alle begeistert zu machen, zusammenzukommen.",
"feedbackEmailSubject": "Feedback für GRUP",
"feedbackEmailBody": "Hallo GRUP-Team,\n\nIch habe ein Feedback für euch:\n\n",
"feedbackPromptTitle": "Wie machen wir uns?",
"feedbackPromptBody": "Wenn dir <title>GRUP</title> gefällt, hinterlasse uns bitte eine positive Bewertung, um mehr Menschen zu erreichen!\nWenn du Feedback hast, lass es uns wissen, damit wir uns verbessern können.",
"feedbackStoreReview": "Bewerte uns auf {store}",
"feedbackTellUs": "Sende uns eine Nachricht",
"gallery": "Galerie",
"groupDismissAdmin": "Admin entfernen",
"groupDismissAdminConfirmation": "Bist du sicher, dass du dieses Mitglied als Admin entfernen möchtest?",
Expand Down
6 changes: 6 additions & 0 deletions lib/l10n/app_es.arb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@
"eventName": "Nombre del evento",
"events": "Eventos",
"eventsEmpty": "¡Es hora de dar vida a tu grupo!<br/>Toca el botón <newevent/> para programar tu primera actividad.<br/>Elige una fecha, establece una hora y añade detalles para que todos estén emocionados de reunirse.",
"feedbackEmailSubject": "Feedback para GRUP",
"feedbackEmailBody": "Hola equipo de GRUP,\n\nTengo algunos comentarios para vosotros:\n\n",
"feedbackPromptTitle": "¿Cómo lo estamos haciendo?",
"feedbackPromptBody": "Si te gusta <title>GRUP</title>, por favor déjanos una reseña positiva para llegar a más personas.\nSi tienes algún comentario, por favor háznoslo saber para que podamos mejorar.",
"feedbackStoreReview": "Califícanos en {store}",
"feedbackTellUs": "Envíanos un mensaje",
"gallery": "Galería",
"groupDismissAdmin": "Descarta como admin",
"groupDismissAdminConfirmation": "¿Seguro que quieres descartar a este miembro como admin?",
Expand Down
6 changes: 6 additions & 0 deletions lib/l10n/app_fr.arb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@
"eventName": "Nom de l'événement",
"events": "Événements",
"eventsEmpty": "Il est temps de donner vie à ton groupe!<br/>Appuie sur le bouton <newevent/> pour planifier ta première activité.<br/>Choisis une date, définis une heure et ajoute des détails pour que tout le monde soit excité de se retrouver.",
"feedbackEmailSubject": "Feedback pour GRUP",
"feedbackEmailBody": "Salut équipe GRUP,\n\nJ'ai des commentaires pour vous:\n\n",
"feedbackPromptTitle": "Comment nous débrouillons-nous?",
"feedbackPromptBody": "Si tu aimes <title>GRUP</title>, laisse-nous un avis positif pour toucher plus de personnes!\nSi tu as des commentaires, fais-le nous savoir pour que nous puissions nous améliorer.",
"feedbackStoreReview": "Évalue-nous sur {store}",
"feedbackTellUs": "Envoie-nous un message",
"gallery": "Galerie",
"groupDismissAdmin": "Révoque l'admin",
"groupDismissAdminConfirmation": "Es-tu sûr de vouloir révoquer ce membre comme admin?",
Expand Down
6 changes: 6 additions & 0 deletions lib/l10n/app_it.arb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@
"eventName": "Nome dell'evento",
"events": "Eventi",
"eventsEmpty": "È il momento di dare vita al tuo gruppo!<br/>Tocca il pulsante <newevent/> per pianificare la tua prima attività.<br/>Scegli una data, imposta un orario e aggiungi dettagli per far sì che tutti siano entusiasti di riunirsi.",
"feedbackEmailSubject": "Feedback per GRUP",
"feedbackEmailBody": "Ciao team GRUP,\n\nHo un feedback per voi:\n\n",
"feedbackPromptTitle": "Come stiamo andando?",
"feedbackPromptBody": "Se ti piace <title>GRUP</title>, lasciaci una recensione positiva per raggiungere più persone!\nSe hai qualche feedback, faccelo sapere così possiamo migliorare.",
"feedbackStoreReview": "Valutaci su {store}",
"feedbackTellUs": "Inviaci un messaggio",
"gallery": "Galleria",
"groupDismissAdmin": "Rimuovi come amministratore",
"groupDismissAdminConfirmation": "Sei sicuro di voler rimuovere questo membro come amministratore?",
Expand Down
12 changes: 12 additions & 0 deletions lib/presentation/containers/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class HomeContainer extends StatelessWidget {
loading: vm.loading,
onGroupCreate: vm.onGroupCreate,
onRefresh: vm.onRefresh,
shouldShowFeedback: vm.shouldShowFeedback,
onFeedbackDismiss: vm.onFeedbackDismiss,
),
converter: _ViewModel.fromStore,
);
Expand All @@ -37,6 +39,8 @@ sealed class _ViewModel with _$ViewModel {
Iterable<Group>? groups,
ValueSetter<GroupCreateResult>? onGroupCreate,
AsyncCallback? onRefresh,
bool? shouldShowFeedback,
VoidCallback? onFeedbackDismiss,
}) = __ViewModel;

static _ViewModel fromStore(Store<AppState> store) {
Expand All @@ -57,6 +61,14 @@ sealed class _ViewModel with _$ViewModel {
store.dispatch(action);
await action.completer.future;
},
shouldShowFeedback: !(store.state.hasSeenFeedbackCard ?? false) &&
store.state.auth.user != null &&
store.state.profiles.entities[store.state.auth.user!.id]?.createdAt
?.isBefore(
DateTime.now().subtract(const Duration(days: 3)),
) ==
true,
onFeedbackDismiss: () => store.dispatch(InteractedWithFeedback()),
);
}
}
69 changes: 60 additions & 9 deletions lib/presentation/containers/home.freezed.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ mixin _$ViewModel {
ValueSetter<GroupCreateResult>? get onGroupCreate =>
throw _privateConstructorUsedError;
AsyncCallback? get onRefresh => throw _privateConstructorUsedError;
bool? get shouldShowFeedback => throw _privateConstructorUsedError;
VoidCallback? get onFeedbackDismiss => throw _privateConstructorUsedError;

/// Create a copy of _ViewModel
/// with the given fields replaced by the non-null parameter values.
Expand All @@ -41,7 +43,9 @@ abstract class _$ViewModelCopyWith<$Res> {
Profile? profile,
Iterable<Group>? groups,
ValueSetter<GroupCreateResult>? onGroupCreate,
AsyncCallback? onRefresh});
AsyncCallback? onRefresh,
bool? shouldShowFeedback,
VoidCallback? onFeedbackDismiss});

$ProfileCopyWith<$Res>? get profile;
}
Expand All @@ -66,6 +70,8 @@ class __$ViewModelCopyWithImpl<$Res, $Val extends _ViewModel>
Object? groups = freezed,
Object? onGroupCreate = freezed,
Object? onRefresh = freezed,
Object? shouldShowFeedback = freezed,
Object? onFeedbackDismiss = freezed,
}) {
return _then(_value.copyWith(
loading: null == loading
Expand All @@ -88,6 +94,14 @@ class __$ViewModelCopyWithImpl<$Res, $Val extends _ViewModel>
? _value.onRefresh
: onRefresh // ignore: cast_nullable_to_non_nullable
as AsyncCallback?,
shouldShowFeedback: freezed == shouldShowFeedback
? _value.shouldShowFeedback
: shouldShowFeedback // ignore: cast_nullable_to_non_nullable
as bool?,
onFeedbackDismiss: freezed == onFeedbackDismiss
? _value.onFeedbackDismiss
: onFeedbackDismiss // ignore: cast_nullable_to_non_nullable
as VoidCallback?,
) as $Val);
}

Expand Down Expand Up @@ -119,7 +133,9 @@ abstract class _$$_ViewModelImplCopyWith<$Res>
Profile? profile,
Iterable<Group>? groups,
ValueSetter<GroupCreateResult>? onGroupCreate,
AsyncCallback? onRefresh});
AsyncCallback? onRefresh,
bool? shouldShowFeedback,
VoidCallback? onFeedbackDismiss});

@override
$ProfileCopyWith<$Res>? get profile;
Expand All @@ -143,6 +159,8 @@ class __$$_ViewModelImplCopyWithImpl<$Res>
Object? groups = freezed,
Object? onGroupCreate = freezed,
Object? onRefresh = freezed,
Object? shouldShowFeedback = freezed,
Object? onFeedbackDismiss = freezed,
}) {
return _then(_$_ViewModelImpl(
loading: null == loading
Expand All @@ -165,6 +183,14 @@ class __$$_ViewModelImplCopyWithImpl<$Res>
? _value.onRefresh
: onRefresh // ignore: cast_nullable_to_non_nullable
as AsyncCallback?,
shouldShowFeedback: freezed == shouldShowFeedback
? _value.shouldShowFeedback
: shouldShowFeedback // ignore: cast_nullable_to_non_nullable
as bool?,
onFeedbackDismiss: freezed == onFeedbackDismiss
? _value.onFeedbackDismiss
: onFeedbackDismiss // ignore: cast_nullable_to_non_nullable
as VoidCallback?,
));
}
}
Expand All @@ -177,7 +203,9 @@ class _$_ViewModelImpl with DiagnosticableTreeMixin implements __ViewModel {
this.profile,
this.groups,
this.onGroupCreate,
this.onRefresh});
this.onRefresh,
this.shouldShowFeedback,
this.onFeedbackDismiss});

@override
final bool loading;
Expand All @@ -189,10 +217,14 @@ class _$_ViewModelImpl with DiagnosticableTreeMixin implements __ViewModel {
final ValueSetter<GroupCreateResult>? onGroupCreate;
@override
final AsyncCallback? onRefresh;
@override
final bool? shouldShowFeedback;
@override
final VoidCallback? onFeedbackDismiss;

@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return '_ViewModel(loading: $loading, profile: $profile, groups: $groups, onGroupCreate: $onGroupCreate, onRefresh: $onRefresh)';
return '_ViewModel(loading: $loading, profile: $profile, groups: $groups, onGroupCreate: $onGroupCreate, onRefresh: $onRefresh, shouldShowFeedback: $shouldShowFeedback, onFeedbackDismiss: $onFeedbackDismiss)';
}

@override
Expand All @@ -204,7 +236,9 @@ class _$_ViewModelImpl with DiagnosticableTreeMixin implements __ViewModel {
..add(DiagnosticsProperty('profile', profile))
..add(DiagnosticsProperty('groups', groups))
..add(DiagnosticsProperty('onGroupCreate', onGroupCreate))
..add(DiagnosticsProperty('onRefresh', onRefresh));
..add(DiagnosticsProperty('onRefresh', onRefresh))
..add(DiagnosticsProperty('shouldShowFeedback', shouldShowFeedback))
..add(DiagnosticsProperty('onFeedbackDismiss', onFeedbackDismiss));
}

@override
Expand All @@ -218,12 +252,23 @@ class _$_ViewModelImpl with DiagnosticableTreeMixin implements __ViewModel {
(identical(other.onGroupCreate, onGroupCreate) ||
other.onGroupCreate == onGroupCreate) &&
(identical(other.onRefresh, onRefresh) ||
other.onRefresh == onRefresh));
other.onRefresh == onRefresh) &&
(identical(other.shouldShowFeedback, shouldShowFeedback) ||
other.shouldShowFeedback == shouldShowFeedback) &&
(identical(other.onFeedbackDismiss, onFeedbackDismiss) ||
other.onFeedbackDismiss == onFeedbackDismiss));
}

@override
int get hashCode => Object.hash(runtimeType, loading, profile,
const DeepCollectionEquality().hash(groups), onGroupCreate, onRefresh);
int get hashCode => Object.hash(
runtimeType,
loading,
profile,
const DeepCollectionEquality().hash(groups),
onGroupCreate,
onRefresh,
shouldShowFeedback,
onFeedbackDismiss);

/// Create a copy of _ViewModel
/// with the given fields replaced by the non-null parameter values.
Expand All @@ -240,7 +285,9 @@ abstract class __ViewModel implements _ViewModel {
final Profile? profile,
final Iterable<Group>? groups,
final ValueSetter<GroupCreateResult>? onGroupCreate,
final AsyncCallback? onRefresh}) = _$_ViewModelImpl;
final AsyncCallback? onRefresh,
final bool? shouldShowFeedback,
final VoidCallback? onFeedbackDismiss}) = _$_ViewModelImpl;

@override
bool get loading;
Expand All @@ -252,6 +299,10 @@ abstract class __ViewModel implements _ViewModel {
ValueSetter<GroupCreateResult>? get onGroupCreate;
@override
AsyncCallback? get onRefresh;
@override
bool? get shouldShowFeedback;
@override
VoidCallback? get onFeedbackDismiss;

/// Create a copy of _ViewModel
/// with the given fields replaced by the non-null parameter values.
Expand Down
7 changes: 6 additions & 1 deletion lib/presentation/screens/group_details.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,12 @@ class GroupDetailsScreen extends StatelessWidget {
SliverToBoxAdapter(
child: Column(
children: [
if (groupDescription != null) Text(groupDescription),
if (groupDescription != null)
Padding(
padding:
EdgeInsets.only(top: 32, left: 16, right: 16),
child: Text(groupDescription),
),
const DateDropdownContainer(),
],
),
Expand Down
12 changes: 10 additions & 2 deletions lib/presentation/screens/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ class HomeScreen extends StatelessWidget {
final bool loading;
final ValueSetter<GroupCreateResult>? onGroupCreate;
final AsyncCallback? onRefresh;
final bool? shouldShowFeedback;
final VoidCallback? onFeedbackDismiss;

const HomeScreen({
super.key,
Expand All @@ -21,6 +23,8 @@ class HomeScreen extends StatelessWidget {
this.groups,
this.onGroupCreate,
this.onRefresh,
this.shouldShowFeedback,
this.onFeedbackDismiss,
});

ImageProvider? _profilePicture() {
Expand Down Expand Up @@ -85,12 +89,16 @@ class HomeScreen extends StatelessWidget {
),
],
),
if (true)
if (shouldShowFeedback == true)
SliverToBoxAdapter(
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: const FeedbackCard(),
child: Dismissible(
key: ValueKey('feedbackCard'),
child: const FeedbackCard(),
onDismissed: (_) => onFeedbackDismiss?.call(),
),
),
),
],
Expand Down
12 changes: 12 additions & 0 deletions lib/reducers/feedback.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'package:parousia/actions/actions.dart';
import 'package:redux/redux.dart';

final feedbackReducer = combineReducers<bool?>([
TypedReducer<bool?, InteractedWithFeedback>(_interactedWithFeedback).call,
]);

/// Whenever the user interacts with the feedback, we should hide it.
bool _interactedWithFeedback(
bool? shouldShowFeedback, InteractedWithFeedback action) {
return true;
}
2 changes: 2 additions & 0 deletions lib/reducers/root_reducer.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:parousia/actions/actions.dart';
import 'package:parousia/reducers/feedback.dart';
import 'package:parousia/state/state.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

Expand Down Expand Up @@ -34,5 +35,6 @@ AppState rootReducer(AppState state, dynamic action) {
selectedGroupId: selectedGroupIdReducer(state.selectedGroupId, action),
selectedScheduleId:
selectedScheduleIdReducer(state.selectedScheduleId, action),
hasSeenFeedbackCard: feedbackReducer(state.hasSeenFeedbackCard, action),
);
}
2 changes: 1 addition & 1 deletion lib/state/app_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import 'auth_state.dart';
import 'locale_state.dart';

part 'app_state.freezed.dart';

part 'app_state.g.dart';

@freezed
Expand All @@ -29,6 +28,7 @@ sealed class AppState with _$AppState {
String? selectedGroupId,
String? selectedScheduleId,
LocaleState? locale,
bool? hasSeenFeedbackCard,
}) = _AppState;

factory AppState.initialState() => AppState(selectedDate: DateTime.now());
Expand Down
Loading

0 comments on commit 7e64dc1

Please sign in to comment.