Nikolas Rimikis
1 year ago
committed by
GitHub
30 changed files with 423 additions and 235 deletions
@ -1,12 +1,23 @@
|
||||
import 'dart:ui'; |
||||
|
||||
import 'package:flutter_svg/flutter_svg.dart'; |
||||
import 'package:neon/models.dart'; |
||||
|
||||
Branding getNeonBranding() => Branding( |
||||
name: 'Nextcloud Neon', |
||||
logo: SvgPicture.asset( |
||||
'assets/logo.svg', |
||||
width: 100, |
||||
height: 100, |
||||
), |
||||
legalese: 'Copyright © 2023, provokateurin\nUnder GPLv3 license', |
||||
); |
||||
import 'package:neon/theme.dart'; |
||||
|
||||
final neonTheme = NeonTheme( |
||||
branding: branding, |
||||
colorScheme: colorScheme, |
||||
); |
||||
|
||||
final branding = Branding( |
||||
name: 'Nextcloud Neon', |
||||
logo: SvgPicture.asset( |
||||
'assets/logo.svg', |
||||
width: 100, |
||||
height: 100, |
||||
), |
||||
legalese: 'Copyright © 2023, provokateurin\nUnder GPLv3 license', |
||||
); |
||||
|
||||
const colorScheme = NeonColorScheme( |
||||
primary: Color(0xFFF37736), |
||||
); |
||||
|
@ -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'; |
||||
|
@ -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,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, |
||||
}; |
||||
} |
@ -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,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, |
||||
); |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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'; |
Loading…
Reference in new issue