Nikolas Rimikis
1 year ago
20 changed files with 383 additions and 216 deletions
@ -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'; |
||||||
|
@ -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; |
|
||||||
} |
|
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -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, |
||||||
|
); |
||||||
|
} |
@ -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; |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue