Browse Source

Merge pull request #461 from Leptopoda/feature/theme_branding

Feature/theme branding
pull/462/head
Nikolas Rimikis 1 year ago committed by GitHub
parent
commit
cfdadf5248
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      packages/app/integration_test/screenshot_test.dart
  2. 17
      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. 10
      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. 1
      packages/neon/neon/lib/src/settings/widgets/settings_list.dart
  13. 66
      packages/neon/neon/lib/src/theme/branding.dart
  14. 58
      packages/neon/neon/lib/src/theme/color_scheme.dart
  15. 41
      packages/neon/neon/lib/src/theme/colors.dart
  16. 64
      packages/neon/neon/lib/src/theme/dialog.dart
  17. 58
      packages/neon/neon/lib/src/theme/neon.dart
  18. 73
      packages/neon/neon/lib/src/theme/theme.dart
  19. 5
      packages/neon/neon/lib/src/utils/confirmation_dialog.dart
  20. 6
      packages/neon/neon/lib/src/utils/push_utils.dart
  21. 164
      packages/neon/neon/lib/src/utils/theme.dart
  22. 4
      packages/neon/neon/lib/src/widgets/image_wrapper.dart
  23. 1
      packages/neon/neon/lib/src/widgets/list_view.dart
  24. 10
      packages/neon/neon/lib/src/widgets/user_avatar.dart
  25. 5
      packages/neon/neon/lib/theme.dart
  26. 1
      packages/neon/neon_files/lib/neon_files.dart
  27. 1
      packages/neon/neon_files/lib/pages/details.dart
  28. 2
      packages/neon/neon_files/lib/widgets/browser_view.dart
  29. 1
      packages/neon/neon_files/lib/widgets/file_preview.dart
  30. 1
      packages/neon/neon_news/lib/widgets/feed_icon.dart

2
packages/app/integration_test/screenshot_test.dart

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

17
packages/app/lib/branding.dart

