diff --git a/packages/rx_bloc_cli/mason_templates/bricks/feature_showcase/__brick__/lib/feature_showcase/views/showcase_page.dart b/packages/rx_bloc_cli/mason_templates/bricks/feature_showcase/__brick__/lib/feature_showcase/views/showcase_page.dart index 753687c47..c0228ac09 100644 --- a/packages/rx_bloc_cli/mason_templates/bricks/feature_showcase/__brick__/lib/feature_showcase/views/showcase_page.dart +++ b/packages/rx_bloc_cli/mason_templates/bricks/feature_showcase/__brick__/lib/feature_showcase/views/showcase_page.dart @@ -58,13 +58,12 @@ class ShowcasePage extends StatelessWidget { extension on BuildContext { List<({String title, String subtitle, RouteDataModel route, Icon icon})> get features => [ - // TODO: Removed until: rx_bloc#929 or rx_bloc#941 is resolved - // ( - // title: l10n.featureNotifications.notificationPageTitle, - // subtitle: l10n.featureNotifications.notificationPageSubtitle, - // route: const NotificationsRoute(), - // icon: designSystem.icons.notifications, - // ), + ( + title: l10n.featureNotifications.notificationPageTitle, + subtitle: l10n.featureNotifications.notificationPageSubtitle, + route: const NotificationsRoute(), + icon: designSystem.icons.notifications, + ), {{#enable_feature_counter}}( title: l10n.featureShowcase.counterShowcase, subtitle: l10n.featureShowcase.counterShowcaseDescription, diff --git a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/base/app/config/app_constants.dart b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/base/app/config/app_constants.dart index 35618b9e0..87538407d 100644 --- a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/base/app/config/app_constants.dart +++ b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/base/app/config/app_constants.dart @@ -15,4 +15,6 @@ const String webVapidKey = ''; /// The duration to debounce actions to prevent multiple actions from being /// triggered in a short period of time. -const actionDebounceDuration = Duration(milliseconds: 500); \ No newline at end of file +const actionDebounceDuration = Duration(milliseconds: 500); + +const String firebaseProjectUrl = 'https://console.firebase.google.com/'; \ No newline at end of file diff --git a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/base/repositories/push_notification_repository.dart b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/base/repositories/push_notification_repository.dart index 38ccb2f08..27cec5425 100644 --- a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/base/repositories/push_notification_repository.dart +++ b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/base/repositories/push_notification_repository.dart @@ -23,28 +23,6 @@ class PushNotificationRepository { final FirebaseMessaging _firebaseMessaging; final NotificationsLocalDataSource _localDataSource; - // Sends a push notification to the server which will be broadcast to all - // logged in users. - Future sendPushMessage({ - required String message, - String? title, - int? delay, - Map? data, - }) async { - final pushToken = await getToken(); - return _errorMapper.execute( - () => _pushDataSource.sendPushMessage( - PushMessageRequestModel( - message: message, - title: title, - delay: delay ?? 0, - data: data ?? {}, - pushToken: pushToken, - ), - ), - ); - } - // Checks if the user has granted permissions for displaying push messages. // If called the very first time, the user is asked to grant permissions. Future requestNotificationPermissions() => diff --git a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/base/theme/design_system/design_system_icons.dart b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/base/theme/design_system/design_system_icons.dart index c1999247d..ec98cf37d 100644 --- a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/base/theme/design_system/design_system_icons.dart +++ b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/base/theme/design_system/design_system_icons.dart @@ -28,9 +28,11 @@ class DesignSystemIcons { final send = Icons.send; + final copy = Icons.copy; + final success = Icons.check_circle_outline; -final dashboardOutlined = Icons.dashboard_outlined; + final dashboardOutlined = Icons.dashboard_outlined; final Icon calculateIcon = _getIcon(Icons.calculate); diff --git a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/feature_notifications/blocs/notifications_bloc.dart b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/feature_notifications/blocs/notifications_bloc.dart index 1f148a3c7..452466dcd 100644 --- a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/feature_notifications/blocs/notifications_bloc.dart +++ b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/feature_notifications/blocs/notifications_bloc.dart @@ -3,6 +3,7 @@ import 'package:rx_bloc/rx_bloc.dart'; import 'package:rxdart/rxdart.dart'; +import '../../base/models/errors/error_model.dart'; import '../services/notifications_service.dart'; part 'notifications_bloc.rxb.g.dart'; @@ -11,39 +12,41 @@ part 'notifications_bloc.rxb.g.dart'; abstract class NotificationsBlocEvents { /// Requests permissions for displaying push notifications void requestNotificationPermissions(); - - /// Issues a new push message - void sendMessage(String message, - {String? title, int? delay, Map? data}); } /// A contract class containing all states of the NotificationsBloC. abstract class NotificationsBlocStates { /// Are the permissions for displaying push notifications granted Stream get permissionsAuthorized; + + /// The push token to which the developers can send notifications + ConnectableStream> get pushToken; } @RxBloc() class NotificationsBloc extends $NotificationsBloc { - NotificationsBloc(this._service); + NotificationsBloc(this._service) { + pushToken.connect().addTo(_compositeSubscription); + } final NotificationService _service; + + @override + Stream _mapToPermissionsAuthorizedState() => +_$requestNotificationPermissionsEvent + .switchMap( +(_) => _service.requestNotificationPermissions().asResultStream(), +) + .setResultStateHandler(this) + .whereSuccess(); + @override - Stream _mapToPermissionsAuthorizedState() => Rx.merge([ - _$sendMessageEvent.switchMap( - (args) => _service - .sendPushMessage( - message: args.message, - title: args.title, - delay: args.delay, - data: args.data, - ) - .then((_) => true) - .asResultStream(), - ), - _$requestNotificationPermissionsEvent.switchMap( - (_) => _service.requestNotificationPermissions().asResultStream(), - ), - ]).setResultStateHandler(this).whereSuccess(); + ConnectableStream> _mapToPushTokenState() => + PublishSubject() + .startWith('') + .switchMap((_) => _service.getPushToken().asResultStream()) + .setResultStateHandler(this) + .mapResult((token) => token ?? (throw NotFoundErrorModel())) + .publish(); } diff --git a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/feature_notifications/services/notifications_service.dart b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/feature_notifications/services/notifications_service.dart index 472d79438..95121a741 100644 --- a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/feature_notifications/services/notifications_service.dart +++ b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/feature_notifications/services/notifications_service.dart @@ -7,19 +7,9 @@ class NotificationService { final PushNotificationRepository _repository; - Future sendPushMessage({ - required String message, - String? title, - int? delay, - Map? data, - }) => - _repository.sendPushMessage( - message: message, - title: title, - delay: delay, - data: data, - ); - Future requestNotificationPermissions() => _repository.requestNotificationPermissions(); + + Future getPushToken() => + _repository.getToken(); } diff --git a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/feature_notifications/ui_components/push_token_widget.dart b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/feature_notifications/ui_components/push_token_widget.dart new file mode 100644 index 000000000..087b06760 --- /dev/null +++ b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/feature_notifications/ui_components/push_token_widget.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:widget_toolkit/shimmer.dart'; +import 'package:widget_toolkit/text_field_dialog.dart'; + +import '../../app_extensions.dart'; + +class PushTokenWidget extends StatelessWidget { + const PushTokenWidget( + {required this.label, this.value, this.error, super.key}); + + final String label; + final String? value; + final String? error; + + @override + Widget build(BuildContext context) => InkWell( + onTap: () { + if (value != null) { + Clipboard.setData(ClipboardData(text: value!)).then((_) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(context.l10n.copiedToYourKeyboard))); + } + }); + } + }, + customBorder: const CircleBorder(), + child: Container( + decoration: BoxDecoration( + color: context.textFieldDialogTheme.editFieldRegularBackground, + borderRadius: BorderRadius.circular( + context.textFieldDialogTheme.editFieldBorderRadius, + ), + ), + child: Padding( + padding: EdgeInsets.symmetric( + vertical: context.textFieldDialogTheme.spacingS, + horizontal: context.textFieldDialogTheme.spacingM, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: context.textFieldDialogTheme.captionBold + .copyWith( + color: context.textFieldDialogTheme + .editFieldLabelNotEditedColor), + ), + SizedBox(height: context.textFieldDialogTheme.spacingXSS), + ShimmerText( + error ?? value, + style: context.textFieldDialogTheme + .editFieldTextNotEditedTextStyle + .copyWith( + color: error == null + ? context.textFieldDialogTheme + .editFieldValueNotEditedColor + : context.designSystem.colors.errorColor), + ) + ], + ), + ), + Visibility( + visible: value != null, + replacement: SizedBox(), + child: Padding( + padding: EdgeInsets.only( + left: context.textFieldDialogTheme.spacingS, + top: context.textFieldDialogTheme.spacingXS, + bottom: context.textFieldDialogTheme.spacingXS), + child: Material( + color: Colors.transparent, + //.copyWith(color: _getValueColor(context)); + child: Icon(context.designSystem.icons.copy), + ), + ), + ), + ], + ), + ), + ), + ); +} diff --git a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/feature_notifications/views/notifications_page.dart b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/feature_notifications/views/notifications_page.dart index 7f5c4d986..27b6f9bcc 100644 --- a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/feature_notifications/views/notifications_page.dart +++ b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/feature_notifications/views/notifications_page.dart @@ -4,14 +4,15 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_rx_bloc/flutter_rx_bloc.dart'; import 'package:provider/provider.dart'; -import 'package:widget_toolkit/ui_components.dart'; +import 'package:widget_toolkit/widget_toolkit.dart'; import '../../app_extensions.dart'; +import '../../base/app/config/app_constants.dart'; import '../../base/common_ui_components/app_list_tile.dart'; import '../../base/common_ui_components/custom_app_bar.dart'; -import '../../base/models/notification_model.dart'; import '../../lib_router/router.dart'; import '../blocs/notifications_bloc.dart'; +import '../ui_components/push_token_widget.dart'; class NotificationsPage extends StatelessWidget { const NotificationsPage({super.key}); @@ -69,47 +70,46 @@ class NotificationsPage extends StatelessWidget { .events .requestNotificationPermissions(), ), - AppListTile( - featureTitle: - context.l10n.featureNotifications.notificationShowText, - trailing: const SizedBox(), - icon: const Icon(Icons.notifications_active_outlined), - onTap: () => context - .read() - .events - .sendMessage(context - .l10n.featureNotifications.notificationsMessage), + SizedBox( + height: context.designSystem.spacing.m, ), - AppListTile( - featureTitle: context - .l10n.featureNotifications.notificationShowDelayedText, - trailing: const SizedBox(), - icon: const Icon(Icons.notifications_paused_outlined), - onTap: () => context - .read() - .events - .sendMessage( - context.l10n.featureNotifications.notificationsDelayed, - delay: 5, - ), + Padding( + padding: EdgeInsets.only( + left: context.designSystem.spacing.l, + right: context.designSystem.spacing.l, ), - AppListTile( - featureTitle: context.l10n.featureNotifications - .notificationShowRedirectingText, - trailing: const SizedBox(), - icon: const Icon(Icons.circle_notifications_outlined), - onTap: () => context - .read() - .events - .sendMessage( - context - .l10n.featureNotifications.notificationRedirecing, - delay: 5, - data: NotificationModel( - type: NotificationModelType.dashboard, - id: '1', - ).toJson()), + child: PushTokenWidget( + label: context + .l10n.featureNotifications.notificationConsoleLabel, + value: firebaseProjectUrl), + ), + SizedBox( + height: context.designSystem.spacing.l, + ), + Padding( + padding: EdgeInsets.only( + left: context.designSystem.spacing.l, + right: context.designSystem.spacing.l, ), + child: RxResultBuilder( + state: (bloc) => bloc.states.pushToken, + buildSuccess: (context, pushToken, bloc) => PushTokenWidget( + label: context + .l10n.featureNotifications.notificationTokenLabel, + value: pushToken, + ), + buildError: (context, error, bloc) => PushTokenWidget( + label: context + .l10n.featureNotifications.notificationTokenLabel, + error: context.l10n.error.notImplemented, + ), + buildLoading: (context, bloc) => PushTokenWidget( + label: context + .l10n.featureNotifications.notificationTokenLabel, + value: null, + ), + ), + ), RxBlocListener( state: (bloc) => bloc.states.permissionsAuthorized, listener: (ctx, authorized) async { diff --git a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/l10n/arb/bg.arb b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/l10n/arb/bg.arb index ca5227a23..0b59a60f2 100644 --- a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/l10n/arb/bg.arb +++ b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/l10n/arb/bg.arb @@ -16,5 +16,6 @@ "english": "Английски", "bulgarian": "Български", "changeLanguage" : "Смени език", - "navShowcase":"Страница с демонстрации" + "navShowcase":"Страница с демонстрации", + "copiedToYourKeyboard":"Стойността беше копирана във вашия клипборд" } \ No newline at end of file diff --git a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/l10n/arb/en.arb b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/l10n/arb/en.arb index 796b4d8f4..a612c760d 100644 --- a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/l10n/arb/en.arb +++ b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/l10n/arb/en.arb @@ -16,5 +16,6 @@ "english" : "English", "bulgarian" : "Bulgarian", "changeLanguage" : "Change Language", - "navShowcase":"Showcase" + "navShowcase":"Showcase", + "copiedToYourKeyboard":"Copied to your clipboard !" } \ No newline at end of file diff --git a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/l10n/arb/error/bg.arb b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/l10n/arb/error/bg.arb index 4700d5d91..36864b87d 100644 --- a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/l10n/arb/error/bg.arb +++ b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/l10n/arb/error/bg.arb @@ -17,5 +17,6 @@ "tooShort": "Въведеният текст е твърде кратък", "notificationsDisabledMessage":"Изглежда, че сте отказали известия на това устройство. За да можем да показваме известия, те трябва да бъдат активирани ръчно от настройките на устройството.", "noMailApp": "Нямате пощенски клиент на устройството. Моля инсталирайте или отворете с браузър.", - "invalidUrl": "Невалиден URL адрес" + "invalidUrl": "Невалиден URL адрес", + "notImplemented": "Тази функционалност не е завършена" } \ No newline at end of file diff --git a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/l10n/arb/error/en.arb b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/l10n/arb/error/en.arb index 28cd390f7..5346c46be 100644 --- a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/l10n/arb/error/en.arb +++ b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/l10n/arb/error/en.arb @@ -18,5 +18,6 @@ "tooShort": "The entered text is too short", "notificationsDisabledMessage":"Seems that you have denied notifications on this device. In order for us to show notifications, they need to be manually enabled from the device settings.", "noMailApp": "No e-mail client found on your device. Please, install one or open your mail provider with a browser.", - "invalidUrl": "Invalid URL" + "invalidUrl": "Invalid URL", +"notImplemented": "This feature is not implemented" } \ No newline at end of file diff --git a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/l10n/arb/feature_notifications/bg.arb b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/l10n/arb/feature_notifications/bg.arb index 4002dbf40..7f88ec175 100644 --- a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/l10n/arb/feature_notifications/bg.arb +++ b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/l10n/arb/feature_notifications/bg.arb @@ -11,5 +11,7 @@ "notificationsMessage": "Това е нотификация!", "notificationsDelayed": "Това е забавена нотификация!", "notificationRedirecing":"Това е пренасочващо известие", - "notificationPageSubtitle": "Демонстрира функционалността на известията" + "notificationPageSubtitle": "Демонстрира функционалността на известията", + "notificationTokenLabel": "Firebase Тукън за известия", + "notificationConsoleLabel": "Линк към Firebase конзола" } \ No newline at end of file diff --git a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/l10n/arb/feature_notifications/en.arb b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/l10n/arb/feature_notifications/en.arb index 5cf1b751c..b18b77737 100644 --- a/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/l10n/arb/feature_notifications/en.arb +++ b/packages/rx_bloc_cli/mason_templates/bricks/rx_bloc_base/__brick__/lib/l10n/arb/feature_notifications/en.arb @@ -11,5 +11,7 @@ "notificationsMessage": "This is a notification!", "notificationsDelayed": "This is a delayed notification!", "notificationRedirecing":"This is a redirecting notification", - "notificationPageSubtitle": "Demonstrates notifications functionallity" + "notificationPageSubtitle": "Demonstrates notifications functionallity", + "notificationTokenLabel": "Firebase Push Token", + "notificationConsoleLabel": "Firebase Console URL" } \ No newline at end of file