From 9e911c48a17a5ddd5340997a2fa1783a2a49bf8f Mon Sep 17 00:00:00 2001 From: jld3103 Date: Mon, 17 Oct 2022 12:48:55 +0200 Subject: [PATCH 1/2] neon: Implement Material 3 theme --- .../integration_test/screenshot_test.dart | 33 +-- packages/neon/lib/l10n/en.arb | 1 + packages/neon/lib/l10n/localizations.dart | 6 + packages/neon/lib/l10n/localizations_en.dart | 3 + packages/neon/lib/src/app.dart | 51 +++-- .../neon/lib/src/apps/notes/pages/note.dart | 210 +++++++++--------- .../src/apps/notifications/pages/main.dart | 1 + packages/neon/lib/src/pages/home/home.dart | 6 +- .../neon/lib/src/pages/settings/settings.dart | 3 + .../lib/src/utils/confirmation_dialog.dart | 2 + .../neon/lib/src/utils/global_options.dart | 8 + packages/neon/lib/src/utils/theme.dart | 113 ++-------- 12 files changed, 200 insertions(+), 237 deletions(-) diff --git a/packages/neon/integration_test/screenshot_test.dart b/packages/neon/integration_test/screenshot_test.dart index e585df17..0ba14bec 100644 --- a/packages/neon/integration_test/screenshot_test.dart +++ b/packages/neon/integration_test/screenshot_test.dart @@ -15,6 +15,7 @@ import 'package:neon/src/neon.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:provider/provider.dart'; import 'package:rxdart/rxdart.dart'; +import 'package:settings/settings.dart'; import 'package:shared_preferences/shared_preferences.dart'; class MemorySharedPreferences implements SharedPreferences { @@ -169,21 +170,25 @@ Future pumpAppPage( ], child: StreamBuilder( stream: userThemeStream, - builder: (final context, final themeSnapshot) => StreamBuilder( - stream: globalOptions.themeMode.stream, - builder: (final context, final themeModeSnapshot) => StreamBuilder( - stream: globalOptions.themeOLEDAsDark.stream, - builder: (final context, final themeOLEDAsDarkSnapshot) => MaterialApp( - debugShowCheckedModeBanner: false, - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - theme: getThemeFromNextcloudTheme( - themeSnapshot.data, - themeModeSnapshot.data ?? ThemeMode.system, - Brightness.light, - oledAsDark: themeOLEDAsDarkSnapshot.data ?? false, + builder: (final context, final themeSnapshot) => OptionBuilder( + option: globalOptions.themeMode, + builder: (final context, final themeMode) => OptionBuilder( + option: globalOptions.themeOLEDAsDark, + builder: (final context, final themeOLEDAsDark) => OptionBuilder( + option: globalOptions.themeKeepOriginalAccentColor, + builder: (final context, final themeKeepOriginalAccentColor) => MaterialApp( + debugShowCheckedModeBanner: false, + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + theme: getThemeFromNextcloudTheme( + themeSnapshot.data, + themeMode ?? ThemeMode.system, + Brightness.light, + oledAsDark: themeOLEDAsDark ?? false, + keepOriginalAccentColor: themeKeepOriginalAccentColor ?? true, + ), + home: builder(context, userThemeStream.add), ), - home: builder(context, userThemeStream.add), ), ), ), diff --git a/packages/neon/lib/l10n/en.arb b/packages/neon/lib/l10n/en.arb index 9a4a0924..1bb7cc90 100644 --- a/packages/neon/lib/l10n/en.arb +++ b/packages/neon/lib/l10n/en.arb @@ -80,6 +80,7 @@ "globalOptionsThemeModeDark": "Dark", "globalOptionsThemeModeAutomatic": "Automatic", "globalOptionsThemeOLEDAsDark": "OLED theme as dark theme", + "globalOptionsThemeKeepOriginalAccentColor": "Keep the original accent color", "globalOptionsPushNotificationsNotice": "External services are used for delivering push notifications. While the content is encrypted and can only be read by this app, extracting metadata like the time and count of notifications is still possible.", "globalOptionsPushNotificationsEnabled": "Enabled", "globalOptionsPushNotificationsEnabledDisabledNotice": "No UnifiedPush distributor could be found. Please go to https://unifiedpush.org/users/distributors and setup any of the listed distributors. Then re-open this app and you should be able to enable notifications", diff --git a/packages/neon/lib/l10n/localizations.dart b/packages/neon/lib/l10n/localizations.dart index 591b1aed..d57084e0 100644 --- a/packages/neon/lib/l10n/localizations.dart +++ b/packages/neon/lib/l10n/localizations.dart @@ -401,6 +401,12 @@ abstract class AppLocalizations { /// **'OLED theme as dark theme'** String get globalOptionsThemeOLEDAsDark; + /// No description provided for @globalOptionsThemeKeepOriginalAccentColor. + /// + /// In en, this message translates to: + /// **'Keep the original accent color'** + String get globalOptionsThemeKeepOriginalAccentColor; + /// No description provided for @globalOptionsPushNotificationsNotice. /// /// In en, this message translates to: diff --git a/packages/neon/lib/l10n/localizations_en.dart b/packages/neon/lib/l10n/localizations_en.dart index f7883e86..76d8f9e4 100644 --- a/packages/neon/lib/l10n/localizations_en.dart +++ b/packages/neon/lib/l10n/localizations_en.dart @@ -171,6 +171,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get globalOptionsThemeOLEDAsDark => 'OLED theme as dark theme'; + @override + String get globalOptionsThemeKeepOriginalAccentColor => 'Keep the original accent color'; + @override String get globalOptionsPushNotificationsNotice => 'External services are used for delivering push notifications. While the content is encrypted and can only be read by this app, extracting metadata like the time and count of notifications is still possible.'; diff --git a/packages/neon/lib/src/app.dart b/packages/neon/lib/src/app.dart index c277b034..87c6af5a 100644 --- a/packages/neon/lib/src/app.dart +++ b/packages/neon/lib/src/app.dart @@ -92,29 +92,34 @@ class _NeonAppState extends State with WidgetsBindingObserver { @override Widget build(final BuildContext context) => StreamBuilder( stream: _platformBrightness, - builder: (final context, final platformBrightnessSnapshot) => StreamBuilder( - stream: widget.globalOptions.themeMode.stream, - builder: (final context, final themeModeSnapshot) => StreamBuilder( - stream: widget.globalOptions.themeOLEDAsDark.stream, - builder: (final context, final themeOLEDAsDarkSnapshot) { - if (!platformBrightnessSnapshot.hasData || - !themeOLEDAsDarkSnapshot.hasData || - !themeModeSnapshot.hasData) { - return Container(); - } - return MaterialApp( - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - navigatorKey: _navigatorKey, - theme: getThemeFromNextcloudTheme( - _userTheme, - themeModeSnapshot.data!, - platformBrightnessSnapshot.data!, - oledAsDark: themeOLEDAsDarkSnapshot.data!, - ), - home: Container(), - ); - }, + 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( + _userTheme, + themeMode, + platformBrightnessSnapshot.data!, + oledAsDark: themeOLEDAsDark, + keepOriginalAccentColor: themeKeepOriginalAccentColor, + ), + home: Container(), + ); + }, + ), ), ), ); diff --git a/packages/neon/lib/src/apps/notes/pages/note.dart b/packages/neon/lib/src/apps/notes/pages/note.dart index ba65449a..1e22f298 100644 --- a/packages/neon/lib/src/apps/notes/pages/note.dart +++ b/packages/neon/lib/src/apps/notes/pages/note.dart @@ -85,120 +85,112 @@ class _NotesNotePageState extends State { } @override - Widget build(final BuildContext context) { - final titleInputBorder = UnderlineInputBorder( - borderSide: BorderSide( - color: Theme.of(context).colorScheme.onPrimary, - ), - ); - return WillPopScope( - onWillPop: () async { - _update(); - - if (Provider.of(context, listen: false).canUseWakelock) { - await Wakelock.disable(); - } - return true; - }, - child: Scaffold( - appBar: AppBar( - titleSpacing: 0, - title: TextField( - controller: _titleController, - focusNode: _titleFocusNode, - style: TextStyle( - fontSize: 22, - color: Theme.of(context).colorScheme.onPrimary, - ), - cursorColor: Theme.of(context).colorScheme.onPrimary, - decoration: InputDecoration( - isDense: true, - contentPadding: EdgeInsets.zero, - border: titleInputBorder, - focusedBorder: titleInputBorder, - ), - ), - actions: [ - IconButton( - icon: Icon( - _synced ? Icons.check : Icons.sync, + Widget build(final BuildContext context) => WillPopScope( + onWillPop: () async { + _update(); + + if (Provider.of(context, listen: false).canUseWakelock) { + await Wakelock.disable(); + } + return true; + }, + child: Scaffold( + resizeToAvoidBottomInset: false, + appBar: AppBar( + titleSpacing: 0, + title: TextField( + controller: _titleController, + focusNode: _titleFocusNode, + style: const TextStyle( + fontSize: 22, ), - onPressed: _update, - ), - IconButton( - icon: Icon( - _showEditor ? Icons.visibility : Icons.edit, + decoration: const InputDecoration( + isDense: true, + contentPadding: EdgeInsets.zero, + border: UnderlineInputBorder(), + focusedBorder: UnderlineInputBorder(), ), - onPressed: () { - setState(() { - _showEditor = !_showEditor; - }); - if (_showEditor) { - _focusEditor(); - } else { - // Prevent the cursor going back to the title field - _contentFocusNode.unfocus(); - _titleFocusNode.unfocus(); - } - }, ), - IconButton( - onPressed: () async { - final result = await showDialog( - context: context, - builder: (final context) => NotesSelectCategoryDialog( - bloc: widget.bloc, - note: _note, - ), - ); - if (result != null) { - _update(result); - } - }, - icon: Icon( - MdiIcons.tag, - color: _note.category.isNotEmpty ? NotesCategoryColor.compute(_note.category) : null, + actions: [ + IconButton( + icon: Icon( + _synced ? Icons.check : Icons.sync, + ), + onPressed: _update, ), - ), - ], - ), - body: GestureDetector( - onTap: () { - setState(() { - _showEditor = true; - }); - }, - child: Container( - padding: EdgeInsets.symmetric( - vertical: 10, - horizontal: _showEditor ? 20 : 10, - ), - color: Colors.transparent, - constraints: const BoxConstraints.expand(), - child: _showEditor - ? TextField( - controller: _contentController, - focusNode: _contentFocusNode, - keyboardType: TextInputType.multiline, - maxLines: null, - decoration: const InputDecoration( - border: InputBorder.none, + IconButton( + icon: Icon( + _showEditor ? Icons.visibility : Icons.edit, + ), + onPressed: () { + setState(() { + _showEditor = !_showEditor; + }); + if (_showEditor) { + _focusEditor(); + } else { + // Prevent the cursor going back to the title field + _contentFocusNode.unfocus(); + _titleFocusNode.unfocus(); + } + }, + ), + IconButton( + onPressed: () async { + final result = await showDialog( + context: context, + builder: (final context) => NotesSelectCategoryDialog( + bloc: widget.bloc, + note: _note, + ), + ); + if (result != null) { + _update(result); + } + }, + icon: Icon( + MdiIcons.tag, + color: _note.category.isNotEmpty ? NotesCategoryColor.compute(_note.category) : null, + ), + ), + ], + ), + body: GestureDetector( + onTap: () { + setState(() { + _showEditor = true; + }); + }, + child: Container( + padding: EdgeInsets.symmetric( + vertical: 10, + horizontal: _showEditor ? 20 : 10, + ), + color: Colors.transparent, + constraints: const BoxConstraints.expand(), + child: _showEditor + ? TextField( + controller: _contentController, + focusNode: _contentFocusNode, + keyboardType: TextInputType.multiline, + maxLines: null, + decoration: const InputDecoration( + border: InputBorder.none, + ), + ) + : MarkdownBody( + data: _contentController.text, + onTapLink: (final text, final href, final title) async { + if (href != null) { + await launchUrlString( + href, + mode: LaunchMode.externalApplication, + ); + } + }, ), - ) - : MarkdownBody( - data: _contentController.text, - onTapLink: (final text, final href, final title) async { - if (href != null) { - await launchUrlString( - href, - mode: LaunchMode.externalApplication, - ); - } - }, - ), + ), ), ), - ), - ); - } + ); } diff --git a/packages/neon/lib/src/apps/notifications/pages/main.dart b/packages/neon/lib/src/apps/notifications/pages/main.dart index 0efabdeb..2a82c727 100644 --- a/packages/neon/lib/src/apps/notifications/pages/main.dart +++ b/packages/neon/lib/src/apps/notifications/pages/main.dart @@ -112,6 +112,7 @@ class _NotificationsMainPageState extends State { ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.red, + foregroundColor: Theme.of(context).colorScheme.onPrimary, ), onPressed: () { Navigator.of(context).pop(); diff --git a/packages/neon/lib/src/pages/home/home.dart b/packages/neon/lib/src/pages/home/home.dart index 463f300a..04d7dc14 100644 --- a/packages/neon/lib/src/pages/home/home.dart +++ b/packages/neon/lib/src/pages/home/home.dart @@ -280,6 +280,7 @@ class _HomePageState extends State with tray.TrayListener, WindowListe ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.red, + foregroundColor: Theme.of(context).colorScheme.onPrimary, ), onPressed: () { Navigator.of(context).pop(); @@ -427,7 +428,7 @@ class _HomePageState extends State with tray.TrayListener, WindowListe } return DrawerHeader( decoration: BoxDecoration( - color: Theme.of(context).appBarTheme.backgroundColor, + color: Theme.of(context).colorScheme.primary, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -461,7 +462,7 @@ class _HomePageState extends State with tray.TrayListener, WindowListe child: DropdownButton( isExpanded: true, dropdownColor: Theme.of(context).colorScheme.primary, - iconEnabledColor: Theme.of(context).appBarTheme.foregroundColor, + iconEnabledColor: Theme.of(context).colorScheme.onBackground, value: widget.account.id, items: accounts .map>( @@ -607,6 +608,7 @@ class _HomePageState extends State with tray.TrayListener, WindowListe ); return Scaffold( + resizeToAvoidBottomInset: false, body: Row( children: [ if (navigationMode == NavigationMode.drawerAlwaysVisible) ...[ diff --git a/packages/neon/lib/src/pages/settings/settings.dart b/packages/neon/lib/src/pages/settings/settings.dart index 651ab389..3a7f710f 100644 --- a/packages/neon/lib/src/pages/settings/settings.dart +++ b/packages/neon/lib/src/pages/settings/settings.dart @@ -105,6 +105,9 @@ class _SettingsPageState extends State { CheckBoxSettingsTile( option: globalOptions.themeOLEDAsDark, ), + CheckBoxSettingsTile( + option: globalOptions.themeKeepOriginalAccentColor, + ), ], ), SettingsCategory( diff --git a/packages/neon/lib/src/utils/confirmation_dialog.dart b/packages/neon/lib/src/utils/confirmation_dialog.dart index 5a037187..d35a51e4 100644 --- a/packages/neon/lib/src/utils/confirmation_dialog.dart +++ b/packages/neon/lib/src/utils/confirmation_dialog.dart @@ -10,6 +10,7 @@ Future showConfirmationDialog(final BuildContext context, final String tit ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.red, + foregroundColor: Theme.of(context).colorScheme.onPrimary, ), onPressed: () { Navigator.of(context).pop(false); @@ -19,6 +20,7 @@ Future showConfirmationDialog(final BuildContext context, final String tit ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.green, + foregroundColor: Theme.of(context).colorScheme.onPrimary, ), onPressed: () { Navigator.of(context).pop(true); diff --git a/packages/neon/lib/src/utils/global_options.dart b/packages/neon/lib/src/utils/global_options.dart index 17d19977..bf5b3969 100644 --- a/packages/neon/lib/src/utils/global_options.dart +++ b/packages/neon/lib/src/utils/global_options.dart @@ -60,6 +60,7 @@ class GlobalOptions { late final List