🐛 Fix share via image errored

This commit is contained in:
LittleSheep 2025-03-06 22:46:02 +08:00
parent 115cb4adc1
commit 1c510d63fe
4 changed files with 165 additions and 84 deletions

View File

@ -22,12 +22,14 @@ class AttachmentItem extends StatelessWidget {
final SnAttachment? data;
final String? heroTag;
final BoxFit fit;
final FilterQuality? filterQuality;
const AttachmentItem({
super.key,
this.fit = BoxFit.cover,
required this.data,
required this.heroTag,
this.filterQuality,
});
Widget _buildContent(BuildContext context) {
@ -47,6 +49,7 @@ class AttachmentItem extends StatelessWidget {
sn.getAttachmentUrl(data!.rid),
key: Key('attachment-${data!.rid}-$tag'),
fit: fit,
filterQuality: filterQuality,
),
);
case 'video':
@ -83,13 +86,16 @@ class _AttachmentItemSensitiveBlur extends StatefulWidget {
final Widget child;
final bool isCompact;
const _AttachmentItemSensitiveBlur({required this.child, this.isCompact = false});
const _AttachmentItemSensitiveBlur(
{required this.child, this.isCompact = false});
@override
State<_AttachmentItemSensitiveBlur> createState() => _AttachmentItemSensitiveBlurState();
State<_AttachmentItemSensitiveBlur> createState() =>
_AttachmentItemSensitiveBlurState();
}
class _AttachmentItemSensitiveBlurState extends State<_AttachmentItemSensitiveBlur> {
class _AttachmentItemSensitiveBlurState
extends State<_AttachmentItemSensitiveBlur> {
bool _doesShow = false;
@override
@ -124,10 +130,15 @@ class _AttachmentItemSensitiveBlurState extends State<_AttachmentItemSensitiveBl
Text(
'sensitiveContentDescription',
textAlign: TextAlign.center,
).tr().fontSize(14).textColor(Colors.white.withOpacity(0.8)),
)
.tr()
.fontSize(14)
.textColor(Colors.white.withOpacity(0.8)),
if (!widget.isCompact) const Gap(16),
InkWell(
child: Text('sensitiveContentReveal').tr().textColor(Colors.white),
child: Text('sensitiveContentReveal')
.tr()
.textColor(Colors.white),
onTap: () {
setState(() => _doesShow = !_doesShow);
},
@ -137,7 +148,9 @@ class _AttachmentItemSensitiveBlurState extends State<_AttachmentItemSensitiveBl
).center(),
),
),
).opacity(_doesShow ? 0 : 1, animate: true).animate(const Duration(milliseconds: 300), Curves.easeInOut),
)
.opacity(_doesShow ? 0 : 1, animate: true)
.animate(const Duration(milliseconds: 300), Curves.easeInOut),
if (_doesShow)
Positioned(
top: 0,
@ -174,10 +187,12 @@ class _AttachmentItemContentVideo extends StatefulWidget {
});
@override
State<_AttachmentItemContentVideo> createState() => _AttachmentItemContentVideoState();
State<_AttachmentItemContentVideo> createState() =>
_AttachmentItemContentVideoState();
}
class _AttachmentItemContentVideoState extends State<_AttachmentItemContentVideo> {
class _AttachmentItemContentVideoState
extends State<_AttachmentItemContentVideo> {
bool _showContent = false;
bool _showOriginal = false;
@ -188,7 +203,9 @@ class _AttachmentItemContentVideoState extends State<_AttachmentItemContentVideo
setState(() => _showContent = true);
MediaKit.ensureInitialized();
final sn = context.read<SnNetworkProvider>();
final url = _showOriginal ? sn.getAttachmentUrl(widget.data.rid) : sn.getAttachmentUrl(widget.data.compressed!.rid);
final url = _showOriginal
? sn.getAttachmentUrl(widget.data.rid)
: sn.getAttachmentUrl(widget.data.compressed!.rid);
_videoPlayer = Player();
_videoController = VideoController(_videoPlayer!);
_videoPlayer!.open(Media(url), play: !widget.isAutoload);
@ -201,7 +218,9 @@ class _AttachmentItemContentVideoState extends State<_AttachmentItemContentVideo
final sn = context.read<SnNetworkProvider>();
_videoPlayer?.open(
Media(
_showOriginal ? sn.getAttachmentUrl(widget.data.rid) : sn.getAttachmentUrl(widget.data.compressed!.rid),
_showOriginal
? sn.getAttachmentUrl(widget.data.rid)
: sn.getAttachmentUrl(widget.data.compressed!.rid),
),
play: true,
);
@ -283,7 +302,9 @@ class _AttachmentItemContentVideoState extends State<_AttachmentItemContentVideo
),
Text(
Duration(
milliseconds: (widget.data.data['duration'] ?? 0).toInt() * 1000,
milliseconds:
(widget.data.data['duration'] ?? 0).toInt() *
1000,
).toString(),
style: GoogleFonts.robotoMono(
fontSize: 12,
@ -346,7 +367,9 @@ class _AttachmentItemContentVideoState extends State<_AttachmentItemContentVideo
MaterialDesktopCustomButton(
iconSize: 24,
onPressed: _toggleOriginal,
icon: _showOriginal ? const Icon(Symbols.high_quality, size: 24) : const Icon(Symbols.sd, size: 24),
icon: _showOriginal
? const Icon(Symbols.high_quality, size: 24)
: const Icon(Symbols.sd, size: 24),
),
],
),
@ -354,8 +377,9 @@ class _AttachmentItemContentVideoState extends State<_AttachmentItemContentVideo
child: Video(
controller: _videoController!,
aspectRatio: ratio,
controls:
!kIsWeb && (Platform.isAndroid || Platform.isIOS) ? MaterialVideoControls : MaterialDesktopVideoControls,
controls: !kIsWeb && (Platform.isAndroid || Platform.isIOS)
? MaterialVideoControls
: MaterialDesktopVideoControls,
),
),
);
@ -378,10 +402,12 @@ class _AttachmentItemContentAudio extends StatefulWidget {
});
@override
State<_AttachmentItemContentAudio> createState() => _AttachmentItemContentAudioState();
State<_AttachmentItemContentAudio> createState() =>
_AttachmentItemContentAudioState();
}
class _AttachmentItemContentAudioState extends State<_AttachmentItemContentAudio> {
class _AttachmentItemContentAudioState
extends State<_AttachmentItemContentAudio> {
bool _showContent = false;
double? _draggingValue;
@ -552,8 +578,12 @@ class _AttachmentItemContentAudioState extends State<_AttachmentItemContentAudio
overlayShape: SliderComponentShape.noOverlay,
),
child: Slider(
secondaryTrackValue: _bufferedPosition.inMilliseconds.abs().toDouble(),
value: _draggingValue?.abs() ?? _position.inMilliseconds.toDouble().abs(),
secondaryTrackValue: _bufferedPosition
.inMilliseconds
.abs()
.toDouble(),
value: _draggingValue?.abs() ??
_position.inMilliseconds.toDouble().abs(),
min: 0,
max: math
.max(
@ -593,7 +623,9 @@ class _AttachmentItemContentAudioState extends State<_AttachmentItemContentAudio
),
const Gap(16),
IconButton.filled(
icon: _isPlaying ? const Icon(Symbols.pause) : const Icon(Symbols.play_arrow),
icon: _isPlaying
? const Icon(Symbols.pause)
: const Icon(Symbols.play_arrow),
onPressed: () {
_audioPlayer!.playOrPause();
},

View File

@ -21,6 +21,7 @@ class AttachmentList extends StatefulWidget {
final double? minWidth;
final double? maxWidth;
final EdgeInsets? padding;
final FilterQuality? filterQuality;
const AttachmentList({
super.key,
@ -33,23 +34,27 @@ class AttachmentList extends StatefulWidget {
this.minWidth,
this.maxWidth,
this.padding,
this.filterQuality,
});
static const BorderRadius kDefaultRadius = BorderRadius.all(Radius.circular(8));
static const BorderRadius kDefaultRadius =
BorderRadius.all(Radius.circular(8));
@override
State<AttachmentList> createState() => _AttachmentListState();
}
class _AttachmentListState extends State<AttachmentList> {
late final List<String> heroTags = List.generate(widget.data.length, (_) => const Uuid().v4());
late final List<String> heroTags =
List.generate(widget.data.length, (_) => const Uuid().v4());
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, layoutConstraints) {
final borderSide =
widget.bordered ? BorderSide(width: 1, color: Theme.of(context).dividerColor) : BorderSide.none;
final borderSide = widget.bordered
? BorderSide(width: 1, color: Theme.of(context).dividerColor)
: BorderSide.none;
final backgroundColor = Theme.of(context).colorScheme.surfaceContainer;
final constraints = BoxConstraints(
minWidth: widget.minWidth ?? 80,
@ -58,13 +63,13 @@ class _AttachmentListState extends State<AttachmentList> {
if (widget.data.isEmpty) return const SizedBox.shrink();
if (widget.data.length == 1) {
final singleAspectRatio =
widget.data[0]?.data['ratio']?.toDouble() ??
final singleAspectRatio = widget.data[0]?.data['ratio']?.toDouble() ??
switch (widget.data[0]?.mimetype.split('/').firstOrNull) {
'audio' => 16 / 9,
'video' => 16 / 9,
_ => 1,
}.toDouble();
}
.toDouble();
return Container(
padding: widget.padding ?? EdgeInsets.zero,
@ -80,12 +85,18 @@ class _AttachmentListState extends State<AttachmentList> {
),
child: ClipRRect(
borderRadius: AttachmentList.kDefaultRadius,
child: AttachmentItem(data: widget.data[0], heroTag: heroTags[0], fit: widget.fit),
child: AttachmentItem(
data: widget.data[0],
heroTag: heroTags[0],
fit: widget.fit,
filterQuality: widget.filterQuality,
),
),
),
),
onTap: () {
if (widget.data.firstOrNull?.mediaType != SnMediaType.image) return;
if (widget.data.firstOrNull?.mediaType != SnMediaType.image)
return;
context.pushTransparentRoute(
AttachmentZoomView(
data: widget.data.where((ele) => ele != null).cast(),
@ -100,8 +111,10 @@ class _AttachmentListState extends State<AttachmentList> {
);
}
final fullOfImage =
widget.data.where((ele) => ele?.mediaType == SnMediaType.image).length == widget.data.length;
final fullOfImage = widget.data
.where((ele) => ele?.mediaType == SnMediaType.image)
.length ==
widget.data.length;
if (widget.gridded && fullOfImage) {
return Container(
@ -117,29 +130,36 @@ class _AttachmentListState extends State<AttachmentList> {
crossAxisCount: math.min(widget.data.length, 2),
crossAxisSpacing: 4,
mainAxisSpacing: 4,
children:
widget.data
.mapIndexed(
(idx, ele) => GestureDetector(
child: Container(
constraints: constraints,
child: AttachmentItem(data: ele, heroTag: heroTags[idx], fit: BoxFit.cover),
),
onTap: () {
if (widget.data[idx]!.mediaType != SnMediaType.image) return;
context.pushTransparentRoute(
AttachmentZoomView(
data: widget.data.where((ele) => ele != null).cast(),
initialIndex: idx,
heroTags: heroTags,
),
backgroundColor: Colors.black.withOpacity(0.7),
rootNavigator: true,
);
},
children: widget.data
.mapIndexed(
(idx, ele) => GestureDetector(
child: Container(
constraints: constraints,
child: AttachmentItem(
data: ele,
heroTag: heroTags[idx],
fit: BoxFit.cover,
filterQuality: widget.filterQuality,
),
)
.toList(),
),
onTap: () {
if (widget.data[idx]!.mediaType != SnMediaType.image)
return;
context.pushTransparentRoute(
AttachmentZoomView(
data: widget.data
.where((ele) => ele != null)
.cast(),
initialIndex: idx,
heroTags: heroTags,
),
backgroundColor: Colors.black.withOpacity(0.7),
rootNavigator: true,
);
},
),
)
.toList(),
),
),
);
@ -156,22 +176,26 @@ class _AttachmentListState extends State<AttachmentList> {
child: ClipRRect(
borderRadius: AttachmentList.kDefaultRadius,
child: Column(
children:
widget.data
.mapIndexed(
(idx, ele) => GestureDetector(
child: AspectRatio(
aspectRatio: ele?.data['ratio']?.toDouble() ?? 1,
child: Container(
constraints: constraints,
child: AttachmentItem(data: ele, heroTag: heroTags[idx], fit: BoxFit.cover),
),
children: widget.data
.mapIndexed(
(idx, ele) => GestureDetector(
child: AspectRatio(
aspectRatio: ele?.data['ratio']?.toDouble() ?? 1,
child: Container(
constraints: constraints,
child: AttachmentItem(
data: ele,
heroTag: heroTags[idx],
fit: BoxFit.cover,
filterQuality: widget.filterQuality,
),
),
)
.expand((ele) => [ele, const Divider(height: 1)])
.toList()
..removeLast(),
),
),
)
.expand((ele) => [ele, const Divider(height: 1)])
.toList()
..removeLast(),
),
),
);
@ -189,16 +213,22 @@ class _AttachmentListState extends State<AttachmentList> {
itemCount: widget.data.length,
itemBuilder: (context, idx) {
return Container(
constraints: constraints.copyWith(maxWidth: widget.maxWidth),
constraints:
constraints.copyWith(maxWidth: widget.maxWidth),
child: AspectRatio(
aspectRatio: (widget.data[idx]?.data['ratio'] ?? 1).toDouble(),
aspectRatio:
(widget.data[idx]?.data['ratio'] ?? 1).toDouble(),
child: GestureDetector(
onTap: () {
if (widget.data[idx]?.mediaType != SnMediaType.image) return;
if (widget.data[idx]?.mediaType != SnMediaType.image)
return;
context.pushTransparentRoute(
AttachmentZoomView(
data:
widget.data.where((ele) => ele != null && ele.mediaType == SnMediaType.image).cast(),
data: widget.data
.where((ele) =>
ele != null &&
ele.mediaType == SnMediaType.image)
.cast(),
initialIndex: idx,
heroTags: heroTags,
),
@ -212,18 +242,25 @@ class _AttachmentListState extends State<AttachmentList> {
Container(
decoration: BoxDecoration(
color: backgroundColor,
border: Border(top: borderSide, bottom: borderSide),
border:
Border(top: borderSide, bottom: borderSide),
borderRadius: AttachmentList.kDefaultRadius,
),
child: ClipRRect(
borderRadius: AttachmentList.kDefaultRadius,
child: AttachmentItem(data: widget.data[idx], heroTag: heroTags[idx]),
child: AttachmentItem(
data: widget.data[idx],
heroTag: heroTags[idx],
filterQuality: widget.filterQuality,
),
),
),
Positioned(
right: 8,
bottom: 8,
child: Chip(label: Text('${idx + 1}/${widget.data.length}')),
child: Chip(
label:
Text('${idx + 1}/${widget.data.length}')),
),
],
),
@ -245,5 +282,6 @@ class _AttachmentListState extends State<AttachmentList> {
class _AttachmentListScrollBehavior extends MaterialScrollBehavior {
@override
Set<PointerDeviceKind> get dragDevices => {PointerDeviceKind.touch, PointerDeviceKind.mouse};
Set<PointerDeviceKind> get dragDevices =>
{PointerDeviceKind.touch, PointerDeviceKind.mouse};
}

View File

@ -149,7 +149,6 @@ class PostItem extends StatelessWidget {
void _doShareViaPicture(BuildContext context) async {
final box = context.findRenderObject() as RenderBox?;
context.showSnackbar('postSharingViaPicture'.tr());
final controller = ScreenshotController();
final capturedImage = await controller.captureFromLongWidget(
@ -160,9 +159,9 @@ class PostItem extends StatelessWidget {
child: Material(
child: MultiProvider(
providers: [
// Create a copy of environments
Provider<SnNetworkProvider>(create: (_) => context.read()),
ChangeNotifierProvider<ConfigProvider>(
create: (_) => context.read()),
Provider<UserDirectoryProvider>(create: (_) => context.read()),
],
child: ResponsiveBreakpoints.builder(
breakpoints: ResponsiveBreakpoints.of(context).breakpoints,
@ -507,6 +506,8 @@ class PostShareImageWidget extends StatelessWidget {
StyledWidget(AttachmentList(
data: data.preload!.attachments!,
columned: true,
fit: BoxFit.contain,
filterQuality: FilterQuality.high,
)).padding(horizontal: 16, bottom: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,

View File

@ -34,11 +34,14 @@ class UniversalImage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
final double? resizeHeight = cacheHeight != null ? (cacheHeight! * devicePixelRatio) : null;
final double? resizeWidth = cacheWidth != null ? (cacheWidth! * devicePixelRatio) : null;
final double? resizeHeight =
cacheHeight != null ? (cacheHeight! * devicePixelRatio) : null;
final double? resizeWidth =
cacheWidth != null ? (cacheWidth! * devicePixelRatio) : null;
return Image(
filterQuality: filterQuality ?? context.read<ConfigProvider>().imageQuality,
filterQuality:
filterQuality ?? context.read<ConfigProvider>().imageQuality,
image: kIsWeb
? UniversalImage.provider(url)
: ResizeImage(
@ -52,7 +55,8 @@ class UniversalImage extends StatelessWidget {
fit: fit,
loadingBuilder: noProgressIndicator
? null
: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
: (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) return child;
return Container(
constraints: BoxConstraints(maxHeight: 80),
@ -61,12 +65,15 @@ class UniversalImage extends StatelessWidget {
tween: Tween(
begin: 0,
end: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: 0,
),
duration: const Duration(milliseconds: 300),
builder: (context, value, _) => CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null ? value.toDouble() : null,
value: loadingProgress.expectedTotalBytes != null
? value.toDouble()
: null,
),
),
),
@ -114,6 +121,7 @@ class AutoResizeUniversalImage extends StatelessWidget {
final BoxFit? fit;
final bool noProgressIndicator;
final bool noErrorWidget;
final FilterQuality? filterQuality;
const AutoResizeUniversalImage(
this.url, {
@ -123,6 +131,7 @@ class AutoResizeUniversalImage extends StatelessWidget {
this.fit,
this.noProgressIndicator = false,
this.noErrorWidget = false,
this.filterQuality,
});
@override
@ -137,6 +146,7 @@ class AutoResizeUniversalImage extends StatelessWidget {
noErrorWidget: noErrorWidget,
cacheHeight: constraints.maxHeight,
cacheWidth: constraints.maxWidth,
filterQuality: filterQuality,
);
});
}