11import 'dart:convert' ;
2+ import 'dart:async' ;
23
34import 'package:flutter/material.dart' ;
45import 'package:flutter/services.dart' ;
@@ -15,6 +16,7 @@ import './predicate.dart';
1516
1617typedef SendMessageHandler = void Function (SendMessageEvent event);
1718typedef TranslationToggledHandler = void Function (TranslationToggledEvent event);
19+ typedef LoadingStateHandler = void Function (LoadingState state);
1820
1921class SendMessageEvent {
2022 final ConversationData conversation;
@@ -36,6 +38,8 @@ class TranslationToggledEvent {
3638 isEnabled = json['isEnabled' ];
3739}
3840
41+ enum LoadingState { loading, loaded }
42+
3943/// A messaging UI for just a single conversation.
4044///
4145/// Create a Chatbox through [Session.createChatbox] and then call [mount] to show it.
@@ -57,6 +61,7 @@ class ChatBox extends StatefulWidget {
5761
5862 final SendMessageHandler ? onSendMessage;
5963 final TranslationToggledHandler ? onTranslationToggled;
64+ final LoadingStateHandler ? onLoadingStateChanged;
6065
6166 const ChatBox ({
6267 Key ? key,
@@ -73,6 +78,7 @@ class ChatBox extends StatefulWidget {
7378 this .asGuest,
7479 this .onSendMessage,
7580 this .onTranslationToggled,
81+ this .onLoadingStateChanged,
7682 }) : super (key: key);
7783
7884 @override
@@ -121,6 +127,10 @@ class ChatBoxState extends State<ChatBox> {
121127 // If it's the first time that the widget is built, then build everything
122128 _webViewCreated = true ;
123129
130+ // Here a Timer is needed, as we can't change the widget's state while the widget
131+ // is being constructed, and the callback may very possibly change the state
132+ Timer .run (() => widget.onLoadingStateChanged? .call (LoadingState .loading));
133+
124134 execute ('let chatBox;' );
125135
126136 _createSession ();
@@ -161,6 +171,7 @@ class ChatBoxState extends State<ChatBox> {
161171 javascriptChannels: < JavascriptChannel > {
162172 JavascriptChannel (name: 'JSCSendMessage' , onMessageReceived: _jscSendMessage),
163173 JavascriptChannel (name: 'JSCTranslationToggled' , onMessageReceived: _jscTranslationToggled),
174+ JavascriptChannel (name: 'JSCLoadingState' , onMessageReceived: _jscLoadingState),
164175 });
165176 }
166177
@@ -180,6 +191,23 @@ class ChatBoxState extends State<ChatBox> {
180191 execute ('options["me"] = $variableName ;' );
181192
182193 execute ('const session = new Talk.Session(options);' );
194+
195+ execute ('''
196+ const observer = new MutationObserver((mutations) => {
197+ for (let mutation of mutations) {
198+ for (let node of mutation.addedNodes) {
199+ if (node.nodeName.toLowerCase().indexOf('iframe') >= 0) {
200+ observer.disconnect();
201+ node.addEventListener('load', () => {
202+ JSCLoadingState.postMessage('loaded');
203+ });
204+ }
205+ }
206+ }
207+ });
208+
209+ observer.observe(document.getElementById('talkjs-container'), {childList: true});
210+ ''' );
183211 }
184212
185213 void _createChatBox () {
@@ -339,6 +367,14 @@ class ChatBoxState extends State<ChatBox> {
339367 widget.onTranslationToggled? .call (TranslationToggledEvent .fromJson (json.decode (message.message)));
340368 }
341369
370+ void _jscLoadingState (JavascriptMessage message) {
371+ if (kDebugMode) {
372+ print ('📗 chatbox._jscLoadingState: ${message .message }' );
373+ }
374+
375+ widget.onLoadingStateChanged? .call (LoadingState .loaded);
376+ }
377+
342378 /// For internal use only. Implementation detail that may change anytime.
343379 ///
344380 /// Return a string with a unique ID
0 commit comments