kopia lustrzana https://gitlab.com/mysocialportal/relatica
Initial ActiveProfileSelector implementation using Notifications system
rodzic
854c142c20
commit
b01ea9b95e
|
@ -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',
|
||||
|
|
|
@ -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)),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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>(),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
Ładowanie…
Reference in New Issue