Browse Source

Merge pull request #456 from Leptopoda/feature/account_settings

Feature/account switcher
pull/473/head
Nikolas Rimikis 1 year ago committed by GitHub
parent
commit
243397dbfa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      packages/app/pubspec.lock
  2. 1
      packages/neon/neon/lib/l10n/en.arb
  3. 6
      packages/neon/neon/lib/l10n/localizations.dart
  4. 3
      packages/neon/neon/lib/l10n/localizations_en.dart
  5. 46
      packages/neon/neon/lib/src/pages/account_settings.dart
  6. 26
      packages/neon/neon/lib/src/pages/nextcloud_app_settings.dart
  7. 89
      packages/neon/neon/lib/src/pages/settings.dart
  8. 7
      packages/neon/neon/lib/src/router.dart
  9. 50
      packages/neon/neon/lib/src/router.g.dart
  10. 25
      packages/neon/neon/lib/src/settings/widgets/settings_list.dart
  11. 3
      packages/neon/neon/lib/src/utils/global_popups.dart
  12. 92
      packages/neon/neon/lib/src/widgets/account_switcher.dart
  13. 1
      packages/neon/neon/lib/src/widgets/account_tile.dart
  14. 16
      packages/neon/neon/lib/src/widgets/app_bar.dart
  15. 104
      packages/neon/neon/lib/src/widgets/drawer.dart
  16. 1
      packages/neon/neon/pubspec.yaml

8
packages/app/pubspec.lock

@ -1037,6 +1037,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.1.6"
scrollable_positioned_list:
dependency: transitive
description:
name: scrollable_positioned_list
sha256: "1b54d5f1329a1e263269abc9e2543d90806131aa14fe7c6062a8054d57249287"
url: "https://pub.dev"
source: hosted
version: "0.3.8"
share_plus:
dependency: transitive
description:

1
packages/neon/neon/lib/l10n/en.arb

@ -82,6 +82,7 @@
"settings": "Settings",
"settingsApps": "Apps",
"settingsAccount": "Account",
"settingsAccountManage": "Manage accounts",
"settingsExport": "Export settings",
"settingsImport": "Import settings",
"settingsImportWrongFileExtension": "Settings import has wrong file extension (has to be .json.base64)",

6
packages/neon/neon/lib/l10n/localizations.dart

