From 1ae697202f3074950369de608c4673cb802fa910 Mon Sep 17 00:00:00 2001 From: Franco Bugnano Date: Thu, 3 Mar 2022 11:32:54 +0100 Subject: [PATCH 1/2] Implemented the onLoadingStateChanged callback --- lib/src/chatbox.dart | 36 +++++++++++++++++++++++++++++++++++ lib/src/conversationlist.dart | 34 +++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/lib/src/chatbox.dart b/lib/src/chatbox.dart index a2b7c06..fe25f5c 100644 --- a/lib/src/chatbox.dart +++ b/lib/src/chatbox.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -15,6 +16,7 @@ import './predicate.dart'; typedef SendMessageHandler = void Function(SendMessageEvent event); typedef TranslationToggledHandler = void Function(TranslationToggledEvent event); +typedef LoadingStateHandler = void Function(LoadingState state); class SendMessageEvent { final ConversationData conversation; @@ -36,6 +38,8 @@ class TranslationToggledEvent { isEnabled = json['isEnabled']; } +enum LoadingState { loading, loaded } + /// A messaging UI for just a single conversation. /// /// Create a Chatbox through [Session.createChatbox] and then call [mount] to show it. @@ -57,6 +61,7 @@ class ChatBox extends StatefulWidget { final SendMessageHandler? onSendMessage; final TranslationToggledHandler? onTranslationToggled; + final LoadingStateHandler? onLoadingStateChanged; const ChatBox({ Key? key, @@ -73,6 +78,7 @@ class ChatBox extends StatefulWidget { this.asGuest, this.onSendMessage, this.onTranslationToggled, + this.onLoadingStateChanged, }) : super(key: key); @override @@ -121,6 +127,10 @@ class ChatBoxState extends State { // If it's the first time that the widget is built, then build everything _webViewCreated = true; + // Here a Timer is needed, as we can't change the widget's state while the widget + // is being constructed, and the callback may very possibly change the state + Timer.run(() => widget.onLoadingStateChanged?.call(LoadingState.loading)); + execute('let chatBox;'); _createSession(); @@ -161,6 +171,7 @@ class ChatBoxState extends State { javascriptChannels: { JavascriptChannel(name: 'JSCSendMessage', onMessageReceived: _jscSendMessage), JavascriptChannel(name: 'JSCTranslationToggled', onMessageReceived: _jscTranslationToggled), + JavascriptChannel(name: 'JSCLoadingState', onMessageReceived: _jscLoadingState), }); } @@ -180,6 +191,23 @@ class ChatBoxState extends State { execute('options["me"] = $variableName;'); execute('const session = new Talk.Session(options);'); + + execute(''' + const observer = new MutationObserver((mutations) => { + for (let mutation of mutations) { + for (let node of mutation.addedNodes) { + if (node.nodeName.toLowerCase().indexOf('iframe') >= 0) { + observer.disconnect(); + node.addEventListener('load', () => { + JSCLoadingState.postMessage('loaded'); + }); + } + } + } + }); + + observer.observe(document.getElementById('talkjs-container'), {childList: true}); + '''); } void _createChatBox() { @@ -339,6 +367,14 @@ class ChatBoxState extends State { widget.onTranslationToggled?.call(TranslationToggledEvent.fromJson(json.decode(message.message))); } + void _jscLoadingState(JavascriptMessage message) { + if (kDebugMode) { + print('📗 chatbox._jscLoadingState: ${message.message}'); + } + + widget.onLoadingStateChanged?.call(LoadingState.loaded); + } + /// For internal use only. Implementation detail that may change anytime. /// /// Return a string with a unique ID diff --git a/lib/src/conversationlist.dart b/lib/src/conversationlist.dart index dec4714..353503d 100644 --- a/lib/src/conversationlist.dart +++ b/lib/src/conversationlist.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -10,6 +11,7 @@ import './session.dart'; import './conversation.dart'; import './user.dart'; import './predicate.dart'; +import './chatbox.dart'; typedef SelectConversationHandler = void Function(SelectConversationEvent event); @@ -80,6 +82,7 @@ class ConversationList extends StatefulWidget { final ConversationPredicate feedFilter; final SelectConversationHandler? onSelectConversation; + final LoadingStateHandler? onLoadingStateChanged; const ConversationList({ Key? key, @@ -88,6 +91,7 @@ class ConversationList extends StatefulWidget { this.theme, this.feedFilter = const ConversationPredicate(), this.onSelectConversation, + this.onLoadingStateChanged, }) : super(key: key); @override @@ -121,6 +125,10 @@ class ConversationListState extends State { if (!_webViewCreated) { _webViewCreated = true; + // Here a Timer is needed, as we can't change the widget's state while the widget + // is being constructed, and the callback may very possibly change the state + Timer.run(() => widget.onLoadingStateChanged?.call(LoadingState.loading)); + _createSession(); _createConversationList(); // feedFilter is set as an option for the inbox @@ -142,6 +150,7 @@ class ConversationListState extends State { onPageFinished: _onPageFinished, javascriptChannels: { JavascriptChannel(name: 'JSCSelectConversation', onMessageReceived: _jscSelectConversation), + JavascriptChannel(name: 'JSCLoadingState', onMessageReceived: _jscLoadingState), }); } @@ -161,6 +170,23 @@ class ConversationListState extends State { execute('options["me"] = $variableName;'); execute('const session = new Talk.Session(options);'); + + execute(''' + const observer = new MutationObserver((mutations) => { + for (let mutation of mutations) { + for (let node of mutation.addedNodes) { + if (node.nodeName.toLowerCase().indexOf('iframe') >= 0) { + observer.disconnect(); + node.addEventListener('load', () => { + JSCLoadingState.postMessage('loaded'); + }); + } + } + } + }); + + observer.observe(document.getElementById('talkjs-container'), {childList: true}); + '''); } void _createConversationList() { @@ -243,6 +269,14 @@ class ConversationListState extends State { widget.onSelectConversation?.call(SelectConversationEvent.fromJson(json.decode(message.message))); } + void _jscLoadingState(JavascriptMessage message) { + if (kDebugMode) { + print('📗 conversationlist._jscLoadingState: ${message.message}'); + } + + widget.onLoadingStateChanged?.call(LoadingState.loaded); + } + /// For internal use only. Implementation detail that may change anytime. /// /// Return a string with a unique ID From dd4155db5f451ed52b06c5bd1ce190d5f9e30d1f Mon Sep 17 00:00:00 2001 From: Franco Bugnano Date: Thu, 3 Mar 2022 13:44:55 +0100 Subject: [PATCH 2/2] Changed the MutationObserver hack with awaiting for mount --- lib/src/chatbox.dart | 19 +------------------ lib/src/conversationlist.dart | 19 +------------------ 2 files changed, 2 insertions(+), 36 deletions(-) diff --git a/lib/src/chatbox.dart b/lib/src/chatbox.dart index fe25f5c..407e67c 100644 --- a/lib/src/chatbox.dart +++ b/lib/src/chatbox.dart @@ -138,7 +138,7 @@ class ChatBoxState extends State { // messageFilter and highlightedWords are set as options for the chatbox _createConversation(); - execute('chatBox.mount(document.getElementById("talkjs-container"));'); + execute('chatBox.mount(document.getElementById("talkjs-container")).then(() => JSCLoadingState.postMessage("loaded"));'); } else { // If it's not the first time that the widget is built, // then check what needs to be rebuilt @@ -191,23 +191,6 @@ class ChatBoxState extends State { execute('options["me"] = $variableName;'); execute('const session = new Talk.Session(options);'); - - execute(''' - const observer = new MutationObserver((mutations) => { - for (let mutation of mutations) { - for (let node of mutation.addedNodes) { - if (node.nodeName.toLowerCase().indexOf('iframe') >= 0) { - observer.disconnect(); - node.addEventListener('load', () => { - JSCLoadingState.postMessage('loaded'); - }); - } - } - } - }); - - observer.observe(document.getElementById('talkjs-container'), {childList: true}); - '''); } void _createChatBox() { diff --git a/lib/src/conversationlist.dart b/lib/src/conversationlist.dart index 353503d..da46619 100644 --- a/lib/src/conversationlist.dart +++ b/lib/src/conversationlist.dart @@ -133,7 +133,7 @@ class ConversationListState extends State { _createConversationList(); // feedFilter is set as an option for the inbox - execute('conversationList.mount(document.getElementById("talkjs-container"));'); + execute('conversationList.mount(document.getElementById("talkjs-container")).then(() => JSCLoadingState.postMessage("loaded"));'); } else { // If it's not the first time that the widget is built, // then check what needs to be rebuilt @@ -170,23 +170,6 @@ class ConversationListState extends State { execute('options["me"] = $variableName;'); execute('const session = new Talk.Session(options);'); - - execute(''' - const observer = new MutationObserver((mutations) => { - for (let mutation of mutations) { - for (let node of mutation.addedNodes) { - if (node.nodeName.toLowerCase().indexOf('iframe') >= 0) { - observer.disconnect(); - node.addEventListener('load', () => { - JSCLoadingState.postMessage('loaded'); - }); - } - } - } - }); - - observer.observe(document.getElementById('talkjs-container'), {childList: true}); - '''); } void _createConversationList() {