From 666a2dfbf59c0b4ea65dff28a7cf59a7f3514dfd Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Mon, 30 Jun 2025 01:11:31 +0800 Subject: [PATCH] :sparkles: Feature flags --- assets/i18n/en-US.json | 7 +- lib/screens/creators/hub.dart | 59 +++++++++++++++ lib/screens/creators/hub.g.dart | 122 ++++++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+), 1 deletion(-) diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index 391f253..1a046c4 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -670,5 +670,10 @@ "uriInvalid": "The URI is invalid.", "add": "Add", "addScope": "Add Scope", - "scope": "Scope" + "scope": "Scope", + "publisherFeatures": "Features", + "publisherFeatureDevelop": "Developer Program", + "publisherFeatureDevelopDescription": "Unlock development abilities for your publisher, including custom apps, API keys, and more.", + "publisherFeatureDevelopHint": "Currently, this feature is under active development, you need send a request to unlock this feature.", + "learnMore": "Learn More" } diff --git a/lib/screens/creators/hub.dart b/lib/screens/creators/hub.dart index cff227a..5712da9 100644 --- a/lib/screens/creators/hub.dart +++ b/lib/screens/creators/hub.dart @@ -11,6 +11,7 @@ import 'package:island/models/publisher.dart'; import 'package:island/pods/network.dart'; import 'package:island/screens/creators/publishers.dart'; import 'package:island/services/responsive.dart'; +import 'package:island/services/text.dart'; import 'package:island/widgets/account/account_picker.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/app_scaffold.dart'; @@ -46,6 +47,14 @@ Future publisherIdentity(Ref ref, String uname) async { } } +@riverpod +Future> publisherFeatures(Ref ref, String? uname) async { + if (uname == null) return {}; + final apiClient = ref.watch(apiClientProvider); + final response = await apiClient.get('/publishers/$uname/features'); + return Map.from(response.data); +} + @riverpod Future> publisherInvites(Ref ref) async { final client = ref.watch(apiClientProvider); @@ -184,6 +193,10 @@ class CreatorHubScreen extends HookConsumerWidget { publisherStatsProvider(currentPublisher.value?.name), ); + final publisherFeatures = ref.watch( + publisherFeaturesProvider(currentPublisher.value?.name), + ); + return AppScaffold( noBackground: false, appBar: AppBar( @@ -374,6 +387,52 @@ class CreatorHubScreen extends HookConsumerWidget { ); }, ), + ExpansionTile( + title: Text('publisherFeatures').tr(), + leading: const Icon(Symbols.flag), + tilePadding: EdgeInsets.symmetric(horizontal: 24), + minTileHeight: 48, + children: [ + ...publisherFeatures.when( + data: (data) { + return data.entries.map((entry) { + final keyPrefix = + 'publisherFeature${entry.key.capitalizeEachWord()}'; + return ListTile( + minTileHeight: 48, + contentPadding: EdgeInsets.symmetric( + horizontal: 24, + ), + leading: Icon( + Symbols.circle, + color: + entry.value + ? Colors.green + : Colors.red, + fill: 1, + size: 16, + ).padding(left: 2, top: 4), + title: Text(keyPrefix).tr(), + subtitle: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text('${keyPrefix}Description').tr(), + if (!entry.value) + Text( + '${keyPrefix}Hint', + ).tr().bold(), + ], + ), + isThreeLine: true, + ); + }).toList(); + }, + error: (_, _) => [], + loading: () => [], + ), + ], + ), Divider(height: 1).padding(vertical: 8), ListTile( minTileHeight: 48, diff --git a/lib/screens/creators/hub.g.dart b/lib/screens/creators/hub.g.dart index 1795cc0..ccbb0ec 100644 --- a/lib/screens/creators/hub.g.dart +++ b/lib/screens/creators/hub.g.dart @@ -271,6 +271,128 @@ class _PublisherIdentityProviderElement String get uname => (origin as PublisherIdentityProvider).uname; } +String _$publisherFeaturesHash() => r'34db65d9a4b6b0c6961733ae79e67f25d5d111d3'; + +/// See also [publisherFeatures]. +@ProviderFor(publisherFeatures) +const publisherFeaturesProvider = PublisherFeaturesFamily(); + +/// See also [publisherFeatures]. +class PublisherFeaturesFamily extends Family>> { + /// See also [publisherFeatures]. + const PublisherFeaturesFamily(); + + /// See also [publisherFeatures]. + PublisherFeaturesProvider call(String? uname) { + return PublisherFeaturesProvider(uname); + } + + @override + PublisherFeaturesProvider getProviderOverride( + covariant PublisherFeaturesProvider 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'publisherFeaturesProvider'; +} + +/// See also [publisherFeatures]. +class PublisherFeaturesProvider + extends AutoDisposeFutureProvider> { + /// See also [publisherFeatures]. + PublisherFeaturesProvider(String? uname) + : this._internal( + (ref) => publisherFeatures(ref as PublisherFeaturesRef, uname), + from: publisherFeaturesProvider, + name: r'publisherFeaturesProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$publisherFeaturesHash, + dependencies: PublisherFeaturesFamily._dependencies, + allTransitiveDependencies: + PublisherFeaturesFamily._allTransitiveDependencies, + uname: uname, + ); + + PublisherFeaturesProvider._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(PublisherFeaturesRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: PublisherFeaturesProvider._internal( + (ref) => create(ref as PublisherFeaturesRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + uname: uname, + ), + ); + } + + @override + AutoDisposeFutureProviderElement> createElement() { + return _PublisherFeaturesProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is PublisherFeaturesProvider && 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 PublisherFeaturesRef on AutoDisposeFutureProviderRef> { + /// The parameter `uname` of this provider. + String? get uname; +} + +class _PublisherFeaturesProviderElement + extends AutoDisposeFutureProviderElement> + with PublisherFeaturesRef { + _PublisherFeaturesProviderElement(super.provider); + + @override + String? get uname => (origin as PublisherFeaturesProvider).uname; +} + String _$publisherInvitesHash() => r'488cd443407895ce11f4edff07cb6ea58f2aa018'; /// See also [publisherInvites].