Skip to content

Commit

Permalink
TW-616 List all image & video of Chat in Chat details
Browse files Browse the repository at this point in the history
  • Loading branch information
drminh2807 committed Sep 18, 2023
1 parent e20a601 commit 808ba61
Show file tree
Hide file tree
Showing 18 changed files with 471 additions and 19 deletions.
2 changes: 2 additions & 0 deletions lib/config/app_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ abstract class AppConfig {
static const int fetchContactsLimit = 20;
static const int chatRoomSearchKeywordMin = 2;
static const bool chatRoomSearchWordStrategy = false;
static const String defaultImageBlurHash = 'LEHV6nWB2yk8pyo0adR*.7kCMdnj';
static const String defaultVideoBlurHash = 'L5H2EC=PM+yV0g-mq.wG9c010J}I';

static String? issueId;

Expand Down
4 changes: 4 additions & 0 deletions lib/di/global/get_it_initializer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import 'package:fluffychat/domain/usecase/forward/forward_message_interactor.dar
import 'package:fluffychat/domain/usecase/get_contacts_interactor.dart';
import 'package:fluffychat/domain/usecase/room/chat_room_search_interactor.dart';
import 'package:fluffychat/domain/usecase/room/create_new_group_chat_interactor.dart';
import 'package:fluffychat/domain/usecase/room/timeline_search_event_interactor.dart';
import 'package:fluffychat/domain/usecase/room/upload_content_interactor.dart';
import 'package:fluffychat/domain/usecase/search/pre_search_recent_contacts_interactor.dart';
import 'package:fluffychat/domain/usecase/search/search_recent_chat_interactor.dart';
Expand Down Expand Up @@ -134,5 +135,8 @@ class GetItInitializer {
getIt.registerSingleton<ChatRoomSearchInteractor>(
ChatRoomSearchInteractor(),
);
getIt.registerSingleton<TimelineSearchEventInteractor>(
TimelineSearchEventInteractor(),
);
}
}
42 changes: 42 additions & 0 deletions lib/domain/app_state/room/timeline_search_event_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import 'package:dartz/dartz.dart';
import 'package:fluffychat/app_state/failure.dart';
import 'package:fluffychat/app_state/success.dart';
import 'package:matrix/matrix.dart';

class TimelineSearchEventInitial extends Success {
@override
List<Object?> get props => [];
}

class TimelineSearchEventSuccess extends Success {
final List<Event> events;

const TimelineSearchEventSuccess({required this.events});

@override
List<Object?> get props => [events];
}

class TimelineSearchEventFailure extends Failure {
final dynamic exception;

const TimelineSearchEventFailure({required this.exception});

@override
List<Object?> get props => [exception];
}

extension TimelineSearchEventSuccessExtension on TimelineSearchEventSuccess {
TimelineSearchEventSuccess addMore(TimelineSearchEventSuccess other) {
return TimelineSearchEventSuccess(
events: events + other.events,
);
}
}

extension TimelineSearchEventEitherExtension on Either {
TimelineSearchEventSuccess? getSuccessOrNull() => fold(
(failure) => null,
(success) => success is TimelineSearchEventSuccess ? success : null,
);
}
35 changes: 35 additions & 0 deletions lib/domain/usecase/room/timeline_search_event_interactor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import 'package:dartz/dartz.dart';
import 'package:fluffychat/app_state/failure.dart';
import 'package:fluffychat/app_state/success.dart';
import 'package:fluffychat/domain/app_state/room/timeline_search_event_state.dart';
import 'package:matrix/matrix.dart';

class TimelineSearchEventInteractor {
Stream<Either<Failure, Success>> execute({
required Timeline timeline,
required bool Function(Event) searchFunc,
required int requestHistoryCount,
required int maxHistoryRequests,
required int? limit,
String? sinceEventId,
}) async* {
try {
yield* timeline
.searchEvent(
searchFunc: searchFunc,
requestHistoryCount: requestHistoryCount,
maxHistoryRequests: maxHistoryRequests,
limit: limit,
sinceEventId: sinceEventId,
)
.map((events) {
Logs().v(
'TimelineSearchEventInteractor::events ${events.length} ${events.map((event) => event.eventId)}',
);
return Right(TimelineSearchEventSuccess(events: events));
});
} catch (e) {
yield Left(TimelineSearchEventFailure(exception: e));
}
}
}
6 changes: 3 additions & 3 deletions lib/pages/chat/events/image_bubble.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'dart:typed_data';

