Browse Source

neon, app: migrate branding into the theme

pull/461/head
Nikolas Rimikis 1 year ago
parent
commit
fcd14bab60
No known key found for this signature in database
GPG Key ID: 85ED1DE9786A4FF2
  1. 2
      packages/app/integration_test/screenshot_test.dart
  2. 33
      packages/app/lib/branding.dart
  3. 2
      packages/app/lib/main.dart
  4. 1
      packages/neon/neon/lib/models.dart
  5. 9
      packages/neon/neon/lib/neon.dart
  6. 7
      packages/neon/neon/lib/src/app.dart
  7. 16
      packages/neon/neon/lib/src/models/branding.dart
  8. 17
      packages/neon/neon/lib/src/pages/login.dart
  9. 2
      packages/neon/neon/lib/src/pages/login_check_account.dart
  10. 2
      packages/neon/neon/lib/src/pages/login_check_server_status.dart
  11. 4
      packages/neon/neon/lib/src/pages/settings.dart
  12. 66
      packages/neon/neon/lib/src/theme/branding.dart
  13. 58
      packages/neon/neon/lib/src/theme/color_scheme.dart
  14. 16
      packages/neon/neon/lib/src/theme/colors.dart
  15. 64
      packages/neon/neon/lib/src/theme/dialog.dart
  16. 58
      packages/neon/neon/lib/src/theme/neon.dart
  17. 68
      packages/neon/neon/lib/src/theme/theme.dart
  18. 6
      packages/neon/neon/lib/src/utils/push_utils.dart
  19. 164
      packages/neon/neon/lib/src/utils/theme.dart
  20. 4
      packages/neon/neon/lib/theme.dart

2
packages/app/integration_test/screenshot_test.dart