@ -1,7 +1,14 @@
import 'dart:ui';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:neon/models.dart';
import 'package:neon/theme.dart';
final neonTheme = NeonTheme(
branding: branding,
colorScheme: colorScheme,
);
Branding getNeonBranding() => Branding(
final branding = Branding(
name: 'Nextcloud Neon',
logo: SvgPicture.asset(
'assets/logo.svg',
@ -9,4 +16,8 @@ Branding getNeonBranding() => Branding(
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 {
await runNeon(
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/app_implementation.dart';
export 'package:neon/src/models/branding.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/models/account.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/theme/neon.dart';
import 'package:neon/src/utils/global_options.dart';
import 'package:neon/src/utils/request_manager.dart';
import 'package:package_info_plus/package_info_plus.dart';
@ -24,7 +24,7 @@ late final String neonUserAgent;
Future runNeon({
required final Iterable<AppImplementation> Function(SharedPreferences, RequestManager, NeonPlatform)
getAppImplementations,
required final Branding branding,
required final NeonTheme theme,
final WidgetsBinding? bindingOverride,
final SharedPreferences? sharedPreferencesOverride,
final Account? account,
@ -115,11 +115,8 @@ Future runNeon({
Provider<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/router.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_options.dart';
import 'package:neon/src/utils/localizations.dart';
import 'package:neon/src/utils/push_utils.dart';
import 'package:neon/src/utils/theme.dart';
import 'package:provider/provider.dart';
import 'package:quick_actions/quick_actions.dart';
import 'package:tray_manager/tray_manager.dart' as tray;
@ -28,9 +29,12 @@ import 'package:window_manager/window_manager.dart';
class NeonApp extends StatefulWidget {
const NeonApp({
required this.neonTheme,
super.key,
});
final NeonTheme neonTheme;
@override
State<NeonApp> createState() => _NeonAppState();
}
@ -291,6 +295,7 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
keepOriginalAccentColor: themeKeepOriginalAccentColor,
oledAsDark: themeOLEDAsDark,
appThemes: _appImplementations.map((final a) => a.theme).whereNotNull(),
neonTheme: widget.neonTheme,
);
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;
}

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

@ -1,11 +1,10 @@
import 'package:flutter/material.dart';
import 'package:neon/l10n/localizations.dart';
import 'package:neon/src/models/branding.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/widgets/nextcloud_logo.dart';
import 'package:provider/provider.dart';
class LoginPage extends StatefulWidget {
const LoginPage({
@ -52,7 +51,7 @@ class _LoginPageState extends State<LoginPage> {
@override
Widget build(final BuildContext context) {
final branding = Provider.of<Branding>(context, listen: false);
final branding = Branding.of(context);
return Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(
@ -62,7 +61,6 @@ class _LoginPageState extends State<LoginPage> {
child: ConstrainedBox(
constraints: NeonDialogTheme.of(context).constraints,
child: Scrollbar(
interactive: true,
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 20),
primary: true,
@ -76,10 +74,12 @@ class _LoginPageState extends State<LoginPage> {
const SizedBox(
height: 30,
),
if (branding.showLoginWithNextcloud) ...[
Text(AppLocalizations.of(context).loginWorksWith),
const SizedBox(
height: 20,
),
],
const NextcloudLogo(),
Form(
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/models/account.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/exception.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_builder.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/linear_progress_indicator.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/models/account.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/router.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_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/global_options.dart';
import 'package:neon/src/utils/save_file.dart';
@ -228,7 +228,7 @@ class _SettingsPageState extends State<SettingsPage> {
),
title: Text(AppLocalizations.of(context).licenses),
onTap: () async {
final branding = Provider.of<Branding>(context, listen: false);
final branding = Branding.of(context);
showLicensePage(
context: context,
applicationName: branding.name,

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

@ -14,7 +14,6 @@ class SettingsList extends StatelessWidget {
@override
Widget build(final BuildContext context) => Scrollbar(
interactive: true,
child: ListView(
primary: true,
padding: const EdgeInsets.all(20),

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;
}
}

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

@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import 'package:nextcloud/nextcloud.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;
/// Color of a starred item.
static const Color starredColor = Colors.yellow;
/// Color used to emphasise declining actions.
///
/// Usually used in conjunction with [NcColors.accept].
static const Color decline = Colors.red;
/// Color used to emphasise accepting actions.
///
/// Usually used in conjunction with [NcColors.decline].
static const Color accept = Colors.green;
}
/// [UserStatusType] color mapping.
extension UserStatusTypeColors on UserStatusType {
/// The color for the user status.
Color? get color => switch (this) {
UserStatusType.online => const Color(0xFF49B382),
UserStatusType.away => const Color(0xFFF4A331),
UserStatusType.dnd => const Color(0xFFED484C),
_ => null,
};
}

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),
);
}
}

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

@ -0,0 +1,73 @@
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,
scrollbarTheme: _scrollbarTheme,
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,
);
static const _scrollbarTheme = ScrollbarThemeData(
interactive: true,
);
}

5
packages/neon/neon/lib/src/utils/confirmation_dialog.dart

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:neon/l10n/localizations.dart';
import 'package:neon/theme.dart';
Future<bool> showConfirmationDialog(final BuildContext context, final String title) async =>
await showDialog<bool>(
@ -10,7 +11,7 @@ Future<bool> showConfirmationDialog(final BuildContext context, final String tit
actions: [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
backgroundColor: NcColors.decline,
foregroundColor: Theme.of(context).colorScheme.onPrimary,
),
onPressed: () {
@ -20,7 +21,7 @@ Future<bool> showConfirmationDialog(final BuildContext context, final String tit
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
backgroundColor: NcColors.accept,
foregroundColor: Theme.of(context).colorScheme.onPrimary,
),
onPressed: () {

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

@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
class NeonImageWrapper extends StatelessWidget {
const NeonImageWrapper({
required this.child,
required this.color,
this.color = Colors.white,
this.size,
this.borderRadius,
super.key,
@ -16,7 +16,7 @@ class NeonImageWrapper extends StatelessWidget {
@override
Widget build(final BuildContext context) => ClipRRect(
borderRadius: borderRadius ?? BorderRadius.zero,
borderRadius: borderRadius,
child: ColorFiltered(
colorFilter: ColorFilter.mode(color, BlendMode.dstATop),
child: SizedBox.fromSize(

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

@ -41,7 +41,6 @@ class NeonListView<T> extends StatelessWidget {
),
Expanded(
child: Scrollbar(
interactive: true,
child: ListView(
primary: true,
key: scrollKey != null ? PageStorageKey<String>(scrollKey!) : null,

10
packages/neon/neon/lib/src/widgets/user_avatar.dart

@ -8,6 +8,7 @@ import 'package:neon/src/bloc/result.dart';
import 'package:neon/src/bloc/result_builder.dart';
import 'package:neon/src/blocs/accounts.dart';
import 'package:neon/src/models/account.dart';
import 'package:neon/src/theme/colors.dart';
import 'package:neon/src/widgets/cached_image.dart';
import 'package:nextcloud/nextcloud.dart';
import 'package:provider/provider.dart';
@ -119,7 +120,7 @@ class _UserAvatarState extends State<NeonUserAvatar> {
} else if (result.hasData) {
decoration = BoxDecoration(
shape: BoxShape.circle,
color: _userStatusToColor(result.data!.status),
color: result.data!.status.color,
);
}
@ -136,11 +137,4 @@ class _UserAvatarState extends State<NeonUserAvatar> {
),
);
}
Color? _userStatusToColor(final UserStatusType userStatusType) => switch (userStatusType) {
UserStatusType.online => const Color(0xFF49B382),
UserStatusType.away => const Color(0xFFF4A331),
UserStatusType.dnd => const Color(0xFFED484C),
_ => null,
};
}

5
packages/neon/neon/lib/theme.dart

@ -0,0 +1,5 @@
export 'src/theme/branding.dart';
export 'src/theme/color_scheme.dart';
export 'src/theme/colors.dart';
export 'src/theme/dialog.dart';
export 'src/theme/neon.dart';

1
packages/neon/neon_files/lib/neon_files.dart

@ -17,6 +17,7 @@ import 'package:neon/models.dart';
import 'package:neon/platform.dart';
import 'package:neon/settings.dart';
import 'package:neon/sort_box.dart';
import 'package:neon/theme.dart';
import 'package:neon/utils.dart';
import 'package:neon/widgets.dart';
import 'package:neon_files/l10n/localizations.dart';

1
packages/neon/neon_files/lib/pages/details.dart

@ -17,7 +17,6 @@ class FilesDetailsPage extends StatelessWidget {
title: Text(details.name),
),
body: Scrollbar(
interactive: true,
child: ListView(
primary: true,
children: [

2
packages/neon/neon_files/lib/widgets/browser_view.dart

@ -283,7 +283,7 @@ class _FilesBrowserViewState extends State<FilesBrowserView> {
child: Icon(
Icons.star,
size: 14,
color: Colors.yellow,
color: NcColors.starredColor,
),
),
],

1
packages/neon/neon_files/lib/widgets/file_preview.dart

@ -49,7 +49,6 @@ class FilePreview extends StatelessWidget {
);
if (withBackground) {
return NeonImageWrapper(
color: Colors.white,
borderRadius: borderRadius,
child: child,
);

1
packages/neon/neon_news/lib/widgets/feed_icon.dart

@ -17,7 +17,6 @@ class NewsFeedIcon extends StatelessWidget {
final faviconLink = feed.faviconLink;
return NeonImageWrapper(
color: Colors.white,
size: Size.square(size),
borderRadius: borderRadius,
child: faviconLink != null && faviconLink.isNotEmpty

Loading…
Cancel
Save