✨ Compress video
This commit is contained in:
parent
2851780dda
commit
91c85e8a58
@ -291,6 +291,7 @@
|
|||||||
"attachmentInsertLink": "Insert Link",
|
"attachmentInsertLink": "Insert Link",
|
||||||
"attachmentSetAsPostThumbnail": "Set as post thumbnail",
|
"attachmentSetAsPostThumbnail": "Set as post thumbnail",
|
||||||
"attachmentUnsetAsPostThumbnail": "Unset as post thumbnail",
|
"attachmentUnsetAsPostThumbnail": "Unset as post thumbnail",
|
||||||
|
"attachmentCompressVideo": "Re-encode video",
|
||||||
"attachmentSetThumbnail": "Set thumbnail",
|
"attachmentSetThumbnail": "Set thumbnail",
|
||||||
"attachmentCopyRandomId": "Copy RID",
|
"attachmentCopyRandomId": "Copy RID",
|
||||||
"attachmentUpload": "Upload",
|
"attachmentUpload": "Upload",
|
||||||
@ -513,5 +514,11 @@
|
|||||||
"postCategoryLiterature": "Literature",
|
"postCategoryLiterature": "Literature",
|
||||||
"postCategoryFunny": "Funny",
|
"postCategoryFunny": "Funny",
|
||||||
"postCategoryUncategorized": "Uncategorized",
|
"postCategoryUncategorized": "Uncategorized",
|
||||||
"waitingForUpload": "Waiting for upload"
|
"waitingForUpload": "Waiting for upload",
|
||||||
|
"attachmentCompressQuality": "Compress quality",
|
||||||
|
"attachmentCompressQualityHighest": "Highest",
|
||||||
|
"attachmentCompressQualityDefault": "Default",
|
||||||
|
"attachmentCompressQualityMedium": "Medium",
|
||||||
|
"attachmentCompressQualityLow": "Low",
|
||||||
|
"attachmentCompressQualityHint": "Solar Network doesn't prevent you from uploading large files, high resolution, high bitrate videos. But for your network conditions, we suggest you choose a suitable compression quality."
|
||||||
}
|
}
|
||||||
|
@ -289,6 +289,7 @@
|
|||||||
"attachmentInsertLink": "插入连接",
|
"attachmentInsertLink": "插入连接",
|
||||||
"attachmentSetAsPostThumbnail": "设置为帖子缩略图",
|
"attachmentSetAsPostThumbnail": "设置为帖子缩略图",
|
||||||
"attachmentUnsetAsPostThumbnail": "取消设置为帖子缩略图",
|
"attachmentUnsetAsPostThumbnail": "取消设置为帖子缩略图",
|
||||||
|
"attachmentCompressVideo": "重新编码视频",
|
||||||
"attachmentSetThumbnail": "设置缩略图",
|
"attachmentSetThumbnail": "设置缩略图",
|
||||||
"attachmentCopyRandomId": "复制访问 ID",
|
"attachmentCopyRandomId": "复制访问 ID",
|
||||||
"attachmentUpload": "上传",
|
"attachmentUpload": "上传",
|
||||||
@ -511,5 +512,11 @@
|
|||||||
"postCategoryLiterature": "文学",
|
"postCategoryLiterature": "文学",
|
||||||
"postCategoryFunny": "搞笑",
|
"postCategoryFunny": "搞笑",
|
||||||
"postCategoryUncategorized": "未分类",
|
"postCategoryUncategorized": "未分类",
|
||||||
"waitingForUpload": "等待上传"
|
"waitingForUpload": "等待上传",
|
||||||
|
"attachmentCompressQuality": "压缩质量",
|
||||||
|
"attachmentCompressQualityHighest": "最高",
|
||||||
|
"attachmentCompressQualityDefault": "默认",
|
||||||
|
"attachmentCompressQualityMedium": "中等",
|
||||||
|
"attachmentCompressQualityLow": "低",
|
||||||
|
"attachmentCompressQualityHint": "Solar Network 并没有阻止你上传大文件、高分辨率、高码率的视频,但是为了你的网络情况观众考虑,我们建议你选择一个合适的压缩质量。"
|
||||||
}
|
}
|
||||||
|
@ -289,6 +289,7 @@
|
|||||||
"attachmentInsertLink": "插入連接",
|
"attachmentInsertLink": "插入連接",
|
||||||
"attachmentSetAsPostThumbnail": "設置為帖子縮略圖",
|
"attachmentSetAsPostThumbnail": "設置為帖子縮略圖",
|
||||||
"attachmentUnsetAsPostThumbnail": "取消設置為帖子縮略圖",
|
"attachmentUnsetAsPostThumbnail": "取消設置為帖子縮略圖",
|
||||||
|
"attachmentCompressVideo": "重新編碼視頻",
|
||||||
"attachmentSetThumbnail": "設置縮略圖",
|
"attachmentSetThumbnail": "設置縮略圖",
|
||||||
"attachmentCopyRandomId": "複製訪問 ID",
|
"attachmentCopyRandomId": "複製訪問 ID",
|
||||||
"attachmentUpload": "上傳",
|
"attachmentUpload": "上傳",
|
||||||
@ -511,5 +512,11 @@
|
|||||||
"postCategoryLiterature": "文學",
|
"postCategoryLiterature": "文學",
|
||||||
"postCategoryFunny": "搞笑",
|
"postCategoryFunny": "搞笑",
|
||||||
"postCategoryUncategorized": "未分類",
|
"postCategoryUncategorized": "未分類",
|
||||||
"waitingForUpload": "等待上傳"
|
"waitingForUpload": "等待上傳",
|
||||||
|
"attachmentCompressQuality": "壓縮質量",
|
||||||
|
"attachmentCompressQualityHighest": "最高",
|
||||||
|
"attachmentCompressQualityDefault": "默認",
|
||||||
|
"attachmentCompressQualityMedium": "中等",
|
||||||
|
"attachmentCompressQualityLow": "低",
|
||||||
|
"attachmentCompressQualityHint": "Solar Network 並沒有阻止你上傳大文件、高分辨率、高碼率的視頻,但是為了你的網絡情況觀眾考慮,我們建議你選擇一個合適的壓縮質量。"
|
||||||
}
|
}
|
||||||
|
@ -289,6 +289,7 @@
|
|||||||
"attachmentInsertLink": "插入連接",
|
"attachmentInsertLink": "插入連接",
|
||||||
"attachmentSetAsPostThumbnail": "設置為帖子縮略圖",
|
"attachmentSetAsPostThumbnail": "設置為帖子縮略圖",
|
||||||
"attachmentUnsetAsPostThumbnail": "取消設置為帖子縮略圖",
|
"attachmentUnsetAsPostThumbnail": "取消設置為帖子縮略圖",
|
||||||
|
"attachmentCompressVideo": "重新編碼視頻",
|
||||||
"attachmentSetThumbnail": "設置縮略圖",
|
"attachmentSetThumbnail": "設置縮略圖",
|
||||||
"attachmentCopyRandomId": "複製訪問 ID",
|
"attachmentCopyRandomId": "複製訪問 ID",
|
||||||
"attachmentUpload": "上傳",
|
"attachmentUpload": "上傳",
|
||||||
@ -511,5 +512,11 @@
|
|||||||
"postCategoryLiterature": "文學",
|
"postCategoryLiterature": "文學",
|
||||||
"postCategoryFunny": "搞笑",
|
"postCategoryFunny": "搞笑",
|
||||||
"postCategoryUncategorized": "未分類",
|
"postCategoryUncategorized": "未分類",
|
||||||
"waitingForUpload": "等待上傳"
|
"waitingForUpload": "等待上傳",
|
||||||
|
"attachmentCompressQuality": "壓縮質量",
|
||||||
|
"attachmentCompressQualityHighest": "最高",
|
||||||
|
"attachmentCompressQualityDefault": "默認",
|
||||||
|
"attachmentCompressQualityMedium": "中等",
|
||||||
|
"attachmentCompressQualityLow": "低",
|
||||||
|
"attachmentCompressQualityHint": "Solar Network 並沒有阻止你上傳大文件、高分辨率、高碼率的視頻,但是為了你的網絡情況觀眾考慮,我們建議你選擇一個合適的壓縮質量。"
|
||||||
}
|
}
|
||||||
|
@ -217,6 +217,8 @@ PODS:
|
|||||||
- SwiftyGif (5.4.5)
|
- SwiftyGif (5.4.5)
|
||||||
- url_launcher_ios (0.0.1):
|
- url_launcher_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- video_compress (0.3.0):
|
||||||
|
- Flutter
|
||||||
- volume_controller (0.0.1):
|
- volume_controller (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- wakelock_plus (0.0.1):
|
- wakelock_plus (0.0.1):
|
||||||
@ -259,6 +261,7 @@ DEPENDENCIES:
|
|||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
|
- video_compress (from `.symlinks/plugins/video_compress/ios`)
|
||||||
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
|
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
|
||||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||||
- workmanager (from `.symlinks/plugins/workmanager/ios`)
|
- workmanager (from `.symlinks/plugins/workmanager/ios`)
|
||||||
@ -348,6 +351,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
|
video_compress:
|
||||||
|
:path: ".symlinks/plugins/video_compress/ios"
|
||||||
volume_controller:
|
volume_controller:
|
||||||
:path: ".symlinks/plugins/volume_controller/ios"
|
:path: ".symlinks/plugins/volume_controller/ios"
|
||||||
wakelock_plus:
|
wakelock_plus:
|
||||||
@ -405,6 +410,7 @@ SPEC CHECKSUMS:
|
|||||||
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||||
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||||
|
video_compress: fce97e4fb1dfd88175aa07d2ffc8a2f297f87fbe
|
||||||
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
||||||
wakelock_plus: 373cfe59b235a6dd5837d0fb88791d2f13a90d56
|
wakelock_plus: 373cfe59b235a6dd5837d0fb88791d2f13a90d56
|
||||||
WebRTC-SDK: 79942c006ea64f6fb48d7da8a4786dfc820bc1db
|
WebRTC-SDK: 79942c006ea64f6fb48d7da8a4786dfc820bc1db
|
||||||
|
@ -215,7 +215,7 @@ class _AttachmentItemContentVideoState extends State<_AttachmentItemContentVideo
|
|||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
if (widget.data.thumbnail != null)
|
if (widget.data.thumbnail?.isNotEmpty ?? false)
|
||||||
AutoResizeUniversalImage(
|
AutoResizeUniversalImage(
|
||||||
sn.getAttachmentUrl(widget.data.thumbnail!),
|
sn.getAttachmentUrl(widget.data.thumbnail!),
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
|
163
lib/widgets/attachment/pending_attachment_compress.dart
Normal file
163
lib/widgets/attachment/pending_attachment_compress.dart
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:cross_file/cross_file.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/controllers/post_write_controller.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:video_compress/video_compress.dart';
|
||||||
|
|
||||||
|
class PendingVideoCompressDialog extends StatefulWidget {
|
||||||
|
final PostWriteMedia media;
|
||||||
|
|
||||||
|
const PendingVideoCompressDialog({super.key, required this.media});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PendingVideoCompressDialog> createState() => _PendingVideoCompressDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PendingVideoCompressDialogState extends State<PendingVideoCompressDialog> {
|
||||||
|
VideoQuality _quality = VideoQuality.DefaultQuality;
|
||||||
|
|
||||||
|
bool _isBusy = false;
|
||||||
|
double? _progress;
|
||||||
|
MediaInfo? _mediaInfo;
|
||||||
|
|
||||||
|
Subscription? _progressSubscription;
|
||||||
|
|
||||||
|
Future<void> _startCompress() async {
|
||||||
|
_mediaInfo = await VideoCompress.compressVideo(
|
||||||
|
widget.media.file!.path,
|
||||||
|
quality: _quality,
|
||||||
|
deleteOrigin: false,
|
||||||
|
frameRate: switch (_quality) {
|
||||||
|
VideoQuality.HighestQuality => 60,
|
||||||
|
VideoQuality.DefaultQuality => 60,
|
||||||
|
_ => 30,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (_mediaInfo == null) return;
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
if (!mounted || _mediaInfo == null) return;
|
||||||
|
Navigator.pop(context, PostWriteMedia.fromFile(XFile(_mediaInfo!.path!)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_progressSubscription = VideoCompress.compressProgress$.subscribe((event) {
|
||||||
|
log('[Compress] Progress: $event');
|
||||||
|
setState(() {
|
||||||
|
_progress = event / 100;
|
||||||
|
_isBusy = event < 100;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_progressSubscription?.unsubscribe();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text('attachmentCompressVideo').tr(),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
FutureBuilder(
|
||||||
|
future: widget.media.file?.length(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (!snapshot.hasData) return const SizedBox.shrink();
|
||||||
|
return Text(
|
||||||
|
snapshot.data!.formatBytes(),
|
||||||
|
style: GoogleFonts.robotoMono(fontSize: 13),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Text('attachmentCompressQuality').tr(),
|
||||||
|
const Gap(8),
|
||||||
|
Card(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
RadioListTile(
|
||||||
|
title: Text('attachmentCompressQualityHighest').tr(),
|
||||||
|
value: VideoQuality.HighestQuality,
|
||||||
|
groupValue: _quality,
|
||||||
|
selected: _quality == VideoQuality.HighestQuality,
|
||||||
|
onChanged: (val) {
|
||||||
|
if (val != null) {
|
||||||
|
setState(() => _quality = val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
RadioListTile(
|
||||||
|
title: Text('attachmentCompressQualityDefault').tr(),
|
||||||
|
value: VideoQuality.DefaultQuality,
|
||||||
|
groupValue: _quality,
|
||||||
|
onChanged: (val) {
|
||||||
|
if (val != null) {
|
||||||
|
setState(() => _quality = val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
RadioListTile(
|
||||||
|
title: Text('attachmentCompressQualityMedium').tr(),
|
||||||
|
value: VideoQuality.MediumQuality,
|
||||||
|
groupValue: _quality,
|
||||||
|
onChanged: (val) {
|
||||||
|
if (val != null) {
|
||||||
|
setState(() => _quality = val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
RadioListTile(
|
||||||
|
title: Text('attachmentCompressQualityLow').tr(),
|
||||||
|
value: VideoQuality.LowQuality,
|
||||||
|
groupValue: _quality,
|
||||||
|
onChanged: (val) {
|
||||||
|
if (val != null) {
|
||||||
|
setState(() => _quality = val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
Text('attachmentCompressQualityHint', style: Theme.of(context).textTheme.bodySmall!).tr(),
|
||||||
|
if (_isBusy)
|
||||||
|
TweenAnimationBuilder<double>(
|
||||||
|
tween: Tween(begin: 0, end: _progress ?? 0),
|
||||||
|
duration: Duration(milliseconds: 100),
|
||||||
|
builder: (context, value, _) => LinearProgressIndicator(
|
||||||
|
value: value,
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
|
),
|
||||||
|
).padding(top: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: _isBusy
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: Text('dialogDismiss').tr(),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: _isBusy ? null : _startCompress,
|
||||||
|
child: Text('dialogConfirm').tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,8 @@ import 'package:surface/widgets/context_menu.dart';
|
|||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/universal_image.dart';
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
|
|
||||||
|
import '../attachment/pending_attachment_compress.dart';
|
||||||
|
|
||||||
class PostMediaPendingList extends StatelessWidget {
|
class PostMediaPendingList extends StatelessWidget {
|
||||||
final PostWriteMedia? thumbnail;
|
final PostWriteMedia? thumbnail;
|
||||||
final List<PostWriteMedia> attachments;
|
final List<PostWriteMedia> attachments;
|
||||||
@ -118,9 +120,28 @@ class PostMediaPendingList extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _compressVideo(BuildContext context, int idx) async {
|
||||||
|
final result = await showDialog<PostWriteMedia?>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => PendingVideoCompressDialog(media: attachments[idx]),
|
||||||
|
);
|
||||||
|
if (result == null) return;
|
||||||
|
|
||||||
|
onUpdate!(idx, result);
|
||||||
|
}
|
||||||
|
|
||||||
ContextMenu _createContextMenu(BuildContext context, int idx, PostWriteMedia media) {
|
ContextMenu _createContextMenu(BuildContext context, int idx, PostWriteMedia media) {
|
||||||
|
final canCompressVideo = !kIsWeb && (Platform.isAndroid || Platform.isIOS || Platform.isMacOS);
|
||||||
return ContextMenu(
|
return ContextMenu(
|
||||||
entries: [
|
entries: [
|
||||||
|
if (media.attachment == null && media.type == SnMediaType.video && canCompressVideo)
|
||||||
|
MenuItem(
|
||||||
|
label: 'attachmentCompressVideo'.tr(),
|
||||||
|
icon: Symbols.compress,
|
||||||
|
onSelected: () {
|
||||||
|
_compressVideo(context, idx);
|
||||||
|
},
|
||||||
|
),
|
||||||
if (media.attachment != null && media.type == SnMediaType.video)
|
if (media.attachment != null && media.type == SnMediaType.video)
|
||||||
MenuItem(
|
MenuItem(
|
||||||
label: 'attachmentSetThumbnail'.tr(),
|
label: 'attachmentSetThumbnail'.tr(),
|
||||||
@ -306,22 +327,22 @@ class _PostMediaPendingItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SnMediaType.audio => Container(
|
SnMediaType.audio => Container(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
if (media.attachment?.thumbnail != null)
|
if (media.attachment?.thumbnail != null)
|
||||||
AutoResizeUniversalImage(sn.getAttachmentUrl(media.attachment!.thumbnail!)),
|
AutoResizeUniversalImage(sn.getAttachmentUrl(media.attachment!.thumbnail!)),
|
||||||
const Icon(Symbols.audio_file, color: Colors.white, shadows: [
|
const Icon(Symbols.audio_file, color: Colors.white, shadows: [
|
||||||
Shadow(
|
Shadow(
|
||||||
offset: Offset(1, 1),
|
offset: Offset(1, 1),
|
||||||
blurRadius: 8.0,
|
blurRadius: 8.0,
|
||||||
color: Color.fromARGB(255, 0, 0, 0),
|
color: Color.fromARGB(255, 0, 0, 0),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
_ => Container(
|
_ => Container(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
child: const Icon(Symbols.docs).center(),
|
child: const Icon(Symbols.docs).center(),
|
||||||
|
Loading…
Reference in New Issue
Block a user