added a swipeable media gallery for chat and folder images and videos with server previews, on-demand originals and zoom, and marked document previews in chat so they no longer pass for photos
This commit is contained in:
1 parent
952f916231
commit
aa176c90e3
22 files changed
+1658
-103
No files matched your search
@@ -58,6 +58,29 @@ const Set<String> _textExtensions = {
|
||||
'srt', 'vtt',
|
||||
};
|
||||
|
||||
/// Further raster formats the gallery shows. They decode via the platform
|
||||
/// codecs (HEIC/AVIF) or are JPEG/ICO under another name. SVG and TIFF stay
|
||||
/// out: without a server preview only the file viewer can render them.
|
||||
const Set<String> _galleryOnlyImageExtensions = {
|
||||
'heic',
|
||||
'heif',
|
||||
'avif',
|
||||
'jfif',
|
||||
'jpe',
|
||||
'ico',
|
||||
};
|
||||
|
||||
/// Extension-only check (no content sniff) for files the media gallery can
|
||||
/// page through — the fallback when no MIME type is known.
|
||||
bool isGalleryImageName(String name) {
|
||||
final ext = name.split('.').last.toLowerCase();
|
||||
return _imageExtensions.contains(ext) ||
|
||||
_galleryOnlyImageExtensions.contains(ext);
|
||||
}
|
||||
|
||||
bool isGalleryVideoName(String name) =>
|
||||
_videoExtensions.contains(name.split('.').last.toLowerCase());
|
||||
|
||||
/// Detects the [FileKind] of the file at [path] from its extension, falling
|
||||
/// back to an 8 KB content sniff ([_looksLikeText]) for unknown extensions.
|
||||
Future<FileKind> detectFileKind(String path) async {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
import '../async_action_button.dart';
|
||||
import '../info_dialog.dart';
|
||||
import '../share_position_origin.dart';
|
||||
|
||||
/// "Teilen"/"Speichern" for a file that is on disk, shared by the file viewer
|
||||
/// and the media gallery.
|
||||
Future<void> shareLocalFile(BuildContext context, String path) =>
|
||||
runWithErrorDialog(
|
||||
context,
|
||||
() => SharePlus.instance.share(
|
||||
ShareParams(
|
||||
files: [XFile(path)],
|
||||
sharePositionOrigin: SharePositionOrigin.get(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> saveLocalFile(BuildContext context, String path) async {
|
||||
try {
|
||||
final source = File(path);
|
||||
final size = await source.length();
|
||||
// file_picker has no path/stream save API, so the whole file
|
||||
// gets loaded into RAM. Cap big media; user falls back to share.
|
||||
const maxBytes = 200 * 1024 * 1024;
|
||||
if (size > maxBytes) {
|
||||
if (!context.mounted) return;
|
||||
InfoDialog.show(
|
||||
context,
|
||||
'Diese Datei ist zu groß (${(size / (1024 * 1024)).toStringAsFixed(0)} MB), '
|
||||
'um direkt gespeichert zu werden. Nutze stattdessen die Teilen-Funktion.',
|
||||
title: 'Speichern nicht möglich',
|
||||
);
|
||||
return;
|
||||
}
|
||||
final bytes = await source.readAsBytes();
|
||||
final saved = await FilePicker.saveFile(
|
||||
fileName: path.split('/').last,
|
||||
bytes: bytes,
|
||||
);
|
||||
if (!context.mounted) return;
|
||||
if (saved != null) {
|
||||
InfoDialog.show(context, 'Datei gespeichert.');
|
||||
}
|
||||
} on Object catch (e) {
|
||||
if (!context.mounted) return;
|
||||
InfoDialog.show(
|
||||
context,
|
||||
'Speichern fehlgeschlagen: $e',
|
||||
copyable: true,
|
||||
title: 'Fehler',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,13 @@ class MediaPlayer extends StatefulWidget {
|
||||
final String path;
|
||||
final bool isAudio;
|
||||
final String? filename;
|
||||
final bool autoPlay;
|
||||
const MediaPlayer({
|
||||
super.key,
|
||||
required this.path,
|
||||
required this.isAudio,
|
||||
this.filename,
|
||||
this.autoPlay = false,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -57,7 +59,7 @@ class _MediaPlayerState extends State<MediaPlayer> {
|
||||
_video = controller;
|
||||
_chewie = ChewieController(
|
||||
videoPlayerController: controller,
|
||||
autoPlay: false,
|
||||
autoPlay: widget.autoPlay,
|
||||
looping: false,
|
||||
allowFullScreen: true,
|
||||
allowPlaybackSpeedChanging: true,
|
||||
|
||||
Reference in new issue
Block a user