diff --git a/assets/locales/en_us.json b/assets/locales/en_us.json index 4f536f7..196cb82 100644 --- a/assets/locales/en_us.json +++ b/assets/locales/en_us.json @@ -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" } diff --git a/assets/locales/zh_cn.json b/assets/locales/zh_cn.json index c1ea4f6..a349c30 100644 --- a/assets/locales/zh_cn.json +++ b/assets/locales/zh_cn.json @@ -411,5 +411,8 @@ "userLevel13": "万古流芳", "postBrowsingIn": "浏览 @region 内的帖子中", "needRestartToApply": "需要重启应用来生效", - "holdToSeeDetail": "长按 / 鼠标悬浮来查看详情" + "holdToSeeDetail": "长按 / 鼠标悬浮来查看详情", + "subscribe": "订阅", + "subscribed": "已订阅", + "unsubscribe": "取消订阅" } diff --git a/lib/main.dart b/lib/main.dart index ad32ba4..3a320aa 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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().requestPermissions(); } diff --git a/lib/models/account.dart b/lib/models/account.dart index 8b4cc66..f545b68 100644 --- a/lib/models/account.dart +++ b/lib/models/account.dart @@ -1,4 +1,4 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:json_annotation/json_annotation.dart'; part 'account.g.dart'; diff --git a/lib/models/account_status.dart b/lib/models/account_status.dart index 40ce724..5ded0bd 100644 --- a/lib/models/account_status.dart +++ b/lib/models/account_status.dart @@ -1,4 +1,4 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:json_annotation/json_annotation.dart'; part 'account_status.g.dart'; diff --git a/lib/models/attachment.dart b/lib/models/attachment.dart index 90e9f9f..bd6c00f 100644 --- a/lib/models/attachment.dart +++ b/lib/models/attachment.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'; diff --git a/lib/models/auth.dart b/lib/models/auth.dart index 339736c..83c00a6 100644 --- a/lib/models/auth.dart +++ b/lib/models/auth.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'; diff --git a/lib/models/call.dart b/lib/models/call.dart index a79b9b4..3b868e7 100644 --- a/lib/models/call.dart +++ b/lib/models/call.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'; diff --git a/lib/models/channel.dart b/lib/models/channel.dart index e5b3748..91ba7ba 100644 --- a/lib/models/channel.dart +++ b/lib/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'; diff --git a/lib/models/daily_sign.dart b/lib/models/daily_sign.dart index 508888d..85d87fc 100644 --- a/lib/models/daily_sign.dart +++ b/lib/models/daily_sign.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'; diff --git a/lib/models/event.dart b/lib/models/event.dart index 784565a..ec4566a 100644 --- a/lib/models/event.dart +++ b/lib/models/event.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'; diff --git a/lib/models/link.dart b/lib/models/link.dart index ca58c82..d5e018f 100644 --- a/lib/models/link.dart +++ b/lib/models/link.dart @@ -1,4 +1,4 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:json_annotation/json_annotation.dart'; part 'link.g.dart'; diff --git a/lib/models/notification.dart b/lib/models/notification.dart index 8e33d21..466d2bd 100755 --- a/lib/models/notification.dart +++ b/lib/models/notification.dart @@ -1,4 +1,4 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:json_annotation/json_annotation.dart'; part 'notification.g.dart'; diff --git a/lib/models/packet.dart b/lib/models/packet.dart index 230225d..14f8ba8 100644 --- a/lib/models/packet.dart +++ b/lib/models/packet.dart @@ -1,4 +1,4 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:json_annotation/json_annotation.dart'; part 'packet.g.dart'; diff --git a/lib/models/pagination.dart b/lib/models/pagination.dart index 5603758..574b37c 100755 --- a/lib/models/pagination.dart +++ b/lib/models/pagination.dart @@ -1,4 +1,4 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:json_annotation/json_annotation.dart'; part 'pagination.g.dart'; diff --git a/lib/models/post.dart b/lib/models/post.dart index ca10ca0..f879441 100755 --- a/lib/models/post.dart +++ b/lib/models/post.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'; diff --git a/lib/models/post_categories.dart b/lib/models/post_categories.dart index 6bcf911..fd193b0 100644 --- a/lib/models/post_categories.dart +++ b/lib/models/post_categories.dart @@ -1,4 +1,4 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:json_annotation/json_annotation.dart'; part 'post_categories.g.dart'; diff --git a/lib/models/realm.dart b/lib/models/realm.dart index ab5aa79..fb89957 100644 --- a/lib/models/realm.dart +++ b/lib/models/realm.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'; diff --git a/lib/models/relations.dart b/lib/models/relations.dart index 42cdb30..52c3df8 100644 --- a/lib/models/relations.dart +++ b/lib/models/relations.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'; diff --git a/lib/models/stickers.dart b/lib/models/stickers.dart index e1e1b6b..8da09b5 100644 --- a/lib/models/stickers.dart +++ b/lib/models/stickers.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'; diff --git a/lib/models/subscription.dart b/lib/models/subscription.dart new file mode 100644 index 0000000..d977a80 --- /dev/null +++ b/lib/models/subscription.dart @@ -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 json) => + _$SubscriptionFromJson(json); + + Map toJson() => _$SubscriptionToJson(this); +} diff --git a/lib/models/subscription.g.dart b/lib/models/subscription.g.dart new file mode 100644 index 0000000..c7ebe7d --- /dev/null +++ b/lib/models/subscription.g.dart @@ -0,0 +1,46 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'subscription.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Subscription _$SubscriptionFromJson(Map 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), + accountId: (json['account_id'] as num?)?.toInt(), + account: json['account'] == null + ? null + : Account.fromJson(json['account'] as Map), + tagId: (json['tag_id'] as num?)?.toInt(), + tag: json['tag'] == null + ? null + : Tag.fromJson(json['tag'] as Map), + categoryId: (json['category_id'] as num?)?.toInt(), + category: json['category'] == null + ? null + : Category.fromJson(json['category'] as Map), + ); + +Map _$SubscriptionToJson(Subscription instance) => + { + '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(), + }; diff --git a/lib/providers/content/posts.dart b/lib/providers/content/posts.dart index 8248687..bd9ecb0 100644 --- a/lib/providers/content/posts.dart +++ b/lib/providers/content/posts.dart @@ -97,8 +97,8 @@ class PostProvider extends GetConnect { return resp; } - Future> listPostFeaturedReply(String alias) async { - final resp = await get('/posts/$alias/replies/featured'); + Future> listPostFeaturedReply(String alias, {int take = 1}) async { + final resp = await get('/posts/$alias/replies/featured?take=$take'); if (resp.statusCode != 200) { throw RequestException(resp); } diff --git a/lib/providers/subscription.dart b/lib/providers/subscription.dart new file mode 100644 index 0000000..9369473 --- /dev/null +++ b/lib/providers/subscription.dart @@ -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 getSubscriptionOnUser(int userId) async { + final auth = Get.find(); + 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 subscribeToUser(int userId) async { + final auth = Get.find(); + 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 unsubscribeFromUser(int userId) async { + final auth = Get.find(); + 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); + } + } +} diff --git a/lib/screens/account/profile_page.dart b/lib/screens/account/profile_page.dart index 09c854a..fca5203 100644 --- a/lib/screens/account/profile_page.dart +++ b/lib/screens/account/profile_page.dart @@ -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 { bool _isBusy = true; bool _isMakingFriend = false; + bool _isSubscribing = false; bool _showMature = false; Account? _userinfo; + Subscription? _subscription; List _pinnedPosts = List.empty(); int _totalUpvote = 0, _totalDownvote = 0; + Future _getSubscription() async { + setState(() => _isSubscribing = true); + _subscription = await Get.find() + .getSubscriptionOnUser(_userinfo!.id); + setState(() => _isSubscribing = false); + } + Future _getUserinfo() async { setState(() => _isBusy = true); @@ -70,7 +81,7 @@ class _AccountProfilePageState extends State { setState(() => _isBusy = false); } - Future getPinnedPosts() async { + Future _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 { } }); - _getUserinfo(); - getPinnedPosts(); + _getUserinfo().then((_) { + _getSubscription(); + _getPinnedPosts(); + }); } Widget _buildStatisticsEntry(String label, String content) { @@ -180,6 +193,40 @@ class _AccountProfilePageState extends State { ], ), ), + 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() + .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() + .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 { RefreshIndicator( onRefresh: () => Future.wait([ _postController.reloadAllOver(), - getPinnedPosts(), + _getPinnedPosts(), ]), child: CustomScrollView(slivers: [ SliverToBoxAdapter( diff --git a/lib/widgets/attachments/attachment_item.dart b/lib/widgets/attachments/attachment_item.dart index f87e82c..b9d2483 100644 --- a/lib/widgets/attachments/attachment_item.dart +++ b/lib/widgets/attachments/attachment_item.dart @@ -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, diff --git a/pubspec.lock b/pubspec.lock index cac38bf..c571551 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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: diff --git a/pubspec.yaml b/pubspec.yaml index 5827d2b..676c96d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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