@@ -2,15 +2,14 @@ import 'dart:convert';
22
33import 'package:flutter/material.dart' ;
44import 'package:flutter/services.dart' ;
5- import 'package:flutter/foundation.dart' show kDebugMode ;
5+ import 'package:flutter/foundation.dart' ;
66
77import 'package:webview_flutter/webview_flutter.dart' ;
88
99import 'package:provider/provider.dart' ;
1010
1111import './session.dart' ;
1212import './conversation.dart' ;
13- import './webview.dart' ;
1413import './chatoptions.dart' ;
1514import './user.dart' ;
1615import './message.dart' ;
@@ -90,6 +89,7 @@ class ChatBox extends StatefulWidget {
9089class ChatBoxState extends State <ChatBox > {
9190 /// Used to control the underlying WebView
9291 WebViewController ? _webViewController;
92+ bool _webViewCreated = false ;
9393
9494 /// List of JavaScript statements that haven't been executed.
9595 final _pending = < String > [];
@@ -100,30 +100,72 @@ class ChatBoxState extends State<ChatBox> {
100100 /// A mapping of user ids to the variable name of the respective JavaScript
101101 /// Talk.User object.
102102 final _users = < String , String > {};
103+ final _userObjs = < String , User > {};
103104
104105 /// A mapping of conversation ids to the variable name of the respective JavaScript
105106 /// Talk.ConversationBuilder object.
106107 final _conversations = < String , String > {};
108+ final _conversationObjs = < String , Conversation > {};
107109
108110 /// Encapsulates the message entry field tied to the currently selected conversation.
109111 // TODO: messageField still needs to be refactored
110112 //late MessageField messageField;
111113
114+ /// Objects stored for comparing changes
115+ ChatBoxOptions ? _oldOptions;
116+ bool ? _oldAsGuest;
117+ Conversation ? _oldConversation;
118+
112119 @override
113120 Widget build (BuildContext context) {
121+ if (kDebugMode) {
122+ print ('📗 chatbox.build (_webViewCreated: $_webViewCreated )' );
123+ }
124+
114125 final sessionState = context.read <SessionState >();
115126
116- _createSession (sessionState);
117- _createChatBox ();
118- _createConversation ();
127+ if (! _webViewCreated) {
128+ // If it's the first time that the widget is built, then build everything
129+ _webViewCreated = true ;
130+
131+ execute ('let chatBox;' );
132+
133+ _createSession (sessionState);
134+ _createChatBox ();
135+ _createConversation ();
119136
120- execute ('chatBox.mount(document.getElementById("talkjs-container"));' );
137+ execute ('chatBox.mount(document.getElementById("talkjs-container"));' );
138+ } else {
139+ // If it's not the first time that the widget is built,
140+ // then check what needs to be rebuilt
141+
142+ // TODO: If something has changed in the Session we should do something
143+
144+ final chatBoxRecreated = _checkRecreateChatBox ();
145+
146+ if (chatBoxRecreated) {
147+ _createConversation ();
148+ } else {
149+ _checkRecreateConversation ();
150+ }
121151
122- return ChatWebView (_webViewCreatedCallback, _onPageFinished, < JavascriptChannel > {
123- JavascriptChannel (name: 'JSCBlur' , onMessageReceived: _jscBlur),
124- JavascriptChannel (name: 'JSCFocus' , onMessageReceived: _jscFocus),
125- JavascriptChannel (name: 'JSCSendMessage' , onMessageReceived: _jscSendMessage),
126- JavascriptChannel (name: 'JSCTranslationToggled' , onMessageReceived: _jscTranslationToggled),
152+ // Mount the chatbox only if it's new (else the existing chatbox has already been mounted)
153+ if (chatBoxRecreated) {
154+ execute ('chatBox.mount(document.getElementById("talkjs-container"));' );
155+ }
156+ }
157+
158+ return WebView (
159+ initialUrl: 'about:blank' ,
160+ javascriptMode: JavascriptMode .unrestricted,
161+ debuggingEnabled: kDebugMode,
162+ onWebViewCreated: _webViewCreatedCallback,
163+ onPageFinished: _onPageFinished,
164+ javascriptChannels: < JavascriptChannel > {
165+ JavascriptChannel (name: 'JSCBlur' , onMessageReceived: _jscBlur),
166+ JavascriptChannel (name: 'JSCFocus' , onMessageReceived: _jscFocus),
167+ JavascriptChannel (name: 'JSCSendMessage' , onMessageReceived: _jscSendMessage),
168+ JavascriptChannel (name: 'JSCTranslationToggled' , onMessageReceived: _jscTranslationToggled),
127169 });
128170 }
129171
@@ -146,7 +188,7 @@ class ChatBoxState extends State<ChatBox> {
146188 }
147189
148190 void _createChatBox () {
149- final options = ChatBoxOptions (
191+ _oldOptions = ChatBoxOptions (
150192 chatSubtitleMode: widget.chatSubtitleMode,
151193 chatTitleMode: widget.chatTitleMode,
152194 dir: widget.dir,
@@ -159,30 +201,70 @@ class ChatBoxState extends State<ChatBox> {
159201 conversationIdsToTranslate: widget.conversationIdsToTranslate,
160202 );
161203
162- execute ('const chatBox = session.createChatbox(${options .getJsonString (this )});' );
204+ execute ('chatBox = session.createChatbox(${_oldOptions ! .getJsonString (this )});' );
163205
164206 execute ('chatBox.on("blur", (event) => JSCBlur.postMessage(JSON.stringify(event)));' );
165207 execute ('chatBox.on("focus", (event) => JSCFocus.postMessage(JSON.stringify(event)));' );
166208 execute ('chatBox.on("sendMessage", (event) => JSCSendMessage.postMessage(JSON.stringify(event)));' );
167209 execute ('chatBox.on("translationToggled", (event) => JSCTranslationToggled.postMessage(JSON.stringify(event)));' );
168210 }
169211
212+ bool _checkRecreateChatBox () {
213+ final options = ChatBoxOptions (
214+ chatSubtitleMode: widget.chatSubtitleMode,
215+ chatTitleMode: widget.chatTitleMode,
216+ dir: widget.dir,
217+ messageField: widget.messageField,
218+ showChatHeader: widget.showChatHeader,
219+ showTranslationToggle: widget.showTranslationToggle,
220+ theme: widget.theme,
221+ translateConversations: widget.translateConversations,
222+ conversationsToTranslate: widget.conversationsToTranslate,
223+ conversationIdsToTranslate: widget.conversationIdsToTranslate,
224+ );
225+
226+ if (options != _oldOptions) {
227+ execute ('chatBox.destroy();' );
228+ _createChatBox ();
229+
230+ return true ;
231+ } else {
232+ return false ;
233+ }
234+ }
235+
170236 void _createConversation () {
171237 final result = < String , dynamic > {};
172238
173- if (widget.asGuest != null ) {
174- result['asGuest' ] = widget.asGuest;
239+ _oldAsGuest = widget.asGuest;
240+ if (_oldAsGuest != null ) {
241+ result['asGuest' ] = _oldAsGuest;
175242 }
176243
177- if (widget.conversation != null ) {
178- execute ('chatBox.select(${getConversationVariableName (widget .conversation !)}, ${json .encode (result )});' );
244+ _oldConversation = widget.conversation;
245+ if (_oldConversation != null ) {
246+ execute ('chatBox.select(${getConversationVariableName (_oldConversation !)}, ${json .encode (result )});' );
179247 } else {
180248 // TODO: null or undefined?
181249 execute ('chatBox.select(null, ${json .encode (result )});' );
182250 }
183251 }
184252
253+ bool _checkRecreateConversation () {
254+ if ((widget.asGuest != _oldAsGuest) || (widget.conversation != _oldConversation)) {
255+ _createConversation ();
256+
257+ return true ;
258+ }
259+
260+ return false ;
261+ }
262+
185263 void _webViewCreatedCallback (WebViewController webViewController) async {
264+ if (kDebugMode) {
265+ print ('📗 chatbox._webViewCreatedCallback' );
266+ }
267+
186268 String htmlData = await rootBundle.loadString ('packages/talkjs/assets/index.html' );
187269 Uri uri = Uri .dataFromString (htmlData, mimeType: 'text/html' , encoding: Encoding .getByName ('utf-8' ));
188270 webViewController.loadUrl (uri.toString ());
@@ -191,6 +273,10 @@ class ChatBoxState extends State<ChatBox> {
191273 }
192274
193275 void _onPageFinished (String url) {
276+ if (kDebugMode) {
277+ print ('📗 chatbox._onPageFinished' );
278+ }
279+
194280 if (url != 'about:blank' ) {
195281 // Wait for TalkJS to be ready
196282 // Not all WebViews support top level await, so it's better to use an
@@ -266,8 +352,17 @@ class ChatBoxState extends State<ChatBox> {
266352 // Generate unique variable name
267353 final variableName = 'user${getUniqueId ()}' ;
268354
269- execute ('const $variableName = new Talk.User(${user .getJsonString ()});' );
270355 _users[user.id] = variableName;
356+
357+ execute ('let $variableName = new Talk.User(${user .getJsonString ()});' );
358+
359+ _userObjs[user.id] = User .of (user);
360+ } else if (_userObjs[user.id] != user) {
361+ final variableName = _users[user.id]! ;
362+
363+ execute ('$variableName = new Talk.User(${user .getJsonString ()});' );
364+
365+ _userObjs[user.id] = User .of (user);
271366 }
272367
273368 return _users[user.id]! ;
@@ -276,54 +371,70 @@ class ChatBoxState extends State<ChatBox> {
276371 /// For internal use only. Implementation detail that may change anytime.
277372 String getConversationVariableName (Conversation conversation) {
278373 if (_conversations[conversation.id] == null ) {
279- // STEP 1: Generate unique variable name
280374 final variableName = 'conversation${getUniqueId ()}' ;
281375
282- execute ( 'const $ variableName = session.getOrCreateConversation("${ conversation .id }")' ) ;
376+ _conversations[ conversation.id] = variableName ;
283377
284- // STEP 2: Attributes
285- final attributes = < String , dynamic > {};
378+ execute ('let $variableName = session.getOrCreateConversation("${conversation .id }")' );
286379
287- if (conversation.custom != null ) {
288- attributes['custom' ] = conversation.custom;
289- }
380+ _setConversationAttributes (variableName, conversation);
381+ _setConversationParticipants (variableName, conversation);
290382
291- if ( conversation.welcomeMessages != null ) {
292- attributes[ 'welcomeMessages' ] = conversation.welcomeMessages;
293- }
383+ _conversationObjs[ conversation.id] = Conversation . of (conversation);
384+ } else if (_conversationObjs[conversation.id] ! = conversation) {
385+ final variableName = _conversations[conversation.id] ! ;
294386
295- if (conversation.photoUrl != null ) {
296- attributes['photoUrl' ] = conversation.photoUrl;
297- }
387+ _setConversationAttributes (variableName, conversation);
298388
299- if (conversation.subject != null ) {
300- attributes[ 'subject' ] = conversation.subject ;
389+ if (! setEquals ( conversation.participants, _conversationObjs[conversation.id] ! .participants) ) {
390+ _setConversationParticipants (variableName, conversation) ;
301391 }
302392
303- if (attributes.isNotEmpty) {
304- execute ('$variableName .setAttributes(${json .encode (attributes )});' );
305- }
393+ _conversationObjs[conversation.id] = Conversation .of (conversation);
394+ }
306395
307- // STEP 3: Participants
308- for (var participant in conversation.participants) {
309- final userVariableName = getUserVariableName (participant.user);
310- final result = < String , dynamic > {};
396+ return _conversations[conversation.id]! ;
397+ }
311398
312- if (participant.access != null ) {
313- result['access' ] = participant.access! .getValue ();
314- }
399+ void _setConversationAttributes (String variableName, Conversation conversation) {
400+ final attributes = < String , dynamic > {};
315401
316- if (participant.notify != null ) {
317- result[ 'notify ' ] = participant.notify ;
318- }
402+ if (conversation.custom != null ) {
403+ attributes[ 'custom ' ] = conversation.custom ;
404+ }
319405
320- execute ('$variableName .setParticipant($userVariableName , ${json .encode (result )});' );
321- }
406+ if (conversation.welcomeMessages != null ) {
407+ attributes['welcomeMessages' ] = conversation.welcomeMessages;
408+ }
322409
323- _conversations[conversation.id] = variableName;
410+ if (conversation.photoUrl != null ) {
411+ attributes['photoUrl' ] = conversation.photoUrl;
324412 }
325413
326- return _conversations[conversation.id]! ;
414+ if (conversation.subject != null ) {
415+ attributes['subject' ] = conversation.subject;
416+ }
417+
418+ if (attributes.isNotEmpty) {
419+ execute ('$variableName .setAttributes(${json .encode (attributes )});' );
420+ }
421+ }
422+
423+ void _setConversationParticipants (String variableName, Conversation conversation) {
424+ for (var participant in conversation.participants) {
425+ final userVariableName = getUserVariableName (participant.user);
426+ final result = < String , dynamic > {};
427+
428+ if (participant.access != null ) {
429+ result['access' ] = participant.access! .getValue ();
430+ }
431+
432+ if (participant.notify != null ) {
433+ result['notify' ] = participant.notify;
434+ }
435+
436+ execute ('$variableName .setParticipant($userVariableName , ${json .encode (result )});' );
437+ }
327438 }
328439
329440 /// For internal use only. Implementation detail that may change anytime.
@@ -342,37 +453,6 @@ class ChatBoxState extends State<ChatBox> {
342453 this ._pending.add (statement);
343454 }
344455 }
345-
346- /// Destroys this UI element and removes all event listeners it has running.
347- void destroy () {
348- execute ('chatBox.destroy();' );
349- }
350-
351- /*
352- void select(ConversationBuilder? conversation, {bool? asGuest}) {
353- final result = <String, dynamic>{};
354-
355- if (asGuest != null) {
356- result['asGuest'] = asGuest;
357- }
358-
359- if (conversation != null) {
360- execute('chatBox.select(${conversation.variableName}, ${json.encode(result)});');
361- } else {
362- execute('chatBox.select(null, ${json.encode(result)});');
363- }
364- }
365-
366- void selectLatestConversation({bool? asGuest}) {
367- final result = <String, dynamic>{};
368-
369- if (asGuest != null) {
370- result['asGuest'] = asGuest;
371- }
372-
373- execute('chatBox.select(undefined, ${json.encode(result)});');
374- }
375- */
376456}
377457
378458/// Encapsulates the message entry field tied to the currently selected conversation.
0 commit comments