✨ Subscriptions
This commit is contained in:
		| @@ -410,5 +410,8 @@ | ||||
|   "userLevel13": "Immortal", | ||||
|   "postBrowsingIn": "Browsing in @region", | ||||
|   "needRestartToApply": "Restart the application to take effect", | ||||
|   "holdToSeeDetail": "Long press / Mouse hover to see detail" | ||||
|   "holdToSeeDetail": "Long press / Mouse hover to see detail", | ||||
|   "subscribe": "Subscribe", | ||||
|   "subscribed": "Subscribed", | ||||
|   "unsubscribe": "Unsubscribe" | ||||
| } | ||||
|   | ||||
| @@ -411,5 +411,8 @@ | ||||
|   "userLevel13": "万古流芳", | ||||
|   "postBrowsingIn": "浏览 @region 内的帖子中", | ||||
|   "needRestartToApply": "需要重启应用来生效", | ||||
|   "holdToSeeDetail": "长按 / 鼠标悬浮来查看详情" | ||||
|   "holdToSeeDetail": "长按 / 鼠标悬浮来查看详情", | ||||
|   "subscribe": "订阅", | ||||
|   "subscribed": "已订阅", | ||||
|   "unsubscribe": "取消订阅" | ||||
| } | ||||
|   | ||||
| @@ -20,6 +20,7 @@ import 'package:solian/providers/last_read.dart'; | ||||
| import 'package:solian/providers/link_expander.dart'; | ||||
| import 'package:solian/providers/navigation.dart'; | ||||
| import 'package:solian/providers/stickers.dart'; | ||||
| import 'package:solian/providers/subscription.dart'; | ||||
| import 'package:solian/providers/theme_switcher.dart'; | ||||
| import 'package:solian/providers/websocket.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| @@ -151,6 +152,7 @@ class SolianApp extends StatelessWidget { | ||||
|     Get.lazyPut(() => LinkExpandProvider()); | ||||
|     Get.lazyPut(() => DailySignProvider()); | ||||
|     Get.lazyPut(() => LastReadProvider()); | ||||
|     Get.lazyPut(() => SubscriptionProvider()); | ||||
|  | ||||
|     Get.find<WebSocketProvider>().requestPermissions(); | ||||
|   } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'package:json_annotation/json_annotation.dart'; | ||||
|  | ||||
| part 'account.g.dart'; | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'package:json_annotation/json_annotation.dart'; | ||||
|  | ||||
| part 'account_status.g.dart'; | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'package:json_annotation/json_annotation.dart'; | ||||
| import 'package:solian/models/account.dart'; | ||||
|  | ||||
| part 'attachment.g.dart'; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'package:json_annotation/json_annotation.dart'; | ||||
| import 'package:solian/models/account.dart'; | ||||
|  | ||||
| part 'auth.g.dart'; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'package:json_annotation/json_annotation.dart'; | ||||
| import 'package:livekit_client/livekit_client.dart'; | ||||
| import 'package:solian/models/channel.dart'; | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'package:json_annotation/json_annotation.dart'; | ||||
| import 'package:solian/models/account.dart'; | ||||
| import 'package:solian/models/realm.dart'; | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'package:json_annotation/json_annotation.dart'; | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:solian/models/account.dart'; | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'package:json_annotation/json_annotation.dart'; | ||||
| import 'package:solian/models/channel.dart'; | ||||
|  | ||||
| part 'event.g.dart'; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'package:json_annotation/json_annotation.dart'; | ||||
|  | ||||
| part 'link.g.dart'; | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'package:json_annotation/json_annotation.dart'; | ||||
|  | ||||
| part 'notification.g.dart'; | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'package:json_annotation/json_annotation.dart'; | ||||
|  | ||||
| part 'packet.g.dart'; | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'package:json_annotation/json_annotation.dart'; | ||||
|  | ||||
| part 'pagination.g.dart'; | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'package:json_annotation/json_annotation.dart'; | ||||
| import 'package:solian/models/account.dart'; | ||||
| import 'package:solian/models/post_categories.dart'; | ||||
| import 'package:solian/models/realm.dart'; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'package:json_annotation/json_annotation.dart'; | ||||
|  | ||||
| part 'post_categories.g.dart'; | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'package:json_annotation/json_annotation.dart'; | ||||
| import 'package:solian/models/account.dart'; | ||||
|  | ||||
| part 'realm.g.dart'; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'package:json_annotation/json_annotation.dart'; | ||||
| import 'package:solian/models/account.dart'; | ||||
|  | ||||
| part 'relations.g.dart'; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'package:json_annotation/json_annotation.dart'; | ||||
| import 'package:solian/models/account.dart'; | ||||
| import 'package:solian/models/attachment.dart'; | ||||
| import 'package:solian/services.dart'; | ||||
|   | ||||
							
								
								
									
										41
									
								
								lib/models/subscription.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								lib/models/subscription.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| import 'package:json_annotation/json_annotation.dart'; | ||||
