@@ -6,40 +6,135 @@ import 'package:flutter/foundation.dart' show kDebugMode;
66
77import 'package:webview_flutter/webview_flutter.dart' ;
88
9+ import 'package:provider/provider.dart' ;
10+
911import './session.dart' ;
1012import './conversation.dart' ;
1113import './webview.dart' ;
14+ import './chatoptions.dart' ;
15+ import './user.dart' ;
1216
1317/// A messaging UI for just a single conversation.
1418///
1519/// Create a Chatbox through [Session.createChatbox] and then call [mount] to show it.
1620/// There is no way for the user to switch between conversations
17- class ChatBox {
21+ class ChatBox extends StatefulWidget {
22+ final ChatMode ? chatSubtitleMode;
23+ final ChatMode ? chatTitleMode;
24+ final TextDirection ? dir;
25+ final MessageFieldOptions ? messageField;
26+ final bool ? showChatHeader;
27+ final TranslationToggle ? showTranslationToggle;
28+ final String ? theme;
29+ final TranslateConversations ? translateConversations;
30+ final List <Conversation >? conversationsToTranslate;
31+ final List <String >? conversationIdsToTranslate;
32+
33+ final Conversation ? conversation;
34+ final bool ? asGuest;
35+
36+ const ChatBox ({Key ? key,
37+ this .chatSubtitleMode,
38+ this .chatTitleMode,
39+ this .dir,
40+ this .messageField,
41+ this .showChatHeader,
42+ this .showTranslationToggle,
43+ this .theme,
44+ this .translateConversations,
45+ this .conversationsToTranslate,
46+ this .conversationIdsToTranslate,
47+ this .conversation,
48+ this .asGuest,
49+ }) : super (key: key);
50+
51+ @override
52+ State <ChatBox > createState () => _ChatBoxState ();
53+ }
54+
55+ class _ChatBoxState extends State <ChatBox > {
1856 /// Used to control the underlying WebView
1957 WebViewController ? _webViewController;
2058
2159 /// List of JavaScript statements that haven't been executed.
2260 final List <String > _pending = [];
2361
24- bool _mounted = false ;
62+ /// A mapping of user ids to the variable name of the respective JavaScript
63+ /// Talk.User object.
64+ final _users = < String , String > {};
2565
26- /// For internal use only. Implementation detail that may change anytime.
27- ///
28- /// The current active TalkJS session.
29- Session session;
66+ /// A mapping of conversation ids to the variable name of the respective JavaScript
67+ /// Talk.ConversationBuilder object.
68+ final _conversations = < String , String > {};
3069
31- /// For internal use only. Implementation detail that may change anytime.
32- ///
33- /// The JavaScript variable name for this object.
34- String variableName;
70+ // A counter to ensure that IDs are unique
71+ int _idCounter = 0 ;
3572
3673 /// Encapsulates the message entry field tied to the currently selected conversation.
37- late MessageField messageField;
74+ // TODO: messageField still needs to be refactored
75+ //late MessageField messageField;
76+
77+ @override
78+ Widget build (BuildContext context) {
79+ final sessionState = context.read <SessionState >();
80+
81+ _createSession (sessionState);
82+ _createChatBox ();
83+ _createConversation ();
84+
85+ execute ('chatBox.mount(document.getElementById("talkjs-container"));' );
86+
87+ return ChatWebView (_webViewCreatedCallback, _onPageFinished);
88+ }
89+
90+ void _createSession (SessionState sessionState) {
91+ // Initialize Session object
92+ final options = < String , dynamic > {};
93+
94+ options['appId' ] = sessionState.appId;
95+
96+ if (sessionState.signature != null ) {
97+ options["signature" ] = sessionState.signature;
98+ }
99+
100+ execute ('const options = ${json .encode (options )};' );
101+
102+ final variableName = getUserVariableName (sessionState.me);
103+ execute ('options["me"] = $variableName ;' );
38104
39- ChatBox ({required this .session, required this .variableName}) {
40- // TODO: It wouldn't be a bad idea to break the ChatBox<->MessageField circular reference
41- // at object destruction (if possible)
42- this .messageField = MessageField (chatbox: this , variableName: "$variableName .messageField" );
105+ execute ('const session = new Talk.Session(options);' );
106+ }
107+
108+ void _createChatBox () {
109+ final options = ChatBoxOptions (
110+ chatSubtitleMode: widget.chatSubtitleMode,
111+ chatTitleMode: widget.chatTitleMode,
112+ dir: widget.dir,
113+ messageField: widget.messageField,
114+ showChatHeader: widget.showChatHeader,
115+ showTranslationToggle: widget.showTranslationToggle,
116+ theme: widget.theme,
117+ translateConversations: widget.translateConversations,
118+ conversationsToTranslate: widget.conversationsToTranslate,
119+ conversationIdsToTranslate: widget.conversationIdsToTranslate,
120+ );
121+
122+ execute ('const chatBox = session.createChatbox(${options .getJsonString ()});' );
123+ }
124+
125+ void _createConversation () {
126+ final result = < String , dynamic > {};
127+
128+ if (widget.asGuest != null ) {
129+ result['asGuest' ] = widget.asGuest;
130+ }
131+
132+ if (widget.conversation != null ) {
133+ execute ('chatBox.select(${getConversationVariableName (widget .conversation !)}, ${json .encode (result )});' );
134+ } else {
135+ // TODO: null or undefined?
136+ execute ('chatBox.select(null, ${json .encode (result )});' );
137+ }
43138 }
44139
45140 void _webViewCreatedCallback (WebViewController webViewController) async {
@@ -74,6 +169,85 @@ class ChatBox {
74169 }
75170 }
76171
172+ /// For internal use only. Implementation detail that may change anytime.
173+ ///
174+ /// Return a string with a unique ID
175+ String getUniqueId () {
176+ final id = _idCounter;
177+
178+ _idCounter += 1 ;
179+
180+ return '_$id ' ;
181+ }
182+
183+ /// For internal use only. Implementation detail that may change anytime.
184+ ///
185+ /// Returns the JavaScript variable name of the Talk.User object associated
186+ /// with the given [User]
187+ String getUserVariableName (User user) {
188+ if (_users[user.id] == null ) {
189+ // Generate unique variable name
190+ final variableName = 'user${getUniqueId ()}' ;
191+
192+ execute ('const $variableName = new Talk.User(${user .getJsonString ()});' );
193+ _users[user.id] = variableName;
194+ }
195+
196+ return _users[user.id]! ;
197+ }
198+
199+ String getConversationVariableName (Conversation conversation) {
200+ if (_conversations[conversation.id] == null ) {
201+ // STEP 1: Generate unique variable name
202+ final variableName = 'conversation${getUniqueId ()}' ;
203+
204+ execute ('const $variableName = session.getOrCreateConversation("${conversation .id }")' );
205+
206+ // STEP 2: Attributes
207+ final attributes = < String , dynamic > {};
208+
209+ if (conversation.custom != null ) {
210+ attributes['custom' ] = conversation.custom;
211+ }
212+
213+ if (conversation.welcomeMessages != null ) {
214+ attributes['welcomeMessages' ] = conversation.welcomeMessages;
215+ }
216+
217+ if (conversation.photoUrl != null ) {
218+ attributes['photoUrl' ] = conversation.photoUrl;
219+ }
220+
221+ if (conversation.subject != null ) {
222+ attributes['subject' ] = conversation.subject;
223+ }
224+
225+ if (attributes.isNotEmpty) {
226+ execute ('$variableName .setAttributes(${json .encode (attributes )});' );
227+ }
228+
229+ // STEP 3: Participants
230+ for (var participant in conversation.participants) {
231+ final userVariableName = getUserVariableName (participant.user);
232+ final result = < String , dynamic > {};
233+
234+ if (participant.access != null ) {
235+ result['access' ] = participant.access! .getValue ();
236+ }
237+
238+ if (participant.notify != null ) {
239+ result['notify' ] = participant.notify;
240+ }
241+
242+ execute ('$variableName .setParticipant($userVariableName , ${json .encode (result )});' );
243+ }
244+
245+ _conversations[conversation.id] = variableName;
246+ }
247+
248+ return _conversations[conversation.id]! ;
249+ }
250+
77251 /// For internal use only. Implementation detail that may change anytime.
78252 ///
79253 /// Evaluates the JavaScript statement given.
@@ -91,19 +265,12 @@ class ChatBox {
91265 }
92266 }
93267
94- void disposeWebView () {
95- if (kDebugMode) {
96- print ('📘 chatbox.disposeWebView' );
97- }
98-
99- _webViewController = null ;
100- }
101-
102268 /// Destroys this UI element and removes all event listeners it has running.
103269 void destroy () {
104- execute ('$ variableName .destroy();' );
270+ execute ('chatBox .destroy();' );
105271 }
106272
273+ /*
107274 void select(ConversationBuilder? conversation, {bool? asGuest}) {
108275 final result = <String, dynamic>{};
109276
@@ -112,9 +279,9 @@ class ChatBox {
112279 }
113280
114281 if (conversation != null) {
115- execute ('$ variableName .select(${conversation .variableName }, ${json .encode (result )});' );
282+ execute('chatBox .select(${conversation.variableName}, ${json.encode(result)});');
116283 } else {
117- execute ('$ variableName .select(null, ${json .encode (result )});' );
284+ execute('chatBox .select(null, ${json.encode(result)});');
118285 }
119286 }
120287
@@ -125,31 +292,15 @@ class ChatBox {
125292 result['asGuest'] = asGuest;
126293 }
127294
128- execute ('$variableName .select(undefined, ${json .encode (result )});' );
129- }
130-
131- /// Renders the UI and returns the Widget containing it.
132- Widget mount () {
133- assert (_webViewController == null );
134-
135- if (kDebugMode) {
136- print ('📘 chatbox.mount' );
137- }
138-
139- if (! _mounted) {
140- execute ('$variableName .mount(document.getElementById("talkjs-container"));' );
141-
142- _mounted = true ;
143- }
144-
145- return ChatWebView (this , _webViewCreatedCallback, _onPageFinished);
295+ execute('chatBox.select(undefined, ${json.encode(result)});');
146296 }
297+ */
147298}
148299
149300/// Encapsulates the message entry field tied to the currently selected conversation.
150301class MessageField {
151302 /// The ChatBox associated with this message field
152- ChatBox chatbox;
303+ _ChatBoxState chatbox;
153304
154305 /// The JavaScript variable name for this object.
155306 String variableName;
0 commit comments