Skip to content

Commit

Permalink
refactor: ♻️ removed grouped_list and implement group separator for c…
Browse files Browse the repository at this point in the history
…hat (#173)

- Removed grouped_list package and implement the same group separator for message when date differ in chat.
  • Loading branch information
apurva010 authored Jun 4, 2024
1 parent 74e7b99 commit afc872c
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 81 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## [2.0.0] (Unreleased)

* **Breaking**: [173](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/pull/173) Added
callback to sort message in chat.
* **Fix**: [181](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/pull/181) Removed
deprecated field `showTypingIndicator` from ChatView.
* **Fix**: [139](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues/139) Added
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,22 @@ ChatView(
```


30. Added callback `messageSorter` to sort message in `ChatBackgroundConfiguration`.
```dart
ChatView(
...
chatBackgroundConfig: ChatBackgroundConfiguration(
...
messageSorter: (message1, message2) {
return message1.createdAt.compareTo(message2.createdAt);
}
...
),
...
),
```


## How to use

Expand Down
2 changes: 1 addition & 1 deletion lib/chatview.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ library chatview;
export 'src/widgets/chat_view.dart';
export 'src/models/models.dart';
export 'src/widgets/chat_view_appbar.dart';
export 'src/values/enumaration.dart';
export 'src/values/enumeration.dart';
export 'src/controller/chat_controller.dart';
export 'src/values/typedefs.dart';
export 'package:audio_waveforms/audio_waveforms.dart'
Expand Down
8 changes: 6 additions & 2 deletions lib/src/models/message_list_configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
* SOFTWARE.
*/
import 'package:flutter/material.dart';
import 'package:grouped_list/grouped_list.dart';

import '../utils/constants/constants.dart';
import '../values/enumeration.dart';
import '../values/typedefs.dart';

class ChatBackgroundConfiguration {
Expand Down Expand Up @@ -71,21 +71,25 @@ class ChatBackgroundConfiguration {
/// message.
final Curve messageTimeAnimationCurve;

/// Provides callback to sort message
final MessageSorter? messageSorter;

const ChatBackgroundConfiguration({
this.defaultGroupSeparatorConfig,
this.backgroundColor,
this.backgroundImage,
this.height,
this.width,
this.groupSeparatorBuilder,
this.groupedListOrder = GroupedListOrder.ASC,
this.groupedListOrder = GroupedListOrder.asc,
this.sortEnable = false,
this.padding,
this.margin,
this.messageTimeTextStyle,
this.messageTimeIconColor,
this.loadingWidget,
this.messageTimeAnimationCurve = Curves.decelerate,
this.messageSorter,
});
}

Expand Down
2 changes: 1 addition & 1 deletion lib/src/models/reply_message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import '../values/enumaration.dart';
import '../values/enumeration.dart';

class ReplyMessage {
/// Provides reply message.
Expand Down
2 changes: 1 addition & 1 deletion lib/src/models/send_message_configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:image_picker/image_picker.dart';

import '../values/enumaration.dart';
import '../values/enumeration.dart';
import '../values/typedefs.dart';

class SendMessageConfiguration {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,11 @@ extension ChatViewStateExtension on ChatViewState {

bool get noMessages => this == ChatViewState.noData;
}

enum GroupedListOrder { asc, desc }

extension GroupedListOrderExtension on GroupedListOrder {
bool get isAsc => this == GroupedListOrder.asc;

bool get isDesc => this == GroupedListOrder.desc;
}
17 changes: 14 additions & 3 deletions lib/src/values/typedefs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@ import 'package:flutter/material.dart';

typedef StringCallback = void Function(String);
typedef StringMessageCallBack = void Function(
String message, ReplyMessage replyMessage, MessageType messageType);
String message,
ReplyMessage replyMessage,
MessageType messageType,
);
typedef ReplyMessageWithReturnWidget = Widget Function(
ReplyMessage? replyMessage);
ReplyMessage? replyMessage,
);
typedef ReplyMessageCallBack = void Function(ReplyMessage replyMessage);
typedef VoidCallBack = void Function();
typedef DoubleCallBack = void Function(double, double);
Expand All @@ -49,10 +53,17 @@ typedef ReactedUserCallback = void Function(
);

/// customMessageType view for a reply of custom message type
typedef CustomMessageReplyViewBuilder = Widget Function(ReplyMessage state);
typedef CustomMessageReplyViewBuilder = Widget Function(
ReplyMessage state,
);
typedef MessageSorter = int Function(
Message message1,
Message message2,
);

/// customView for replying to any message
typedef CustomViewForReplyMessage = Widget Function(
BuildContext context,
ReplyMessage state,
);
typedef GetMessageSeparator = (Map<int, DateTime>, DateTime);
232 changes: 161 additions & 71 deletions lib/src/widgets/chat_groupedlist_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import 'package:chatview/src/extensions/extensions.dart';
import 'package:chatview/src/widgets/chat_view_inherited_widget.dart';
import 'package:chatview/src/widgets/type_indicator_widget.dart';
import 'package:flutter/material.dart';
import 'package:grouped_list/grouped_list.dart';

import 'chat_bubble_widget.dart';
import 'chat_group_header.dart';
Expand Down Expand Up @@ -287,82 +286,173 @@ class _ChatGroupedListWidgetState extends State<ChatGroupedListWidget>
return StreamBuilder<List<Message>>(
stream: chatController?.messageStreamController.stream,
builder: (context, snapshot) {
return snapshot.connectionState.isActive
? GroupedListView<Message, DateTime>(
shrinkWrap: true,
elements: snapshot.data!,
groupBy: (message) {
/// If the conversation is ongoing on the same date,
/// return the same date [lastMatchedDate].
/// When the conversation starts on a new date,
/// we assign the new date [message.createdAt]
/// to [lastMatchedDate].
return lastMatchedDate =
lastMatchedDate.getDateFromDateTime ==
message.createdAt.getDateFromDateTime
? lastMatchedDate
: message.createdAt;
},
itemComparator: (message1, message2) =>
message1.message.compareTo(message2.message),
physics: const NeverScrollableScrollPhysics(),
order: chatBackgroundConfig.groupedListOrder,
sort: chatBackgroundConfig.sortEnable,
groupSeparatorBuilder: (separator) =>
featureActiveConfig?.enableChatSeparator ?? false
? _GroupSeparatorBuilder(
separator: separator,
defaultGroupSeparatorConfig: chatBackgroundConfig
.defaultGroupSeparatorConfig,
groupSeparatorBuilder:
chatBackgroundConfig.groupSeparatorBuilder,
)
: const SizedBox.shrink(),
indexedItemBuilder: (context, message, index) {
return ValueListenableBuilder<String?>(
valueListenable: _replyId,
builder: (context, state, child) {
return ChatBubbleWidget(
key: message.key,
messageTimeTextStyle:
chatBackgroundConfig.messageTimeTextStyle,
messageTimeIconColor:
chatBackgroundConfig.messageTimeIconColor,
message: message,
messageConfig: widget.messageConfig,
chatBubbleConfig: chatBubbleConfig,
profileCircleConfig: profileCircleConfig,
swipeToReplyConfig: widget.swipeToReplyConfig,
repliedMessageConfig: widget.repliedMessageConfig,
slideAnimation: _slideAnimation,
onLongPress: (yCoordinate, xCoordinate) =>
widget.onChatBubbleLongPress(
yCoordinate,
xCoordinate,
message,
),
onSwipe: widget.assignReplyMessage,
shouldHighlight: state == message.id,
onReplyTap: widget
.repliedMessageConfig
?.repliedMsgAutoScrollConfig
.enableScrollToRepliedMsg ??
false
? (replyId) => _onReplyTap(replyId, snapshot.data)
: null,
);
},
if (!snapshot.connectionState.isActive) {
return Center(
child: chatBackgroundConfig.loadingWidget ??
const CircularProgressIndicator(),
);
} else {
final messages = widget.chatBackgroundConfig.sortEnable
? sortMessage(snapshot.data!)
: snapshot.data!;

final enableSeparator =
featureActiveConfig?.enableChatSeparator ?? false;

Map<int, DateTime> messageSeparator = {};

if (enableSeparator) {
/// Get separator when date differ for two messages
(messageSeparator, lastMatchedDate) = _getMessageSeparator(
messages,
lastMatchedDate,
);
}

/// [count] that indicates how many separators
/// needs to be display in chat
var count = 0;

return ListView.builder(
key: widget.key,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: (enableSeparator
? messages.length + messageSeparator.length
: messages.length),
itemBuilder: (context, index) {
/// By removing [count] from [index] will get actual index
/// to display message in chat
var newIndex = index - count;

/// Check [messageSeparator] contains group separator for [index]
if (enableSeparator && messageSeparator.containsKey(index)) {
/// Increase counter each time
/// after separating messages with separator
count++;
return _groupSeparator(
messageSeparator[index]!,
);
}

return ValueListenableBuilder<String?>(
valueListenable: _replyId,
builder: (context, state, child) {
final message = messages[newIndex];
return ChatBubbleWidget(
key: message.key,
messageTimeTextStyle:
chatBackgroundConfig.messageTimeTextStyle,
messageTimeIconColor:
chatBackgroundConfig.messageTimeIconColor,
message: message,
messageConfig: widget.messageConfig,
chatBubbleConfig: chatBubbleConfig,
profileCircleConfig: profileCircleConfig,
swipeToReplyConfig: widget.swipeToReplyConfig,
repliedMessageConfig: widget.repliedMessageConfig,
slideAnimation: _slideAnimation,
onLongPress: (yCoordinate, xCoordinate) =>
widget.onChatBubbleLongPress(
yCoordinate,
xCoordinate,
message,
),
onSwipe: widget.assignReplyMessage,
shouldHighlight: state == message.id,
onReplyTap: widget
.repliedMessageConfig
?.repliedMsgAutoScrollConfig
.enableScrollToRepliedMsg ??
false
? (replyId) => _onReplyTap(replyId, snapshot.data)
: null,
);
},
)
: Center(
child: chatBackgroundConfig.loadingWidget ??
const CircularProgressIndicator(),
);
},
);
}
},
);
}

List<Message> sortMessage(List<Message> messages) {
final elements = [...messages];

elements.sort(
widget.chatBackgroundConfig.messageSorter ??
(a, b) => a.createdAt.compareTo(b.createdAt),
);
if (widget.chatBackgroundConfig.groupedListOrder.isAsc) {
return elements.toList();
} else {
return elements.reversed.toList();
}
}

/// return DateTime by checking lastMatchedDate and message created DateTime
DateTime _groupBy(
Message message,
DateTime lastMatchedDate,
) {
/// If the conversation is ongoing on the same date,
/// return the same date [lastMatchedDate].
/// When the conversation starts on a new date,
/// we are returning new date [message.createdAt].
return lastMatchedDate.getDateFromDateTime ==
message.createdAt.getDateFromDateTime
? lastMatchedDate
: message.createdAt;
}

Widget _groupSeparator(DateTime createdAt) {
return featureActiveConfig?.enableChatSeparator ?? false
? _GroupSeparatorBuilder(
separator: createdAt,
defaultGroupSeparatorConfig:
chatBackgroundConfig.defaultGroupSeparatorConfig,
groupSeparatorBuilder: chatBackgroundConfig.groupSeparatorBuilder,
)
: const SizedBox.shrink();
}

GetMessageSeparator _getMessageSeparator(
List<Message> messages,
DateTime lastDate,
) {
final messageSeparator = <int, DateTime>{};
var lastMatchedDate = lastDate;
var counter = 0;

/// Holds index and separator mapping to display in chat
for (var i = 0; i < messages.length; i++) {
if (messageSeparator.isEmpty) {
/// Separator for initial message
messageSeparator[0] = messages[0].createdAt;
continue;
}
lastMatchedDate = _groupBy(
messages[i],
lastMatchedDate,
);
var previousDate = _groupBy(
messages[i - 1],
lastMatchedDate,
);

if (previousDate != lastMatchedDate) {
/// Group separator when previous message and
/// current message time differ
counter++;

messageSeparator[i + counter] = messages[i].createdAt;
}
}

return (messageSeparator, lastMatchedDate);
}
}

class _GroupSeparatorBuilder extends StatelessWidget {
Expand Down
Loading

0 comments on commit afc872c

Please sign in to comment.