diff --git a/lib/main.dart b/lib/main.dart index 3d1d5d26..34324b73 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -31,6 +31,7 @@ import "package:moxxyv2/ui/bloc/profile_bloc.dart"; import "package:moxxyv2/ui/bloc/preferences_bloc.dart"; import "package:moxxyv2/ui/bloc/addcontact_bloc.dart"; import "package:moxxyv2/ui/service/download.dart"; +import "package:moxxyv2/ui/service/data.dart"; import "package:moxxyv2/service/service.dart"; import "package:moxxyv2/shared/commands.dart"; import "package:moxxyv2/shared/events.dart"; @@ -51,8 +52,10 @@ void setupLogging() { GetIt.I.registerSingleton(Logger("MoxxyMain")); } -void setupUIServices() { +Future setupUIServices() async { GetIt.I.registerSingleton(UIDownloadService()); + GetIt.I.registerSingleton(UIDataService()); + await GetIt.I.get().init(); } void setupBlocs(GlobalKey navKey) { @@ -71,7 +74,7 @@ void setupBlocs(GlobalKey navKey) { // TODO: Theme the switches void main() async { setupLogging(); - setupUIServices(); + await setupUIServices(); await initializeServiceIfNeeded(); setupEventHandler(); diff --git a/lib/shared/helpers.dart b/lib/shared/helpers.dart index 3c0525b3..1972d9fd 100644 --- a/lib/shared/helpers.dart +++ b/lib/shared/helpers.dart @@ -144,6 +144,7 @@ String? guessMimeTypeFromExtension(String ext) { case "jpg": case "jpeg": return "image/jpeg"; case "webp": return "image/webp"; + case "mp4": return "video/mp4"; } return null; diff --git a/lib/ui/service/data.dart b/lib/ui/service/data.dart new file mode 100644 index 00000000..11df139f --- /dev/null +++ b/lib/ui/service/data.dart @@ -0,0 +1,19 @@ +import "package:flutter/material.dart"; +import "package:external_path/external_path.dart"; +import "package:path/path.dart" as pathlib; + +class UIDataService { + late String _thumbnailBase; + + UIDataService(); + + Future init() async { + WidgetsFlutterBinding.ensureInitialized(); + + final base = await ExternalPath.getExternalStoragePublicDirectory(ExternalPath.DIRECTORY_PICTURES); + _thumbnailBase = pathlib.join(base, "Moxxy", ".thumbnail"); + } + + // The base path for thumbnails + String get thumbnailBase => _thumbnailBase; +} diff --git a/lib/ui/widgets/chat/media/video.dart b/lib/ui/widgets/chat/media/video.dart index bc738552..87c9afa4 100644 --- a/lib/ui/widgets/chat/media/video.dart +++ b/lib/ui/widgets/chat/media/video.dart @@ -1,15 +1,17 @@ import "dart:io"; import "package:moxxyv2/shared/models/message.dart"; +import "package:moxxyv2/ui/service/data.dart"; import "package:moxxyv2/ui/widgets/chat/bottom.dart"; import "package:moxxyv2/ui/widgets/chat/blurhash.dart"; import "package:moxxyv2/ui/widgets/chat/playbutton.dart"; import "package:moxxyv2/ui/widgets/chat/helpers.dart"; import "package:moxxyv2/ui/widgets/chat/media/image.dart"; +import "package:moxxyv2/ui/widgets/chat/media/file.dart"; import "package:flutter/material.dart"; import "package:path/path.dart" as pathlib; -import "package:external_path/external_path.dart"; +import "package:get_it/get_it.dart"; import "package:video_compress/video_compress.dart"; import "package:open_file/open_file.dart"; @@ -45,41 +47,92 @@ class _VideoChatWidgetState extends State { final double maxWidth; final Message message; - String _thumbnailPath; - bool _hasThumbnail; - _VideoChatWidgetState( this.message, this.maxWidth, this.timestamp, this.radius, - ) : _thumbnailPath = "", _hasThumbnail = true; + ); - Future _getThumbnailPath() async { - final base = await ExternalPath.getExternalStoragePublicDirectory(ExternalPath.DIRECTORY_PICTURES); - return pathlib.join(base, "Moxxy", ".thumbnail", message.conversationJid, pathlib.basename(message.mediaUrl!)); + /// Returns the path of a possible thumbnail for the video. Does not imply that the file + /// exists. + String _getThumbnailPath() { + final base = GetIt.I.get().thumbnailBase; + return pathlib.join(base, message.conversationJid, pathlib.basename(message.mediaUrl!)); } + /// Generate the thumbnail if needed. + Future _thumbnailFuture() async { + final thumbnailFile = File(_getThumbnailPath()); + if (await thumbnailFile.exists()) { + return true; + } + + // Thumbnail does not exist + final sourceFile = File(message.mediaUrl!); + if (await sourceFile.exists()) { + final bytes = await VideoCompress.getByteThumbnail( + sourceFile.path, + quality: 75 + ); + await thumbnailFile.writeAsBytes(bytes!); + + return true; + } + + // Source file also does not exist. Return "error". + return false; + } + Widget _buildNonDownloaded() { // TODO - return const SizedBox(); + if (message.thumbnailData != null) {} + + return FileChatWidget( + message, + timestamp, + extra: ElevatedButton( + // TODO + onPressed: () {}, + child: const Text("Download") + ) + ); } Widget _buildDownloading() { // TODO - return const SizedBox(); + if (message.thumbnailData != null) {} + + return FileChatWidget( + message, + timestamp, + ); } Widget _buildVideo() { - return ImageBaseChatWidget( - message.mediaUrl!, - radius, - Image.file(File(message.mediaUrl!)), - MessageBubbleBottom( - message, - timestamp: timestamp, - ), - extra: const PlayButton() + return FutureBuilder( + future: _thumbnailFuture(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data!) { + return ImageBaseChatWidget( + message.mediaUrl!, + radius, + Image.file(File(_getThumbnailPath())), + MessageBubbleBottom( + message, + timestamp: timestamp, + ), + extra: const PlayButton() + ); + } else { + // TODO: Error + return const Text("Error"); + } + } else { + return const CircularProgressIndicator(); + } + } ); }