Skip to content

Commit 09c81c2

Browse files
Merge pull request #24 from sendbird/release/3.12.1
3.12.1
2 parents 2e6956f + db881e6 commit 09c81c2

26 files changed

+149
-262
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# Changelog
2+
### v3.12.1 (Jan 18, 2024) with Chat SDK `v4.14.1`
3+
* Fix memory leaks in UIKit.
24
### v3.12.0 (Jan, 2024) with Chat SDK `v4.13.0`
35
* Added `sendLogImpression(List<BaseMessage>)` in `FeedNotificationChannelViewModel`.
46
* Improved performance of scrolling in Message List.

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ org.gradle.jvmargs=-Xmx1536m
1616
# https://developer.android.com/topic/libraries/support-library/androidx-rn
1717
android.useAndroidX=true
1818

19-
UIKIT_VERSION = 3.12.0
19+
UIKIT_VERSION = 3.12.1
2020
UIKIT_VERSION_CODE = 1

uikit-samples/src/main/java/com/sendbird/uikit/samples/basic/GroupChannelMainActivity.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ class GroupChannelMainActivity : AppCompatActivity() {
9191
})
9292
}
9393

94+
override fun onPause() {
95+
super.onPause()
96+
SendbirdChat.removeUserEventHandler(USER_EVENT_HANDLER_KEY)
97+
}
98+
9499
private fun redirectChannelIfNeeded(intent: Intent?) {
95100
if (intent == null) return
96101
if (intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY == Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) {

uikit-samples/src/main/java/com/sendbird/uikit/samples/common/LoginActivity.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ open class LoginActivity : AppCompatActivity() {
8282
Logger.e(e)
8383
ContextUtils.toastError(this@LoginActivity, "${e.message}")
8484
WaitingDialog.dismiss()
85-
PreferenceUtils.clearAll()
8685
return@authenticate
8786
}
8887
WaitingDialog.dismiss()

uikit-samples/src/main/java/com/sendbird/uikit/samples/notification/NotificationLoginActivity.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ class NotificationLoginActivity : LoginActivity() {
3232
if (e != null) {
3333
Logger.e(e)
3434
ContextUtils.toastError(this@NotificationLoginActivity, "${e.message}")
35-
PreferenceUtils.clearAll()
3635
return@authenticate
3736
}
3837
PreferenceUtils.userId = userId

uikit-samples/src/main/res/values-ko/strings.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@
172172
<string name="text_confirm">확인</string>
173173
<string name="text_select_count">%d명 선택</string>
174174

175-
<string name="app_name">SendbirdUIKitCustomSample</string>
175+
<string name="app_name">UIKitSamples</string>
176176
<string name="text_title_login_activity">Sendbird UIKit Sample</string>
177177

178178
<string name="text_version_info">UI Kit v%1$s &#160;&#160;&#160;&#160; SDK v%2$s</string>

uikit/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ dependencies {
6464
implementation fileTree(dir: 'libs', include: ['*.jar'])
6565

6666
// Sendbird
67-
api 'com.sendbird.sdk:sendbird-chat:4.13.0'
67+
api 'com.sendbird.sdk:sendbird-chat:4.14.1'
6868

6969
implementation 'com.github.bumptech.glide:glide:4.13.0'
7070
annotationProcessor 'com.github.bumptech.glide:compiler:4.13.0'

uikit/src/main/java/com/sendbird/uikit/activities/adapter/FormFieldAdapter.kt

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,23 @@ package com.sendbird.uikit.activities.adapter
33
import android.view.LayoutInflater
44
import android.view.ViewGroup
55
import androidx.recyclerview.widget.DiffUtil
6+
import com.sendbird.android.message.Form
7+
import com.sendbird.android.message.FormField
68
import com.sendbird.uikit.activities.viewholder.BaseViewHolder
79
import com.sendbird.uikit.databinding.SbViewFormFieldBinding
8-
import com.sendbird.uikit.internal.model.Form
9-
import com.sendbird.uikit.internal.model.FormField
10+
import com.sendbird.uikit.internal.extensions.lastValidation
1011

1112
internal class FormFieldAdapter : BaseAdapter<FormField, BaseViewHolder<FormField>>() {
1213
private val formFields: MutableList<FormField> = mutableListOf()
1314

14-
fun isReadyToSubmit(): Boolean {
15-
return formFields.all { it.isReadyToSubmit() }
15+
fun isSubmittable(): Boolean {
16+
return formFields.all { it.isSubmittable }
1617
}
1718

1819
fun updateValidation() {
1920
formFields.forEachIndexed { index, formField ->
2021
val lastValidation = formField.lastValidation
21-
val validation = formField.isReadyToSubmit()
22+
val validation = formField.isSubmittable
2223
formField.lastValidation = validation
2324
if (lastValidation != validation) {
2425
notifyItemChanged(index)
@@ -27,7 +28,7 @@ internal class FormFieldAdapter : BaseAdapter<FormField, BaseViewHolder<FormFiel
2728
}
2829

2930
fun setFormFields(form: Form) {
30-
val newFormFields = if (form.isAnswered) {
31+
val newFormFields = if (form.isSubmitted) {
3132
form.formFields.filter { it.answer != null }
3233
} else {
3334
form.formFields
@@ -83,7 +84,7 @@ internal class FormFieldAdapter : BaseAdapter<FormField, BaseViewHolder<FormFiel
8384
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
8485
val oldItem = oldList[oldItemPosition]
8586
val newItem = newList[newItemPosition]
86-
return oldItem.formFieldKey == newItem.formFieldKey &&
87+
return oldItem.key == newItem.key &&
8788
oldItem.messageId == newItem.messageId
8889
}
8990

uikit/src/main/java/com/sendbird/uikit/activities/adapter/MessageListAdapter.java

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@
1010
import com.sendbird.android.message.BaseMessage;
1111
import com.sendbird.uikit.activities.viewholder.MessageViewHolder;
1212
import com.sendbird.uikit.interfaces.OnItemClickListener;
13-
import com.sendbird.uikit.internal.extensions.MessageListAdapterExtensionsKt;
14-
import com.sendbird.uikit.internal.interfaces.OnSubmitButtonClickListener;
13+
import com.sendbird.uikit.interfaces.FormSubmitButtonClickListener;
1514
import com.sendbird.uikit.internal.ui.viewholders.FormMessageViewHolder;
1615
import com.sendbird.uikit.internal.ui.viewholders.SuggestedRepliesViewHolder;
1716
import com.sendbird.uikit.internal.wrappers.SendbirdUIKitImpl;
@@ -25,6 +24,9 @@ public class MessageListAdapter extends BaseMessageListAdapter {
2524
@Nullable
2625
protected OnItemClickListener<String> suggestedRepliesClickListener;
2726

27+
@Nullable
28+
protected FormSubmitButtonClickListener formSubmitButtonClickListener;
29+
2830
public MessageListAdapter(boolean useMessageGroupUI) {
2931
this(null, useMessageGroupUI);
3032
}
@@ -68,7 +70,7 @@ public void onBindViewHolder(@NonNull MessageViewHolder holder, int position) {
6870
if (holder instanceof FormMessageViewHolder) {
6971
FormMessageViewHolder formMessageViewHolder = (FormMessageViewHolder) holder;
7072
formMessageViewHolder.setOnSubmitClickListener((message, form) -> {
71-
final OnSubmitButtonClickListener finalListener = MessageListAdapterExtensionsKt.getSubmitButtonClickListener(this);
73+
final FormSubmitButtonClickListener finalListener = this.formSubmitButtonClickListener;
7274
if (finalListener != null) {
7375
finalListener.onClicked(message, form);
7476
}
@@ -96,4 +98,25 @@ public OnItemClickListener<String> getSuggestedRepliesClickListener() {
9698
public void setSuggestedRepliesClickListener(@Nullable OnItemClickListener<String> suggestedRepliesClickListener) {
9799
this.suggestedRepliesClickListener = suggestedRepliesClickListener;
98100
}
101+
102+
/**
103+
* Returns a callback to be invoked when the Form submit button is clicked.
104+
*
105+
* @return {@link FormSubmitButtonClickListener} to be invoked when the Form submit button is clicked.
106+
* since 3.12.1
107+
*/
108+
@Nullable
109+
public FormSubmitButtonClickListener getFormSubmitButtonClickListener() {
110+
return formSubmitButtonClickListener;
111+
}
112+
113+
/**
114+
* Register a callback to be invoked when the Form submit button is clicked.
115+
*
116+
* @param formSubmitButtonClickListener The callback that will run when the Form submit button is clicked.
117+
* since 3.12.1
118+
*/
119+
public void setFormSubmitButtonClickListener(@Nullable FormSubmitButtonClickListener formSubmitButtonClickListener) {
120+
this.formSubmitButtonClickListener = formSubmitButtonClickListener;
121+
}
99122
}

uikit/src/main/java/com/sendbird/uikit/activities/viewholder/MessageViewHolderFactory.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -265,13 +265,8 @@ public static int getViewType(@NonNull BaseMessage message) {
265265
public static MessageType getMessageType(@NonNull BaseMessage message) {
266266
MessageType type;
267267

268-
Map<String, String> extendedMessagePayload = message.getExtendedMessagePayload();
269-
if (!extendedMessagePayload.isEmpty()) {
270-
if (message.getChannelType() == ChannelType.GROUP
271-
&& !MessageExtensionsKt.getForms(message).isEmpty()
272-
) {
273-
return MessageType.VIEW_TYPE_FORM_TYPE_MESSAGE;
274-
}
268+
if (message.getChannelType() == ChannelType.GROUP && !message.getForms().isEmpty()) {
269+
return MessageType.VIEW_TYPE_FORM_TYPE_MESSAGE;
275270
}
276271

277272
if (message instanceof UserMessage) {

uikit/src/main/java/com/sendbird/uikit/consts/StringSet.kt

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -148,22 +148,4 @@ object StringSet {
148148
const val quote_reply = "quote_reply"
149149
const val thread = "thread"
150150
const val parent = "parent"
151-
152-
// AI ChatBot
153-
const val sub_type = "sub_type"
154-
const val sub_data = "sub_data"
155-
const val suggested_replies = "suggested_replies"
156-
const val forms = "forms"
157-
const val key = "key"
158-
const val fields = "fields"
159-
const val data = "data"
160-
const val title = "title"
161-
const val input_type = "input_type"
162-
const val regex = "regex"
163-
const val placeholder = "placeholder"
164-
const val required = "required"
165-
const val text = "text"
166-
const val phone = "phone"
167-
const val email = "email"
168-
const val password = "password"
169151
}

uikit/src/main/java/com/sendbird/uikit/fragments/ChannelFragment.java

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.sendbird.android.channel.Role;
2323
import com.sendbird.android.message.BaseMessage;
2424
import com.sendbird.android.message.FileMessage;
25+
import com.sendbird.android.message.Form;
2526
import com.sendbird.android.message.SendingStatus;
2627
import com.sendbird.android.params.MessageListParams;
2728
import com.sendbird.android.params.UserMessageCreateParams;
@@ -51,7 +52,6 @@
5152
import com.sendbird.uikit.interfaces.OnItemClickListener;
5253
import com.sendbird.uikit.interfaces.OnItemLongClickListener;
5354
import com.sendbird.uikit.internal.extensions.MessageExtensionsKt;
54-
import com.sendbird.uikit.internal.extensions.MessageListComponentExtensionsKt;
5555
import com.sendbird.uikit.internal.model.VoicePlayerManager;
5656
import com.sendbird.uikit.log.Logger;
5757
import com.sendbird.uikit.model.DialogListItem;
@@ -203,7 +203,11 @@ public void onDestroy() {
203203
if (!isInitCallFinished.get()) {
204204
shouldDismissLoadingDialog();
205205
}
206-
MessageExtensionsKt.clearTemporaryAnswers(getChannelUrl());
206+
207+
ChannelViewModel.ChannelMessageData channelMessageData = getViewModel().getMessageList().getValue();
208+
if (channelMessageData != null) {
209+
MessageExtensionsKt.clearLastValidations(channelMessageData.getMessages());
210+
}
207211
}
208212

209213
/**
@@ -260,6 +264,7 @@ protected void onBindMessageListComponent(@NonNull MessageListComponent messageL
260264
messageListComponent.setOnEmojiReactionLongClickListener(emojiReactionLongClickListener != null ? emojiReactionLongClickListener : (view, position, message, reactionKey) -> showEmojiReactionDialog(message, position));
261265
messageListComponent.setOnEmojiReactionMoreButtonClickListener(emojiReactionMoreButtonClickListener != null ? emojiReactionMoreButtonClickListener : (view, position, message) -> showEmojiListDialog(message));
262266
messageListComponent.setSuggestedRepliesClickListener((view, position, data) -> onSuggestedRepliesClicked(data));
267+
messageListComponent.setFormSubmitButtonClickListener(this::onFormSubmitButtonClicked);
263268
messageListComponent.setOnTooltipClickListener(tooltipClickListener != null ? tooltipClickListener : this::onMessageTooltipClicked);
264269

265270
messageListComponent.setOnQuoteReplyMessageLongClickListener(this::onQuoteReplyMessageLongClicked);
@@ -274,14 +279,6 @@ protected void onBindMessageListComponent(@NonNull MessageListComponent messageL
274279
return false;
275280
});
276281

277-
MessageListComponentExtensionsKt.setSubmitButtonClickListener(messageListComponent, (message, form) -> {
278-
MessageExtensionsKt.submitForm(message, form, (e) -> {
279-
if (e != null) {
280-
showConfirmDialog(getString(R.string.sb_forms_submit_failed));
281-
}
282-
});
283-
});
284-
285282
final ChannelModule module = getModule();
286283
viewModel.getMessageList().observeAlways(getViewLifecycleOwner(), receivedMessageData -> {
287284
boolean isInitialCallFinished = isInitCallFinished.getAndSet(true);
@@ -528,6 +525,20 @@ protected void onSuggestedRepliesClicked(@NonNull String suggestedReply) {
528525
sendUserMessage(params);
529526
}
530527

528+
/**
529+
* Called when the Form submit button is clicked.
530+
*
531+
* @param message The message that contains the form
532+
* @param form The form to be submitted
533+
* since 3.12.1
534+
*/
535+
protected void onFormSubmitButtonClicked(@NonNull BaseMessage message, @NonNull Form form) {
536+
message.submitForm(form, (e) -> {
537+
if (e != null) {
538+
showConfirmDialog(getString(R.string.sb_forms_submit_failed));
539+
}
540+
});
541+
}
531542

532543
/**
533544
* Find the same message as the message ID and move it to the matching message.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.sendbird.uikit.interfaces
2+
3+
import com.sendbird.android.message.BaseMessage
4+
import com.sendbird.android.message.Form
5+
6+
/**
7+
* Interface definition for a callback to be invoked when the submit button of the form is clicked.
8+
*
9+
* @since 3.12.1
10+
*/
11+
fun interface FormSubmitButtonClickListener {
12+
/**
13+
* Called when the submit button of the form is clicked.
14+
*
15+
* @param message the message that contains the form
16+
* @param form the form to be submitted
17+
* @since 3.12.1
18+
*/
19+
fun onClicked(message: BaseMessage, form: Form)
20+
}

uikit/src/main/java/com/sendbird/uikit/internal/extensions/MessageExtensions.kt

Lines changed: 15 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
11
package com.sendbird.uikit.internal.extensions
22

33
import android.content.Context
4-
import com.sendbird.android.annotation.AIChatBotExperimental
5-
import com.sendbird.android.handler.CompletionHandler
64
import com.sendbird.android.message.BaseFileMessage
75
import com.sendbird.android.message.BaseMessage
86
import com.sendbird.android.message.FileMessage
7+
import com.sendbird.android.message.FormField
98
import com.sendbird.android.message.MultipleFilesMessage
109
import com.sendbird.uikit.R
1110
import com.sendbird.uikit.consts.StringSet
12-
import com.sendbird.uikit.internal.model.Form
13-
import com.sendbird.uikit.internal.singleton.JsonParser
1411
import com.sendbird.uikit.internal.singleton.MessageDisplayDataManager
1512
import com.sendbird.uikit.model.UserMessageDisplayData
1613
import com.sendbird.uikit.utils.MessageUtils
17-
import java.util.concurrent.ConcurrentHashMap
1814

1915
internal fun BaseMessage.hasParentMessage() = parentMessageId != 0L
2016

@@ -81,59 +77,22 @@ internal fun BaseFileMessage.getName(context: Context): String {
8177
}
8278
}
8379

84-
@OptIn(AIChatBotExperimental::class)
85-
internal fun BaseMessage.submitForm(form: Form, handler: CompletionHandler? = null) {
86-
val answers = form.formFields.fold(mutableMapOf<String, String>()) { acc, formField ->
87-
val answer = formField.temporaryAnswer ?: return@fold acc
88-
acc.apply { put(answer.formFieldKey, answer.answer) }
89-
}
90-
91-
this.submitForm(form.formKey, answers) { e ->
92-
handler?.onResult(e)
93-
}
80+
internal fun List<BaseMessage>.clearLastValidations() {
81+
this.flatMap { message -> message.forms }
82+
.flatMap { form -> form.formFields }
83+
.forEach { formField -> formField.lastValidation = null }
9484
}
9585

96-
internal val BaseMessage.suggestedReplies: List<String>
97-
get() {
98-
val suggestedReplies = extendedMessagePayload[StringSet.suggested_replies] ?: return emptyList()
99-
return try {
100-
JsonParser.fromJson(suggestedReplies)
101-
} catch (e: Exception) {
102-
emptyList()
103-
}
104-
}
105-
106-
internal val formMap: MutableMap<Long, Pair<String, List<Form>>> = ConcurrentHashMap()
107-
108-
internal val BaseMessage.forms: List<Form>
109-
get() {
110-
formMap[this.messageId]?.let { return it.second }
111-
val forms = extendedMessagePayload[StringSet.forms] ?: return emptyList()
112-
return try {
113-
JsonParser.fromJson<List<Form>>(forms).onEach { form ->
114-
// setting answer to formField manually.
115-
val answeredList = form.answeredList
116-
form.formFields.forEach { formField ->
117-
formField.messageId = this.messageId
118-
formField.answer = answeredList?.find { it.formFieldKey == formField.formFieldKey }
119-
}
120-
}.also {
121-
formMap[messageId] = this.channelUrl to it
122-
}
123-
} catch (e: Exception) {
124-
emptyList()
86+
internal val lastValidations: MutableMap<String, Boolean?> = mutableMapOf()
87+
internal var FormField.lastValidation: Boolean?
88+
get() = lastValidations[this.identifier]
89+
set(value) {
90+
if (value == null) {
91+
lastValidations.remove(this.identifier)
92+
} else {
93+
lastValidations[this.identifier] = value
12594
}
12695
}
12796

128-
internal fun clearTemporaryAnswers(channelUrl: String) {
129-
formMap.forEach {
130-
if (it.value.first == channelUrl) {
131-
it.value.second.forEach { form ->
132-
form.formFields.forEach { formField ->
133-
formField.temporaryAnswer = null
134-
formField.lastValidation = null
135-
}
136-
}
137-
}
138-
}
139-
}
97+
private val FormField.identifier: String
98+
get() = "${this.messageId}_${this.key}"

0 commit comments

Comments
 (0)