diff --git a/assets/locales/en_us.json b/assets/locales/en_us.json index 4254b5b..011007b 100644 --- a/assets/locales/en_us.json +++ b/assets/locales/en_us.json @@ -98,6 +98,8 @@ "accountFriendBlocked": "Friend blocklist", "accountFriendListHint": "Swipe left to decline, right to approve", "accountFriendRequestSent": "Friend request sent, waiting for processing...", + "accountBlocked": "Account has been blocked", + "accountUnblocked": "Account has been unblocked", "accountSuspended": "Account was suspended", "accountSuspendedAt": "Account was suspended since @date", "aspectRatio": "Aspect Ratio", @@ -457,5 +459,9 @@ "serviceStatus": "Status of Service", "firstBootTime": "First boot at @time", "rateTheApp": "Rate the app", - "rateTheAppDesc": "Rate Solar Network on the App Store to let us serve you better!" + "rateTheAppDesc": "Rate Solar Network on the App Store to let us serve you better!", + "friendAdd": "Add as friend", + "blockUser": "Block user", + "unblockUser": "Unblock user", + "learnMoreAboutPerson": "Learn more about that person" } diff --git a/assets/locales/zh_cn.json b/assets/locales/zh_cn.json index 4c68a02..d444d0a 100644 --- a/assets/locales/zh_cn.json +++ b/assets/locales/zh_cn.json @@ -98,6 +98,8 @@ "accountFriendBlocked": "好友黑名单", "accountFriendListHint": "左滑来拒绝,右滑来接受", "accountFriendRequestSent": "好友请求已发送,等待处理对方中……", + "accountBlocked": "已屏蔽账号", + "accountUnblocked": "已解除屏蔽账号", "accountSuspended": "帐号被停用", "accountSuspendedAt": "该帐号自 @date 起被停用", "aspectRatio": "纵横比", @@ -453,5 +455,9 @@ "serviceStatus": "服务状态", "firstBootTime": "首次启动于 @time", "rateTheApp": "给应用评分", - "rateTheAppDesc": "在 App Store 上给 Solar Network 评分,让我们更好地为您服务吧!" + "rateTheAppDesc": "在 App Store 上给 Solar Network 评分,让我们更好地为您服务吧!", + "friendAdd": "添加好友", + "blockUser": "屏蔽用户", + "unblockUser": "解除屏蔽用户", + "learMoreAboutPerson": "了解关于 TA 的更多" } diff --git a/lib/models/attachment.dart b/lib/models/attachment.dart index bd6c00f..2d351f2 100644 --- a/lib/models/attachment.dart +++ b/lib/models/attachment.dart @@ -34,7 +34,7 @@ class Attachment { String alt; String mimetype; String hash; - int destination; + String destination; bool isAnalyzed; bool isUploaded; Map? metadata; diff --git a/lib/models/attachment.g.dart b/lib/models/attachment.g.dart index 5628ca5..eb15493 100644 --- a/lib/models/attachment.g.dart +++ b/lib/models/attachment.g.dart @@ -36,7 +36,7 @@ Attachment _$AttachmentFromJson(Map json) => Attachment( alt: json['alt'] as String, mimetype: json['mimetype'] as String, hash: json['hash'] as String, - destination: (json['destination'] as num).toInt(), + destination: json['destination'] as String, isAnalyzed: json['is_analyzed'] as bool, isUploaded: json['is_uploaded'] as bool, metadata: json['metadata'] as Map?, diff --git a/lib/providers/relation.dart b/lib/providers/relation.dart index c1fdcac..105d285 100644 --- a/lib/providers/relation.dart +++ b/lib/providers/relation.dart @@ -26,6 +26,19 @@ class RelationshipProvider extends GetxController { return _friends.any((x) => x.relatedId == account.id); } + Future getRelationship(int relatedId) async { + final AuthProvider auth = Get.find(); + final client = await auth.configureClient('auth'); + final resp = await client.get('/users/me/relations/$relatedId'); + if (resp.statusCode == 404) { + return null; + } else if (resp.statusCode != 200) { + throw RequestException(resp); + } + + return Relationship.fromJson(resp.body); + } + Future listRelation() async { final AuthProvider auth = Get.find(); final client = await auth.configureClient('auth'); @@ -38,7 +51,19 @@ class RelationshipProvider extends GetxController { return client.get('/users/me/relations?status=$status'); } - Future makeFriend(String username) async { + Future blockUser(String username) async { + final AuthProvider auth = Get.find(); + final client = await auth.configureClient('auth'); + final resp = + await client.post('/users/me/relations/block?related=$username', {}); + if (resp.statusCode != 200) { + throw RequestException(resp); + } + + return Relationship.fromJson(resp.body); + } + + Future makeFriend(String username) async { final AuthProvider auth = Get.find(); final client = await auth.configureClient('auth'); final resp = await client.post('/users/me/relations?related=$username', {}); @@ -46,7 +71,7 @@ class RelationshipProvider extends GetxController { throw RequestException(resp); } - return resp; + return Relationship.fromJson(resp.body); } Future handleRelation( @@ -64,17 +89,17 @@ class RelationshipProvider extends GetxController { return resp; } - Future editRelation(Relationship relationship, int status) async { + Future editRelation(int relatedId, int status) async { final AuthProvider auth = Get.find(); final client = await auth.configureClient('auth'); - final resp = await client.patch( - '/users/me/relations/${relationship.relatedId}', + final resp = await client.put( + '/users/me/relations/$relatedId', {'status': status}, ); if (resp.statusCode != 200) { throw RequestException(resp); } - return resp; + return Relationship.fromJson(resp.body); } } diff --git a/lib/screens/account/profile_page.dart b/lib/screens/account/profile_page.dart index e61e01f..a4c47ef 100644 --- a/lib/screens/account/profile_page.dart +++ b/lib/screens/account/profile_page.dart @@ -13,6 +13,7 @@ import 'package:solian/models/attachment.dart'; import 'package:solian/models/daily_sign.dart'; import 'package:solian/models/pagination.dart'; import 'package:solian/models/post.dart'; +import 'package:solian/models/relations.dart'; import 'package:solian/models/subscription.dart'; import 'package:solian/providers/account_status.dart'; import 'package:solian/providers/relation.dart'; @@ -26,6 +27,7 @@ import 'package:solian/widgets/attachments/attachment_list.dart'; import 'package:solian/widgets/daily_sign/history_chart.dart'; import 'package:solian/widgets/posts/post_list.dart'; import 'package:solian/widgets/posts/post_warped_list.dart'; +import 'package:solian/widgets/reports/abuse_report.dart'; import 'package:solian/widgets/sized_container.dart'; class AccountProfilePage extends StatefulWidget { @@ -50,6 +52,7 @@ class _AccountProfilePageState extends State { Account? _userinfo; Subscription? _subscription; + Relationship? _relationship; List _pinnedPosts = List.empty(); List _dailySignRecords = List.empty(); int _totalUpvote = 0, _totalDownvote = 0; @@ -61,6 +64,15 @@ class _AccountProfilePageState extends State { setState(() => _isSubscribing = false); } + Future _getRelationship() async { + setState(() => _isBusy = true); + + final relations = Get.find(); + _relationship = await relations.getRelationship(_userinfo!.id); + + setState(() => _isBusy = false); + } + Future _getUserinfo() async { setState(() => _isBusy = true); @@ -151,6 +163,7 @@ class _AccountProfilePageState extends State { }); _getUserinfo().then((_) { + _getRelationship(); _getSubscription(); _getPinnedPosts(); _getDailySignRecords(); @@ -221,7 +234,7 @@ class _AccountProfilePageState extends State { ), ), if (_userinfo != null && _subscription == null) - OutlinedButton( + IconButton( style: const ButtonStyle( visualDensity: VisualDensity(horizontal: -4, vertical: -2), @@ -235,10 +248,11 @@ class _AccountProfilePageState extends State { .subscribeToUser(_userinfo!.id); setState(() => _isSubscribing = false); }, - child: Text('subscribe'.tr), + icon: const Icon(Icons.add_circle_outline), + tooltip: 'subscribe'.tr, ) else if (_userinfo != null) - OutlinedButton( + IconButton( style: const ButtonStyle( visualDensity: VisualDensity(horizontal: -4, vertical: -2), @@ -252,10 +266,10 @@ class _AccountProfilePageState extends State { _subscription = null; setState(() => _isSubscribing = false); }, - child: Text('unsubscribe'.tr), + icon: const Icon(Icons.remove_circle_outline), + tooltip: 'unsubscribe'.tr, ), - if (_userinfo != null && - !_relationshipProvider.hasFriend(_userinfo!)) + if (_userinfo != null && _relationship == null) IconButton( icon: const Icon(Icons.person_add), onPressed: _isMakingFriend @@ -263,7 +277,7 @@ class _AccountProfilePageState extends State { : () async { setState(() => _isMakingFriend = true); try { - await _relationshipProvider + _relationship = await _relationshipProvider .makeFriend(widget.name); context.showSnackbar( 'accountFriendRequestSent'.tr, @@ -274,6 +288,7 @@ class _AccountProfilePageState extends State { setState(() => _isMakingFriend = false); } }, + tooltip: 'friendAdd'.tr, ) else const IconButton( @@ -300,6 +315,7 @@ class _AccountProfilePageState extends State { physics: const NeverScrollableScrollPhysics(), children: [ ListView( + padding: EdgeInsets.zero, children: [ const Gap(16), CenteredContainer( @@ -421,9 +437,117 @@ class _AccountProfilePageState extends State { ), ), ).marginOnly( - right: 24, left: 12, bottom: 8, top: 24), + right: 24, + left: 12, + bottom: 8, + top: 24, + ), ) ], + appendWidgets: [ + Card( + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 4, + horizontal: 8, + ), + width: double.maxFinite, + child: Wrap( + alignment: WrapAlignment.spaceAround, + children: [ + TextButton.icon( + style: const ButtonStyle( + visualDensity: VisualDensity( + horizontal: -4, + vertical: -2, + ), + ), + onPressed: () { + showDialog( + context: context, + builder: (context) => AbuseReportDialog( + resourceId: 'user:${_userinfo!.id}', + ), + ); + }, + icon: const Icon( + Icons.flag, + size: 16, + ), + label: Text('reportAbuse'.tr), + ), + if (_relationship?.status != 2) + TextButton.icon( + style: const ButtonStyle( + visualDensity: VisualDensity( + horizontal: -4, + vertical: -2, + ), + ), + onPressed: _isMakingFriend + ? null + : () async { + setState( + () => _isMakingFriend = true); + try { + _relationship = + await _relationshipProvider + .blockUser(widget.name); + context.showSnackbar( + 'accountBlocked'.tr, + ); + } catch (e) { + context.showErrorDialog(e); + } finally { + setState(() => + _isMakingFriend = false); + } + }, + icon: const Icon( + Icons.block, + size: 16, + ), + label: Text('blockUser'.tr), + ) + else + TextButton.icon( + style: const ButtonStyle( + visualDensity: VisualDensity( + horizontal: -4, + vertical: -2, + ), + ), + onPressed: _isMakingFriend + ? null + : () async { + setState( + () => _isMakingFriend = true); + try { + _relationship = + await _relationshipProvider + .editRelation( + _userinfo!.id, 1); + context.showSnackbar( + 'accountUnblocked'.tr, + ); + } catch (e) { + context.showErrorDialog(e); + } finally { + setState(() => + _isMakingFriend = false); + } + }, + icon: const Icon( + Icons.add_circle_outline, + size: 16, + ), + label: Text('unblockUser'.tr), + ), + ], + ), + ), + ), + ], ), ), ], diff --git a/lib/widgets/account/account_heading.dart b/lib/widgets/account/account_heading.dart index ffd4305..7e7d2f0 100644 --- a/lib/widgets/account/account_heading.dart +++ b/lib/widgets/account/account_heading.dart @@ -23,6 +23,7 @@ class AccountHeadingWidget extends StatelessWidget { final AccountProfile? profile; final List? badges; final List? extraWidgets; + final List? appendWidgets; final Future? status; final Function? onEditStatus; @@ -39,6 +40,7 @@ class AccountHeadingWidget extends StatelessWidget { this.profile, this.status, this.extraWidgets, + this.appendWidgets, this.onEditStatus, }); @@ -257,6 +259,7 @@ class AccountHeadingWidget extends StatelessWidget { ), ), ).paddingSymmetric(horizontal: 16), + ...?appendWidgets?.map((x) => x.paddingSymmetric(horizontal: 16)), ], ), ); diff --git a/lib/widgets/account/account_profile_popup.dart b/lib/widgets/account/account_profile_popup.dart index e28f14f..7ca58f4 100644 --- a/lib/widgets/account/account_profile_popup.dart +++ b/lib/widgets/account/account_profile_popup.dart @@ -106,10 +106,14 @@ class _AccountProfilePopupState extends State { extraWidgets: [ Card( child: ListTile( + leading: const Icon( + Icons.contact_page_outlined, + ), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(8)), ), title: Text('visitProfilePage'.tr), + subtitle: Text('learMoreAboutPerson'.tr), visualDensity: const VisualDensity(horizontal: -4, vertical: -2), trailing: const Icon(Icons.chevron_right), diff --git a/lib/widgets/account/relative_list.dart b/lib/widgets/account/relative_list.dart index de2e7a2..0a8cabc 100644 --- a/lib/widgets/account/relative_list.dart +++ b/lib/widgets/account/relative_list.dart @@ -28,42 +28,46 @@ class SilverRelativeList extends StatelessWidget { showModalBottomSheet( useRootNavigator: true, isScrollControlled: true, - backgroundColor: Theme - .of(context) - .colorScheme - .surface, + backgroundColor: Theme.of(context).colorScheme.surface, context: context, - builder: (context) => - AccountProfilePopup( - name: element.related.name, - ), + builder: (context) => AccountProfilePopup( + name: element.related.name, + ), ); }, ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ - if(element.status != 1 && element.status != 3) + if (element.status != 1 && element.status != 3) IconButton( icon: const Icon(Icons.check), onPressed: () { final RelationshipProvider provider = Get.find(); if (element.status == 0) { - provider.handleRelation(element, true).then((_) => onUpdate()); + provider + .handleRelation(element, true) + .then((_) => onUpdate()); } else { - provider.editRelation(element, 1).then((_) => onUpdate()); + provider + .editRelation(element.relatedId, 1) + .then((_) => onUpdate()); } }, ), - if(element.status != 2 && element.status != 3) + if (element.status != 2 && element.status != 3) IconButton( icon: const Icon(Icons.close), onPressed: () { final RelationshipProvider provider = Get.find(); if (element.status == 0) { - provider.handleRelation(element, false).then((_) => onUpdate()); + provider + .handleRelation(element, false) + .then((_) => onUpdate()); } else { - provider.editRelation(element, 2).then((_) => onUpdate()); + provider + .editRelation(element.relatedId, 2) + .then((_) => onUpdate()); } }, ), diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements index 70d3e94..da39c2f 100644 --- a/macos/Runner/DebugProfile.entitlements +++ b/macos/Runner/DebugProfile.entitlements @@ -12,8 +12,6 @@ com.apple.security.device.audio-input - com.apple.security.device.bluetooth - com.apple.security.device.camera com.apple.security.files.user-selected.read-only diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements index 8679d81..4235e02 100644 --- a/macos/Runner/Release.entitlements +++ b/macos/Runner/Release.entitlements @@ -10,8 +10,6 @@ com.apple.security.device.audio-input - com.apple.security.device.bluetooth - com.apple.security.device.camera com.apple.security.files.user-selected.read-only