Compare commits
3 Commits
8c12eb47ce
...
b004d8364c
Author | SHA1 | Date | |
---|---|---|---|
b004d8364c | |||
2498e23bd5 | |||
3a80d50cf5 |
@ -445,8 +445,23 @@ class HttpFileTransferService {
|
||||
mediaWidth = imageSize?.width.toInt();
|
||||
mediaHeight = imageSize?.height.toInt();
|
||||
} else if (mime.startsWith('video/')) {
|
||||
// TODO(Unknown): Also figure out the thumbnail size here
|
||||
MoxplatformPlugin.media.scanFile(downloadedPath);
|
||||
|
||||
/*
|
||||
// Generate thumbnail
|
||||
final thumbnailPath = await getVideoThumbnailPath(
|
||||
downloadedPath,
|
||||
job.conversationJid,
|
||||
);
|
||||
|
||||
// Find out the dimensions
|
||||
final imageSize = await getImageSizeFromPath(thumbnailPath);
|
||||
if (imageSize == null) {
|
||||
_log.warning('Failed to get image size for $downloadedPath ($thumbnailPath)');
|
||||
}
|
||||
|
||||
mediaWidth = imageSize?.width.toInt();
|
||||
mediaHeight = imageSize?.height.toInt();*/
|
||||
} else if (mime.startsWith('audio/')) {
|
||||
MoxplatformPlugin.media.scanFile(downloadedPath);
|
||||
}
|
||||
|
@ -4,7 +4,10 @@ import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
import 'package:moxxyv2/i18n/strings.g.dart';
|
||||
import 'package:moxxyv2/shared/models/message.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:synchronized/synchronized.dart';
|
||||
import 'package:video_thumbnail/video_thumbnail.dart';
|
||||
|
||||
/// Add a leading zero, if required, to ensure that an integer is rendered
|
||||
/// as a two "digit" string.
|
||||
@ -348,3 +351,36 @@ Future<Size?> getImageSizeFromData(Uint8List bytes) async {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a thumbnail file (JPEG) for the video at [path]. [conversationJid] refers
|
||||
/// to the JID of the conversation the file comes from.
|
||||
/// If the thumbnail already exists, then just its path is returned. If not, then
|
||||
/// it gets generated first.
|
||||
Future<String> getVideoThumbnailPath(String path, String conversationJid) async {
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final thumbnailFilenameNoExtension = p.withoutExtension(
|
||||
p.basename(path),
|
||||
);
|
||||
final thumbnailFilename = '$thumbnailFilenameNoExtension.jpg';
|
||||
final thumbnailDirectory = p.join(
|
||||
tempDir.path,
|
||||
'thumbnails',
|
||||
conversationJid,
|
||||
);
|
||||
final thumbnailPath = p.join(thumbnailDirectory, thumbnailFilename);
|
||||
|
||||
final dir = Directory(thumbnailDirectory);
|
||||
if (!dir.existsSync()) await dir.create(recursive: true);
|
||||
final file = File(thumbnailPath);
|
||||
if (file.existsSync()) return thumbnailPath;
|
||||
|
||||
final r = await VideoThumbnail.thumbnailFile(
|
||||
video: path,
|
||||
thumbnailPath: thumbnailDirectory,
|
||||
imageFormat: ImageFormat.JPEG,
|
||||
quality: 75,
|
||||
);
|
||||
assert(r == thumbnailPath, 'The generated video thumbnail has a different path than we expected: $r vs. $thumbnailPath');
|
||||
|
||||
return thumbnailPath;
|
||||
}
|
||||
|
@ -63,7 +63,9 @@ class SendFilesPage extends StatelessWidget {
|
||||
padding: const EdgeInsets.only(right: 4),
|
||||
child: SharedVideoWidget(
|
||||
path,
|
||||
() {
|
||||
// TODO(PapaTutuWawa): Fix
|
||||
'sendfiles',
|
||||
onTap: () {
|
||||
if (selected) {
|
||||
// The trash can icon has been tapped
|
||||
context.read<SendFilesBloc>().add(
|
||||
|
@ -5,7 +5,6 @@ import 'package:moxxyv2/ui/widgets/topbar.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
// TODO(PapaTutuWawa): Include license text
|
||||
// TODO(Unknown): Maybe include the version number
|
||||
class SettingsAboutPage extends StatelessWidget {
|
||||
const SettingsAboutPage({ super.key });
|
||||
|
||||
|
@ -193,8 +193,9 @@ Widget buildSharedMediaWidget(SharedMedium medium, String conversationJid) {
|
||||
} else if (medium.mime!.startsWith('video/')) {
|
||||
return SharedVideoWidget(
|
||||
medium.path,
|
||||
() => OpenFile.open(medium.path),
|
||||
child: const PlayButton(),
|
||||
conversationJid,
|
||||
onTap: () => OpenFile.open(medium.path),
|
||||
child: const PlayButton(size: 32),
|
||||
);
|
||||
}
|
||||
// TODO(Unknown): Audio
|
||||
|
@ -30,19 +30,23 @@ class VideoChatWidget extends StatelessWidget {
|
||||
final bool sent;
|
||||
|
||||
Widget _buildUploading() {
|
||||
// TODO(PapaTutuWawa): Fix
|
||||
return MediaBaseChatWidget(
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(32),
|
||||
child: Icon(
|
||||
Icons.error_outline,
|
||||
size: 32,
|
||||
),
|
||||
FutureBuilder<String>(
|
||||
future: getVideoThumbnailPath(message.mediaUrl!, message.conversationJid),
|
||||
builder: (context, snapshot) {
|
||||
Widget widget;
|
||||
if (snapshot.hasData) {
|
||||
widget = Image.file(File(snapshot.data!));
|
||||
} else {
|
||||
widget = const Padding(
|
||||
padding: EdgeInsets.all(32),
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
return widget;
|
||||
},
|
||||
),
|
||||
/*VideoThumbnailWidget(
|
||||
message.mediaUrl!,
|
||||
Image.memory,
|
||||
),*/
|
||||
MessageBubbleBottom(message, sent),
|
||||
radius,
|
||||
extra: ProgressWidget(id: message.id),
|
||||
@ -81,19 +85,23 @@ class VideoChatWidget extends StatelessWidget {
|
||||
|
||||
/// The video exists locally
|
||||
Widget _buildVideo() {
|
||||
// TODO(PapaTutuWawa): Fix
|
||||
return MediaBaseChatWidget(
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(32),
|
||||
child: Icon(
|
||||
Icons.error_outline,
|
||||
size: 32,
|
||||
),
|
||||
FutureBuilder<String>(
|
||||
future: getVideoThumbnailPath(message.mediaUrl!, message.conversationJid),
|
||||
builder: (context, snapshot) {
|
||||
Widget widget;
|
||||
if (snapshot.hasData) {
|
||||
widget = Image.file(File(snapshot.data!));
|
||||
} else {
|
||||
widget = const Padding(
|
||||
padding: EdgeInsets.all(32),
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
return widget;
|
||||
},
|
||||
),
|
||||
/*VideoThumbnailWidget(
|
||||
message.mediaUrl!,
|
||||
Image.memory,
|
||||
),*/
|
||||
MessageBubbleBottom(message, sent),
|
||||
radius,
|
||||
onTap: () {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:decorated_icon/decorated_icon.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PlayButton extends StatelessWidget {
|
||||
@ -9,17 +10,11 @@ class PlayButton extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DecoratedBox(
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.black45,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Icon(
|
||||
Icons.play_arrow,
|
||||
size: size,
|
||||
),
|
||||
return Align(
|
||||
child: DecoratedIcon(
|
||||
Icons.play_arrow,
|
||||
shadows: const [ BoxShadow(blurRadius: 16) ],
|
||||
size: size,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -1,20 +1,55 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:moxxyv2/shared/helpers.dart';
|
||||
import 'package:moxxyv2/ui/widgets/chat/shared/base.dart';
|
||||
|
||||
class SharedVideoWidget extends StatelessWidget {
|
||||
const SharedVideoWidget(this.path, this.onTap, { this.borderColor, this.child, super.key });
|
||||
const SharedVideoWidget(
|
||||
this.path,
|
||||
this.conversationJid, {
|
||||
this.onTap,
|
||||
this.borderColor,
|
||||
this.child,
|
||||
this.size = sharedMediaContainerDimension,
|
||||
this.borderRadius = 10,
|
||||
super.key,
|
||||
}
|
||||
);
|
||||
final String path;
|
||||
final String conversationJid;
|
||||
final Color? borderColor;
|
||||
final void Function() onTap;
|
||||
final void Function()? onTap;
|
||||
final Widget? child;
|
||||
final double size;
|
||||
final double borderRadius;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SharedMediaContainer(
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(32),
|
||||
child: CircularProgressIndicator(),
|
||||
FutureBuilder<String>(
|
||||
future: getVideoThumbnailPath(path, conversationJid),
|
||||
builder: (context, snapshot) {
|
||||
Widget widget;
|
||||
if (snapshot.hasData) {
|
||||
widget = Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(borderRadius),
|
||||
image: DecorationImage(
|
||||
fit: BoxFit.cover,
|
||||
image: FileImage(File(snapshot.data!)),
|
||||
),
|
||||
),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: child,
|
||||
);
|
||||
} else {
|
||||
widget = const CircularProgressIndicator();
|
||||
}
|
||||
|
||||
return widget;
|
||||
},
|
||||
),
|
||||
size: size,
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import 'package:moxxyv2/ui/constants.dart';
|
||||
import 'package:moxxyv2/ui/service/data.dart';
|
||||
import 'package:moxxyv2/ui/widgets/avatar.dart';
|
||||
import 'package:moxxyv2/ui/widgets/chat/shared/image.dart';
|
||||
import 'package:moxxyv2/ui/widgets/chat/shared/video.dart';
|
||||
import 'package:moxxyv2/ui/widgets/chat/typing.dart';
|
||||
|
||||
class ConversationsListRow extends StatefulWidget {
|
||||
@ -98,6 +99,29 @@ class ConversationsListRowState extends State<ConversationsListRow> {
|
||||
|
||||
return avatar;
|
||||
}
|
||||
|
||||
Widget _buildLastMessagePreview() {
|
||||
Widget? preview;
|
||||
if (widget.conversation.lastMessage!.mediaType!.startsWith('image/')) {
|
||||
preview = SharedImageWidget(
|
||||
widget.conversation.lastMessage!.mediaUrl!,
|
||||
borderRadius: 5,
|
||||
size: 30,
|
||||
);
|
||||
} else if (widget.conversation.lastMessage!.mediaType!.startsWith('video/')) {
|
||||
preview = SharedVideoWidget(
|
||||
widget.conversation.lastMessage!.mediaUrl!,
|
||||
widget.conversation.jid,
|
||||
borderRadius: 5,
|
||||
size: 30,
|
||||
);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 5),
|
||||
child: preview,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLastMessageBody() {
|
||||
if (widget.conversation.isTyping) {
|
||||
@ -233,11 +257,7 @@ class ConversationsListRowState extends State<ConversationsListRow> {
|
||||
...widget.conversation.lastMessage?.isThumbnailable == true && !widget.conversation.isTyping ? [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 5),
|
||||
child: SharedImageWidget(
|
||||
widget.conversation.lastMessage!.mediaUrl!,
|
||||
borderRadius: 5,
|
||||
size: 30,
|
||||
),
|
||||
child: _buildLastMessagePreview(),
|
||||
),
|
||||
] : [
|
||||
const SizedBox(height: 30),
|
||||
|
@ -1365,6 +1365,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
video_thumbnail:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: video_thumbnail
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.5.3"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -94,6 +94,7 @@ dependencies:
|
||||
url_launcher: 6.1.5
|
||||
#unifiedpush: 3.0.1
|
||||
uuid: 3.0.5
|
||||
video_thumbnail: 0.5.3
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^2.1.11
|
||||
|
Loading…
Reference in New Issue
Block a user