Browse Source

refactor(neon,neon_files,neon_news,neon_notes,neon_notifications,app): Use global instance of NeonPlatform

Signed-off-by: jld3103 <jld3103yt@gmail.com>
pull/495/head
jld3103 1 year ago
parent
commit
7253352a9f
No known key found for this signature in database
GPG Key ID: 9062417B9E8EB7B3
  1. 10
      packages/app/lib/apps.dart
  2. 13
      packages/neon/neon/lib/neon.dart
  3. 22
      packages/neon/neon/lib/src/app.dart
  4. 4
      packages/neon/neon/lib/src/blocs/accounts.dart
  5. 4
      packages/neon/neon/lib/src/blocs/push_notifications.dart
  6. 12
      packages/neon/neon/lib/src/blocs/user_statuses.dart
  7. 3
      packages/neon/neon/lib/src/models/app_implementation.dart
  8. 5
      packages/neon/neon/lib/src/models/notifications_interface.dart
  9. 4
      packages/neon/neon/lib/src/pages/login.dart
  10. 392
      packages/neon/neon/lib/src/pages/settings.dart
  11. 46
      packages/neon/neon/lib/src/platform/platform.dart
  12. 3
      packages/neon/neon/lib/src/utils/global_popups.dart
  13. 9
      packages/neon/neon/lib/src/utils/request_manager.dart
  14. 6
      packages/neon/neon_files/lib/blocs/files.dart
  15. 2
      packages/neon/neon_files/lib/dialogs/choose_create.dart
  16. 3
      packages/neon/neon_files/lib/neon_files.dart
  17. 4
      packages/neon/neon_news/lib/neon_news.dart
  18. 6
      packages/neon/neon_news/lib/options.dart
  19. 2
      packages/neon/neon_news/lib/widgets/articles_view.dart
  20. 2
      packages/neon/neon_notes/lib/neon_notes.dart
  21. 2
      packages/neon/neon_notifications/lib/neon_notifications.dart

10
packages/app/lib/apps.dart

@ -1,5 +1,4 @@
import 'package:neon/models.dart'; import 'package:neon/models.dart';
import 'package:neon/platform.dart';
import 'package:neon_files/neon_files.dart'; import 'package:neon_files/neon_files.dart';
import 'package:neon_news/neon_news.dart'; import 'package:neon_news/neon_news.dart';
import 'package:neon_notes/neon_notes.dart'; import 'package:neon_notes/neon_notes.dart';
@ -8,11 +7,10 @@ import 'package:shared_preferences/shared_preferences.dart';
List<AppImplementation> getAppImplementations( List<AppImplementation> getAppImplementations(
final SharedPreferences sharedPreferences, final SharedPreferences sharedPreferences,
final NeonPlatform platform,
) => ) =>
[ [
FilesApp(sharedPreferences, platform), FilesApp(sharedPreferences),
NewsApp(sharedPreferences, platform), NewsApp(sharedPreferences),
NotesApp(sharedPreferences, platform), NotesApp(sharedPreferences),
NotificationsApp(sharedPreferences, platform), NotificationsApp(sharedPreferences),
]; ];

13
packages/neon/neon/lib/neon.dart

