✨ Attachment thumbnail
This commit is contained in:
parent
8d9a8b5435
commit
e385f79df2
@ -172,7 +172,7 @@ class _AttachmentFullScreenState extends State<AttachmentFullScreen> {
|
|||||||
end: Alignment.topCenter,
|
end: Alignment.topCenter,
|
||||||
colors: [
|
colors: [
|
||||||
Theme.of(context).colorScheme.surface,
|
Theme.of(context).colorScheme.surface,
|
||||||
Theme.of(context).colorScheme.surface.withOpacity(0),
|
Colors.transparent,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:math';
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
@ -221,21 +222,87 @@ class _AttachmentItemVideoState extends State<_AttachmentItemVideo> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
const labelShadows = <Shadow>[
|
||||||
|
Shadow(
|
||||||
|
offset: Offset(1, 1),
|
||||||
|
blurRadius: 5.0,
|
||||||
|
color: Color.fromARGB(255, 0, 0, 0),
|
||||||
|
),
|
||||||
|
];
|
||||||
final ratio = widget.item.metadata?['ratio'] ?? 16 / 9;
|
final ratio = widget.item.metadata?['ratio'] ?? 16 / 9;
|
||||||
if (!_showContent) {
|
if (!_showContent) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
child: Column(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
if (widget.item.metadata?['thumbnail'] != null)
|
if (widget.item.metadata?['thumbnail'] != null)
|
||||||
AspectRatio(
|
AspectRatio(
|
||||||
aspectRatio: 16 / 9,
|
aspectRatio: 16 / 9,
|
||||||
child: Image.network(
|
child: AutoCacheImage(
|
||||||
ServiceFinder.buildUrl(
|
ServiceFinder.buildUrl(
|
||||||
'uc',
|
'uc',
|
||||||
'/attachments/${widget.item.metadata?['thumbnail']}',
|
'/attachments/${widget.item.metadata?['thumbnail']}',
|
||||||
),
|
),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
const Center(
|
||||||
|
child: Icon(Icons.movie, size: 64),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: IgnorePointer(
|
||||||
|
child: Container(
|
||||||
|
height: 56,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.bottomCenter,
|
||||||
|
end: Alignment.topCenter,
|
||||||
|
colors: [
|
||||||
|
Theme.of(context).colorScheme.surface,
|
||||||
|
Colors.transparent,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: 4,
|
||||||
|
left: 16,
|
||||||
|
right: 16,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 45,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.item.alt,
|
||||||
|
style: const TextStyle(shadows: labelShadows),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
Duration(
|
||||||
|
milliseconds:
|
||||||
|
(widget.item.metadata?['duration'] ?? 0) *
|
||||||
|
1000,
|
||||||
|
).toHumanReadableString(),
|
||||||
|
style: GoogleFonts.robotoMono(
|
||||||
|
fontSize: 12,
|
||||||
|
shadows: labelShadows,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Icon(Icons.play_arrow, shadows: labelShadows)
|
||||||
|
.paddingOnly(bottom: 4, right: 8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -302,6 +369,25 @@ class _AttachmentItemAudioState extends State<_AttachmentItemAudio> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _formatBytes(int bytes, {int decimals = 2}) {
|
||||||
|
if (bytes == 0) return '0 Bytes';
|
||||||
|
const k = 1024;
|
||||||
|
final dm = decimals < 0 ? 0 : decimals;
|
||||||
|
final sizes = [
|
||||||
|
'Bytes',
|
||||||
|
'KiB',
|
||||||
|
'MiB',
|
||||||
|
'GiB',
|
||||||
|
'TiB',
|
||||||
|
'PiB',
|
||||||
|
'EiB',
|
||||||
|
'ZiB',
|
||||||
|
'YiB'
|
||||||
|
];
|
||||||
|
final i = (math.log(bytes) / math.log(k)).floor().toInt();
|
||||||
|
return '${(bytes / math.pow(k, i)).toStringAsFixed(dm)} ${sizes[i]}';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -312,38 +398,84 @@ class _AttachmentItemAudioState extends State<_AttachmentItemAudio> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
const labelShadows = <Shadow>[
|
||||||
|
Shadow(
|
||||||
|
offset: Offset(1, 1),
|
||||||
|
blurRadius: 5.0,
|
||||||
|
color: Color.fromARGB(255, 0, 0, 0),
|
||||||
|
),
|
||||||
|
];
|
||||||
const ratio = 16 / 9;
|
const ratio = 16 / 9;
|
||||||
if (!_showContent) {
|
if (!_showContent) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
child: AspectRatio(
|
child: Stack(
|
||||||
aspectRatio: ratio,
|
children: [
|
||||||
child: CenteredContainer(
|
if (widget.item.metadata?['thumbnail'] != null)
|
||||||
maxWidth: 280,
|
AspectRatio(
|
||||||
child: Column(
|
aspectRatio: 16 / 9,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
child: AutoCacheImage(
|
||||||
children: [
|
ServiceFinder.buildUrl(
|
||||||
const Icon(
|
'uc',
|
||||||
Icons.not_started,
|
'/attachments/${widget.item.metadata?['thumbnail']}',
|
||||||
color: Colors.white,
|
),
|
||||||
size: 32,
|
fit: BoxFit.cover,
|
||||||
),
|
),
|
||||||
const Gap(8),
|
)
|
||||||
Text(
|
else
|
||||||
'attachmentUnload'.tr,
|
const Center(
|
||||||
style: const TextStyle(
|
child: Icon(Icons.radio, size: 64),
|
||||||
color: Colors.white,
|
),
|
||||||
fontWeight: FontWeight.bold,
|
Align(
|
||||||
fontSize: 16,
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: IgnorePointer(
|
||||||
|
child: Container(
|
||||||
|
height: 56,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.bottomCenter,
|
||||||
|
end: Alignment.topCenter,
|
||||||
|
colors: [
|
||||||
|
Theme.of(context).colorScheme.surface,
|
||||||
|
Colors.transparent,
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
),
|
||||||
'attachmentUnloadCaption'.tr,
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
Positioned(
|
||||||
|
bottom: 4,
|
||||||
|
left: 16,
|
||||||
|
right: 16,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 45,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.item.alt,
|
||||||
|
style: const TextStyle(shadows: labelShadows),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
_formatBytes(widget.item.size),
|
||||||
|
style: GoogleFonts.robotoMono(
|
||||||
|
fontSize: 12,
|
||||||
|
shadows: labelShadows,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Icon(Icons.play_arrow, shadows: labelShadows)
|
||||||
|
.paddingOnly(bottom: 4, right: 8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_startLoad();
|
_startLoad();
|
||||||
@ -355,92 +487,116 @@ class _AttachmentItemAudioState extends State<_AttachmentItemAudio> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return AspectRatio(
|
return Stack(
|
||||||
aspectRatio: ratio,
|
children: [
|
||||||
child: CenteredContainer(
|
if (widget.item.metadata?['thumbnail'] != null)
|
||||||
maxWidth: 320,
|
AspectRatio(
|
||||||
child: Column(
|
aspectRatio: 16 / 9,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
child: AutoCacheImage(
|
||||||
children: [
|
ServiceFinder.buildUrl(
|
||||||
const Icon(Icons.audio_file, size: 32),
|
'uc',
|
||||||
const Gap(8),
|
'/attachments/${widget.item.metadata?['thumbnail']}',
|
||||||
Text(
|
),
|
||||||
widget.item.alt,
|
fit: BoxFit.cover,
|
||||||
style: const TextStyle(fontSize: 13),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
),
|
||||||
const Gap(12),
|
).animate().blur(
|
||||||
Row(
|
duration: 300.ms,
|
||||||
|
end: const Offset(10, 10),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
),
|
||||||
|
AspectRatio(
|
||||||
|
aspectRatio: ratio,
|
||||||
|
child: CenteredContainer(
|
||||||
|
maxWidth: 320,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
const Icon(Icons.audio_file, size: 32),
|
||||||
child: Column(
|
const Gap(8),
|
||||||
children: [
|
Text(
|
||||||
SliderTheme(
|
widget.item.alt,
|
||||||
data: SliderThemeData(
|
style: const TextStyle(fontSize: 13),
|
||||||
trackHeight: 2,
|
textAlign: TextAlign.center,
|
||||||
trackShape: _PlayerProgressTrackShape(),
|
|
||||||
thumbShape: const RoundSliderThumbShape(
|
|
||||||
enabledThumbRadius: 8,
|
|
||||||
),
|
|
||||||
overlayShape: SliderComponentShape.noOverlay,
|
|
||||||
),
|
|
||||||
child: Slider(
|
|
||||||
secondaryTrackValue:
|
|
||||||
_bufferedPosition.inMilliseconds.abs().toDouble(),
|
|
||||||
value: _draggingValue?.abs() ??
|
|
||||||
_position.inMilliseconds.toDouble().abs(),
|
|
||||||
min: 0,
|
|
||||||
max: max(
|
|
||||||
_bufferedPosition.inMilliseconds.abs(),
|
|
||||||
max(
|
|
||||||
_position.inMilliseconds.abs(),
|
|
||||||
_duration.inMilliseconds.abs(),
|
|
||||||
),
|
|
||||||
).toDouble(),
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() => _draggingValue = value);
|
|
||||||
},
|
|
||||||
onChangeEnd: (value) {
|
|
||||||
_audioPlayer!
|
|
||||||
.seek(Duration(milliseconds: value.toInt()));
|
|
||||||
setState(() => _draggingValue = null);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
_position.toHumanReadableString(),
|
|
||||||
style: GoogleFonts.robotoMono(fontSize: 12),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
_duration.toHumanReadableString(),
|
|
||||||
style: GoogleFonts.robotoMono(fontSize: 12),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).paddingSymmetric(horizontal: 8, vertical: 4),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const Gap(16),
|
const Gap(12),
|
||||||
IconButton.filled(
|
Row(
|
||||||
icon: _isPlaying
|
children: [
|
||||||
? const Icon(Icons.pause)
|
Expanded(
|
||||||
: const Icon(Icons.play_arrow),
|
child: Column(
|
||||||
onPressed: () {
|
children: [
|
||||||
_audioPlayer!.playOrPause();
|
SliderTheme(
|
||||||
},
|
data: SliderThemeData(
|
||||||
visualDensity: const VisualDensity(
|
trackHeight: 2,
|
||||||
horizontal: -4,
|
trackShape: _PlayerProgressTrackShape(),
|
||||||
vertical: 0,
|
thumbShape: const RoundSliderThumbShape(
|
||||||
),
|
enabledThumbRadius: 8,
|
||||||
|
),
|
||||||
|
overlayShape: SliderComponentShape.noOverlay,
|
||||||
|
),
|
||||||
|
child: Slider(
|
||||||
|
secondaryTrackValue: _bufferedPosition
|
||||||
|
.inMilliseconds
|
||||||
|
.abs()
|
||||||
|
.toDouble(),
|
||||||
|
value: _draggingValue?.abs() ??
|
||||||
|
_position.inMilliseconds.toDouble().abs(),
|
||||||
|
min: 0,
|
||||||
|
max: math
|
||||||
|
.max(
|
||||||
|
_bufferedPosition.inMilliseconds.abs(),
|
||||||
|
math.max(
|
||||||
|
_position.inMilliseconds.abs(),
|
||||||
|
_duration.inMilliseconds.abs(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toDouble(),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() => _draggingValue = value);
|
||||||
|
},
|
||||||
|
onChangeEnd: (value) {
|
||||||
|
_audioPlayer!.seek(
|
||||||
|
Duration(milliseconds: value.toInt()),
|
||||||
|
);
|
||||||
|
setState(() => _draggingValue = null);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
_position.toHumanReadableString(),
|
||||||
|
style: GoogleFonts.robotoMono(fontSize: 12),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
_duration.toHumanReadableString(),
|
||||||
|
style: GoogleFonts.robotoMono(fontSize: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).paddingSymmetric(horizontal: 8, vertical: 4),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(16),
|
||||||
|
IconButton.filled(
|
||||||
|
icon: _isPlaying
|
||||||
|
? const Icon(Icons.pause)
|
||||||
|
: const Icon(Icons.play_arrow),
|
||||||
|
onPressed: () {
|
||||||
|
_audioPlayer!.playOrPause();
|
||||||
|
},
|
||||||
|
visualDensity: const VisualDensity(
|
||||||
|
horizontal: -4,
|
||||||
|
vertical: 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,10 +368,7 @@ class _PostItemState extends State<PostItem> {
|
|||||||
end: Alignment.topCenter,
|
end: Alignment.topCenter,
|
||||||
colors: [
|
colors: [
|
||||||
Theme.of(context).colorScheme.surfaceContainerLow,
|
Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
Theme.of(context)
|
Colors.transparent,
|
||||||
.colorScheme
|
|
||||||
.surface
|
|
||||||
.withOpacity(0),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -464,10 +461,7 @@ class _PostItemState extends State<PostItem> {
|
|||||||
end: Alignment.topCenter,
|
end: Alignment.topCenter,
|
||||||
colors: [
|
colors: [
|
||||||
Theme.of(context).colorScheme.surface,
|
Theme.of(context).colorScheme.surface,
|
||||||
Theme.of(context)
|
Colors.transparent,
|
||||||
.colorScheme
|
|
||||||
.surface
|
|
||||||
.withOpacity(0),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Loading…
Reference in New Issue
Block a user