Initial ActiveProfileSelector implementation using Notifications system

merge-requests/67/merge
Hank Grabowski 2023-03-11 13:40:36 -05:00
rodzic 854c142c20
commit b01ea9b95e
7 zmienionych plików z 172 dodań i 84 usunięć

Wyświetl plik

@ -1,10 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:logging/logging.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:relatica/utils/snackbar_builder.dart'; import 'package:relatica/utils/snackbar_builder.dart';
import '../routes.dart'; import '../routes.dart';
import '../services/notifications_manager.dart'; import '../services/notifications_manager.dart';
import '../utils/active_profile_selector.dart';
enum NavBarButtons { enum NavBarButtons {
timelines, timelines,
@ -14,15 +16,26 @@ enum NavBarButtons {
} }
class AppBottomNavBar extends StatelessWidget { class AppBottomNavBar extends StatelessWidget {
static final _logger = Logger('$AppBottomNavBar');
final NavBarButtons currentButton; final NavBarButtons currentButton;
const AppBottomNavBar({super.key, required this.currentButton}); const AppBottomNavBar({super.key, required this.currentButton});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final notificationManager = context.watch<NotificationsManager>(); final nmResult = context
final hasNotifications = .watch<ActiveProfileSelector<NotificationsManager>>()
notificationManager.notifications.where((n) => !n.dismissed).isNotEmpty; .activeEntry;
final hasNotifications = nmResult.fold(
onSuccess: (manager) =>
manager.notifications
.where((n) => !n.dismissed)
.isNotEmpty,
onError: (error) {
_logger.info('Error getting notifications manager: $error');
return false;
}
);
return BottomNavigationBar( return BottomNavigationBar(
onTap: (index) { onTap: (index) {
final newButton = _indexToButton(index); final newButton = _indexToButton(index);
@ -89,8 +102,8 @@ class AppBottomNavBar extends StatelessWidget {
throw ArgumentError('$index has no button type'); throw ArgumentError('$index has no button type');
} }
List<BottomNavigationBarItem> _menuItems( List<BottomNavigationBarItem> _menuItems(BuildContext context,
BuildContext context, bool hasNotifications) { bool hasNotifications) {
return [ return [
const BottomNavigationBarItem( const BottomNavigationBarItem(
label: 'Timelines', label: 'Timelines',

Wyświetl plik

@ -1,7 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart'; import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:logging/logging.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:relatica/utils/active_profile_selector.dart';
import '../globals.dart'; import '../globals.dart';
import '../models/user_notification.dart'; import '../models/user_notification.dart';
@ -14,6 +16,7 @@ import '../utils/snackbar_builder.dart';
import 'image_control.dart'; import 'image_control.dart';
class NotificationControl extends StatelessWidget { class NotificationControl extends StatelessWidget {
static final _logger = Logger('$NotificationControl');
final UserNotification notification; final UserNotification notification;
const NotificationControl({ const NotificationControl({
@ -41,7 +44,17 @@ class NotificationControl extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
const iconSize = 50.0; const iconSize = 50.0;
final manager = context.watch<NotificationsManager>(); final manager = context
.watch<ActiveProfileSelector<NotificationsManager>>()
.activeEntry
.fold(
onSuccess: (manager) => manager,
onError: (error) {
_logger.severe('Error getting notification manager: $error');
return null;
},
);
final fromIcon = final fromIcon =
getIt<ConnectionsManager>().getById(notification.fromId).fold( getIt<ConnectionsManager>().getById(notification.fromId).fold(
onSuccess: (connection) => ImageControl( onSuccess: (connection) => ImageControl(
@ -120,11 +133,13 @@ class NotificationControl extends StatelessWidget {
notification.type == NotificationType.direct_message notification.type == NotificationType.direct_message
? null ? null
: IconButton( : IconButton(
onPressed: () async { onPressed: manager == null
? null
: () async {
final result = await manager.markSeen(notification); final result = await manager.markSeen(notification);
if (result.isFailure) { if (result.isFailure) {
buildSnackbar( buildSnackbar(context,
context, 'Error marking notification: ${result.error}'); 'Error marking notification: ${result.error}');
} }
}, },
icon: Icon(Icons.close_rounded)), icon: Icon(Icons.close_rounded)),

Wyświetl plik

@ -17,7 +17,9 @@ class StandardAppDrawer extends StatelessWidget {
(p) => ListTile( (p) => ListTile(
onTap: () async { onTap: () async {
await getIt<AccountsService>().setActiveProfile(p); await getIt<AccountsService>().setActiveProfile(p);
clearCaches(); if (context.mounted) {
context.pop();
}
}, },
leading: CircleAvatar( leading: CircleAvatar(
child: CachedNetworkImage(imageUrl: p.avatar)), child: CachedNetworkImage(imageUrl: p.avatar)),

Wyświetl plik

@ -1,4 +1,5 @@
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:relatica/utils/active_profile_selector.dart';
import 'data/interfaces/connections_repo_intf.dart'; import 'data/interfaces/connections_repo_intf.dart';
import 'data/interfaces/groups_repo.intf.dart'; import 'data/interfaces/groups_repo.intf.dart';
@ -57,8 +58,8 @@ Future<void> dependencyInjectionInitialization() async {
getIt.registerSingleton<TimelineManager>(timelineManager); getIt.registerSingleton<TimelineManager>(timelineManager);
getIt.registerLazySingleton<MediaUploadAttachmentHelper>( getIt.registerLazySingleton<MediaUploadAttachmentHelper>(
() => MediaUploadAttachmentHelper()); () => MediaUploadAttachmentHelper());
getIt.registerLazySingleton<NotificationsManager>( getIt.registerLazySingleton<ActiveProfileSelector<NotificationsManager>>(
() => NotificationsManager()); () => ActiveProfileSelector((_) => NotificationsManager()));
getIt.registerLazySingleton<DirectMessageService>( getIt.registerLazySingleton<DirectMessageService>(
() => DirectMessageService()); () => DirectMessageService());
getIt.registerLazySingleton<InteractionsManager>(() => InteractionsManager()); getIt.registerLazySingleton<InteractionsManager>(() => InteractionsManager());

Wyświetl plik

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:multi_trigger_autocomplete/multi_trigger_autocomplete.dart'; import 'package:multi_trigger_autocomplete/multi_trigger_autocomplete.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:relatica/utils/active_profile_selector.dart';
import 'app_theme.dart'; import 'app_theme.dart';
import 'di_initialization.dart'; import 'di_initialization.dart';
@ -75,8 +76,10 @@ class App extends StatelessWidget {
ChangeNotifierProvider<TimelineManager>( ChangeNotifierProvider<TimelineManager>(
create: (_) => getIt<TimelineManager>(), create: (_) => getIt<TimelineManager>(),
), ),
ChangeNotifierProvider<NotificationsManager>( ChangeNotifierProvider<
create: (_) => getIt<NotificationsManager>(), ActiveProfileSelector<NotificationsManager>>(
create: (_) =>
getIt<ActiveProfileSelector<NotificationsManager>>(),
), ),
ChangeNotifierProvider<DirectMessageService>( ChangeNotifierProvider<DirectMessageService>(
create: (_) => getIt<DirectMessageService>(), create: (_) => getIt<DirectMessageService>(),

Wyświetl plik

@ -10,6 +10,7 @@ import '../controls/status_and_refresh_button.dart';
import '../globals.dart'; import '../globals.dart';
import '../services/network_status_service.dart'; import '../services/network_status_service.dart';
import '../services/notifications_manager.dart'; import '../services/notifications_manager.dart';
import '../utils/active_profile_selector.dart';
import '../utils/snackbar_builder.dart'; import '../utils/snackbar_builder.dart';
class NotificationsScreen extends StatelessWidget { class NotificationsScreen extends StatelessWidget {
@ -21,17 +22,32 @@ class NotificationsScreen extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
_logger.finest('Building'); _logger.finest('Building');
final nss = getIt<NetworkStatusService>(); final nss = getIt<NetworkStatusService>();
final manager = context.watch<NotificationsManager>(); final managerResult = context
final notifications = manager.notifications; .watch<ActiveProfileSelector<NotificationsManager>>()
.activeEntry;
late final String title; late final String title;
late final Widget body; late final Widget body;
late final List<Widget> actions;
managerResult.match(onSuccess: (manager) {
final notifications = manager.notifications;
actions = [
StatusAndRefreshButton(
valueListenable: nss.notificationsUpdateStatus,
refreshFunction: () async => manager.updateNotifications(),
busyColor: Theme.of(context).colorScheme.background,
),
IconButton(
onPressed: () async => _clearAllNotifications(context, manager),
icon: const Icon(Icons.cleaning_services),
),
];
if (notifications.isEmpty) { if (notifications.isEmpty) {
manager.updateNotifications(); manager.updateNotifications();
title = 'Notifications'; title = 'Notifications';
body = Center( body = Center(
child: Column( child: Column(
children: [ children: const [
const Center(child: Text('No notifications')), Center(child: Text('No notifications')),
], ],
)); ));
} else { } else {
@ -83,23 +99,23 @@ class NotificationsScreen extends StatelessWidget {
itemCount: notifications.length + 2), itemCount: notifications.length + 2),
); );
} }
}, onError: (error) {
title = 'Notifications';
actions = [];
body = Center(
child: Column(
children: const [
Center(child: Text('Error getting notifications')),
],
));
});
return Scaffold( return Scaffold(
appBar: StandardAppBar.build( appBar: StandardAppBar.build(
context, context,
title, title,
withDrawer: true, withDrawer: true,
actions: [ actions: actions,
StatusAndRefreshButton(
valueListenable: nss.notificationsUpdateStatus,
refreshFunction: () async => manager.updateNotifications(),
busyColor: Theme.of(context).colorScheme.background,
),
IconButton(
onPressed: () async => _clearAllNotifications(context, manager),
icon: const Icon(Icons.cleaning_services),
),
],
), ),
drawer: StandardAppDrawer(), drawer: StandardAppDrawer(),
body: body, body: body,

Wyświetl plik

@ -0,0 +1,38 @@
import 'package:flutter/foundation.dart';
import 'package:relatica/services/auth_service.dart';
import 'package:result_monad/result_monad.dart';
import '../globals.dart';
import '../models/auth/profile.dart';
import '../models/exec_error.dart';
class ActiveProfileSelector<T extends ChangeNotifier> extends ChangeNotifier {
final _entries = <Profile, T>{};
final T Function(Profile p) _entryBuilder;
ActiveProfileSelector(T Function(Profile p) entryBuilder)
: _entryBuilder = entryBuilder;
Result<T, ExecError> get activeEntry {
final service = getIt<AccountsService>();
if (!service.loggedIn) {
return buildErrorResult(
type: ErrorType.localError,
message: 'No Logged In User',
);
}
final p = service.currentProfile;
return runCatching(() {
final entry = _entries.putIfAbsent(p, () => _buildNewEntry(p));
return Result.ok(entry).execErrorCast();
}).execErrorCast();
}
T _buildNewEntry(Profile p) {
final newEntry = _entryBuilder(p);
newEntry.addListener(() => notifyListeners());
return newEntry;
}
}