@ -341,6 +341,12 @@ abstract class AppLocalizations {
/// **'Account'**
String get settingsAccount;
/// No description provided for @settingsAccountManage.
///
/// In en, this message translates to:
/// **'Manage accounts'**
String get settingsAccountManage;
/// No description provided for @settingsExport.
///
/// In en, this message translates to:

3
packages/neon/neon/lib/l10n/localizations_en.dart

@ -160,6 +160,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get settingsAccount => 'Account';
@override
String get settingsAccountManage => 'Manage accounts';
@override
String get settingsExport => 'Export settings';

46
packages/neon/neon/lib/src/pages/account_settings.dart

@ -10,13 +10,14 @@ import 'package:neon/src/settings/widgets/custom_settings_tile.dart';
import 'package:neon/src/settings/widgets/dropdown_button_settings_tile.dart';
import 'package:neon/src/settings/widgets/settings_category.dart';
import 'package:neon/src/settings/widgets/settings_list.dart';
import 'package:neon/src/theme/dialog.dart';
import 'package:neon/src/utils/confirmation_dialog.dart';
import 'package:neon/src/widgets/exception.dart';
import 'package:neon/src/widgets/linear_progress_indicator.dart';
import 'package:nextcloud/nextcloud.dart';
class AccountSettingsPage extends StatelessWidget {
AccountSettingsPage({
const AccountSettingsPage({
required this.bloc,
required this.account,
super.key,
@ -25,15 +26,14 @@ class AccountSettingsPage extends StatelessWidget {
final AccountsBloc bloc;
final Account account;
late final _options = bloc.getOptionsFor(account);
late final _userDetailsBloc = bloc.getUserDetailsBlocFor(account);
late final _name = account.client.humanReadableID;
@override
Widget build(final BuildContext context) => Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: Text(_name),
Widget build(final BuildContext context) {
final options = bloc.getOptionsFor(account);
final userDetailsBloc = bloc.getUserDetailsBlocFor(account);
final name = account.client.humanReadableID;
final appBar = AppBar(
title: Text(name),
actions: [
IconButton(
onPressed: () async {
@ -64,18 +64,19 @@ class AccountSettingsPage extends StatelessWidget {
onPressed: () async {
if (await showConfirmationDialog(
context,
AppLocalizations.of(context).settingsResetForConfirmation(_name),
AppLocalizations.of(context).settingsResetForConfirmation(name),
)) {
await _options.reset();
await options.reset();
}
},
tooltip: AppLocalizations.of(context).settingsResetFor(_name),
tooltip: AppLocalizations.of(context).settingsResetFor(name),
icon: Icon(MdiIcons.cogRefresh),
),
],
),
body: ResultBuilder<ProvisioningApiUserDetails>.behaviorSubject(
stream: _userDetailsBloc.userDetails,
);
final body = ResultBuilder<ProvisioningApiUserDetails>.behaviorSubject(
stream: userDetailsBloc.userDetails,
builder: (final context, final userDetails) => SettingsList(
categories: [
SettingsCategory(
@ -103,7 +104,7 @@ class AccountSettingsPage extends StatelessWidget {
],
NeonException(
userDetails.error,
onRetry: _userDetailsBloc.refresh,
onRetry: userDetailsBloc.refresh,
),
NeonLinearProgressIndicator(
visible: userDetails.isLoading,
@ -117,12 +118,23 @@ class AccountSettingsPage extends StatelessWidget {
title: Text(AppLocalizations.of(context).optionsCategoryGeneral),
tiles: [
DropdownButtonSettingsTile(
option: _options.initialApp,
option: options.initialApp,
),
],
),
],
),
);
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: appBar,
body: Center(
child: ConstrainedBox(
constraints: NeonDialogTheme.of(context).constraints,
child: body,
),
),
);
}
}

26
packages/neon/neon/lib/src/pages/nextcloud_app_settings.dart

@ -8,6 +8,7 @@ import 'package:neon/src/settings/widgets/checkbox_settings_tile.dart';
import 'package:neon/src/settings/widgets/dropdown_button_settings_tile.dart';
import 'package:neon/src/settings/widgets/settings_category.dart';
import 'package:neon/src/settings/widgets/settings_list.dart';
import 'package:neon/src/theme/dialog.dart';
import 'package:neon/src/utils/confirmation_dialog.dart';
class NextcloudAppSettingsPage extends StatelessWidget {
@ -19,9 +20,8 @@ class NextcloudAppSettingsPage extends StatelessWidget {
final AppImplementation appImplementation;
@override
Widget build(final BuildContext context) => Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
Widget build(final BuildContext context) {
final appBar = AppBar(
title: Text(appImplementation.name(context)),
actions: [
IconButton(
@ -37,13 +37,12 @@ class NextcloudAppSettingsPage extends StatelessWidget {
icon: Icon(MdiIcons.cogRefresh),
),
],
),
body: SettingsList(
);
final body = SettingsList(
categories: [
for (final category in [...appImplementation.options.categories, null]) ...[
if (appImplementation.options.options
.where((final option) => option.category == category)
.isNotEmpty) ...[
if (appImplementation.options.options.where((final option) => option.category == category).isNotEmpty) ...[
SettingsCategory(
title: Text(
category != null ? category.name(context) : AppLocalizations.of(context).optionsCategoryOther,
@ -66,6 +65,17 @@ class NextcloudAppSettingsPage extends StatelessWidget {
],
],
],
);
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: appBar,
body: Center(
child: ConstrainedBox(
constraints: NeonDialogTheme.of(context).constraints,
child: body,
),
),
);
}
}

89
packages/neon/neon/lib/src/pages/settings.dart

@ -19,6 +19,7 @@ import 'package:neon/src/settings/widgets/settings_list.dart';
import 'package:neon/src/settings/widgets/settings_tile.dart';
import 'package:neon/src/settings/widgets/text_settings_tile.dart';
import 'package:neon/src/theme/branding.dart';
import 'package:neon/src/theme/dialog.dart';
import 'package:neon/src/utils/confirmation_dialog.dart';
import 'package:neon/src/utils/global_options.dart';
import 'package:neon/src/utils/save_file.dart';
@ -27,11 +28,25 @@ import 'package:neon/src/widgets/exception.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:provider/provider.dart';
enum SettingsCageories {
apps,
theme,
navigation,
pushNotifications,
startup,
systemTray,
accounts,
other,
}
class SettingsPage extends StatefulWidget {
const SettingsPage({
this.initialCategory,
super.key,
});
final SettingsCageories? initialCategory;
@override
State<SettingsPage> createState() => _SettingsPageState();
}
@ -42,9 +57,8 @@ class _SettingsPageState extends State<SettingsPage> {
final globalOptions = Provider.of<GlobalOptions>(context);
final accountsBloc = Provider.of<AccountsBloc>(context, listen: false);
final appImplementations = Provider.of<Iterable<AppImplementation>>(context);
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
final appBar = AppBar(
title: Text(AppLocalizations.of(context).settings),
actions: [
IconButton(
@ -65,41 +79,28 @@ class _SettingsPageState extends State<SettingsPage> {
icon: Icon(MdiIcons.cogRefresh),
),
],
),
body: StreamBuilder<List<Account>>(
);
final body = StreamBuilder<List<Account>>(
stream: accountsBloc.accounts,
initialData: accountsBloc.accounts.valueOrNull,
builder: (
final context,
final accountsSnapshot,
) {
final settingsExportHelper = SettingsExportHelper(
globalOptions: globalOptions,
appImplementations: appImplementations,
accountSpecificOptions: {
if (accountsSnapshot.hasData) ...{
for (final account in accountsSnapshot.requireData) ...{
account: accountsBloc.getOptionsFor(account).options,
},
},
},
);
final platform = Provider.of<NeonPlatform>(context, listen: false);
return StreamBuilder<Account?>(
stream: accountsBloc.activeAccount,
builder: (
final context,
final activeAccountSnapshot,
) =>
StreamBuilder<bool>(
return StreamBuilder<bool>(
stream: globalOptions.pushNotificationsEnabled.enabled,
initialData: globalOptions.pushNotificationsEnabled.enabled.valueOrNull,
builder: (
final context,
final pushNotificationsEnabledEnabledSnapshot,
) =>
SettingsList(
initialCategory: widget.initialCategory?.name,
categories: [
SettingsCategory(
title: Text(AppLocalizations.of(context).settingsApps),
key: ValueKey(SettingsCageories.apps.name),
tiles: <SettingsTile>[
for (final appImplementation in appImplementations) ...[
if (appImplementation.options.options.isNotEmpty) ...[
@ -116,6 +117,7 @@ class _SettingsPageState extends State<SettingsPage> {
),
SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryTheme),
key: ValueKey(SettingsCageories.theme.name),
tiles: [
DropdownButtonSettingsTile(
option: globalOptions.themeMode,
@ -130,6 +132,7 @@ class _SettingsPageState extends State<SettingsPage> {
),
SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryNavigation),
key: ValueKey(SettingsCageories.navigation.name),
tiles: [
DropdownButtonSettingsTile(
option: globalOptions.navigationMode,
@ -139,6 +142,7 @@ class _SettingsPageState extends State<SettingsPage> {
if (platform.canUsePushNotifications) ...[
SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryPushNotifications),
key: ValueKey(SettingsCageories.pushNotifications.name),
tiles: [
if (pushNotificationsEnabledEnabledSnapshot.hasData &&
!pushNotificationsEnabledEnabledSnapshot.requireData) ...[
@ -163,6 +167,7 @@ class _SettingsPageState extends State<SettingsPage> {
if (platform.canUseWindowManager) ...[
SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryStartup),
key: ValueKey(SettingsCageories.startup.name),
tiles: [
CheckBoxSettingsTile(
option: globalOptions.startupMinimized,
@ -176,6 +181,7 @@ class _SettingsPageState extends State<SettingsPage> {
if (platform.canUseWindowManager && platform.canUseSystemTray) ...[
SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategorySystemTray),
key: ValueKey(SettingsCageories.systemTray.name),
tiles: [
CheckBoxSettingsTile(
option: globalOptions.systemTrayEnabled,
@ -186,9 +192,9 @@ class _SettingsPageState extends State<SettingsPage> {
],
),
],
if (accountsSnapshot.hasData) ...[
SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryAccounts),
key: ValueKey(SettingsCageories.accounts.name),
tiles: [
if (accountsSnapshot.requireData.length > 1) ...[
CheckBoxSettingsTile(
@ -215,9 +221,9 @@ class _SettingsPageState extends State<SettingsPage> {
)
],
),
],
SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryOther),
key: ValueKey(SettingsCageories.other.name),
tiles: <SettingsTile>[
CustomSettingsTile(
leading: Icon(
@ -243,6 +249,8 @@ class _SettingsPageState extends State<SettingsPage> {
),
title: Text(AppLocalizations.of(context).settingsExport),
onTap: () async {
final settingsExportHelper = _buildSettingsExportHelper(context);
try {
final fileName =
'nextcloud-neon-settings-${DateTime.now().millisecondsSinceEpoch ~/ 1000}.json.base64';
@ -268,6 +276,8 @@ class _SettingsPageState extends State<SettingsPage> {
),
title: Text(AppLocalizations.of(context).settingsImport),
onTap: () async {
final settingsExportHelper = _buildSettingsExportHelper(context);
try {
final result = await FilePicker.platform.pickFiles(
withData: true,
@ -287,8 +297,8 @@ class _SettingsPageState extends State<SettingsPage> {
return;
}
final data =
json.decode(utf8.decode(base64.decode(utf8.decode(result.files.single.bytes!))));
final data = json.decode(utf8.decode(base64.decode(utf8.decode(result.files.single.bytes!))));
await settingsExportHelper.applyFromJson(data as Map<String, dynamic>);
} catch (e, s) {
debugPrint(e.toString());
@ -301,9 +311,32 @@ class _SettingsPageState extends State<SettingsPage> {
),
],
),
),
);
},
);
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: appBar,
body: Center(
child: ConstrainedBox(
constraints: NeonDialogTheme.of(context).constraints,
child: body,
),
),
);
}
SettingsExportHelper _buildSettingsExportHelper(final BuildContext context) {
final globalOptions = Provider.of<GlobalOptions>(context);
final accountsBloc = Provider.of<AccountsBloc>(context, listen: false);
final appImplementations = Provider.of<Iterable<AppImplementation>>(context);
return SettingsExportHelper(
globalOptions: globalOptions,
appImplementations: appImplementations,
accountSpecificOptions: accountsBloc.accounts.value.asMap().map(
(final _, final account) => MapEntry(account, accountsBloc.getOptionsFor(account).options),
),
);
}

7
packages/neon/neon/lib/src/router.dart

@ -367,8 +367,11 @@ class NextcloudAppSettingsRoute extends GoRouteData {
@immutable
class SettingsRoute extends GoRouteData {
const SettingsRoute();
const SettingsRoute({this.initialCategory});
/// The initial category to show.
final SettingsCageories? initialCategory;
@override
Widget build(final BuildContext context, final GoRouterState state) => const SettingsPage();
Widget build(final BuildContext context, final GoRouterState state) => SettingsPage(initialCategory: initialCategory);
}

50
packages/neon/neon/lib/src/router.g.dart

@ -71,13 +71,21 @@ extension $HomeRouteExtension on HomeRoute {
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}
extension $SettingsRouteExtension on SettingsRoute {
static SettingsRoute _fromState(GoRouterState state) => const SettingsRoute();
static SettingsRoute _fromState(GoRouterState state) => SettingsRoute(
initialCategory:
_$convertMapValue('initial-category', state.queryParameters, _$SettingsCageoriesEnumMap._$fromName),
);
String get location => GoRouteData.$location(
'/settings',
queryParams: {
if (initialCategory != null) 'initial-category': _$SettingsCageoriesEnumMap[initialCategory!],
},
);
void go(BuildContext context) => context.go(location);
@ -85,8 +93,21 @@ extension $SettingsRouteExtension on SettingsRoute {
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}
const _$SettingsCageoriesEnumMap = {
SettingsCageories.apps: 'apps',
SettingsCageories.theme: 'theme',
SettingsCageories.navigation: 'navigation',
SettingsCageories.pushNotifications: 'push-notifications',
SettingsCageories.startup: 'startup',
SettingsCageories.systemTray: 'system-tray',
SettingsCageories.accounts: 'accounts',
SettingsCageories.other: 'other',
};
extension $NextcloudAppSettingsRouteExtension on NextcloudAppSettingsRoute {
static NextcloudAppSettingsRoute _fromState(GoRouterState state) => NextcloudAppSettingsRoute(
appid: state.pathParameters['appid']!,
@ -101,6 +122,8 @@ extension $NextcloudAppSettingsRouteExtension on NextcloudAppSettingsRoute {
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}
extension $_AddAccountRouteExtension on _AddAccountRoute {
@ -115,6 +138,8 @@ extension $_AddAccountRouteExtension on _AddAccountRoute {
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}
extension $_AddAccountFlowRouteExtension on _AddAccountFlowRoute {
@ -206,6 +231,21 @@ extension $AccountSettingsRouteExtension on AccountSettingsRoute {
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}
T? _$convertMapValue<T>(
String key,
Map<String, String> map,
T Function(String) converter,
) {
final value = map[key];
return value == null ? null : converter(value);
}
extension<T extends Enum> on Map<T, String> {
T _$fromName(String value) => entries.singleWhere((element) => element.value == value).key;
}
RouteBase get $loginRoute => GoRouteData.$route(
@ -244,6 +284,8 @@ extension $LoginRouteExtension on LoginRoute {
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}
extension $LoginFlowRouteExtension on LoginFlowRoute {
@ -277,6 +319,8 @@ extension $LoginQrcodeRouteExtension on LoginQrcodeRoute {
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}
extension $LoginCheckServerStatusRouteExtension on LoginCheckServerStatusRoute {
@ -296,6 +340,8 @@ extension $LoginCheckServerStatusRouteExtension on LoginCheckServerStatusRoute {
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}
extension $LoginCheckAccountRouteExtension on LoginCheckAccountRoute {
@ -319,4 +365,6 @@ extension $LoginCheckAccountRouteExtension on LoginCheckAccountRoute {
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}

25
packages/neon/neon/lib/src/settings/widgets/settings_list.dart

@ -1,23 +1,36 @@
import 'package:flutter/material.dart';
import 'package:intersperse/intersperse.dart';
import 'package:meta/meta.dart';
import 'package:neon/src/settings/widgets/settings_category.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
@internal
class SettingsList extends StatelessWidget {
const SettingsList({
required this.categories,
this.initialCategory,
super.key,
});
final List<SettingsCategory> categories;
final String? initialCategory;
int? _getIndex(final String? initialCategory) {
if (initialCategory == null) {
return null;
}
final key = Key(initialCategory);
final index = categories.indexWhere((final category) => category.key == key);
return index != -1 ? index : null;
}
@override
Widget build(final BuildContext context) => Scrollbar(
child: ListView(
primary: true,
Widget build(final BuildContext context) => ScrollablePositionedList.separated(
padding: const EdgeInsets.all(20),
children: categories.cast<Widget>().intersperse(const Divider()).toList(),
),
itemCount: categories.length,
initialScrollIndex: _getIndex(initialCategory) ?? 0,
itemBuilder: (final context, final index) => categories[index],
separatorBuilder: (final context, final index) => const Divider(),
);
}

3
packages/neon/neon/lib/src/utils/global_popups.dart

@ -3,6 +3,7 @@ import 'package:meta/meta.dart';
import 'package:neon/l10n/localizations.dart';
import 'package:neon/src/blocs/first_launch.dart';
import 'package:neon/src/blocs/next_push.dart';
import 'package:neon/src/pages/settings.dart';
import 'package:neon/src/router.dart';
import 'package:neon/src/utils/global_options.dart';
import 'package:provider/provider.dart';
@ -43,7 +44,7 @@ class GlobalPopups {
action: SnackBarAction(
label: AppLocalizations.of(context).settings,
onPressed: () {
const SettingsRoute().go(context);
const SettingsRoute(initialCategory: SettingsCageories.pushNotifications).go(context);
},
),
),

92
packages/neon/neon/lib/src/widgets/account_switcher.dart

@ -0,0 +1,92 @@
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
import 'package:neon/l10n/localizations.dart';
import 'package:neon/src/blocs/accounts.dart';
import 'package:neon/src/pages/settings.dart';
import 'package:neon/src/router.dart';
import 'package:neon/src/theme/dialog.dart';
import 'package:neon/src/widgets/account_tile.dart';
import 'package:neon/src/widgets/user_avatar.dart';
import 'package:provider/provider.dart';
@internal
class AccountSwitcherButton extends StatelessWidget {
const AccountSwitcherButton({
super.key,
});
Future<void> _onPressed(final BuildContext context) async {
final accountsBloc = Provider.of<AccountsBloc>(context, listen: false);
final accounts = accountsBloc.accounts.value;
final aa = accountsBloc.activeAccount.value!;
await showDialog(
context: context,
builder: (final context) {
final body = Column(
children: [
NeonAccountTile(
account: aa,
trailing: const Icon(Icons.check_circle),
onTap: Navigator.of(context).pop,
),
const Divider(),
if (accounts.length > 1)
Builder(
builder: (final context) {
final inactiveAccounts = List.of(accounts)..removeWhere((final account) => (account.id == aa.id));
final tiles = inactiveAccounts.map(
(final account) => NeonAccountTile(
account: account,
onTap: () {
accountsBloc.setActiveAccount(account);
Navigator.of(context).pop();
},
),
);
return SingleChildScrollView(
child: ListBody(
children: tiles.toList(),
),
);
},
),
ListTile(
leading: const Icon(Icons.settings),
title: Text(AppLocalizations.of(context).settingsAccountManage),
onTap: () {
Navigator.of(context).pop();
const SettingsRoute(initialCategory: SettingsCageories.accounts).push(context);
},
)
],
);
return Dialog(
child: IntrinsicHeight(
child: Container(
padding: const EdgeInsets.all(24),
constraints: NeonDialogTheme.of(context).constraints,
child: body,
),
),
);
},
);
}
@override
Widget build(final BuildContext context) {
final accountsBloc = Provider.of<AccountsBloc>(context, listen: false);
final account = accountsBloc.activeAccount.value!;
return IconButton(
onPressed: () async => _onPressed(context),
tooltip: AppLocalizations.of(context).settingsAccount,
icon: NeonUserAvatar(
account: account,
),
);
}
}

1
packages/neon/neon/lib/src/widgets/account_tile.dart

@ -49,6 +49,7 @@ class NeonAccountTile extends StatelessWidget {
account: account,
showStatus: showStatus,
),
trailing: trailing,
title: ResultBuilder<ProvisioningApiUserDetails>.behaviorSubject(
stream: userDetailsBloc.userDetails,
builder: (final context, final userDetails) => Row(

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

@ -9,7 +9,7 @@ import 'package:neon/src/blocs/apps.dart';
import 'package:neon/src/models/account.dart';
import 'package:neon/src/models/app_implementation.dart';
import 'package:neon/src/models/notifications_interface.dart';
import 'package:neon/src/router.dart';
import 'package:neon/src/widgets/account_switcher.dart';
import 'package:neon/src/widgets/app_implementation_icon.dart';
import 'package:neon/src/widgets/exception.dart';
import 'package:neon/src/widgets/linear_progress_indicator.dart';
@ -77,17 +77,9 @@ class NeonAppBar extends StatelessWidget implements PreferredSizeWidget {
],
],
),
actions: [
const NotificationIconButton(),
IconButton(
onPressed: () {
AccountSettingsRoute(accountid: account.id).go(context);
},
tooltip: AppLocalizations.of(context).settingsAccount,
icon: NeonUserAvatar(
account: account,
),
),
actions: const [
NotificationIconButton(),
AccountSwitcherButton(),
],
),
),

104
packages/neon/neon/lib/src/widgets/drawer.dart

@ -7,10 +7,8 @@ import 'package:neon/src/bloc/result_builder.dart';
import 'package:neon/src/blocs/accounts.dart';
import 'package:neon/src/blocs/apps.dart';
import 'package:neon/src/blocs/capabilities.dart';
import 'package:neon/src/models/account.dart';
import 'package:neon/src/models/app_implementation.dart';
import 'package:neon/src/router.dart';
import 'package:neon/src/widgets/account_tile.dart';
import 'package:neon/src/widgets/cached_image.dart';
import 'package:neon/src/widgets/drawer_destination.dart';
import 'package:neon/src/widgets/exception.dart';
@ -54,13 +52,12 @@ class _NeonDrawer extends StatefulWidget {
State<_NeonDrawer> createState() => __NeonDrawerState();
}
class __NeonDrawerState extends State<_NeonDrawer> with SingleTickerProviderStateMixin {
late TabController _tabController;
class __NeonDrawerState extends State<_NeonDrawer> {
late AccountsBloc _accountsBloc;
late AppsBloc _appsBloc;
late List<AppImplementation> _apps;
int _activeApp = 0;
late int _activeApp;
@override
void initState() {
@ -71,17 +68,6 @@ class __NeonDrawerState extends State<_NeonDrawer> with SingleTickerProviderStat
_apps = widget.apps.toList();
_activeApp = _apps.indexWhere((final app) => app.id == _appsBloc.activeApp.valueOrNull?.id);
_tabController = TabController(
vsync: this,
length: widget.apps.length,
);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
void onAppChange(final int index) {
@ -135,85 +121,57 @@ class NeonDrawerHeader extends StatelessWidget {
final accountsBloc = Provider.of<AccountsBloc>(context, listen: false);
final capabilitiesBloc = accountsBloc.activeCapabilitiesBloc;
final accountSelecor = StreamBuilder<List<Account>>(
stream: accountsBloc.accounts,
builder: (final context, final accountsSnapshot) {
final accounts = accountsSnapshot.data;
if (accounts == null || accounts.length <= 1) {
return const SizedBox.shrink();
}
final items = accounts.map((final account) {
final child = NeonAccountTile(
account: account,
dense: true,
textColor: Theme.of(context).appBarTheme.foregroundColor,
final branding = ResultBuilder<Capabilities>.behaviorSubject(
stream: capabilitiesBloc.capabilities,
builder: (final context, final capabilities) {
if (!capabilities.hasData) {
return NeonLinearProgressIndicator(
visible: capabilities.isLoading,
);
}
return DropdownMenuItem(
value: account,
child: child,
if (capabilities.hasError) {
return NeonException(
capabilities.error,
onRetry: capabilitiesBloc.refresh,
);
}).toList();
return DropdownButtonHideUnderline(
child: DropdownButton(
isExpanded: true,
dropdownColor: Theme.of(context).colorScheme.primary,
iconEnabledColor: Theme.of(context).colorScheme.onBackground,
value: accountsBloc.activeAccount.value,
items: items,
onChanged: (final account) {
if (account == null) {
return;
}
accountsBloc.setActiveAccount(account);
},
),
);
},
);
final theme = capabilities.requireData.capabilities.theming;
return ResultBuilder<Capabilities>.behaviorSubject(
stream: capabilitiesBloc.capabilities,
builder: (final context, final capabilities) => DrawerHeader(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
),
child: Column(
if (theme == null) {
return const SizedBox();
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (capabilities.hasData) ...[
if (capabilities.requireData.capabilities.theming?.name != null) ...[
if (theme.name != null) ...[
Text(
capabilities.requireData.capabilities.theming!.name!,
theme.name!,
style: DefaultTextStyle.of(context).style.copyWith(
color: Theme.of(context).appBarTheme.foregroundColor,
),
),
],
if (capabilities.requireData.capabilities.theming?.logo != null) ...[
if (theme.logo != null) ...[
Flexible(
child: NeonCachedImage.url(
url: capabilities.requireData.capabilities.theming!.logo!,
url: theme.logo!,
),
),
],
] else ...[
NeonException(
capabilities.error,
onRetry: capabilitiesBloc.refresh,
),
NeonLinearProgressIndicator(
visible: capabilities.isLoading,
),
],
accountSelecor,
],
);
},
);
return DrawerHeader(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
),
),
child: branding,
);
}
}

1
packages/neon/neon/pubspec.yaml

@ -40,6 +40,7 @@ dependencies:
provider: ^6.0.5
quick_actions: ^1.0.3
rxdart: ^0.27.7
scrollable_positioned_list: ^0.3.8
shared_preferences: ^2.1.1
sort_box:
git:

Loading…
Cancel
Save