Compare commits
	
		
			2 Commits
		
	
	
		
			3.2.0+128
			...
			c7f5b63fe5
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c7f5b63fe5 | |||
| 96c2f45c85 | 
| @@ -867,7 +867,7 @@ | ||||
|   "failedToLoadUserInfoNetwork": "It seems be network issue, you can tap the button below to try again.", | ||||
|   "failedToLoadUserInfoUnauthorized": "It seems your session has been logged out or not available anymore, you can still try agian to fetch the user info if you want.", | ||||
|   "okay": "Okay", | ||||
|   "postDetails": "Post Details", | ||||
|   "postDetail": "Post Detail", | ||||
|   "postCount": { | ||||
|     "zero": "No posts", | ||||
|     "one": "{} post", | ||||
|   | ||||
| @@ -829,7 +829,7 @@ | ||||
|   "failedToLoadUserInfoNetwork": "这看起来是个网络问题,你可以按下面的按钮来重试", | ||||
|   "failedToLoadUserInfoUnauthorized": "看来您的会话已被注销或不再可用,如果您愿意,您仍然可以再次尝试获取用户信息。", | ||||
|   "okay": "了解", | ||||
|   "postDetails": "帖子详情", | ||||
|   "postDetail": "帖子详情", | ||||
|   "mimeType": "类型", | ||||
|   "fileSize": "大小", | ||||
|   "fileHash": "哈希", | ||||
|   | ||||
| @@ -137,6 +137,7 @@ class ArticlesScreen extends ConsumerWidget { | ||||
|         return DefaultTabController( | ||||
|           length: feeds.length + 1, | ||||
|           child: AppScaffold( | ||||
|             isNoBackground: false, | ||||
|             appBar: AppBar( | ||||
|               title: const Text('Articles'), | ||||
|               bottom: TabBar( | ||||
| @@ -192,11 +193,13 @@ class ArticlesScreen extends ConsumerWidget { | ||||
|       }, | ||||
|       loading: | ||||
|           () => AppScaffold( | ||||
|             isNoBackground: false, | ||||
|             appBar: AppBar(title: const Text('Articles')), | ||||
|             body: const Center(child: CircularProgressIndicator()), | ||||
|           ), | ||||
|       error: | ||||
|           (err, stack) => AppScaffold( | ||||
|             isNoBackground: false, | ||||
|             appBar: AppBar(title: const Text('Articles')), | ||||
|             body: Center(child: Text('Error: $err')), | ||||
|           ), | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import 'package:island/pods/network.dart'; | ||||
| import 'package:island/widgets/app_scaffold.dart'; | ||||
| import 'package:island/widgets/post/post_list.dart'; | ||||
| import 'package:island/widgets/response.dart'; | ||||
| import 'package:material_symbols_icons/material_symbols_icons.dart'; | ||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
|  | ||||
| @@ -27,6 +28,49 @@ Future<SnPostTag> postTag(Ref ref, String slug) async { | ||||
|   return SnPostTag.fromJson(resp.data); | ||||
| } | ||||
|  | ||||
| @riverpod | ||||
| Future<bool> postCategorySubscriptionStatus( | ||||
|   Ref ref, | ||||
|   String slug, | ||||
|   bool isCategory, | ||||
| ) async { | ||||
|   final apiClient = ref.watch(apiClientProvider); | ||||
|   try { | ||||
|     final resp = await apiClient.get( | ||||
|       '/sphere/posts/${isCategory ? 'categories' : 'tags'}/$slug/subscription', | ||||
|     ); | ||||
|     return resp.statusCode == 200; | ||||
|   } catch (_) { | ||||
|     return false; | ||||
|   } | ||||
| } | ||||
|  | ||||
| Future<void> _subscribeToCategoryOrTag( | ||||
|   WidgetRef ref, { | ||||
|   required String slug, | ||||
|   required bool isCategory, | ||||
| }) async { | ||||
|   final apiClient = ref.read(apiClientProvider); | ||||
|   await apiClient.post( | ||||
|     '/sphere/posts/${isCategory ? 'categories' : 'tags'}/$slug/subscribe', | ||||
|   ); | ||||
|   // Invalidate the subscription status to refresh it | ||||
|   ref.invalidate(postCategorySubscriptionStatusProvider(slug, isCategory)); | ||||
| } | ||||
|  | ||||
| Future<void> _unsubscribeFromCategoryOrTag( | ||||
|   WidgetRef ref, { | ||||
|   required String slug, | ||||
|   required bool isCategory, | ||||
| }) async { | ||||
|   final apiClient = ref.read(apiClientProvider); | ||||
|   await apiClient.post( | ||||
|     '/sphere/posts/${isCategory ? 'categories' : 'tags'}/$slug/unsubscribe', | ||||
|   ); | ||||
|   // Invalidate the subscription status to refresh it | ||||
|   ref.invalidate(postCategorySubscriptionStatusProvider(slug, isCategory)); | ||||
| } | ||||
|  | ||||
| class PostCategoryDetailScreen extends HookConsumerWidget { | ||||
|   final String slug; | ||||
|   final bool isCategory; | ||||
| @@ -41,6 +85,9 @@ class PostCategoryDetailScreen extends HookConsumerWidget { | ||||
|     final postCategory = | ||||
|         isCategory ? ref.watch(postCategoryProvider(slug)) : null; | ||||
|     final postTag = isCategory ? null : ref.watch(postTagProvider(slug)); | ||||
|     final subscriptionStatus = ref.watch( | ||||
|       postCategorySubscriptionStatusProvider(slug, isCategory), | ||||
|     ); | ||||
|  | ||||
|     final postFilterTitle = | ||||
|         isCategory | ||||
| @@ -50,58 +97,155 @@ class PostCategoryDetailScreen extends HookConsumerWidget { | ||||
|     return AppScaffold( | ||||
|       isNoBackground: false, | ||||
|       appBar: AppBar(title: Text(postFilterTitle).tr()), | ||||
|       body: Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           if (isCategory) | ||||
|             postCategory!.when( | ||||
|               data: | ||||
|                   (category) => Column( | ||||
|                     crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                     children: [ | ||||
|                       Text(category.categoryDisplayTitle).bold().fontSize(15), | ||||
|                       Text('A category'), | ||||
|                     ], | ||||
|                   ).padding(horizontal: 24, vertical: 16), | ||||
|               error: | ||||
|                   (error, _) => ResponseErrorWidget( | ||||
|                     error: error, | ||||
|                     onRetry: () => ref.invalidate(postCategoryProvider(slug)), | ||||
|                   ), | ||||
|               loading: () => ResponseLoadingWidget(), | ||||
|             ) | ||||
|           else | ||||
|             postTag!.when( | ||||
|               data: | ||||
|                   (tag) => Column( | ||||
|                     crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                     children: [ | ||||
|                       Text(tag.name ?? '#${tag.slug}').bold().fontSize(15), | ||||
|                       Text('A tag'), | ||||
|                     ], | ||||
|                   ).padding(horizontal: 24, vertical: 16), | ||||
|               error: | ||||
|                   (error, _) => ResponseErrorWidget( | ||||
|                     error: error, | ||||
|                     onRetry: () => ref.invalidate(postTagProvider(slug)), | ||||
|                   ), | ||||
|               loading: () => ResponseLoadingWidget(), | ||||
|             ), | ||||
|           const Divider(height: 1), | ||||
|           Expanded( | ||||
|       body: Expanded( | ||||
|         child: CustomScrollView( | ||||
|           slivers: [ | ||||
|             if (isCategory) | ||||
|               SliverToBoxAdapter( | ||||
|                 child: Center( | ||||
|                   child: ConstrainedBox( | ||||
|                     constraints: const BoxConstraints(maxWidth: 540), | ||||
|                     child: Card( | ||||
|                       margin: EdgeInsets.only(top: 8), | ||||
|                       child: postCategory!.when( | ||||
|                         data: | ||||
|                             (category) => Column( | ||||
|                               crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                               children: [ | ||||
|                                 Text( | ||||
|                                   category.categoryDisplayTitle, | ||||
|                                 ).bold().fontSize(15), | ||||
|                                 Text('A category'), | ||||
|                                 const Gap(8), | ||||
|                                 subscriptionStatus.when( | ||||
|                                   data: | ||||
|                                       (isSubscribed) => | ||||
|                                           isSubscribed | ||||
|                                               ? FilledButton.icon( | ||||
|                                                 onPressed: () async { | ||||
|                                                   await _unsubscribeFromCategoryOrTag( | ||||
|                                                     ref, | ||||
|                                                     slug: slug, | ||||
|                                                     isCategory: isCategory, | ||||
|                                                   ); | ||||
|                                                 }, | ||||
|                                                 icon: const Icon( | ||||
|                                                   Symbols.remove_circle, | ||||
|                                                 ), | ||||
|                                                 label: Text('unsubscribe'.tr()), | ||||
|                                               ) | ||||
|                                               : FilledButton.icon( | ||||
|                                                 onPressed: () async { | ||||
|                                                   await _subscribeToCategoryOrTag( | ||||
|                                                     ref, | ||||
|                                                     slug: slug, | ||||
|                                                     isCategory: isCategory, | ||||
|                                                   ); | ||||
|                                                 }, | ||||
|                                                 icon: const Icon( | ||||
|                                                   Symbols.add_circle, | ||||
|                                                 ), | ||||
|                                                 label: Text('subscribe'.tr()), | ||||
|                                               ), | ||||
|                                   error: | ||||
|                                       (error, _) => Text( | ||||
|                                         'Error loading subscription status', | ||||
|                                       ), | ||||
|                                   loading: () => CircularProgressIndicator(), | ||||
|                                 ), | ||||
|                               ], | ||||
|                             ).padding(horizontal: 24, vertical: 16), | ||||
|                         error: | ||||
|                             (error, _) => ResponseErrorWidget( | ||||
|                               error: error, | ||||
|                               onRetry: | ||||
|                                   () => ref.invalidate( | ||||
|                                     postCategoryProvider(slug), | ||||
|                                   ), | ||||
|                             ), | ||||
|                         loading: () => ResponseLoadingWidget(), | ||||
|                       ), | ||||
|                     ), | ||||
|                   ), | ||||
|                 ), | ||||
|               ) | ||||
|             else | ||||
|               SliverToBoxAdapter( | ||||
|                 child: Center( | ||||
|                   child: ConstrainedBox( | ||||
|                     constraints: const BoxConstraints(maxWidth: 540), | ||||
|                     child: Card( | ||||
|                       margin: EdgeInsets.only(top: 8), | ||||
|                       child: postTag!.when( | ||||
|                         data: | ||||
|                             (tag) => Column( | ||||
|                               crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                               children: [ | ||||
|                                 Text( | ||||
|                                   tag.name ?? '#${tag.slug}', | ||||
|                                 ).bold().fontSize(15), | ||||
|                                 Text('A tag'), | ||||
|                                 const Gap(8), | ||||
|                                 subscriptionStatus.when( | ||||
|                                   data: | ||||
|                                       (isSubscribed) => | ||||
|                                           isSubscribed | ||||
|                                               ? FilledButton.icon( | ||||
|                                                 onPressed: () async { | ||||
|                                                   await _unsubscribeFromCategoryOrTag( | ||||
|                                                     ref, | ||||
|                                                     slug: slug, | ||||
|                                                     isCategory: isCategory, | ||||
|                                                   ); | ||||
|                                                 }, | ||||
|                                                 icon: const Icon( | ||||
|                                                   Symbols.add_circle, | ||||
|                                                 ), | ||||
|                                                 label: Text('unsubscribe'.tr()), | ||||
|                                               ) | ||||
|                                               : FilledButton.icon( | ||||
|                                                 onPressed: () async { | ||||
|                                                   await _subscribeToCategoryOrTag( | ||||
|                                                     ref, | ||||
|                                                     slug: slug, | ||||
|                                                     isCategory: isCategory, | ||||
|                                                   ); | ||||
|                                                 }, | ||||
|                                                 icon: const Icon( | ||||
|                                                   Symbols.remove_circle, | ||||
|                                                 ), | ||||
|                                                 label: Text('subscribe'.tr()), | ||||
|                                               ), | ||||
|                                   error: | ||||
|                                       (error, _) => Text( | ||||
|                                         'Error loading subscription status', | ||||
|                                       ), | ||||
|                                   loading: () => CircularProgressIndicator(), | ||||
|                                 ), | ||||
|                               ], | ||||
|                             ).padding(horizontal: 24, vertical: 16), | ||||
|                         error: | ||||
|                             (error, _) => ResponseErrorWidget( | ||||
|                               error: error, | ||||
|                               onRetry: | ||||
|                                   () => ref.invalidate(postTagProvider(slug)), | ||||
|                             ), | ||||
|                         loading: () => ResponseLoadingWidget(), | ||||
|                       ), | ||||
|                     ), | ||||
|                   ), | ||||
|                 ), | ||||
|               ), | ||||
|             const SliverGap(4), | ||||
|             SliverPostList( | ||||
|               categories: isCategory ? [slug] : null, | ||||
|               tags: isCategory ? null : [slug], | ||||
|               maxWidth: 540 + 16, | ||||
|             ), | ||||
|             SliverGap(MediaQuery.of(context).padding.bottom + 8), | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -266,5 +266,146 @@ class _PostTagProviderElement | ||||
|   String get slug => (origin as PostTagProvider).slug; | ||||
| } | ||||
|  | ||||
| String _$postCategorySubscriptionStatusHash() => | ||||
|     r'407dc7fcaeffc461b591b4ee2418811aa4f0a63f'; | ||||
|  | ||||
| /// See also [postCategorySubscriptionStatus]. | ||||
| @ProviderFor(postCategorySubscriptionStatus) | ||||
| const postCategorySubscriptionStatusProvider = | ||||
|     PostCategorySubscriptionStatusFamily(); | ||||
|  | ||||
| /// See also [postCategorySubscriptionStatus]. | ||||
| class PostCategorySubscriptionStatusFamily extends Family<AsyncValue<bool>> { | ||||
|   /// See also [postCategorySubscriptionStatus]. | ||||
|   const PostCategorySubscriptionStatusFamily(); | ||||
|  | ||||
|   /// See also [postCategorySubscriptionStatus]. | ||||
|   PostCategorySubscriptionStatusProvider call(String slug, bool isCategory) { | ||||
|     return PostCategorySubscriptionStatusProvider(slug, isCategory); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   PostCategorySubscriptionStatusProvider getProviderOverride( | ||||
|     covariant PostCategorySubscriptionStatusProvider provider, | ||||
|   ) { | ||||
|     return call(provider.slug, provider.isCategory); | ||||
|   } | ||||
|  | ||||
|   static const Iterable<ProviderOrFamily>? _dependencies = null; | ||||
|  | ||||
|   @override | ||||
|   Iterable<ProviderOrFamily>? get dependencies => _dependencies; | ||||
|  | ||||
|   static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null; | ||||
|  | ||||
|   @override | ||||
|   Iterable<ProviderOrFamily>? get allTransitiveDependencies => | ||||
|       _allTransitiveDependencies; | ||||
|  | ||||
|   @override | ||||
|   String? get name => r'postCategorySubscriptionStatusProvider'; | ||||
| } | ||||
|  | ||||
| /// See also [postCategorySubscriptionStatus]. | ||||
| class PostCategorySubscriptionStatusProvider | ||||
|     extends AutoDisposeFutureProvider<bool> { | ||||
|   /// See also [postCategorySubscriptionStatus]. | ||||
|   PostCategorySubscriptionStatusProvider(String slug, bool isCategory) | ||||
|     : this._internal( | ||||
|         (ref) => postCategorySubscriptionStatus( | ||||
|           ref as PostCategorySubscriptionStatusRef, | ||||
|           slug, | ||||
|           isCategory, | ||||
|         ), | ||||
|         from: postCategorySubscriptionStatusProvider, | ||||
|         name: r'postCategorySubscriptionStatusProvider', | ||||
|         debugGetCreateSourceHash: | ||||
|             const bool.fromEnvironment('dart.vm.product') | ||||
|                 ? null | ||||
|                 : _$postCategorySubscriptionStatusHash, | ||||
|         dependencies: PostCategorySubscriptionStatusFamily._dependencies, | ||||
|         allTransitiveDependencies: | ||||
|             PostCategorySubscriptionStatusFamily._allTransitiveDependencies, | ||||
|         slug: slug, | ||||
|         isCategory: isCategory, | ||||
|       ); | ||||
|  | ||||
|   PostCategorySubscriptionStatusProvider._internal( | ||||
|     super._createNotifier, { | ||||
|     required super.name, | ||||
|     required super.dependencies, | ||||
|     required super.allTransitiveDependencies, | ||||
|     required super.debugGetCreateSourceHash, | ||||
|     required super.from, | ||||
|     required this.slug, | ||||
|     required this.isCategory, | ||||
|   }) : super.internal(); | ||||
|  | ||||
|   final String slug; | ||||
|   final bool isCategory; | ||||
|  | ||||
|   @override | ||||
|   Override overrideWith( | ||||
|     FutureOr<bool> Function(PostCategorySubscriptionStatusRef provider) create, | ||||
|   ) { | ||||
|     return ProviderOverride( | ||||
|       origin: this, | ||||
|       override: PostCategorySubscriptionStatusProvider._internal( | ||||
|         (ref) => create(ref as PostCategorySubscriptionStatusRef), | ||||
|         from: from, | ||||
|         name: null, | ||||
|         dependencies: null, | ||||
|         allTransitiveDependencies: null, | ||||
|         debugGetCreateSourceHash: null, | ||||
|         slug: slug, | ||||
|         isCategory: isCategory, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   AutoDisposeFutureProviderElement<bool> createElement() { | ||||
|     return _PostCategorySubscriptionStatusProviderElement(this); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(Object other) { | ||||
|     return other is PostCategorySubscriptionStatusProvider && | ||||
|         other.slug == slug && | ||||
|         other.isCategory == isCategory; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   int get hashCode { | ||||
|     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||
|     hash = _SystemHash.combine(hash, slug.hashCode); | ||||
|     hash = _SystemHash.combine(hash, isCategory.hashCode); | ||||
|  | ||||
|     return _SystemHash.finish(hash); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||
| // ignore: unused_element | ||||
| mixin PostCategorySubscriptionStatusRef on AutoDisposeFutureProviderRef<bool> { | ||||
|   /// The parameter `slug` of this provider. | ||||
|   String get slug; | ||||
|  | ||||
|   /// The parameter `isCategory` of this provider. | ||||
|   bool get isCategory; | ||||
| } | ||||
|  | ||||
| class _PostCategorySubscriptionStatusProviderElement | ||||
|     extends AutoDisposeFutureProviderElement<bool> | ||||
|     with PostCategorySubscriptionStatusRef { | ||||
|   _PostCategorySubscriptionStatusProviderElement(super.provider); | ||||
|  | ||||
|   @override | ||||
|   String get slug => (origin as PostCategorySubscriptionStatusProvider).slug; | ||||
|   @override | ||||
|   bool get isCategory => | ||||
|       (origin as PostCategorySubscriptionStatusProvider).isCategory; | ||||
| } | ||||
|  | ||||
| // 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 | ||||
|   | ||||
| @@ -83,6 +83,7 @@ class SliverPostList extends HookConsumerWidget { | ||||
|   final bool isOpenable; | ||||
|   final Function? onRefresh; | ||||
|   final Function(SnPost)? onUpdate; | ||||
|   final double? maxWidth; | ||||
|  | ||||
|   const SliverPostList({ | ||||
|     super.key, | ||||
| @@ -98,6 +99,7 @@ class SliverPostList extends HookConsumerWidget { | ||||
|     this.isOpenable = true, | ||||
|     this.onRefresh, | ||||
|     this.onUpdate, | ||||
|     this.maxWidth, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
| @@ -139,6 +141,15 @@ class SliverPostList extends HookConsumerWidget { | ||||
|  | ||||
|               final post = data.items[index]; | ||||
|  | ||||
|               if (maxWidth != null) { | ||||
|                 return Center( | ||||
|                   child: ConstrainedBox( | ||||
|                     constraints: BoxConstraints(maxWidth: maxWidth!), | ||||
|                     child: _buildPostItem(post), | ||||
|                   ), | ||||
|                 ); | ||||
|               } | ||||
|  | ||||
|               return _buildPostItem(post); | ||||
|             }, | ||||
|           ), | ||||
|   | ||||
		Reference in New Issue
	
	Block a user