6177214e-ce7c-49e3-99de-ff9721b26f63 — Commit ce007177

AuthorMikkel Thygesen<mikkelet@gmail.com>
Date2026-02-19 23:30:53 +0100
3672: moved notifications screen to presentation

Changed files

.../domain/models/notification_permission.g.dart   |  42 +++
 .../models/notification_permission.g.dart          |  42 ---
 .../bloc/notifications_state.freezed.dart          | 283 +++++++++++++++++++++
 .../notifications/notifications_route.g.dart       |  35 +++
 .../lib/database/daos/notifications_dao.dart       |   2 +-
 .../lib/domain/models/notification_permission.dart | 105 ++++++++
 .../repositories/notifications_repository.dart     |  71 ++++++
 .../components/communications_list.dart            |  70 -----
 .../notifications/cubit/notifications_cubit.dart   | 137 ----------
 .../notifications/cubit/notifications_state.dart   |  35 ---
 .../models/notification_permission.dart            | 131 ----------
 .../lib/notifications/notifications_page.dart      | 149 -----------
 .../notifications/notifications_repository.dart    |  71 ------
 .../lib/notifications/notifications_route.dart     |  16 --
 .../notifications/bloc/notifications_cubit.dart    |  77 ++++++
 .../notifications/bloc/notifications_state.dart    |  18 ++
 .../components/communications_list.dart            |  70 +++++
 .../screens/notifications/notifications_route.dart |  25 ++
 .../notifications/notifications_screen.dart        | 149 +++++++++++
 .../bloc/permission_overview_cubit.dart            |   2 +-
 comwell_key_app/lib/routing/app_router.dart        |   4 +-
 comwell_key_app/lib/services/api.dart              |   2 +-
 comwell_key_app/lib/utils/locator.dart             |   2 +-
 23 files changed, 881 insertions(+), 657 deletions(-)

Diff

