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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -10,6 +10,7 @@ import '../controls/status_and_refresh_button.dart';
import '../globals.dart';
import '../services/network_status_service.dart';
import '../services/notifications_manager.dart';
import '../utils/active_profile_selector.dart';
import '../utils/snackbar_builder.dart';
class NotificationsScreen extends StatelessWidget {
@ -21,85 +22,100 @@ class NotificationsScreen extends StatelessWidget {
Widget build(BuildContext context) {
_logger.finest('Building');
final nss = getIt<NetworkStatusService>();
final manager = context.watch<NotificationsManager>();
final notifications = manager.notifications;
final managerResult = context
.watch<ActiveProfileSelector<NotificationsManager>>()
.activeEntry;
late final String title;
late final Widget body;
if (notifications.isEmpty) {
manager.updateNotifications();
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) {
manager.updateNotifications();
title = 'Notifications';
body = Center(
child: Column(
children: const [
Center(child: Text('No notifications')),
],
));
} else {
final unreadCount = notifications.where((e) => !e.dismissed).length;
title = 'Notifications ($unreadCount)';
body = RefreshIndicator(
onRefresh: () async {
manager.updateNotifications();
},
child: ListView.separated(
itemBuilder: (context, index) {
if (index == 0) {
return TextButton(
onPressed: () async {
final result = await manager.loadNewerNotifications();
final noMore = result.fold(
onSuccess: (values) => values.isEmpty,
onError: (_) => true);
if (context.mounted && noMore) {
buildSnackbar(
context, 'No newer notifications to load');
}
},
child: const Text('Load newer notifications'));
}
if (index == notifications.length + 1) {
return TextButton(
onPressed: () async {
final result = await manager.loadOlderNotifications();
final noMore = result.fold(
onSuccess: (values) => values.isEmpty,
onError: (_) => true);
if (context.mounted && noMore) {
buildSnackbar(
context, 'No older notifications to load');
}
},
child: const Text('Load older notifications'));
}
return NotificationControl(
notification: notifications[index - 1]);
},
separatorBuilder: (context, index) {
return const Divider(
color: Colors.black54,
height: 0.0,
);
},
itemCount: notifications.length + 2),
);
}
}, onError: (error) {
title = 'Notifications';
actions = [];
body = Center(
child: Column(
children: [
const Center(child: Text('No notifications')),
children: const [
Center(child: Text('Error getting notifications')),
],
));
} else {
final unreadCount = notifications.where((e) => !e.dismissed).length;
title = 'Notifications ($unreadCount)';
body = RefreshIndicator(
onRefresh: () async {
manager.updateNotifications();
},
child: ListView.separated(
itemBuilder: (context, index) {
if (index == 0) {
return TextButton(
onPressed: () async {
final result = await manager.loadNewerNotifications();
final noMore = result.fold(
onSuccess: (values) => values.isEmpty,
onError: (_) => true);
if (context.mounted && noMore) {
buildSnackbar(
context, 'No newer notifications to load');
}
},
child: const Text('Load newer notifications'));
}
if (index == notifications.length + 1) {
return TextButton(
onPressed: () async {
final result = await manager.loadOlderNotifications();
final noMore = result.fold(
onSuccess: (values) => values.isEmpty,
onError: (_) => true);
if (context.mounted && noMore) {
buildSnackbar(
context, 'No older notifications to load');
}
},
child: const Text('Load older notifications'));
}
return NotificationControl(
notification: notifications[index - 1]);
},
separatorBuilder: (context, index) {
return const Divider(
color: Colors.black54,
height: 0.0,
);
},
itemCount: notifications.length + 2),
);
}
});
return Scaffold(
appBar: StandardAppBar.build(
context,
title,
withDrawer: true,
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),
),
],
actions: actions,
),
drawer: StandardAppDrawer(),
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;
}
}