💄 Better abuse reports

This commit is contained in:
LittleSheep 2025-07-03 21:31:37 +08:00
parent 2c98b348d5
commit 274168d4bc
11 changed files with 242 additions and 79 deletions

View File

@ -1,5 +1,4 @@
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:island/models/user.dart';
part 'abuse_report.freezed.dart'; part 'abuse_report.freezed.dart';
part 'abuse_report.g.dart'; part 'abuse_report.g.dart';
@ -14,7 +13,6 @@ sealed class SnAbuseReport with _$SnAbuseReport {
required DateTime? resolvedAt, required DateTime? resolvedAt,
required String? resolution, required String? resolution,
required String accountId, required String accountId,
required SnAccount? account,
required DateTime createdAt, required DateTime createdAt,
required DateTime updatedAt, required DateTime updatedAt,
required DateTime? deletedAt, required DateTime? deletedAt,

View File

@ -16,7 +16,7 @@ T _$identity<T>(T value) => value;
/// @nodoc /// @nodoc
mixin _$SnAbuseReport { mixin _$SnAbuseReport {
String get id; String get resourceIdentifier; int get type; String get reason; DateTime? get resolvedAt; String? get resolution; String get accountId; SnAccount? get account; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String get id; String get resourceIdentifier; int get type; String get reason; DateTime? get resolvedAt; String? get resolution; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of SnAbuseReport /// Create a copy of SnAbuseReport
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@ -29,16 +29,16 @@ $SnAbuseReportCopyWith<SnAbuseReport> get copyWith => _$SnAbuseReportCopyWithImp
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAbuseReport&&(identical(other.id, id) || other.id == id)&&(identical(other.resourceIdentifier, resourceIdentifier) || other.resourceIdentifier == resourceIdentifier)&&(identical(other.type, type) || other.type == type)&&(identical(other.reason, reason) || other.reason == reason)&&(identical(other.resolvedAt, resolvedAt) || other.resolvedAt == resolvedAt)&&(identical(other.resolution, resolution) || other.resolution == resolution)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAbuseReport&&(identical(other.id, id) || other.id == id)&&(identical(other.resourceIdentifier, resourceIdentifier) || other.resourceIdentifier == resourceIdentifier)&&(identical(other.type, type) || other.type == type)&&(identical(other.reason, reason) || other.reason == reason)&&(identical(other.resolvedAt, resolvedAt) || other.resolvedAt == resolvedAt)&&(identical(other.resolution, resolution) || other.resolution == resolution)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType,id,resourceIdentifier,type,reason,resolvedAt,resolution,accountId,account,createdAt,updatedAt,deletedAt); int get hashCode => Object.hash(runtimeType,id,resourceIdentifier,type,reason,resolvedAt,resolution,accountId,createdAt,updatedAt,deletedAt);
@override @override
String toString() { String toString() {
return 'SnAbuseReport(id: $id, resourceIdentifier: $resourceIdentifier, type: $type, reason: $reason, resolvedAt: $resolvedAt, resolution: $resolution, accountId: $accountId, account: $account, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; return 'SnAbuseReport(id: $id, resourceIdentifier: $resourceIdentifier, type: $type, reason: $reason, resolvedAt: $resolvedAt, resolution: $resolution, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
} }
@ -49,11 +49,11 @@ abstract mixin class $SnAbuseReportCopyWith<$Res> {
factory $SnAbuseReportCopyWith(SnAbuseReport value, $Res Function(SnAbuseReport) _then) = _$SnAbuseReportCopyWithImpl; factory $SnAbuseReportCopyWith(SnAbuseReport value, $Res Function(SnAbuseReport) _then) = _$SnAbuseReportCopyWithImpl;
@useResult @useResult
$Res call({ $Res call({
String id, String resourceIdentifier, int type, String reason, DateTime? resolvedAt, String? resolution, String accountId, SnAccount? account, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt String id, String resourceIdentifier, int type, String reason, DateTime? resolvedAt, String? resolution, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
}); });
$SnAccountCopyWith<$Res>? get account;
} }
/// @nodoc /// @nodoc
@ -66,7 +66,7 @@ class _$SnAbuseReportCopyWithImpl<$Res>
/// Create a copy of SnAbuseReport /// Create a copy of SnAbuseReport
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? resourceIdentifier = null,Object? type = null,Object? reason = null,Object? resolvedAt = freezed,Object? resolution = freezed,Object? accountId = null,Object? account = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? resourceIdentifier = null,Object? type = null,Object? reason = null,Object? resolvedAt = freezed,Object? resolution = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_self.copyWith( return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,resourceIdentifier: null == resourceIdentifier ? _self.resourceIdentifier : resourceIdentifier // ignore: cast_nullable_to_non_nullable as String,resourceIdentifier: null == resourceIdentifier ? _self.resourceIdentifier : resourceIdentifier // ignore: cast_nullable_to_non_nullable
@ -75,26 +75,13 @@ as int,reason: null == reason ? _self.reason : reason // ignore: cast_nullable_t
as String,resolvedAt: freezed == resolvedAt ? _self.resolvedAt : resolvedAt // ignore: cast_nullable_to_non_nullable as String,resolvedAt: freezed == resolvedAt ? _self.resolvedAt : resolvedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,resolution: freezed == resolution ? _self.resolution : resolution // ignore: cast_nullable_to_non_nullable as DateTime?,resolution: freezed == resolution ? _self.resolution : resolution // ignore: cast_nullable_to_non_nullable
as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as SnAccount?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?, as DateTime?,
)); ));
} }
/// Create a copy of SnAbuseReport
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAccountCopyWith<$Res>? get account {
if (_self.account == null) {
return null;
}
return $SnAccountCopyWith<$Res>(_self.account!, (value) {
return _then(_self.copyWith(account: value));
});
}
} }
@ -102,7 +89,7 @@ $SnAccountCopyWith<$Res>? get account {
@JsonSerializable() @JsonSerializable()
class _SnAbuseReport implements SnAbuseReport { class _SnAbuseReport implements SnAbuseReport {
const _SnAbuseReport({required this.id, required this.resourceIdentifier, required this.type, required this.reason, required this.resolvedAt, required this.resolution, required this.accountId, required this.account, required this.createdAt, required this.updatedAt, required this.deletedAt}); const _SnAbuseReport({required this.id, required this.resourceIdentifier, required this.type, required this.reason, required this.resolvedAt, required this.resolution, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt});
factory _SnAbuseReport.fromJson(Map<String, dynamic> json) => _$SnAbuseReportFromJson(json); factory _SnAbuseReport.fromJson(Map<String, dynamic> json) => _$SnAbuseReportFromJson(json);
@override final String id; @override final String id;
@ -112,7 +99,6 @@ class _SnAbuseReport implements SnAbuseReport {
@override final DateTime? resolvedAt; @override final DateTime? resolvedAt;
@override final String? resolution; @override final String? resolution;
@override final String accountId; @override final String accountId;
@override final SnAccount? account;
@override final DateTime createdAt; @override final DateTime createdAt;
@override final DateTime updatedAt; @override final DateTime updatedAt;
@override final DateTime? deletedAt; @override final DateTime? deletedAt;
@ -130,16 +116,16 @@ Map<String, dynamic> toJson() {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAbuseReport&&(identical(other.id, id) || other.id == id)&&(identical(other.resourceIdentifier, resourceIdentifier) || other.resourceIdentifier == resourceIdentifier)&&(identical(other.type, type) || other.type == type)&&(identical(other.reason, reason) || other.reason == reason)&&(identical(other.resolvedAt, resolvedAt) || other.resolvedAt == resolvedAt)&&(identical(other.resolution, resolution) || other.resolution == resolution)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAbuseReport&&(identical(other.id, id) || other.id == id)&&(identical(other.resourceIdentifier, resourceIdentifier) || other.resourceIdentifier == resourceIdentifier)&&(identical(other.type, type) || other.type == type)&&(identical(other.reason, reason) || other.reason == reason)&&(identical(other.resolvedAt, resolvedAt) || other.resolvedAt == resolvedAt)&&(identical(other.resolution, resolution) || other.resolution == resolution)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType,id,resourceIdentifier,type,reason,resolvedAt,resolution,accountId,account,createdAt,updatedAt,deletedAt); int get hashCode => Object.hash(runtimeType,id,resourceIdentifier,type,reason,resolvedAt,resolution,accountId,createdAt,updatedAt,deletedAt);
@override @override
String toString() { String toString() {
return 'SnAbuseReport(id: $id, resourceIdentifier: $resourceIdentifier, type: $type, reason: $reason, resolvedAt: $resolvedAt, resolution: $resolution, accountId: $accountId, account: $account, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; return 'SnAbuseReport(id: $id, resourceIdentifier: $resourceIdentifier, type: $type, reason: $reason, resolvedAt: $resolvedAt, resolution: $resolution, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
} }
@ -150,11 +136,11 @@ abstract mixin class _$SnAbuseReportCopyWith<$Res> implements $SnAbuseReportCopy
factory _$SnAbuseReportCopyWith(_SnAbuseReport value, $Res Function(_SnAbuseReport) _then) = __$SnAbuseReportCopyWithImpl; factory _$SnAbuseReportCopyWith(_SnAbuseReport value, $Res Function(_SnAbuseReport) _then) = __$SnAbuseReportCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $Res call({
String id, String resourceIdentifier, int type, String reason, DateTime? resolvedAt, String? resolution, String accountId, SnAccount? account, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt String id, String resourceIdentifier, int type, String reason, DateTime? resolvedAt, String? resolution, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
}); });
@override $SnAccountCopyWith<$Res>? get account;
} }
/// @nodoc /// @nodoc
@ -167,7 +153,7 @@ class __$SnAbuseReportCopyWithImpl<$Res>
/// Create a copy of SnAbuseReport /// Create a copy of SnAbuseReport
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? resourceIdentifier = null,Object? type = null,Object? reason = null,Object? resolvedAt = freezed,Object? resolution = freezed,Object? accountId = null,Object? account = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? resourceIdentifier = null,Object? type = null,Object? reason = null,Object? resolvedAt = freezed,Object? resolution = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_SnAbuseReport( return _then(_SnAbuseReport(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,resourceIdentifier: null == resourceIdentifier ? _self.resourceIdentifier : resourceIdentifier // ignore: cast_nullable_to_non_nullable as String,resourceIdentifier: null == resourceIdentifier ? _self.resourceIdentifier : resourceIdentifier // ignore: cast_nullable_to_non_nullable
@ -176,27 +162,14 @@ as int,reason: null == reason ? _self.reason : reason // ignore: cast_nullable_t
as String,resolvedAt: freezed == resolvedAt ? _self.resolvedAt : resolvedAt // ignore: cast_nullable_to_non_nullable as String,resolvedAt: freezed == resolvedAt ? _self.resolvedAt : resolvedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,resolution: freezed == resolution ? _self.resolution : resolution // ignore: cast_nullable_to_non_nullable as DateTime?,resolution: freezed == resolution ? _self.resolution : resolution // ignore: cast_nullable_to_non_nullable
as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as SnAccount?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?, as DateTime?,
)); ));
} }
/// Create a copy of SnAbuseReport
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAccountCopyWith<$Res>? get account {
if (_self.account == null) {
return null;
}
return $SnAccountCopyWith<$Res>(_self.account!, (value) {
return _then(_self.copyWith(account: value));
});
}
} }
// dart format on // dart format on

