Skip to content

Commit

Permalink
TW-616 Video player for media page
Browse files Browse the repository at this point in the history
  • Loading branch information
drminh2807 authored and hoangdat committed Sep 25, 2023
1 parent 01130f2 commit 1bae918
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 54 deletions.
49 changes: 45 additions & 4 deletions lib/pages/chat/events/event_video_player.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pages/chat/events/download_video_state.dart';
import 'package:fluffychat/pages/chat/events/message_content_style.dart';
import 'package:fluffychat/pages/chat_details/chat_details_page_view/media/chat_details_media_style.dart';
import 'package:fluffychat/presentation/mixins/play_video_action_mixin.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
import 'package:fluffychat/widgets/mxc_image.dart';
import 'package:flutter/material.dart';

import 'package:flutter_blurhash/flutter_blurhash.dart';
Expand All @@ -11,6 +13,9 @@ import 'package:matrix/matrix.dart';

import 'package:fluffychat/pages/chat/events/image_bubble.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:linagora_design_flutter/extensions/duration_extension.dart';

typedef DownloadVideoEventCallback = Future<String> Function(Event event);

class EventVideoPlayer extends StatefulWidget {
final Event event;
Expand All @@ -19,15 +24,30 @@ class EventVideoPlayer extends StatefulWidget {

final double? height;

final Future<String> Function({required Event event})?
handleDownloadVideoEvent;
final bool rounded;

final bool showDuration;

final DownloadVideoEventCallback? handleDownloadVideoEvent;

final String? thumbnailCacheKey;

final Map<EventId, ImageData>? thumbnailCacheMap;

/// Enable it if the thumbnail image is stretched, and you don't want to resize it
final bool noResizeThumbnail;

const EventVideoPlayer(
this.event, {
Key? key,
this.width,
this.height,
this.handleDownloadVideoEvent,
this.rounded = true,
this.showDuration = false,
this.thumbnailCacheMap,
this.thumbnailCacheKey,
this.noResizeThumbnail = false,
}) : super(key: key);

@override
Expand All @@ -42,7 +62,7 @@ class EventVideoPlayerState extends State<EventVideoPlayer>
void _downloadAction() async {
_downloadStateNotifier.value = DownloadVideoState.loading;
try {
path = await widget.handleDownloadVideoEvent?.call(event: widget.event);
path = await widget.handleDownloadVideoEvent?.call(widget.event);
_downloadStateNotifier.value = DownloadVideoState.done;
} on MatrixConnectionException catch (e) {
_downloadStateNotifier.value = DownloadVideoState.failed;
Expand Down Expand Up @@ -71,7 +91,9 @@ class EventVideoPlayerState extends State<EventVideoPlayer>
final height = widget.height ?? MessageContentStyle.imageHeight(context);

return ClipRRect(
borderRadius: MessageContentStyle.borderRadiusBubble,
borderRadius: widget.rounded
? MessageContentStyle.borderRadiusBubble
: BorderRadius.zero,
child: Material(
color: Colors.black,
child: SizedBox(
Expand All @@ -87,6 +109,10 @@ class EventVideoPlayerState extends State<EventVideoPlayer>
tapToView: false,
width: MessageContentStyle.imageBubbleWidth(width),
height: MessageContentStyle.videoBubbleHeight(height),
rounded: widget.rounded,
thumbnailCacheKey: widget.thumbnailCacheKey,
thumbnailCacheMap: widget.thumbnailCacheMap,
noResizeThumbnail: widget.noResizeThumbnail,
),
)
else
Expand Down Expand Up @@ -138,6 +164,21 @@ class EventVideoPlayerState extends State<EventVideoPlayer>
},
),
),
if (widget.showDuration)
Positioned(
bottom: ChatDetailsMediaStyle.durationPosition,
right: ChatDetailsMediaStyle.durationPosition,
child: Container(
padding: ChatDetailsMediaStyle.durationPadding,
decoration: ChatDetailsMediaStyle.durationBoxDecoration(
context,
),
child: Text(
widget.event.duration?.mediaTimeLength() ?? "--:--",
style: ChatDetailsMediaStyle.durationTextStyle(context),
),
),
),
],
),
),
Expand Down
25 changes: 21 additions & 4 deletions lib/pages/chat/events/image_bubble.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,16 @@ class ImageBubble extends StatelessWidget {
final bool animated;
final double width;
final double height;
final bool rounded;
final void Function()? onTapPreview;
final void Function()? onTapSelectMode;
final Uint8List? imageData;
final Duration animationDuration;

final String? thumbnailCacheKey;
final Map<EventId, ImageData>? thumbnailCacheMap;
final bool noResizeThumbnail;

const ImageBubble(
this.event, {
this.imageData,
Expand All @@ -34,9 +39,13 @@ class ImageBubble extends StatelessWidget {
this.width = 256,
this.height = 300,
this.animated = false,
this.rounded = true,
this.onTapSelectMode,
this.onTapPreview,
this.animationDuration = const Duration(milliseconds: 500),
this.thumbnailCacheKey,
this.thumbnailCacheMap,
this.noResizeThumbnail = false,
Key? key,
}) : super(key: key);

Expand All @@ -59,7 +68,8 @@ class ImageBubble extends StatelessWidget {
width = (height * ratio).round();
}
return ClipRRect(
borderRadius: BorderRadius.circular(12),
borderRadius:
rounded ? MessageContentStyle.borderRadiusBubble : BorderRadius.zero,
child: SizedBox(
width: this.width,
height: this.height,
Expand All @@ -82,8 +92,10 @@ class ImageBubble extends StatelessWidget {
child: AnimatedSwitcher(
duration: const Duration(seconds: 1),
child: Container(
decoration: const BoxDecoration(
borderRadius: MessageContentStyle.borderRadiusBubble,
decoration: BoxDecoration(
borderRadius: rounded
? MessageContentStyle.borderRadiusBubble
: BorderRadius.zero,
),
constraints: maxSize
? BoxConstraints(
Expand All @@ -92,7 +104,9 @@ class ImageBubble extends StatelessWidget {
)
: null,
child: ClipRRect(
borderRadius: MessageContentStyle.borderRadiusBubble,
borderRadius: rounded
? MessageContentStyle.borderRadiusBubble
: BorderRadius.zero,
child: Stack(
alignment: Alignment.center,
children: [
Expand All @@ -115,6 +129,9 @@ class ImageBubble extends StatelessWidget {
imageData: imageData,
isPreview: true,
animationDuration: animationDuration,
cacheKey: thumbnailCacheKey,
cacheMap: thumbnailCacheMap,
noResize: noResizeThumbnail,
),
],
),
Expand Down
7 changes: 3 additions & 4 deletions lib/pages/chat/events/message_content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -163,12 +163,12 @@ class MessageContent extends StatelessWidget with PlayVideoActionMixin {
return _MessageVideoBuilder(
event: event,
onFileTapped: controller.onFileTapped,
handleDownloadVideoEvent: (({required Event event}) {
handleDownloadVideoEvent: (event) {
return controller.handleDownloadVideoEvent(
event: event,
playVideoAction: (path) => playVideoAction(context, path),
);
}),
},
);
case MessageTypes.File:
return Column(
Expand Down Expand Up @@ -500,8 +500,7 @@ class _MessageVideoBuilder extends StatelessWidget {

final void Function(Event event) onFileTapped;

final Future<String> Function({required Event event})
handleDownloadVideoEvent;
final DownloadVideoEventCallback handleDownloadVideoEvent;

const _MessageVideoBuilder({
required this.event,
Expand Down
13 changes: 12 additions & 1 deletion lib/pages/chat_details/chat_details.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import 'package:fluffychat/pages/chat_details/chat_details_page_view/media/chat_
import 'package:fluffychat/pages/invitation_selection/invitation_selection.dart';
import 'package:fluffychat/pages/invitation_selection/invitation_selection_web.dart';
import 'package:fluffychat/presentation/extensions/room_summary_extension.dart';
import 'package:fluffychat/presentation/mixins/handle_video_download_mixin.dart';
import 'package:fluffychat/presentation/mixins/play_video_action_mixin.dart';
import 'package:fluffychat/presentation/model/chat_details/chat_details_page_model.dart';
import 'package:fluffychat/utils/extension/build_context_extension.dart';
import 'package:fluffychat/utils/responsive/responsive_utils.dart';
Expand Down Expand Up @@ -41,7 +43,8 @@ class ChatDetails extends StatefulWidget {
ChatDetailsController createState() => ChatDetailsController();
}

class ChatDetailsController extends State<ChatDetails> {
class ChatDetailsController extends State<ChatDetails>
with HandleVideoDownloadMixin, PlayVideoActionMixin {
final invitationSelectionMobileAndTabletKey =
const Key('InvitationSelectionMobileAndTabletKey');

Expand Down Expand Up @@ -453,6 +456,7 @@ class ChatDetailsController extends State<ChatDetails> {
child: ChatDetailsMediaPage(
getTimeline: getTimeline,
cacheMap: _mediaCacheMap,
handleDownloadVideoEvent: _handleDownloadAndPlayVideo,
),
),
const ChatDetailsPageModel(
Expand All @@ -471,6 +475,13 @@ class ChatDetailsController extends State<ChatDetails> {
),
];

Future<String> _handleDownloadAndPlayVideo(Event event) {
return handleDownloadVideoEvent(
event: event,
playVideoAction: (path) => playVideoAction(context, path),
);
}

void onTapActionsButton(ChatDetailsActions action) {
switch (action) {
case ChatDetailsActions.addMembers:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
import 'package:fluffychat/app_state/success.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/domain/app_state/room/timeline_search_event_state.dart';
import 'package:fluffychat/pages/chat_details/chat_details_page_view/media/chat_details_media_style.dart';
import 'package:fluffychat/pages/chat/events/event_video_player.dart';
import 'package:fluffychat/pages/chat_details/chat_details_page_view/same_type_events_list_controller.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
import 'package:fluffychat/widgets/mxc_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_blurhash/flutter_blurhash.dart';
import 'package:linagora_design_flutter/extensions/duration_extension.dart';
import 'package:matrix/matrix.dart';

class ChatDetailsMediaPage extends StatelessWidget {
static const _mediaFetchLimit = 20;
final Future<Timeline> Function() getTimeline;
final Map<EventId, ImageData>? cacheMap;
final DownloadVideoEventCallback handleDownloadVideoEvent;

const ChatDetailsMediaPage({
Key? key,
required this.getTimeline,
required this.handleDownloadVideoEvent,
this.cacheMap,
}) : super(key: key);

Expand All @@ -37,41 +39,68 @@ class ChatDetailsMediaPage extends StatelessWidget {
crossAxisCount: 3,
),
itemCount: events.length,
itemBuilder: (context, index) => Stack(
fit: StackFit.expand,
children: [
MxcImage(
event: events[index],
isThumbnail: true,
fit: BoxFit.cover,
onTapPreview: () {},
isPreview: true,
placeholder: (context) => BlurHash(
hash:
events[index].blurHash ?? AppConfig.defaultImageBlurHash,
),
cacheKey: events[index].eventId,
cacheMap: cacheMap,
),
if (events[index].messageType == MessageTypes.Video)
Positioned(
bottom: ChatDetailsMediaStyle.durationPosition,
right: ChatDetailsMediaStyle.durationPosition,
child: Container(
padding: ChatDetailsMediaStyle.durationPadding,
decoration: ChatDetailsMediaStyle.durationBoxDecoration(
context,
),
child: Text(
events[index].duration?.mediaTimeLength() ?? "--:--",
style: ChatDetailsMediaStyle.durationTextStyle(context),
itemBuilder: (context, index) =>
events[index].messageType == MessageTypes.Image
? _ImageItem(
event: events[index],
cacheMap: cacheMap,
)
: _VideoItem(
event: events[index],
handleDownloadVideoEvent: handleDownloadVideoEvent,
thumbnailCacheMap: cacheMap,
),
),
),
],
),
);
},
);
}
}

class _ImageItem extends StatelessWidget {
final Event event;
final Map<EventId, ImageData>? cacheMap;
const _ImageItem({
required this.event,
this.cacheMap,
});

@override
Widget build(BuildContext context) {
return MxcImage(
event: event,
isThumbnail: true,
fit: BoxFit.cover,
onTapPreview: () {},
isPreview: true,
placeholder: (context) => BlurHash(
hash: event.blurHash ?? AppConfig.defaultImageBlurHash,
),
cacheKey: event.eventId,
cacheMap: cacheMap,
);
}
}

class _VideoItem extends StatelessWidget {
final Event event;
final DownloadVideoEventCallback handleDownloadVideoEvent;
final Map<EventId, ImageData>? thumbnailCacheMap;
const _VideoItem({
required this.event,
required this.handleDownloadVideoEvent,
this.thumbnailCacheMap,
});

@override
Widget build(BuildContext context) {
return EventVideoPlayer(
event,
handleDownloadVideoEvent: handleDownloadVideoEvent,
rounded: false,
showDuration: true,
thumbnailCacheKey: event.eventId,
thumbnailCacheMap: thumbnailCacheMap,
noResizeThumbnail: true,
);
}
}
1 change: 1 addition & 0 deletions lib/presentation/mixins/play_video_action_mixin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mixin PlayVideoActionMixin {
await showDialog(
context: context,
useRootNavigator: PlatformInfos.isWeb,
useSafeArea: false,
builder: (context) {
return VideoViewerDialog(path: uriOrFilePath);
},
Expand Down
Loading

0 comments on commit 1bae918

Please sign in to comment.