⚡ Optimize
This commit is contained in:
64
lib/widgets/attachments/attachment_item.dart
Normal file
64
lib/widgets/attachments/attachment_item.dart
Normal file
@ -0,0 +1,64 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:solian/models/attachment.dart';
|
||||
import 'package:solian/services.dart';
|
||||
|
||||
class AttachmentItem extends StatelessWidget {
|
||||
final Attachment item;
|
||||
final bool showBadge;
|
||||
final bool showHideButton;
|
||||
final BoxFit fit;
|
||||
final String? badge;
|
||||
final Function? onHide;
|
||||
|
||||
const AttachmentItem({
|
||||
super.key,
|
||||
required this.item,
|
||||
this.badge,
|
||||
this.fit = BoxFit.cover,
|
||||
this.showBadge = true,
|
||||
this.showHideButton = true,
|
||||
this.onHide,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Hero(
|
||||
tag: Key('a${item.uuid}'),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Image.network(
|
||||
'${ServiceFinder.services['paperclip']}/api/attachments/${item.id}',
|
||||
fit: fit,
|
||||
),
|
||||
if (showBadge && badge != null)
|
||||
Positioned(
|
||||
right: 12,
|
||||
bottom: 8,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Chip(label: Text(badge!)),
|
||||
),
|
||||
),
|
||||
if (showHideButton && item.isMature)
|
||||
Positioned(
|
||||
top: 8,
|
||||
left: 12,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: ActionChip(
|
||||
visualDensity: const VisualDensity(vertical: -4, horizontal: -4),
|
||||
avatar: Icon(Icons.visibility_off, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
label: Text('hide'.tr),
|
||||
onPressed: () {
|
||||
if (onHide != null) onHide!();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -4,8 +4,8 @@ import 'package:carousel_slider/carousel_slider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:solian/models/attachment.dart';
|
||||
import 'package:solian/providers/content/attachment_item.dart';
|
||||
import 'package:solian/providers/content/attachment_list.dart';
|
||||
import 'package:solian/widgets/attachments/attachment_item.dart';
|
||||
import 'package:solian/providers/content/attachment.dart';
|
||||
import 'package:solian/widgets/attachments/attachment_list_fullscreen.dart';
|
||||
|
||||
class AttachmentList extends StatefulWidget {
|
||||
@ -26,7 +26,7 @@ class _AttachmentListState extends State<AttachmentList> {
|
||||
List<Attachment?> _attachmentsMeta = List.empty();
|
||||
|
||||
void getMetadataList() {
|
||||
final AttachmentListProvider provider = Get.find();
|
||||
final AttachmentProvider provider = Get.find();
|
||||
|
||||
if (widget.attachmentsId.isEmpty) {
|
||||
return;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:solian/models/attachment.dart';
|
||||
import 'package:solian/providers/content/attachment_item.dart';
|
||||
import 'package:solian/widgets/attachments/attachment_item.dart';
|
||||
|
||||
class AttachmentListFullscreen extends StatefulWidget {
|
||||
final Attachment attachment;
|
||||
|
@ -1,6 +1,4 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
@ -8,19 +6,10 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:solian/exts.dart';
|
||||
import 'package:solian/models/attachment.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:solian/providers/content/attachment_list.dart';
|
||||
import 'package:solian/services.dart';
|
||||
|
||||
Future<String> calculateFileSha256(File file) async {
|
||||
final bytes = await file.readAsBytes();
|
||||
final digest = await Isolate.run(() => sha256.convert(bytes));
|
||||
return digest.toString();
|
||||
}
|
||||
import 'package:solian/providers/content/attachment.dart';
|
||||
|
||||
class AttachmentPublishingPopup extends StatefulWidget {
|
||||
final String usage;
|
||||
@ -59,11 +48,13 @@ class _AttachmentPublishingPopupState extends State<AttachmentPublishingPopup> {
|
||||
for (final media in medias) {
|
||||
final file = File(media.path);
|
||||
final hash = await calculateFileSha256(file);
|
||||
final image = await decodeImageFromList(await file.readAsBytes());
|
||||
final ratio = image.width / image.height;
|
||||
|
||||
try {
|
||||
await uploadAttachment(file, hash, ratio: ratio);
|
||||
await uploadAttachment(
|
||||
file,
|
||||
hash,
|
||||
ratio: await calculateFileAspectRatio(file),
|
||||
);
|
||||
} catch (err) {
|
||||
this.context.showErrorDialog(err);
|
||||
}
|
||||
@ -102,10 +93,10 @@ class _AttachmentPublishingPopupState extends State<AttachmentPublishingPopup> {
|
||||
await FilePicker.platform.pickFiles(allowMultiple: true);
|
||||
if (result == null) return;
|
||||
|
||||
List<File> files = result.paths.map((path) => File(path!)).toList();
|
||||
|
||||
setState(() => _isBusy = true);
|
||||
|
||||
List<File> files = result.paths.map((path) => File(path!)).toList();
|
||||
|
||||
for (final file in files) {
|
||||
final hash = await calculateFileSha256(file);
|
||||
try {
|
||||
@ -139,8 +130,7 @@ class _AttachmentPublishingPopupState extends State<AttachmentPublishingPopup> {
|
||||
if (isVideo) {
|
||||
ratio = 16 / 9;
|
||||
} else {
|
||||
final image = await decodeImageFromList(await file.readAsBytes());
|
||||
ratio = image.width / image.height;
|
||||
ratio = await calculateFileAspectRatio(file);
|
||||
}
|
||||
|
||||
try {
|
||||
@ -153,36 +143,19 @@ class _AttachmentPublishingPopupState extends State<AttachmentPublishingPopup> {
|
||||
}
|
||||
|
||||
Future<void> uploadAttachment(File file, String hash, {double? ratio}) async {
|
||||
final AuthProvider auth = Get.find();
|
||||
|
||||
final client = GetConnect();
|
||||
client.httpClient.baseUrl = ServiceFinder.services['paperclip'];
|
||||
client.httpClient.addAuthenticator(auth.reqAuthenticator);
|
||||
|
||||
final filePayload =
|
||||
MultipartFile(await file.readAsBytes(), filename: basename(file.path));
|
||||
final fileAlt = basename(file.path).contains('.')
|
||||
? basename(file.path).substring(0, basename(file.path).lastIndexOf('.'))
|
||||
: basename(file.path);
|
||||
|
||||
final resp = await client.post(
|
||||
'/api/attachments',
|
||||
FormData({
|
||||
'alt': fileAlt,
|
||||
'file': filePayload,
|
||||
'hash': hash,
|
||||
'usage': widget.usage,
|
||||
'metadata': jsonEncode({
|
||||
if (ratio != null) 'ratio': ratio,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
if (resp.statusCode == 200) {
|
||||
final AttachmentProvider provider = Get.find();
|
||||
try {
|
||||
final resp = await provider.createAttachment(
|
||||
file,
|
||||
hash,
|
||||
widget.usage,
|
||||
ratio: ratio,
|
||||
);
|
||||
var result = Attachment.fromJson(resp.body);
|
||||
setState(() => _attachments.add(result));
|
||||
widget.onUpdate(_attachments.map((e) => e!.id).toList());
|
||||
} else {
|
||||
throw Exception(resp.bodyString);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,7 +179,7 @@ class _AttachmentPublishingPopupState extends State<AttachmentPublishingPopup> {
|
||||
}
|
||||
|
||||
void revertMetadataList() {
|
||||
final AttachmentListProvider provider = Get.find();
|
||||
final AttachmentProvider provider = Get.find();
|
||||
|
||||
if (widget.current.isEmpty) {
|
||||
_isFirstTimeBusy = false;
|
||||
@ -299,7 +272,7 @@ class _AttachmentPublishingPopupState extends State<AttachmentPublishingPopup> {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AttachmentEditingPopup(
|
||||
return AttachmentEditingDialog(
|
||||
item: element,
|
||||
onDelete: () {
|
||||
setState(
|
||||
@ -379,22 +352,23 @@ class _AttachmentPublishingPopupState extends State<AttachmentPublishingPopup> {
|
||||
}
|
||||
}
|
||||
|
||||
class AttachmentEditingPopup extends StatefulWidget {
|
||||
class AttachmentEditingDialog extends StatefulWidget {
|
||||
final Attachment item;
|
||||
final Function onDelete;
|
||||
final Function(Attachment item) onUpdate;
|
||||
|
||||
const AttachmentEditingPopup(
|
||||
const AttachmentEditingDialog(
|
||||
{super.key,
|
||||
required this.item,
|
||||
required this.onDelete,
|
||||
required this.onUpdate});
|
||||
|
||||
@override
|
||||
State<AttachmentEditingPopup> createState() => _AttachmentEditingPopupState();
|
||||
State<AttachmentEditingDialog> createState() =>
|
||||
_AttachmentEditingDialogState();
|
||||
}
|
||||
|
||||
class _AttachmentEditingPopupState extends State<AttachmentEditingPopup> {
|
||||
class _AttachmentEditingDialogState extends State<AttachmentEditingDialog> {
|
||||
final _ratioController = TextEditingController();
|
||||
final _altController = TextEditingController();
|
||||
|
||||
@ -402,49 +376,40 @@ class _AttachmentEditingPopupState extends State<AttachmentEditingPopup> {
|
||||
bool _isMature = false;
|
||||
bool _hasAspectRatio = false;
|
||||
|
||||
Future<Attachment?> applyAttachment() async {
|
||||
final AuthProvider auth = Get.find();
|
||||
|
||||
final client = GetConnect();
|
||||
client.httpClient.baseUrl = ServiceFinder.services['paperclip'];
|
||||
client.httpClient.addAuthenticator(auth.reqAuthenticator);
|
||||
Future<Attachment?> updateAttachment() async {
|
||||
final AttachmentProvider provider = Get.find();
|
||||
|
||||
setState(() => _isBusy = true);
|
||||
var resp = await client.put('/api/attachments/${widget.item.id}', {
|
||||
'metadata': {
|
||||
if (_hasAspectRatio)
|
||||
'ratio': double.tryParse(_ratioController.value.text) ?? 1,
|
||||
},
|
||||
'alt': _altController.value.text,
|
||||
'usage': widget.item.usage,
|
||||
'is_mature': _isMature,
|
||||
});
|
||||
|
||||
setState(() => _isBusy = false);
|
||||
|
||||
if (resp.statusCode != 200) {
|
||||
this.context.showErrorDialog(resp.bodyString);
|
||||
return null;
|
||||
} else {
|
||||
try {
|
||||
final resp = await provider.updateAttachment(
|
||||
widget.item.id,
|
||||
_altController.value.text,
|
||||
widget.item.usage,
|
||||
ratio: _hasAspectRatio
|
||||
? (double.tryParse(_ratioController.value.text) ?? 1)
|
||||
: null,
|
||||
isMature: _isMature,
|
||||
);
|
||||
return Attachment.fromJson(resp.body);
|
||||
} catch (e) {
|
||||
this.context.showErrorDialog(e);
|
||||
return null;
|
||||
} finally {
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteAttachment() async {
|
||||
final AuthProvider auth = Get.find();
|
||||
|
||||
final client = GetConnect();
|
||||
client.httpClient.baseUrl = ServiceFinder.services['paperclip'];
|
||||
client.httpClient.addAuthenticator(auth.reqAuthenticator);
|
||||
|
||||
setState(() => _isBusy = true);
|
||||
var resp = await client.delete('/api/attachments/${widget.item.id}');
|
||||
if (resp.statusCode == 200) {
|
||||
try {
|
||||
final AttachmentProvider provider = Get.find();
|
||||
await provider.deleteAttachment(widget.item.id);
|
||||
widget.onDelete();
|
||||
} else {
|
||||
this.context.showErrorDialog(resp.bodyString);
|
||||
} catch (e) {
|
||||
this.context.showErrorDialog(e);
|
||||
} finally {
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
|
||||
void syncWidget() {
|
||||
@ -586,7 +551,7 @@ class _AttachmentEditingPopupState extends State<AttachmentEditingPopup> {
|
||||
TextButton(
|
||||
child: Text('apply'.tr),
|
||||
onPressed: () {
|
||||
applyAttachment().then((value) {
|
||||
updateAttachment().then((value) {
|
||||
if (value != null) {
|
||||
widget.onUpdate(value);
|
||||
Navigator.pop(context);
|
||||
|
19
lib/widgets/prev_page.dart
Normal file
19
lib/widgets/prev_page.dart
Normal file
@ -0,0 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:solian/router.dart';
|
||||
|
||||
class PrevPageButton extends StatelessWidget {
|
||||
const PrevPageButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
|
||||
onPressed: () {
|
||||
if (AppRouter.instance.canPop()) {
|
||||
AppRouter.instance.pop();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user