diff --git a/comwell_key_app/lib/.generated/domain/models/notification_permission.g.dart b/comwell_key_app/lib/.generated/domain/models/notification_permission.g.dart
new file mode 100644
index 00000000..14fd01ac
--- /dev/null
+++ b/comwell_key_app/lib/.generated/domain/models/notification_permission.g.dart
@@ -0,0 +1,42 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of '../../../domain/models/notification_permission.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+NotificationPermission _$NotificationPermissionFromJson(Map json) =>
+ NotificationPermission(
+ code: json['code'] as String,
+ displayName: json['displayName'] as String,
+ notificationPermissionDescription: $enumDecodeNullable(
+ _$NotificationPermissionTypeEnumMap,
+ json['notificationPermissionDescription'],
+ ),
+ given: json['given'] as bool,
+ );
+
+Map<String, dynamic> _$NotificationPermissionToJson(
+ NotificationPermission instance,
+) => <String, dynamic>{
+ 'code': instance.code,
+ 'displayName': instance.displayName,
+ 'notificationPermissionDescription':
+ _$NotificationPermissionTypeEnumMap[instance
+ .notificationPermissionDescription],
+ 'given': instance.given,
+};
+
+const _$NotificationPermissionTypeEnumMap = {
+ NotificationPermissionType.b2bNewsletter: 'b2bNewsletter',
+ NotificationPermissionType.ccEmail: 'ccEmail',
+ NotificationPermissionType.ccDigital: 'ccDigital',
+ NotificationPermissionType.ccSms: 'ccSms',
+ NotificationPermissionType.b2bDigital: 'b2bDigital',
+ NotificationPermissionType.companyEmail: 'companyEmail',
+ NotificationPermissionType.companyNotifications: 'companyNotifications',
+ NotificationPermissionType.stayEmails: 'stayEmails',
+ NotificationPermissionType.appNotifications: 'appNotifications',
+ NotificationPermissionType.similarProducts: 'similarProducts',
+};
diff --git a/comwell_key_app/lib/.generated/notifications/models/notification_permission.g.dart b/comwell_key_app/lib/.generated/notifications/models/notification_permission.g.dart
deleted file mode 100644
index 1f9baa7e..00000000
--- a/comwell_key_app/lib/.generated/notifications/models/notification_permission.g.dart
+++ /dev/null
@@ -1,42 +0,0 @@
-// GENERATED CODE - DO NOT MODIFY BY HAND
-
-part of '../../../notifications/models/notification_permission.dart';
-
-// **************************************************************************
-// JsonSerializableGenerator
-// **************************************************************************
-
-NotificationPermission _$NotificationPermissionFromJson(Map json) =>
- NotificationPermission(
- code: json['code'] as String,
- displayName: json['displayName'] as String,
- notificationPermissionDescription: $enumDecodeNullable(
- _$NotificationPermissionTypeEnumMap,
- json['notificationPermissionDescription'],
- ),
- given: json['given'] as bool,
- );
-
-Map<String, dynamic> _$NotificationPermissionToJson(
- NotificationPermission instance,
-) => <String, dynamic>{
- 'code': instance.code,
- 'displayName': instance.displayName,
- 'notificationPermissionDescription':
- _$NotificationPermissionTypeEnumMap[instance
- .notificationPermissionDescription],
- 'given': instance.given,
-};
-
-const _$NotificationPermissionTypeEnumMap = {
- NotificationPermissionType.b2bNewsletter: 'b2bNewsletter',
- NotificationPermissionType.ccEmail: 'ccEmail',
- NotificationPermissionType.ccDigital: 'ccDigital',
- NotificationPermissionType.ccSms: 'ccSms',
- NotificationPermissionType.b2bDigital: 'b2bDigital',
- NotificationPermissionType.companyEmail: 'companyEmail',
- NotificationPermissionType.companyNotifications: 'companyNotifications',
- NotificationPermissionType.stayEmails: 'stayEmails',
- NotificationPermissionType.appNotifications: 'appNotifications',
- NotificationPermissionType.similarProducts: 'similarProducts',
-};
diff --git a/comwell_key_app/lib/.generated/presentation/screens/notifications/bloc/notifications_state.freezed.dart b/comwell_key_app/lib/.generated/presentation/screens/notifications/bloc/notifications_state.freezed.dart
new file mode 100644
index 00000000..070954fc
--- /dev/null
+++ b/comwell_key_app/lib/.generated/presentation/screens/notifications/bloc/notifications_state.freezed.dart
@@ -0,0 +1,283 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// coverage:ignore-file
+// ignore_for_file: type=lint
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
+
+part of '../../../../../presentation/screens/notifications/bloc/notifications_state.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+// dart format off
+T _$identity<T>(T value) => value;
+/// @nodoc
+mixin _$NotificationsState {
+
+ bool get isLoading; AppError get error; List<NotificationPermission> get allNotifications;
+/// Create a copy of NotificationsState
+/// with the given fields replaced by the non-null parameter values.
+@JsonKey(includeFromJson: false, includeToJson: false)
+@pragma('vm:prefer-inline')
+$NotificationsStateCopyWith<NotificationsState> get copyWith => _$NotificationsStateCopyWithImpl<NotificationsState>(this as NotificationsState, _$identity);
+
+
+
+@override
+bool operator ==(Object other) {
+ return identical(this, other) || (other.runtimeType == runtimeType&&other is NotificationsState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.error, error) || other.error == error)&&const DeepCollectionEquality().equals(other.allNotifications, allNotifications));
+}
+
+
+@override
+int get hashCode => Object.hash(runtimeType,isLoading,error,const DeepCollectionEquality().hash(allNotifications));
+
+@override
+String toString() {
+ return 'NotificationsState(isLoading: $isLoading, error: $error, allNotifications: $allNotifications)';
+}
+
+
+}
+
+/// @nodoc
+abstract mixin class $NotificationsStateCopyWith<$Res> {
+ factory $NotificationsStateCopyWith(NotificationsState value, $Res Function(NotificationsState) _then) = _$NotificationsStateCopyWithImpl;
+@useResult
+$Res call({
+ bool isLoading, AppError error, List<NotificationPermission> allNotifications
+});
+
+
+
+
+}
+/// @nodoc
+class _$NotificationsStateCopyWithImpl<$Res>
+ implements $NotificationsStateCopyWith<$Res> {
+ _$NotificationsStateCopyWithImpl(this._self, this._then);
+
+ final NotificationsState _self;
+ final $Res Function(NotificationsState) _then;
+
+/// Create a copy of NotificationsState
+/// with the given fields replaced by the non-null parameter values.
+@pragma('vm:prefer-inline') @override $Res call({Object? isLoading = null,Object? error = null,Object? allNotifications = null,}) {
+ return _then(_self.copyWith(
+isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
+as bool,error: null == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
+as AppError,allNotifications: null == allNotifications ? _self.allNotifications : allNotifications // ignore: cast_nullable_to_non_nullable
+as List<NotificationPermission>,
+ ));
+}
+
+}
+
+
+/// Adds pattern-matching-related methods to [NotificationsState].
+extension NotificationsStatePatterns on NotificationsState {
+/// A variant of `map` that fallback to returning `orElse`.
+///
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case final Subclass value:
+/// return ...;
+/// case _:
+/// return orElse();
+/// }
+/// ```
+
+@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _NotificationsState value)? $default,{required TResult orElse(),}){
+final _that = this;
+switch (_that) {
+case _NotificationsState() when $default != null:
+return $default(_that);case _:
+ return orElse();
+
+}
+}
+/// A `switch`-like method, using callbacks.
+///
+/// Callbacks receives the raw object, upcasted.
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case final Subclass value:
+/// return ...;
+/// case final Subclass2 value:
+/// return ...;
+/// }
+/// ```
+
+@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _NotificationsState value) $default,){
+final _that = this;
+switch (_that) {
+case _NotificationsState():
+return $default(_that);case _:
+ throw StateError('Unexpected subclass');
+
+}
+}
+/// A variant of `map` that fallback to returning `null`.
+///
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case final Subclass value:
+/// return ...;
+/// case _:
+/// return null;
+/// }
+/// ```
+
+@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _NotificationsState value)? $default,){
+final _that = this;
+switch (_that) {
+case _NotificationsState() when $default != null:
+return $default(_that);case _:
+ return null;
+
+}
+}
+/// A variant of `when` that fallback to an `orElse` callback.
+///
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case Subclass(:final field):
+/// return ...;
+/// case _:
+/// return orElse();
+/// }
+/// ```
+
+@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isLoading, AppError error, List<NotificationPermission> allNotifications)? $default,{required TResult orElse(),}) {final _that = this;
+switch (_that) {
+case _NotificationsState() when $default != null:
+return $default(_that.isLoading,_that.error,_that.allNotifications);case _:
+ return orElse();
+
+}
+}
+/// A `switch`-like method, using callbacks.
+///
+/// As opposed to `map`, this offers destructuring.
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case Subclass(:final field):
+/// return ...;
+/// case Subclass2(:final field2):
+/// return ...;
+/// }
+/// ```
+
+@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isLoading, AppError error, List<NotificationPermission> allNotifications) $default,) {final _that = this;
+switch (_that) {
+case _NotificationsState():
+return $default(_that.isLoading,_that.error,_that.allNotifications);case _:
+ throw StateError('Unexpected subclass');
+
+}
+}
+/// A variant of `when` that fallback to returning `null`
+///
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case Subclass(:final field):
+/// return ...;
+/// case _:
+/// return null;
+/// }
+/// ```
+
+@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isLoading, AppError error, List<NotificationPermission> allNotifications)? $default,) {final _that = this;
+switch (_that) {
+case _NotificationsState() when $default != null:
+return $default(_that.isLoading,_that.error,_that.allNotifications);case _:
+ return null;
+
+}
+}
+
+}
+
+/// @nodoc
+
+
+class _NotificationsState extends NotificationsState {
+ const _NotificationsState({this.isLoading = false, this.error = AppError.none, final List<NotificationPermission> allNotifications = const []}): _allNotifications = allNotifications,super._();
+
+
+@override@JsonKey() final bool isLoading;
+@override@JsonKey() final AppError error;
+ final List<NotificationPermission> _allNotifications;
+@override@JsonKey() List<NotificationPermission> get allNotifications {
+ if (_allNotifications is EqualUnmodifiableListView) return _allNotifications;
+ // ignore: implicit_dynamic_type
+ return EqualUnmodifiableListView(_allNotifications);
+}
+
+
+/// Create a copy of NotificationsState
+/// with the given fields replaced by the non-null parameter values.
+@override @JsonKey(includeFromJson: false, includeToJson: false)
+@pragma('vm:prefer-inline')
+_$NotificationsStateCopyWith<_NotificationsState> get copyWith => __$NotificationsStateCopyWithImpl<_NotificationsState>(this, _$identity);
+
+
+
+@override
+bool operator ==(Object other) {
+ return identical(this, other) || (other.runtimeType == runtimeType&&other is _NotificationsState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.error, error) || other.error == error)&&const DeepCollectionEquality().equals(other._allNotifications, _allNotifications));
+}
+
+
+@override
+int get hashCode => Object.hash(runtimeType,isLoading,error,const DeepCollectionEquality().hash(_allNotifications));
+
+@override
+String toString() {
+ return 'NotificationsState(isLoading: $isLoading, error: $error, allNotifications: $allNotifications)';
+}
+
+
+}
+
+/// @nodoc
+abstract mixin class _$NotificationsStateCopyWith<$Res> implements $NotificationsStateCopyWith<$Res> {
+ factory _$NotificationsStateCopyWith(_NotificationsState value, $Res Function(_NotificationsState) _then) = __$NotificationsStateCopyWithImpl;
+@override @useResult
+$Res call({
+ bool isLoading, AppError error, List<NotificationPermission> allNotifications
+});
+
+
+
+
+}
+/// @nodoc
+class __$NotificationsStateCopyWithImpl<$Res>
+ implements _$NotificationsStateCopyWith<$Res> {
+ __$NotificationsStateCopyWithImpl(this._self, this._then);
+
+ final _NotificationsState _self;
+ final $Res Function(_NotificationsState) _then;
+
+/// Create a copy of NotificationsState
+/// with the given fields replaced by the non-null parameter values.
+@override @pragma('vm:prefer-inline') $Res call({Object? isLoading = null,Object? error = null,Object? allNotifications = null,}) {
+ return _then(_NotificationsState(
+isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
+as bool,error: null == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
+as AppError,allNotifications: null == allNotifications ? _self._allNotifications : allNotifications // ignore: cast_nullable_to_non_nullable
+as List<NotificationPermission>,
+ ));
+}
+
+
+}
+
+// dart format on
diff --git a/comwell_key_app/lib/.generated/presentation/screens/notifications/notifications_route.g.dart b/comwell_key_app/lib/.generated/presentation/screens/notifications/notifications_route.g.dart
new file mode 100644
index 00000000..8988b0a9
--- /dev/null
+++ b/comwell_key_app/lib/.generated/presentation/screens/notifications/notifications_route.g.dart
@@ -0,0 +1,35 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of '../../../../presentation/screens/notifications/notifications_route.dart';
+
+// **************************************************************************
+// GoRouterGenerator
+// **************************************************************************
+
+List<RouteBase> get $appRoutes => [$notificationsRoute];
+
+RouteBase get $notificationsRoute => GoRouteData.$route(
+ path: '/notifications',
+ factory: $NotificationsRoute._fromState,
+);
+
+mixin $NotificationsRoute on GoRouteData {
+ static NotificationsRoute _fromState(GoRouterState state) =>
+ NotificationsRoute();
+
+ @override
+ String get location => GoRouteData.$location('/notifications');
+
+ @override
+ void go(BuildContext context) => context.go(location);
+
+ @override
+ Future<T?> push<T>(BuildContext context) => context.push<T>(location);
+
+ @override
+ void pushReplacement(BuildContext context) =>
+ context.pushReplacement(location);
+
+ @override
+ void replace(BuildContext context) => context.replace(location);
+}
diff --git a/comwell_key_app/lib/database/daos/notifications_dao.dart b/comwell_key_app/lib/database/daos/notifications_dao.dart
index 481d1c7c..6c4a8727 100644
--- a/comwell_key_app/lib/database/daos/notifications_dao.dart
+++ b/comwell_key_app/lib/database/daos/notifications_dao.dart
@@ -2,7 +2,7 @@ import 'dart:convert';
import 'package:comwell_key_app/database/comwell_db.dart';
import 'package:comwell_key_app/database/tables/notification_table.dart';
-import 'package:comwell_key_app/notifications/models/notification_permission.dart';
+import 'package:comwell_key_app/domain/models/notification_permission.dart';
import 'package:comwell_key_app/utils/json.dart';
import 'package:drift/drift.dart';
diff --git a/comwell_key_app/lib/domain/models/notification_permission.dart b/comwell_key_app/lib/domain/models/notification_permission.dart
new file mode 100644
index 00000000..7acd1222
--- /dev/null
+++ b/comwell_key_app/lib/domain/models/notification_permission.dart
@@ -0,0 +1,105 @@
+import 'package:comwell_key_app/utils/json.dart';
+import 'package:comwell_key_app/utils/l10n_utils.dart';
+import 'package:flutter/material.dart';
+import 'package:json_annotation/json_annotation.dart';
+
+part '../../.generated/domain/models/notification_permission.g.dart';
+
+enum NotificationPermissionType {
+ b2bNewsletter("b2bmarketingnewsletter"),
+ ccEmail("ccmarketingemail"),
+ ccDigital("ccmarketingdigi"),
+ ccSms("ccmarketingsms"),
+ b2bDigital("b2bmarketingdigi"),
+ companyEmail("companymarketingemail"),
+ companyNotifications("companymarketingnewsletter"),
+ stayEmails("staymarketingemail"),
+ appNotifications("ccmarketingapp"),
+ similarProducts("similarproducts");
+
+ final String code;
+
+ const NotificationPermissionType(this.code);
+
+ String localizedDisplayName(BuildContext context) {
+ switch (this) {
+ case NotificationPermissionType.b2bNewsletter:
+ return 'B2B Newsletter';
+ case NotificationPermissionType.ccEmail:
+ return 'CC Email Permission';
+ case NotificationPermissionType.ccDigital:
+ return 'CC Digital Permission';
+ case NotificationPermissionType.ccSms:
+ return 'CC SMS Permission';
+ case NotificationPermissionType.b2bDigital:
+ return 'B2B Digital permission';
+ case NotificationPermissionType.companyEmail:
+ return 'Company Email permission';
+ case NotificationPermissionType.companyNotifications:
+ return 'Comm. Pref: Company Notifications';
+ case NotificationPermissionType.stayEmails:
+ return 'Comm. Pref: Stay Emails';
+ case NotificationPermissionType.appNotifications:
+ return 'Comm. Pref: App notifications';
+ case NotificationPermissionType.similarProducts:
+ return 'Similar products permissions';
+ }
+ }
+
+ // Not all permissions are used so some of the descriptions are not implemented
+ String description(BuildContext context) {
+ switch (this) {
+ case NotificationPermissionType.b2bNewsletter:
+ return context.strings.company_deal_subtitle;
+ case NotificationPermissionType.ccEmail:
+ return context.strings.email_subtitle;
+ case NotificationPermissionType.ccDigital:
+ return context.strings.digital_media_subtitle;
+ case NotificationPermissionType.ccSms:
+ return context.strings.sms_subtitle;
+ case NotificationPermissionType.b2bDigital:
+ case NotificationPermissionType.companyNotifications:
+ return context.strings.club_newsletter_subtitle;
+ case NotificationPermissionType.appNotifications:
+ return context.strings.push_subtitle;
+ case NotificationPermissionType.companyEmail:
+ case NotificationPermissionType.stayEmails:
+ case NotificationPermissionType.similarProducts:
+ return '';
+ }
+ }
+}
+
+@JsonSerializable()
+class NotificationPermission {
+ final String code;
+ final String displayName;
+ final NotificationPermissionType? notificationPermissionDescription;
+ final bool given;
+
+ const NotificationPermission({
+ required this.code,
+ required this.displayName,
+ this.notificationPermissionDescription,
+ required this.given,
+ });
+
+ factory NotificationPermission.fromJson(Json json) => _$NotificationPermissionFromJson(json);
+
+ Json toJson() => _$NotificationPermissionToJson(this);
+
+ NotificationPermission copyWith({
+ bool? given,
+ NotificationPermissionType? notificationPermissionDescription,
+ }) => NotificationPermission(
+ code: code,
+ displayName: displayName,
+ notificationPermissionDescription: notificationPermissionDescription,
+ given: given ?? this.given,
+ );
+
+ @override
+ String toString() {
+ return 'NotificationPermission{code: $code, displayName: $displayName, given: $given}';
+ }
+}
diff --git a/comwell_key_app/lib/domain/repositories/notifications_repository.dart b/comwell_key_app/lib/domain/repositories/notifications_repository.dart
new file mode 100644
index 00000000..b8fc228b
--- /dev/null
+++ b/comwell_key_app/lib/domain/repositories/notifications_repository.dart
@@ -0,0 +1,71 @@
+import 'package:comwell_key_app/database/comwell_db.dart';
+import 'package:comwell_key_app/domain/models/notification_permission.dart';
+import 'package:comwell_key_app/services/api.dart';
+import 'package:comwell_key_app/utils/json.dart';
+import 'package:comwell_key_app/utils/locator.dart';
+import 'package:permission_handler/permission_handler.dart';
+
+class NotificationsRepository {
+ final Api _api;
+ final ComwellDatabase _comwellDatabase;
+
+ const NotificationsRepository(this._api, this._comwellDatabase);
+
+ Future<void> updatePreferences(List<NotificationPermission> notificationPermissions) async {
+ return _api.updateNotificationPreferences(notificationPermissions);
+ }
+
+ Future<List<NotificationPermission>> fetchNotificationPermissions() async {
+ final notificationPermissions = await _comwellDatabase.notificationPermissionDAO
+ .getNotificationPermissions();
+ if (notificationPermissions.isEmpty) {
+ final response = await _api.getNotificationPermissions();
+ final data = response.data as Json;
+ final notificationPermissions = (data['codes'] as List<dynamic>)
+ .map((json) => NotificationPermission.fromJson(json as Json))
+ .toList();
+
+ final notificationPermissionsWithDescription = _addDescriptionsToPermissions(
+ notificationPermissions,
+ );
+
+ await _comwellDatabase.notificationPermissionDAO.saveNotificationPermission(
+ notificationPermissionsWithDescription,
+ );
+ return notificationPermissionsWithDescription;
+ }
+ return _addDescriptionsToPermissions(notificationPermissions);
+ }
+
+ Future<bool> isPushNotificationPermissionGranted() async {
+ return Permission.notification.isGranted;
+ }
+
+ Future<bool> requestPushNotificationPermission() async {
+ await Permission.notification.request();
+ return isPushNotificationPermissionGranted();
+ }
+
+ Future<dynamic> updateNotificationPreferences(
+ List<NotificationPermission> notificationPermissions,
+ ) async {
+ await locator<ComwellDatabase>().notificationPermissionDAO.saveNotificationPermission(
+ notificationPermissions,
+ );
+
+ return _api.updateNotificationPreferences(notificationPermissions);
+ }
+
+ List<NotificationPermission> _addDescriptionsToPermissions(
+ List<NotificationPermission> permissions,
+ ) {
+ return permissions.map((permission) {
+ final enumValue = NotificationPermissionType.values.firstWhere(
+ (type) => type.code == permission.code,
+ );
+ return permission.copyWith(
+ notificationPermissionDescription: enumValue,
+ );
+ }).toList();
+ }
+}
diff --git a/comwell_key_app/lib/notifications/components/communications_list.dart b/comwell_key_app/lib/notifications/components/communications_list.dart
deleted file mode 100644
index 7b573f69..00000000
--- a/comwell_key_app/lib/notifications/components/communications_list.dart
+++ /dev/null
@@ -1,70 +0,0 @@
-import 'package:comwell_key_app/notifications/models/notification_permission.dart';
-import 'package:comwell_key_app/themes/light_theme.dart';
-import 'package:flutter/material.dart';
-
-class CommunicationsList extends StatelessWidget {
- final List<NotificationPermission> notificationPermissions;
- final void Function(String) valueChanged;
-
- const CommunicationsList({
- super.key,
- required this.notificationPermissions,
- required this.valueChanged,
- });
-
- @override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
- return Column(
- children: [
- Expanded(
- child: ListView.builder(
- shrinkWrap: true,
- itemCount: notificationPermissions.length,
- itemBuilder: (context, index) {
- return SwitchListTile(
- thumbColor: WidgetStateProperty.resolveWith((states) {
- if (states.contains(WidgetState.selected)) {
- return sandColor;
- }
- return Colors.white;
- }),
- trackColor: WidgetStateProperty.resolveWith((states) {
- if (!states.contains(WidgetState.selected)) {
- return Colors.grey[200];
- }
- return null;
- }),
- trackOutlineColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.surface),
- title: Text(
- notificationPermissions.elementAt(index).displayName,
- style: theme.textTheme.titleMedium?.copyWith(
- color: Theme.of(context).colorScheme.onSurface,
- fontWeight: FontWeight.w600,
- ),
- ),
- subtitle: Text(
- notificationPermissions
- .elementAt(index)
- .notificationPermissionDescription
- ?.description(context) ??
- '',
- style: theme.textTheme.bodySmall?.copyWith(
- color: Theme.of(context).colorScheme.surfaceTint,
- fontSize: 12,
- fontWeight: FontWeight.w400,
- ),
- ),
- value: notificationPermissions.elementAt(index).given,
- onChanged: (bool value) {
- valueChanged(notificationPermissions.elementAt(index).displayName);
- },
- contentPadding: EdgeInsets.zero,
- );
- },
- ),
- ),
- ],
- );
- }
-}
diff --git a/comwell_key_app/lib/notifications/cubit/notifications_cubit.dart b/comwell_key_app/lib/notifications/cubit/notifications_cubit.dart
deleted file mode 100644
index fdc1f2f7..00000000
--- a/comwell_key_app/lib/notifications/cubit/notifications_cubit.dart
+++ /dev/null
@@ -1,137 +0,0 @@
-import 'package:comwell_key_app/notifications/cubit/notifications_state.dart';
-import 'package:comwell_key_app/notifications/models/notification_permission.dart';
-import 'package:comwell_key_app/notifications/notifications_repository.dart';
-import 'package:comwell_key_app/profile_settings/model/user.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-
-class NotificationsCubit extends Cubit<NotificationsState> {
- final NotificationsRepository _notificationsRepository;
-
- NotificationsCubit(this._notificationsRepository)
- : super(
- const NotificationsState(
- allNotifications: [],
- isLoading: false,
- error: null,
- ),
- ) {
- init();
- }
-
- List<NotificationPermission> notificationPermissions = [];
- late User user;
-
- // List of permission types that should be shown in the UI
- final List<NotificationPermissionType> _visiblePermissionTypes = [
- NotificationPermissionType.appNotifications,
- NotificationPermissionType.ccSms,
- NotificationPermissionType.ccDigital,
- NotificationPermissionType.ccEmail,
- ];
-
- List<NotificationPermission> _filterVisiblePermissions(
- List<NotificationPermission> permissions,
- ) {
- return permissions
- .where(
- (permission) => _visiblePermissionTypes.any(
- (type) => type.notificationPermissionId == permission.code,
- ),
- )
- .toList();
- }
-
- void init() async {
- emit(
- NotificationsState(
- allNotifications: notificationPermissions,
- isLoading: true,
- error: null,
- ),
- );
- try {
- notificationPermissions = await _notificationsRepository.fetchNotificationPermissions();
-
- notificationPermissions = _filterVisiblePermissions(notificationPermissions);
- emit(
- NotificationsState(
- allNotifications: notificationPermissions,
- isLoading: false,
- error: null,
- ),
- );
- } catch (e) {
- emit(
- NotificationsState(
- allNotifications: notificationPermissions,
- isLoading: false,
- error: Error(),
- ),
- );
- }
- }
-
- void onNotificationPermissionClicked(String name) {
- notificationPermissions = notificationPermissions
- .map(
- (permission) => permission.displayName == name
- ? permission.copyWith(
- given: !permission.given,
- notificationPermissionDescription: permission.notificationPermissionDescription,
- )
- : permission,
- )
- .toList();
-
- emit(
- NotificationsState(
- allNotifications: notificationPermissions,
- isLoading: false,
- error: null,
- ),
- );
- }
-
- void updateAllPermissionsUI(bool value) {
- notificationPermissions = notificationPermissions
- .map(
- (permission) => permission.copyWith(
- given: value,
- notificationPermissionDescription: permission.notificationPermissionDescription,
- ),
- )
- .toList();
-
- emit(
- NotificationsState(
- allNotifications: notificationPermissions,
- isLoading: false,
- error: null,
- ),
- );
- }
-
- void updatePreferences(List<NotificationPermission> notificationPermissions) async {
- await _notificationsRepository.updateNotificationPreferences(notificationPermissions);
-
- emit(
- NotificationsState(
- allNotifications: notificationPermissions,
- isLoading: false,
- error: null,
- ),
- );
- }
-
- void fetchNotificationPermissions() async {
- notificationPermissions = await _notificationsRepository.fetchNotificationPermissions();
-
- emit(
- NotificationsState(
- allNotifications: notificationPermissions,
- isLoading: false,
- error: null,
- ),
- );
- }
-}
diff --git a/comwell_key_app/lib/notifications/cubit/notifications_state.dart b/comwell_key_app/lib/notifications/cubit/notifications_state.dart
deleted file mode 100644
index 24f7b080..00000000
--- a/comwell_key_app/lib/notifications/cubit/notifications_state.dart
+++ /dev/null
@@ -1,35 +0,0 @@
-import 'package:comwell_key_app/notifications/models/notification_permission.dart';
-import 'package:equatable/equatable.dart';
-
-class NotificationsState extends Equatable {
- final Error? error;
- final bool isLoading;
- final List<NotificationPermission> allNotifications;
-
- const NotificationsState({
- required this.allNotifications,
- this.error,
- required this.isLoading,
- });
-
- NotificationsState.initial() : allNotifications = [], error = null, isLoading = false;
-
- NotificationsState notificationSelected({
- required NotificationPermission notificationPermission,
- }) => _copyWith(allNotifications: allNotifications);
-
- NotificationsState _copyWith({
- List<NotificationPermission>? allNotifications,
- bool? isLoading,
- Error? error,
- }) {
- return NotificationsState(
- allNotifications: allNotifications ?? this.allNotifications,
- isLoading: isLoading ?? this.isLoading,
- error: error ?? this.error,
- );
- }
-
- @override
- List<Object> get props => [allNotifications];
-}
diff --git a/comwell_key_app/lib/notifications/models/notification_permission.dart b/comwell_key_app/lib/notifications/models/notification_permission.dart
deleted file mode 100644
index 08532ab8..00000000
--- a/comwell_key_app/lib/notifications/models/notification_permission.dart
+++ /dev/null
@@ -1,131 +0,0 @@
-import 'package:comwell_key_app/utils/json.dart';
-import 'package:comwell_key_app/utils/l10n_utils.dart';
-import 'package:flutter/material.dart';
-import 'package:json_annotation/json_annotation.dart';
-
-part '../../.generated/notifications/models/notification_permission.g.dart';
-
-enum NotificationPermissionType {
- b2bNewsletter,
- ccEmail,
- ccDigital,
- ccSms,
- b2bDigital,
- companyEmail,
- companyNotifications,
- stayEmails,
- appNotifications,
- similarProducts;
-
- String get notificationPermissionId {
- switch (this) {
- case NotificationPermissionType.appNotifications:
- return "ccmarketingapp";
- case NotificationPermissionType.ccEmail:
- return "ccmarketingemail"; //1405
- case NotificationPermissionType.ccDigital:
- return "ccmarketingdigi"; //1406
- case NotificationPermissionType.ccSms:
- return "ccmarketingsms";
-
- case NotificationPermissionType.b2bNewsletter:
- return "b2bmarketingnewsletter"; //1400
- case NotificationPermissionType.b2bDigital:
- return "b2bmarketingdigi";
- case NotificationPermissionType.companyEmail:
- return "companymarketingemail";
- case NotificationPermissionType.companyNotifications:
- return "companymarketingnewsletter";
- case NotificationPermissionType.stayEmails:
- return "staymarketingemail";
- case NotificationPermissionType.similarProducts:
- return "similarproducts";
- }
- }
-
- //TODO: Change displayname to correct name
- String get displayName {
- switch (this) {
- case NotificationPermissionType.b2bNewsletter:
- return 'B2B Newsletter';
- case NotificationPermissionType.ccEmail:
- return 'CC Email Permission';
- case NotificationPermissionType.ccDigital:
- return 'CC Digital Permission';
- case NotificationPermissionType.ccSms:
- return 'CC SMS Permission';
- case NotificationPermissionType.b2bDigital:
- return 'B2B Digital permission';
- case NotificationPermissionType.companyEmail:
- return 'Company Email permission';
- case NotificationPermissionType.companyNotifications:
- return 'Comm. Pref: Company Notifications';
- case NotificationPermissionType.stayEmails:
- return 'Comm. Pref: Stay Emails';
- case NotificationPermissionType.appNotifications:
- return 'Comm. Pref: App notifications';
- case NotificationPermissionType.similarProducts:
- return 'Similar products permissions';
- }
- }
-
- // Not all permissions are used so some of the descriptions are not implemented
- String description(BuildContext context) {
- switch (this) {
- case NotificationPermissionType.b2bNewsletter:
- return context.strings.company_deal_subtitle;
- case NotificationPermissionType.ccEmail:
- return context.strings.email_subtitle;
- case NotificationPermissionType.ccDigital:
- return context.strings.digital_media_subtitle;
- case NotificationPermissionType.ccSms:
- return context.strings.sms_subtitle;
- case NotificationPermissionType.b2bDigital:
- return '';
- case NotificationPermissionType.companyEmail:
- return '';
- case NotificationPermissionType.companyNotifications:
- return context.strings.club_newsletter_subtitle;
- case NotificationPermissionType.stayEmails:
- return '';
- case NotificationPermissionType.appNotifications:
- return context.strings.push_subtitle;
- case NotificationPermissionType.similarProducts:
- return '';
- }
- }
-}
-
-@JsonSerializable()
-class NotificationPermission {
- final String code;
- final String displayName;
- final NotificationPermissionType? notificationPermissionDescription;
- final bool given;
-
- const NotificationPermission({
- required this.code,
- required this.displayName,
- this.notificationPermissionDescription,
- required this.given,
- });
-
- factory NotificationPermission.fromJson(Json json) => _$NotificationPermissionFromJson(json);
-
- Json toJson() => _$NotificationPermissionToJson(this);
-
- NotificationPermission copyWith({
- bool? given,
- NotificationPermissionType? notificationPermissionDescription,
- }) => NotificationPermission(
- code: code,
- displayName: displayName,
- notificationPermissionDescription: notificationPermissionDescription,
- given: given ?? this.given,
- );
-
- @override
- String toString() {
- return 'NotificationPermission{code: $code, displayName: $displayName, given: $given}';
- }
-}
diff --git a/comwell_key_app/lib/notifications/notifications_page.dart b/comwell_key_app/lib/notifications/notifications_page.dart
deleted file mode 100644
index ee3a89cd..00000000
--- a/comwell_key_app/lib/notifications/notifications_page.dart
+++ /dev/null
@@ -1,149 +0,0 @@
-import 'package:comwell_key_app/common/components/comwell_app_bar.dart';
-import 'package:comwell_key_app/common/components/comwell_error_widget.dart';
-import 'package:comwell_key_app/notifications/cubit/notifications_state.dart';
-import 'package:comwell_key_app/themes/light_theme.dart';
-import 'package:comwell_key_app/utils/l10n_utils.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:comwell_key_app/notifications/cubit/notifications_cubit.dart';
-import 'components/communications_list.dart';
-import '../common/components/shimmer_loader/notifications_shimmer_loader.dart';
-
-class NotificationsPage extends StatelessWidget {
- const NotificationsPage({super.key});
-
- @override
- Widget build(BuildContext context) {
- final cubit = context.read<NotificationsCubit>();
- return Scaffold(
- backgroundColor: Theme.of(context).colorScheme.surface,
- appBar: const ComwellAppBar(shouldShowProfileButton: false),
- body: BlocBuilder<NotificationsCubit, NotificationsState>(
- builder: (context, state) {
- if (state.isLoading) {
- return const Center(child: NotificationsShimmerLoader());
- } else if (state.error != null) {
- return Column(
- crossAxisAlignment: CrossAxisAlignment.center,
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- ComwellErrorWidget(
- title: context.strings.generic_error_title,
- subtitle: context.strings.notifications_error_subtitle,
- border: false,
- ),
- ],
- );
- } else {
- return _buildNotificationsPage(context, cubit);
- }
- },
- ),
- );
- }
-
- Widget _buildNotificationsPage(BuildContext context, NotificationsCubit cubit) {
- final theme = Theme.of(context);
- return SafeArea(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Expanded(
- child: Padding(
- padding: const EdgeInsets.all(16.0),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- context.strings.notifications_page_title,
- style: const TextStyle(
- fontSize: 28,
- fontWeight: FontWeight.w600,
- ),
- ),
- const SizedBox(height: 20),
- Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- Switch(
- thumbColor: WidgetStateProperty.resolveWith((states) {
- if (states.contains(WidgetState.selected)) {
- return sandColor;
- }
- return Colors.white;
- }),
- trackColor: WidgetStateProperty.resolveWith((states) {
- if (!states.contains(WidgetState.selected)) {
- return Colors.grey[200];
- }
- return null;
- }),
- trackOutlineColor: WidgetStatePropertyAll(
- Theme.of(context).colorScheme.surface,
- ),
- value: cubit.state.allNotifications.every((e) => e.given),
- onChanged: (bool value) {
- cubit.updateAllPermissionsUI(value);
- },
- ),
- const SizedBox(width: 8),
- Text(
- context.strings.subscribe_all,
- style: const TextStyle(fontSize: 16),
- ),
- ],
- ),
- Expanded(
- child: CommunicationsList(
- notificationPermissions: cubit.state.allNotifications,
- valueChanged: (String name) {
- cubit.onNotificationPermissionClicked(name);
- },
- ),
- ),
- ],
- ),
- ),
- ),
- Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- Divider(
- color: Theme.of(context).colorScheme.outline,
- thickness: 1,
- height: 0,
- ),
- const SizedBox(height: 16),
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16.0),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- SizedBox(
- width: double.infinity,
- height: 50,
- child: ElevatedButton(
- onPressed: () {
- cubit.updatePreferences(cubit.state.allNotifications);
- },
- style: theme.elevatedButtonTheme.style,
- child: Text(
- context.strings.save,
- style: const TextStyle(
- color: Colors.white,
- fontSize: 16,
- ),
- ),
- ),
- ),
- const SizedBox(height: 16),
- ],
- ),
- ),
- ],
- ),
- ],
- ),
- );
- }
-}
diff --git a/comwell_key_app/lib/notifications/notifications_repository.dart b/comwell_key_app/lib/notifications/notifications_repository.dart
deleted file mode 100644
index bcb2817a..00000000
--- a/comwell_key_app/lib/notifications/notifications_repository.dart
+++ /dev/null
@@ -1,71 +0,0 @@
-import 'package:comwell_key_app/database/comwell_db.dart';
-import 'package:comwell_key_app/notifications/models/notification_permission.dart';
-import 'package:comwell_key_app/services/api.dart';
-import 'package:comwell_key_app/utils/json.dart';
-import 'package:comwell_key_app/utils/locator.dart';
-import 'package:permission_handler/permission_handler.dart';
-
-class NotificationsRepository {
- final Api _api;
- final ComwellDatabase _comwellDatabase;
-
- const NotificationsRepository(this._api, this._comwellDatabase);
-
- Future<void> updatePreferences(List<NotificationPermission> notificationPermissions) async {
- return _api.updateNotificationPreferences(notificationPermissions);
- }
-
- Future<List<NotificationPermission>> fetchNotificationPermissions() async {
- final notificationPermissions = await _comwellDatabase.notificationPermissionDAO
- .getNotificationPermissions();
- if (notificationPermissions.isEmpty) {
- final response = await _api.getNotificationPermissions();
- final data = response.data as Json;
- final notificationPermissions = (data['codes'] as List<dynamic>)
- .map((json) => NotificationPermission.fromJson(json as Json))
- .toList();
-
- final notificationPermissionsWithDescription = addDescriptionsToPermissions(
- notificationPermissions,
- );
-
- await _comwellDatabase.notificationPermissionDAO.saveNotificationPermission(
- notificationPermissionsWithDescription,
- );
- return notificationPermissionsWithDescription;
- }
- return addDescriptionsToPermissions(notificationPermissions);
- }
-
- List<NotificationPermission> addDescriptionsToPermissions(
- List<NotificationPermission> permissions,
- ) {
- return permissions.map((permission) {
- final enumValue = NotificationPermissionType.values.firstWhere(
- (type) => type.notificationPermissionId == permission.code,
- );
- return permission.copyWith(
- notificationPermissionDescription: enumValue,
- );
- }).toList();
- }
-
- Future<bool> isPushNotificationPermissionGranted() async {
- return Permission.notification.isGranted;
- }
-
- Future<bool> requestPushNotificationPermission() async {
- await Permission.notification.request();
- return isPushNotificationPermissionGranted();
- }
-
- Future<dynamic> updateNotificationPreferences(
- List<NotificationPermission> notificationPermissions,
- ) async {
- await locator<ComwellDatabase>().notificationPermissionDAO.saveNotificationPermission(
- notificationPermissions,
- );
-
- return _api.updateNotificationPreferences(notificationPermissions);
- }
-}
diff --git a/comwell_key_app/lib/notifications/notifications_route.dart b/comwell_key_app/lib/notifications/notifications_route.dart
deleted file mode 100644
index 52ee4795..00000000
--- a/comwell_key_app/lib/notifications/notifications_route.dart
+++ /dev/null
@@ -1,16 +0,0 @@
-import 'package:comwell_key_app/notifications/cubit/notifications_cubit.dart';
-import 'package:comwell_key_app/notifications/notifications_page.dart';
-import 'package:comwell_key_app/routing/app_routes.dart';
-import 'package:comwell_key_app/utils/locator.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:go_router/go_router.dart';
-
-final notificationsRoute = GoRoute(
- path: AppRoutes.notifications,
- builder: (context, state) {
- return BlocProvider(
- create: (context) => NotificationsCubit(locator()),
- child: const NotificationsPage(),
- );
- },
-);
diff --git a/comwell_key_app/lib/presentation/screens/notifications/bloc/notifications_cubit.dart b/comwell_key_app/lib/presentation/screens/notifications/bloc/notifications_cubit.dart
new file mode 100644
index 00000000..3995ff67
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/notifications/bloc/notifications_cubit.dart
@@ -0,0 +1,77 @@
+import 'package:comwell_key_app/base/base_cubit.dart';
+import 'package:comwell_key_app/domain/models/app_error.dart';
+import 'package:comwell_key_app/domain/models/notification_permission.dart';
+import 'package:comwell_key_app/domain/repositories/notifications_repository.dart';
+import 'package:comwell_key_app/presentation/screens/notifications/bloc/notifications_state.dart';
+
+class NotificationsCubit extends BaseCubit<NotificationsState> {
+ final NotificationsRepository _notificationsRepository;
+
+ NotificationsCubit(this._notificationsRepository) : super(const NotificationsState()) {
+ init();
+ }
+
+ // List of permission types that should be shown in the UI
+ final List<NotificationPermissionType> _visiblePermissionTypes = [
+ NotificationPermissionType.appNotifications,
+ NotificationPermissionType.ccSms,
+ NotificationPermissionType.ccDigital,
+ NotificationPermissionType.ccEmail,
+ ];
+
+ void init() async {
+ try {
+ safeEmit(state.loading());
+ final notifications = await _notificationsRepository.fetchNotificationPermissions();
+ final visible = notifications
+ .where(
+ (permission) => _visiblePermissionTypes.any(
+ (type) => type.code == permission.code,
+ ),
+ )
+ .toList();
+ safeEmit(state.copyWith(allNotifications: visible));
+ } catch (e, st) {
+ logError(e, st);
+ safeEmit(state.copyWith(error: AppError.unknown(e.toString())));
+ } finally {
+ safeEmit(state.copyWith(isLoading: false));
+ }
+ }
+
+ void onNotificationPermissionClicked(String name) {
+ final notificationPermissions = state.allNotifications
+ .map(
+ (permission) => permission.displayName == name
+ ? permission.copyWith(
+ given: !permission.given,
+ notificationPermissionDescription: permission.notificationPermissionDescription,
+ )
+ : permission,
+ )
+ .toList();
+ safeEmit(state.copyWith(allNotifications: notificationPermissions));
+ }
+
+ void updateAllPermissionsUI(bool value) {
+ final notificationPermissions = state.allNotifications
+ .map(
+ (permission) => permission.copyWith(
+ given: value,
+ notificationPermissionDescription: permission.notificationPermissionDescription,
+ ),
+ )
+ .toList();
+ safeEmit(state.copyWith(allNotifications: notificationPermissions));
+ }
+
+ void updatePreferences(List<NotificationPermission> notificationPermissions) async {
+ await _notificationsRepository.updateNotificationPreferences(notificationPermissions);
+ safeEmit(state.copyWith(allNotifications: notificationPermissions));
+ }
+
+ void fetchNotificationPermissions() async {
+ final notificationPermissions = await _notificationsRepository.fetchNotificationPermissions();
+ safeEmit(state.copyWith(allNotifications: notificationPermissions));
+ }
+}
diff --git a/comwell_key_app/lib/presentation/screens/notifications/bloc/notifications_state.dart b/comwell_key_app/lib/presentation/screens/notifications/bloc/notifications_state.dart
new file mode 100644
index 00000000..a6612e8d
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/notifications/bloc/notifications_state.dart
@@ -0,0 +1,18 @@
+import 'package:comwell_key_app/domain/models/app_error.dart';
+import 'package:comwell_key_app/domain/models/notification_permission.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part '../../../../.generated/presentation/screens/notifications/bloc/notifications_state.freezed.dart';
+
+@freezed
+abstract class NotificationsState with _$NotificationsState {
+ const factory NotificationsState({
+ @Default(false) bool isLoading,
+ @Default(AppError.none) AppError error,
+ @Default([]) List<NotificationPermission> allNotifications
+ }) = _NotificationsState;
+
+ const NotificationsState._();
+
+ NotificationsState loading() => copyWith(isLoading: true, error: AppError.none);
+}
\ No newline at end of file
diff --git a/comwell_key_app/lib/presentation/screens/notifications/components/communications_list.dart b/comwell_key_app/lib/presentation/screens/notifications/components/communications_list.dart
new file mode 100644
index 00000000..c4d348d2
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/notifications/components/communications_list.dart
@@ -0,0 +1,70 @@
+import 'package:comwell_key_app/domain/models/notification_permission.dart';
+import 'package:comwell_key_app/themes/light_theme.dart';
+import 'package:flutter/material.dart';
+
+class CommunicationsList extends StatelessWidget {
+ final List<NotificationPermission> notificationPermissions;
+ final void Function(String) valueChanged;
+
+ const CommunicationsList({
+ super.key,
+ required this.notificationPermissions,
+ required this.valueChanged,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = Theme.of(context);
+ return Column(
+ children: [
+ Expanded(
+ child: ListView.builder(
+ shrinkWrap: true,
+ itemCount: notificationPermissions.length,
+ itemBuilder: (context, index) {
+ return SwitchListTile(
+ thumbColor: WidgetStateProperty.resolveWith((states) {
+ if (states.contains(WidgetState.selected)) {
+ return sandColor;
+ }
+ return Colors.white;
+ }),
+ trackColor: WidgetStateProperty.resolveWith((states) {
+ if (!states.contains(WidgetState.selected)) {
+ return Colors.grey[200];
+ }
+ return null;
+ }),
+ trackOutlineColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.surface),
+ title: Text(
+ notificationPermissions.elementAt(index).displayName,
+ style: theme.textTheme.titleMedium?.copyWith(
+ color: Theme.of(context).colorScheme.onSurface,
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ subtitle: Text(
+ notificationPermissions
+ .elementAt(index)
+ .notificationPermissionDescription
+ ?.description(context) ??
+ '',
+ style: theme.textTheme.bodySmall?.copyWith(
+ color: Theme.of(context).colorScheme.surfaceTint,
+ fontSize: 12,
+ fontWeight: FontWeight.w400,
+ ),
+ ),
+ value: notificationPermissions.elementAt(index).given,
+ onChanged: (bool value) {
+ valueChanged(notificationPermissions.elementAt(index).displayName);
+ },
+ contentPadding: EdgeInsets.zero,
+ );
+ },
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/comwell_key_app/lib/presentation/screens/notifications/notifications_route.dart b/comwell_key_app/lib/presentation/screens/notifications/notifications_route.dart
new file mode 100644
index 00000000..30b34356
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/notifications/notifications_route.dart
@@ -0,0 +1,25 @@
+import 'package:comwell_key_app/utils/locator.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:go_router/go_router.dart';
+import 'package:comwell_key_app/presentation/navigation/transitions/slide_in_transition.dart';
+import 'package:comwell_key_app/presentation/screens/notifications/bloc/notifications_cubit.dart';
+import 'package:comwell_key_app/presentation/screens/notifications/notifications_screen.dart';
+
+import '../../../routing/app_routes.dart';
+
+part '../../../.generated/presentation/screens/notifications/notifications_route.g.dart';
+
+@TypedGoRoute<NotificationsRoute>(path: AppRoutes.notifications)
+class NotificationsRoute extends GoRouteData with $NotificationsRoute {
+ @override
+ Page<void> buildPage(BuildContext context, GoRouterState state) {
+ return SlideInTransition(
+ state: state,
+ child: BlocProvider(
+ create: (context) => NotificationsCubit(locator()),
+ child: const NotificationsScreen(),
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/presentation/screens/notifications/notifications_screen.dart b/comwell_key_app/lib/presentation/screens/notifications/notifications_screen.dart
new file mode 100644
index 00000000..b58d6ae6
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/notifications/notifications_screen.dart
@@ -0,0 +1,149 @@
+import 'package:comwell_key_app/common/components/comwell_app_bar.dart';
+import 'package:comwell_key_app/common/components/comwell_error_widget.dart';
+import 'package:comwell_key_app/common/components/shimmer_loader/notifications_shimmer_loader.dart';
+import 'package:comwell_key_app/presentation/screens/notifications/components/communications_list.dart';
+import 'package:comwell_key_app/utils/l10n_utils.dart';
+import 'package:concierge/presentation/theme/app_colors.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:comwell_key_app/presentation/screens/notifications/bloc/notifications_cubit.dart';
+import 'package:comwell_key_app/presentation/screens/notifications/bloc/notifications_state.dart';
+
+class NotificationsScreen extends StatelessWidget {
+ const NotificationsScreen({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final cubit = context.read<NotificationsCubit>();
+ return Scaffold(
+ backgroundColor: Theme.of(context).colorScheme.surface,
+ appBar: const ComwellAppBar(shouldShowProfileButton: false),
+ body: BlocBuilder<NotificationsCubit, NotificationsState>(
+ builder: (context, state) {
+ if (state.isLoading) {
+ return const Center(child: NotificationsShimmerLoader());
+ } else if (state.error.isError) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ ComwellErrorWidget(
+ title: context.strings.generic_error_title,
+ subtitle: context.strings.notifications_error_subtitle,
+ border: false,
+ ),
+ ],
+ );
+ } else {
+ return _buildNotificationsPage(context, cubit);
+ }
+ },
+ ),
+ );
+ }
+
+ Widget _buildNotificationsPage(BuildContext context, NotificationsCubit cubit) {
+ final theme = Theme.of(context);
+ return SafeArea(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Expanded(
+ child: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ context.strings.notifications_page_title,
+ style: const TextStyle(
+ fontSize: 28,
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ const SizedBox(height: 20),
+ Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Switch(
+ thumbColor: WidgetStateProperty.resolveWith((states) {
+ if (states.contains(WidgetState.selected)) {
+ return AppColors.sandColor;
+ }
+ return Colors.white;
+ }),
+ trackColor: WidgetStateProperty.resolveWith((states) {
+ if (!states.contains(WidgetState.selected)) {
+ return Colors.grey[200];
+ }
+ return null;
+ }),
+ trackOutlineColor: WidgetStatePropertyAll(
+ Theme.of(context).colorScheme.surface,
+ ),
+ value: cubit.state.allNotifications.every((e) => e.given),
+ onChanged: (bool value) {
+ cubit.updateAllPermissionsUI(value);
+ },
+ ),
+ const SizedBox(width: 8),
+ Text(
+ context.strings.subscribe_all,
+ style: const TextStyle(fontSize: 16),
+ ),
+ ],
+ ),
+ Expanded(
+ child: CommunicationsList(
+ notificationPermissions: cubit.state.allNotifications,
+ valueChanged: (String name) {
+ cubit.onNotificationPermissionClicked(name);
+ },
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Divider(
+ color: Theme.of(context).colorScheme.outline,
+ thickness: 1,
+ height: 0,
+ ),
+ const SizedBox(height: 16),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16.0),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ SizedBox(
+ width: double.infinity,
+ height: 50,
+ child: ElevatedButton(
+ onPressed: () {
+ cubit.updatePreferences(cubit.state.allNotifications);
+ },
+ style: theme.elevatedButtonTheme.style,
+ child: Text(
+ context.strings.save,
+ style: const TextStyle(
+ color: Colors.white,
+ fontSize: 16,
+ ),
+ ),
+ ),
+ ),
+ const SizedBox(height: 16),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/presentation/screens/permission_overview/bloc/permission_overview_cubit.dart b/comwell_key_app/lib/presentation/screens/permission_overview/bloc/permission_overview_cubit.dart
index 314dd960..9fdeee90 100644
--- a/comwell_key_app/lib/presentation/screens/permission_overview/bloc/permission_overview_cubit.dart
+++ b/comwell_key_app/lib/presentation/screens/permission_overview/bloc/permission_overview_cubit.dart
@@ -8,7 +8,7 @@ import 'package:comwell_key_app/presentation/base/base_cubit.dart';
import 'package:comwell_key_app/presentation/screens/permission_overview/bloc/permission_overview_state.dart';
import 'package:comwell_key_app/utils/secure_storage.dart';
-import '../../../../notifications/notifications_repository.dart';
+import '../../../../domain/repositories/notifications_repository.dart';
class PermissionOverviewCubit extends BaseCubit<PermissionOverviewState> {
PermissionOverviewCubit(
diff --git a/comwell_key_app/lib/routing/app_router.dart b/comwell_key_app/lib/routing/app_router.dart
index 06c88814..0a7c79f0 100644
--- a/comwell_key_app/lib/routing/app_router.dart
+++ b/comwell_key_app/lib/routing/app_router.dart
@@ -5,11 +5,11 @@ import 'package:comwell_key_app/choose_share_room/choose_share_room_route.dart';
import 'package:comwell_key_app/hotel_information/hotel_information_route.dart';
import 'package:comwell_key_app/housekeeping/house_keeping_route.dart';
import 'package:comwell_key_app/my_booking/my_booking_route.dart';
-import 'package:comwell_key_app/notifications/notifications_route.dart';
import 'package:comwell_key_app/pregistration/preregistration_route.dart';
import 'package:comwell_key_app/presentation/screens/booking_details/booking_details_route.dart';
import 'package:comwell_key_app/presentation/screens/change_password/change_password_route.dart';
import 'package:comwell_key_app/presentation/screens/concierge/concierge_route.dart';
+import 'package:comwell_key_app/presentation/screens/notifications/notifications_route.dart';
import 'package:comwell_key_app/presentation/screens/payment_processing/payment_processing_route.dart';
import 'package:comwell_key_app/presentation/screens/permission_overview/permission_overview_route.dart';
import 'package:comwell_key_app/presentation/screens/room_info/room_info_route.dart';
@@ -103,7 +103,7 @@ final router = GoRouter(
preregistrationRoute,
houseKeepingRoute,
paymentProcessingRoute,
- notificationsRoute,
+ $notificationsRoute,
...checkOutRoutes,
upSalesRoute,
roomInfoRoute,
diff --git a/comwell_key_app/lib/services/api.dart b/comwell_key_app/lib/services/api.dart
index 2f55c4be..5c4383b6 100644
--- a/comwell_key_app/lib/services/api.dart
+++ b/comwell_key_app/lib/services/api.dart
@@ -2,7 +2,7 @@ import 'dart:convert';
import 'dart:io';
import 'package:comwell_key_app/hotel_information/models/hotel.dart';
import 'package:comwell_key_app/housekeeping/models/housekeeping.dart';
-import 'package:comwell_key_app/notifications/models/notification_permission.dart';
+import 'package:comwell_key_app/domain/models/notification_permission.dart';
import 'package:comwell_key_app/pregistration/prereg_request_model.dart';
import 'package:comwell_key_app/services/http_client.dart';
import 'package:comwell_key_app/services/models/booking_dto.dart';
diff --git a/comwell_key_app/lib/utils/locator.dart b/comwell_key_app/lib/utils/locator.dart
index 01d6cbf4..904cfe11 100644
--- a/comwell_key_app/lib/utils/locator.dart
+++ b/comwell_key_app/lib/utils/locator.dart
@@ -12,7 +12,7 @@ import 'package:comwell_key_app/hotel_information/repository/hotel_information_r
import 'package:comwell_key_app/housekeeping/housekeeping_repository.dart';
import 'package:comwell_key_app/key/repository/key_repository.dart';
import 'package:comwell_key_app/my_booking/my_booking_repository.dart';
-import 'package:comwell_key_app/notifications/notifications_repository.dart';
+import 'package:comwell_key_app/domain/repositories/notifications_repository.dart';
import 'package:comwell_key_app/overview/repository/overview_repository.dart';
import 'package:comwell_key_app/pregistration/pregistration_repository.dart';
import 'package:comwell_key_app/profile/profile_repository.dart';