Skip to content

Commit ee57d03

Browse files
authored
Merge pull request #5 from talkjs/fetaure/new-js-event-handlers
Updated JS event handlers and implemented onCustomMessageAction
2 parents 6a90a85 + 271efb8 commit ee57d03

File tree

4 files changed

+174
-4
lines changed

4 files changed

+174
-4
lines changed

lib/src/chatbox.dart

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import './predicate.dart';
1717
typedef SendMessageHandler = void Function(SendMessageEvent event);
1818
typedef TranslationToggledHandler = void Function(TranslationToggledEvent event);
1919
typedef LoadingStateHandler = void Function(LoadingState state);
20+
typedef MessageActionHandler = void Function(MessageActionEvent event);
2021

2122
class SendMessageEvent {
2223
final ConversationData conversation;
@@ -40,6 +41,15 @@ class TranslationToggledEvent {
4041

4142
enum LoadingState { loading, loaded }
4243

44+
class MessageActionEvent {
45+
final String action;
46+
final Message message;
47+
48+
MessageActionEvent.fromJson(Map<String, dynamic> json)
49+
: action = json['action'],
50+
message = Message.fromJson(json['message']);
51+
}
52+
4353
/// A messaging UI for just a single conversation.
4454
///
4555
/// Create a Chatbox through [Session.createChatbox] and then call [mount] to show it.
@@ -62,6 +72,7 @@ class ChatBox extends StatefulWidget {
6272
final SendMessageHandler? onSendMessage;
6373
final TranslationToggledHandler? onTranslationToggled;
6474
final LoadingStateHandler? onLoadingStateChanged;
75+
final Map<String, MessageActionHandler>? onCustomMessageAction;
6576

6677
const ChatBox({
6778
Key? key,
@@ -79,6 +90,7 @@ class ChatBox extends StatefulWidget {
7990
this.onSendMessage,
8091
this.onTranslationToggled,
8192
this.onLoadingStateChanged,
93+
this.onCustomMessageAction,
8294
}) : super(key: key);
8395

8496
@override
@@ -116,6 +128,7 @@ class ChatBoxState extends State<ChatBox> {
116128
MessagePredicate _oldMessageFilter = const MessagePredicate();
117129
bool? _oldAsGuest;
118130
Conversation? _oldConversation;
131+
Set<String> _oldCustomActions = {};
119132

120133
@override
121134
Widget build(BuildContext context) {
@@ -132,6 +145,11 @@ class ChatBoxState extends State<ChatBox> {
132145
Timer.run(() => widget.onLoadingStateChanged?.call(LoadingState.loading));
133146

134147
execute('let chatBox;');
148+
execute('''
149+
function customMessageActionHandler(event) {
150+
JSCCustomMessageAction.postMessage(JSON.stringify(event));
151+
}
152+
''');
135153

136154
_createSession();
137155
_createChatBox();
@@ -151,6 +169,7 @@ class ChatBoxState extends State<ChatBox> {
151169
// messageFilter and highlightedWords are set as options for the chatbox
152170
_createConversation();
153171
} else {
172+
_checkActionHandlers();
154173
_checkMessageFilter();
155174
_checkHighlightedWords();
156175
_checkRecreateConversation();
@@ -172,6 +191,7 @@ class ChatBoxState extends State<ChatBox> {
172191
JavascriptChannel(name: 'JSCSendMessage', onMessageReceived: _jscSendMessage),
173192
JavascriptChannel(name: 'JSCTranslationToggled', onMessageReceived: _jscTranslationToggled),
174193
JavascriptChannel(name: 'JSCLoadingState', onMessageReceived: _jscLoadingState),
194+
JavascriptChannel(name: 'JSCCustomMessageAction', onMessageReceived: _jscCustomMessageAction),
175195
});
176196
}
177197

@@ -208,8 +228,17 @@ class ChatBoxState extends State<ChatBox> {
208228

209229
execute('chatBox = session.createChatbox(${_oldOptions!.getJsonString(this)});');
210230

211-
execute('chatBox.on("sendMessage", (event) => JSCSendMessage.postMessage(JSON.stringify(event)));');
212-
execute('chatBox.on("translationToggled", (event) => JSCTranslationToggled.postMessage(JSON.stringify(event)));');
231+
execute('chatBox.onSendMessage((event) => JSCSendMessage.postMessage(JSON.stringify(event)));');
232+
execute('chatBox.onTranslationToggled((event) => JSCTranslationToggled.postMessage(JSON.stringify(event)));');
233+
234+
if (widget.onCustomMessageAction != null) {
235+
_oldCustomActions = Set<String>.of(widget.onCustomMessageAction!.keys);
236+
for (var action in _oldCustomActions) {
237+
execute('chatBox.onCustomMessageAction("$action", customMessageActionHandler);');
238+
}
239+
} else {
240+
_oldCustomActions = {};
241+
}
213242
}
214243

215244
bool _checkRecreateChatBox() {
@@ -232,6 +261,38 @@ class ChatBoxState extends State<ChatBox> {
232261
}
233262
}
234263

264+
bool _checkActionHandlers() {
265+
// If there are no handlers specified, then we don't need to create new handlers
266+
if (widget.onCustomMessageAction == null) {
267+
return false;
268+
}
269+
270+
var customActions = Set<String>.of(widget.onCustomMessageAction!.keys);
271+
272+
if (!setEquals(customActions, _oldCustomActions)) {
273+
var retval = false;
274+
275+
// Register only the new event handlers
276+
//
277+
// Possible memory leak: old event handlers are not getting unregistered
278+
// This should not be a big problem in practice, as it is *very* rare that
279+
// custom message handlers are being constantly changed
280+
for (var action in customActions) {
281+
if (!_oldCustomActions.contains(action)) {
282+
_oldCustomActions.add(action);
283+
284+
execute('chatBox.onCustomMessageAction("$action", customMessageActionHandler);');
285+
286+
retval = true;
287+
}
288+
}
289+
290+
return retval;
291+
} else {
292+
return false;
293+
}
294+
}
295+
235296
void _createConversation() {
236297
final result = <String, dynamic>{};
237298

@@ -358,6 +419,17 @@ class ChatBoxState extends State<ChatBox> {
358419
widget.onLoadingStateChanged?.call(LoadingState.loaded);
359420
}
360421

422+
void _jscCustomMessageAction(JavascriptMessage message) {
423+
if (kDebugMode) {
424+
print('📗 chatbox._jscCustomMessageAction: ${message.message}');
425+
}
426+
427+
Map<String, dynamic> jsonMessage = json.decode(message.message);
428+
String action = jsonMessage['action'];
429+
430+
widget.onCustomMessageAction?[action]?.call(MessageActionEvent.fromJson(jsonMessage));
431+
}
432+
361433
/// For internal use only. Implementation detail that may change anytime.
362434
///
363435
/// Return a string with a unique ID

lib/src/conversationlist.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ class ConversationListState extends State<ConversationList> {
182182

183183
execute('const conversationList = session.createInbox(${options.getJsonString(this)});');
184184

185-
execute('''conversationList.on("selectConversation", (event) => {
185+
execute('''conversationList.onSelectConversation((event) => {
186186
event.preventDefault();
187187
JSCSelectConversation.postMessage(JSON.stringify(event));
188188
}); ''');

lib/src/message.dart

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
import './conversation.dart';
2+
import './predicate.dart';
3+
import './user.dart';
14

25
enum MessageType { UserMessage, SystemMessage }
36

7+
enum ContentType { media, text, location }
8+
49
class Attachment {
510
final String url;
611
final int size;
@@ -49,3 +54,89 @@ class SentMessage {
4954
location = (json['location'] != null ? List<double>.from(json['location']) : null);
5055
}
5156

57+
class Message {
58+
/// Only given if the message contains a file. An object with the URL and filesize (in bytes) of the given file.
59+
final Attachment? attachment;
60+
61+
/// The message's content
62+
final String body;
63+
64+
/// The ConversationData that the message belongs to
65+
final ConversationData conversation;
66+
67+
/// Custom metadata for this conversation
68+
final Map<String, String?>? custom;
69+
70+
/// The message ID of the message that was sent
71+
final String id;
72+
73+
/// 'true' if the message was sent by the current user
74+
final bool isByMe;
75+
76+
/// Only given if the message contains a location. An array of two numbers which represent the longitude and latitude of this location, respectively. Only given if this message is a shared location.
77+
///
78+
/// Example:
79+
/// [51.481083, -3.178306]
80+
final List<double>? location;
81+
82+
// Determines how this message was sent
83+
final MessageOrigin origin;
84+
85+
/// 'true' if the message has been read, 'false' has not been seen yet
86+
final bool read;
87+
88+
/// The User that sent the message
89+
final UserData? sender;
90+
91+
/// Contains the user ID for the person that sent the message
92+
final String? senderId;
93+
94+
/// UNIX timestamp specifying when the message was sent (UTC, in milliseconds)
95+
final double timestamp;
96+
97+
/// Specifies if if the message is media (file), text or a shared location
98+
final ContentType type;
99+
100+
Message.fromJson(Map<String, dynamic> json)
101+
: attachment = (json['attachment'] != null ? Attachment.fromJson(json['attachment']) : null),
102+
body = json['body'],
103+
conversation = ConversationData.fromJson(json['conversation']),
104+
custom = (json['custom'] != null ? Map<String, String?>.from(json['custom']) : null),
105+
id = json['id'],
106+
isByMe = json['isByMe'],
107+
location = (json['location'] != null ? List<double>.from(json['location']) : null),
108+
origin = _originFromString(json['origin']),
109+
read = json['read'],
110+
sender = (json['sender'] != null ? UserData.fromJson(json['sender']) : null),
111+
senderId = json['senderId'],
112+
timestamp = json['timestamp'].toDouble(),
113+
type = _contentTypeFromString(json['type']);
114+
}
115+
116+
MessageOrigin _originFromString(String str) {
117+
switch (str) {
118+
case 'web':
119+
return MessageOrigin.web;
120+
case 'rest':
121+
return MessageOrigin.rest;
122+
case 'email':
123+
return MessageOrigin.email;
124+
case 'import':
125+
return MessageOrigin.import;
126+
default:
127+
throw ArgumentError('Unknown MessageOrigin $str');
128+
}
129+
}
130+
131+
ContentType _contentTypeFromString(String str) {
132+
switch (str) {
133+
case 'media':
134+
return ContentType.media;
135+
case 'text':
136+
return ContentType.text;
137+
case 'location':
138+
return ContentType.location;
139+
default:
140+
throw ArgumentError('Unknown ContentType $str');
141+
}
142+
}

pubspec.lock

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ packages:
7474
url: "https://pub.dartlang.org"
7575
source: hosted
7676
version: "0.12.11"
77+
material_color_utilities:
78+
dependency: transitive
79+
description:
80+
name: material_color_utilities
81+
url: "https://pub.dartlang.org"
82+
source: hosted
83+
version: "0.1.3"
7784
meta:
7885
dependency: transitive
7986
description:
@@ -141,7 +148,7 @@ packages:
141148
name: test_api
142149
url: "https://pub.dartlang.org"
143150
source: hosted
144-
version: "0.4.3"
151+
version: "0.4.8"
145152
typed_data:
146153
dependency: transitive
147154
description:

0 commit comments

Comments
 (0)