View File

@ -18,10 +18,6 @@ _SnAbuseReport _$SnAbuseReportFromJson(Map<String, dynamic> json) =>
: DateTime.parse(json['resolved_at'] as String), : DateTime.parse(json['resolved_at'] as String),
resolution: json['resolution'] as String?, resolution: json['resolution'] as String?,
accountId: json['account_id'] as String, accountId: json['account_id'] as String,
account:
json['account'] == null
? null
: SnAccount.fromJson(json['account'] as Map<String, dynamic>),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: deletedAt:
@ -39,7 +35,6 @@ Map<String, dynamic> _$SnAbuseReportToJson(_SnAbuseReport instance) =>
'resolved_at': instance.resolvedAt?.toIso8601String(), 'resolved_at': instance.resolvedAt?.toIso8601String(),
'resolution': instance.resolution, 'resolution': instance.resolution,
'account_id': instance.accountId, 'account_id': instance.accountId,
'account': instance.account?.toJson(),
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(), 'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(), 'deleted_at': instance.deletedAt?.toIso8601String(),

View File

@ -0,0 +1,38 @@
enum AbuseReportType {
copyright(0),
harassment(1),
impersonation(2),
offensiveContent(3),
spam(4),
privacyViolation(5),
illegalContent(6),
other(7);
const AbuseReportType(this.value);
final int value;
static AbuseReportType fromValue(int value) {
return values.firstWhere((e) => e.value == value);
}
String get displayName {
switch (this) {
case AbuseReportType.copyright:
return 'Copyright';
case AbuseReportType.harassment:
return 'Harassment';
case AbuseReportType.impersonation:
return 'Impersonation';
case AbuseReportType.offensiveContent:
return 'Offensive Content';
case AbuseReportType.spam:
return 'Spam';
case AbuseReportType.privacyViolation:
return 'Privacy Violation';
case AbuseReportType.illegalContent:
return 'Illegal Content';
case AbuseReportType.other:
return 'Other';
}
}
}

