You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
334 lines
11 KiB
334 lines
11 KiB
part of 'neon.dart'; |
|
|
|
class NeonApp extends StatefulWidget { |
|
const NeonApp({ |
|
required this.accountsBloc, |
|
required this.sharedPreferences, |
|
required this.env, |
|
required this.platform, |
|
required this.globalOptions, |
|
super.key, |
|
}); |
|
|
|
final AccountsBloc accountsBloc; |
|
final SharedPreferences sharedPreferences; |
|
final Env? env; |
|
final NeonPlatform platform; |
|
final GlobalOptions globalOptions; |
|
|
|
@override |
|
State<NeonApp> createState() => _NeonAppState(); |
|
} |
|
|
|
// ignore: prefer_mixin |
|
class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.TrayListener, WindowListener { |
|
final _appRegex = RegExp(r'^app_([a-z]+)$', multiLine: true); |
|
final _navigatorKey = GlobalKey<NavigatorState>(); |
|
late NeonPlatform _platform; |
|
late GlobalOptions _globalOptions; |
|
late AccountsBloc _accountsBloc; |
|
|
|
CoreServerCapabilities_Ocs_Data_Capabilities_Theming? _nextcloudTheme; |
|
final _platformBrightness = BehaviorSubject<Brightness>.seeded(WidgetsBinding.instance.window.platformBrightness); |
|
Rect? _lastBounds; |
|
|
|
@override |
|
void didChangePlatformBrightness() { |
|
_platformBrightness.add(WidgetsBinding.instance.window.platformBrightness); |
|
|
|
super.didChangePlatformBrightness(); |
|
} |
|
|
|
@override |
|
void initState() { |
|
super.initState(); |
|
|
|
_platform = Provider.of<NeonPlatform>(context, listen: false); |
|
_globalOptions = Provider.of<GlobalOptions>(context, listen: false); |
|
_accountsBloc = RxBlocProvider.of<AccountsBloc>(context); |
|
|
|
WidgetsBinding.instance.addObserver(this); |
|
if (_platform.canUseSystemTray) { |
|
tray.trayManager.addListener(this); |
|
} |
|
if (_platform.canUseWindowManager) { |
|
windowManager.addListener(this); |
|
} |
|
|
|
WidgetsBinding.instance.addPostFrameCallback((final _) async { |
|
widget.accountsBloc.activeAccount.listen((final activeAccount) async { |
|
FlutterNativeSplash.remove(); |
|
|
|
if (activeAccount == null) { |
|
await _navigatorKey.currentState!.pushAndRemoveUntil( |
|
MaterialPageRoute( |
|
builder: (final context) => const LoginPage(), |
|
), |
|
(final _) => false, |
|
); |
|
} else { |
|
const settings = RouteSettings( |
|
name: 'home', |
|
); |
|
Widget builder(final context) => HomePage( |
|
account: activeAccount, |
|
onThemeChanged: (final nextcloudTheme) { |
|
setState(() { |
|
_nextcloudTheme = nextcloudTheme; |
|
}); |
|
}, |
|
); |
|
await _navigatorKey.currentState!.pushAndRemoveUntil( |
|
widget.globalOptions.navigationMode.value == NavigationMode.drawer |
|
? MaterialPageRoute( |
|
settings: settings, |
|
builder: builder, |
|
) |
|
: NoAnimationPageRoute( |
|
settings: settings, |
|
builder: builder, |
|
), |
|
(final _) => false, |
|
); |
|
} |
|
}); |
|
final localizations = await appLocalizationsFromSystem(); |
|
|
|
if (!mounted) { |
|
return; |
|
} |
|
final appImplementations = Provider.of<List<AppImplementation>>(context, listen: false); |
|
if (_platform.canUseQuickActions) { |
|
const quickActions = QuickActions(); |
|
await quickActions.setShortcutItems( |
|
appImplementations |
|
.map( |
|
(final app) => ShortcutItem( |
|
type: 'app_${app.id}', |
|
localizedTitle: app.nameFromLocalization(localizations), |
|
icon: 'app_${app.id}', |
|
), |
|
) |
|
.toList(), |
|
); |
|
await quickActions.initialize(_handleShortcut); |
|
} |
|
|
|
if (_platform.canUseWindowManager) { |
|
await windowManager.setPreventClose(true); |
|
|
|
if (_globalOptions.startupMinimized.value) { |
|
await _saveAndMinimizeWindow(); |
|
} |
|
} |
|
|
|
if (_platform.canUseSystemTray) { |
|
_globalOptions.systemTrayEnabled.stream.listen((final enabled) async { |
|
if (enabled) { |
|
// TODO: This works on Linux, but maybe not on macOS or Windows |
|
await tray.trayManager.setIcon('assets/logo_neon.svg'); |
|
if (mounted) { |
|
await tray.trayManager.setContextMenu( |
|
tray.Menu( |
|
items: [ |
|
for (final app in appImplementations) ...[ |
|
tray.MenuItem( |
|
key: 'app_${app.id}', |
|
label: app.nameFromLocalization(localizations), |
|
// TODO: Add icons which should work on macOS and Windows |
|
), |
|
], |
|
tray.MenuItem.separator(), |
|
tray.MenuItem( |
|
key: 'show_hide', |
|
label: localizations.showSlashHide, |
|
), |
|
tray.MenuItem( |
|
key: 'exit', |
|
label: localizations.exit, |
|
), |
|
], |
|
), |
|
); |
|
} |
|
} else { |
|
await tray.trayManager.destroy(); |
|
} |
|
}); |
|
} |
|
|
|
if (_platform.canUsePushNotifications) { |
|
final localNotificationsPlugin = await PushUtils.initLocalNotifications(); |
|
Global.onPushNotificationReceived = (final accountID) async { |
|
final account = RxBlocProvider.of<AccountsBloc>(context).accounts.value.find(accountID); |
|
if (account == null) { |
|
return; |
|
} |
|
final appImplementation = Provider.of<List<AppImplementation>>(context, listen: false) |
|
.singleWhere((final a) => a.id == 'notifications'); |
|
_accountsBloc.getAppsBloc(account).getAppBloc<NotificationsBloc>(appImplementation).refresh(); |
|
}; |
|
Global.onPushNotificationClicked = (final pushNotificationWithAccountID) async { |
|
final allAppImplementations = Provider.of<List<AppImplementation>>(context, listen: false); |
|
|
|
final matchingAppImplementations = |
|
allAppImplementations.where((final a) => a.id == pushNotificationWithAccountID.notification.subject.app); |
|
|
|
late AppImplementation appImplementation; |
|
if (matchingAppImplementations.isNotEmpty) { |
|
appImplementation = matchingAppImplementations.single; |
|
} else { |
|
appImplementation = allAppImplementations.singleWhere((final a) => a.id == 'notifications'); |
|
} |
|
|
|
final account = |
|
RxBlocProvider.of<AccountsBloc>(context).accounts.value.find(pushNotificationWithAccountID.accountID); |
|
if (account == null) { |
|
return; |
|
} |
|
|
|
_accountsBloc.setActiveAccount(account); |
|
if (appImplementation.id != 'notifications') { |
|
_accountsBloc |
|
.getAppsBloc(account) |
|
.getAppBloc<NotificationsBloc>(appImplementation) |
|
.deleteNotification(pushNotificationWithAccountID.notification.subject.nid!); |
|
} |
|
await _openAppFromExternal(account, appImplementation.id); |
|
}; |
|
|
|
final details = await localNotificationsPlugin.getNotificationAppLaunchDetails(); |
|
if (details != null && details.didNotificationLaunchApp && details.notificationResponse?.payload != null) { |
|
await Global.onPushNotificationClicked!( |
|
PushNotificationWithAccountID.fromJson( |
|
json.decode(details.notificationResponse!.payload!) as Map<String, dynamic>, |
|
), |
|
); |
|
} |
|
} |
|
}); |
|
} |
|
|
|
@override |
|
void onTrayMenuItemClick(final tray.MenuItem menuItem) { |
|
if (menuItem.key != null) { |
|
unawaited(_handleShortcut(menuItem.key!)); |
|
} |
|
} |
|
|
|
@override |
|
Future onWindowClose() async { |
|
if (_globalOptions.startupMinimizeInsteadOfExit.value) { |
|
await _saveAndMinimizeWindow(); |
|
} else { |
|
await windowManager.destroy(); |
|
} |
|
} |
|
|
|
@override |
|
Future onWindowMinimize() async { |
|
await _saveAndMinimizeWindow(); |
|
} |
|
|
|
Future _handleShortcut(final String shortcutType) async { |
|
if (shortcutType == 'show_hide') { |
|
if (_platform.canUseWindowManager) { |
|
if (await windowManager.isVisible()) { |
|
await _saveAndMinimizeWindow(); |
|
} else { |
|
await _showAndRestoreWindow(); |
|
} |
|
} |
|
return; |
|
} |
|
if (shortcutType == 'exit') { |
|
exit(0); |
|
} |
|
|
|
final matches = _appRegex.allMatches(shortcutType).toList(); |
|
if (matches.isNotEmpty) { |
|
final activeAccount = _accountsBloc.activeAccount.valueOrNull; |
|
if (activeAccount == null) { |
|
return; |
|
} |
|
await _openAppFromExternal(activeAccount, matches[0].group(1)!); |
|
} |
|
} |
|
|
|
Future _openAppFromExternal(final Account account, final String id) async { |
|
_accountsBloc.getAppsBloc(account).setActiveApp(id); |
|
_navigatorKey.currentState!.popUntil((final route) => route.settings.name == 'home'); |
|
await _showAndRestoreWindow(); |
|
} |
|
|
|
Future _saveAndMinimizeWindow() async { |
|
_lastBounds = await windowManager.getBounds(); |
|
if (_globalOptions.systemTrayEnabled.value && _globalOptions.systemTrayHideToTrayWhenMinimized.value) { |
|
await windowManager.hide(); |
|
} else { |
|
await windowManager.minimize(); |
|
} |
|
} |
|
|
|
Future _showAndRestoreWindow() async { |
|
if (!_platform.canUseWindowManager) { |
|
return; |
|
} |
|
|
|
final wasVisible = await windowManager.isVisible(); |
|
await windowManager.show(); |
|
await windowManager.focus(); |
|
if (_lastBounds != null && !wasVisible) { |
|
await windowManager.setBounds(_lastBounds); |
|
} |
|
} |
|
|
|
@override |
|
void dispose() { |
|
WidgetsBinding.instance.removeObserver(this); |
|
if (_platform.canUseSystemTray) { |
|
tray.trayManager.removeListener(this); |
|
} |
|
if (_platform.canUseWindowManager) { |
|
windowManager.removeListener(this); |
|
} |
|
unawaited(_platformBrightness.close()); |
|
|
|
super.dispose(); |
|
} |
|
|
|
@override |
|
Widget build(final BuildContext context) => StreamBuilder<Brightness>( |
|
stream: _platformBrightness, |
|
builder: (final context, final platformBrightnessSnapshot) => OptionBuilder( |
|
option: widget.globalOptions.themeMode, |
|
builder: (final context, final themeMode) => OptionBuilder( |
|
option: widget.globalOptions.themeOLEDAsDark, |
|
builder: (final context, final themeOLEDAsDark) => OptionBuilder( |
|
option: widget.globalOptions.themeKeepOriginalAccentColor, |
|
builder: (final context, final themeKeepOriginalAccentColor) { |
|
if (!platformBrightnessSnapshot.hasData || |
|
themeMode == null || |
|
themeOLEDAsDark == null || |
|
themeKeepOriginalAccentColor == null) { |
|
return Container(); |
|
} |
|
return MaterialApp( |
|
localizationsDelegates: AppLocalizations.localizationsDelegates, |
|
supportedLocales: AppLocalizations.supportedLocales, |
|
navigatorKey: _navigatorKey, |
|
theme: getThemeFromNextcloudTheme( |
|
_nextcloudTheme, |
|
themeMode, |
|
platformBrightnessSnapshot.data!, |
|
oledAsDark: themeOLEDAsDark, |
|
keepOriginalAccentColor: themeKeepOriginalAccentColor, |
|
), |
|
home: Container(), |
|
); |
|
}, |
|
), |
|
), |
|
), |
|
); |
|
}
|
|
|