| import 'package:solian/models/account.dart'; | ||||
| import 'package:solian/models/post_categories.dart'; | ||||
|  | ||||
| part 'subscription.g.dart'; | ||||
|  | ||||
| @JsonSerializable() | ||||
| class Subscription { | ||||
|   int id; | ||||
|   DateTime createdAt; | ||||
|   DateTime updatedAt; | ||||
|   DateTime? deletedAt; | ||||
|   int followerId; | ||||
|   Account follower; | ||||
|   int? accountId; | ||||
|   Account? account; | ||||
|   int? tagId; | ||||
|   Tag? tag; | ||||
|   int? categoryId; | ||||
|   Category? category; | ||||
|  | ||||
|   Subscription({ | ||||
|     required this.id, | ||||
|     required this.createdAt, | ||||
|     required this.updatedAt, | ||||
|     required this.deletedAt, | ||||
|     required this.followerId, | ||||
|     required this.follower, | ||||
|     required this.accountId, | ||||
|     required this.account, | ||||
|     required this.tagId, | ||||
|     required this.tag, | ||||
|     required this.categoryId, | ||||
|     required this.category, | ||||
|   }); | ||||
|  | ||||
|   factory Subscription.fromJson(Map<String, dynamic> json) => | ||||
|       _$SubscriptionFromJson(json); | ||||
|  | ||||
|   Map<String, dynamic> toJson() => _$SubscriptionToJson(this); | ||||
| } | ||||
							
								
								
									
										46
									
								
								lib/models/subscription.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								lib/models/subscription.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| // GENERATED CODE - DO NOT MODIFY BY HAND | ||||
