Social credit points & quick send friend request

This commit is contained in:
LittleSheep 2024-07-26 22:37:08 +08:00
parent 4552dfd3f3
commit 33d69908a6
7 changed files with 134 additions and 44 deletions

View File

@ -112,6 +112,7 @@ class SolianApp extends StatelessWidget {
Get.find<WebSocketProvider>().connect(); Get.find<WebSocketProvider>().connect();
Get.find<ChannelProvider>().refreshAvailableChannel(); Get.find<ChannelProvider>().refreshAvailableChannel();
Get.find<RelationshipProvider>().refreshFriendList();
try { try {
Get.find<WebSocketProvider>().registerPushNotifications(); Get.find<WebSocketProvider>().registerPushNotifications();

View File

@ -208,7 +208,6 @@ class AuthProvider extends GetConnect {
Future<void> refreshUserProfile() async { Future<void> refreshUserProfile() async {
final client = configureClient('auth'); final client = configureClient('auth');
final resp = await client.get('/users/me'); final resp = await client.get('/users/me');
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
throw Exception(resp.bodyString); throw Exception(resp.bodyString);

View File

@ -1,24 +1,42 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:solian/models/account.dart';
import 'package:solian/models/relations.dart'; import 'package:solian/models/relations.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/services.dart';
class RelationshipProvider extends GetConnect { class RelationshipProvider extends GetxController {
@override final RxList<Relationship> _friends = RxList.empty(growable: true);
void onInit() {
final AuthProvider auth = Get.find();
httpClient.baseUrl = ServiceFinder.buildUrl('auth', null); Future<void> refreshFriendList() async {
httpClient.addAuthenticator(auth.requestAuthenticator); final resp = await listRelationWithStatus(1);
_friends.value = resp.body
.map((e) => Relationship.fromJson(e))
.toList()
.cast<Relationship>();
_friends.refresh();
} }
Future<Response> listRelation() => get('/users/me/relations'); bool hasFriend(Account account) {
final auth = Get.find<AuthProvider>();
if (auth.userProfile.value!['id'] == account.id) return true;
return _friends.any((x) => x.id == account.id);
}
Future<Response> listRelationWithStatus(int status) => Future<Response> listRelation() {
get('/users/me/relations?status=$status'); final AuthProvider auth = Get.find();
final client = auth.configureClient('auth');
return client.get('/users/me/relations');
}
Future<Response> listRelationWithStatus(int status) {
final AuthProvider auth = Get.find();
final client = auth.configureClient('auth');
return client.get('/users/me/relations?status=$status');
}
Future<Response> makeFriend(String username) async { Future<Response> makeFriend(String username) async {
final resp = await post('/users/me/relations?related=$username', {}); final AuthProvider auth = Get.find();
final client = auth.configureClient('auth');
final resp = await client.post('/users/me/relations?related=$username', {});
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
throw Exception(resp.bodyString); throw Exception(resp.bodyString);
} }
@ -44,10 +62,10 @@ class RelationshipProvider extends GetConnect {
Future<Response> editRelation(Relationship relationship, int status) async { Future<Response> editRelation(Relationship relationship, int status) async {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
final client = auth.configureClient('auth'); final client = auth.configureClient('auth');
final resp = final resp = await client.patch(
await client.patch('/users/me/relations/${relationship.relatedId}', { '/users/me/relations/${relationship.relatedId}',
'status': status, {'status': status},
}); );
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
throw Exception(resp.bodyString); throw Exception(resp.bodyString);
} }

View File

@ -7,13 +7,12 @@ import 'package:solian/models/account.dart';
import 'package:solian/models/attachment.dart'; import 'package:solian/models/attachment.dart';
import 'package:solian/models/pagination.dart'; import 'package:solian/models/pagination.dart';
import 'package:solian/models/post.dart'; import 'package:solian/models/post.dart';
import 'package:solian/screens/account/notification.dart'; import 'package:solian/providers/relation.dart';
import 'package:solian/services.dart'; import 'package:solian/services.dart';
import 'package:solian/theme.dart'; import 'package:solian/theme.dart';
import 'package:solian/widgets/account/account_avatar.dart'; import 'package:solian/widgets/account/account_avatar.dart';
import 'package:solian/widgets/app_bar_leading.dart'; import 'package:solian/widgets/app_bar_leading.dart';
import 'package:solian/widgets/attachments/attachment_list.dart'; import 'package:solian/widgets/attachments/attachment_list.dart';
import 'package:solian/widgets/current_state_action.dart';
import 'package:solian/widgets/feed/feed_list.dart'; import 'package:solian/widgets/feed/feed_list.dart';
import 'package:solian/widgets/posts/post_list.dart'; import 'package:solian/widgets/posts/post_list.dart';
import 'package:solian/widgets/sized_container.dart'; import 'package:solian/widgets/sized_container.dart';
@ -28,11 +27,13 @@ class AccountProfilePage extends StatefulWidget {
} }
class _AccountProfilePageState extends State<AccountProfilePage> { class _AccountProfilePageState extends State<AccountProfilePage> {
late final RelationshipProvider _relationshipProvider;
late final PostListController _postController; late final PostListController _postController;
final PagingController<int, Attachment> _albumPagingController = final PagingController<int, Attachment> _albumPagingController =
PagingController(firstPageKey: 0); PagingController(firstPageKey: 0);
bool _isBusy = true; bool _isBusy = true;
bool _isMakingFriend = false;
bool _showMature = false; bool _showMature = false;
Account? _userinfo; Account? _userinfo;
@ -63,22 +64,35 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
_totalDownvote = resp.body['total_downvote']; _totalDownvote = resp.body['total_downvote'];
} }
resp = await client.get('/users/${widget.name}/pin'); setState(() => _isBusy = false);
}
Future<void> getPinnedPosts() async {
final client = ServiceFinder.configureClient('interactive');
final resp = await client.get('/users/${widget.name}/pin');
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
context.showErrorDialog(resp.bodyString).then((_) { context.showErrorDialog(resp.bodyString).then((_) {
Navigator.pop(context); Navigator.pop(context);
}); });
} else { } else {
_pinnedPosts = setState(() {
resp.body.map((x) => Post.fromJson(x)).toList().cast<Post>(); _pinnedPosts =
resp.body.map((x) => Post.fromJson(x)).toList().cast<Post>();
});
} }
}
setState(() => _isBusy = false); int get _userSocialCreditPoints {
int birthPart =
DateTime.now().difference(_userinfo!.createdAt.toLocal()).inSeconds;
birthPart = birthPart >> 16;
return _totalUpvote * 2 - _totalDownvote + birthPart;
} }
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_relationshipProvider = Get.find();
_postController = PostListController(author: widget.name); _postController = PostListController(author: widget.name);
_albumPagingController.addPageRequestListener((pageKey) async { _albumPagingController.addPageRequestListener((pageKey) async {
final client = ServiceFinder.configureClient('files'); final client = ServiceFinder.configureClient('files');
@ -99,7 +113,26 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
_albumPagingController.error = resp.bodyString; _albumPagingController.error = resp.bodyString;
} }
}); });
getUserinfo(); getUserinfo();
getPinnedPosts();
}
Widget _buildStatisticsEntry(String label, String content) {
return Expanded(
child: Column(
children: [
Text(
label,
style: Theme.of(context).textTheme.bodySmall,
),
Text(
content,
style: Theme.of(context).textTheme.bodyLarge,
),
],
),
);
} }
@override @override
@ -147,8 +180,29 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
], ],
), ),
), ),
const BackgroundStateWidget(), if (_userinfo != null &&
const NotificationButton(), !_relationshipProvider.hasFriend(_userinfo!))
IconButton(
icon: const Icon(Icons.person_add),
onPressed: _isMakingFriend
? null
: () async {
setState(() => _isMakingFriend = true);
try {
await _relationshipProvider.makeFriend(widget.name);
context.showSnackbar('accountFriendRequestSent'.tr);
} catch (e) {
context.showErrorDialog(e);
} finally {
setState(() => _isMakingFriend = false);
}
},
)
else
const IconButton(
icon: Icon(Icons.handshake),
onPressed: null,
),
SizedBox( SizedBox(
width: SolianTheme.isLargeScreen(context) ? 8 : 16, width: SolianTheme.isLargeScreen(context) ? 8 : 16,
), ),
@ -167,33 +221,42 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
children: [ children: [
RefreshIndicator( RefreshIndicator(
onRefresh: () => _postController.reloadAllOver(), onRefresh: () => Future.wait([
_postController.reloadAllOver(),
getPinnedPosts(),
]),
child: CustomScrollView(slivers: [ child: CustomScrollView(slivers: [
SliverToBoxAdapter( SliverToBoxAdapter(
child: Row( child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
Column( Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
Text( _buildStatisticsEntry(
'totalUpvote'.tr, 'totalSocialCreditPoints'.tr,
style: Theme.of(context).textTheme.bodySmall, _userinfo != null
), ? _userSocialCreditPoints.toString()
Text( : 0.toString(),
_totalUpvote.toString(),
style: Theme.of(context).textTheme.bodyLarge,
), ),
], ],
), ),
Column( const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
Text( Obx(
'totalDownvote'.tr, () => _buildStatisticsEntry(
style: Theme.of(context).textTheme.bodySmall, 'totalPostCount'.tr,
_postController.postTotal.value.toString(),
),
), ),
Text( _buildStatisticsEntry(
'totalUpvote'.tr,
_totalUpvote.toString(),
),
_buildStatisticsEntry(
'totalDownvote'.tr,
_totalDownvote.toString(), _totalDownvote.toString(),
style: Theme.of(context).textTheme.bodyLarge,
), ),
], ],
), ),

View File

@ -62,8 +62,10 @@ class _SignInPopupState extends State<SignInPopup> with ProtocolListener {
try { try {
await auth.signin(context, username, password); await auth.signin(context, username, password);
await auth.refreshAuthorizeStatus(); await Future.delayed(const Duration(milliseconds: 250), () async {
await auth.refreshUserProfile(); await auth.refreshAuthorizeStatus();
await auth.refreshUserProfile();
});
} on RiskyAuthenticateException catch (e) { } on RiskyAuthenticateException catch (e) {
showDialog( showDialog(
context: context, context: context,
@ -77,7 +79,8 @@ class _SignInPopupState extends State<SignInPopup> with ProtocolListener {
onPressed: () { onPressed: () {
const redirect = 'solink://auth?status=done'; const redirect = 'solink://auth?status=done';
launchUrlString( launchUrlString(
ServiceFinder.buildUrl('passport', '/mfa?redirect_uri=$redirect&ticketId=${e.ticketId}'), ServiceFinder.buildUrl('passport',
'/mfa?redirect_uri=$redirect&ticketId=${e.ticketId}'),
mode: LaunchMode.inAppWebView, mode: LaunchMode.inAppWebView,
); );
Navigator.pop(context); Navigator.pop(context);

View File

@ -53,6 +53,7 @@ const messagesEnglish = {
'accountFriendPending': 'Friend requests', 'accountFriendPending': 'Friend requests',
'accountFriendBlocked': 'Friend blocklist', 'accountFriendBlocked': 'Friend blocklist',
'accountFriendListHint': 'Swipe left to decline, right to approve', 'accountFriendListHint': 'Swipe left to decline, right to approve',
'accountFriendRequestSent': 'Friend request sent, waiting for processing...',
'accountSuspended': 'Account was suspended', 'accountSuspended': 'Account was suspended',
'accountSuspendedAt': 'Account was suspended since @date', 'accountSuspendedAt': 'Account was suspended since @date',
'aspectRatio': 'Aspect Ratio', 'aspectRatio': 'Aspect Ratio',
@ -86,6 +87,8 @@ const messagesEnglish = {
'notifyAllRead': 'Mark all as read', 'notifyAllRead': 'Mark all as read',
'notifyEmpty': 'All notifications read', 'notifyEmpty': 'All notifications read',
'notifyEmptyCaption': 'It seems like nothing happened recently', 'notifyEmptyCaption': 'It seems like nothing happened recently',
'totalSocialCreditPoints': 'Social Credit Points',
'totalPostCount': 'Posts',
'totalUpvote': 'Upvote', 'totalUpvote': 'Upvote',
'totalDownvote': 'Downvote', 'totalDownvote': 'Downvote',
'pinPost': 'Pin this post', 'pinPost': 'Pin this post',

View File

@ -52,6 +52,7 @@ const simplifiedChineseMessages = {
'accountFriendPending': '好友请求', 'accountFriendPending': '好友请求',
'accountFriendBlocked': '好友黑名单', 'accountFriendBlocked': '好友黑名单',
'accountFriendListHint': '左滑来拒绝,右滑来接受', 'accountFriendListHint': '左滑来拒绝,右滑来接受',
'accountFriendRequestSent': '好友请求已发送,等待处理对方中……',
'accountSuspended': '帐号被停用', 'accountSuspended': '帐号被停用',
'accountSuspendedAt': '该帐号自 @date 起被停用', 'accountSuspendedAt': '该帐号自 @date 起被停用',
'aspectRatio': '纵横比', 'aspectRatio': '纵横比',
@ -80,6 +81,8 @@ const simplifiedChineseMessages = {
'notifyAllRead': '已读所有通知', 'notifyAllRead': '已读所有通知',
'notifyEmpty': '通知箱为空', 'notifyEmpty': '通知箱为空',
'notifyEmptyCaption': '看起来最近没发生什么呢', 'notifyEmptyCaption': '看起来最近没发生什么呢',
'totalSocialCreditPoints': '社会信用点 async',
'totalPostCount': '总帖数',
'totalUpvote': '获顶数', 'totalUpvote': '获顶数',
'totalDownvote': '获踩数', 'totalDownvote': '获踩数',
'pinPost': '置顶本帖', 'pinPost': '置顶本帖',