Browse Source

neon: Open notifications page on unknown push notification

pull/162/head
jld3103 2 years ago
parent
commit
e2ac5e3e26
No known key found for this signature in database
GPG Key ID: 9062417B9E8EB7B3
  1. 8
      packages/neon/lib/src/blocs/apps.dart
  2. 807
      packages/neon/lib/src/pages/home.dart

8
packages/neon/lib/src/blocs/apps.dart

@ -14,6 +14,8 @@ abstract class AppsBlocStates {
BehaviorSubject<Result<NotificationsApp?>> get notificationsAppImplementation;
BehaviorSubject<String?> get activeAppID;
BehaviorSubject get openNotifications;
}
class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates {
@ -88,6 +90,7 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates
unawaited(appImplementations.close());
unawaited(notificationsAppImplementation.close());
unawaited(activeAppID.close());
unawaited(openNotifications.close());
for (final key in _blocs.keys) {
_blocs[key]!.dispose();
}
@ -107,6 +110,9 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates
BehaviorSubject<Result<NotificationsApp?>> notificationsAppImplementation =
BehaviorSubject<Result<NotificationsApp?>>();
@override
BehaviorSubject openNotifications = BehaviorSubject();
@override
Future refresh() async {
await _requestManager.wrapNextcloud<List<NextcloudApp>, NextcloudCoreNavigationApps>(
@ -128,7 +134,7 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates
activeAppID.add(appID);
}
} else if (appID == 'notifications') {
// TODO: Open notifications page
openNotifications.add(null);
} else {
throw Exception('App $appID not found');
}

807
packages/neon/lib/src/pages/home.dart

@ -21,8 +21,9 @@ class _HomePageState extends State<HomePage> {
final _scaffoldKey = GlobalKey<ScaffoldState>();
late GlobalOptions _globalOptions;
late CapabilitiesBloc _capabilitiesBloc;
late AccountsBloc _accountsBloc;
late AppsBloc _appsBloc;
late CapabilitiesBloc _capabilitiesBloc;
late FirstLaunchBloc _firstLaunchBloc;
@override
@ -30,10 +31,22 @@ class _HomePageState extends State<HomePage> {
super.initState();
_globalOptions = Provider.of<GlobalOptions>(context, listen: false);
_appsBloc = Provider.of<AccountsBloc>(context, listen: false).getAppsBloc(widget.account);
_capabilitiesBloc = Provider.of<AccountsBloc>(context, listen: false).getCapabilitiesBloc(widget.account);
_accountsBloc = Provider.of<AccountsBloc>(context, listen: false);
_appsBloc = _accountsBloc.getAppsBloc(widget.account);
_capabilitiesBloc = _accountsBloc.getCapabilitiesBloc(widget.account);
_firstLaunchBloc = Provider.of<FirstLaunchBloc>(context, listen: false);
_appsBloc.openNotifications.listen((final _) async {
final notificationsAppImplementation = _appsBloc.notificationsAppImplementation.valueOrNull;
if (notificationsAppImplementation != null) {
await _openNotifications(
notificationsAppImplementation.data!,
_accountsBloc.accounts.value,
_accountsBloc.activeAccount.value!,
);
}
});
_capabilitiesBloc.capabilities.listen((final result) async {
if (result.data != null) {
widget.onThemeChanged(result.data!.capabilities.theming);
@ -153,6 +166,34 @@ class _HomePageState extends State<HomePage> {
);
}
Future _openNotifications(
final NotificationsApp app,
final List<Account> accounts,
final Account account,
) async {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (final context) => Scaffold(
appBar: AppBar(
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(app.name(context)),
if (accounts.length > 1) ...[
Text(
account.client.humanReadableID,
style: Theme.of(context).textTheme.bodySmall,
),
],
],
),
),
body: app.buildPage(context, _appsBloc),
),
),
);
}
@override
void dispose() {
_capabilitiesBloc.dispose();
@ -160,450 +201,432 @@ class _HomePageState extends State<HomePage> {
}
@override
Widget build(final BuildContext context) {
final accountsBloc = Provider.of<AccountsBloc>(context, listen: false);
return ResultBuilder<CapabilitiesBloc, Capabilities>(
stream: _capabilitiesBloc.capabilities,
builder: (final context, final capabilities) => ResultBuilder<AppsBloc, List<AppImplementation>>(
stream: _appsBloc.appImplementations,
builder: (final context, final appImplementations) => ResultBuilder<AppsBloc, NotificationsApp?>(
stream: _appsBloc.notificationsAppImplementation,
builder: (final context, final notificationsAppImplementation) => StreamBuilder<String?>(
stream: _appsBloc.activeAppID,
builder: (
final context,
final activeAppIDSnapshot,
) =>
StreamBuilder<List<Account>>(
stream: accountsBloc.accounts,
Widget build(final BuildContext context) => ResultBuilder<CapabilitiesBloc, Capabilities>(
stream: _capabilitiesBloc.capabilities,
builder: (final context, final capabilities) => ResultBuilder<AppsBloc, List<AppImplementation>>(
stream: _appsBloc.appImplementations,
builder: (final context, final appImplementations) => ResultBuilder<AppsBloc, NotificationsApp?>(
stream: _appsBloc.notificationsAppImplementation,
builder: (final context, final notificationsAppImplementation) => StreamBuilder<String?>(
stream: _appsBloc.activeAppID,
builder: (
final context,
final accountsSnapshot,
final activeAppIDSnapshot,
) =>
OptionBuilder<NavigationMode>(
option: _globalOptions.navigationMode,
builder: (final context, final navigationMode) => WillPopScope(
onWillPop: () async {
if (_scaffoldKey.currentState!.isDrawerOpen) {
Navigator.pop(context);
return true;
}
StreamBuilder<List<Account>>(
stream: _accountsBloc.accounts,
builder: (
final context,
final accountsSnapshot,
) =>
OptionBuilder<NavigationMode>(
option: _globalOptions.navigationMode,
builder: (final context, final navigationMode) => WillPopScope(
onWillPop: () async {
if (_scaffoldKey.currentState!.isDrawerOpen) {
Navigator.pop(context);
return true;
}
_scaffoldKey.currentState!.openDrawer();
return false;
},
child: Builder(
builder: (final context) {
if (accountsSnapshot.hasData) {
final accounts = accountsSnapshot.data!;
final account = accounts.singleWhere((final account) => account.id == widget.account.id);
_scaffoldKey.currentState!.openDrawer();
return false;
},
child: Builder(
builder: (final context) {
if (accountsSnapshot.hasData) {
final accounts = accountsSnapshot.data!;
final account = accounts.singleWhere((final account) => account.id == widget.account.id);
final isQuickBar = navigationMode == NavigationMode.quickBar;
final drawer = Drawer(
width: isQuickBar ? kQuickBarWidth : null,
child: Container(
padding: isQuickBar ? const EdgeInsets.all(5) : null,
child: Column(
children: [
Expanded(
child: Scrollbar(
child: ListView(
// Needed for the drawer header to also render in the statusbar
padding: EdgeInsets.zero,
children: [
Builder(
builder: (final context) {
if (accountsSnapshot.hasData) {
if (isQuickBar) {
return Column(
children: [
if (accounts.length != 1) ...[
for (final account in accounts) ...[
Container(
margin: const EdgeInsets.symmetric(
vertical: 5,
),
child: Tooltip(
message: account.client.humanReadableID,
child: IconButton(
onPressed: () {
accountsBloc.setActiveAccount(account);
},
icon: IntrinsicHeight(
child: AccountAvatar(
account: account,
final isQuickBar = navigationMode == NavigationMode.quickBar;
final drawer = Drawer(
width: isQuickBar ? kQuickBarWidth : null,
child: Container(
padding: isQuickBar ? const EdgeInsets.all(5) : null,
child: Column(
children: [
Expanded(
child: Scrollbar(
child: ListView(
// Needed for the drawer header to also render in the statusbar
padding: EdgeInsets.zero,
children: [
Builder(
builder: (final context) {
if (accountsSnapshot.hasData) {
if (isQuickBar) {
return Column(
children: [
if (accounts.length != 1) ...[
for (final account in accounts) ...[
Container(
margin: const EdgeInsets.symmetric(
vertical: 5,
),
child: Tooltip(
message: account.client.humanReadableID,
child: IconButton(
onPressed: () {
_accountsBloc.setActiveAccount(account);
},
icon: IntrinsicHeight(
child: AccountAvatar(
account: account,
),
),
),
),
),
),
],
Container(
margin: const EdgeInsets.only(
top: 10,
),
child: Divider(
height: 5,
color: Theme.of(context).appBarTheme.foregroundColor,
),
),
],
],
);
}
return DrawerHeader(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (capabilities.data != null) ...[
if (capabilities.data!.capabilities.theming?.name != null) ...[
Text(
capabilities.data!.capabilities.theming!.name!,
style: DefaultTextStyle.of(context).style.copyWith(
color: Theme.of(context).appBarTheme.foregroundColor,
),
),
],
if (capabilities.data!.capabilities.theming?.logo != null) ...[
Flexible(
child: CachedURLImage(
url: capabilities.data!.capabilities.theming!.logo!,
],
Container(
margin: const EdgeInsets.only(
top: 10,
),
child: Divider(
height: 5,
color: Theme.of(context).appBarTheme.foregroundColor,
),
),
],
] else ...[
ExceptionWidget(
capabilities.error,
onRetry: _capabilitiesBloc.refresh,
),
CustomLinearProgressIndicator(
visible: capabilities.loading,
),
],
if (accounts.length != 1) ...[
DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
dropdownColor: Theme.of(context).colorScheme.primary,
iconEnabledColor: Theme.of(context).colorScheme.onBackground,
value: widget.account.id,
items: accounts
.map<DropdownMenuItem<String>>(
(final account) => DropdownMenuItem<String>(
value: account.id,
child: AccountTile(
account: account,
dense: true,
textColor:
Theme.of(context).appBarTheme.foregroundColor,
),
),
)
.toList(),
onChanged: (final id) {
for (final account in accounts) {
if (account.id == id) {
accountsBloc.setActiveAccount(account);
break;
}
}
},
),
),
],
],
),
);
}
return Container();
},
),
ExceptionWidget(
appImplementations.error,
onlyIcon: isQuickBar,
onRetry: _appsBloc.refresh,
),
CustomLinearProgressIndicator(
visible: appImplementations.loading,
),
if (appImplementations.data != null) ...[
for (final appImplementation in appImplementations.data!) ...[
StreamBuilder<int>(
stream: appImplementation.getUnreadCounter(_appsBloc) ??
BehaviorSubject<int>.seeded(0),
builder: (final context, final unreadCounterSnapshot) {
final unreadCount = unreadCounterSnapshot.data ?? 0;
if (isQuickBar) {
return Tooltip(
message: appImplementation.name(context),
child: IconButton(
onPressed: () async {
await _appsBloc.setActiveApp(appImplementation.id);
},
icon: AppImplementationIcon(
appImplementation: appImplementation,
unreadCount: unreadCount,
color: Theme.of(context).colorScheme.primary,
),
),
);
}
return ListTile(
key: Key('app-${appImplementation.id}'),
title: Row(
return DrawerHeader(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(appImplementation.name(context)),
if (unreadCount > 0) ...[
Text(
unreadCount.toString(),
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.bold,
fontSize: 14,
if (capabilities.data != null) ...[
if (capabilities.data!.capabilities.theming?.name != null) ...[
Text(
capabilities.data!.capabilities.theming!.name!,
style: DefaultTextStyle.of(context).style.copyWith(
color: Theme.of(context).appBarTheme.foregroundColor,
),
),
],
if (capabilities.data!.capabilities.theming?.logo != null) ...[
Flexible(
child: CachedURLImage(
url: capabilities.data!.capabilities.theming!.logo!,
),
),
],
] else ...[
ExceptionWidget(
capabilities.error,
onRetry: _capabilitiesBloc.refresh,
),
CustomLinearProgressIndicator(
visible: capabilities.loading,
),
],
if (accounts.length != 1) ...[
DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
dropdownColor: Theme.of(context).colorScheme.primary,
iconEnabledColor:
Theme.of(context).colorScheme.onBackground,
value: widget.account.id,
items: accounts
.map<DropdownMenuItem<String>>(
(final account) => DropdownMenuItem<String>(
value: account.id,
child: AccountTile(
account: account,
dense: true,
textColor:
Theme.of(context).appBarTheme.foregroundColor,
),
),
)
.toList(),
onChanged: (final id) {
for (final account in accounts) {
if (account.id == id) {
_accountsBloc.setActiveAccount(account);
break;
}
}
},
),
),
],
],
),
leading: appImplementation.buildIcon(context),
minLeadingWidth: 0,
onTap: () async {
await _appsBloc.setActiveApp(appImplementation.id);
if (navigationMode == NavigationMode.drawer) {
// Don't pop when the drawer is always shown
if (!mounted) {
return;
}
Navigator.of(context).pop();
}
},
);
},
),
}
return Container();
},
),
ExceptionWidget(
appImplementations.error,
onlyIcon: isQuickBar,
onRetry: _appsBloc.refresh,
),
CustomLinearProgressIndicator(
visible: appImplementations.loading,
),
if (appImplementations.data != null) ...[
for (final appImplementation in appImplementations.data!) ...[
StreamBuilder<int>(
stream: appImplementation.getUnreadCounter(_appsBloc) ??
BehaviorSubject<int>.seeded(0),
builder: (final context, final unreadCounterSnapshot) {
final unreadCount = unreadCounterSnapshot.data ?? 0;
if (isQuickBar) {
return Tooltip(
message: appImplementation.name(context),
child: IconButton(
onPressed: () async {
await _appsBloc.setActiveApp(appImplementation.id);
},
icon: AppImplementationIcon(
appImplementation: appImplementation,
unreadCount: unreadCount,
color: Theme.of(context).colorScheme.primary,
),
),
);
}
return ListTile(
key: Key('app-${appImplementation.id}'),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(appImplementation.name(context)),
if (unreadCount > 0) ...[
Text(
unreadCount.toString(),
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
],
],
),
leading: appImplementation.buildIcon(context),
minLeadingWidth: 0,
onTap: () async {
await _appsBloc.setActiveApp(appImplementation.id);
if (navigationMode == NavigationMode.drawer) {
// Don't pop when the drawer is always shown
if (!mounted) {
return;
}
Navigator.of(context).pop();
}
},
);
},
),
],
],
],
],
),
),
),
),
if (isQuickBar) ...[
IconButton(
icon: Icon(
Icons.settings,
color: Theme.of(context).appBarTheme.foregroundColor,
if (isQuickBar) ...[
IconButton(
icon: Icon(
Icons.settings,
color: Theme.of(context).appBarTheme.foregroundColor,
),
onPressed: _openSettings,
),
onPressed: _openSettings,
),
] else ...[
ListTile(
key: const Key('settings'),
title: Text(AppLocalizations.of(context).settings),
leading: const Icon(Icons.settings),
minLeadingWidth: 0,
onTap: () async {
if (navigationMode == NavigationMode.drawer) {
Navigator.of(context).pop();
}
await _openSettings();
},
),
] else ...[
ListTile(
key: const Key('settings'),
title: Text(AppLocalizations.of(context).settings),
leading: const Icon(Icons.settings),
minLeadingWidth: 0,
onTap: () async {
if (navigationMode == NavigationMode.drawer) {
Navigator.of(context).pop();
}
await _openSettings();
},
),
],
],
],
),
),
),
);
);
return Scaffold(
resizeToAvoidBottomInset: false,
body: Row(
children: [
if (navigationMode == NavigationMode.drawerAlwaysVisible) ...[
drawer,
],
Expanded(
child: Scaffold(
key: _scaffoldKey,
resizeToAvoidBottomInset: false,
drawer: navigationMode == NavigationMode.drawer ? drawer : null,
appBar: AppBar(
scrolledUnderElevation: navigationMode != NavigationMode.drawer ? 0 : null,
automaticallyImplyLeading: navigationMode == NavigationMode.drawer,
leadingWidth: isQuickBar ? kQuickBarWidth : null,
leading: isQuickBar
? Container(
padding: const EdgeInsets.all(5),
child: capabilities.data?.capabilities.theming?.logo != null
? CachedURLImage(
url: capabilities.data!.capabilities.theming!.logo!,
)
: null,
)
: null,
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
if (appImplementations.data != null && activeAppIDSnapshot.hasData) ...[
Flexible(
child: Text(
appImplementations.data!
.singleWhere((final a) => a.id == activeAppIDSnapshot.data!)
.name(context),
return Scaffold(
resizeToAvoidBottomInset: false,
body: Row(
children: [
if (navigationMode == NavigationMode.drawerAlwaysVisible) ...[
drawer,
],
Expanded(
child: Scaffold(
key: _scaffoldKey,
resizeToAvoidBottomInset: false,
drawer: navigationMode == NavigationMode.drawer ? drawer : null,
appBar: AppBar(
scrolledUnderElevation: navigationMode != NavigationMode.drawer ? 0 : null,
automaticallyImplyLeading: navigationMode == NavigationMode.drawer,
leadingWidth: isQuickBar ? kQuickBarWidth : null,
leading: isQuickBar
? Container(
padding: const EdgeInsets.all(5),
child: capabilities.data?.capabilities.theming?.logo != null
? CachedURLImage(
url: capabilities.data!.capabilities.theming!.logo!,
)
: null,
)
: null,
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
if (appImplementations.data != null && activeAppIDSnapshot.hasData) ...[
Flexible(
child: Text(
appImplementations.data!
.singleWhere((final a) => a.id == activeAppIDSnapshot.data!)
.name(context),
),
),
),
],
if (appImplementations.error != null) ...[
const SizedBox(
width: 8,
),
Icon(
Icons.error_outline,
size: 30,
color: Theme.of(context).colorScheme.onPrimary,
),
],
if (appImplementations.loading) ...[
const SizedBox(
width: 8,
),
Expanded(
child: CustomLinearProgressIndicator(
color: Theme.of(context).appBarTheme.foregroundColor,
],
if (appImplementations.error != null) ...[
const SizedBox(
width: 8,
),
),
Icon(
Icons.error_outline,
size: 30,
color: Theme.of(context).colorScheme.onPrimary,
),
],
if (appImplementations.loading) ...[
const SizedBox(
width: 8,
),
Expanded(
child: CustomLinearProgressIndicator(
color: Theme.of(context).appBarTheme.foregroundColor,
),
),
],
],
),
if (accounts.length > 1) ...[
Text(
account.client.humanReadableID,
style: Theme.of(context).textTheme.bodySmall,
),
],
),
if (accounts.length > 1) ...[
Text(
account.client.humanReadableID,
style: Theme.of(context).textTheme.bodySmall,
],
),
actions: [
if (notificationsAppImplementation.data != null) ...[
StreamBuilder<int>(
stream: notificationsAppImplementation.data!.getUnreadCounter(_appsBloc),
builder: (final context, final unreadCounterSnapshot) {
final unreadCount = unreadCounterSnapshot.data ?? 0;
return IconButton(
key: Key('app-${notificationsAppImplementation.data!.id}'),
icon: AppImplementationIcon(
appImplementation: notificationsAppImplementation.data!,
unreadCount: unreadCount,
color: unreadCount > 0
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onBackground,
width: kAvatarSize * 2 / 3,
height: kAvatarSize * 2 / 3,
),
onPressed: () async {
await _openNotifications(
notificationsAppImplementation.data!,
accounts,
account,
);
},
);
},
),
],
],
),
actions: [
if (notificationsAppImplementation.data != null) ...[
StreamBuilder<int>(
stream: notificationsAppImplementation.data!.getUnreadCounter(_appsBloc),
builder: (final context, final unreadCounterSnapshot) {
final unreadCount = unreadCounterSnapshot.data ?? 0;
return IconButton(
key: Key('app-${notificationsAppImplementation.data!.id}'),
icon: AppImplementationIcon(
appImplementation: notificationsAppImplementation.data!,
unreadCount: unreadCount,
color: unreadCount > 0
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onBackground,
width: kAvatarSize * 2 / 3,
height: kAvatarSize * 2 / 3,
IconButton(
icon: IntrinsicWidth(
child: AccountAvatar(
account: account,
),
),
onPressed: () async {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (final context) => AccountSettingsPage(
bloc: _accountsBloc,
account: account,
),
),
onPressed: () async {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (final context) => Scaffold(
appBar: AppBar(
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(notificationsAppImplementation.data!.name(context)),
if (accounts.length > 1) ...[
Text(
account.client.humanReadableID,
style: Theme.of(context).textTheme.bodySmall,
),
],
],
),
),
body: notificationsAppImplementation.data!
.buildPage(context, _appsBloc),
),
),
);
},
);
},
),
],
IconButton(
icon: IntrinsicWidth(
child: AccountAvatar(
account: account,
),
),
onPressed: () async {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (final context) => AccountSettingsPage(
bloc: accountsBloc,
account: account,
),
body: Row(
children: [
if (navigationMode == NavigationMode.quickBar) ...[
drawer,
],
Expanded(
child: Column(
children: [
ExceptionWidget(
appImplementations.error,
onRetry: _appsBloc.refresh,
),
),
);
},
),
],
),
body: Row(
children: [
if (navigationMode == NavigationMode.quickBar) ...[
drawer,
],
Expanded(
child: Column(
children: [
ExceptionWidget(
appImplementations.error,
onRetry: _appsBloc.refresh,
),
if (appImplementations.data != null) ...[
if (appImplementations.data!.isEmpty) ...[
Expanded(
child: Center(
child: Text(
AppLocalizations.of(context).errorNoCompatibleNextcloudAppsFound,
textAlign: TextAlign.center,
),
),
),
] else ...[
if (activeAppIDSnapshot.hasData) ...[
if (appImplementations.data != null) ...[
if (appImplementations.data!.isEmpty) ...[
Expanded(
child: appImplementations.data!
.singleWhere((final a) => a.id == activeAppIDSnapshot.data!)
.buildPage(context, _appsBloc),
child: Center(
child: Text(
AppLocalizations.of(context)
.errorNoCompatibleNextcloudAppsFound,
textAlign: TextAlign.center,
),
),
),
] else ...[
if (activeAppIDSnapshot.hasData) ...[
Expanded(
child: appImplementations.data!
.singleWhere((final a) => a.id == activeAppIDSnapshot.data!)
.buildPage(context, _appsBloc),
),
],
],
],
],
],
),
),
),
],
],
),
),
),
),
],
),
);
}
return Container();
},
],
),
);
}
return Container();
},
),
),
),
),
),
),
),
),
);
}
);
}

Loading…
Cancel
Save