💄 Animated image gates

This commit is contained in:
2026-01-10 23:20:16 +08:00
parent 2fd93246c7
commit c93b543da9
2 changed files with 67 additions and 71 deletions

View File

@@ -244,22 +244,20 @@ class CloudFileList extends HookConsumerWidget {
minWidth: minWidth ?? 0, minWidth: minWidth ?? 0,
maxWidth: files.length == 1 ? maxWidth : double.infinity, maxWidth: files.length == 1 ? maxWidth : double.infinity,
), ),
child: child: (ratio == null && isImage)
(ratio == null && isImage) ? IntrinsicWidth(child: IntrinsicHeight(child: widgetItem))
? IntrinsicWidth(child: IntrinsicHeight(child: widgetItem)) : (ratio == null && isAudio)
: (ratio == null && isAudio) ? IntrinsicHeight(child: widgetItem)
? IntrinsicHeight(child: widgetItem) : AspectRatio(
: AspectRatio( aspectRatio: ratio?.toDouble() ?? 1,
aspectRatio: ratio?.toDouble() ?? 1, child: widgetItem,
child: widgetItem, ),
),
); );
} }
final allImages = final allImages = !files.any(
!files.any( (e) => e.mimeType == null || !e.mimeType!.startsWith('image'),
(e) => e.mimeType == null || !e.mimeType!.startsWith('image'), );
);
if (allImages) { if (allImages) {
return ConstrainedBox( return ConstrainedBox(
@@ -270,10 +268,9 @@ class CloudFileList extends HookConsumerWidget {
padding: padding ?? EdgeInsets.zero, padding: padding ?? EdgeInsets.zero,
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
final availableWidth = final availableWidth = constraints.maxWidth.isFinite
constraints.maxWidth.isFinite ? constraints.maxWidth
? constraints.maxWidth : MediaQuery.of(context).size.width;
: MediaQuery.of(context).size.width;
final itemExtent = math.min( final itemExtent = math.min(
math.min(availableWidth * 0.75, maxWidth * 0.75).toDouble(), math.min(availableWidth * 0.75, maxWidth * 0.75).toDouble(),
640.0, 640.0,
@@ -339,10 +336,9 @@ class CloudFileList extends HookConsumerWidget {
padding: padding, padding: padding,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return AspectRatio( return AspectRatio(
aspectRatio: aspectRatio: files[index].fileMeta?['ratio'] is num
files[index].fileMeta?['ratio'] is num ? files[index].fileMeta!['ratio'].toDouble()
? files[index].fileMeta!['ratio'].toDouble() : 1.0,
: 1.0,
child: Stack( child: Stack(
children: [ children: [
ClipRRect( ClipRRect(
@@ -440,40 +436,68 @@ class _CloudFileListEntry extends HookConsumerWidget {
} }
final bool fullyUnlocked = !lockedByDS && !lockedByMature; final bool fullyUnlocked = !lockedByDS && !lockedByMature;
Widget fg = Widget fg = fullyUnlocked
fullyUnlocked ? (isImage
? (isImage ? CloudFileWidget(
? CloudFileWidget(
item: file, item: file,
heroTag: heroTag, heroTag: heroTag,
noBlurhash: true, noBlurhash: true,
fit: fit, fit: fit,
useInternalGate: false, useInternalGate: false,
) )
: CloudFileWidget( : CloudFileWidget(
item: file, item: file,
heroTag: heroTag, heroTag: heroTag,
fit: fit, fit: fit,
useInternalGate: false, useInternalGate: false,
)) ))
: const SizedBox.shrink(); : const SizedBox.shrink();
Widget overlays; Widget overlays = AnimatedSwitcher(
if (lockedByDS) { duration: const Duration(milliseconds: 300),
overlays = _DataSavingOverlay(); child: lockedByDS
} else if (file.sensitiveMarks.isNotEmpty) { ? _DataSavingOverlay(key: const ValueKey('ds'))
overlays = _SensitiveOverlay( : (file.sensitiveMarks.isNotEmpty && !showMature.value
file: file, ? _SensitiveOverlay(
isRevealed: showMature.value, key: const ValueKey('sensitive-blur'),
onHide: () => showMature.value = false, file: file,
)
: const SizedBox.shrink(key: ValueKey('none'))),
);
Widget hideButton = const SizedBox.shrink();
if (file.sensitiveMarks.isNotEmpty && showMature.value) {
hideButton = Positioned(
top: 3,
left: 4,
child: IconButton(
iconSize: 16,
constraints: const BoxConstraints(),
icon: const Icon(
Icons.visibility_off,
color: Colors.white,
shadows: [
Shadow(
color: Colors.black,
blurRadius: 5.0,
offset: Offset(1.0, 1.0),
),
],
),
tooltip: 'Blur content',
onPressed: () => showMature.value = false,
),
); );
} else {
overlays = const SizedBox.shrink();
} }
final content = Stack( final content = Stack(
fit: StackFit.expand, fit: StackFit.expand,
children: [if (isImage) Positioned.fill(child: bg), fg, overlays], children: [
if (isImage) Positioned.fill(child: bg),
fg,
overlays,
hideButton,
],
); );
return InkWell( return InkWell(
@@ -494,41 +518,11 @@ class _CloudFileListEntry extends HookConsumerWidget {
class _SensitiveOverlay extends StatelessWidget { class _SensitiveOverlay extends StatelessWidget {
final SnCloudFile file; final SnCloudFile file;
final VoidCallback? onHide;
final bool isRevealed;
const _SensitiveOverlay({ const _SensitiveOverlay({required this.file, super.key});
required this.file,
this.onHide,
this.isRevealed = false,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (isRevealed) {
return Positioned(
top: 3,
left: 4,
child: IconButton(
iconSize: 16,
constraints: const BoxConstraints(),
icon: const Icon(
Icons.visibility_off,
color: Colors.white,
shadows: [
Shadow(
color: Colors.black,
blurRadius: 5.0,
offset: Offset(1.0, 1.0),
),
],
),
tooltip: 'Blur content',
onPressed: onHide,
),
);
}
return BackdropFilter( return BackdropFilter(
filter: ImageFilter.blur(sigmaX: 64, sigmaY: 64), filter: ImageFilter.blur(sigmaX: 64, sigmaY: 64),
child: Container( child: Container(
@@ -549,6 +543,8 @@ class _SensitiveOverlay extends StatelessWidget {
} }
class _DataSavingOverlay extends StatelessWidget { class _DataSavingOverlay extends StatelessWidget {
const _DataSavingOverlay({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ColoredBox( return ColoredBox(

View File

@@ -2,11 +2,11 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/pods/config.dart'; import 'package:island/pods/config.dart';
typedef WidgetBuilder0 = Widget Function(); typedef WidgetBuilder = Widget Function();
class DataSavingGate extends ConsumerWidget { class DataSavingGate extends ConsumerWidget {
final bool bypass; final bool bypass;
final WidgetBuilder0 content; final WidgetBuilder content;
final Widget placeholder; final Widget placeholder;
const DataSavingGate({ const DataSavingGate({