improved unknown file preview handling with probe failure fallbacks and switched to an explicit TabController in the share view to prevent build-time layout issues
This commit is contained in:
+47
-24
@@ -508,8 +508,6 @@ class _FileViewerState extends State<FileViewer> {
|
||||
Widget _buildUnknownPlaceholder() {
|
||||
final theme = Theme.of(context);
|
||||
final descriptors = _availableActions();
|
||||
final remote = widget.remoteFile;
|
||||
final hasPreview = remote?.hasPreview == true;
|
||||
return ListView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24),
|
||||
children: [
|
||||
@@ -517,16 +515,8 @@ class _FileViewerState extends State<FileViewer> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Column(
|
||||
children: [
|
||||
_UnknownPreviewHeader(remoteFile: remote),
|
||||
_UnknownPreviewBlock(remoteFile: widget.remoteFile),
|
||||
const SizedBox(height: 16),
|
||||
if (!hasPreview) ...[
|
||||
Text(
|
||||
'Vorschau nicht verfügbar',
|
||||
style: theme.textTheme.titleMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
],
|
||||
Text(
|
||||
widget.path.split('/').last,
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
@@ -607,24 +597,48 @@ class _TextPayload {
|
||||
const _TextPayload({required this.content, required this.truncated});
|
||||
}
|
||||
|
||||
/// Header for the "Vorschau nicht verfügbar" screen: tries to fetch the
|
||||
/// Nextcloud thumbnail and shows it mid-sized if available, otherwise
|
||||
/// falls back to the generic file icon.
|
||||
class _UnknownPreviewHeader extends StatelessWidget {
|
||||
/// Header block for the "Vorschau nicht verfügbar" screen.
|
||||
///
|
||||
/// Two visual modes — kept layout-equivalent so the screen looks identical
|
||||
/// whether the server already said "no preview" or the probe failed late:
|
||||
/// * **No preview available** (server said no, no remoteFile, or probe
|
||||
/// errored): compact "file icon + 'Vorschau nicht verfügbar' text".
|
||||
/// * **Preview rendering / loaded**: mid-sized thumbnail without text.
|
||||
class _UnknownPreviewBlock extends StatefulWidget {
|
||||
final RemoteFileRef? remoteFile;
|
||||
const _UnknownPreviewHeader({required this.remoteFile});
|
||||
const _UnknownPreviewBlock({required this.remoteFile});
|
||||
|
||||
@override
|
||||
State<_UnknownPreviewBlock> createState() => _UnknownPreviewBlockState();
|
||||
}
|
||||
|
||||
class _UnknownPreviewBlockState extends State<_UnknownPreviewBlock> {
|
||||
static const double _previewSize = 180;
|
||||
bool _failed = false;
|
||||
|
||||
Widget _fallbackIcon() =>
|
||||
const Icon(Icons.insert_drive_file_outlined, size: 60);
|
||||
Widget _compact(ThemeData theme) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.insert_drive_file_outlined, size: 60),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Vorschau nicht verfügbar',
|
||||
style: theme.textTheme.titleMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final remote = remoteFile;
|
||||
// Skip the probe outright when the server already told us there is no
|
||||
// preview — saves an HTTP request that would 404 anyway.
|
||||
if (remote == null || remote.hasPreview == false) return _fallbackIcon();
|
||||
final theme = Theme.of(context);
|
||||
final remote = widget.remoteFile;
|
||||
final canProbe =
|
||||
remote != null &&
|
||||
remote.hasPreview != false &&
|
||||
remote.fileId != null &&
|
||||
!_failed;
|
||||
if (!canProbe) return _compact(theme);
|
||||
return SizedBox(
|
||||
width: _previewSize,
|
||||
height: _previewSize,
|
||||
@@ -633,10 +647,19 @@ class _UnknownPreviewHeader extends StatelessWidget {
|
||||
imageUrl: _ncPreviewUrl(remote, width: 360),
|
||||
fadeInDuration: Duration.zero,
|
||||
fadeOutDuration: Duration.zero,
|
||||
errorListener: (_) {},
|
||||
// Late probe failure: re-render into the compact layout so the
|
||||
// screen doesn't keep a 180×180 box around a tiny icon. Deferred
|
||||
// to the next frame because setState during build is illegal.
|
||||
errorListener: (_) {
|
||||
if (!mounted) return;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) setState(() => _failed = true);
|
||||
});
|
||||
},
|
||||
placeholder: (_, _) =>
|
||||
const Center(child: AppProgressIndicator.large()),
|
||||
errorWidget: (_, _, _) => Center(child: _fallbackIcon()),
|
||||
// Briefly empty while the post-frame setState swaps layouts.
|
||||
errorWidget: (_, _, _) => const SizedBox.shrink(),
|
||||
imageBuilder: (_, imageProvider) => ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Image(
|
||||
|
||||
Reference in New Issue
Block a user