Skip to content

Commit 289c35f

Browse files
committed
feat: Add edit message support. (closes #336)
- Add enableEditMessage flag to FeatureActiveConfig (default: true) - Add onEditTap callback to ChatView and ReplyPopupConfiguration - Add EditMessageCallback typedef - Create EditMessageView widget showing 'Editing' indicator above textfield - Extend SendMessageWidget with assignEditMessage() and edit mode logic - Add Edit button to ReplyPopupWidget (own messages only) - Wire assignEditMessage through ChatListWidget -> ChatView - Show 'Edited' label on messages where updateAt is non-null - Add edit/edited/editing strings to ChatViewLocale (backward-compatible) - Expose ChatView.getEditingMessage() static helper - Add doc/documentation.md section for Edit Message feature
1 parent 11230cb commit 289c35f

14 files changed

Lines changed: 466 additions & 2 deletions

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
## [Unreleased]
22

3-
* **Feat**: ✨Added backgroundImageFit and backgroundImageRepeat to
4-
ChatBackgroundConfiguration for controllable chat wallpaper layout.
53
* **Fix**: [423](https://github.com/SimformSolutionsPvtLtd/chatview/pull/423)
64
Rendering issue in attached image preview when sending message on web.
75
* **Feat**: [420](https://github.com/SimformSolutionsPvtLtd/chatview/pull/420) Added support for
86
`playerMode` in `VoiceMessageConfiguration` with `single` and `multi`.
97
* **Feat**: [434](https://github.com/SimformSolutionsPvtLtd/chatview/pull/434) Add optional
108
`boxShadow` to `ChatBubble` and apply it in `TextMessageView` when set.
9+
* **Feat**: ✨Added backgroundImageFit and backgroundImageRepeat to
10+
ChatBackgroundConfiguration for controllable chat wallpaper layout.
11+
* **Feat**: [336](https://github.com/SimformSolutionsPvtLtd/chatview/issues/336) Add edit message support.
1112

1213
## [3.0.0]
1314

doc/documentation.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -853,6 +853,107 @@ ChatView(
853853
lastSeenAgoBuilderVisibility: false,
854854
receiptsBuilderVisibility: false,
855855
enableTextSelection: true,
856+
enableEditMessage: true, // Enable the Edit Message feature (default: true)
857+
),
858+
// ...
859+
)
860+
```
861+
862+
## Edit Message Feature
863+
864+
ChatView supports editing previously sent text messages, similar to WhatsApp and Telegram.
865+
866+
### How It Works
867+
868+
1. Long-press a message bubble sent by the current user.
869+
2. Tap **Edit** in the reply pop-up.
870+
3. The original text is pre-filled into the input field with an *Editing* indicator above it.
871+
4. Modify the text and press Send.
872+
5. Your `onEditTap` callback is called with the **original `Message`** and the **new text**.
873+
6. Messages that have been edited display a subtle *Edited* label below the bubble.
874+
875+
### Basic Integration
876+
877+
```dart
878+
ChatView(
879+
// ...
880+
onEditTap: (Message originalMessage, String newText) {
881+
// Update the message in your data source / backend.
882+
// Set `updateAt` on the message to trigger the "Edited" label in the UI.
883+
final updatedMessage = Message(
884+
id: originalMessage.id,
885+
message: newText,
886+
createdAt: originalMessage.createdAt,
887+
sentBy: originalMessage.sentBy,
888+
updateAt: DateTime.now(), // marks the message as edited
889+
replyMessage: originalMessage.replyMessage,
890+
messageType: originalMessage.messageType,
891+
);
892+
893+
chatController.updateMessage(updatedMessage); // depends on your controller API
894+
},
895+
replyPopupConfig: ReplyPopupConfiguration(
896+
// Optional: react to the edit tap in the popup before the editing UI opens.
897+
onEditTap: (message) => debugPrint('Editing: ${message.id}'),
898+
),
899+
// ...
900+
)
901+
```
902+
903+
### Disabling the Feature
904+
905+
```dart
906+
ChatView(
907+
featureActiveConfig: const FeatureActiveConfig(
908+
enableEditMessage: false, // hides the Edit button entirely
909+
),
910+
)
911+
```
912+
913+
### Localization
914+
915+
The edit-related strings can be customized via `ChatViewLocale`:
916+
917+
```dart
918+
PackageStrings.addLocaleObject(
919+
'es',
920+
const ChatViewLocale(
921+
// ... all other required fields ...
922+
edit: 'Editar',
923+
edited: 'Editado',
924+
editing: 'Editando',
925+
),
926+
);
927+
PackageStrings.setLocale('es');
928+
```
929+
930+
### Static Helper
931+
932+
Use `ChatView.getEditingMessage(context)` to read the message currently being edited
933+
from a widget that is a descendant of `ChatView`:
934+
935+
```dart
936+
final editingMsg = ChatView.getEditingMessage(context);
937+
if (editingMsg != null) {
938+
// User is in edit mode for editingMsg.
939+
}
940+
```
941+
942+
### Customizing the Edit Indicator Label
943+
944+
When a user enters edit mode, an indicator bar is displayed above the text field showing a label
945+
(default: locale-resolved `"Editing"`). You can override this label via `SendMessageConfiguration`:
946+
947+
```dart
948+
ChatView(
949+
// ...
950+
sendMessageConfig: SendMessageConfiguration(
951+
/// The label shown in the editing indicator bar above the text field
952+
/// when the user is editing an existing message.
953+
///
954+
/// If omitted, falls back to the locale-resolved value of
955+
/// `PackageStrings.currentLocale.editing` (e.g. `"Editing"`).
956+
editLabel: 'Editing message',
856957
),
857958
// ...
858959
)

lib/chatview.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,4 @@ export 'src/widgets/action_widgets/text_field_action_button.dart';
5757
export 'src/widgets/chat_list/chatlist.dart';
5858
export 'src/widgets/chat_view.dart';
5959
export 'src/widgets/chat_view_appbar.dart';
60+
export 'src/widgets/edit_message_view.dart';

lib/src/models/config_models/feature_active_config.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class FeatureActiveConfig {
3737
this.enableOtherUserName = true,
3838
this.enableScrollToBottomButton = false,
3939
this.enableTextSelection = false,
40+
this.enableMessageEditing = true,
4041
});
4142

4243
/// Used for enable/disable swipe to reply.
@@ -85,4 +86,13 @@ class FeatureActiveConfig {
8586
///
8687
/// Defaults to `false`.
8788
final bool enableTextSelection;
89+
90+
/// Used for enable/disable the Edit Message feature.
91+
///
92+
/// When enabled, an "Edit" option appears in the reply popup for
93+
/// messages sent by the current user, allowing them to modify the
94+
/// message text in-place.
95+
///
96+
/// Defaults to `true`.
97+
final bool enableMessageEditing;
8898
}

lib/src/models/config_models/reply_popup_configuration.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class ReplyPopupConfiguration {
3232
this.onReplyTap,
3333
this.onReportTap,
3434
this.onMoreTap,
35+
this.onEditTap,
3536
this.backgroundColor,
3637
this.replyPopupBuilder,
3738
});
@@ -48,6 +49,9 @@ class ReplyPopupConfiguration {
4849
/// Provides callback on onReply button.
4950
final ValueSetter<Message>? onReplyTap;
5051

52+
/// Provides callback on edit button (only shown for current user's messages).
53+
final ValueSetter<Message>? onEditTap;
54+
5155
/// Provides callback on onReport button.
5256
final ValueSetter<Message>? onReportTap;
5357

lib/src/models/config_models/send_message_configuration.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class SendMessageConfiguration {
5353
this.imageBorderRadius,
5454
this.selectedImageViewBuilder,
5555
this.sendButtonStyle,
56+
this.editLabel,
5657
});
5758

5859
/// Used to give background color to text field.
@@ -123,6 +124,15 @@ class SendMessageConfiguration {
123124

124125
/// Used to give style to send button.
125126
final ButtonStyle? sendButtonStyle;
127+
128+
/// Custom label displayed in the "editing message" indicator bar shown
129+
/// above the text field when the user is editing an existing message.
130+
///
131+
/// Example: `"Editing message"` or a localized equivalent.
132+
///
133+
/// If not provided, falls back to the locale-resolved value of
134+
/// `PackageStrings.currentLocale.editing`.
135+
final String? editLabel;
126136
}
127137

128138
class ImagePickerIconsConfiguration {

lib/src/utils/chat_view_locale.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ final class ChatViewLocale {
5656
required this.deleteChat,
5757
required this.noChats,
5858
required this.noSearchResults,
59+
this.edit = 'Edit',
60+
this.edited = 'Edited',
61+
this.editing = 'Editing',
5962
});
6063

6164
/// Create from `Map<String, String>`
@@ -94,6 +97,9 @@ final class ChatViewLocale {
9497
deleteChat: map['deleteChat']?.toString() ?? '',
9598
noChats: map['noChats']?.toString() ?? '',
9699
noSearchResults: map['noSearchResults']?.toString() ?? '',
100+
edit: map['edit']?.toString() ?? 'Edit',
101+
edited: map['edited']?.toString() ?? 'Edited',
102+
editing: map['editing']?.toString() ?? 'Editing',
97103
);
98104
}
99105

@@ -131,6 +137,15 @@ final class ChatViewLocale {
131137
final String noChats;
132138
final String noSearchResults;
133139

140+
/// Label for the edit action button in the reply popup.
141+
final String edit;
142+
143+
/// Label shown next to an edited message.
144+
final String edited;
145+
146+
/// Label shown in the text field header when editing a message.
147+
final String editing;
148+
134149
/// English defaults
135150
static const en = ChatViewLocale(
136151
today: 'Today',
@@ -166,5 +181,8 @@ final class ChatViewLocale {
166181
deleteChat: 'Delete Chat',
167182
noChats: 'No Chats',
168183
noSearchResults: 'No search results',
184+
edit: 'Edit',
185+
edited: 'Edited',
186+
editing: 'Editing',
169187
);
170188
}

lib/src/values/typedefs.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,14 @@ typedef ChatBubbleLongPressCallback = void Function(
9696
double xCordinate,
9797
Message message,
9898
);
99+
100+
/// Callback invoked when the user confirms an edit.
101+
/// [message] is the original message being edited.
102+
/// [updatedMessage] is the updated text content.
103+
typedef EditMessageCallback = void Function(
104+
Message message,
105+
String updatedMessage,
106+
);
99107
typedef ChatTextFieldViewBuilderCallback<T> = Widget Function(
100108
BuildContext context,
101109
T value,

lib/src/widgets/chat_bubble_widget.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import 'package:flutter/material.dart';
2525
import '../extensions/extensions.dart';
2626
import '../models/config_models/feature_active_config.dart';
2727
import '../utils/constants/constants.dart';
28+
import '../utils/package_strings.dart';
2829
import '../values/enumeration.dart';
2930
import '../values/typedefs.dart';
3031
import 'chat_view_inherited_widget.dart';
@@ -273,6 +274,22 @@ class _ChatBubbleWidgetState extends State<ChatBubbleWidget> {
273274
onTap: () => widget.onReplyTap
274275
?.call(widget.message.replyMessage.messageId),
275276
),
277+
if (widget.message.updateAt != null)
278+
Padding(
279+
padding: EdgeInsets.only(
280+
left: isMessageBySender ? 0 : 8,
281+
right: isMessageBySender ? 8 : 0,
282+
bottom: 2,
283+
),
284+
child: Text(
285+
PackageStrings.currentLocale.edited,
286+
style: const TextStyle(
287+
fontSize: 11,
288+
color: Colors.grey,
289+
fontStyle: FontStyle.italic,
290+
),
291+
),
292+
),
276293
SwipeToReply(
277294
isMessageByCurrentUser: isMessageBySender,
278295
onSwipe: isMessageBySender ? onLeftSwipe : onRightSwipe,

lib/src/widgets/chat_list_widget.dart

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class ChatListWidget extends StatefulWidget {
3434
Key? key,
3535
required this.chatController,
3636
required this.assignReplyMessage,
37+
this.assignEditMessage,
3738
this.loadingWidget,
3839
this.loadMoreData,
3940
this.isLastPage,
@@ -58,6 +59,9 @@ class ChatListWidget extends StatefulWidget {
5859
/// bubble.
5960
final ValueSetter<Message> assignReplyMessage;
6061

62+
/// Provides callback for entering edit mode for a message.
63+
final ValueSetter<Message>? assignEditMessage;
64+
6165
/// Provides callback when user tap anywhere on whole chat.
6266
final VoidCallback? onChatListTap;
6367

@@ -187,12 +191,43 @@ class _ChatListWidgetState extends State<ChatListWidget> {
187191
ScaffoldMessenger.of(context).hideCurrentSnackBar();
188192
replyPopup?.onReplyTap?.call(message);
189193
},
194+
onEditTap: _buildEditTapHandler(
195+
message: message,
196+
sentByCurrentUser: sentByCurrentUser,
197+
replyPopup: replyPopup,
198+
),
190199
sentByCurrentUser: sentByCurrentUser,
191200
),
192201
),
193202
).closed;
194203
}
195204

205+
VoidCallback? _buildEditTapHandler({
206+
required Message message,
207+
required bool sentByCurrentUser,
208+
required ReplyPopupConfiguration? replyPopup,
209+
}) {
210+
if (!sentByCurrentUser) return null;
211+
if (!(featureActiveConfig?.enableMessageEditing ?? true)) return null;
212+
213+
// Only enable Edit when we can actually enter edit mode.
214+
if (widget.assignEditMessage == null) return null;
215+
216+
return () => _handleEditTap(message, replyPopup);
217+
}
218+
219+
void _handleEditTap(
220+
Message message,
221+
ReplyPopupConfiguration? replyPopup,
222+
) {
223+
widget.assignEditMessage?.call(message);
224+
if (featureActiveConfig?.enableReactionPopup ?? false) {
225+
chatViewIW?.showPopUp.value = false;
226+
}
227+
ScaffoldMessenger.of(context).hideCurrentSnackBar();
228+
replyPopup?.onEditTap?.call(message);
229+
}
230+
196231
void _onChatListTap() {
197232
widget.onChatListTap?.call();
198233
if (!kIsWeb && (Platform.isIOS || Platform.isAndroid)) {

0 commit comments

Comments
 (0)