From 018441ea0b15dcc82a8298af7cfd0c1ab2b92411 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Thu, 12 Dec 2024 00:37:07 +0800 Subject: [PATCH] :sparkles: Support actions on user profile --- assets/translations/en-US.json | 2 +- lib/screens/account/pfp.dart | 186 ++++++++++++++++++++++++++++++++- 2 files changed, 185 insertions(+), 3 deletions(-) diff --git a/assets/translations/en-US.json b/assets/translations/en-US.json index bff96e5..b0da426 100644 --- a/assets/translations/en-US.json +++ b/assets/translations/en-US.json @@ -396,7 +396,7 @@ "accountJoinedAt": "Joined at {}", "accountBirthday": "Born on {}", "accountBadge": "Badge", - "badgeCompanyStaff": "Solsynth LLC Staff", + "badgeCompanyStaff": "Solsynth Staff", "badgeSiteMigration": "Solar Network Native", "accountStatus": "Status", "accountStatusOnline": "Online", diff --git a/lib/screens/account/pfp.dart b/lib/screens/account/pfp.dart index fe9e1e7..4b36c89 100644 --- a/lib/screens/account/pfp.dart +++ b/lib/screens/account/pfp.dart @@ -3,13 +3,17 @@ import 'dart:ui'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; +import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import 'package:relative_time/relative_time.dart'; import 'package:styled_widget/styled_widget.dart'; +import 'package:surface/providers/relationship.dart'; import 'package:surface/providers/sn_network.dart'; +import 'package:surface/screens/abuse_report.dart'; import 'package:surface/types/account.dart'; +import 'package:surface/types/post.dart'; import 'package:surface/widgets/account/account_image.dart'; import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/universal_image.dart'; @@ -73,6 +77,98 @@ class _UserScreenState extends State with SingleTickerProviderStateM } } + List? _publishers; + + Future _fetchPublishers() async { + try { + final sn = context.read(); + final resp = await sn.client.get('/cgi/co/publishers?user=${widget.name}'); + _publishers = List.from( + resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [], + ); + } catch (err) { + if (mounted) context.showErrorDialog(err); + rethrow; + } finally { + setState(() {}); + } + } + + bool _isBusy = false; + SnRelationship? _accountRelationship; + + Future _addFriend() async { + if (_isBusy) return; + + setState(() => _isBusy = true); + + try { + final sn = context.read(); + await sn.client.post('/cgi/id/users/me/relations/friend', data: { + 'related': _account!.name, + }); + if (!mounted) return; + context.showSnackbar('friendRequestSent'.tr()); + } catch (err) { + if (!mounted) return; + context.showErrorDialog(err); + } finally { + setState(() => _isBusy = false); + } + } + + Future _blockAccount() async { + if (_isBusy) return; + + setState(() => _isBusy = true); + + try { + final sn = context.read(); + await sn.client.post('/cgi/id/users/me/relations/block', data: { + 'related': _account!.name, + }); + if (!mounted) return; + context.showSnackbar('userBlocked'.tr(args: ['@${_account?.name ?? 'unknown'}'])); + } catch (err) { + if (!mounted) return; + context.showErrorDialog(err); + } finally { + setState(() => _isBusy = false); + } + } + + Future _unblockAccount() async { + if (_isBusy) return; + + setState(() => _isBusy = true); + + try { + final rel = context.read(); + await rel.updateRelationship(_account!.id, 1, _accountRelationship?.permNodes ?? {}); + if (!mounted) return; + context.showSnackbar('userUnblocked'.tr(args: ['@${_account?.name ?? 'unknown'}'])); + } catch (err) { + if (!mounted) return; + context.showErrorDialog(err); + } finally { + setState(() => _isBusy = false); + } + } + + void _showAbuseReportDialog() { + showDialog( + context: context, + builder: (context) => AbuseReportDialog( + resourceLocation: 'user:${_account?.name}', + ), + ).then((value) { + if (value == true && mounted) { + _fetchAccount(); + context.showSnackbar('abuseReportSubmitted'.tr()); + } + }); + } + double _appBarBlur = 0.0; late final _appBarWidth = MediaQuery.of(context).size.width; @@ -88,8 +184,19 @@ class _UserScreenState extends State with SingleTickerProviderStateM @override void initState() { super.initState(); - _fetchAccount().then((_) { + _fetchAccount().then((_) async { + if (!mounted) return; + _fetchStatus(); + _fetchPublishers(); + + try { + final rel = context.read(); + _accountRelationship = await rel.getRelationship(_account!.id); + if (mounted) setState(() {}); + } catch (_) { + // ignore + } }); _scrollController.addListener(_updateAppBarBlur); } @@ -205,6 +312,57 @@ class _UserScreenState extends State with SingleTickerProviderStateM ], ), ), + PopupMenuButton( + padding: EdgeInsets.zero, + style: ButtonStyle( + visualDensity: VisualDensity(horizontal: -4, vertical: -4), + ), + itemBuilder: (context) => [ + PopupMenuItem( + onTap: _showAbuseReportDialog, + child: Row( + children: [ + const Icon(Symbols.flag), + const Gap(16), + Text('report').tr(), + ], + ), + ), + if (_accountRelationship == null) + PopupMenuItem( + onTap: _addFriend, + child: Row( + children: [ + const Icon(Symbols.person_add), + const Gap(16), + Text('friendNew').tr(), + ], + ), + ), + if (_accountRelationship?.status != 2) + PopupMenuItem( + onTap: _blockAccount, + child: Row( + children: [ + const Icon(Symbols.block), + const Gap(16), + Text('friendBlock').tr(), + ], + ), + ) + else + PopupMenuItem( + onTap: _unblockAccount, + child: Row( + children: [ + const Icon(Symbols.block), + const Gap(16), + Text('friendUnblock').tr(), + ], + ), + ), + ], + ), ], ).padding(right: 8), const Gap(12), @@ -346,7 +504,31 @@ class _UserScreenState extends State with SingleTickerProviderStateM ), ], ), - ) + ), + const SliverGap(8), + SliverToBoxAdapter(child: const Divider()), + SliverList.builder( + itemCount: _publishers?.length ?? 0, + itemBuilder: (context, idx) { + final ele = _publishers![idx]; + return ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 24), + leading: AccountImage( + content: ele.avatar, + fallbackWidget: const Icon(Symbols.group, size: 24), + ), + title: Text(ele.nick), + subtitle: Text('@${ele.name}'), + trailing: const Icon(Symbols.chevron_right), + onTap: () { + GoRouter.of(context).pushNamed( + 'postPublisher', + pathParameters: {'name': ele.name}, + ); + }, + ); + }, + ), ], ), );