Skip to content

Commit 39b4e11

Browse files
author
Franco Bugnano
committed
Started refactoring to make the API declarative
1 parent 40b7558 commit 39b4e11

File tree

7 files changed

+287
-221
lines changed

7 files changed

+287
-221
lines changed

lib/src/chatbox.dart

Lines changed: 196 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,40 +6,135 @@ import 'package:flutter/foundation.dart' show kDebugMode;
66

77
import 'package:webview_flutter/webview_flutter.dart';
88

9+
import 'package:provider/provider.dart';
10+
911
import './session.dart';
1012
import './conversation.dart';
1113
import './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.
150301
class 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;

lib/src/chatoptions.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ abstract class _ChatOptions {
176176
TranslateConversations? translateConversations;
177177

178178
/// This option specifies which conversations should be translated in this UI.
179-
List<ConversationBuilder>? conversationsToTranslate;
179+
List<Conversation>? conversationsToTranslate;
180180

181181
/// This option specifies which conversation Ids should be translated in this UI.
182182
List<String>? conversationIdsToTranslate;
@@ -232,6 +232,7 @@ abstract class _ChatOptions {
232232
result['theme'] = theme;
233233
}
234234

235+
/* TODO: Conversation.variableName is to be done
235236
if (conversationsToTranslate != null) {
236237
// Highest priority: TranslateConversations.off
237238
if (translateConversations != TranslateConversations.off) {
@@ -243,6 +244,7 @@ abstract class _ChatOptions {
243244
+ ']';
244245
}
245246
}
247+
*/
246248

247249
if (conversationIdsToTranslate != null) {
248250
// Highest priority: TranslateConversations.off

0 commit comments

Comments
 (0)