Browse Source

Merge pull request #397 from Leptopoda/refactor/appBar

Refactor/app bar
pull/405/head
Nikolas Rimikis 1 year ago committed by GitHub
parent
commit
935a3470ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      packages/neon/neon/lib/neon.dart
  2. 163
      packages/neon/neon/lib/src/pages/home.dart
  3. 196
      packages/neon/neon/lib/src/widgets/app_bar.dart

1
packages/neon/neon/lib/neon.dart

@ -26,6 +26,7 @@ import 'package:neon/src/blocs/blocs.dart';
import 'package:neon/src/models/account.dart'; import 'package:neon/src/models/account.dart';
import 'package:neon/src/models/push_notification.dart'; import 'package:neon/src/models/push_notification.dart';
import 'package:neon/src/router.dart'; import 'package:neon/src/router.dart';
import 'package:neon/src/widgets/app_bar.dart';
import 'package:neon/src/widgets/drawer.dart'; import 'package:neon/src/widgets/drawer.dart';
import 'package:neon/src/widgets/drawer_destination.dart'; import 'package:neon/src/widgets/drawer_destination.dart';
import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/nextcloud.dart';

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

@ -11,16 +11,13 @@ class HomePage extends StatefulWidget {
State<HomePage> createState() => _HomePageState(); State<HomePage> createState() => _HomePageState();
} }
// ignore: prefer_mixin
class _HomePageState extends State<HomePage> { class _HomePageState extends State<HomePage> {
final _scaffoldKey = GlobalKey<ScaffoldState>(); final _scaffoldKey = GlobalKey<ScaffoldState>();
final drawerScrollController = ScrollController();
late Account _account; late Account _account;
late GlobalOptions _globalOptions; late GlobalOptions _globalOptions;
late AccountsBloc _accountsBloc; late AccountsBloc _accountsBloc;
late AppsBloc _appsBloc; late AppsBloc _appsBloc;
late CapabilitiesBloc _capabilitiesBloc;
@override @override
void initState() { void initState() {
@ -29,18 +26,6 @@ class _HomePageState extends State<HomePage> {
_accountsBloc = Provider.of<AccountsBloc>(context, listen: false); _accountsBloc = Provider.of<AccountsBloc>(context, listen: false);
_account = _accountsBloc.activeAccount.value!; _account = _accountsBloc.activeAccount.value!;
_appsBloc = _accountsBloc.activeAppsBloc; _appsBloc = _accountsBloc.activeAppsBloc;
_capabilitiesBloc = _accountsBloc.activeCapabilitiesBloc;
_appsBloc.openNotifications.listen((final _) async {
final notificationsAppImplementation = _appsBloc.notificationsAppImplementation.valueOrNull;
if (notificationsAppImplementation != null) {
await _openNotifications(
notificationsAppImplementation.data!,
_accountsBloc.accounts.value,
_accountsBloc.activeAccount.value!,
);
}
});
_appsBloc.appVersions.listen((final values) { _appsBloc.appVersions.listen((final values) {
if (values == null || !mounted) { if (values == null || !mounted) {
@ -108,155 +93,18 @@ class _HomePageState extends State<HomePage> {
); );
} }
Future _openNotifications(
final NotificationsAppInterface app,
final List<Account> accounts,
final Account account,
) async {
final page = Scaffold(
resizeToAvoidBottomInset: false,
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.page,
);
await Navigator.of(context).push(
MaterialPageRoute(
builder: (final context) => Provider<NotificationsBlocInterface>(
create: (final context) => app.getBloc(account),
child: page,
),
),
);
}
@override
void dispose() {
drawerScrollController.dispose();
super.dispose();
}
@override @override
Widget build(final BuildContext context) => ResultBuilder<Capabilities>.behaviorSubject( Widget build(final BuildContext context) => ResultBuilder<Iterable<AppImplementation>>.behaviorSubject(
stream: _capabilitiesBloc.capabilities,
builder: (final context, final capabilities) => ResultBuilder<Iterable<AppImplementation>>.behaviorSubject(
stream: _appsBloc.appImplementations, stream: _appsBloc.appImplementations,
builder: (final context, final appImplementations) => builder: (final context, final appImplementations) => StreamBuilder<String?>(
ResultBuilder<NotificationsAppInterface?>.behaviorSubject(
stream: _appsBloc.notificationsAppImplementation,
builder: (final context, final notificationsAppImplementation) => StreamBuilder<String?>(
stream: _appsBloc.activeAppID, stream: _appsBloc.activeAppID,
builder: (final context, final activeAppIDSnapshot) => StreamBuilder<List<Account>>( builder: (final context, final activeAppIDSnapshot) => OptionBuilder<NavigationMode>(
stream: _accountsBloc.accounts,
builder: (final context, final accountsSnapshot) => OptionBuilder<NavigationMode>(
option: _globalOptions.navigationMode, option: _globalOptions.navigationMode,
builder: (final context, final navigationMode) { builder: (final context, final navigationMode) {
final accounts = accountsSnapshot.data;
final account = accounts?.find(_account.id);
if (accounts == null || account == null) {
return const Scaffold();
}
final drawerAlwaysVisible = navigationMode == NavigationMode.drawerAlwaysVisible; final drawerAlwaysVisible = navigationMode == NavigationMode.drawerAlwaysVisible;
const drawer = NeonDrawer(); const drawer = NeonDrawer();
final appBar = AppBar( const appBar = NeonAppBar();
automaticallyImplyLeading: !drawerAlwaysVisible,
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
if (appImplementations.data != null && activeAppIDSnapshot.hasData) ...[
Flexible(
child: Text(
appImplementations.data!.find(activeAppIDSnapshot.data!)!.name(context),
),
),
],
if (appImplementations.hasError) ...[
const SizedBox(
width: 8,
),
NeonException(
appImplementations.error,
onRetry: _appsBloc.refresh,
onlyIcon: true,
),
],
if (appImplementations.isLoading) ...[
const SizedBox(
width: 8,
),
Expanded(
child: NeonLinearProgressIndicator(
color: Theme.of(context).appBarTheme.foregroundColor,
),
),
],
],
),
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(notificationsAppImplementation.data!.getBloc(account)),
builder: (final context, final unreadCounterSnapshot) {
final unreadCount = unreadCounterSnapshot.data ?? 0;
return IconButton(
key: Key('app-${notificationsAppImplementation.data!.id}'),
onPressed: () async {
await _openNotifications(
notificationsAppImplementation.data!,
accounts,
account,
);
},
tooltip: AppLocalizations.of(context)
.appImplementationName(notificationsAppImplementation.data!.id),
icon: NeonAppImplementationIcon(
appImplementation: notificationsAppImplementation.data!,
unreadCount: unreadCount,
color: unreadCount > 0
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onBackground,
size: const Size.square(kAvatarSize * 2 / 3),
),
);
},
),
],
IconButton(
onPressed: () {
AccountSettingsRoute(accountid: account.id).go(context);
},
tooltip: AppLocalizations.of(context).settingsAccount,
icon: NeonUserAvatar(
account: account,
),
),
],
);
Widget body = Builder( Widget body = Builder(
builder: (final context) => Column( builder: (final context) => Column(
@ -320,8 +168,5 @@ class _HomePageState extends State<HomePage> {
}, },
), ),
), ),
),
),
),
); );
} }

196
packages/neon/neon/lib/src/widgets/app_bar.dart

@ -0,0 +1,196 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
import 'package:neon/l10n/localizations.dart';
import 'package:neon/neon.dart';
import 'package:neon/src/router.dart';
import 'package:provider/provider.dart';
@internal
class NeonAppBar extends StatelessWidget implements PreferredSizeWidget {
const NeonAppBar({super.key});
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
@override
Widget build(final BuildContext context) {
final accountsBloc = Provider.of<AccountsBloc>(context, listen: false);
final accounts = accountsBloc.accounts.value;
final account = accountsBloc.activeAccount.value!;
final appsBloc = accountsBloc.activeAppsBloc;
return ResultBuilder<Iterable<AppImplementation>>.behaviorSubject(
stream: appsBloc.appImplementations,
builder: (final context, final appImplementations) => StreamBuilder<String?>(
stream: appsBloc.activeAppID,
builder: (final context, final activeAppIDSnapshot) => AppBar(
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
if (appImplementations.hasData && activeAppIDSnapshot.hasData) ...[
Flexible(
child: Text(
appImplementations.requireData.find(activeAppIDSnapshot.data!)!.name(context),
),
),
],
if (appImplementations.hasError) ...[
const SizedBox(
width: 8,
),
NeonException(
appImplementations.error,
onRetry: appsBloc.refresh,
onlyIcon: true,
),
],
if (appImplementations.isLoading) ...[
const SizedBox(
width: 8,
),
Expanded(
child: NeonLinearProgressIndicator(
color: Theme.of(context).appBarTheme.foregroundColor,
),
),
],
],
),
if (accounts.length > 1) ...[
Text(
account.client.humanReadableID,
style: Theme.of(context).textTheme.bodySmall,
),
],
],
),
actions: [
const NotificationIconButton(),
IconButton(
onPressed: () {
AccountSettingsRoute(accountid: account.id).go(context);
},
tooltip: AppLocalizations.of(context).settingsAccount,
icon: NeonUserAvatar(
account: account,
),
),
],
),
),
);
}
}
@internal
class NotificationIconButton extends StatefulWidget {
const NotificationIconButton({
super.key,
});
@override
State<NotificationIconButton> createState() => _NotificationIconButtonState();
}
class _NotificationIconButtonState extends State<NotificationIconButton> {
late AccountsBloc _accountsBloc;
late AppsBloc _appsBloc;
late List<Account> _accounts;
late Account _account;
late StreamSubscription notificationSubscription;
@override
void initState() {
super.initState();
_accountsBloc = Provider.of<AccountsBloc>(context, listen: false);
_appsBloc = _accountsBloc.activeAppsBloc;
_accounts = _accountsBloc.accounts.value;
_account = _accountsBloc.activeAccount.value!;
notificationSubscription = _appsBloc.openNotifications.listen((final _) async {
final notificationsAppImplementation = _appsBloc.notificationsAppImplementation.valueOrNull;
if (notificationsAppImplementation != null && notificationsAppImplementation.hasData) {
await _openNotifications(notificationsAppImplementation.data!);
}
});
}
@override
void dispose() {
unawaited(notificationSubscription.cancel());
super.dispose();
}
// TODO: migrate to go_router with a separate page
Future<void> _openNotifications(
final NotificationsAppInterface app,
) async {
final page = Scaffold(
resizeToAvoidBottomInset: false,
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.page,
);
await Navigator.of(context).push(
MaterialPageRoute(
builder: (final context) => Provider<NotificationsBlocInterface>(
create: (final context) => app.getBloc(_account),
child: page,
),
),
);
}
@override
Widget build(final BuildContext context) => ResultBuilder<NotificationsAppInterface?>.behaviorSubject(
stream: _appsBloc.notificationsAppImplementation,
builder: (final context, final notificationsAppImplementation) {
if (!notificationsAppImplementation.hasData) {
return const SizedBox.shrink();
}
final notificationsImplementationData = notificationsAppImplementation.data!;
final notificationBloc = notificationsImplementationData.getBloc(_account);
return StreamBuilder<int>(
stream: notificationsImplementationData.getUnreadCounter(notificationBloc),
builder: (final context, final unreadCounterSnapshot) {
final unreadCount = unreadCounterSnapshot.data ?? 0;
return IconButton(
key: Key('app-${notificationsImplementationData.id}'),
onPressed: () async {
await _openNotifications(notificationsImplementationData);
},
tooltip: AppLocalizations.of(context).appImplementationName(notificationsImplementationData.id),
icon: NeonAppImplementationIcon(
appImplementation: notificationsImplementationData,
unreadCount: unreadCount,
color: unreadCount > 0
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onBackground,
size: const Size.square(kAvatarSize * 2 / 3),
),
);
},
);
},
);
}
Loading…
Cancel
Save