From 61e61866d7a512291151d02cb8a998dce460ece5 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Wed, 11 Jun 2025 00:47:26 +0800 Subject: [PATCH] :sparkles: Dynamic generate color to fit user background image --- lib/screens/account/profile.dart | 34 ++++++- lib/screens/account/profile.g.dart | 128 ++++++++++++++++++++++++++ lib/screens/posts/pub_profile.dart | 36 +++++++- lib/screens/posts/pub_profile.g.dart | 131 +++++++++++++++++++++++++++ lib/services/color.dart | 7 ++ lib/widgets/app_scaffold.dart | 4 +- pubspec.lock | 8 ++ pubspec.yaml | 1 + 8 files changed, 338 insertions(+), 11 deletions(-) create mode 100644 lib/services/color.dart diff --git a/lib/screens/account/profile.dart b/lib/screens/account/profile.dart index fc7bf75..2ab3ab7 100644 --- a/lib/screens/account/profile.dart +++ b/lib/screens/account/profile.dart @@ -4,13 +4,16 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/user.dart'; +import 'package:island/pods/config.dart'; import 'package:island/pods/network.dart'; import 'package:island/pods/userinfo.dart'; +import 'package:island/services/color.dart'; import 'package:island/widgets/account/badge.dart'; import 'package:island/widgets/account/leveling_progress.dart'; import 'package:island/widgets/account/status.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/content/cloud_files.dart'; +import 'package:palette_generator/palette_generator.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -38,6 +41,21 @@ Future> accountBadges(Ref ref, String uname) async { ); } +@riverpod +Future accountAppbarForcegroundColor(Ref ref, String uname) async { + final account = await ref.watch(accountProvider(uname).future); + if (account.profile.background == null) return null; + final palette = await PaletteGenerator.fromImageProvider( + CloudImageWidget.provider( + fileId: account.profile.background!.id, + serverUrl: ref.watch(serverUrlProvider), + ), + ); + final dominantColor = palette.dominantColor?.color; + if (dominantColor == null) return null; + return dominantColor.computeLuminance() > 0.5 ? Colors.black : Colors.white; +} + @RoutePage() class AccountProfileScreen extends HookConsumerWidget { final String name; @@ -49,11 +67,12 @@ class AccountProfileScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final account = ref.watch(accountProvider(name)); + final appbarColor = ref.watch(accountAppbarForcegroundColorProvider(name)); - final iconShadow = Shadow( - color: Colors.black54, + final appbarShadow = Shadow( + color: appbarColor.value?.invert ?? Colors.black54, blurRadius: 5.0, - offset: const Offset(1.0, 1.0), + offset: Offset(1.0, 1.0), ); return account.when( @@ -62,9 +81,13 @@ class AccountProfileScreen extends HookConsumerWidget { body: CustomScrollView( slivers: [ SliverAppBar( + foregroundColor: appbarColor.value, expandedHeight: 180, pinned: true, - leading: PageBackButton(shadows: [iconShadow]), + leading: PageBackButton( + color: appbarColor.value, + shadows: [appbarShadow], + ), flexibleSpace: Stack( children: [ Positioned.fill( @@ -85,8 +108,9 @@ class AccountProfileScreen extends HookConsumerWidget { data.nick, style: TextStyle( color: + appbarColor.value ?? Theme.of(context).appBarTheme.foregroundColor, - shadows: [iconShadow], + shadows: [appbarShadow], ), ), ), diff --git a/lib/screens/account/profile.g.dart b/lib/screens/account/profile.g.dart index df0f91d..9a29e06 100644 --- a/lib/screens/account/profile.g.dart +++ b/lib/screens/account/profile.g.dart @@ -267,5 +267,133 @@ class _AccountBadgesProviderElement String get uname => (origin as AccountBadgesProvider).uname; } +String _$accountAppbarForcegroundColorHash() => + r'f654a7a5594eda1500906e9ad023c22772257a9b'; + +/// See also [accountAppbarForcegroundColor]. +@ProviderFor(accountAppbarForcegroundColor) +const accountAppbarForcegroundColorProvider = + AccountAppbarForcegroundColorFamily(); + +/// See also [accountAppbarForcegroundColor]. +class AccountAppbarForcegroundColorFamily extends Family> { + /// See also [accountAppbarForcegroundColor]. + const AccountAppbarForcegroundColorFamily(); + + /// See also [accountAppbarForcegroundColor]. + AccountAppbarForcegroundColorProvider call(String uname) { + return AccountAppbarForcegroundColorProvider(uname); + } + + @override + AccountAppbarForcegroundColorProvider getProviderOverride( + covariant AccountAppbarForcegroundColorProvider provider, + ) { + return call(provider.uname); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'accountAppbarForcegroundColorProvider'; +} + +/// See also [accountAppbarForcegroundColor]. +class AccountAppbarForcegroundColorProvider + extends AutoDisposeFutureProvider { + /// See also [accountAppbarForcegroundColor]. + AccountAppbarForcegroundColorProvider(String uname) + : this._internal( + (ref) => accountAppbarForcegroundColor( + ref as AccountAppbarForcegroundColorRef, + uname, + ), + from: accountAppbarForcegroundColorProvider, + name: r'accountAppbarForcegroundColorProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$accountAppbarForcegroundColorHash, + dependencies: AccountAppbarForcegroundColorFamily._dependencies, + allTransitiveDependencies: + AccountAppbarForcegroundColorFamily._allTransitiveDependencies, + uname: uname, + ); + + AccountAppbarForcegroundColorProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.uname, + }) : super.internal(); + + final String uname; + + @override + Override overrideWith( + FutureOr Function(AccountAppbarForcegroundColorRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: AccountAppbarForcegroundColorProvider._internal( + (ref) => create(ref as AccountAppbarForcegroundColorRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + uname: uname, + ), + ); + } + + @override + AutoDisposeFutureProviderElement createElement() { + return _AccountAppbarForcegroundColorProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is AccountAppbarForcegroundColorProvider && + other.uname == uname; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, uname.hashCode); + + return _SystemHash.finish(hash); + } +} + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +mixin AccountAppbarForcegroundColorRef on AutoDisposeFutureProviderRef { + /// The parameter `uname` of this provider. + String get uname; +} + +class _AccountAppbarForcegroundColorProviderElement + extends AutoDisposeFutureProviderElement + with AccountAppbarForcegroundColorRef { + _AccountAppbarForcegroundColorProviderElement(super.provider); + + @override + String get uname => (origin as AccountAppbarForcegroundColorProvider).uname; +} + // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/screens/posts/pub_profile.dart b/lib/screens/posts/pub_profile.dart index 867c1df..20b239b 100644 --- a/lib/screens/posts/pub_profile.dart +++ b/lib/screens/posts/pub_profile.dart @@ -7,13 +7,16 @@ import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/post.dart'; import 'package:island/models/user.dart'; +import 'package:island/pods/config.dart'; import 'package:island/pods/network.dart'; +import 'package:island/services/color.dart'; import 'package:island/widgets/account/badge.dart'; import 'package:island/widgets/account/status.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/content/cloud_files.dart'; import 'package:island/widgets/post/post_list.dart'; +import 'package:palette_generator/palette_generator.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -47,6 +50,21 @@ Future publisherSubscriptionStatus( return SnSubscriptionStatus.fromJson(resp.data); } +@riverpod +Future publisherAppbarForcegroundColor(Ref ref, String pubName) async { + final publisher = await ref.watch(publisherProvider(pubName).future); + if (publisher.background == null) return null; + final palette = await PaletteGenerator.fromImageProvider( + CloudImageWidget.provider( + fileId: publisher.background!.id, + serverUrl: ref.watch(serverUrlProvider), + ), + ); + final dominantColor = palette.dominantColor?.color; + if (dominantColor == null) return null; + return dominantColor.computeLuminance() > 0.5 ? Colors.black : Colors.white; +} + @RoutePage() class PublisherProfileScreen extends HookConsumerWidget { final String name; @@ -60,6 +78,9 @@ class PublisherProfileScreen extends HookConsumerWidget { final publisher = ref.watch(publisherProvider(name)); final badges = ref.watch(publisherBadgesProvider(name)); final subStatus = ref.watch(publisherSubscriptionStatusProvider(name)); + final appbarColor = ref.watch( + publisherAppbarForcegroundColorProvider(name), + ); final subscribing = useState(false); @@ -91,8 +112,8 @@ class PublisherProfileScreen extends HookConsumerWidget { } } - final iconShadow = Shadow( - color: Colors.black54, + final appbarShadow = Shadow( + color: appbarColor.value?.invert ?? Colors.black54, blurRadius: 5.0, offset: Offset(1.0, 1.0), ); @@ -103,9 +124,13 @@ class PublisherProfileScreen extends HookConsumerWidget { body: CustomScrollView( slivers: [ SliverAppBar( + foregroundColor: appbarColor.value, expandedHeight: 180, pinned: true, - leading: PageBackButton(shadows: [iconShadow]), + leading: PageBackButton( + color: appbarColor.value, + shadows: [appbarShadow], + ), flexibleSpace: Stack( children: [ Positioned.fill( @@ -124,8 +149,9 @@ class PublisherProfileScreen extends HookConsumerWidget { data.nick, style: TextStyle( color: + appbarColor.value ?? Theme.of(context).appBarTheme.foregroundColor, - shadows: [iconShadow], + shadows: [appbarShadow], ), ), background: @@ -147,7 +173,7 @@ class PublisherProfileScreen extends HookConsumerWidget { status.isSubscribed ? Icons.remove_circle : Icons.add_circle, - shadows: [iconShadow], + shadows: [appbarShadow], ), ), error: (_, _) => const SizedBox(), diff --git a/lib/screens/posts/pub_profile.g.dart b/lib/screens/posts/pub_profile.g.dart index c197701..78dfe1c 100644 --- a/lib/screens/posts/pub_profile.g.dart +++ b/lib/screens/posts/pub_profile.g.dart @@ -399,5 +399,136 @@ class _PublisherSubscriptionStatusProviderElement String get pubName => (origin as PublisherSubscriptionStatusProvider).pubName; } +String _$publisherAppbarForcegroundColorHash() => + r'3ff2eebb48d3f3af1907052f471e648f5b14b13c'; + +/// See also [publisherAppbarForcegroundColor]. +@ProviderFor(publisherAppbarForcegroundColor) +const publisherAppbarForcegroundColorProvider = + PublisherAppbarForcegroundColorFamily(); + +/// See also [publisherAppbarForcegroundColor]. +class PublisherAppbarForcegroundColorFamily extends Family> { + /// See also [publisherAppbarForcegroundColor]. + const PublisherAppbarForcegroundColorFamily(); + + /// See also [publisherAppbarForcegroundColor]. + PublisherAppbarForcegroundColorProvider call(String pubName) { + return PublisherAppbarForcegroundColorProvider(pubName); + } + + @override + PublisherAppbarForcegroundColorProvider getProviderOverride( + covariant PublisherAppbarForcegroundColorProvider provider, + ) { + return call(provider.pubName); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'publisherAppbarForcegroundColorProvider'; +} + +/// See also [publisherAppbarForcegroundColor]. +class PublisherAppbarForcegroundColorProvider + extends AutoDisposeFutureProvider { + /// See also [publisherAppbarForcegroundColor]. + PublisherAppbarForcegroundColorProvider(String pubName) + : this._internal( + (ref) => publisherAppbarForcegroundColor( + ref as PublisherAppbarForcegroundColorRef, + pubName, + ), + from: publisherAppbarForcegroundColorProvider, + name: r'publisherAppbarForcegroundColorProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$publisherAppbarForcegroundColorHash, + dependencies: PublisherAppbarForcegroundColorFamily._dependencies, + allTransitiveDependencies: + PublisherAppbarForcegroundColorFamily._allTransitiveDependencies, + pubName: pubName, + ); + + PublisherAppbarForcegroundColorProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.pubName, + }) : super.internal(); + + final String pubName; + + @override + Override overrideWith( + FutureOr Function(PublisherAppbarForcegroundColorRef provider) + create, + ) { + return ProviderOverride( + origin: this, + override: PublisherAppbarForcegroundColorProvider._internal( + (ref) => create(ref as PublisherAppbarForcegroundColorRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + pubName: pubName, + ), + ); + } + + @override + AutoDisposeFutureProviderElement createElement() { + return _PublisherAppbarForcegroundColorProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is PublisherAppbarForcegroundColorProvider && + other.pubName == pubName; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, pubName.hashCode); + + return _SystemHash.finish(hash); + } +} + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +mixin PublisherAppbarForcegroundColorRef + on AutoDisposeFutureProviderRef { + /// The parameter `pubName` of this provider. + String get pubName; +} + +class _PublisherAppbarForcegroundColorProviderElement + extends AutoDisposeFutureProviderElement + with PublisherAppbarForcegroundColorRef { + _PublisherAppbarForcegroundColorProviderElement(super.provider); + + @override + String get pubName => + (origin as PublisherAppbarForcegroundColorProvider).pubName; +} + // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/services/color.dart b/lib/services/color.dart new file mode 100644 index 0000000..09ddac2 --- /dev/null +++ b/lib/services/color.dart @@ -0,0 +1,7 @@ +import 'package:flutter/widgets.dart'; + +extension ColorInversion on Color { + Color get invert { + return Color.fromARGB(alpha, 255 - red, 255 - green, 255 - blue); + } +} diff --git a/lib/widgets/app_scaffold.dart b/lib/widgets/app_scaffold.dart index 8f7b50e..676d54c 100644 --- a/lib/widgets/app_scaffold.dart +++ b/lib/widgets/app_scaffold.dart @@ -167,9 +167,10 @@ class AppScaffold extends StatelessWidget { } class PageBackButton extends StatelessWidget { + final Color? color; final List? shadows; final VoidCallback? onWillPop; - const PageBackButton({super.key, this.shadows, this.onWillPop}); + const PageBackButton({super.key, this.shadows, this.onWillPop, this.color}); @override Widget build(BuildContext context) { @@ -179,6 +180,7 @@ class PageBackButton extends StatelessWidget { context.router.maybePop(); }, icon: Icon( + color: color, (!kIsWeb && (Platform.isMacOS || Platform.isIOS)) ? Symbols.arrow_back_ios_new : Symbols.arrow_back, diff --git a/pubspec.lock b/pubspec.lock index 60fa47b..83d4233 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1398,6 +1398,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.0" + palette_generator: + dependency: "direct main" + description: + name: palette_generator + sha256: "4420f7ccc3f0a4a906144e73f8b6267cd940b64f57a7262e95cb8cec3a8ae0ed" + url: "https://pub.dev" + source: hosted + version: "0.3.3+7" pasteboard: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 51173a9..d5589d6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -108,6 +108,7 @@ dependencies: record: ^6.0.0 qr_flutter: ^4.1.0 flutter_otp_text_field: ^1.5.1+1 + palette_generator: ^0.3.3+7 dev_dependencies: flutter_test: