显示证件照 pdf待测试
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class AttachmentViewerController extends GetxController {
|
||||
late final String url;
|
||||
late final String fileType;
|
||||
|
||||
final RxBool isLoading = true.obs;
|
||||
// This is the correct state variable: it stores the local file path.
|
||||
final RxString localFilePath = ''.obs;
|
||||
final RxString loadingText = '加载中...'.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
url = Get.arguments['url'] ?? '';
|
||||
if (url.isEmpty) {
|
||||
showErrorToast('无效的附件链接');
|
||||
Get.back();
|
||||
return;
|
||||
}
|
||||
|
||||
if (url.toLowerCase().endsWith('.pdf')) {
|
||||
fileType = 'pdf';
|
||||
// This is the correct logic: download the file first.
|
||||
_downloadPdf();
|
||||
} else {
|
||||
fileType = 'image';
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Downloads the PDF file to a temporary directory and stores its path.
|
||||
Future<void> _downloadPdf() async {
|
||||
try {
|
||||
final dio = Dio();
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
// Use a unique name to avoid caching issues
|
||||
final fileName = '${DateTime.now().millisecondsSinceEpoch}_${url.split('/').last}';
|
||||
final savePath = '${tempDir.path}/$fileName';
|
||||
|
||||
await dio.download(
|
||||
url,
|
||||
savePath,
|
||||
onReceiveProgress: (received, total) {
|
||||
if (total != -1) {
|
||||
loadingText.value = '下载中...${(received / total * 100).toStringAsFixed(0)}%';
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// On success, update the local file path
|
||||
localFilePath.value = savePath;
|
||||
|
||||
} catch (e) {
|
||||
showErrorToast('PDF文件加载失败,请检查网络或文件链接');
|
||||
print('PDF Download Error: $e');
|
||||
Get.back();
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_pdfview/flutter_pdfview.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
|
||||
import 'attachment_viewer_controller.dart';
|
||||
|
||||
class AttachmentViewerPage extends GetView<AttachmentViewerController> {
|
||||
const AttachmentViewerPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Get.put(AttachmentViewerController());
|
||||
final fileName = controller.url.split('/').last;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
fileName,
|
||||
style: const TextStyle(fontSize: 16),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: Obx(() {
|
||||
if (controller.isLoading.value) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: 20),
|
||||
Text(controller.loadingText.value),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (controller.fileType == 'pdf') {
|
||||
if (controller.localFilePath.isNotEmpty) {
|
||||
return PDFView(
|
||||
key: ValueKey(controller.localFilePath.value),
|
||||
filePath: controller.localFilePath.value,
|
||||
enableSwipe: true,
|
||||
swipeHorizontal: false,
|
||||
autoSpacing: false,
|
||||
pageFling: true,
|
||||
onRender: (pages) {
|
||||
print("PDF rendered with $pages pages.");
|
||||
},
|
||||
onError: (error) {
|
||||
print(error.toString());
|
||||
showErrorToast('渲染PDF时出错');
|
||||
},
|
||||
onPageError: (page, error) {
|
||||
print('$page: ${error.toString()}');
|
||||
showErrorToast('渲染第$page页时出错');
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return const Center(child: Text('无法加载PDF文件'));
|
||||
}
|
||||
} else {
|
||||
return PhotoView(
|
||||
imageProvider: NetworkImage(controller.url),
|
||||
minScale: PhotoViewComputedScale.contained * 0.8,
|
||||
maxScale: PhotoViewComputedScale.covered * 2.0,
|
||||
loadingBuilder: (context, event) => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
backgroundDecoration: const BoxDecoration(
|
||||
color: Colors.black,
|
||||
),
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||
|
||||
import 'attachment_viewer_page.dart';
|
||||
|
||||
class CertificateViewerController extends GetxController {
|
||||
late final String title;
|
||||
late final List<String> attachments;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
// 从 Get.to 的 arguments 中获取标题和附件列表
|
||||
title = Get.arguments['title'] ?? '证件详情';
|
||||
attachments = List<String>.from(Get.arguments['attachments'] ?? []);
|
||||
}
|
||||
|
||||
/// 导航到通用的附件查看器页面
|
||||
void openAttachment(String url) {
|
||||
if (url.isEmpty) {
|
||||
showErrorToast('附件链接无效');
|
||||
return;
|
||||
}
|
||||
|
||||
Get.to(
|
||||
() => const AttachmentViewerPage(),
|
||||
arguments: {
|
||||
'url': url,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 检查 URL 是否为 PDF,以便在视图中显示不同的图标
|
||||
bool isPdf(String url) {
|
||||
return url.toLowerCase().endsWith('.pdf');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import 'certificate_viewer_controller.dart';
|
||||
|
||||
class CertificateViewerPage extends GetView<CertificateViewerController> {
|
||||
const CertificateViewerPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Get.put(CertificateViewerController());
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(controller.title),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
|
||||
itemCount: controller.attachments.length,
|
||||
itemBuilder: (context, index) {
|
||||
final url = controller.attachments[index];
|
||||
// 从 URL 中提取文件名用于显示
|
||||
final fileName = url.split('/').last;
|
||||
|
||||
return Card(
|
||||
elevation: 2,
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
child: ListTile(
|
||||
leading: Icon(
|
||||
controller.isPdf(url)
|
||||
? Icons.picture_as_pdf_rounded // PDF 图标
|
||||
: Icons.image_rounded, // 图片图标
|
||||
color: controller.isPdf(url) ? Colors.red.shade700 : Colors.blue.shade700,
|
||||
size: 32,
|
||||
),
|
||||
title: Text(
|
||||
fileName,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
trailing: const Icon(Icons.arrow_forward_ios_rounded, size: 16, color: Colors.grey),
|
||||
onTap: () => controller.openAttachment(url),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,72 +1,85 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||
import 'package:ln_jq_app/common/model/base_model.dart';
|
||||
import 'package:ln_jq_app/common/model/vehicle_info.dart';
|
||||
import 'package:ln_jq_app/storage_service.dart';
|
||||
|
||||
import 'certificate_viewer_page.dart';
|
||||
|
||||
class CarInfoController extends GetxController with BaseControllerMixin {
|
||||
@override
|
||||
String get builderId => 'car_info';
|
||||
|
||||
CarInfoController();
|
||||
|
||||
// --- 车辆基本信息 ---
|
||||
String plateNumber = "";
|
||||
String vin = "未知";
|
||||
String modelName = "未知";
|
||||
String brandName = "未知";
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
getUserBindCarInfo();
|
||||
super.onInit();
|
||||
}
|
||||
// --- 证件附件列表 ---
|
||||
final RxList<String> drivingAttachments = <String>[].obs;
|
||||
final RxList<String> operationAttachments = <String>[].obs;
|
||||
final RxList<String> hydrogenationAttachments = <String>[].obs;
|
||||
final RxList<String> registerAttachments = <String>[].obs;
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
super.onClose();
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
getUserBindCarInfo();
|
||||
}
|
||||
|
||||
void getUserBindCarInfo() async {
|
||||
if (StorageService.to.hasVehicleInfo) {
|
||||
VehicleInfo? bean = StorageService.to.vehicleInfo;
|
||||
if (bean == null) {
|
||||
return;
|
||||
}
|
||||
if (bean == null) return;
|
||||
|
||||
// 填充基本信息
|
||||
plateNumber = bean.plateNumber;
|
||||
vin = bean.vin;
|
||||
modelName = bean.modelName;
|
||||
brandName = bean.brandName;
|
||||
|
||||
// 获取证件信息
|
||||
final response = await HttpService.to.get(
|
||||
'appointment/vehicle/getPicInfoByVin?vin=${vin}',
|
||||
'appointment/vehicle/getPicInfoByVin?vin=$vin',
|
||||
);
|
||||
if (response != null) {
|
||||
|
||||
if (response != null && response.data != null) {
|
||||
final result = BaseModel.fromJson(response.data);
|
||||
if (result.code == 0 && result.data != null) {
|
||||
result.data;
|
||||
/*{
|
||||
"plateNumber": "浙F08860F",
|
||||
"vin": "LA9GG64L9NBAF4174",
|
||||
"drivingAttachment": [
|
||||
"https://lnh2e.com/api/lingniu-export-v1/v1/resource/file/2024/05/202405162107520002.pdf",
|
||||
"https://lnh2e.com/api/lingniu-export-v1/v1/resource/file/2024/05/202405162107530002.jpg",
|
||||
"https://lnh2e.com/api/lingniu-export-v1/v1/resource/file/2024/12/202412101043260001.jpg"
|
||||
],
|
||||
"operationAttachment": [
|
||||
"https://lnh2e.com/api/lingniu-export-v1/v1/resource/file/2024/05/202405162107530003.pdf",
|
||||
"https://lnh2e.com/api/lingniu-export-v1/v1/resource/file/2024/12/202412101043460001.jpg",
|
||||
"https://lnh2e.com/api/lingniu-export-v1/v1/resource/file/2024/12/202412181551190001.pdf"
|
||||
],
|
||||
"hydrogenationAttachment": [
|
||||
"https://lnh2e.com/api/lingniu-export-v1/v1/resource/file/2024/05/202405162107570001.pdf"
|
||||
],
|
||||
"registerAttachment": [
|
||||
"https://lnh2e.com/api/lingniu-export-v1/v1/resource/file/2024/05/202405162108010001.pdf"
|
||||
]
|
||||
}*/
|
||||
final data = result.data as Map<String, dynamic>;
|
||||
|
||||
List<String> parseAttachments(dynamic list) {
|
||||
if (list is List) {
|
||||
// 确保列表中的每一项都是字符串
|
||||
return List<String>.from(list.map((item) => item.toString()));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
// 将解析出的 URL 列表赋值给对应的 RxList
|
||||
drivingAttachments.assignAll(parseAttachments(data['drivingAttachment']));
|
||||
operationAttachments.assignAll(parseAttachments(data['operationAttachment']));
|
||||
hydrogenationAttachments.assignAll(parseAttachments(data['hydrogenationAttachment']));
|
||||
registerAttachments.assignAll(parseAttachments(data['registerAttachment']));
|
||||
}
|
||||
}
|
||||
|
||||
updateUi();
|
||||
}
|
||||
}
|
||||
|
||||
/// 跳转到证件查看页面
|
||||
void navigateToCertificateViewer(String title, List<String> attachments) {
|
||||
if (attachments.isEmpty) {
|
||||
showToast('暂无相关证件附件');
|
||||
return;
|
||||
}
|
||||
Get.to(
|
||||
() => const CertificateViewerPage(),
|
||||
arguments: {
|
||||
'title': title,
|
||||
'attachments': attachments,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||
import 'package:ln_jq_app/common/styles/theme.dart';
|
||||
import 'package:ln_jq_app/pages/qr_code/view.dart';
|
||||
import 'package:ln_jq_app/storage_service.dart';
|
||||
// import 'package:getx_scaffold/getx_scaffold.dart'; // 如果不使用其中的扩展,可以注释掉
|
||||
|
||||
import 'controller.dart';
|
||||
|
||||
@@ -17,10 +15,8 @@ class CarInfoPage extends GetView<CarInfoController> {
|
||||
init: CarInfoController(),
|
||||
id: 'car_info',
|
||||
builder: (_) {
|
||||
// 将所有 UI 构建逻辑都放在这里
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey[100],
|
||||
// 我们不再使用单独的 AppBar,而是通过自定义的 Container 来实现类似效果
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
@@ -43,7 +39,7 @@ class CarInfoPage extends GetView<CarInfoController> {
|
||||
);
|
||||
}
|
||||
|
||||
/// 1. 构建顶部的司机信息卡片(包含蓝色背景)
|
||||
/// 构建顶部的司机信息卡片
|
||||
Widget _buildDriverInfoCard() {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
@@ -65,13 +61,13 @@ class CarInfoPage extends GetView<CarInfoController> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
Text(
|
||||
"${StorageService.to.name}",
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
"${StorageService.to.phone}",
|
||||
Text(
|
||||
"${StorageService.to.phone}",
|
||||
style: TextStyle(color: Colors.grey, fontSize: 11),
|
||||
),
|
||||
],
|
||||
@@ -137,7 +133,7 @@ class CarInfoPage extends GetView<CarInfoController> {
|
||||
);
|
||||
}
|
||||
|
||||
/// 2. 构建车辆绑定信息卡片
|
||||
/// 构建车辆绑定信息卡片
|
||||
Widget _buildCarBindingCard() {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
@@ -176,46 +172,46 @@ class CarInfoPage extends GetView<CarInfoController> {
|
||||
const SizedBox(width: 8),
|
||||
isButton
|
||||
? GestureDetector(
|
||||
onTap: () async {
|
||||
//判断是否绑定成功
|
||||
var scanResult = await Get.to(() => const QrCodePage());
|
||||
if (scanResult == true) {
|
||||
controller.getUserBindCarInfo();
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
margin: EdgeInsetsGeometry.only(left: 10.w),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.blue.shade300, width: 1),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: Colors.blue.withOpacity(0.05),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min, // Keep the row compact
|
||||
children: [
|
||||
Icon(
|
||||
StorageService.to.hasVehicleInfo ? Icons.repeat : Icons.search,
|
||||
size: 13,
|
||||
color: Colors.blue,
|
||||
),
|
||||
const SizedBox(width: 3),
|
||||
Text(
|
||||
StorageService.to.hasVehicleInfo ? "换车牌" : value,
|
||||
style: const TextStyle(
|
||||
color: Colors.blue,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
onTap: () async {
|
||||
//判断是否绑定成功
|
||||
var scanResult = await Get.to(() => const QrCodePage());
|
||||
if (scanResult == true) {
|
||||
controller.getUserBindCarInfo();
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
margin: EdgeInsetsGeometry.only(left: 10.w),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.blue.shade300, width: 1),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: Colors.blue.withOpacity(0.05),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min, // Keep the row compact
|
||||
children: [
|
||||
Icon(
|
||||
StorageService.to.hasVehicleInfo ? Icons.repeat : Icons.search,
|
||||
size: 13,
|
||||
color: Colors.blue,
|
||||
),
|
||||
const SizedBox(width: 3),
|
||||
Text(
|
||||
StorageService.to.hasVehicleInfo ? "换车牌" : value,
|
||||
style: const TextStyle(
|
||||
color: Colors.blue,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
: Text(
|
||||
value,
|
||||
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
|
||||
),
|
||||
value,
|
||||
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -226,30 +222,44 @@ class CarInfoPage extends GetView<CarInfoController> {
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'车辆证件',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
_buildCertificateRow(
|
||||
icon: Icons.credit_card_rounded,
|
||||
title: '行驶证',
|
||||
attachments: controller.drivingAttachments,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildCertificateRow(Icons.local_gas_station, '驾驶证', '车辆驾驶证相关证件'),
|
||||
const Divider(),
|
||||
_buildCertificateRow(Icons.article_outlined, '营运证', '道路运输经营许可证'),
|
||||
_buildCertificateRow(
|
||||
icon: Icons.article_rounded,
|
||||
title: '运营证',
|
||||
attachments: controller.operationAttachments,
|
||||
),
|
||||
const Divider(),
|
||||
_buildCertificateRow(Icons.person_pin_outlined, '加氢证', '车辆加氢许可证'),
|
||||
_buildCertificateRow(
|
||||
icon: Icons.propane_tank_rounded,
|
||||
title: '氢瓶证',
|
||||
attachments: controller.hydrogenationAttachments,
|
||||
),
|
||||
const Divider(),
|
||||
_buildCertificateRow(Icons.credit_card_outlined, '行驶证', '车辆行驶证相关证件'),
|
||||
_buildCertificateRow(
|
||||
icon: Icons.app_registration_rounded,
|
||||
title: '登记证',
|
||||
attachments: controller.registerAttachments,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 车辆证件列表项
|
||||
Widget _buildCertificateRow(IconData icon, String title, String subtitle) {
|
||||
/// 证件展示
|
||||
Widget _buildCertificateRow({
|
||||
required IconData icon,
|
||||
required String title,
|
||||
required List<String> attachments,
|
||||
}) {
|
||||
return ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: CircleAvatar(
|
||||
@@ -257,24 +267,30 @@ class CarInfoPage extends GetView<CarInfoController> {
|
||||
backgroundColor: Colors.blue.withOpacity(0.1),
|
||||
child: Icon(icon, color: Colors.blue, size: 28),
|
||||
),
|
||||
title: Text(title, style: const TextStyle(fontWeight: FontWeight.bold,fontSize: 14)),
|
||||
subtitle: Text(subtitle, style: const TextStyle(color: Colors.grey, fontSize: 12)),
|
||||
title: Text(
|
||||
title,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
|
||||
),
|
||||
// 使用 Obx 响应式地显示附件数量
|
||||
subtitle: Obx(
|
||||
() => Text(
|
||||
'共 ${attachments.length} 个附件',
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 12),
|
||||
),
|
||||
),
|
||||
trailing: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[200],
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
// 图片中的图标是“查看”的意思,这里用一个类似的图标代替
|
||||
child: const Icon(Icons.find_in_page_outlined, color: AppTheme.themeColor),
|
||||
),
|
||||
onTap: () {
|
||||
// TODO: 查看证件详情逻辑
|
||||
},
|
||||
// 更新 onTap 逻辑
|
||||
onTap: () => controller.navigateToCertificateViewer(title, attachments),
|
||||
);
|
||||
}
|
||||
|
||||
/// 4. 构建提示信息卡片
|
||||
Widget _buildTipsCard() {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
|
||||
Reference in New Issue
Block a user