From 180fbcc5587cd42a2d43389087056d299977fc2f Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Fri, 27 Jun 2025 17:30:42 +0800 Subject: [PATCH] :sparkles: Join the realm by user own --- assets/i18n/en-US.json | 3 +- lib/main.dart | 43 ++++++++--- lib/screens/realm/detail.dart | 104 ++++++++++++++++++++----- lib/screens/realm/detail.g.dart | 130 +++++++++++++++++++++++++++++++- 4 files changed, 247 insertions(+), 33 deletions(-) diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index 8385a9a..51acc8f 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -620,5 +620,6 @@ "tags": "Tags", "tagsHint": "Enter tags, separated by commas", "categories": "Categories", - "categoriesHint": "Enter categories, separated by commas" + "categoriesHint": "Enter categories, separated by commas", + "joinRealm": "Join the Realm" } diff --git a/lib/main.dart b/lib/main.dart index 34b310a..3a76cfb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -29,6 +29,12 @@ import 'package:image_picker_platform_interface/image_picker_platform_interface. import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:url_launcher/url_launcher_string.dart'; +@pragma('vm:entry-point') +Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { + await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); + log('Handling a background message: ${message.messageId}'); +} + void main() async { final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) { @@ -43,6 +49,7 @@ void main() async { await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); + FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); log("[SplashScreen] Firebase is ready!"); } catch (err) { showErrorAlert(err); @@ -151,17 +158,30 @@ class IslandApp extends HookConsumerWidget { } useEffect(() { - Future(() async { - RemoteMessage? initialMessage = - await FirebaseMessaging.instance.getInitialMessage(); - if (initialMessage != null) { - handleMessage(initialMessage); + // When the app is opened from a terminated state. + FirebaseMessaging.instance.getInitialMessage().then((message) { + if (message != null) { + handleMessage(message); } - - FirebaseMessaging.onMessageOpenedApp.listen(handleMessage); }); - return null; + // When the app is in the background and opened. + final onMessageOpenedAppSubscription = FirebaseMessaging + .onMessageOpenedApp + .listen(handleMessage); + + // When the app is in the foreground. + final onMessageSubscription = FirebaseMessaging.onMessage.listen(( + message, + ) { + log('Foreground message received: ${message.messageId}'); + handleMessage(message); + }); + + return () { + onMessageOpenedAppSubscription.cancel(); + onMessageSubscription.cancel(); + }; }, []); useEffect(() { @@ -185,7 +205,7 @@ class IslandApp extends HookConsumerWidget { }, []); final router = ref.watch(routerProvider); - + return MaterialApp.router( theme: theme?.light, darkTheme: theme?.dark, @@ -204,9 +224,8 @@ class IslandApp extends HookConsumerWidget { initialEntries: [ OverlayEntry( builder: - (_) => WindowScaffold( - child: child ?? const SizedBox.shrink(), - ), + (_) => + WindowScaffold(child: child ?? const SizedBox.shrink()), ), ], ); diff --git a/lib/screens/realm/detail.dart b/lib/screens/realm/detail.dart index 545bdf8..e4cd046 100644 --- a/lib/screens/realm/detail.dart +++ b/lib/screens/realm/detail.dart @@ -1,12 +1,15 @@ import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:island/services/color.dart'; +import 'package:palette_generator/palette_generator.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/realm.dart'; import 'package:island/pods/network.dart'; +import 'package:island/pods/config.dart'; import 'package:island/screens/realm/realms.dart'; import 'package:island/widgets/account/account_picker.dart'; import 'package:island/widgets/alert.dart'; @@ -19,6 +22,21 @@ import 'package:styled_widget/styled_widget.dart'; part 'detail.g.dart'; +@riverpod +Future realmAppbarForegroundColor(Ref ref, String realmSlug) async { + final realm = await ref.watch(realmProvider(realmSlug).future); + if (realm?.background == null) return null; + final palette = await PaletteGenerator.fromImageProvider( + CloudImageWidget.provider( + fileId: realm!.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; +} + @riverpod Future realmIdentity(Ref ref, String realmSlug) async { final apiClient = ref.watch(apiClientProvider); @@ -34,9 +52,10 @@ class RealmDetailScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final realmState = ref.watch(realmProvider(slug)); + final appbarColor = ref.watch(realmAppbarForegroundColorProvider(slug)); - const iconShadow = Shadow( - color: Colors.black54, + final iconShadow = Shadow( + color: appbarColor.value?.invert ?? Colors.black54, blurRadius: 5.0, offset: Offset(1.0, 1.0), ); @@ -51,7 +70,11 @@ class RealmDetailScreen extends HookConsumerWidget { SliverAppBar( expandedHeight: 180, pinned: true, - leading: PageBackButton(shadows: [iconShadow]), + foregroundColor: appbarColor.value, + leading: PageBackButton( + color: appbarColor.value, + shadows: [iconShadow], + ), flexibleSpace: FlexibleSpaceBar( background: realm!.background?.id != null @@ -63,14 +86,16 @@ class RealmDetailScreen extends HookConsumerWidget { title: Text( realm.name, style: TextStyle( - color: Theme.of(context).appBarTheme.foregroundColor, + color: + appbarColor.value ?? + Theme.of(context).appBarTheme.foregroundColor, shadows: [iconShadow], ), ), ), actions: [ IconButton( - icon: const Icon(Icons.people, shadows: [iconShadow]), + icon: Icon(Icons.people, shadows: [iconShadow]), onPressed: () { showModalBottomSheet( isScrollControlled: true, @@ -86,17 +111,58 @@ class RealmDetailScreen extends HookConsumerWidget { ], ), SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - realm.description, - style: const TextStyle(fontSize: 16), - ), - ], - ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ref + .watch(realmIdentityProvider(slug)) + .when( + loading: () => const SizedBox.shrink(), + error: (_, _) => const SizedBox.shrink(), + data: + (identity) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ExpansionTile( + title: const Text('description').tr(), + initiallyExpanded: identity == null, + children: [ + Text( + realm.description, + style: const TextStyle(fontSize: 16), + ).padding(horizontal: 16, vertical: 16), + ], + ), + const Gap(4), + if (identity != null && realm.isPublic) + FilledButton.tonalIcon( + onPressed: () async { + try { + final apiClient = ref.read( + apiClientProvider, + ); + await apiClient.post( + '/realms/$slug/members/me', + ); + ref.invalidate( + realmIdentityProvider(slug), + ); + ref.invalidate( + realmsJoinedProvider, + ); + } catch (err) { + showErrorAlert(err); + } + }, + icon: const Icon(Symbols.add), + label: const Text('joinRealm').tr(), + ).padding(horizontal: 16) + else + const SizedBox.shrink(), + ], + ), + ), + ], ), ), ], @@ -114,8 +180,8 @@ class _RealmActionMenu extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final realmIdentityAsync = ref.watch(realmIdentityProvider(realmSlug)); - final isModerator = realmIdentityAsync.when( + final realmIdentity = ref.watch(realmIdentityProvider(realmSlug)); + final isModerator = realmIdentity.when( data: (identity) => (identity?.role ?? 0) >= 50, loading: () => false, error: (_, _) => false, @@ -141,7 +207,7 @@ class _RealmActionMenu extends HookConsumerWidget { ], ), ), - realmIdentityAsync.when( + realmIdentity.when( data: (identity) => (identity?.role ?? 0) >= 100 diff --git a/lib/screens/realm/detail.g.dart b/lib/screens/realm/detail.g.dart index 2591d79..fdb0e5e 100644 --- a/lib/screens/realm/detail.g.dart +++ b/lib/screens/realm/detail.g.dart @@ -6,7 +6,8 @@ part of 'detail.dart'; // RiverpodGenerator // ************************************************************************** -String _$realmIdentityHash() => r'eac6e829b5b46bcfadbf201ab6f918d78c894b9f'; +String _$realmAppbarForegroundColorHash() => + r'14b5563d861996ea182d0d2db7aa5c2bb3bbaf48'; /// Copied from Dart SDK class _SystemHash { @@ -29,6 +30,133 @@ class _SystemHash { } } +/// See also [realmAppbarForegroundColor]. +@ProviderFor(realmAppbarForegroundColor) +const realmAppbarForegroundColorProvider = RealmAppbarForegroundColorFamily(); + +/// See also [realmAppbarForegroundColor]. +class RealmAppbarForegroundColorFamily extends Family> { + /// See also [realmAppbarForegroundColor]. + const RealmAppbarForegroundColorFamily(); + + /// See also [realmAppbarForegroundColor]. + RealmAppbarForegroundColorProvider call(String realmSlug) { + return RealmAppbarForegroundColorProvider(realmSlug); + } + + @override + RealmAppbarForegroundColorProvider getProviderOverride( + covariant RealmAppbarForegroundColorProvider provider, + ) { + return call(provider.realmSlug); + } + + 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'realmAppbarForegroundColorProvider'; +} + +/// See also [realmAppbarForegroundColor]. +class RealmAppbarForegroundColorProvider + extends AutoDisposeFutureProvider { + /// See also [realmAppbarForegroundColor]. + RealmAppbarForegroundColorProvider(String realmSlug) + : this._internal( + (ref) => realmAppbarForegroundColor( + ref as RealmAppbarForegroundColorRef, + realmSlug, + ), + from: realmAppbarForegroundColorProvider, + name: r'realmAppbarForegroundColorProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$realmAppbarForegroundColorHash, + dependencies: RealmAppbarForegroundColorFamily._dependencies, + allTransitiveDependencies: + RealmAppbarForegroundColorFamily._allTransitiveDependencies, + realmSlug: realmSlug, + ); + + RealmAppbarForegroundColorProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.realmSlug, + }) : super.internal(); + + final String realmSlug; + + @override + Override overrideWith( + FutureOr Function(RealmAppbarForegroundColorRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: RealmAppbarForegroundColorProvider._internal( + (ref) => create(ref as RealmAppbarForegroundColorRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + realmSlug: realmSlug, + ), + ); + } + + @override + AutoDisposeFutureProviderElement createElement() { + return _RealmAppbarForegroundColorProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is RealmAppbarForegroundColorProvider && + other.realmSlug == realmSlug; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, realmSlug.hashCode); + + return _SystemHash.finish(hash); + } +} + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +mixin RealmAppbarForegroundColorRef on AutoDisposeFutureProviderRef { + /// The parameter `realmSlug` of this provider. + String get realmSlug; +} + +class _RealmAppbarForegroundColorProviderElement + extends AutoDisposeFutureProviderElement + with RealmAppbarForegroundColorRef { + _RealmAppbarForegroundColorProviderElement(super.provider); + + @override + String get realmSlug => + (origin as RealmAppbarForegroundColorProvider).realmSlug; +} + +String _$realmIdentityHash() => r'eac6e829b5b46bcfadbf201ab6f918d78c894b9f'; + /// See also [realmIdentity]. @ProviderFor(realmIdentity) const realmIdentityProvider = RealmIdentityFamily();