View File

@ -7,6 +7,7 @@ import 'package:island/pods/network.dart';
import 'package:island/services/notify.dart'; import 'package:island/services/notify.dart';
import 'package:island/services/udid.native.dart'; import 'package:island/services/udid.native.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
@ -90,7 +91,7 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
return Scaffold( return AppScaffold(
appBar: AppBar(title: Text('about'.tr()), elevation: 0), appBar: AppBar(title: Text('about'.tr()), elevation: 0),
body: body:
_isLoading _isLoading

View File

@ -6,6 +6,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/webfeed.dart'; import 'package:island/models/webfeed.dart';
import 'package:island/pods/webfeed.dart'; import 'package:island/pods/webfeed.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
@ -183,7 +184,7 @@ class WebFeedEditScreen extends HookConsumerWidget {
} }
}, [pubName, feedId, ref, context]); }, [pubName, feedId, ref, context]);
return Scaffold( return AppScaffold(
appBar: AppBar( appBar: AppBar(
title: Text(hasFeedId ? 'Edit Web Feed' : 'New Web Feed'), title: Text(hasFeedId ? 'Edit Web Feed' : 'New Web Feed'),
actions: [ actions: [

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/webfeed.dart'; import 'package:island/models/webfeed.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/web_article_card.dart'; import 'package:island/widgets/web_article_card.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
@ -124,7 +125,7 @@ class ArticlesScreen extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return Scaffold( return AppScaffold(
appBar: AppBar(title: Text(title ?? 'Articles')), appBar: AppBar(title: Text(title ?? 'Articles')),
body: Center( body: Center(
child: ConstrainedBox( child: ConstrainedBox(

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/post.dart'; import 'package:island/models/post.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/post/post_item.dart'; import 'package:island/widgets/post/post_item.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
@ -107,7 +108,7 @@ class _PostSearchScreenState extends ConsumerState<PostSearchScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return AppScaffold(
appBar: AppBar( appBar: AppBar(
title: TextField( title: TextField(
controller: _searchController, controller: _searchController,

View File

@ -1,7 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/abuse_report.dart'; import 'package:island/models/abuse_report.dart';
import 'package:island/models/abuse_report_type.dart';
import 'package:island/services/abuse_report_service.dart'; import 'package:island/services/abuse_report_service.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:styled_widget/styled_widget.dart';
class AbuseReportDetailScreen extends ConsumerStatefulWidget { class AbuseReportDetailScreen extends ConsumerStatefulWidget {
final String reportId; final String reportId;
@ -20,16 +23,15 @@ class _AbuseReportDetailScreenState
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_reportFuture = _reportFuture = ref
ref.read(abuseReportServiceProvider).getReport(widget.reportId); .read(abuseReportServiceProvider)
.getReport(widget.reportId);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return AppScaffold(
appBar: AppBar( appBar: AppBar(title: const Text('Abuse Report Details')),
title: const Text('Abuse Report Details'),
),
body: FutureBuilder<SnAbuseReport>( body: FutureBuilder<SnAbuseReport>(
future: _reportFuture, future: _reportFuture,
builder: (context, snapshot) { builder: (context, snapshot) {
@ -44,15 +46,39 @@ class _AbuseReportDetailScreenState
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Report ID: ${report.id}'), _buildDetailRow(context, 'Report ID', report.id),
Text('Resource Identifier: ${report.resourceIdentifier}'), _buildDetailRow(
Text('Type: ${report.type}'), context,
Text('Reason: ${report.reason}'), 'Resource Identifier',
Text('Resolved At: ${report.resolvedAt}'), report.resourceIdentifier,
Text('Resolution: ${report.resolution}'), ),
Text('Account ID: ${report.accountId}'), _buildDetailRow(
Text('Created At: ${report.createdAt}'), context,
Text('Updated At: ${report.updatedAt}'), 'Type',
AbuseReportType.fromValue(report.type).displayName,
),
_buildDetailRow(context, 'Reason', report.reason),
_buildDetailRow(
context,
'Resolved At',
report.resolvedAt?.toString() ?? 'N/A',
),
_buildDetailRow(
context,
'Resolution',
report.resolution ?? 'N/A',
),
_buildDetailRow(context, 'Account ID', report.accountId),
_buildDetailRow(
context,
'Created At',
report.createdAt.toString(),
),
_buildDetailRow(
context,
'Updated At',
report.updatedAt.toString(),
),
], ],
), ),
); );
@ -63,4 +89,17 @@ class _AbuseReportDetailScreenState
), ),
); );
} }
Widget _buildDetailRow(BuildContext context, String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: Theme.of(context).textTheme.titleMedium).bold(),
Text(value, style: Theme.of(context).textTheme.bodyLarge),
],
),
);
}
} }

View File

@ -1,8 +1,13 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/abuse_report.dart'; import 'package:island/models/abuse_report.dart';
import 'package:island/models/abuse_report_type.dart';
import 'package:island/services/abuse_report_service.dart'; import 'package:island/services/abuse_report_service.dart';
import 'package:island/services/time.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/safety/abuse_report_helper.dart';
class AbuseReportListScreen extends ConsumerStatefulWidget { class AbuseReportListScreen extends ConsumerStatefulWidget {
const AbuseReportListScreen({super.key}); const AbuseReportListScreen({super.key});
@ -23,9 +28,13 @@ class _AbuseReportListScreenState extends ConsumerState<AbuseReportListScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return AppScaffold(
appBar: AppBar( appBar: AppBar(title: Text('abuseReports').tr()),
title: const Text('My Abuse Reports'), floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
showAbuseReportSheet(context, resourceIdentifier: 'unidentified');
},
), ),
body: FutureBuilder<List<SnAbuseReport>>( body: FutureBuilder<List<SnAbuseReport>>(
future: _reportsFuture, future: _reportsFuture,
@ -37,15 +46,100 @@ class _AbuseReportListScreenState extends ConsumerState<AbuseReportListScreen> {
} else if (snapshot.hasData) { } else if (snapshot.hasData) {
final reports = snapshot.data!; final reports = snapshot.data!;
return ListView.builder( return ListView.builder(
padding: EdgeInsets.zero,
itemCount: reports.length, itemCount: reports.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final report = reports[index]; final report = reports[index];
return ListTile( return Card(
title: Text(report.reason), elevation: 2,
subtitle: Text(report.id), margin: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
child: InkWell(
onTap: () { onTap: () {
context.push('/safety/reports/me/${report.id}'); context.push('/safety/reports/me/${report.id}');
}, },
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
report.reason,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'ID',
style: Theme.of(context).textTheme.bodySmall,
),
Text(
report.id,
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Type',
style: Theme.of(context).textTheme.bodySmall,
),
Text(
AbuseReportType.fromValue(
report.type,
).displayName,
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Created at',
style: Theme.of(context).textTheme.bodySmall,
),
Text(
'${report.createdAt.formatRelative(context)} · ${report.createdAt.formatSystem()}',
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Status',
style: Theme.of(context).textTheme.bodySmall,
),
Text(
report.resolvedAt != null
? 'Resolved'
: 'Unresolved',
style: Theme.of(
context,
).textTheme.bodyMedium?.copyWith(
color:
report.resolvedAt != null
? Colors.green
: Colors.orange,
),
),
],
),
],
),
),
),
); );
}, },
); );

View File

@ -0,0 +1,22 @@
String getAbuseReportTypeString(int type) {
switch (type) {
case 0:
return 'Copyright';
case 1:
return 'Harassment';
case 2:
return 'Impersonation';
case 3:
return 'Offensive Content';
case 4:
return 'Spam';
case 5:
return 'Privacy Violation';
case 6:
return 'Illegal Content';
case 7:
return 'Other';
default:
return 'Unknown';
}
}