import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
import 'package:flutter/material.dart';
import 'package:flutter_blurhash/flutter_blurhash.dart';
import 'package:matrix/matrix.dart';
Expand Down Expand Up @@ -44,9 +46,7 @@ class ImageBubble extends StatelessWidget {
);
}
final String blurHashString =
event.infoMap['xyz.amorgan.blurhash'] is String
? event.infoMap['xyz.amorgan.blurhash']
: 'LEHV6nWB2yk8pyo0adR*.7kCMdnj';
event.blurHash ?? AppConfig.defaultImageBlurHash;
final ratio = event.infoMap['w'] is int && event.infoMap['h'] is int
? event.infoMap['w'] / event.infoMap['h']
: 1.0;
Expand Down
7 changes: 2 additions & 5 deletions lib/pages/chat/events/video_player.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:io';

import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pages/chat/events/message_content_style.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
Expand Down Expand Up @@ -95,14 +96,10 @@ class EventVideoPlayerState extends State<EventVideoPlayer> {
super.dispose();
}

static const String fallbackBlurHash = 'L5H2EC=PM+yV0g-mq.wG9c010J}I';

@override
Widget build(BuildContext context) {
final hasThumbnail = widget.event.hasThumbnail;
final blurHash = (widget.event.infoMap as Map<String, dynamic>)
.tryGet<String>('xyz.amorgan.blurhash') ??
fallbackBlurHash;
final blurHash = widget.event.blurHash ?? AppConfig.defaultVideoBlurHash;

final chewieManager = _chewieManager;
return ClipRRect(
Expand Down
40 changes: 36 additions & 4 deletions lib/pages/chat_details/chat_details.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@ import 'package:fluffychat/di/global/get_it_initializer.dart';
import 'package:fluffychat/pages/chat_details/chat_details_actions_enum.dart';
import 'package:fluffychat/pages/chat_details/chat_details_page_view/chat_details_members_page.dart';
import 'package:fluffychat/pages/chat_details/chat_details_page_view/chat_details_page_enum.dart';
import 'package:fluffychat/pages/chat_details/chat_details_page_view/links/chat_details_links_page.dart';
import 'package:fluffychat/pages/chat_details/chat_details_page_view/media/chat_details_media_page.dart';
import 'package:fluffychat/pages/chat_details/chat_details_page_view/same_type_events_list_controller.dart';
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/model/chat_details/chat_details_page_model.dart';
import 'package:fluffychat/utils/extension/build_context_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
import 'package:fluffychat/utils/responsive/responsive_utils.dart';
import 'package:fluffychat/widgets/mxc_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

Expand Down Expand Up @@ -39,6 +44,8 @@ class ChatDetails extends StatefulWidget {
}

class ChatDetailsController extends State<ChatDetails> {
static const _mediaFetchLimit = 20;
static const _linksFetchLimit = 20;
final invitationSelectionMobileAndTabletKey =
const Key('InvitationSelectionMobileAndTabletKey');

Expand Down Expand Up @@ -66,6 +73,24 @@ class ChatDetailsController extends State<ChatDetails> {

Room? get room => Matrix.of(context).client.getRoomById(roomId!);

Timeline? _timeline;

Future<Timeline> getTimeline() async {
_timeline ??= await room!.getTimeline();
return _timeline!;
}

final mediaListController = SameTypeEventsListController(
searchFunc: (event) => event.isVideoOrImage,
limit: _mediaFetchLimit,
);
final linksListController = SameTypeEventsListController(
searchFunc: (event) => event.isContainsLink,
limit: _linksFetchLimit,
);

final MxcImageCacheMap _mediaCacheMap = {};

int get actualMembersCount => room!.summary.actualMembersCount;

void toggleDisplaySettings() =>
Expand Down Expand Up @@ -436,17 +461,24 @@ class ChatDetailsController extends State<ChatDetails> {
isMobileAndTablet: isMobileAndTablet,
),
),
const ChatDetailsPageModel(
ChatDetailsPageModel(
page: ChatDetailsPage.media,
child: SizedBox.shrink(),
child: ChatDetailsMediaPage(
eventsListController: mediaListController,
getTimeline: getTimeline,
cacheMap: _mediaCacheMap,
),
),
const ChatDetailsPageModel(
page: ChatDetailsPage.files,
child: SizedBox.shrink(),
),
const ChatDetailsPageModel(
ChatDetailsPageModel(
page: ChatDetailsPage.links,
child: SizedBox.shrink(),
child: ChatDetailsLinksPage(
eventsListController: linksListController,
getTimeline: getTimeline,
),
),
const ChatDetailsPageModel(
page: ChatDetailsPage.downloads,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import 'package:fluffychat/domain/app_state/room/timeline_search_event_state.dart';
import 'package:fluffychat/pages/chat_details/chat_details_page_view/links/chat_details_links_style.dart';
import 'package:fluffychat/utils/string_extension.dart';
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';

import 'package:fluffychat/pages/chat_details/chat_details_page_view/same_type_events_list_builder.dart';
import 'package:fluffychat/pages/chat_details/chat_details_page_view/same_type_events_list_controller.dart';

class ChatDetailsLinksPage extends StatelessWidget {
final SameTypeEventsListController eventsListController;
final Future<Timeline> Function() getTimeline;

const ChatDetailsLinksPage({
Key? key,
required this.eventsListController,
required this.getTimeline,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return SameTypeEventsListBuilder(
controller: eventsListController,
getTimeline: getTimeline,
builder: (context, eventsState) {
final events = eventsState.getSuccessOrNull()?.events ?? [];
return ListView.separated(
itemCount: events.length,
itemBuilder: (context, index) {
final body = events[index].body;
final link = body.getFirstValidUrl() ?? '';
return ListTile(
leading: Container(
width: ChatDetailsLinksStyle.avatarSize,
height: ChatDetailsLinksStyle.avatarSize,
alignment: Alignment.center,
decoration: ChatDetailsLinksStyle.avatarDecoration(context),
child: Text(
link.getShortcutNameForAvatar(),
style: ChatDetailsLinksStyle.avatarTextStyle(context),
),
),
title: Text(link),
subtitle: Text(
link,
style: ChatDetailsLinksStyle.subtitleTextStyle(context),
),
);
},
separatorBuilder: (context, index) => const Divider(),
);
},
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import 'package:flutter/material.dart';

class ChatDetailsLinksStyle {
static const double avatarSize = 56;
static BoxDecoration avatarDecoration(BuildContext context) => BoxDecoration(
color: Theme.of(context).colorScheme.secondary,
borderRadius: BorderRadius.circular(16),
);
static TextStyle? avatarTextStyle(BuildContext context) =>
Theme.of(context).textTheme.headlineLarge?.copyWith(
color: Theme.of(context).colorScheme.onSecondary,
);
static TextStyle? subtitleTextStyle(BuildContext context) =>
Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.secondary,
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
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_details/chat_details_page_view/same_type_events_list_builder.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:matrix/matrix.dart';

class ChatDetailsMediaPage extends StatelessWidget {
final SameTypeEventsListController eventsListController;
final Future<Timeline> Function() getTimeline;
final MxcImageCacheMap cacheMap;
const ChatDetailsMediaPage({
Key? key,
required this.eventsListController,
required this.getTimeline,
required this.cacheMap,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return SameTypeEventsListBuilder(
controller: eventsListController,
getTimeline: getTimeline,
builder: (context, eventsState) {
final events = eventsState.getSuccessOrNull()?.events ?? [];
return CustomScrollView(
slivers: [
SliverGrid.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
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: 10,
right: 10,
child: Container(
padding: ChatDetailsMediaStyle.durationPadding,
decoration: ChatDetailsMediaStyle.durationBoxDecoration(
context,
),
child: Text(
"00:00",
style:
ChatDetailsMediaStyle.durationTextStyle(context),
),
),
),
],
),
)
],
);
},
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';

class ChatDetailsMediaStyle {
static const durationPadding = EdgeInsets.symmetric(
horizontal: 4,
vertical: 2,
);

static Decoration durationBoxDecoration(BuildContext context) =>
ShapeDecoration(
color: Theme.of(context).colorScheme.onBackground.withOpacity(0.5),
shape: const StadiumBorder(),
);

static TextStyle? durationTextStyle(BuildContext context) =>
Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
);
}
Loading

0 comments on commit 808ba61

Please sign in to comment.