|  | ||||
| part of 'subscription.dart'; | ||||
|  | ||||
| // ************************************************************************** | ||||
| // JsonSerializableGenerator | ||||
| // ************************************************************************** | ||||
|  | ||||
| Subscription _$SubscriptionFromJson(Map<String, dynamic> json) => Subscription( | ||||
|       id: (json['id'] as num).toInt(), | ||||
|       createdAt: DateTime.parse(json['created_at'] as String), | ||||
|       updatedAt: DateTime.parse(json['updated_at'] as String), | ||||
|       deletedAt: json['deleted_at'] == null | ||||
|           ? null | ||||
|           : DateTime.parse(json['deleted_at'] as String), | ||||
|       followerId: (json['follower_id'] as num).toInt(), | ||||
|       follower: Account.fromJson(json['follower'] as Map<String, dynamic>), | ||||
|       accountId: (json['account_id'] as num?)?.toInt(), | ||||
|       account: json['account'] == null | ||||
|           ? null | ||||
|           : Account.fromJson(json['account'] as Map<String, dynamic>), | ||||
|       tagId: (json['tag_id'] as num?)?.toInt(), | ||||
|       tag: json['tag'] == null | ||||
|           ? null | ||||
|           : Tag.fromJson(json['tag'] as Map<String, dynamic>), | ||||
|       categoryId: (json['category_id'] as num?)?.toInt(), | ||||
|       category: json['category'] == null | ||||
|           ? null | ||||
|           : Category.fromJson(json['category'] as Map<String, dynamic>), | ||||
|     ); | ||||
|  | ||||
| Map<String, dynamic> _$SubscriptionToJson(Subscription instance) => | ||||
|     <String, dynamic>{ | ||||
|       'id': instance.id, | ||||
|       'created_at': instance.createdAt.toIso8601String(), | ||||
|       'updated_at': instance.updatedAt.toIso8601String(), | ||||
|       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||
|       'follower_id': instance.followerId, | ||||
|       'follower': instance.follower.toJson(), | ||||
|       'account_id': instance.accountId, | ||||
|       'account': instance.account?.toJson(), | ||||
|       'tag_id': instance.tagId, | ||||
|       'tag': instance.tag?.toJson(), | ||||
|       'category_id': instance.categoryId, | ||||
|       'category': instance.category?.toJson(), | ||||
|     }; | ||||
| @@ -97,8 +97,8 @@ class PostProvider extends GetConnect { | ||||
|     return resp; | ||||
|   } | ||||
|  | ||||
|   Future<List<Post>> listPostFeaturedReply(String alias) async { | ||||
|     final resp = await get('/posts/$alias/replies/featured'); | ||||
|   Future<List<Post>> listPostFeaturedReply(String alias, {int take = 1}) async { | ||||
|     final resp = await get('/posts/$alias/replies/featured?take=$take'); | ||||
|     if (resp.statusCode != 200) { | ||||
|       throw RequestException(resp); | ||||
|     } | ||||
|   | ||||
							
								
								
									
										46
									
								
								lib/providers/subscription.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								lib/providers/subscription.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:solian/exceptions/request.dart'; | ||||
| import 'package:solian/exceptions/unauthorized.dart'; | ||||
| import 'package:solian/models/subscription.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
|  | ||||
| class SubscriptionProvider extends GetxController { | ||||
|   Future<Subscription?> getSubscriptionOnUser(int userId) async { | ||||
|     final auth = Get.find<AuthProvider>(); | ||||
|     if (!auth.isAuthorized.value) throw const UnauthorizedException(); | ||||
|  | ||||
|     final client = await auth.configureClient('co'); | ||||
|     final resp = await client.get('/subscriptions/users/$userId'); | ||||
|     if (resp.statusCode == 404) { | ||||
|       return null; | ||||
|     } else if (resp.statusCode != 200) { | ||||
|       throw RequestException(resp); | ||||
|     } | ||||
|  | ||||
|     return Subscription.fromJson(resp.body); | ||||
|   } | ||||
|  | ||||
|   Future<Subscription> subscribeToUser(int userId) async { | ||||
|     final auth = Get.find<AuthProvider>(); | ||||
|     if (!auth.isAuthorized.value) throw const UnauthorizedException(); | ||||
|  | ||||
|     final client = await auth.configureClient('co'); | ||||
|     final resp = await client.post('/subscriptions/users/$userId', {}); | ||||
|     if (resp.statusCode != 200) { | ||||
|       throw RequestException(resp); | ||||
|     } | ||||
|  | ||||
|     return Subscription.fromJson(resp.body); | ||||
|   } | ||||
|  | ||||
|   Future<void> unsubscribeFromUser(int userId) async { | ||||
|     final auth = Get.find<AuthProvider>(); | ||||
|     if (!auth.isAuthorized.value) throw const UnauthorizedException(); | ||||
|  | ||||
|     final client = await auth.configureClient('co'); | ||||
|     final resp = await client.delete('/subscriptions/users/$userId'); | ||||
|     if (resp.statusCode != 200) { | ||||
|       throw RequestException(resp); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -8,8 +8,10 @@ import 'package:solian/models/account.dart'; | ||||
| import 'package:solian/models/attachment.dart'; | ||||
| import 'package:solian/models/pagination.dart'; | ||||
| import 'package:solian/models/post.dart'; | ||||
| import 'package:solian/models/subscription.dart'; | ||||
| import 'package:solian/providers/account_status.dart'; | ||||
| import 'package:solian/providers/relation.dart'; | ||||
| import 'package:solian/providers/subscription.dart'; | ||||
| import 'package:solian/services.dart'; | ||||
| import 'package:solian/theme.dart'; | ||||
| import 'package:solian/widgets/account/account_avatar.dart'; | ||||
| @@ -37,12 +39,21 @@ class _AccountProfilePageState extends State<AccountProfilePage> { | ||||
|  | ||||
|   bool _isBusy = true; | ||||
|   bool _isMakingFriend = false; | ||||
|   bool _isSubscribing = false; | ||||
|   bool _showMature = false; | ||||
|  | ||||
|   Account? _userinfo; | ||||
|   Subscription? _subscription; | ||||
|   List<Post> _pinnedPosts = List.empty(); | ||||
|   int _totalUpvote = 0, _totalDownvote = 0; | ||||
|  | ||||
|   Future<void> _getSubscription() async { | ||||
|     setState(() => _isSubscribing = true); | ||||
|     _subscription = await Get.find<SubscriptionProvider>() | ||||
|         .getSubscriptionOnUser(_userinfo!.id); | ||||
|     setState(() => _isSubscribing = false); | ||||
|   } | ||||
|  | ||||
|   Future<void> _getUserinfo() async { | ||||
|     setState(() => _isBusy = true); | ||||
|  | ||||
| @@ -70,7 +81,7 @@ class _AccountProfilePageState extends State<AccountProfilePage> { | ||||
|     setState(() => _isBusy = false); | ||||
|   } | ||||
|  | ||||
|   Future<void> getPinnedPosts() async { | ||||
|   Future<void> _getPinnedPosts() async { | ||||
|     final client = await ServiceFinder.configureClient('interactive'); | ||||
|     final resp = await client.get('/users/${widget.name}/pin'); | ||||
|     if (resp.statusCode != 200) { | ||||
| @@ -115,8 +126,10 @@ class _AccountProfilePageState extends State<AccountProfilePage> { | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     _getUserinfo(); | ||||
|     getPinnedPosts(); | ||||
|     _getUserinfo().then((_) { | ||||
|       _getSubscription(); | ||||
|       _getPinnedPosts(); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   Widget _buildStatisticsEntry(String label, String content) { | ||||
| @@ -180,6 +193,40 @@ class _AccountProfilePageState extends State<AccountProfilePage> { | ||||
|                         ], | ||||
|                       ), | ||||
|                     ), | ||||
|                     if (_userinfo != null && _subscription == null) | ||||
|                       OutlinedButton( | ||||
|                         style: const ButtonStyle( | ||||
|                           visualDensity: | ||||
|                               VisualDensity(horizontal: -4, vertical: -2), | ||||
|                         ), | ||||
|                         onPressed: _isSubscribing | ||||
|                             ? null | ||||
|                             : () async { | ||||
|                                 setState(() => _isSubscribing = true); | ||||
|                                 _subscription = | ||||
|                                     await Get.find<SubscriptionProvider>() | ||||
|                                         .subscribeToUser(_userinfo!.id); | ||||
|                                 setState(() => _isSubscribing = false); | ||||
|                               }, | ||||
|                         child: Text('subscribe'.tr), | ||||
|                       ) | ||||
|                     else if (_userinfo != null) | ||||
|                       OutlinedButton( | ||||
|                         style: const ButtonStyle( | ||||
|                           visualDensity: | ||||
|                               VisualDensity(horizontal: -4, vertical: -2), | ||||
|                         ), | ||||
|                         onPressed: _isSubscribing | ||||
|                             ? null | ||||
|                             : () async { | ||||
|                                 setState(() => _isSubscribing = true); | ||||
|                                 await Get.find<SubscriptionProvider>() | ||||
|                                     .unsubscribeFromUser(_userinfo!.id); | ||||
|                                 _subscription = null; | ||||
|                                 setState(() => _isSubscribing = false); | ||||
|                               }, | ||||
|                         child: Text('unsubscribe'.tr), | ||||
|                       ), | ||||
|                     if (_userinfo != null && | ||||
|                         !_relationshipProvider.hasFriend(_userinfo!)) | ||||
|                       IconButton( | ||||
| @@ -245,7 +292,7 @@ class _AccountProfilePageState extends State<AccountProfilePage> { | ||||
|               RefreshIndicator( | ||||
|                 onRefresh: () => Future.wait([ | ||||
|                   _postController.reloadAllOver(), | ||||
|                   getPinnedPosts(), | ||||
|                   _getPinnedPosts(), | ||||
|                 ]), | ||||
|                 child: CustomScrollView(slivers: [ | ||||
|                   SliverToBoxAdapter( | ||||
|   | ||||
| @@ -282,6 +282,8 @@ class _AttachmentItemVideoState extends State<_AttachmentItemVideo> { | ||||
|                         children: [ | ||||
|                           Text( | ||||
|                             widget.item.alt, | ||||
|                             maxLines: 1, | ||||
|                             overflow: TextOverflow.ellipsis, | ||||
|                             style: const TextStyle( | ||||
|                               shadows: labelShadows, | ||||
|                               color: Colors.white, | ||||
| @@ -447,6 +449,8 @@ class _AttachmentItemAudioState extends State<_AttachmentItemAudio> { | ||||
|                         children: [ | ||||
|                           Text( | ||||
|                             widget.item.alt, | ||||
|                             maxLines: 1, | ||||
|                             overflow: TextOverflow.ellipsis, | ||||
|                             style: const TextStyle( | ||||
|                               shadows: labelShadows, | ||||
|                               color: Colors.white, | ||||
|   | ||||
| @@ -909,14 +909,6 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "10.7.0" | ||||
|   freezed_annotation: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: freezed_annotation | ||||
|       sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.4.4" | ||||
|   frontend_server_client: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|   | ||||
| @@ -68,7 +68,6 @@ dependencies: | ||||
|   flutter_svg: ^2.0.10+1 | ||||
|   cross_file: ^0.3.4+2 | ||||
|   google_fonts: ^6.2.1 | ||||
|   freezed_annotation: ^2.4.4 | ||||
|   json_annotation: ^4.9.0 | ||||
|   gap: ^3.0.1 | ||||
|   fl_chart: ^0.69.0 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user