✨ Allow user blocking publisher's user and report it
This commit is contained in:
parent
e05209ba3c
commit
811fc40d79
@ -432,5 +432,9 @@
|
|||||||
"serviceStatus": "Service Status",
|
"serviceStatus": "Service Status",
|
||||||
"termRelated": "Related Terms",
|
"termRelated": "Related Terms",
|
||||||
"appDetails": "App Details",
|
"appDetails": "App Details",
|
||||||
"postRecommendation": "Highlight Posts"
|
"postRecommendation": "Highlight Posts",
|
||||||
|
"publisherBlockHint": "Block {}",
|
||||||
|
"publisherBlockHintDescription": "You are going to block this publisher's maintainer, this will also block publishers that run by the same user.",
|
||||||
|
"userUnblocked": "{} has been unblocked.",
|
||||||
|
"userBlocked": "{} has been blocked."
|
||||||
}
|
}
|
||||||
|
@ -430,5 +430,9 @@
|
|||||||
"serviceStatus": "服务状态",
|
"serviceStatus": "服务状态",
|
||||||
"termRelated": "相关条款",
|
"termRelated": "相关条款",
|
||||||
"appDetails": "应用程序详情",
|
"appDetails": "应用程序详情",
|
||||||
"postRecommendation": "推荐帖子"
|
"postRecommendation": "推荐帖子",
|
||||||
|
"publisherBlockHint": "屏蔽 {}",
|
||||||
|
"publisherBlockHintDescription": "你正要屏蔽此发布者的运营者,该操作也将屏蔽由同一用户运营的发布者。",
|
||||||
|
"userUnblocked": "已解除屏蔽用户 {}",
|
||||||
|
"userBlocked": "已屏蔽用户 {}"
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/types/account.dart';
|
||||||
|
|
||||||
class SnRelationshipProvider {
|
class SnRelationshipProvider {
|
||||||
late final SnNetworkProvider _sn;
|
late final SnNetworkProvider _sn;
|
||||||
@ -9,6 +10,15 @@ class SnRelationshipProvider {
|
|||||||
_sn = context.read<SnNetworkProvider>();
|
_sn = context.read<SnNetworkProvider>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<SnRelationship?> getRelationship(int relatedId) async {
|
||||||
|
try {
|
||||||
|
final resp = await _sn.client.get('/cgi/id/users/me/relations/$relatedId');
|
||||||
|
return SnRelationship.fromJson(resp.data);
|
||||||
|
} catch (err) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> updateRelationship(
|
Future<void> updateRelationship(
|
||||||
int relatedId,
|
int relatedId,
|
||||||
int status,
|
int status,
|
||||||
|
@ -39,7 +39,7 @@ class _AbuseReportScreenState extends State<AbuseReportScreen> {
|
|||||||
void _showAbuseReportDialog() {
|
void _showAbuseReportDialog() {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => _AbuseReportDialog(),
|
builder: (context) => AbuseReportDialog(),
|
||||||
).then((value) {
|
).then((value) {
|
||||||
if (value == true && mounted) {
|
if (value == true && mounted) {
|
||||||
_fetchReports();
|
_fetchReports();
|
||||||
@ -91,19 +91,29 @@ class _AbuseReportScreenState extends State<AbuseReportScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AbuseReportDialog extends StatefulWidget {
|
class AbuseReportDialog extends StatefulWidget {
|
||||||
const _AbuseReportDialog({super.key});
|
final String? resourceLocation;
|
||||||
|
|
||||||
|
const AbuseReportDialog({super.key, this.resourceLocation});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_AbuseReportDialog> createState() => _AbuseReportDialogState();
|
State<AbuseReportDialog> createState() => _AbuseReportDialogState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AbuseReportDialogState extends State<_AbuseReportDialog> {
|
class _AbuseReportDialogState extends State<AbuseReportDialog> {
|
||||||
bool _isBusy = false;
|
bool _isBusy = false;
|
||||||
|
|
||||||
final _resourceController = TextEditingController();
|
final _resourceController = TextEditingController();
|
||||||
final _reasonController = TextEditingController();
|
final _reasonController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (widget.resourceLocation != null) {
|
||||||
|
_resourceController.text = widget.resourceLocation!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
dispose() {
|
dispose() {
|
||||||
_resourceController.dispose();
|
_resourceController.dispose();
|
||||||
@ -144,6 +154,7 @@ class _AbuseReportDialogState extends State<_AbuseReportDialog> {
|
|||||||
const Gap(12),
|
const Gap(12),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _resourceController,
|
controller: _resourceController,
|
||||||
|
readOnly: widget.resourceLocation != null,
|
||||||
maxLength: null,
|
maxLength: null,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: const UnderlineInputBorder(),
|
border: const UnderlineInputBorder(),
|
||||||
|
@ -21,6 +21,9 @@ import 'package:surface/widgets/post/post_item.dart';
|
|||||||
import 'package:surface/widgets/universal_image.dart';
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
|
|
||||||
|
import '../../providers/relationship.dart';
|
||||||
|
import '../abuse_report.dart';
|
||||||
|
|
||||||
class PostPublisherScreen extends StatefulWidget {
|
class PostPublisherScreen extends StatefulWidget {
|
||||||
final String name;
|
final String name;
|
||||||
|
|
||||||
@ -35,18 +38,21 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi
|
|||||||
late final TabController _tabController = TabController(length: 3, vsync: this);
|
late final TabController _tabController = TabController(length: 3, vsync: this);
|
||||||
|
|
||||||
SnPublisher? _publisher;
|
SnPublisher? _publisher;
|
||||||
SnRealm? _realm;
|
|
||||||
SnAccount? _account;
|
SnAccount? _account;
|
||||||
|
SnRelationship? _accountRelationship;
|
||||||
|
SnRealm? _realm;
|
||||||
|
|
||||||
Future<void> _fetchPublisher() async {
|
Future<void> _fetchPublisher() async {
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final ud = context.read<UserDirectoryProvider>();
|
final ud = context.read<UserDirectoryProvider>();
|
||||||
|
final rel = context.read<SnRelationshipProvider>();
|
||||||
final resp = await sn.client.get('/cgi/co/publishers/${widget.name}');
|
final resp = await sn.client.get('/cgi/co/publishers/${widget.name}');
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
_publisher = SnPublisher.fromJson(resp.data);
|
_publisher = SnPublisher.fromJson(resp.data);
|
||||||
_account = await ud.getAccount(_publisher?.accountId);
|
_account = await ud.getAccount(_publisher?.accountId);
|
||||||
if (_publisher?.realmId != null) {
|
_accountRelationship = await rel.getRelationship(_account!.id);
|
||||||
|
if (_publisher?.realmId != null && _publisher!.realmId != 0) {
|
||||||
final resp = await sn.client.get('/cgi/id/realms/${_publisher!.realmId}');
|
final resp = await sn.client.get('/cgi/id/realms/${_publisher!.realmId}');
|
||||||
_realm = SnRealm.fromJson(resp.data);
|
_realm = SnRealm.fromJson(resp.data);
|
||||||
}
|
}
|
||||||
@ -160,11 +166,73 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isWorking = false;
|
||||||
|
|
||||||
|
Future<void> _blockPublisher() async {
|
||||||
|
if (_isWorking) return;
|
||||||
|
|
||||||
|
final confirm = await context.showConfirmDialog(
|
||||||
|
'publisherBlockHint'.tr(args: ['@${_publisher?.name ?? 'unknown'.tr()}']),
|
||||||
|
'publisherBlockHintDescription'.tr(),
|
||||||
|
);
|
||||||
|
if (!confirm) return;
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
setState(() => _isWorking = true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
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'.tr()}']));
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isWorking = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _unblockPublisher() async {
|
||||||
|
if (_isWorking) return;
|
||||||
|
|
||||||
|
setState(() => _isWorking = true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final rel = context.read<SnRelationshipProvider>();
|
||||||
|
await rel.updateRelationship(_account!.id, 1, _accountRelationship?.permNodes ?? {});
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showSnackbar('userUnblocked'.tr(args: ['@${_account?.name ?? 'unknown'.tr()}']));
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isWorking = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _updateFetchType() {
|
void _updateFetchType() {
|
||||||
_posts.clear();
|
_posts.clear();
|
||||||
_fetchPosts();
|
_fetchPosts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showAbuseReportDialog() {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AbuseReportDialog(
|
||||||
|
resourceLocation: 'pub:${_publisher?.name}',
|
||||||
|
),
|
||||||
|
).then((value) {
|
||||||
|
if (value == true && mounted) {
|
||||||
|
_fetchPosts();
|
||||||
|
context.showSnackbar('abuseReportSubmitted'.tr());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -319,8 +387,48 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi
|
|||||||
label: Text('unsubscribe').tr(),
|
label: Text('unsubscribe').tr(),
|
||||||
icon: const Icon(Symbols.remove),
|
icon: const Icon(Symbols.remove),
|
||||||
),
|
),
|
||||||
|
PopupMenuButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
style: ButtonStyle(
|
||||||
|
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
|
||||||
|
),
|
||||||
|
itemBuilder: (BuildContext context) => [
|
||||||
|
PopupMenuItem(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.flag),
|
||||||
|
const Gap(16),
|
||||||
|
Text('report').tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () => _showAbuseReportDialog(),
|
||||||
|
),
|
||||||
|
if (_accountRelationship?.status != 2)
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: _blockPublisher,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.block),
|
||||||
|
const Gap(16),
|
||||||
|
Text('friendBlock').tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: _unblockPublisher,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.block),
|
||||||
|
const Gap(16),
|
||||||
|
Text('friendUnblock').tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
).padding(right: 8),
|
),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
Text(_publisher!.description).padding(horizontal: 8),
|
Text(_publisher!.description).padding(horizontal: 8),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
|
@ -149,7 +149,7 @@ class _RealmDetailHomeWidget extends StatelessWidget {
|
|||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx) {
|
||||||
final ele = publishers![idx];
|
final ele = publishers![idx];
|
||||||
return ListTile(
|
return ListTile(
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
leading: AccountImage(
|
leading: AccountImage(
|
||||||
content: ele.avatar,
|
content: ele.avatar,
|
||||||
fallbackWidget: const Icon(Symbols.group, size: 24),
|
fallbackWidget: const Icon(Symbols.group, size: 24),
|
||||||
|
@ -99,7 +99,7 @@ extension AppPromptExtension on BuildContext {
|
|||||||
|
|
||||||
if (exception.response != null) {
|
if (exception.response != null) {
|
||||||
content = Text(
|
content = Text(
|
||||||
'$preview\n\n(${exception.response?.statusCode}) ${exception.response?.data}',
|
'$preview\n\n${exception.requestOptions.uri.path}\n(${exception.response?.statusCode}) ${exception.response?.data}',
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
content = Text(preview);
|
content = Text(preview);
|
||||||
|
Loading…
Reference in New Issue
Block a user