From 60bdc6151bbffc8183cf0b0e539ee8e3b48005ad Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 14 Nov 2022 10:40:31 -0600 Subject: [PATCH] desktop notifications view --- lib/notifications/notification_card.dart | 75 ++++++++++++++--- .../home/desktop_home_view.dart | 44 ++++++++-- .../home/desktop_menu.dart | 83 ++++++++++++------- .../desktop_notifications_view.dart | 20 ++++- .../desktop/current_desktop_menu_item.dart | 5 ++ 5 files changed, 179 insertions(+), 48 deletions(-) create mode 100644 lib/providers/desktop/current_desktop_menu_item.dart diff --git a/lib/notifications/notification_card.dart b/lib/notifications/notification_card.dart index 67be236f0..2a181499c 100644 --- a/lib/notifications/notification_card.dart +++ b/lib/notifications/notification_card.dart @@ -4,6 +4,8 @@ import 'package:stackwallet/models/notification_model.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -20,22 +22,33 @@ class NotificationCard extends StatelessWidget { return Format.extractDateFrom(date.millisecondsSinceEpoch ~/ 1000); } + static const double mobileIconSize = 24; + static const double desktopIconSize = 30; + @override Widget build(BuildContext context) { + final isDesktop = Util.isDesktop; + return Stack( children: [ RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.symmetric( + horizontal: 20, + vertical: 10, + ) + : const EdgeInsets.all(12), child: Row( children: [ notification.changeNowId == null ? SvgPicture.asset( notification.iconAssetName, - width: 24, - height: 24, + width: isDesktop ? desktopIconSize : mobileIconSize, + height: isDesktop ? desktopIconSize : mobileIconSize, ) : Container( - width: 24, - height: 24, + width: isDesktop ? desktopIconSize : mobileIconSize, + height: isDesktop ? desktopIconSize : mobileIconSize, decoration: BoxDecoration( color: Colors.transparent, borderRadius: BorderRadius.circular(24), @@ -45,8 +58,8 @@ class NotificationCard extends StatelessWidget { color: Theme.of(context) .extension()! .accentColorDark, - width: 24, - height: 24, + width: isDesktop ? desktopIconSize : mobileIconSize, + height: isDesktop ? desktopIconSize : mobileIconSize, ), ), const SizedBox( @@ -56,9 +69,35 @@ class NotificationCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Text( - notification.title, - style: STextStyles.titleBold12(context), + ConditionalParent( + condition: isDesktop && !notification.read, + builder: (child) => Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + child, + Text( + "New", + style: + STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorGreen, + ), + ) + ], + ), + child: Text( + notification.title, + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ) + : STextStyles.titleBold12(context), + ), ), const SizedBox( height: 2, @@ -68,11 +107,25 @@ class NotificationCard extends StatelessWidget { children: [ Text( notification.description, - style: STextStyles.label(context), + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ) + : STextStyles.label(context), ), Text( extractPrettyDateString(notification.date), - style: STextStyles.label(context), + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ) + : STextStyles.label(context), ), ], ), diff --git a/lib/pages_desktop_specific/home/desktop_home_view.dart b/lib/pages_desktop_specific/home/desktop_home_view.dart index c0b0145f7..b1c35f00b 100644 --- a/lib/pages_desktop_specific/home/desktop_home_view.dart +++ b/lib/pages_desktop_specific/home/desktop_home_view.dart @@ -7,6 +7,9 @@ import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/my_stack_v import 'package:stackwallet/pages_desktop_specific/home/notifications/desktop_notifications_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/support_and_about_view/desktop_about_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/support_and_about_view/desktop_support_view.dart'; +import 'package:stackwallet/providers/desktop/current_desktop_menu_item.dart'; +import 'package:stackwallet/providers/global/notifications_provider.dart'; +import 'package:stackwallet/providers/ui/unread_notifications_provider.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -20,7 +23,6 @@ class DesktopHomeView extends ConsumerStatefulWidget { } class _DesktopHomeViewState extends ConsumerState { - DesktopMenuItemId currentViewKey = DesktopMenuItemId.myStack; final Map contentViews = { DesktopMenuItemId.myStack: const Navigator( key: Key("desktopStackHomeKey"), @@ -58,10 +60,36 @@ class _DesktopHomeViewState extends ConsumerState { ), }; - void onMenuSelectionChanged(DesktopMenuItemId newKey) { - setState(() { - currentViewKey = newKey; - }); + void onMenuSelectionWillChange(DesktopMenuItemId newKey) { + // check for unread notifications and refresh provider before + // showing notifications view + if (newKey == DesktopMenuItemId.notifications) { + ref.refresh(unreadNotificationsStateProvider); + } + // mark notifications as read if leaving notifications view + if (ref.read(currentDesktopMenuItemProvider.state).state == + DesktopMenuItemId.notifications && + newKey != DesktopMenuItemId.notifications) { + final Set unreadNotificationIds = + ref.read(unreadNotificationsStateProvider.state).state; + + if (unreadNotificationIds.isNotEmpty) { + List> futures = []; + for (int i = 0; i < unreadNotificationIds.length - 1; i++) { + futures.add(ref + .read(notificationsProvider) + .markAsRead(unreadNotificationIds.elementAt(i), false)); + } + + // wait for multiple to update if any + Future.wait(futures).then((_) { + // only notify listeners once + ref + .read(notificationsProvider) + .markAsRead(unreadNotificationIds.last, true); + }); + } + } } @override @@ -71,14 +99,16 @@ class _DesktopHomeViewState extends ConsumerState { child: Row( children: [ DesktopMenu( - onSelectionChanged: onMenuSelectionChanged, + // onSelectionChanged: onMenuSelectionChanged, + onSelectionWillChange: onMenuSelectionWillChange, ), Container( width: 1, color: Theme.of(context).extension()!.background, ), Expanded( - child: contentViews[currentViewKey]!, + child: contentViews[ + ref.watch(currentDesktopMenuItemProvider.state).state]!, ), ], ), diff --git a/lib/pages_desktop_specific/home/desktop_menu.dart b/lib/pages_desktop_specific/home/desktop_menu.dart index cfa1a0ff0..bdaa1d6ce 100644 --- a/lib/pages_desktop_specific/home/desktop_menu.dart +++ b/lib/pages_desktop_specific/home/desktop_menu.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_menu_item.dart'; +import 'package:stackwallet/providers/desktop/current_desktop_menu_item.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -21,10 +22,12 @@ enum DesktopMenuItemId { class DesktopMenu extends ConsumerStatefulWidget { const DesktopMenu({ Key? key, - required this.onSelectionChanged, + this.onSelectionChanged, + this.onSelectionWillChange, }) : super(key: key); final void Function(DesktopMenuItemId)? onSelectionChanged; + final void Function(DesktopMenuItemId)? onSelectionWillChange; @override ConsumerState createState() => _DesktopMenuState(); @@ -35,12 +38,12 @@ class _DesktopMenuState extends ConsumerState { static const minimizedWidth = 72.0; double _width = expandedWidth; - DesktopMenuItemId selectedMenuItem = DesktopMenuItemId.myStack; void updateSelectedMenuItem(DesktopMenuItemId idKey) { - setState(() { - selectedMenuItem = idKey; - }); + widget.onSelectionWillChange?.call(idKey); + + ref.read(currentDesktopMenuItemProvider.state).state = idKey; + widget.onSelectionChanged?.call(idKey); } @@ -95,7 +98,10 @@ class _DesktopMenuState extends ConsumerState { Assets.svg.walletDesktop, width: 20, height: 20, - color: DesktopMenuItemId.myStack == selectedMenuItem + color: DesktopMenuItemId.myStack == + ref + .watch(currentDesktopMenuItemProvider.state) + .state ? Theme.of(context) .extension()! .textDark @@ -106,7 +112,8 @@ class _DesktopMenuState extends ConsumerState { ), label: "My Stack", value: DesktopMenuItemId.myStack, - group: selectedMenuItem, + group: + ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, iconOnly: _width == minimizedWidth, ), @@ -118,7 +125,7 @@ class _DesktopMenuState extends ConsumerState { // Assets.svg.exchangeDesktop, // width: 20, // height: 20, - // color: DesktopMenuItemId.exchange == selectedMenuItem + // color: DesktopMenuItemId.exchange == ref.watch(currentDesktopMenuItemProvider.state).state // ? Theme.of(context) // .extension()! // .textDark @@ -129,7 +136,7 @@ class _DesktopMenuState extends ConsumerState { // ), // label: "Exchange", // value: DesktopMenuItemId.exchange, - // group: selectedMenuItem, + // group: ref.watch(currentDesktopMenuItemProvider.state).state, // onChanged: updateSelectedMenuItem, // iconOnly: _width == minimizedWidth, // ), @@ -141,19 +148,22 @@ class _DesktopMenuState extends ConsumerState { Assets.svg.bell, width: 20, height: 20, - color: - DesktopMenuItemId.notifications == selectedMenuItem - ? Theme.of(context) - .extension()! - .textDark - : Theme.of(context) - .extension()! - .textDark - .withOpacity(0.8), + color: DesktopMenuItemId.notifications == + ref + .watch(currentDesktopMenuItemProvider.state) + .state + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textDark + .withOpacity(0.8), ), label: "Notifications", value: DesktopMenuItemId.notifications, - group: selectedMenuItem, + group: + ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, iconOnly: _width == minimizedWidth, ), @@ -165,7 +175,10 @@ class _DesktopMenuState extends ConsumerState { Assets.svg.addressBookDesktop, width: 20, height: 20, - color: DesktopMenuItemId.addressBook == selectedMenuItem + color: DesktopMenuItemId.addressBook == + ref + .watch(currentDesktopMenuItemProvider.state) + .state ? Theme.of(context) .extension()! .textDark @@ -176,7 +189,8 @@ class _DesktopMenuState extends ConsumerState { ), label: "Address Book", value: DesktopMenuItemId.addressBook, - group: selectedMenuItem, + group: + ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, iconOnly: _width == minimizedWidth, ), @@ -188,7 +202,10 @@ class _DesktopMenuState extends ConsumerState { Assets.svg.gear, width: 20, height: 20, - color: DesktopMenuItemId.settings == selectedMenuItem + color: DesktopMenuItemId.settings == + ref + .watch(currentDesktopMenuItemProvider.state) + .state ? Theme.of(context) .extension()! .textDark @@ -199,7 +216,8 @@ class _DesktopMenuState extends ConsumerState { ), label: "Settings", value: DesktopMenuItemId.settings, - group: selectedMenuItem, + group: + ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, iconOnly: _width == minimizedWidth, ), @@ -211,7 +229,10 @@ class _DesktopMenuState extends ConsumerState { Assets.svg.messageQuestion, width: 20, height: 20, - color: DesktopMenuItemId.support == selectedMenuItem + color: DesktopMenuItemId.support == + ref + .watch(currentDesktopMenuItemProvider.state) + .state ? Theme.of(context) .extension()! .textDark @@ -222,7 +243,8 @@ class _DesktopMenuState extends ConsumerState { ), label: "Support", value: DesktopMenuItemId.support, - group: selectedMenuItem, + group: + ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, iconOnly: _width == minimizedWidth, ), @@ -234,7 +256,10 @@ class _DesktopMenuState extends ConsumerState { Assets.svg.aboutDesktop, width: 20, height: 20, - color: DesktopMenuItemId.about == selectedMenuItem + color: DesktopMenuItemId.about == + ref + .watch(currentDesktopMenuItemProvider.state) + .state ? Theme.of(context) .extension()! .textDark @@ -245,7 +270,8 @@ class _DesktopMenuState extends ConsumerState { ), label: "About", value: DesktopMenuItemId.about, - group: selectedMenuItem, + group: + ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: updateSelectedMenuItem, iconOnly: _width == minimizedWidth, ), @@ -262,7 +288,8 @@ class _DesktopMenuState extends ConsumerState { ), label: "Exit", value: 7, - group: selectedMenuItem, + group: + ref.watch(currentDesktopMenuItemProvider.state).state, onChanged: (_) { // todo: save stuff/ notify before exit? exit(0); diff --git a/lib/pages_desktop_specific/home/notifications/desktop_notifications_view.dart b/lib/pages_desktop_specific/home/notifications/desktop_notifications_view.dart index c8e688aa6..0c51f899d 100644 --- a/lib/pages_desktop_specific/home/notifications/desktop_notifications_view.dart +++ b/lib/pages_desktop_specific/home/notifications/desktop_notifications_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/notifications/notification_card.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/providers/ui/unread_notifications_provider.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; @@ -47,10 +48,25 @@ class _DesktopNotificationsViewState ), ) : ListView.builder( + primary: false, itemCount: notifications.length, itemBuilder: (context, index) { - return NotificationCard( - notification: notifications[index], + final notification = notifications[index]; + if (notification.read == false) { + ref + .read(unreadNotificationsStateProvider.state) + .state + .add(notification.id); + } + + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 5, + ), + child: NotificationCard( + notification: notification, + ), ); }, ), diff --git a/lib/providers/desktop/current_desktop_menu_item.dart b/lib/providers/desktop/current_desktop_menu_item.dart new file mode 100644 index 000000000..6a58db6a0 --- /dev/null +++ b/lib/providers/desktop/current_desktop_menu_item.dart @@ -0,0 +1,5 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages_desktop_specific/home/desktop_menu.dart'; + +final currentDesktopMenuItemProvider = + StateProvider((ref) => DesktopMenuItemId.myStack);