@ -9,7 +9,6 @@ import 'package:neon/src/blocs/next_push.dart';
import 'package:neon/src/blocs/push_notifications.dart'; import 'package:neon/src/blocs/push_notifications.dart';
import 'package:neon/src/models/account.dart'; import 'package:neon/src/models/account.dart';
import 'package:neon/src/models/app_implementation.dart'; import 'package:neon/src/models/app_implementation.dart';
import 'package:neon/src/platform/platform.dart';
import 'package:neon/src/theme/neon.dart'; import 'package:neon/src/theme/neon.dart';
import 'package:neon/src/utils/global_options.dart'; import 'package:neon/src/utils/global_options.dart';
import 'package:neon/src/utils/request_manager.dart'; import 'package:neon/src/utils/request_manager.dart';
@ -19,7 +18,7 @@ import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
Future runNeon({ Future runNeon({
required final Iterable<AppImplementation> Function(SharedPreferences, NeonPlatform) getAppImplementations, required final Iterable<AppImplementation> Function(SharedPreferences) getAppImplementations,
required final NeonTheme theme, required final NeonTheme theme,
@visibleForTesting final WidgetsBinding? bindingOverride, @visibleForTesting final WidgetsBinding? bindingOverride,
@visibleForTesting final Account? account, @visibleForTesting final Account? account,
@ -31,9 +30,8 @@ Future runNeon({
final sharedPreferences = await SharedPreferences.getInstance(); final sharedPreferences = await SharedPreferences.getInstance();
final platform = await getNeonPlatform(); await RequestManager.instance.initCache();
await RequestManager.instance.initCache(platform); final allAppImplementations = getAppImplementations(sharedPreferences);
final allAppImplementations = getAppImplementations(sharedPreferences, platform);
final packageInfo = await PackageInfo.fromPlatform(); final packageInfo = await PackageInfo.fromPlatform();
buildUserAgent(packageInfo); buildUserAgent(packageInfo);
@ -44,7 +42,6 @@ Future runNeon({
); );
final accountsBloc = AccountsBloc( final accountsBloc = AccountsBloc(
platform,
sharedPreferences, sharedPreferences,
globalOptions, globalOptions,
allAppImplementations, allAppImplementations,
@ -58,7 +55,6 @@ Future runNeon({
accountsBloc, accountsBloc,
sharedPreferences, sharedPreferences,
globalOptions, globalOptions,
platform,
); );
final firstLaunchBloc = FirstLaunchBloc( final firstLaunchBloc = FirstLaunchBloc(
sharedPreferences, sharedPreferences,
@ -76,9 +72,6 @@ Future runNeon({
Provider<SharedPreferences>( Provider<SharedPreferences>(
create: (final _) => sharedPreferences, create: (final _) => sharedPreferences,
), ),
Provider<NeonPlatform>(
create: (final _) => platform,
),
Provider<GlobalOptions>( Provider<GlobalOptions>(
create: (final _) => globalOptions, create: (final _) => globalOptions,
), ),

22
packages/neon/neon/lib/src/app.dart

@ -44,7 +44,6 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
final _appRegex = RegExp(r'^app_([a-z]+)$', multiLine: true); final _appRegex = RegExp(r'^app_([a-z]+)$', multiLine: true);
final _navigatorKey = GlobalKey<NavigatorState>(); final _navigatorKey = GlobalKey<NavigatorState>();
late final Iterable<AppImplementation> _appImplementations; late final Iterable<AppImplementation> _appImplementations;
late final NeonPlatform _platform;
late final GlobalOptions _globalOptions; late final GlobalOptions _globalOptions;
late final AccountsBloc _accountsBloc; late final AccountsBloc _accountsBloc;
late final _routerDelegate = AppRouter( late final _routerDelegate = AppRouter(
@ -59,15 +58,14 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
super.initState(); super.initState();
_appImplementations = Provider.of<Iterable<AppImplementation>>(context, listen: false); _appImplementations = Provider.of<Iterable<AppImplementation>>(context, listen: false);
_platform = Provider.of<NeonPlatform>(context, listen: false);
_globalOptions = Provider.of<GlobalOptions>(context, listen: false); _globalOptions = Provider.of<GlobalOptions>(context, listen: false);
_accountsBloc = Provider.of<AccountsBloc>(context, listen: false); _accountsBloc = Provider.of<AccountsBloc>(context, listen: false);
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
if (_platform.canUseSystemTray) { if (NeonPlatform.instance.canUseSystemTray) {
tray.trayManager.addListener(this); tray.trayManager.addListener(this);
} }
if (_platform.canUseWindowManager) { if (NeonPlatform.instance.canUseWindowManager) {
windowManager.addListener(this); windowManager.addListener(this);
} }
@ -77,7 +75,7 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
if (!mounted) { if (!mounted) {
return; return;
} }
if (_platform.canUseQuickActions) { if (NeonPlatform.instance.canUseQuickActions) {
const quickActions = QuickActions(); const quickActions = QuickActions();
await quickActions.setShortcutItems( await quickActions.setShortcutItems(
_appImplementations _appImplementations
@ -93,7 +91,7 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
await quickActions.initialize(_handleShortcut); await quickActions.initialize(_handleShortcut);
} }
if (_platform.canUseWindowManager) { if (NeonPlatform.instance.canUseWindowManager) {
await windowManager.setPreventClose(true); await windowManager.setPreventClose(true);
if (_globalOptions.startupMinimized.value) { if (_globalOptions.startupMinimized.value) {
@ -101,7 +99,7 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
} }
} }
if (_platform.canUseSystemTray) { if (NeonPlatform.instance.canUseSystemTray) {
_globalOptions.systemTrayEnabled.addListener(() async { _globalOptions.systemTrayEnabled.addListener(() async {
if (_globalOptions.systemTrayEnabled.value) { if (_globalOptions.systemTrayEnabled.value) {
// TODO: This works on Linux, but maybe not on macOS or Windows // TODO: This works on Linux, but maybe not on macOS or Windows
@ -136,7 +134,7 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
}); });
} }
if (_platform.canUsePushNotifications) { if (NeonPlatform.instance.canUsePushNotifications) {
final localNotificationsPlugin = await PushUtils.initLocalNotifications(); final localNotificationsPlugin = await PushUtils.initLocalNotifications();
Global.onPushNotificationReceived = (final accountID) async { Global.onPushNotificationReceived = (final accountID) async {
final account = _accountsBloc.accounts.value.tryFind(accountID); final account = _accountsBloc.accounts.value.tryFind(accountID);
@ -213,7 +211,7 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
Future _handleShortcut(final String shortcutType) async { Future _handleShortcut(final String shortcutType) async {
if (shortcutType == 'show_hide') { if (shortcutType == 'show_hide') {
if (_platform.canUseWindowManager) { if (NeonPlatform.instance.canUseWindowManager) {
if (await windowManager.isVisible()) { if (await windowManager.isVisible()) {
await _saveAndMinimizeWindow(); await _saveAndMinimizeWindow();
} else { } else {
@ -249,7 +247,7 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
} }
Future _showAndRestoreWindow() async { Future _showAndRestoreWindow() async {
if (!_platform.canUseWindowManager) { if (!NeonPlatform.instance.canUseWindowManager) {
return; return;
} }
@ -264,10 +262,10 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
@override @override
void dispose() { void dispose() {
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(this);
if (_platform.canUseSystemTray) { if (NeonPlatform.instance.canUseSystemTray) {
tray.trayManager.removeListener(this); tray.trayManager.removeListener(this);
} }
if (_platform.canUseWindowManager) { if (NeonPlatform.instance.canUseWindowManager) {
windowManager.removeListener(this); windowManager.removeListener(this);
} }

4
packages/neon/neon/lib/src/blocs/accounts.dart

@ -10,7 +10,6 @@ import 'package:neon/src/blocs/user_details.dart';
import 'package:neon/src/blocs/user_statuses.dart'; import 'package:neon/src/blocs/user_statuses.dart';
import 'package:neon/src/models/account.dart'; import 'package:neon/src/models/account.dart';
import 'package:neon/src/models/app_implementation.dart'; import 'package:neon/src/models/app_implementation.dart';
import 'package:neon/src/platform/platform.dart';
import 'package:neon/src/settings/models/storage.dart'; import 'package:neon/src/settings/models/storage.dart';
import 'package:neon/src/utils/account_options.dart'; import 'package:neon/src/utils/account_options.dart';
import 'package:neon/src/utils/global_options.dart'; import 'package:neon/src/utils/global_options.dart';
@ -57,7 +56,6 @@ abstract interface class AccountsBlocStates {
class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocStates { class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocStates {
AccountsBloc( AccountsBloc(
this._platform,
this._sharedPreferences, this._sharedPreferences,
this._globalOptions, this._globalOptions,
this._allAppImplementations, this._allAppImplementations,
@ -98,7 +96,6 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
} }
} }
final NeonPlatform _platform;
late final AppStorage _storage = AppStorage('accounts', _sharedPreferences); late final AppStorage _storage = AppStorage('accounts', _sharedPreferences);
final SharedPreferences _sharedPreferences; final SharedPreferences _sharedPreferences;
final GlobalOptions _globalOptions; final GlobalOptions _globalOptions;
@ -287,7 +284,6 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
} }
return _userStatusesBlocs[account.id] = UserStatusesBloc( return _userStatusesBlocs[account.id] = UserStatusesBloc(
_platform,
account, account,
); );
} }

4
packages/neon/neon/lib/src/blocs/push_notifications.dart

@ -28,9 +28,8 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents,
this._accountsBloc, this._accountsBloc,
this._sharedPreferences, this._sharedPreferences,
this._globalOptions, this._globalOptions,
this._platform,
) { ) {
if (_platform.canUsePushNotifications) { if (NeonPlatform.instance.canUsePushNotifications) {
unawaited(UnifiedPush.getDistributors().then(_globalOptions.updateDistributors)); unawaited(UnifiedPush.getDistributors().then(_globalOptions.updateDistributors));
_globalOptions.pushNotificationsEnabled.addListener(_pushNotificationsEnabledListener); _globalOptions.pushNotificationsEnabled.addListener(_pushNotificationsEnabledListener);
@ -40,7 +39,6 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents,
} }
final AccountsBloc _accountsBloc; final AccountsBloc _accountsBloc;
final NeonPlatform _platform;
final SharedPreferences _sharedPreferences; final SharedPreferences _sharedPreferences;
late final _storage = AppStorage(AppIDs.notifications, _sharedPreferences); late final _storage = AppStorage(AppIDs.notifications, _sharedPreferences);
final GlobalOptions _globalOptions; final GlobalOptions _globalOptions;

12
packages/neon/neon/lib/src/blocs/user_statuses.dart

@ -2,11 +2,11 @@ import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:neon/platform.dart';
import 'package:neon/src/bloc/bloc.dart'; import 'package:neon/src/bloc/bloc.dart';
import 'package:neon/src/bloc/result.dart'; import 'package:neon/src/bloc/result.dart';
import 'package:neon/src/blocs/timer.dart'; import 'package:neon/src/blocs/timer.dart';
import 'package:neon/src/models/account.dart'; import 'package:neon/src/models/account.dart';
import 'package:neon/src/platform/platform.dart';
import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/nextcloud.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
@ -22,14 +22,12 @@ abstract class UserStatusesBlocStates {
@internal @internal
class UserStatusesBloc extends InteractiveBloc implements UserStatusesBlocEvents, UserStatusesBlocStates { class UserStatusesBloc extends InteractiveBloc implements UserStatusesBlocEvents, UserStatusesBlocStates {
UserStatusesBloc( UserStatusesBloc(
this._platform,
this._account, this._account,
) { ) {
unawaited(refresh()); unawaited(refresh());
_timer = TimerBloc().registerTimer(const Duration(minutes: 5), refresh); _timer = TimerBloc().registerTimer(const Duration(minutes: 5), refresh);
} }
final NeonPlatform _platform;
final Account _account; final Account _account;
late final NeonTimer _timer; late final NeonTimer _timer;
@ -63,8 +61,12 @@ class UserStatusesBloc extends InteractiveBloc implements UserStatusesBlocEvents
UserStatusPublic? data; UserStatusPublic? data;
if (_account.username == username) { if (_account.username == username) {
final isAway = var isAway = false;
_platform.canUseWindowManager && (!(await windowManager.isFocused()) || !(await windowManager.isVisible())); if (NeonPlatform.instance.canUseWindowManager) {
final focused = await windowManager.isFocused();
final visible = await windowManager.isFocused();
isAway = !focused || !visible;
}
try { try {
final response = await _account.client.userStatus.heartbeat.heartbeat( final response = await _account.client.userStatus.heartbeat.heartbeat(
status: isAway ? 'away' : 'online', status: isAway ? 'away' : 'online',

3
packages/neon/neon/lib/src/models/app_implementation.dart

@ -6,7 +6,6 @@ import 'package:neon/l10n/localizations.dart';
import 'package:neon/src/bloc/bloc.dart'; import 'package:neon/src/bloc/bloc.dart';
import 'package:neon/src/blocs/accounts.dart'; import 'package:neon/src/blocs/accounts.dart';
import 'package:neon/src/models/account.dart'; import 'package:neon/src/models/account.dart';
import 'package:neon/src/platform/platform.dart';
import 'package:neon/src/settings/models/nextcloud_app_options.dart'; import 'package:neon/src/settings/models/nextcloud_app_options.dart';
import 'package:neon/src/settings/models/storage.dart'; import 'package:neon/src/settings/models/storage.dart';
import 'package:neon/src/widgets/drawer_destination.dart'; import 'package:neon/src/widgets/drawer_destination.dart';
@ -17,7 +16,6 @@ import 'package:shared_preferences/shared_preferences.dart';
abstract class AppImplementation<T extends Bloc, R extends NextcloudAppOptions> { abstract class AppImplementation<T extends Bloc, R extends NextcloudAppOptions> {
AppImplementation( AppImplementation(
final SharedPreferences sharedPreferences, final SharedPreferences sharedPreferences,
this.platform,
) { ) {
final storage = AppStorage('app-$id', sharedPreferences); final storage = AppStorage('app-$id', sharedPreferences);
options = buildOptions(storage); options = buildOptions(storage);
@ -26,7 +24,6 @@ abstract class AppImplementation<T extends Bloc, R extends NextcloudAppOptions>
String get id; String get id;
LocalizationsDelegate get localizationsDelegate; LocalizationsDelegate get localizationsDelegate;
List<Locale> get supportedLocales; List<Locale> get supportedLocales;
final NeonPlatform platform;
String nameFromLocalization(final AppLocalizations localizations) => localizations.appImplementationName(id); String nameFromLocalization(final AppLocalizations localizations) => localizations.appImplementationName(id);
String name(final BuildContext context) => nameFromLocalization(AppLocalizations.of(context)); String name(final BuildContext context) => nameFromLocalization(AppLocalizations.of(context));

5
packages/neon/neon/lib/src/models/notifications_interface.dart

@ -4,10 +4,7 @@ import 'package:neon/src/settings/models/nextcloud_app_options.dart';
abstract interface class NotificationsAppInterface<T extends NotificationsBlocInterface, abstract interface class NotificationsAppInterface<T extends NotificationsBlocInterface,
R extends NotificationsOptionsInterface> extends AppImplementation<T, R> { R extends NotificationsOptionsInterface> extends AppImplementation<T, R> {
NotificationsAppInterface( NotificationsAppInterface(super.sharedPreferences);
super.sharedPreferences,
super.platform,
);
} }
abstract interface class NotificationsBlocInterface extends InteractiveBloc { abstract interface class NotificationsBlocInterface extends InteractiveBloc {

4
packages/neon/neon/lib/src/pages/login.dart

@ -6,7 +6,6 @@ import 'package:neon/src/theme/branding.dart';
import 'package:neon/src/theme/dialog.dart'; import 'package:neon/src/theme/dialog.dart';
import 'package:neon/src/utils/validators.dart'; import 'package:neon/src/utils/validators.dart';
import 'package:neon/src/widgets/nextcloud_logo.dart'; import 'package:neon/src/widgets/nextcloud_logo.dart';
import 'package:provider/provider.dart';
class LoginPage extends StatefulWidget { class LoginPage extends StatefulWidget {
const LoginPage({ const LoginPage({
@ -40,7 +39,6 @@ class _LoginPageState extends State<LoginPage> {
@override @override
Widget build(final BuildContext context) { Widget build(final BuildContext context) {
final branding = Branding.of(context); final branding = Branding.of(context);
final platform = Provider.of<NeonPlatform>(context, listen: false);
return Scaffold( return Scaffold(
resizeToAvoidBottomInset: true, resizeToAvoidBottomInset: true,
@ -101,7 +99,7 @@ class _LoginPageState extends State<LoginPage> {
onFieldSubmitted: login, onFieldSubmitted: login,
), ),
), ),
if (platform.canUseCamera) ...[ if (NeonPlatform.instance.canUseCamera) ...[
const SizedBox( const SizedBox(
height: 50, height: 50,
), ),

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

@ -86,236 +86,234 @@ class _SettingsPageState extends State<SettingsPage> {
builder: ( builder: (
final context, final context,
final accountsSnapshot, final accountsSnapshot,
) { ) =>
final platform = Provider.of<NeonPlatform>(context, listen: false); ValueListenableBuilder<bool>(
return ValueListenableBuilder<bool>( valueListenable: globalOptions.pushNotificationsEnabled,
valueListenable: globalOptions.pushNotificationsEnabled, builder: (
builder: ( final context,
final context, final _,
final _, final __,
final __, ) =>
) => SettingsList(
SettingsList( initialCategory: widget.initialCategory?.name,
initialCategory: widget.initialCategory?.name, categories: [
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) ...[
CustomSettingsTile(
leading: appImplementation.buildIcon(),
title: Text(appImplementation.name(context)),
onTap: () {
NextcloudAppSettingsRoute(appid: appImplementation.id).go(context);
},
),
],
],
],
),
SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryTheme),
key: ValueKey(SettingsCageories.theme.name),
tiles: [
DropdownButtonSettingsTile(
option: globalOptions.themeMode,
),
CheckBoxSettingsTile(
option: globalOptions.themeOLEDAsDark,
),
CheckBoxSettingsTile(
option: globalOptions.themeKeepOriginalAccentColor,
),
],
),
SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryNavigation),
key: ValueKey(SettingsCageories.navigation.name),
tiles: [
DropdownButtonSettingsTile(
option: globalOptions.navigationMode,
),
],
),
if (NeonPlatform.instance.canUsePushNotifications) ...[
SettingsCategory( SettingsCategory(
title: Text(AppLocalizations.of(context).settingsApps), title: Text(AppLocalizations.of(context).optionsCategoryPushNotifications),
key: ValueKey(SettingsCageories.apps.name), key: ValueKey(SettingsCageories.pushNotifications.name),
tiles: <SettingsTile>[ tiles: [
for (final appImplementation in appImplementations) ...[ if (!globalOptions.pushNotificationsEnabled.enabled) ...[
if (appImplementation.options.options.isNotEmpty) ...[ TextSettingsTile(
CustomSettingsTile( text: AppLocalizations.of(context).globalOptionsPushNotificationsEnabledDisabledNotice,
leading: appImplementation.buildIcon(), style: TextStyle(
title: Text(appImplementation.name(context)), fontWeight: FontWeight.w600,
onTap: () { fontStyle: FontStyle.italic,
NextcloudAppSettingsRoute(appid: appImplementation.id).go(context); color: Theme.of(context).colorScheme.error,
},
), ),
], ),
], ],
CheckBoxSettingsTile(
option: globalOptions.pushNotificationsEnabled,
),
DropdownButtonSettingsTile(
option: globalOptions.pushNotificationsDistributor,
),
], ],
), ),
],
if (NeonPlatform.instance.canUseWindowManager) ...[
SettingsCategory( SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryTheme), title: Text(AppLocalizations.of(context).optionsCategoryStartup),
key: ValueKey(SettingsCageories.theme.name), key: ValueKey(SettingsCageories.startup.name),
tiles: [ tiles: [
DropdownButtonSettingsTile(
option: globalOptions.themeMode,
),
CheckBoxSettingsTile( CheckBoxSettingsTile(
option: globalOptions.themeOLEDAsDark, option: globalOptions.startupMinimized,
), ),
CheckBoxSettingsTile( CheckBoxSettingsTile(
option: globalOptions.themeKeepOriginalAccentColor, option: globalOptions.startupMinimizeInsteadOfExit,
), ),
], ],
), ),
],
if (NeonPlatform.instance.canUseWindowManager && NeonPlatform.instance.canUseSystemTray) ...[
SettingsCategory( SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryNavigation), title: Text(AppLocalizations.of(context).optionsCategorySystemTray),
key: ValueKey(SettingsCageories.navigation.name), key: ValueKey(SettingsCageories.systemTray.name),
tiles: [ tiles: [
DropdownButtonSettingsTile( CheckBoxSettingsTile(
option: globalOptions.navigationMode, option: globalOptions.systemTrayEnabled,
),
CheckBoxSettingsTile(
option: globalOptions.systemTrayHideToTrayWhenMinimized,
), ),
], ],
), ),
if (platform.canUsePushNotifications) ...[ ],
SettingsCategory( SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryPushNotifications), title: Text(AppLocalizations.of(context).optionsCategoryAccounts),
key: ValueKey(SettingsCageories.pushNotifications.name), key: ValueKey(SettingsCageories.accounts.name),
tiles: [ tiles: [
if (!globalOptions.pushNotificationsEnabled.enabled) ...[ if (accountsSnapshot.requireData.length > 1) ...[
TextSettingsTile( CheckBoxSettingsTile(
text: AppLocalizations.of(context).globalOptionsPushNotificationsEnabledDisabledNotice, option: globalOptions.rememberLastUsedAccount,
style: TextStyle( ),
fontWeight: FontWeight.w600, DropdownButtonSettingsTile(
fontStyle: FontStyle.italic, option: globalOptions.initialAccount,
color: Theme.of(context).colorScheme.error,
),
),
],
CheckBoxSettingsTile(
option: globalOptions.pushNotificationsEnabled,
),
DropdownButtonSettingsTile(
option: globalOptions.pushNotificationsDistributor,
),
],
),
],
if (platform.canUseWindowManager) ...[
SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryStartup),
key: ValueKey(SettingsCageories.startup.name),
tiles: [
CheckBoxSettingsTile(
option: globalOptions.startupMinimized,
),
CheckBoxSettingsTile(
option: globalOptions.startupMinimizeInsteadOfExit,
),
],
),
],
if (platform.canUseWindowManager && platform.canUseSystemTray) ...[
SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategorySystemTray),
key: ValueKey(SettingsCageories.systemTray.name),
tiles: [
CheckBoxSettingsTile(
option: globalOptions.systemTrayEnabled,
),
CheckBoxSettingsTile(
option: globalOptions.systemTrayHideToTrayWhenMinimized,
),
],
),
],
SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryAccounts),
key: ValueKey(SettingsCageories.accounts.name),
tiles: [
if (accountsSnapshot.requireData.length > 1) ...[
CheckBoxSettingsTile(
option: globalOptions.rememberLastUsedAccount,
),
DropdownButtonSettingsTile(
option: globalOptions.initialAccount,
),
],
for (final account in accountsSnapshot.requireData) ...[
AccountSettingsTile(
account: account,
onTap: () {
AccountSettingsRoute(accountid: account.id).go(context);
},
),
],
CustomSettingsTile(
title: ElevatedButton.icon(
onPressed: () async => const LoginRoute().push(context),
icon: Icon(MdiIcons.accountPlus),
label: Text(AppLocalizations.of(context).globalOptionsAccountsAdd),
),
), ),
], ],
), for (final account in accountsSnapshot.requireData) ...[
SettingsCategory( AccountSettingsTile(
title: Text(AppLocalizations.of(context).optionsCategoryOther), account: account,
key: ValueKey(SettingsCageories.other.name), onTap: () {
tiles: <SettingsTile>[ AccountSettingsRoute(accountid: account.id).go(context);
CustomSettingsTile(
leading: Icon(
MdiIcons.scriptText,
color: Theme.of(context).colorScheme.primary,
),
title: Text(AppLocalizations.of(context).licenses),
onTap: () async {
final branding = Branding.of(context);
showLicensePage(
context: context,
applicationName: branding.name,
applicationIcon: branding.logo,
applicationLegalese: branding.legalese,
applicationVersion: Provider.of<PackageInfo>(context, listen: false).version,
);
}, },
), ),
CustomSettingsTile( ],
leading: Icon( CustomSettingsTile(
MdiIcons.export, title: ElevatedButton.icon(
color: Theme.of(context).colorScheme.primary, onPressed: () async => const LoginRoute().push(context),
), icon: Icon(MdiIcons.accountPlus),
title: Text(AppLocalizations.of(context).settingsExport), label: Text(AppLocalizations.of(context).globalOptionsAccountsAdd),
onTap: () async { ),
final settingsExportHelper = _buildSettingsExportHelper(context); ),
],
),
SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryOther),
key: ValueKey(SettingsCageories.other.name),
tiles: <SettingsTile>[
CustomSettingsTile(
leading: Icon(
MdiIcons.scriptText,
color: Theme.of(context).colorScheme.primary,
),
title: Text(AppLocalizations.of(context).licenses),
onTap: () async {
final branding = Branding.of(context);
showLicensePage(
context: context,
applicationName: branding.name,
applicationIcon: branding.logo,
applicationLegalese: branding.legalese,
applicationVersion: Provider.of<PackageInfo>(context, listen: false).version,
);
},
),
CustomSettingsTile(
leading: Icon(
MdiIcons.export,
color: Theme.of(context).colorScheme.primary,
),
title: Text(AppLocalizations.of(context).settingsExport),
onTap: () async {
final settingsExportHelper = _buildSettingsExportHelper(context);
try { try {
final fileName = final fileName =
'nextcloud-neon-settings-${DateTime.now().millisecondsSinceEpoch ~/ 1000}.json.base64'; 'nextcloud-neon-settings-${DateTime.now().millisecondsSinceEpoch ~/ 1000}.json.base64';
final data = base64.encode( final data = base64.encode(
utf8.encode( utf8.encode(
json.encode( json.encode(
settingsExportHelper.toJsonExport(), settingsExportHelper.toJsonExport(),
),
), ),
); ),
await saveFileWithPickDialog(fileName, utf8.encode(data) as Uint8List); );
} catch (e, s) { await saveFileWithPickDialog(fileName, utf8.encode(data) as Uint8List);
debugPrint(e.toString()); } catch (e, s) {
debugPrint(s.toString()); debugPrint(e.toString());
if (mounted) { debugPrint(s.toString());
NeonException.showSnackbar(context, e); if (mounted) {
} NeonException.showSnackbar(context, e);
} }
}, }
},
),
CustomSettingsTile(
leading: Icon(
MdiIcons.import,
color: Theme.of(context).colorScheme.primary,
), ),
CustomSettingsTile( title: Text(AppLocalizations.of(context).settingsImport),
leading: Icon( onTap: () async {
MdiIcons.import, final settingsExportHelper = _buildSettingsExportHelper(context);
color: Theme.of(context).colorScheme.primary,
),
title: Text(AppLocalizations.of(context).settingsImport),
onTap: () async {
final settingsExportHelper = _buildSettingsExportHelper(context);
try { try {
final result = await FilePicker.platform.pickFiles( final result = await FilePicker.platform.pickFiles(
withData: true, withData: true,
); );
if (result == null) { if (result == null) {
return; return;
} }
if (!result.files.single.path!.endsWith('.json.base64')) { if (!result.files.single.path!.endsWith('.json.base64')) {
if (mounted) { if (mounted) {
NeonException.showSnackbar( NeonException.showSnackbar(
context, context,
AppLocalizations.of(context).settingsImportWrongFileExtension, AppLocalizations.of(context).settingsImportWrongFileExtension,
); );
}
return;
} }
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>); await settingsExportHelper.applyFromJson(data as Map<String, dynamic>);
} catch (e, s) { } catch (e, s) {
debugPrint(e.toString()); debugPrint(e.toString());
debugPrint(s.toString()); debugPrint(s.toString());
if (mounted) { if (mounted) {
NeonException.showSnackbar(context, e); NeonException.showSnackbar(context, e);
}
} }
}, }
), },
], ),
), ],
], ),
), ],
); ),
}, ),
); );
return Scaffold( return Scaffold(

46
packages/neon/neon/lib/src/platform/platform.dart

@ -5,22 +5,42 @@ import 'package:meta/meta.dart';
import 'package:neon/src/platform/android.dart'; import 'package:neon/src/platform/android.dart';
import 'package:neon/src/platform/linux.dart'; import 'package:neon/src/platform/linux.dart';
Future<NeonPlatform> getNeonPlatform() async { /// Implements platform specific functionality and exposes the availability of certain features.
final NeonPlatform platform; @immutable
if (Platform.isAndroid) { abstract interface class NeonPlatform {
platform = const AndroidNeonPlatform(); @visibleForTesting
} else if (Platform.isLinux) { factory NeonPlatform.mocked(final NeonPlatform platform) => _platform = platform;
platform = const LinuxNeonPlatform();
} else { static NeonPlatform? _platform;
throw UnimplementedError('No implementation for platform ${Platform.operatingSystem} found');
/// Infers and configures the platform automatically.
///
/// Required to be called before accessing [NeonPlatform.instance].
static Future setup() async {
if (Platform.isAndroid) {
_platform = const AndroidNeonPlatform();
} else if (Platform.isLinux) {
_platform = const LinuxNeonPlatform();
} else {
throw UnimplementedError('No implementation for platform ${Platform.operatingSystem} found');
}
await _platform!.init();
} }
await platform.init.call(); /// Gets the current instance of [NeonPlatform].
return platform; ///
} /// Make sure [NeonPlatform.setup] has been called before accessing the instance.
static NeonPlatform get instance {
if (_platform == null) {
throw StateError(
'NeonPlatform has not been set up yet. Please make sure NeonPlatform.setup() has been called before and completed.',
);
}
return _platform!;
}
@immutable
abstract interface class NeonPlatform {
abstract final bool canUseWebView; abstract final bool canUseWebView;
abstract final bool canUseQuickActions; abstract final bool canUseQuickActions;

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

@ -45,10 +45,9 @@ class GlobalPopups {
final globalOptions = Provider.of<GlobalOptions>(context, listen: false); final globalOptions = Provider.of<GlobalOptions>(context, listen: false);
final firstLaunchBloc = Provider.of<FirstLaunchBloc>(context, listen: false); final firstLaunchBloc = Provider.of<FirstLaunchBloc>(context, listen: false);
final nextPushBloc = Provider.of<NextPushBloc>(context, listen: false); final nextPushBloc = Provider.of<NextPushBloc>(context, listen: false);
final platform = Provider.of<NeonPlatform>(context, listen: false);
_subscriptions.addAll([ _subscriptions.addAll([
if (platform.canUsePushNotifications) ...[ if (NeonPlatform.instance.canUsePushNotifications) ...[
firstLaunchBloc.onFirstLaunch.listen((final _) { firstLaunchBloc.onFirstLaunch.listen((final _) {
assert(context.mounted, 'Context should be mounted'); assert(context.mounted, 'Context should be mounted');
if (!globalOptions.pushNotificationsEnabled.enabled) { if (!globalOptions.pushNotificationsEnabled.enabled) {

9
packages/neon/neon/lib/src/utils/request_manager.dart

@ -22,8 +22,8 @@ class RequestManager {
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static RequestManager get instance => _requestManager ??= RequestManager(); static RequestManager get instance => _requestManager ??= RequestManager();
Future initCache(final NeonPlatform platform) async { Future initCache() async {
_cache = Cache(platform); _cache = Cache();
await _cache!.init(); await _cache!.init();
} }
@ -187,9 +187,6 @@ class RequestManager {
@internal @internal
class Cache { class Cache {
Cache(this._platform);
final NeonPlatform _platform;
Database? _database; Database? _database;
Future init() async { Future init() async {
@ -199,7 +196,7 @@ class Cache {
_database = await openDatabase( _database = await openDatabase(
p.join( p.join(
await _platform.getApplicationCachePath(), await NeonPlatform.instance.getApplicationCachePath(),
'cache.db', 'cache.db',
), ),
version: 1, version: 1,

6
packages/neon/neon_files/lib/blocs/files.dart

@ -28,7 +28,6 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta
FilesBloc( FilesBloc(
this.options, this.options,
this.account, this.account,
this._platform,
) { ) {
options.uploadQueueParallelism.addListener(_uploadParalelismListener); options.uploadQueueParallelism.addListener(_uploadParalelismListener);
options.downloadQueueParallelism.addListener(_downloadParalelismListener); options.downloadQueueParallelism.addListener(_downloadParalelismListener);
@ -36,7 +35,6 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta
final FilesAppSpecificOptions options; final FilesAppSpecificOptions options;
final Account account; final Account account;
final NeonPlatform _platform;
late final browser = getNewFilesBrowserBloc(); late final browser = getNewFilesBrowserBloc();
final _uploadQueue = Queue(); final _uploadQueue = Queue();
@ -86,7 +84,7 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta
() async { () async {
final file = File( final file = File(
p.join( p.join(
await _platform.getApplicationCachePath(), await NeonPlatform.instance.getApplicationCachePath(),
'files', 'files',
etag.replaceAll('"', ''), etag.replaceAll('"', ''),
path.last, path.last,
@ -139,7 +137,7 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta
() async { () async {
final file = File( final file = File(
p.join( p.join(
await _platform.getUserAccessibleAppDataPath(), await NeonPlatform.instance.getUserAccessibleAppDataPath(),
account.humanReadableID, account.humanReadableID,
'files', 'files',
path.join(Platform.pathSeparator), path.join(Platform.pathSeparator),

2
packages/neon/neon_files/lib/dialogs/choose_create.dart

@ -77,7 +77,7 @@ class _FilesChooseCreateDialogState extends State<FilesChooseCreateDialog> {
} }
}, },
), ),
if (Provider.of<NeonPlatform>(context, listen: false).canUseCamera) ...[ if (NeonPlatform.instance.canUseCamera) ...[
ListTile( ListTile(
leading: Icon( leading: Icon(
MdiIcons.cameraPlus, MdiIcons.cameraPlus,

3
packages/neon/neon_files/lib/neon_files.dart

@ -44,7 +44,7 @@ part 'widgets/browser_view.dart';
part 'widgets/file_preview.dart'; part 'widgets/file_preview.dart';
class FilesApp extends AppImplementation<FilesBloc, FilesAppSpecificOptions> { class FilesApp extends AppImplementation<FilesBloc, FilesAppSpecificOptions> {
FilesApp(super.sharedPreferences, super.platform); FilesApp(super.sharedPreferences);
@override @override
String id = AppIDs.files; String id = AppIDs.files;
@ -62,7 +62,6 @@ class FilesApp extends AppImplementation<FilesBloc, FilesAppSpecificOptions> {
FilesBloc buildBloc(final Account account) => FilesBloc( FilesBloc buildBloc(final Account account) => FilesBloc(
options, options,
account, account,
platform,
); );
@override @override

4
packages/neon/neon_news/lib/neon_news.dart

@ -52,7 +52,7 @@ part 'widgets/folder_view.dart';
part 'widgets/folders_view.dart'; part 'widgets/folders_view.dart';
class NewsApp extends AppImplementation<NewsBloc, NewsAppSpecificOptions> { class NewsApp extends AppImplementation<NewsBloc, NewsAppSpecificOptions> {
NewsApp(super.sharedPreferences, super.platform); NewsApp(super.sharedPreferences);
@override @override
String id = AppIDs.news; String id = AppIDs.news;
@ -64,7 +64,7 @@ class NewsApp extends AppImplementation<NewsBloc, NewsAppSpecificOptions> {
List<Locale> supportedLocales = AppLocalizations.supportedLocales; List<Locale> supportedLocales = AppLocalizations.supportedLocales;
@override @override
NewsAppSpecificOptions buildOptions(final AppStorage storage) => NewsAppSpecificOptions(storage, platform); NewsAppSpecificOptions buildOptions(final AppStorage storage) => NewsAppSpecificOptions(storage);
@override @override
NewsBloc buildBloc(final Account account) => NewsBloc( NewsBloc buildBloc(final Account account) => NewsBloc(

6
packages/neon/neon_news/lib/options.dart

@ -1,7 +1,7 @@
part of 'neon_news.dart'; part of 'neon_news.dart';
class NewsAppSpecificOptions extends NextcloudAppOptions { class NewsAppSpecificOptions extends NextcloudAppOptions {
NewsAppSpecificOptions(super.storage, this._platform) { NewsAppSpecificOptions(super.storage) {
super.categories = [ super.categories = [
generalCategory, generalCategory,
articlesCategory, articlesCategory,
@ -23,8 +23,6 @@ class NewsAppSpecificOptions extends NextcloudAppOptions {
]; ];
} }
final NeonPlatform _platform;
final generalCategory = OptionsCategory( final generalCategory = OptionsCategory(
name: (final context) => AppLocalizations.of(context).general, name: (final context) => AppLocalizations.of(context).general,
); );
@ -62,7 +60,7 @@ class NewsAppSpecificOptions extends NextcloudAppOptions {
defaultValue: ArticleViewType.direct, defaultValue: ArticleViewType.direct,
values: { values: {
ArticleViewType.direct: (final context) => AppLocalizations.of(context).optionsArticleViewTypeDirect, ArticleViewType.direct: (final context) => AppLocalizations.of(context).optionsArticleViewTypeDirect,
if (_platform.canUseWebView) if (NeonPlatform.instance.canUseWebView)
ArticleViewType.internalBrowser: (final context) => ArticleViewType.internalBrowser: (final context) =>
AppLocalizations.of(context).optionsArticleViewTypeInternalBrowser, AppLocalizations.of(context).optionsArticleViewTypeInternalBrowser,
ArticleViewType.externalBrowser: (final context) => ArticleViewType.externalBrowser: (final context) =>

2
packages/neon/neon_news/lib/widgets/articles_view.dart

@ -203,7 +203,7 @@ class _NewsArticlesViewState extends State<NewsArticlesView> {
); );
} else if (viewType == ArticleViewType.internalBrowser && } else if (viewType == ArticleViewType.internalBrowser &&
article.url != null && article.url != null &&
Provider.of<NeonPlatform>(context, listen: false).canUseWebView) { NeonPlatform.instance.canUseWebView) {
await Navigator.of(context).push( await Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (final context) => NewsArticlePage( builder: (final context) => NewsArticlePage(

2
packages/neon/neon_notes/lib/neon_notes.dart

@ -42,7 +42,7 @@ part 'widgets/notes_floating_action_button.dart';
part 'widgets/notes_view.dart'; part 'widgets/notes_view.dart';
class NotesApp extends AppImplementation<NotesBloc, NotesAppSpecificOptions> { class NotesApp extends AppImplementation<NotesBloc, NotesAppSpecificOptions> {
NotesApp(super.sharedPreferences, super.platform); NotesApp(super.sharedPreferences);
@override @override
String id = AppIDs.notes; String id = AppIDs.notes;

2
packages/neon/neon_notifications/lib/neon_notifications.dart

@ -22,7 +22,7 @@ part 'pages/main.dart';
class NotificationsApp extends AppImplementation<NotificationsBloc, NotificationsAppSpecificOptions> class NotificationsApp extends AppImplementation<NotificationsBloc, NotificationsAppSpecificOptions>
implements NotificationsAppInterface<NotificationsBloc, NotificationsAppSpecificOptions> { implements NotificationsAppInterface<NotificationsBloc, NotificationsAppSpecificOptions> {
NotificationsApp(super.sharedPreferences, super.platform); NotificationsApp(super.sharedPreferences);
@override @override
String id = AppIDs.notifications; String id = AppIDs.notifications;

Loading…
Cancel
Save