Solian/lib/widgets/attachments/attachment_fullscreen.dart

283 lines
9.5 KiB
Dart
Raw Normal View History

2024-07-31 18:10:57 +00:00
import 'dart:io';
import 'dart:math' as math;
2024-07-31 18:10:57 +00:00
import 'package:dio/dio.dart';
import 'package:dismissible_page/dismissible_page.dart';
2024-05-20 16:02:39 +00:00
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
2024-07-31 18:10:57 +00:00
import 'package:gal/gal.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
2024-07-31 18:10:57 +00:00
import 'package:solian/exts.dart';
2024-05-20 16:02:39 +00:00
import 'package:solian/models/attachment.dart';
2024-07-31 18:10:57 +00:00
import 'package:solian/platform.dart';
import 'package:solian/services.dart';
import 'package:solian/widgets/account/account_avatar.dart';
2024-05-22 15:18:01 +00:00
import 'package:solian/widgets/attachments/attachment_item.dart';
2024-07-31 18:10:57 +00:00
import 'package:url_launcher/url_launcher_string.dart';
import 'package:path/path.dart' show extension;
2024-05-20 16:02:39 +00:00
class AttachmentFullScreen extends StatefulWidget {
2024-05-25 16:11:00 +00:00
final String parentId;
2024-07-31 18:10:57 +00:00
final Attachment item;
2024-05-20 16:02:39 +00:00
const AttachmentFullScreen(
2024-07-31 18:10:57 +00:00
{super.key, required this.parentId, required this.item});
2024-05-20 16:02:39 +00:00
@override
State<AttachmentFullScreen> createState() => _AttachmentFullScreenState();
2024-05-20 16:02:39 +00:00
}
class _AttachmentFullScreenState extends State<AttachmentFullScreen> {
bool _showDetails = true;
2024-07-31 18:10:57 +00:00
bool _isDownloading = false;
bool _isCompletedDownload = false;
double? _progressOfDownload = 0;
Color get _unFocusColor =>
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
double _getRatio() {
2024-07-31 18:10:57 +00:00
final value = widget.item.metadata?['ratio'];
if (value == null) return 1;
if (value is int) return value.toDouble();
if (value is double) return value;
return 1;
}
2024-07-31 18:10:57 +00:00
Future<void> _saveToAlbum() async {
final url = ServiceFinder.buildUrl(
'files',
'/attachments/${widget.item.rid}',
2024-07-31 18:10:57 +00:00
);
if (PlatformInfo.isWeb || PlatformInfo.isDesktop) {
2024-07-31 18:10:57 +00:00
await launchUrlString(url);
return;
}
if (!await Gal.hasAccess(toAlbum: true)) {
if (!await Gal.requestAccess(toAlbum: true)) return;
}
setState(() => _isDownloading = true);
var extName = extension(widget.item.name);
if (extName.isEmpty) extName = '.png';
2024-07-31 18:10:57 +00:00
final imagePath =
'${Directory.systemTemp.path}/${widget.item.uuid}$extName';
await Dio().download(
url,
imagePath,
onReceiveProgress: (count, total) {
setState(() => _progressOfDownload = count / total);
},
);
bool isSuccess = false;
try {
await Gal.putImage(imagePath);
isSuccess = true;
} on GalException catch (e) {
context.showErrorDialog(e.type.message);
}
context.showSnackbar(
'attachmentSaved'.tr,
action: SnackBarAction(
label: 'openInAlbum'.tr,
onPressed: () async => Gal.open(),
),
);
setState(() {
_isDownloading = false;
_isCompletedDownload = isSuccess;
});
}
2024-05-20 16:02:39 +00:00
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
2024-07-31 18:10:57 +00:00
final metaTextStyle = TextStyle(
fontSize: 12,
color: _unFocusColor,
);
return DismissiblePage(
2024-07-31 18:10:57 +00:00
key: Key('attachment-dismissible${widget.item.id}'),
direction: DismissiblePageDismissDirection.vertical,
onDismissed: () => Navigator.pop(context),
dismissThresholds: const {
DismissiblePageDismissDirection.vertical: 0.0,
},
2024-07-27 11:20:53 +00:00
onDragStart: () {
setState(() => _showDetails = false);
},
onDragEnd: () {
setState(() => _showDetails = true);
},
2024-05-20 16:02:39 +00:00
child: GestureDetector(
child: Stack(
fit: StackFit.loose,
children: [
SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: InteractiveViewer(
boundaryMargin: EdgeInsets.zero,
minScale: 1,
maxScale: 16,
2024-07-27 11:20:53 +00:00
panEnabled: true,
scaleEnabled: true,
child: AttachmentItem(
parentId: widget.parentId,
showHideButton: false,
2024-07-31 18:10:57 +00:00
item: widget.item,
fit: BoxFit.contain,
),
),
2024-05-20 16:02:39 +00:00
),
Align(
alignment: Alignment.bottomCenter,
2024-07-27 06:16:49 +00:00
child: IgnorePointer(
child: Container(
height: 300,
decoration: BoxDecoration(
2024-07-27 06:16:49 +00:00
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Theme.of(context).colorScheme.surface,
2024-09-10 14:47:28 +00:00
Colors.transparent,
],
2024-07-27 06:16:49 +00:00
),
),
),
),
)
.animate(target: _showDetails ? 1 : 0)
.fadeIn(curve: Curves.fastEaseInToSlowEaseOut),
Positioned(
2024-07-27 11:20:53 +00:00
bottom: math.max(MediaQuery.of(context).padding.bottom, 16),
left: 16,
right: 16,
2024-07-31 18:10:57 +00:00
child: Material(
color: Colors.transparent,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.item.account != null)
Row(
children: [
IgnorePointer(
child: AccountAvatar(
content: widget.item.account!.avatar,
2024-07-27 06:16:49 +00:00
radius: 19,
),
2024-07-31 18:10:57 +00:00
),
2024-09-07 17:48:01 +00:00
const Gap(8),
2024-07-31 18:10:57 +00:00
Expanded(
child: IgnorePointer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'attachmentUploadBy'.tr,
style:
Theme.of(context).textTheme.bodySmall,
),
Text(
widget.item.account!.nick,
style:
Theme.of(context).textTheme.bodyMedium,
),
],
),
),
),
IconButton(
visualDensity: const VisualDensity(
horizontal: -4,
vertical: -2,
2024-07-27 06:16:49 +00:00
),
2024-07-31 18:10:57 +00:00
icon: !_isDownloading
? !_isCompletedDownload
? const Icon(Icons.save_alt)
: const Icon(Icons.download_done)
: SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
value: _progressOfDownload,
strokeWidth: 3,
),
),
onPressed:
_isDownloading ? null : () => _saveToAlbum(),
),
],
),
2024-09-07 17:48:01 +00:00
const Gap(4),
2024-07-31 18:10:57 +00:00
IgnorePointer(
child: Text(
widget.item.alt,
2024-07-27 06:16:49 +00:00
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
),
),
2024-07-31 18:10:57 +00:00
),
2024-09-07 17:48:01 +00:00
const Gap(2),
2024-07-31 18:10:57 +00:00
IgnorePointer(
child: Wrap(
2024-07-27 06:16:49 +00:00
spacing: 6,
children: [
Text(
'#${widget.item.rid}',
style: metaTextStyle,
),
2024-07-31 18:10:57 +00:00
if (widget.item.metadata?['width'] != null &&
widget.item.metadata?['height'] != null)
2024-07-27 06:16:49 +00:00
Text(
2024-07-31 18:10:57 +00:00
'${widget.item.metadata?['width']}x${widget.item.metadata?['height']}',
style: metaTextStyle,
2024-07-27 06:16:49 +00:00
),
2024-07-31 18:10:57 +00:00
if (widget.item.metadata?['ratio'] != null)
2024-07-27 06:16:49 +00:00
Text(
'${_getRatio().toPrecision(2)}',
2024-07-31 18:10:57 +00:00
style: metaTextStyle,
),
Text(
widget.item.size.formatBytes(),
2024-07-31 18:10:57 +00:00
style: metaTextStyle,
),
Text(
widget.item.mimetype,
style: metaTextStyle,
),
2024-07-27 06:16:49 +00:00
],
),
2024-07-31 18:10:57 +00:00
),
],
),
),
)
.animate(target: _showDetails ? 1 : 0)
.fadeIn(curve: Curves.fastEaseInToSlowEaseOut),
],
2024-05-20 16:02:39 +00:00
),
onTap: () {
setState(() => _showDetails = !_showDetails);
2024-05-20 16:02:39 +00:00
},
),
);
}
}