@ -95,7 +95,7 @@ Future runTestApp(
}) async { }) async {
await runNeon( await runNeon(
getAppImplementations: getAppImplementations, getAppImplementations: getAppImplementations,
branding: getNeonBranding(), theme: neonTheme,
bindingOverride: binding, bindingOverride: binding,
sharedPreferencesOverride: MemorySharedPreferences(), sharedPreferencesOverride: MemorySharedPreferences(),
account: account, account: account,

33
packages/app/lib/branding.dart

@ -1,12 +1,23 @@
import 'dart:ui';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:neon/models.dart'; import 'package:neon/theme.dart';
Branding getNeonBranding() => Branding( final neonTheme = NeonTheme(
name: 'Nextcloud Neon', branding: branding,
logo: SvgPicture.asset( colorScheme: colorScheme,
'assets/logo.svg', );
width: 100,
height: 100, final branding = Branding(
), name: 'Nextcloud Neon',
legalese: 'Copyright © 2023, provokateurin\nUnder GPLv3 license', logo: SvgPicture.asset(
); 'assets/logo.svg',
width: 100,
height: 100,
),
legalese: 'Copyright © 2023, provokateurin\nUnder GPLv3 license',
);
const colorScheme = NeonColorScheme(
primary: Color(0xFFF37736),
);

2
packages/app/lib/main.dart

@ -5,6 +5,6 @@ import 'package:neon/neon.dart';
Future main() async { Future main() async {
await runNeon( await runNeon(
getAppImplementations: getAppImplementations, getAppImplementations: getAppImplementations,
branding: getNeonBranding(), theme: neonTheme,
); );
} }

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

@ -1,4 +1,3 @@
export 'package:neon/src/models/account.dart'; export 'package:neon/src/models/account.dart';
export 'package:neon/src/models/app_implementation.dart'; export 'package:neon/src/models/app_implementation.dart';
export 'package:neon/src/models/branding.dart';
export 'package:neon/src/models/notifications_interface.dart'; export 'package:neon/src/models/notifications_interface.dart';

9
packages/neon/neon/lib/neon.dart

@ -10,8 +10,8 @@ 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/models/branding.dart';
import 'package:neon/src/platform/platform.dart'; import 'package:neon/src/platform/platform.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';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
@ -24,7 +24,7 @@ late final String neonUserAgent;
Future runNeon({ Future runNeon({
required final Iterable<AppImplementation> Function(SharedPreferences, RequestManager, NeonPlatform) required final Iterable<AppImplementation> Function(SharedPreferences, RequestManager, NeonPlatform)
getAppImplementations, getAppImplementations,
required final Branding branding, required final NeonTheme theme,
final WidgetsBinding? bindingOverride, final WidgetsBinding? bindingOverride,
final SharedPreferences? sharedPreferencesOverride, final SharedPreferences? sharedPreferencesOverride,
final Account? account, final Account? account,
@ -115,11 +115,8 @@ Future runNeon({
Provider<PackageInfo>( Provider<PackageInfo>(
create: (final _) => packageInfo, create: (final _) => packageInfo,
), ),
Provider<Branding>(
create: (final _) => branding,
),
], ],
child: const NeonApp(), child: NeonApp(neonTheme: theme),
), ),
); );
} }

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

@ -16,11 +16,12 @@ import 'package:neon/src/models/push_notification.dart';
import 'package:neon/src/platform/platform.dart'; import 'package:neon/src/platform/platform.dart';
import 'package:neon/src/router.dart'; import 'package:neon/src/router.dart';
import 'package:neon/src/settings/widgets/option_builder.dart'; import 'package:neon/src/settings/widgets/option_builder.dart';
import 'package:neon/src/theme/neon.dart';
import 'package:neon/src/theme/theme.dart';
import 'package:neon/src/utils/global.dart'; import 'package:neon/src/utils/global.dart';
import 'package:neon/src/utils/global_options.dart'; import 'package:neon/src/utils/global_options.dart';
import 'package:neon/src/utils/localizations.dart'; import 'package:neon/src/utils/localizations.dart';
import 'package:neon/src/utils/push_utils.dart'; import 'package:neon/src/utils/push_utils.dart';
import 'package:neon/src/utils/theme.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:quick_actions/quick_actions.dart'; import 'package:quick_actions/quick_actions.dart';
import 'package:tray_manager/tray_manager.dart' as tray; import 'package:tray_manager/tray_manager.dart' as tray;
@ -28,9 +29,12 @@ import 'package:window_manager/window_manager.dart';
class NeonApp extends StatefulWidget { class NeonApp extends StatefulWidget {
const NeonApp({ const NeonApp({
required this.neonTheme,
super.key, super.key,
}); });
final NeonTheme neonTheme;
@override @override
State<NeonApp> createState() => _NeonAppState(); State<NeonApp> createState() => _NeonAppState();
} }
@ -291,6 +295,7 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
keepOriginalAccentColor: themeKeepOriginalAccentColor, keepOriginalAccentColor: themeKeepOriginalAccentColor,
oledAsDark: themeOLEDAsDark, oledAsDark: themeOLEDAsDark,
appThemes: _appImplementations.map((final a) => a.theme).whereNotNull(), appThemes: _appImplementations.map((final a) => a.theme).whereNotNull(),
neonTheme: widget.neonTheme,
); );
return MaterialApp.router( return MaterialApp.router(

16
packages/neon/neon/lib/src/models/branding.dart

@ -1,16 +0,0 @@
import 'package:flutter/widgets.dart';
@immutable
class Branding {
const Branding({
required this.name,
required this.logo,
this.legalese,
});
final String name;
final Widget logo;
final String? legalese;
}

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

@ -1,11 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:neon/l10n/localizations.dart'; import 'package:neon/l10n/localizations.dart';
import 'package:neon/src/models/branding.dart';
import 'package:neon/src/router.dart'; import 'package:neon/src/router.dart';
import 'package:neon/src/utils/theme.dart'; import 'package:neon/src/theme/branding.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({
@ -52,7 +51,7 @@ class _LoginPageState extends State<LoginPage> {
@override @override
Widget build(final BuildContext context) { Widget build(final BuildContext context) {
final branding = Provider.of<Branding>(context, listen: false); final branding = Branding.of(context);
return Scaffold( return Scaffold(
resizeToAvoidBottomInset: true, resizeToAvoidBottomInset: true,
appBar: AppBar( appBar: AppBar(
@ -76,10 +75,12 @@ class _LoginPageState extends State<LoginPage> {
const SizedBox( const SizedBox(
height: 30, height: 30,
), ),
Text(AppLocalizations.of(context).loginWorksWith), if (branding.showLoginWithNextcloud) ...[
const SizedBox( Text(AppLocalizations.of(context).loginWorksWith),
height: 20, const SizedBox(
), height: 20,
),
],
const NextcloudLogo(), const NextcloudLogo(),
Form( Form(
key: _formKey, key: _formKey,

2
packages/neon/neon/lib/src/pages/login_check_account.dart

@ -6,7 +6,7 @@ import 'package:neon/src/blocs/accounts.dart';
import 'package:neon/src/blocs/login_check_account.dart'; import 'package:neon/src/blocs/login_check_account.dart';
import 'package:neon/src/models/account.dart'; import 'package:neon/src/models/account.dart';
import 'package:neon/src/router.dart'; import 'package:neon/src/router.dart';
import 'package:neon/src/utils/theme.dart'; import 'package:neon/src/theme/dialog.dart';
import 'package:neon/src/widgets/account_tile.dart'; import 'package:neon/src/widgets/account_tile.dart';
import 'package:neon/src/widgets/exception.dart'; import 'package:neon/src/widgets/exception.dart';
import 'package:neon/src/widgets/linear_progress_indicator.dart'; import 'package:neon/src/widgets/linear_progress_indicator.dart';

2
packages/neon/neon/lib/src/pages/login_check_server_status.dart

@ -3,7 +3,7 @@ import 'package:neon/l10n/localizations.dart';
import 'package:neon/src/bloc/result.dart'; import 'package:neon/src/bloc/result.dart';
import 'package:neon/src/bloc/result_builder.dart'; import 'package:neon/src/bloc/result_builder.dart';
import 'package:neon/src/blocs/login_check_server_status.dart'; import 'package:neon/src/blocs/login_check_server_status.dart';
import 'package:neon/src/utils/theme.dart'; import 'package:neon/src/theme/dialog.dart';
import 'package:neon/src/widgets/exception.dart'; import 'package:neon/src/widgets/exception.dart';
import 'package:neon/src/widgets/linear_progress_indicator.dart'; import 'package:neon/src/widgets/linear_progress_indicator.dart';
import 'package:neon/src/widgets/validation_tile.dart'; import 'package:neon/src/widgets/validation_tile.dart';

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

@ -8,7 +8,6 @@ import 'package:neon/l10n/localizations.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/models/app_implementation.dart'; import 'package:neon/src/models/app_implementation.dart';
import 'package:neon/src/models/branding.dart';
import 'package:neon/src/platform/platform.dart'; import 'package:neon/src/platform/platform.dart';
import 'package:neon/src/router.dart'; import 'package:neon/src/router.dart';
import 'package:neon/src/settings/widgets/account_settings_tile.dart'; import 'package:neon/src/settings/widgets/account_settings_tile.dart';
@ -19,6 +18,7 @@ import 'package:neon/src/settings/widgets/settings_category.dart';
import 'package:neon/src/settings/widgets/settings_list.dart'; import 'package:neon/src/settings/widgets/settings_list.dart';
import 'package:neon/src/settings/widgets/settings_tile.dart'; import 'package:neon/src/settings/widgets/settings_tile.dart';
import 'package:neon/src/settings/widgets/text_settings_tile.dart'; import 'package:neon/src/settings/widgets/text_settings_tile.dart';
import 'package:neon/src/theme/branding.dart';
import 'package:neon/src/utils/confirmation_dialog.dart'; import 'package:neon/src/utils/confirmation_dialog.dart';
import 'package:neon/src/utils/global_options.dart'; import 'package:neon/src/utils/global_options.dart';
import 'package:neon/src/utils/save_file.dart'; import 'package:neon/src/utils/save_file.dart';
@ -228,7 +228,7 @@ class _SettingsPageState extends State<SettingsPage> {
), ),
title: Text(AppLocalizations.of(context).licenses), title: Text(AppLocalizations.of(context).licenses),
onTap: () async { onTap: () async {
final branding = Provider.of<Branding>(context, listen: false); final branding = Branding.of(context);
showLicensePage( showLicensePage(
context: context, context: context,
applicationName: branding.name, applicationName: branding.name,

66
packages/neon/neon/lib/src/theme/branding.dart

@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
import 'package:neon/src/theme/neon.dart';
/// Custom app branding
///
/// Descendant widgets obtain the current [Branding] object using
/// `Branding.of(context)`. Instances of [Branding] can be customized with
/// [Branding.copyWith].
@immutable
class Branding {
/// Creates a custom branding
const Branding({
required this.name,
required this.logo,
this.legalese,
this.showLoginWithNextcloud = false,
});
/// App name
final String name;
/// Logo of the app shown on various places in the app.
final Widget logo;
/// A string to show in small print.
///
/// Typically this is a copyright notice shown as the [AboutDialog.applicationLegalese].
final String? legalese;
/// Whether to show the Nextcloud logo on the LoginPage
final bool showLoginWithNextcloud;
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
Branding copyWith({
final String? name,
final Widget? logo,
final String? legalese,
}) =>
Branding(
name: name ?? this.name,
logo: logo ?? this.logo,
legalese: legalese ?? this.legalese,
);
/// The data from the closest [Branding] instance given the build context.
static Branding of(final BuildContext context) => Theme.of(context).extension<NeonTheme>()!.branding;
@override
int get hashCode => Object.hashAll([
name,
logo,
legalese,
]);
@override
bool operator ==(final Object other) {
if (identical(this, other)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
return other is Branding && name == other.name && logo == other.logo && legalese == other.legalese;
}
}

58
packages/neon/neon/lib/src/theme/color_scheme.dart

@ -0,0 +1,58 @@
import 'package:flutter/material.dart';
import 'package:neon/src/theme/colors.dart';
import 'package:neon/src/theme/neon.dart';
/// A ColorScheme used in the [NeonTheme].
@immutable
class NeonColorScheme {
const NeonColorScheme({
this.primary = NcColors.primary,
});
/// Primary color used throughout the app.
///
/// See [ColorScheme.primary]
final Color primary;
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
NeonColorScheme copyWith({
final Color? primary,
final Color? oledBackground,
}) =>
NeonColorScheme(
primary: primary ?? this.primary,
);
/// The data from the closest [NeonColorScheme] instance given the build context.
static NeonColorScheme of(final BuildContext context) => Theme.of(context).extension<NeonTheme>()!.colorScheme;
/// Linearly interpolate between two [NeonColorScheme]s.
///
/// {@macro dart.ui.shadow.lerp}
// ignore: prefer_constructors_over_static_methods
static NeonColorScheme lerp(final NeonColorScheme a, final NeonColorScheme b, final double t) {
if (identical(a, b)) {
return a;
}
return NeonColorScheme(
primary: Color.lerp(a.primary, b.primary, t)!,
);
}
@override
int get hashCode => Object.hashAll([
primary,
]);
@override
bool operator ==(final Object other) {
if (identical(this, other)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
return other is NeonColorScheme && other.primary == primary;
}
}

16
packages/neon/neon/lib/src/theme/colors.dart

@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
/// [Color] constants which represent Nextcloud's
/// [color palette](https://docs.nextcloud.com/server/latest/developer_manual/design/foundations.html#color).
abstract final class NcColors {
/// Nextcloud blue.
///
/// The default primary clolor as specified by the
/// [design guidlines](https://docs.nextcloud.com/server/latest/developer_manual/design/foundations.html#primary-color).
static const Color primary = Color(0xFF0082C9);
/// The [ColorScheme.background] color used on OLED devices.
///
/// This color is only used at the users discretion.
static const Color oledBackground = Colors.black;
}

64
packages/neon/neon/lib/src/theme/dialog.dart

@ -0,0 +1,64 @@
import 'package:flutter/material.dart';
import 'package:neon/src/theme/neon.dart';
import 'package:neon/src/widgets/dialog.dart';
/// Defines a theme for [NeonDialog] widgets.
///
/// Descendant widgets obtain the current [NeonDialogTheme] object using
/// `NeonDialogTheme.of(context)`. Instances of [NeonDialogTheme] can be customized with
/// [NeonDialogTheme.copyWith].
@immutable
class NeonDialogTheme {
/// Creates a dialog theme that can be used for [NeonTheme.dialogTheme].
const NeonDialogTheme({
this.constraints = const BoxConstraints(
minWidth: 280,
maxWidth: 560,
),
});
/// Used to configure the [BoxConstraints] for the [NeonDialog] widget.
///
/// This value should also be used on [Dialog.fullscreen] and other similar pages.
/// By default it follows the default [m3 dialog specification](https://m3.material.io/components/dialogs/specs).
final BoxConstraints constraints;
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
NeonDialogTheme copyWith({
final BoxConstraints? constraints,
}) =>
NeonDialogTheme(
constraints: constraints ?? this.constraints,
);
/// The data from the closest [NeonDialogTheme] instance given the build context.
static NeonDialogTheme of(final BuildContext context) => Theme.of(context).extension<NeonTheme>()!.dialogTheme;
/// Linearly interpolate between two [NeonDialogTheme]s.
///
/// {@macro dart.ui.shadow.lerp}
// ignore: prefer_constructors_over_static_methods
static NeonDialogTheme lerp(final NeonDialogTheme a, final NeonDialogTheme b, final double t) {
if (identical(a, b)) {
return a;
}
return NeonDialogTheme(
constraints: BoxConstraints.lerp(a.constraints, b.constraints, t)!,
);
}
@override
int get hashCode => constraints.hashCode;
@override
bool operator ==(final Object other) {
if (identical(this, other)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
return other is NeonDialogTheme && other.constraints == constraints;
}
}

58
packages/neon/neon/lib/src/theme/neon.dart

@ -0,0 +1,58 @@
import 'package:flutter/material.dart';
import 'package:neon/src/theme/branding.dart';
import 'package:neon/src/theme/color_scheme.dart';
import 'package:neon/src/theme/dialog.dart';
import 'package:neon/src/widgets/dialog.dart';
/// Defines the configuration of the overall visual [Theme] for the app
/// or a widget subtree within the app.
///
/// It is typically only needed to provide a [branding].
@immutable
class NeonTheme extends ThemeExtension<NeonTheme> {
/// Create a [NeonTheme] that's used to configure a [Theme].
const NeonTheme({
required this.branding,
this.colorScheme = const NeonColorScheme(),
this.dialogTheme = const NeonDialogTheme(),
});
/// A theme for customizing the Branding of the app.
///
/// This is the value returned from [Branding.of].
final Branding branding;
/// A color scheme for customizing the default appearance of the app.
///
/// This is the value returned from [NeonColorScheme.of].
final NeonColorScheme colorScheme;
/// A theme for customizing the visual properties of [NeonDialog]s.
///
/// This is the value returned from [NeonDialogTheme.of].
final NeonDialogTheme dialogTheme;
@override
NeonTheme copyWith({
final Branding? branding,
final NeonColorScheme? colorScheme,
final NeonDialogTheme? dialogTheme,
}) =>
NeonTheme(
branding: branding ?? this.branding,
colorScheme: colorScheme ?? this.colorScheme,
dialogTheme: dialogTheme ?? this.dialogTheme,
);
@override
NeonTheme lerp(final NeonTheme? other, final double t) {
if (other is! NeonTheme) {
return this;
}
return NeonTheme(
branding: branding,
colorScheme: NeonColorScheme.lerp(colorScheme, other.colorScheme, t),
dialogTheme: NeonDialogTheme.lerp(dialogTheme, other.dialogTheme, t),
);
}
}

68
packages/neon/neon/lib/src/theme/theme.dart

@ -0,0 +1,68 @@
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
import 'package:neon/src/theme/colors.dart';
import 'package:neon/src/theme/neon.dart';
import 'package:neon/src/utils/hex_color.dart';
import 'package:nextcloud/nextcloud.dart';
@internal
@immutable
class AppTheme {
const AppTheme(
this.nextcloudTheme, {
required this.neonTheme,
final bool keepOriginalAccentColor = false,
this.oledAsDark = false,
this.appThemes,
}) : keepOriginalAccentColor = nextcloudTheme == null || keepOriginalAccentColor;
final CoreServerCapabilities_Ocs_Data_Capabilities_Theming? nextcloudTheme;
final bool keepOriginalAccentColor;
final bool oledAsDark;
final Iterable<ThemeExtension>? appThemes;
final NeonTheme neonTheme;
ColorScheme _buildColorScheme(final Brightness brightness) {
final primary = nextcloudTheme?.color != null ? HexColor(nextcloudTheme!.color!) : neonTheme.colorScheme.primary;
final keepOriginalAccentColorOverride = keepOriginalAccentColor ? primary : null;
final oledBackgroundOverride = oledAsDark && brightness == Brightness.dark ? NcColors.oledBackground : null;
return ColorScheme.fromSeed(
seedColor: primary,
brightness: brightness,
).copyWith(
background: oledBackgroundOverride,
primary: keepOriginalAccentColorOverride,
secondary: keepOriginalAccentColorOverride,
);
}
ThemeData _getTheme(final Brightness brightness) {
final colorScheme = _buildColorScheme(brightness);
return ThemeData(
useMaterial3: true,
colorScheme: colorScheme,
scaffoldBackgroundColor: colorScheme.background,
cardColor: colorScheme.background, // For LicensePage
snackBarTheme: _snackBarTheme,
dividerTheme: _dividerTheme,
extensions: [
neonTheme,
...?appThemes,
],
);
}
ThemeData get lightTheme => _getTheme(Brightness.light);
ThemeData get darkTheme => _getTheme(Brightness.dark);
static const _snackBarTheme = SnackBarThemeData(
behavior: SnackBarBehavior.floating,
);
static const _dividerTheme = DividerThemeData(
thickness: 1.5,
space: 30,
);
}

6
packages/neon/neon/lib/src/utils/push_utils.dart

@ -14,10 +14,10 @@ import 'package:neon/src/models/account.dart';
import 'package:neon/src/models/push_notification.dart'; import 'package:neon/src/models/push_notification.dart';
import 'package:neon/src/platform/platform.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/theme/colors.dart';
import 'package:neon/src/utils/global.dart'; import 'package:neon/src/utils/global.dart';
import 'package:neon/src/utils/localizations.dart'; import 'package:neon/src/utils/localizations.dart';
import 'package:neon/src/utils/request_manager.dart'; import 'package:neon/src/utils/request_manager.dart';
import 'package:neon/src/utils/theme.dart';
import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/nextcloud.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@ -124,7 +124,7 @@ class PushUtils {
Canvas(recorder) Canvas(recorder)
..scale(scale) ..scale(scale)
..drawPicture(pictureInfo.picture) ..drawPicture(pictureInfo.picture)
..drawColor(themePrimaryColor, BlendMode.srcIn); ..drawColor(NcColors.primary, BlendMode.srcIn);
pictureInfo.picture.dispose(); pictureInfo.picture.dispose();
@ -164,7 +164,7 @@ class PushUtils {
icon: '@mipmap/ic_launcher', icon: '@mipmap/ic_launcher',
largeIcon: largeIconBitmap, largeIcon: largeIconBitmap,
when: when?.millisecondsSinceEpoch, when: when?.millisecondsSinceEpoch,
color: themePrimaryColor, color: NcColors.primary,
category: pushNotification.type == 'voip' ? AndroidNotificationCategory.call : null, category: pushNotification.type == 'voip' ? AndroidNotificationCategory.call : null,
importance: Importance.max, importance: Importance.max,
priority: pushNotification.priority == 'high' priority: pushNotification.priority == 'high'

164
packages/neon/neon/lib/src/utils/theme.dart

@ -1,164 +0,0 @@
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
import 'package:neon/src/utils/hex_color.dart';
import 'package:neon/src/widgets/dialog.dart';
import 'package:nextcloud/nextcloud.dart';
@internal
const themePrimaryColor = Color(0xFFF37736);
@internal
@immutable
class AppTheme {
AppTheme(
this.nextcloudTheme, {
final bool keepOriginalAccentColor = false,
this.oledAsDark = false,
this.appThemes,
}) : keepOriginalAccentColor = nextcloudTheme == null || keepOriginalAccentColor;
final CoreServerCapabilities_Ocs_Data_Capabilities_Theming? nextcloudTheme;
final bool keepOriginalAccentColor;
final bool oledAsDark;
final Iterable<ThemeExtension>? appThemes;
late final _primaryColor = nextcloudTheme?.color != null ? HexColor(nextcloudTheme!.color!) : themePrimaryColor;
late final _keepOriginalAccentColorOverride = keepOriginalAccentColor ? _primaryColor : null;
ColorScheme _buildColorScheme(final Brightness brightness) {
final oledBackgroundOverride = oledAsDark && brightness == Brightness.dark ? Colors.black : null;
return ColorScheme.fromSeed(
seedColor: _primaryColor,
brightness: brightness,
).copyWith(
background: oledBackgroundOverride,
primary: _keepOriginalAccentColorOverride,
secondary: _keepOriginalAccentColorOverride,
);
}
ThemeData _getTheme(final Brightness brightness) {
final colorScheme = _buildColorScheme(brightness);
return ThemeData(
useMaterial3: true,
colorScheme: colorScheme,
scaffoldBackgroundColor: colorScheme.background,
cardColor: colorScheme.background, // For LicensePage
snackBarTheme: _snackBarTheme,
dividerTheme: _dividerTheme,
extensions: [
const NeonTheme(),
...?appThemes,
],
);
}
ThemeData get lightTheme => _getTheme(Brightness.light);
ThemeData get darkTheme => _getTheme(Brightness.dark);
static const _snackBarTheme = SnackBarThemeData(
behavior: SnackBarBehavior.floating,
);
static const _dividerTheme = DividerThemeData(
thickness: 1.5,
space: 30,
);
}
/// Defines the configuration of the overall visual [Theme] for a NeonApp
/// or a widget subtree within the app.
@internal
@immutable
class NeonTheme extends ThemeExtension<NeonTheme> {
/// Create a [NeonTheme] that's used to configure a [Theme].
const NeonTheme({
this.dialogTheme = const NeonDialogTheme(),
});
/// A theme for customizing the visual properties of [NeonDialog]s.
///
/// This is the value returned from [NeonDialogTheme.of].
final NeonDialogTheme dialogTheme;
@override
NeonTheme copyWith({
final NeonDialogTheme? dialogTheme,
}) =>
NeonTheme(
dialogTheme: dialogTheme ?? this.dialogTheme,
);
@override
NeonTheme lerp(final NeonTheme? other, final double t) {
if (other is! NeonTheme) {
return this;
}
return NeonTheme(
dialogTheme: NeonDialogTheme.lerp(dialogTheme, other.dialogTheme, t),
);
}
}
/// Defines a theme for [NeonDialog] widgets.
///
/// Descendant widgets obtain the current [NeonDialogTheme] object using
/// `NeonDialogTheme.of(context)`. Instances of [NeonDialogTheme] can be customized with
/// [NeonDialogTheme.copyWith].
@immutable
class NeonDialogTheme {
/// Creates a dialog theme that can be used for [NeonTheme.dialogTheme].
const NeonDialogTheme({
this.constraints = const BoxConstraints(
minWidth: 280,
maxWidth: 560,
),
});
/// Used to configure the [BoxConstraints] for the [NeonDialog] widget.
///
/// This value should also be used on [Dialog.fullscreen] and other similar pages.
/// By default it follows the default [m3 dialog specification](https://m3.material.io/components/dialogs/specs).
final BoxConstraints constraints;
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
NeonDialogTheme copyWith({
final BoxConstraints? constraints,
}) =>
NeonDialogTheme(
constraints: constraints ?? this.constraints,
);
/// The data from the closest [NeonDialogTheme] instance given the build context.
static NeonDialogTheme of(final BuildContext context) => Theme.of(context).extension<NeonTheme>()!.dialogTheme;
/// Linearly interpolate between two neon dialog themes.
///
/// {@macro dart.ui.shadow.lerp}
// ignore: prefer_constructors_over_static_methods
static NeonDialogTheme lerp(final NeonDialogTheme a, final NeonDialogTheme b, final double t) {
if (identical(a, b)) {
return a;
}
return NeonDialogTheme(
constraints: BoxConstraints.lerp(a.constraints, b.constraints, t)!,
);
}
@override
int get hashCode => constraints.hashCode;
@override
bool operator ==(final Object other) {
if (identical(this, other)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
return other is NeonDialogTheme && other.constraints == constraints;
}
}

4
packages/neon/neon/lib/theme.dart

@ -0,0 +1,4 @@
export 'src/theme/branding.dart';
export 'src/theme/color_scheme.dart';
export 'src/theme/dialog.dart';
export 'src/theme/neon.dart';
Loading…
Cancel
Save