From 035099c1888fbba503ccf14e12cb2d7513e10109 Mon Sep 17 00:00:00 2001 From: Jakob Hoyer Date: Tue, 21 Dec 2021 14:09:44 +0200 Subject: [PATCH 1/7] MUIC-598 Audio/Video - Application may crash if visitor minimizes a call --- .../chat/controller/ChatController.java | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/widgetssdk/src/main/java/com/glia/widgets/chat/controller/ChatController.java b/widgetssdk/src/main/java/com/glia/widgets/chat/controller/ChatController.java index bc2ae6425..28aee7cfb 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/chat/controller/ChatController.java +++ b/widgetssdk/src/main/java/com/glia/widgets/chat/controller/ChatController.java @@ -226,7 +226,13 @@ public ChatController( IsEnableChatEditTextUseCase isEnableChatEditTextUseCase ) { Logger.d(TAG, "constructor"); - this.viewCallback = chatViewCallback; + + // viewCallback is accessed from multiple threads + // and must be protected from race condition + synchronized (this) { + this.viewCallback = chatViewCallback; + } + this.chatState = new ChatState.Builder() .setQueueTicketId(null) .setHistoryLoaded(false) @@ -355,7 +361,13 @@ private synchronized void emitChatItems(ChatState state) { public void onDestroy(boolean retain) { Logger.d(TAG, "onDestroy, retain:" + retain); destroyView(); - viewCallback = null; + + // viewCallback is accessed from multiple threads + // and must be protected from race condition + synchronized (this) { + viewCallback = null; + } + if (disposable != null) disposable.dispose(); if (!retain) { mediaUpgradeOfferRepository.stopAll(); @@ -525,7 +537,9 @@ public boolean isChatVisible() { return chatState.isVisible; } - public void setViewCallback(ChatViewCallback chatViewCallback) { + // viewCallback is accessed from multiple threads + // and must be protected from race condition + public synchronized void setViewCallback(ChatViewCallback chatViewCallback) { Logger.d(TAG, "setViewCallback"); this.viewCallback = chatViewCallback; viewCallback.emitState(chatState); From 42109ebbd009484f30c77ecabc9135686affcd82 Mon Sep 17 00:00:00 2001 From: Jakob Hoyer Date: Tue, 28 Dec 2021 16:11:34 +0200 Subject: [PATCH 2/7] MUIC-546 Make chat text selectable --- widgetssdk/src/main/res/layout/chat_receive_message_content.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/widgetssdk/src/main/res/layout/chat_receive_message_content.xml b/widgetssdk/src/main/res/layout/chat_receive_message_content.xml index e8809275e..4c64e2736 100644 --- a/widgetssdk/src/main/res/layout/chat_receive_message_content.xml +++ b/widgetssdk/src/main/res/layout/chat_receive_message_content.xml @@ -8,4 +8,5 @@ android:backgroundTint="?attr/gliaSystemAgentBubbleColor" android:textAppearance="?attr/textAppearanceBody1" android:textColor="?attr/gliaBaseDarkColor" + android:textIsSelectable="true" android:textColorLink="?attr/gliaBaseDarkColor" /> From 1e55292e722b27aded45a6bd4118fe69a77f26e8 Mon Sep 17 00:00:00 2001 From: Andrii Horishnii Date: Thu, 30 Dec 2021 18:10:22 +0200 Subject: [PATCH 3/7] Fixed bug where when an operator upgrades to two-way video, a visitor sees an Audio call screen title Used the requested media type to set the title on the call screen. MOB-1037 --- .../com/glia/widgets/call/CallController.java | 7 +++++++ .../java/com/glia/widgets/call/CallState.java | 7 +++++++ .../main/java/com/glia/widgets/call/CallView.java | 15 +++++---------- .../widgets/chat/controller/ChatController.java | 2 +- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/widgetssdk/src/main/java/com/glia/widgets/call/CallController.java b/widgetssdk/src/main/java/com/glia/widgets/call/CallController.java index 751d6a109..bcafe805a 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/call/CallController.java +++ b/widgetssdk/src/main/java/com/glia/widgets/call/CallController.java @@ -253,6 +253,13 @@ public void upgradeOfferChoiceSubmitSuccess( MediaUpgradeOfferRepository.Submitter submitter ) { Logger.d(TAG, "upgradeOfferChoiceSubmitSuccess"); + Engagement.MediaType mediaType; + if (offer.video != null && offer.video != MediaDirection.NONE) { + mediaType = Engagement.MediaType.VIDEO; + } else { + mediaType = Engagement.MediaType.AUDIO; + } + emitViewState(callState.changeRequestedMediaType(mediaType)); dialogController.dismissDialogs(); } diff --git a/widgetssdk/src/main/java/com/glia/widgets/call/CallState.java b/widgetssdk/src/main/java/com/glia/widgets/call/CallState.java index 835d729f9..ca6c292da 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/call/CallState.java +++ b/widgetssdk/src/main/java/com/glia/widgets/call/CallState.java @@ -139,6 +139,13 @@ public CallState changeNumberOfMessages(int numberOfMessages) { .createCallState(); } + public CallState changeRequestedMediaType(Engagement.MediaType requestedMediaType) { + return new Builder() + .copyFrom(this) + .setRequestedMediaType(requestedMediaType) + .createCallState(); + } + public CallState engagementStarted( String operatorName, String operatorProfileImgUrl) { diff --git a/widgetssdk/src/main/java/com/glia/widgets/call/CallView.java b/widgetssdk/src/main/java/com/glia/widgets/call/CallView.java index 1a7e61058..1b328a8a9 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/call/CallView.java +++ b/widgetssdk/src/main/java/com/glia/widgets/call/CallView.java @@ -285,18 +285,13 @@ public void emitState(CallState callState) { post(() -> { if (callState.isMediaEngagementStarted()) { appBar.showEndButton(); - if (callState.isVideoCall()) { - appBar.setTitle(resources.getString(R.string.glia_call_video_app_bar_title)); - } else { - appBar.setTitle(resources.getString(R.string.glia_call_audio_app_bar_title)); - } } else { appBar.showXButton(); - if (callState.requestedMediaType == Engagement.MediaType.VIDEO) { - appBar.setTitle(resources.getString(R.string.glia_call_video_app_bar_title)); - } else { - appBar.setTitle(resources.getString(R.string.glia_call_audio_app_bar_title)); - } + } + if (callState.requestedMediaType == Engagement.MediaType.VIDEO) { + appBar.setTitle(resources.getString(R.string.glia_call_video_app_bar_title)); + } else { + appBar.setTitle(resources.getString(R.string.glia_call_audio_app_bar_title)); } operatorStatusView.isRippleAnimationShowing( callState.isCallNotOngoing() || diff --git a/widgetssdk/src/main/java/com/glia/widgets/chat/controller/ChatController.java b/widgetssdk/src/main/java/com/glia/widgets/chat/controller/ChatController.java index bc2ae6425..a1dbce60f 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/chat/controller/ChatController.java +++ b/widgetssdk/src/main/java/com/glia/widgets/chat/controller/ChatController.java @@ -631,7 +631,7 @@ public void upgradeOfferChoiceSubmitSuccess( Logger.d(TAG, "upgradeOfferChoiceSubmitSuccess"); if (submitter == MediaUpgradeOfferRepository.Submitter.CHAT) { String requestedMediaType; - if (offer.video != null) { + if (offer.video != null && offer.video != MediaDirection.NONE) { requestedMediaType = GliaWidgets.MEDIA_TYPE_VIDEO; } else { requestedMediaType = GliaWidgets.MEDIA_TYPE_AUDIO; From 9f28abcc762709770a3733bc83a30049104bb978 Mon Sep 17 00:00:00 2001 From: Deniss Denissov Date: Tue, 28 Dec 2021 13:46:10 +0200 Subject: [PATCH 4/7] =?UTF-8?q?MUIC-638:=20[Android]=C2=A0Update=20Glia=20?= =?UTF-8?q?Android=20SDK=20dependency=20to=200.23.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/glia/exampleapp/Application.java | 32 ++++++- .../com/glia/exampleapp/SettingsFragment.java | 48 +++++++++++ .../auth/AuthorizationFragment.java | 83 +++++++++++++++++++ .../auth/AuthorizationPageFragment.java | 30 +++++++ .../exampleapp/auth/AuthorizationType.java | 8 ++ .../adapter/AuthorizationPagerAdapter.java | 27 ++++++ .../res/layout/authorization_fragment.xml | 23 +++++ app/src/main/res/navigation/nav_graph.xml | 10 ++- app/src/main/res/values/donottranslate.xml | 4 + app/src/main/res/values/setup.xml | 10 ++- app/src/main/res/values/strings.xml | 9 +- app/src/main/res/xml/auth_site_api_key.xml | 13 +++ app/src/main/res/xml/auth_token.xml | 8 ++ app/src/main/res/xml/preferences.xml | 27 +++--- widgetssdk/build.gradle | 2 +- .../java/com/glia/widgets/GliaWidgets.java | 35 ++++++-- .../com/glia/widgets/GliaWidgetsConfig.java | 81 ++++++++++++++++-- 17 files changed, 408 insertions(+), 42 deletions(-) create mode 100644 app/src/main/java/com/glia/exampleapp/auth/AuthorizationFragment.java create mode 100644 app/src/main/java/com/glia/exampleapp/auth/AuthorizationPageFragment.java create mode 100644 app/src/main/java/com/glia/exampleapp/auth/AuthorizationType.java create mode 100644 app/src/main/java/com/glia/exampleapp/auth/adapter/AuthorizationPagerAdapter.java create mode 100644 app/src/main/res/layout/authorization_fragment.xml create mode 100644 app/src/main/res/xml/auth_site_api_key.xml create mode 100644 app/src/main/res/xml/auth_token.xml diff --git a/app/src/main/java/com/glia/exampleapp/Application.java b/app/src/main/java/com/glia/exampleapp/Application.java index e29535de3..534c8526f 100644 --- a/app/src/main/java/com/glia/exampleapp/Application.java +++ b/app/src/main/java/com/glia/exampleapp/Application.java @@ -4,8 +4,10 @@ import androidx.preference.PreferenceManager; +import com.glia.exampleapp.auth.AuthorizationType; import com.glia.widgets.GliaWidgets; import com.glia.widgets.GliaWidgetsConfig; +import com.glia.androidsdk.SiteApiKey; public class Application extends android.app.Application { @@ -13,17 +15,39 @@ public class Application extends android.app.Application { public void onCreate() { super.onCreate(); GliaWidgets.onAppCreate(this); + GliaWidgets.init(createGliaAuthenticationConfiguration()); + } + private GliaWidgetsConfig createGliaAuthenticationConfiguration() { SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); - String appToken = sharedPreferences.getString(getString(R.string.pref_app_token), getString(R.string.app_token)); - String siteId = sharedPreferences.getString(getString(R.string.pref_site_id), getString(R.string.site_id)); - GliaWidgetsConfig gliaConfig = new GliaWidgetsConfig.Builder() + int authorizationType = sharedPreferences.getInt(getString(R.string.pref_authorization_type), AuthorizationType.DEFAULT); + if (authorizationType == AuthorizationType.SITE_API_KEY) + return configWithSiteApiKeyAuth(sharedPreferences); + else if (authorizationType == AuthorizationType.APP_TOKEN) + return configWithAppTokenAuth(sharedPreferences); + else return null; + } + + private GliaWidgetsConfig configWithAppTokenAuth(SharedPreferences preferences) { + String appToken = preferences.getString(getString(R.string.pref_app_token), getString(R.string.app_token)); + String siteId = preferences.getString(getString(R.string.pref_site_id), getString(R.string.site_id)); + return new GliaWidgetsConfig.Builder() .setAppToken(appToken) .setSiteId(siteId) .setRegion("beta") .setContext(getApplicationContext()) .build(); + } - GliaWidgets.init(gliaConfig); + private GliaWidgetsConfig configWithSiteApiKeyAuth(SharedPreferences preferences) { + String apiKeyId = preferences.getString(getString(R.string.pref_api_key_id), getString(R.string.glia_api_key_id)); + String apiKeySecret = preferences.getString(getString(R.string.pref_api_key_secret), getString(R.string.glia_api_key_secret)); + String siteId = preferences.getString(getString(R.string.pref_site_id), getString(R.string.site_id)); + return new GliaWidgetsConfig.Builder() + .setSiteApiKey(new SiteApiKey(apiKeyId, apiKeySecret)) + .setSiteId(siteId) + .setRegion("beta") + .setContext(getApplicationContext()) + .build(); } } diff --git a/app/src/main/java/com/glia/exampleapp/SettingsFragment.java b/app/src/main/java/com/glia/exampleapp/SettingsFragment.java index 4325ca874..0253e11ba 100644 --- a/app/src/main/java/com/glia/exampleapp/SettingsFragment.java +++ b/app/src/main/java/com/glia/exampleapp/SettingsFragment.java @@ -1,16 +1,64 @@ package com.glia.exampleapp; +import android.content.SharedPreferences; import android.os.Bundle; +import androidx.navigation.NavController; +import androidx.navigation.fragment.NavHostFragment; +import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceManager; + +import com.glia.exampleapp.auth.AuthorizationType; public class SettingsFragment extends PreferenceFragmentCompat { + private SharedPreferences sharedPreferences; + public SettingsFragment() { } @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { setPreferencesFromResource(R.xml.preferences, rootKey); + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()); + initAuthorizationPreferenceClickListener(); + } + + @Override + public void onResume() { + super.onResume(); + updateAuthorizationDescription(); + } + + private void initAuthorizationPreferenceClickListener() { + NavController navController = NavHostFragment.findNavController(this); + Preference authorizationButtonPreference = findPreference(getString(R.string.pref_app_authorization)); + if (authorizationButtonPreference != null) + authorizationButtonPreference.setOnPreferenceClickListener(preference -> { + navController.navigate(R.id.authorization); + return true; + }); + } + + private void updateAuthorizationDescription() { + int authorizationType = sharedPreferences.getInt(getString(R.string.pref_authorization_type), AuthorizationType.DEFAULT); + Preference preference = findPreference(getString(R.string.pref_app_authorization)); + if (preference != null) setAuthorizationSummary(preference, authorizationType); + } + + private void setAuthorizationSummary(Preference preference, int authorizationType) { + if (authorizationType == AuthorizationType.APP_TOKEN) + setAppTokenSummary(preference); + else if (authorizationType == AuthorizationType.SITE_API_KEY) + setApiKeySummary(preference); + } + + private void setAppTokenSummary(Preference preference) { + preference.setSummary(R.string.authorization_app_token); + } + + private void setApiKeySummary(Preference preference) { + preference.setSummary(R.string.authorization_site_api_key); } } diff --git a/app/src/main/java/com/glia/exampleapp/auth/AuthorizationFragment.java b/app/src/main/java/com/glia/exampleapp/auth/AuthorizationFragment.java new file mode 100644 index 000000000..163f332d3 --- /dev/null +++ b/app/src/main/java/com/glia/exampleapp/auth/AuthorizationFragment.java @@ -0,0 +1,83 @@ +package com.glia.exampleapp.auth; + +import android.content.SharedPreferences; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.preference.PreferenceManager; +import androidx.viewpager2.widget.ViewPager2; + +import com.glia.exampleapp.R; +import com.glia.exampleapp.auth.adapter.AuthorizationPagerAdapter; +import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; + +import java.util.ArrayList; +import java.util.List; + + +public class AuthorizationFragment extends Fragment { + private final List authorizationPages = new ArrayList() {{ + add(new AuthorizationPageFragment( + AuthorizationType.APP_TOKEN, + R.string.authorization_app_token, + R.xml.auth_token + )); + add(new AuthorizationPageFragment( + AuthorizationType.SITE_API_KEY, + R.string.authorization_site_api_key, + R.xml.auth_site_api_key + )); + }}; + + private TabLayout tabLayout; + private ViewPager2 viewPager; + + public AuthorizationFragment() { + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.authorization_fragment, container, false); + tabLayout = view.findViewById(R.id.tab_layout); + viewPager = view.findViewById(R.id.view_pager); + viewPager.setAdapter(new AuthorizationPagerAdapter<>(this, authorizationPages)); + setupMediator(); + return view; + } + + private void setupMediator() { + new TabLayoutMediator(tabLayout, viewPager, + (tab, position) -> + tab.setText(authorizationPages.get(position).getTitleResource()) + ).attach(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()); + int item = sharedPreferences.getInt(getString(R.string.pref_authorization_type), AuthorizationType.DEFAULT); + viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { + @Override + public void onPageSelected(int position) { + super.onPageSelected(position); + sharedPreferences.edit() + .putInt( + getString(R.string.pref_authorization_type), + authorizationPages.get(position).getAuthType() + ).apply(); + } + }); + viewPager.post(() -> viewPager.setCurrentItem(item, false)); + } +} + diff --git a/app/src/main/java/com/glia/exampleapp/auth/AuthorizationPageFragment.java b/app/src/main/java/com/glia/exampleapp/auth/AuthorizationPageFragment.java new file mode 100644 index 000000000..fcf805a27 --- /dev/null +++ b/app/src/main/java/com/glia/exampleapp/auth/AuthorizationPageFragment.java @@ -0,0 +1,30 @@ +package com.glia.exampleapp.auth; + +import android.os.Bundle; + +import androidx.preference.PreferenceFragmentCompat; + +public class AuthorizationPageFragment extends PreferenceFragmentCompat { + private final int titleRes; + private final int authType; + private final int prefRes; + + public AuthorizationPageFragment(int authType, int titleRes, int prefRes) { + this.titleRes = titleRes; + this.authType = authType; + this.prefRes = prefRes; + } + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + setPreferencesFromResource(prefRes, rootKey); + } + + public int getTitleResource() { + return titleRes; + } + + public int getAuthType() { + return authType; + } +} diff --git a/app/src/main/java/com/glia/exampleapp/auth/AuthorizationType.java b/app/src/main/java/com/glia/exampleapp/auth/AuthorizationType.java new file mode 100644 index 000000000..7e2478d94 --- /dev/null +++ b/app/src/main/java/com/glia/exampleapp/auth/AuthorizationType.java @@ -0,0 +1,8 @@ +package com.glia.exampleapp.auth; + +public class AuthorizationType { + public static final int APP_TOKEN = 0; + public static final int SITE_API_KEY = 1; + + public static final int DEFAULT = SITE_API_KEY; +} diff --git a/app/src/main/java/com/glia/exampleapp/auth/adapter/AuthorizationPagerAdapter.java b/app/src/main/java/com/glia/exampleapp/auth/adapter/AuthorizationPagerAdapter.java new file mode 100644 index 000000000..5d1420ff1 --- /dev/null +++ b/app/src/main/java/com/glia/exampleapp/auth/adapter/AuthorizationPagerAdapter.java @@ -0,0 +1,27 @@ +package com.glia.exampleapp.auth.adapter; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.viewpager2.adapter.FragmentStateAdapter; + +import java.util.List; + +public class AuthorizationPagerAdapter extends FragmentStateAdapter { + private final List authorizationScreens; + + public AuthorizationPagerAdapter(Fragment fragment, List authorizationScreens) { + super(fragment); + this.authorizationScreens = authorizationScreens; + } + + @NonNull + @Override + public Fragment createFragment(int position) { + return authorizationScreens.get(position); + } + + @Override + public int getItemCount() { + return authorizationScreens.size(); + } +} diff --git a/app/src/main/res/layout/authorization_fragment.xml b/app/src/main/res/layout/authorization_fragment.xml new file mode 100644 index 000000000..b2684b8dc --- /dev/null +++ b/app/src/main/res/layout/authorization_fragment.xml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index a18a71a74..ce2db0c1a 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -6,7 +6,11 @@ + android:name="com.glia.exampleapp.SettingsFragment"> + + + + diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 164f1bd5a..fb19278a1 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -14,8 +14,12 @@ font_color font_size header_title + authorization_type app_token + app_authorization site_id + api_key + api_key_secret company_name queue_id context_url diff --git a/app/src/main/res/values/setup.xml b/app/src/main/res/values/setup.xml index d74c42f7e..e3df284f7 100644 --- a/app/src/main/res/values/setup.xml +++ b/app/src/main/res/values/setup.xml @@ -1,8 +1,10 @@ - - xWa2EKJTN35En26o - 72c76913-ff1b-48a6-b209-bbeb57bd8649 - a0b87175-4ed1-44a7-9e78-e798f45d233f + + xWa2EKJTN35En26o + 72c76913-ff1b-48a6-b209-bbeb57bd8649 + a0b87175-4ed1-44a7-9e78-e798f45d233f + 9542b30d-faf2-443c-8553-d76ae331f9eb + gls_wZSeWDzOGFYzy794U5yDL5Sq4UlIadxPPglH diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2ea0a7773..8f0e90037 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -29,7 +29,9 @@ Chat message font family Title App token - Site id + Site Api Key id + Site Api Key Secret + Site id (needs restart) Negative color Visitor message bg color Visitor message txt color @@ -54,12 +56,17 @@ Operator Layout Styling Other Company name + Authorization type (needs restart) + Authorization type Queue id White label Use Overlay Context url Align AlertDialog Buttons Vertically + App Token + Site Api Key + Open Settings Launch Chat in Activity Launch Chat in Fragment diff --git a/app/src/main/res/xml/auth_site_api_key.xml b/app/src/main/res/xml/auth_site_api_key.xml new file mode 100644 index 000000000..3d007d0b0 --- /dev/null +++ b/app/src/main/res/xml/auth_site_api_key.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/src/main/res/xml/auth_token.xml b/app/src/main/res/xml/auth_token.xml new file mode 100644 index 000000000..c67ee3b74 --- /dev/null +++ b/app/src/main/res/xml/auth_token.xml @@ -0,0 +1,8 @@ + + + + diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 6db0bf9e0..224037a8d 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -1,14 +1,11 @@ - - - - + + + + + + - - { - if(e != null){ + if (e != null) { exceptionConsumer.accept(new GliaWidgetException(e.debugMessage, e.cause)); - }else { + } else { exceptionConsumer.accept(null); } }); @@ -174,10 +191,10 @@ public static void updateVisitorInfo(VisitorInfoUpdate visitorInfoUpdate, Consum */ public static void getVisitorInfo(Consumer visitorCallback, Consumer exceptionConsumer) { Glia.getVisitorInfo((visitorInfo, e) -> { - if(visitorInfo != null){ + if (visitorInfo != null) { visitorCallback.accept(new GliaVisitorInfo(visitorInfo)); } - if(e != null){ + if (e != null) { exceptionConsumer.accept(new GliaWidgetException(e.debugMessage, e.cause)); } }); diff --git a/widgetssdk/src/main/java/com/glia/widgets/GliaWidgetsConfig.java b/widgetssdk/src/main/java/com/glia/widgets/GliaWidgetsConfig.java index 3ded42291..a545bef61 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/GliaWidgetsConfig.java +++ b/widgetssdk/src/main/java/com/glia/widgets/GliaWidgetsConfig.java @@ -2,6 +2,8 @@ import android.content.Context; +import com.glia.androidsdk.SiteApiKey; + /** * Configurations used to initialize Glia SDK * @@ -10,6 +12,7 @@ public class GliaWidgetsConfig { private final String appToken; private final String siteId; + private final SiteApiKey siteApiKey; private final Context context; private final String region; private final int requestCode; @@ -22,12 +25,34 @@ public GliaWidgetsConfig(String appToken, String apiToken, String siteId, Contex this(appToken, siteId, context, region, requestCode); } - public GliaWidgetsConfig(String appToken, String siteId, Context context, String region, int requestCode) { - this.appToken = appToken; - this.siteId = siteId; - this.context = context; - this.region = region; - this.requestCode = requestCode; + /** + * @deprecated Deprecated since SDK version 1.6.18. Please use {@link GliaWidgetsConfig.Builder#setSiteApiKey(SiteApiKey)} instead. + */ + @Deprecated + public GliaWidgetsConfig( + String appToken, + String siteId, + Context context, + String region, + int requestCode + ) { + this( + new Builder() + .setAppToken(appToken) + .setSiteId(siteId) + .setContext(context) + .setRegion(region) + .setRequestCode(requestCode) + ); + } + + private GliaWidgetsConfig(Builder builder) { + this.appToken = builder.appToken; + this.siteApiKey = builder.siteApiKey; + this.siteId = builder.siteId; + this.context = builder.context; + this.region = builder.region; + this.requestCode = builder.requestCode; } /** @@ -54,6 +79,10 @@ public String getRegion() { return region; } + public SiteApiKey getSiteApiKey() { + return siteApiKey; + } + /** * @deprecated API token is no longer needed for SDK to function correctly. * Deprecated since SDK version 1.6.5 @@ -76,6 +105,14 @@ public int getRequestCode() { *

* Required information is: *

    + *
  • Site Api Key Id
  • + *
  • Site Api Key Secret
  • + *
  • Site ID
  • + *
  • Region
  • + *
  • Context
  • + *
+ * or this + *
    *
  • APP token
  • *
  • Site ID
  • *
  • Region
  • @@ -87,6 +124,18 @@ public int getRequestCode() { *
          * 
          * GliaBuildConfig gliaBuildConfig = new GliaBuildConfig.Builder(
    +     *   .setSiteApiKey(new SiteApiKey(SITE_API_KEY_ID, SITE_API_KEY_SECRET))
    +     *   .setSiteId("SITE_ID")
    +     *   .setRegion(Regions.US)
    +     *   .setContext(getApplicationContext())
    +     *   .build();
    +     * 
    +     * 
    + *

    + * or + *

    +     * 
    +     * GliaBuildConfig gliaBuildConfig = new GliaBuildConfig.Builder(
          *   .setAppToken("APP_TOKEN")
          *   .setSiteId("SITE_ID")
          *   .setRegion(Regions.US)
    @@ -98,13 +147,19 @@ public int getRequestCode() {
         public static class Builder {
             String appToken;
             String siteId;
    +        SiteApiKey siteApiKey;
             Context context;
             String region;
    -        int requestCode = 45554442;
    +        int requestCode;
    +
    +        public Builder() {
    +            requestCode = 45554442;
    +        }
     
             /**
              * @param appToken - your APP token
              * @return Builder instance
    +         * @deprecated use site api key instead
              */
             public Builder setAppToken(String appToken) {
                 this.appToken = appToken;
    @@ -148,13 +203,23 @@ public Builder setRegion(String region) {
                 return this;
             }
     
    +        public Builder setSiteApiKey(SiteApiKey siteApiKey) {
    +            this.siteApiKey = siteApiKey;
    +            return this;
    +        }
    +
    +        public Builder setRequestCode(int requestCode) {
    +            this.requestCode = requestCode;
    +            return this;
    +        }
    +
             /**
              * Builds the final configurations
              *
              * @return Glia SDK configurations
              */
             public GliaWidgetsConfig build() {
    -            return new GliaWidgetsConfig(appToken, siteId, context, region, requestCode);
    +            return new GliaWidgetsConfig(this);
             }
         }
     }
    
    From f36ad16a2a69e96ed8cf953f3b2a428b5334344c Mon Sep 17 00:00:00 2001
    From: Jakob Hoyer 
    Date: Mon, 3 Jan 2022 13:16:41 +0200
    Subject: [PATCH 5/7] MUIC-546 Make visitor message text selectable
    
    ---
     widgetssdk/src/main/res/layout/chat_visitor_message_layout.xml | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/widgetssdk/src/main/res/layout/chat_visitor_message_layout.xml b/widgetssdk/src/main/res/layout/chat_visitor_message_layout.xml
    index f55c38fa7..e934c6577 100644
    --- a/widgetssdk/src/main/res/layout/chat_visitor_message_layout.xml
    +++ b/widgetssdk/src/main/res/layout/chat_visitor_message_layout.xml
    @@ -27,6 +27,7 @@
             android:backgroundTint="?attr/gliaBrandPrimaryColor"
             android:textAppearance="?attr/textAppearanceBody1"
             android:textColor="?attr/gliaBaseLightColor"
    +        android:textIsSelectable="true"
             app:layout_constrainedWidth="true"
             app:layout_constraintEnd_toEndOf="@id/end_guideline"
             app:layout_constraintHorizontal_bias="1"
    
    From 42a6d879d34b38d2bac8461a102a22163d1f5b59 Mon Sep 17 00:00:00 2001
    From: Jakob Hoyer 
    Date: Tue, 28 Dec 2021 15:19:25 +0200
    Subject: [PATCH 6/7] Add "delivered" message to image and file messages
    
    ---
     .../java/com/glia/widgets/chat/ChatView.java  |  91 +++++-------
     .../widgets/chat/adapter/ChatAdapter.java     |  32 +++--
     .../FileAttachmentViewHolder.java             |  51 +++----
     .../OperatorFileAttachmentViewHolder.java     |  35 +++++
     .../VisitorFileAttachmentViewHolder.java      |  38 +++++
     .../ImageAttachmentViewHolder.java            |   5 +-
     .../OperatorImageAttachmentViewHolder.java    |  20 +++
     .../VisitorImageAttachmentViewHolder.java     |  50 +++++++
     .../chat/controller/ChatController.java       | 130 ++++++++++--------
     .../model/history/OperatorAttachmentItem.java |   8 +-
     .../model/history/OperatorMessageItem.java    |   4 +
     .../model/history/VisitorAttachmentItem.java  |  25 ++--
     .../model/history/VisitorMessageItem.java     |  15 +-
     .../java/com/glia/widgets/helper/Utils.java   |  11 ++
     .../chat_attachment_visitor_file_layout.xml   |  14 ++
     .../chat_attachment_visitor_image_layout.xml  |  15 +-
     16 files changed, 357 insertions(+), 187 deletions(-)
     rename widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/{ => fileattachment}/FileAttachmentViewHolder.java (50%)
     create mode 100644 widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/fileattachment/OperatorFileAttachmentViewHolder.java
     create mode 100644 widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/fileattachment/VisitorFileAttachmentViewHolder.java
     rename widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/{ => imageattachment}/ImageAttachmentViewHolder.java (95%)
     create mode 100644 widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/imageattachment/OperatorImageAttachmentViewHolder.java
     create mode 100644 widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/imageattachment/VisitorImageAttachmentViewHolder.java
    
    diff --git a/widgetssdk/src/main/java/com/glia/widgets/chat/ChatView.java b/widgetssdk/src/main/java/com/glia/widgets/chat/ChatView.java
    index efdf3f6bb..b4c15cd3b 100644
    --- a/widgetssdk/src/main/java/com/glia/widgets/chat/ChatView.java
    +++ b/widgetssdk/src/main/java/com/glia/widgets/chat/ChatView.java
    @@ -486,56 +486,36 @@ public void emitState(ChatState chatState) {
                 @Override
                 public void emitItems(List items) {
                     List updatedItems = items.stream()
    -                        .map(this::updateChatItem)
    +                        .map(this::updateIsFileDownloaded)
                             .collect(Collectors.toList());
     
                     post(() -> adapter.submitList(updatedItems));
                 }
     
    -            private ChatItem updateChatItem(ChatItem item) {
    +            private ChatItem updateIsFileDownloaded(ChatItem item) {
                     if (item instanceof OperatorAttachmentItem) {
    -                    AttachmentFile attachmentFile = ((OperatorAttachmentItem) item).attachmentFile;
    -                    OperatorAttachmentItem newItem;
    -                    if (FileHelper.isFileDownloaded(attachmentFile)) {
    -                        newItem = new OperatorAttachmentItem(
    -                                attachmentFile.getId(),
    -                                item.getViewType(),
    -                                ((OperatorAttachmentItem) item).showChatHead,
    -                                ((OperatorAttachmentItem) item).attachmentFile,
    -                                ((OperatorAttachmentItem) item).operatorProfileImgUrl,
    -                                true,
    -                                ((OperatorAttachmentItem) item).isDownloading);
    -                    } else {
    -                        newItem = new OperatorAttachmentItem(
    -                                attachmentFile.getId(),
    -                                item.getViewType(),
    -                                ((OperatorAttachmentItem) item).showChatHead,
    -                                ((OperatorAttachmentItem) item).attachmentFile,
    -                                ((OperatorAttachmentItem) item).operatorProfileImgUrl,
    -                                false,
    -                                ((OperatorAttachmentItem) item).isDownloading);
    -                    }
    -
    -                    return newItem;
    +                    OperatorAttachmentItem oldItem = (OperatorAttachmentItem) item;
    +                    boolean isFileDownloaded = FileHelper.isFileDownloaded(oldItem.attachmentFile);
    +                    return new OperatorAttachmentItem(
    +                            oldItem.getId(),
    +                            oldItem.getViewType(),
    +                            oldItem.showChatHead,
    +                            oldItem.attachmentFile,
    +                            oldItem.operatorProfileImgUrl,
    +                            isFileDownloaded,
    +                            oldItem.isDownloading
    +                    );
                     } else if (item instanceof VisitorAttachmentItem) {
    -                    AttachmentFile attachmentFile = ((VisitorAttachmentItem) item).attachmentFile;
    -                    VisitorAttachmentItem newItem;
    -                    if (FileHelper.isFileDownloaded(attachmentFile)) {
    -                        newItem = new VisitorAttachmentItem(
    -                                attachmentFile.getId(),
    -                                item.getViewType(),
    -                                ((VisitorAttachmentItem) item).attachmentFile,
    -                                true,
    -                                ((VisitorAttachmentItem) item).isDownloading);
    -                    } else {
    -                        newItem = new VisitorAttachmentItem(
    -                                attachmentFile.getId(),
    -                                item.getViewType(),
    -                                ((VisitorAttachmentItem) item).attachmentFile,
    -                                false,
    -                                ((VisitorAttachmentItem) item).isDownloading);
    -                    }
    -                    return newItem;
    +                    VisitorAttachmentItem oldItem = (VisitorAttachmentItem) item;
    +                    boolean isFileDownloaded = FileHelper.isFileDownloaded(oldItem.attachmentFile);
    +                    return new VisitorAttachmentItem(
    +                            oldItem.getId(),
    +                            oldItem.getViewType(),
    +                            oldItem.attachmentFile,
    +                            isFileDownloaded,
    +                            oldItem.isDownloading,
    +                            oldItem.showDelivered
    +                    );
                     } else {
                         return item;
                     }
    @@ -1288,28 +1268,33 @@ private void submitUpdatedItems(AttachmentFile attachmentFile, boolean isDownloa
         }
     
         @NonNull
    -    private ChatItem updatedDownloadingItemState(AttachmentFile attachmentFile, ChatItem currentItem, boolean isDownloading, boolean isFileExists) {
    -        if (currentItem.getId().equals(attachmentFile.getId())) {
    -            if (currentItem instanceof VisitorAttachmentItem) {
    +    private ChatItem updatedDownloadingItemState(AttachmentFile attachmentFile, ChatItem currentChatItem, boolean isDownloading, boolean isFileExists) {
    +        if (currentChatItem instanceof VisitorAttachmentItem) {
    +            VisitorAttachmentItem currentItem = (VisitorAttachmentItem) currentChatItem;
    +            if (currentItem.attachmentFile.getId().equals(attachmentFile.getId())) {
                     return new VisitorAttachmentItem(
                             currentItem.getId(),
                             currentItem.getViewType(),
    -                        ((VisitorAttachmentItem) currentItem).attachmentFile,
    +                        currentItem.attachmentFile,
                             isFileExists,
    -                        isDownloading
    +                        isDownloading,
    +                        currentItem.showDelivered
                     );
    -            } else if (currentItem instanceof OperatorAttachmentItem) {
    +            }
    +        } else if (currentChatItem instanceof OperatorAttachmentItem) {
    +            OperatorAttachmentItem currentItem = (OperatorAttachmentItem) currentChatItem;
    +            if (currentItem.attachmentFile.getId().equals(attachmentFile.getId())) {
                     return new OperatorAttachmentItem(
                             currentItem.getId(),
                             currentItem.getViewType(),
    -                        ((OperatorAttachmentItem) currentItem).showChatHead,
    -                        ((OperatorAttachmentItem) currentItem).attachmentFile,
    -                        ((OperatorAttachmentItem) currentItem).operatorProfileImgUrl,
    +                        currentItem.showChatHead,
    +                        currentItem.attachmentFile,
    +                        currentItem.operatorProfileImgUrl,
                             isFileExists,
                             isDownloading);
                 }
             }
    -        return currentItem;
    +        return currentChatItem;
         }
     
         @Override
    diff --git a/widgetssdk/src/main/java/com/glia/widgets/chat/adapter/ChatAdapter.java b/widgetssdk/src/main/java/com/glia/widgets/chat/adapter/ChatAdapter.java
    index c53822a7c..7a7eb80e7 100644
    --- a/widgetssdk/src/main/java/com/glia/widgets/chat/adapter/ChatAdapter.java
    +++ b/widgetssdk/src/main/java/com/glia/widgets/chat/adapter/ChatAdapter.java
    @@ -12,8 +12,11 @@
     import com.glia.androidsdk.chat.AttachmentFile;
     import com.glia.widgets.R;
     import com.glia.widgets.UiTheme;
    -import com.glia.widgets.chat.adapter.holder.FileAttachmentViewHolder;
    -import com.glia.widgets.chat.adapter.holder.ImageAttachmentViewHolder;
    +import com.glia.widgets.chat.adapter.holder.fileattachment.OperatorFileAttachmentViewHolder;
    +import com.glia.widgets.chat.adapter.holder.fileattachment.VisitorFileAttachmentViewHolder;
    +import com.glia.widgets.chat.adapter.holder.imageattachment.OperatorImageAttachmentViewHolder;
    +import com.glia.widgets.chat.adapter.holder.imageattachment.VisitorImageAttachmentViewHolder;
    +import com.glia.widgets.chat.adapter.holder.imageattachment.ImageAttachmentViewHolder;
     import com.glia.widgets.chat.adapter.holder.MediaUpgradeStartedViewHolder;
     import com.glia.widgets.chat.adapter.holder.OperatorMessageViewHolder;
     import com.glia.widgets.chat.adapter.holder.OperatorStatusViewHolder;
    @@ -109,10 +112,11 @@ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int
                 return new OperatorStatusViewHolder(inflater.inflate(R.layout.chat_operator_status_layout, parent, false), uiTheme);
             } else if (viewType == VISITOR_FILE_VIEW_TYPE) {
                 View view = inflater.inflate(R.layout.chat_attachment_visitor_file_layout, parent, false);
    -            return new FileAttachmentViewHolder(view, uiTheme);
    +            return new VisitorFileAttachmentViewHolder(view, uiTheme);
             } else if (viewType == VISITOR_IMAGE_VIEW_TYPE) {
    -            return new ImageAttachmentViewHolder(
    +            return new VisitorImageAttachmentViewHolder(
                         inflater.inflate(R.layout.chat_attachment_visitor_image_layout, parent, false),
    +                    uiTheme,
                         getImageFileFromCacheUseCase,
                         getImageFileFromDownloadsUseCase,
                         getImageFileFromNetworkUseCase
    @@ -120,14 +124,14 @@ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int
             } else if (viewType == VISITOR_MESSAGE_TYPE) {
                 return new VisitorMessageViewHolder(inflater.inflate(R.layout.chat_visitor_message_layout, parent, false), uiTheme);
             } else if (viewType == OPERATOR_IMAGE_VIEW_TYPE) {
    -            return new ImageAttachmentViewHolder(
    +            return new OperatorImageAttachmentViewHolder(
                         inflater.inflate(R.layout.chat_attachment_operator_image_layout, parent, false),
                         getImageFileFromCacheUseCase,
                         getImageFileFromDownloadsUseCase,
                         getImageFileFromNetworkUseCase
                 );
             } else if (viewType == OPERATOR_FILE_VIEW_TYPE) {
    -            return new FileAttachmentViewHolder(inflater.inflate(R.layout.chat_attachment_operator_file_layout, parent, false), uiTheme);
    +            return new OperatorFileAttachmentViewHolder(inflater.inflate(R.layout.chat_attachment_operator_file_layout, parent, false), uiTheme);
             } else if (viewType == OPERATOR_MESSAGE_VIEW_TYPE) {
                 return new OperatorMessageViewHolder(inflater.inflate(R.layout.chat_operator_message_layout, parent, false), uiTheme);
             } else if (viewType == MEDIA_UPGRADE_ITEM_TYPE) {
    @@ -152,7 +156,7 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi
                 ((MediaUpgradeStartedViewHolder) holder).bind((MediaUpgradeStartedTimerItem) chatItem);
             } else if (chatItem instanceof OperatorAttachmentItem) {
                 if (chatItem.getViewType() == OPERATOR_FILE_VIEW_TYPE) {
    -                FileAttachmentViewHolder viewHolder = (FileAttachmentViewHolder) holder;
    +                OperatorFileAttachmentViewHolder viewHolder = (OperatorFileAttachmentViewHolder) holder;
                     OperatorAttachmentItem item = (OperatorAttachmentItem) chatItem;
                     viewHolder.bind(item);
                     viewHolder.itemView.setOnClickListener(v -> {
    @@ -163,15 +167,14 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi
                         }
                     });
                 } else {
    -                //holder.setIsRecyclable(false);
    -                ImageAttachmentViewHolder viewHolder = (ImageAttachmentViewHolder) holder;
    +                OperatorImageAttachmentViewHolder viewHolder = (OperatorImageAttachmentViewHolder) holder;
                     AttachmentFile file = ((OperatorAttachmentItem) chatItem).attachmentFile;
                     viewHolder.bind(file);
                     viewHolder.itemView.setOnClickListener(v -> onImageItemClickListener.onImageItemClick(file));
                 }
             } else if (chatItem instanceof VisitorAttachmentItem) {
                 if (chatItem.getViewType() == VISITOR_FILE_VIEW_TYPE) {
    -                FileAttachmentViewHolder viewHolder = (FileAttachmentViewHolder) holder;
    +                VisitorFileAttachmentViewHolder viewHolder = (VisitorFileAttachmentViewHolder) holder;
                     VisitorAttachmentItem item = (VisitorAttachmentItem) chatItem;
                     viewHolder.bind(item);
                     viewHolder.itemView.setOnClickListener(v -> {
    @@ -182,11 +185,10 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi
                         }
                     });
                 } else {
    -                //holder.setIsRecyclable(false);
    -                ImageAttachmentViewHolder viewHolder = (ImageAttachmentViewHolder) holder;
    -                AttachmentFile file = ((VisitorAttachmentItem) chatItem).attachmentFile;
    -                viewHolder.bind(file);
    -                viewHolder.itemView.setOnClickListener(v -> onImageItemClickListener.onImageItemClick(file));
    +                VisitorImageAttachmentViewHolder viewHolder = (VisitorImageAttachmentViewHolder) holder;
    +                VisitorAttachmentItem item = (VisitorAttachmentItem) chatItem;
    +                viewHolder.bind(item.attachmentFile, item.showDelivered);
    +                viewHolder.itemView.setOnClickListener(v -> onImageItemClickListener.onImageItemClick(item.attachmentFile));
                 }
             }
         }
    diff --git a/widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/FileAttachmentViewHolder.java b/widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/fileattachment/FileAttachmentViewHolder.java
    similarity index 50%
    rename from widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/FileAttachmentViewHolder.java
    rename to widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/fileattachment/FileAttachmentViewHolder.java
    index f41b9c7be..c66c2d5f6 100644
    --- a/widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/FileAttachmentViewHolder.java
    +++ b/widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/fileattachment/FileAttachmentViewHolder.java
    @@ -1,4 +1,4 @@
    -package com.glia.widgets.chat.adapter.holder;
    +package com.glia.widgets.chat.adapter.holder.fileattachment;
     
     import android.text.format.Formatter;
     import android.view.View;
    @@ -11,40 +11,30 @@
     
     import com.glia.androidsdk.chat.AttachmentFile;
     import com.glia.widgets.R;
    -import com.glia.widgets.UiTheme;
    -import com.glia.widgets.chat.model.history.OperatorAttachmentItem;
    -import com.glia.widgets.chat.model.history.VisitorAttachmentItem;
     import com.glia.widgets.helper.Utils;
    -import com.glia.widgets.view.OperatorStatusView;
     import com.google.android.material.progressindicator.LinearProgressIndicator;
     
     public class FileAttachmentViewHolder extends RecyclerView.ViewHolder {
    +    private final CardView extensionContainerView;
    +    private final TextView extensionTypeText;
    +    private final LinearProgressIndicator progressIndicator;
    +    private final TextView titleText;
    +    private final TextView statusIndicator;
     
    -    private final OperatorStatusView operatorStatusView;
    -
    -    public FileAttachmentViewHolder(@NonNull View itemView, UiTheme uiTheme) {
    +    public FileAttachmentViewHolder(@NonNull View itemView) {
             super(itemView);
    -
    -        operatorStatusView = itemView.findViewById(R.id.chat_head_view);
    -        operatorStatusView.setTheme(uiTheme);
    -        operatorStatusView.isRippleAnimationShowing(false);
    -    }
    -
    -    public void bind(OperatorAttachmentItem item) {
    -        setData(item.isFileExists, item.isDownloading, item.attachmentFile, item.showChatHead, item.operatorProfileImgUrl);
    -    }
    -
    -    public void bind(VisitorAttachmentItem item) {
    -        setData(item.isFileExists, item.isDownloading, item.attachmentFile, false, null);
    +        extensionContainerView = itemView.findViewById(R.id.type_indicator_view);
    +        extensionTypeText = itemView.findViewById(R.id.type_indicator_text);
    +        progressIndicator = itemView.findViewById(R.id.progress_indicator);
    +        titleText = itemView.findViewById(R.id.item_title);
    +        statusIndicator = itemView.findViewById(R.id.status_indicator);
         }
     
    -    public void setData(boolean isFileExists, boolean isDownloading, AttachmentFile attachmentFile, boolean showChatHead, String operatorProfileImgUrl) {
    -        CardView extensionContainerView = itemView.findViewById(R.id.type_indicator_view);
    -        TextView extensionTypeText = itemView.findViewById(R.id.type_indicator_text);
    -        LinearProgressIndicator progressIndicator = itemView.findViewById(R.id.progress_indicator);
    -        TextView titleText = itemView.findViewById(R.id.item_title);
    -        TextView statusIndicator = itemView.findViewById(R.id.status_indicator);
    -
    +    protected void setData(
    +            boolean isFileExists,
    +            boolean isDownloading,
    +            AttachmentFile attachmentFile
    +    ) {
             if (isFileExists) {
                 statusIndicator.setText(R.string.glia_chat_attachment_open_button_label);
             } else {
    @@ -70,12 +60,5 @@ public void setData(boolean isFileExists, boolean isDownloading, AttachmentFile
             titleText.setText(String.format("%s • %s", name, Formatter.formatFileSize(itemView.getContext(), byteSize)));
             String extension = Utils.getExtensionByStringHandling(name).orElse("");
             extensionTypeText.setText(extension);
    -
    -        operatorStatusView.setVisibility(showChatHead ? View.VISIBLE : View.GONE);
    -        if (operatorProfileImgUrl != null) {
    -            operatorStatusView.showProfileImage(operatorProfileImgUrl);
    -        } else {
    -            operatorStatusView.showPlaceHolder();
    -        }
         }
     }
    diff --git a/widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/fileattachment/OperatorFileAttachmentViewHolder.java b/widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/fileattachment/OperatorFileAttachmentViewHolder.java
    new file mode 100644
    index 000000000..95629570a
    --- /dev/null
    +++ b/widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/fileattachment/OperatorFileAttachmentViewHolder.java
    @@ -0,0 +1,35 @@
    +package com.glia.widgets.chat.adapter.holder.fileattachment;
    +
    +import android.view.View;
    +
    +import androidx.annotation.NonNull;
    +
    +import com.glia.widgets.R;
    +import com.glia.widgets.UiTheme;
    +import com.glia.widgets.chat.model.history.OperatorAttachmentItem;
    +import com.glia.widgets.view.OperatorStatusView;
    +
    +public class OperatorFileAttachmentViewHolder extends FileAttachmentViewHolder {
    +    private final OperatorStatusView operatorStatusView;
    +
    +    public OperatorFileAttachmentViewHolder(@NonNull View itemView, UiTheme uiTheme) {
    +        super(itemView);
    +        operatorStatusView = itemView.findViewById(R.id.chat_head_view);
    +        setupOperatorStatusView(uiTheme);
    +    }
    +
    +    private void setupOperatorStatusView(UiTheme uiTheme) {
    +        operatorStatusView.setTheme(uiTheme);
    +        operatorStatusView.isRippleAnimationShowing(false);
    +    }
    +
    +    public void bind(OperatorAttachmentItem item) {
    +        super.setData(item.isFileExists, item.isDownloading, item.attachmentFile);
    +        operatorStatusView.setVisibility(item.showChatHead ? View.VISIBLE : View.GONE);
    +        if (item.operatorProfileImgUrl != null) {
    +            operatorStatusView.showProfileImage(item.operatorProfileImgUrl);
    +        } else {
    +            operatorStatusView.showPlaceHolder();
    +        }
    +    }
    +}
    diff --git a/widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/fileattachment/VisitorFileAttachmentViewHolder.java b/widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/fileattachment/VisitorFileAttachmentViewHolder.java
    new file mode 100644
    index 000000000..96f2c92f9
    --- /dev/null
    +++ b/widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/fileattachment/VisitorFileAttachmentViewHolder.java
    @@ -0,0 +1,38 @@
    +package com.glia.widgets.chat.adapter.holder.fileattachment;
    +
    +import android.content.Context;
    +import android.graphics.Typeface;
    +import android.view.View;
    +import android.widget.TextView;
    +
    +import androidx.annotation.NonNull;
    +import androidx.core.content.ContextCompat;
    +import androidx.core.content.res.ResourcesCompat;
    +
    +import com.glia.widgets.R;
    +import com.glia.widgets.UiTheme;
    +import com.glia.widgets.chat.model.history.VisitorAttachmentItem;
    +
    +public class VisitorFileAttachmentViewHolder extends FileAttachmentViewHolder {
    +    private final TextView deliveredView;
    +
    +    public VisitorFileAttachmentViewHolder(@NonNull View itemView, UiTheme uiTheme) {
    +        super(itemView);
    +        deliveredView = itemView.findViewById(R.id.delivered_view);
    +        setupDeliveredView(itemView.getContext(), uiTheme);
    +    }
    +
    +    private void setupDeliveredView(Context context, UiTheme uiTheme) {
    +        if (uiTheme.getFontRes() != null) {
    +            Typeface fontFamily = ResourcesCompat.getFont(context, uiTheme.getFontRes());
    +            deliveredView.setTypeface(fontFamily);
    +        }
    +        deliveredView.setTextColor(ContextCompat.getColor(context, uiTheme.getBaseNormalColor()));
    +    }
    +
    +
    +    public void bind(VisitorAttachmentItem item) {
    +        super.setData(item.isFileExists, item.isDownloading, item.attachmentFile);
    +        deliveredView.setVisibility(item.showDelivered ? View.VISIBLE : View.GONE);
    +    }
    +}
    diff --git a/widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/ImageAttachmentViewHolder.java b/widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/imageattachment/ImageAttachmentViewHolder.java
    similarity index 95%
    rename from widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/ImageAttachmentViewHolder.java
    rename to widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/imageattachment/ImageAttachmentViewHolder.java
    index d694dbc40..215f3b0ad 100644
    --- a/widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/ImageAttachmentViewHolder.java
    +++ b/widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/imageattachment/ImageAttachmentViewHolder.java
    @@ -1,4 +1,4 @@
    -package com.glia.widgets.chat.adapter.holder;
    +package com.glia.widgets.chat.adapter.holder.imageattachment;
     
     import android.graphics.Color;
     import android.view.View;
    @@ -39,7 +39,7 @@ public ImageAttachmentViewHolder(
                 GetImageFileFromNetworkUseCase getImageFileFromNetworkUseCase
         ) {
             super(itemView);
    -        imageView = itemView.findViewById(R.id.incoming_image_attachment);
    +        this.imageView = itemView.findViewById(R.id.incoming_image_attachment);
             this.getImageFileFromCacheUseCase = getImageFileFromCacheUseCase;
             this.getImageFileFromDownloadsUseCase = getImageFileFromDownloadsUseCase;
             this.getImageFileFromNetworkUseCase = getImageFileFromNetworkUseCase;
    @@ -65,6 +65,7 @@ public void bind(AttachmentFile attachmentFile) {
                     );
         }
     
    +
         public void onStopView() {
             if (disposable != null) disposable.dispose();
         }
    diff --git a/widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/imageattachment/OperatorImageAttachmentViewHolder.java b/widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/imageattachment/OperatorImageAttachmentViewHolder.java
    new file mode 100644
    index 000000000..18d151c1c
    --- /dev/null
    +++ b/widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/imageattachment/OperatorImageAttachmentViewHolder.java
    @@ -0,0 +1,20 @@
    +package com.glia.widgets.chat.adapter.holder.imageattachment;
    +
    +import android.view.View;
    +
    +import androidx.annotation.NonNull;
    +
    +import com.glia.widgets.filepreview.domain.usecase.GetImageFileFromCacheUseCase;
    +import com.glia.widgets.filepreview.domain.usecase.GetImageFileFromDownloadsUseCase;
    +import com.glia.widgets.filepreview.domain.usecase.GetImageFileFromNetworkUseCase;
    +
    +public class OperatorImageAttachmentViewHolder extends ImageAttachmentViewHolder {
    +    public OperatorImageAttachmentViewHolder(
    +            @NonNull View itemView,
    +            GetImageFileFromCacheUseCase getImageFileFromCacheUseCase,
    +            GetImageFileFromDownloadsUseCase getImageFileFromDownloadsUseCase,
    +            GetImageFileFromNetworkUseCase getImageFileFromNetworkUseCase
    +    ) {
    +        super(itemView, getImageFileFromCacheUseCase, getImageFileFromDownloadsUseCase, getImageFileFromNetworkUseCase);
    +    }
    +}
    diff --git a/widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/imageattachment/VisitorImageAttachmentViewHolder.java b/widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/imageattachment/VisitorImageAttachmentViewHolder.java
    new file mode 100644
    index 000000000..67955a034
    --- /dev/null
    +++ b/widgetssdk/src/main/java/com/glia/widgets/chat/adapter/holder/imageattachment/VisitorImageAttachmentViewHolder.java
    @@ -0,0 +1,50 @@
    +package com.glia.widgets.chat.adapter.holder.imageattachment;
    +
    +import android.content.Context;
    +import android.graphics.Typeface;
    +import android.view.View;
    +import android.widget.TextView;
    +
    +import androidx.annotation.NonNull;
    +import androidx.core.content.ContextCompat;
    +import androidx.core.content.res.ResourcesCompat;
    +
    +import com.glia.androidsdk.chat.AttachmentFile;
    +import com.glia.widgets.R;
    +import com.glia.widgets.UiTheme;
    +import com.glia.widgets.filepreview.domain.usecase.GetImageFileFromCacheUseCase;
    +import com.glia.widgets.filepreview.domain.usecase.GetImageFileFromDownloadsUseCase;
    +import com.glia.widgets.filepreview.domain.usecase.GetImageFileFromNetworkUseCase;
    +
    +public class VisitorImageAttachmentViewHolder extends ImageAttachmentViewHolder {
    +    private final TextView deliveredView;
    +
    +    public VisitorImageAttachmentViewHolder(
    +            @NonNull View itemView,
    +            UiTheme uiTheme,
    +            GetImageFileFromCacheUseCase getImageFileFromCacheUseCase,
    +            GetImageFileFromDownloadsUseCase getImageFileFromDownloadsUseCase,
    +            GetImageFileFromNetworkUseCase getImageFileFromNetworkUseCase
    +    ) {
    +        super(itemView, getImageFileFromCacheUseCase, getImageFileFromDownloadsUseCase, getImageFileFromNetworkUseCase);
    +        this.deliveredView = itemView.findViewById(R.id.delivered_view);
    +        setupDeliveredView(itemView.getContext(), uiTheme);
    +    }
    +
    +    public void bind(AttachmentFile attachmentFile, boolean showDelivered) {
    +        super.bind(attachmentFile);
    +        setShowDelivered(showDelivered);
    +    }
    +
    +    private void setShowDelivered(boolean showDelivered) {
    +        deliveredView.setVisibility(showDelivered ? View.VISIBLE : View.GONE);
    +    }
    +
    +    private void setupDeliveredView(Context context, UiTheme uiTheme) {
    +        if (uiTheme.getFontRes() != null) {
    +            Typeface fontFamily = ResourcesCompat.getFont(context, uiTheme.getFontRes());
    +            deliveredView.setTypeface(fontFamily);
    +        }
    +        deliveredView.setTextColor(ContextCompat.getColor(context, uiTheme.getBaseNormalColor()));
    +    }
    +}
    diff --git a/widgetssdk/src/main/java/com/glia/widgets/chat/controller/ChatController.java b/widgetssdk/src/main/java/com/glia/widgets/chat/controller/ChatController.java
    index e35fe9d4f..b908c55d4 100644
    --- a/widgetssdk/src/main/java/com/glia/widgets/chat/controller/ChatController.java
    +++ b/widgetssdk/src/main/java/com/glia/widgets/chat/controller/ChatController.java
    @@ -723,7 +723,7 @@ private void stop() {
         private void appendHistoryChatItem(List currentChatItems, ChatMessage message) {
             if (message.getSender() == Chat.Participant.VISITOR) {
                 appendHistoryMessage(currentChatItems, message);
    -            addVisitorAttachmentItemsToChatItems(currentChatItems, message.getAttachment());
    +            addVisitorAttachmentItemsToChatItems(currentChatItems, message);
             } else if (message.getSender() == Chat.Participant.OPERATOR) {
                 changeLastOperatorMessages(currentChatItems, message);
             }
    @@ -745,9 +745,7 @@ private void appendHistoryMessage(List currentChatItems, ChatMessage m
         private void appendMessageItem(List currentChatItems, ChatMessage message) {
             if (message.getSender() == Chat.Participant.VISITOR) {
                 appendSentMessage(currentChatItems, message);
    -
    -            MessageAttachment attachment = message.getAttachment();
    -            addVisitorAttachmentItemsToChatItems(currentChatItems, attachment);
    +            addVisitorAttachmentItemsToChatItems(currentChatItems, message);
             } else if (message.getSender() == Chat.Participant.OPERATOR) {
                 changeLastOperatorMessages(currentChatItems, message);
                 appendMessagesNotSeen();
    @@ -764,35 +762,21 @@ private void appendMessageItem(List currentChatItems, ChatMessage mess
             }
         }
     
    -    private void addVisitorAttachmentItemsToChatItems(List currentChatItems, MessageAttachment attachment) {
    +    private void addVisitorAttachmentItemsToChatItems(List currentChatItems, ChatMessage chatMessage) {
    +        MessageAttachment attachment = chatMessage.getAttachment();
             if (attachment instanceof FilesAttachment) {
                 FilesAttachment filesAttachment = (FilesAttachment) attachment;
                 AttachmentFile[] files = filesAttachment.getFiles();
    -
                 for (AttachmentFile file : files) {
    -                String mimeType = file.getContentType();
    -                if (mimeType.startsWith("image")) {
    -                    currentChatItems.add(
    -                            new VisitorAttachmentItem(
    -                                    file.getId(),
    -                                    ChatAdapter.VISITOR_IMAGE_VIEW_TYPE,
    -                                    file,
    -                                    false,
    -                                    false
    -                            )
    -                    );
    +                int type;
    +                if (file.getContentType().startsWith("image")) {
    +                    type = ChatAdapter.VISITOR_IMAGE_VIEW_TYPE;
                     } else {
    -                    currentChatItems.add(
    -                            new VisitorAttachmentItem(
    -                                    file.getId(),
    -                                    ChatAdapter.VISITOR_FILE_VIEW_TYPE,
    -                                    file,
    -                                    false,
    -                                    false
    -                            )
    -                    );
    +                    type = ChatAdapter.VISITOR_FILE_VIEW_TYPE;
                     }
    -
    +                currentChatItems.add(
    +                        new VisitorAttachmentItem(chatMessage.getId(), type, file, false, false, false)
    +                );
                 }
             }
         }
    @@ -824,32 +808,54 @@ private void initGliaEngagementObserving() {
         }
     
         private void changeDeliveredIndex(List currentChatItems, VisitorMessage message) {
    +        // "Delivered" status only applies to visitor messages
    +        if (message.getSender() != Chat.Participant.VISITOR) return;
    +        String messageId = message.getId();
    +        boolean foundDelivered = false;
             for (int i = currentChatItems.size() - 1; i >= 0; i--) {
    -            if (currentChatItems.get(i) instanceof VisitorMessageItem) {
    -                VisitorMessageItem item = (VisitorMessageItem) currentChatItems.get(i);
    -                if (item.getId().equals(VisitorMessageItem.HISTORY_ID)) {
    +            ChatItem currentChatItem = currentChatItems.get(i);
    +            if (currentChatItem instanceof VisitorMessageItem) {
    +                VisitorMessageItem item = (VisitorMessageItem) currentChatItem;
    +                String itemId = item.getId();
    +                if (itemId.equals(VisitorMessageItem.HISTORY_ID)) {
                         // we reached the history items no point in going searching further
                         break;
    -                } else if (item.getId().equals(message.getId())) {
    -                    // the visitormessage. show delivered for it.
    -                    currentChatItems.remove(i);
    -                    currentChatItems
    -                            .add(i, new VisitorMessageItem(message.getId(), true, message.getContent()));
    +                } else if (!foundDelivered && itemId.equals(messageId)) {
    +                    foundDelivered = true;
    +                    currentChatItems.set(i, new VisitorMessageItem(itemId, true, item.getMessage()));
                     } else if (item.isShowDelivered()) {
    -                    // remove all other delivered references
    -                    currentChatItems.remove(i);
    -                    currentChatItems.add(i, new VisitorMessageItem(item.getId(), false, item.getMessage()));
    +                    currentChatItems.set(i, new VisitorMessageItem(itemId, false, item.getMessage()));
    +                }
    +            } else if (currentChatItem instanceof VisitorAttachmentItem) {
    +                VisitorAttachmentItem item = (VisitorAttachmentItem) currentChatItem;
    +                if (!foundDelivered && item.getId().equals(messageId)) {
    +                    foundDelivered = true;
    +                    setDelivered(currentChatItems, i, item, true);
    +                } else if (item.showDelivered) {
    +                    setDelivered(currentChatItems, i, item, false);
                     }
                 }
             }
         }
     
    -    private void changeLastOperatorMessages(List currentChatItems, ChatMessage message) {
    -        MessageAttachment attachment = message.getAttachment();
    +    private void setDelivered(List currentChatItems, int i, VisitorAttachmentItem item, boolean delivered) {
    +        currentChatItems.set(
    +                i,
    +                new VisitorAttachmentItem(
    +                        item.getId(),
    +                        item.getViewType(),
    +                        item.attachmentFile,
    +                        item.isFileExists,
    +                        item.isDownloading,
    +                        delivered
    +                )
    +        );
    +    }
     
    +    private void changeLastOperatorMessages(List currentChatItems, ChatMessage message) {
             replaceLastChatHeadItem(currentChatItems);
    -        addOperatorDownloadableItems(currentChatItems, attachment);
    -        addLastMessageItem(currentChatItems, message, attachment);
    +        addOperatorDownloadableItems(currentChatItems, message);
    +        addLastMessageItem(currentChatItems, message);
         }
     
         private void replaceLastChatHeadItem(List currentChatItems) {
    @@ -881,7 +887,8 @@ private void replaceLastChatHeadItem(List currentChatItems) {
             }
         }
     
    -    private void addOperatorDownloadableItems(List currentChatItems, MessageAttachment attachment) {
    +    private void addOperatorDownloadableItems(List currentChatItems, ChatMessage message) {
    +        MessageAttachment attachment = message.getAttachment();
             if (attachment instanceof FilesAttachment) {
                 FilesAttachment filesAttachment = (FilesAttachment) attachment;
                 AttachmentFile[] files = filesAttachment.getFiles();
    @@ -891,37 +898,40 @@ private void addOperatorDownloadableItems(List currentChatItems, Messa
                     boolean showChatHead = i == files.length - 1;
                     String mimeType = file.getContentType();
                     if (mimeType.startsWith("image")) {
    -                    currentChatItems.add(new OperatorAttachmentItem(
    -                            file.getId(),
    -                            ChatAdapter.OPERATOR_IMAGE_VIEW_TYPE,
    -                            showChatHead,
    -                            file,
    -                            chatState.operatorProfileImgUrl, false, false));
    -
    -
    +                    currentChatItems.add(
    +                            new OperatorAttachmentItem(
    +                                    message.getId(),
    +                                    ChatAdapter.OPERATOR_IMAGE_VIEW_TYPE,
    +                                    showChatHead,
    +                                    file,
    +                                    chatState.operatorProfileImgUrl, false, false)
    +                    );
                     } else {
    -                    currentChatItems.add(new OperatorAttachmentItem(
    -                            file.getId(),
    -                            ChatAdapter.OPERATOR_FILE_VIEW_TYPE,
    -                            showChatHead,
    -                            file,
    -                            chatState.operatorProfileImgUrl, false, false));
    +                    currentChatItems.add(
    +                            new OperatorAttachmentItem(
    +                                    message.getId(),
    +                                    ChatAdapter.OPERATOR_FILE_VIEW_TYPE,
    +                                    showChatHead,
    +                                    file,
    +                                    chatState.operatorProfileImgUrl, false, false)
    +                    );
                     }
     
                 }
             }
         }
     
    -    private void addLastMessageItem(List currentChatItems, ChatMessage message, MessageAttachment attachment) {
    +    private void addLastMessageItem(List currentChatItems, ChatMessage message) {
             if (!message.getContent().equals("")) {
    +            MessageAttachment messageAttachment = message.getAttachment();
                 currentChatItems.add(new OperatorMessageItem(
                         message.getId(),
                         chatState.operatorProfileImgUrl,
                         true,
                         message.getContent(),
    -                    getSingleChoiceAttachmentOptions(attachment),
    +                    getSingleChoiceAttachmentOptions(messageAttachment),
                         null,
    -                    getSingleChoiceAttachmentImgUrl(attachment)
    +                    getSingleChoiceAttachmentImgUrl(messageAttachment)
                 ));
             }
         }
    diff --git a/widgetssdk/src/main/java/com/glia/widgets/chat/model/history/OperatorAttachmentItem.java b/widgetssdk/src/main/java/com/glia/widgets/chat/model/history/OperatorAttachmentItem.java
    index e3f605897..e07715d7a 100644
    --- a/widgetssdk/src/main/java/com/glia/widgets/chat/model/history/OperatorAttachmentItem.java
    +++ b/widgetssdk/src/main/java/com/glia/widgets/chat/model/history/OperatorAttachmentItem.java
    @@ -3,6 +3,7 @@
     import androidx.annotation.NonNull;
     
     import com.glia.androidsdk.chat.AttachmentFile;
    +import com.glia.widgets.helper.Utils;
     
     import java.util.Objects;
     
    @@ -14,8 +15,8 @@ public class OperatorAttachmentItem extends ChatItem {
         public final boolean isFileExists;
         public final boolean isDownloading;
     
    -    public OperatorAttachmentItem(String id, int viewType, boolean showChatHead, AttachmentFile attachmentFile, String operatorProfileImgUrl, boolean isFileExists, boolean isDownloading) {
    -        super(id, viewType);
    +    public OperatorAttachmentItem(String chatItemId, int viewType, boolean showChatHead, AttachmentFile attachmentFile, String operatorProfileImgUrl, boolean isFileExists, boolean isDownloading) {
    +        super(chatItemId, viewType);
             this.showChatHead = showChatHead;
             this.attachmentFile = attachmentFile;
             this.operatorProfileImgUrl = operatorProfileImgUrl;
    @@ -46,7 +47,8 @@ public int hashCode() {
         public String toString() {
             return "OperatorAttachmentItem{" +
                     ", showChatHead=" + showChatHead +
    -                ", attachmentFile='" + attachmentFile + '\'' +
    +                ", attachmentFile=" + Utils.toString(attachmentFile) +
    +                ", chatItemId='" + getId() + '\'' +
                     ", operatorProfileImgUrl='" + operatorProfileImgUrl + '\'' +
                     ", isFileExists='" + isFileExists + '\'' +
                     ", isDownloading='" + isDownloading + '\'' +
    diff --git a/widgetssdk/src/main/java/com/glia/widgets/chat/model/history/OperatorMessageItem.java b/widgetssdk/src/main/java/com/glia/widgets/chat/model/history/OperatorMessageItem.java
    index c6d1526bd..d76bdd277 100644
    --- a/widgetssdk/src/main/java/com/glia/widgets/chat/model/history/OperatorMessageItem.java
    +++ b/widgetssdk/src/main/java/com/glia/widgets/chat/model/history/OperatorMessageItem.java
    @@ -1,5 +1,7 @@
     package com.glia.widgets.chat.model.history;
     
    +import androidx.annotation.NonNull;
    +
     import com.glia.androidsdk.chat.SingleChoiceOption;
     import com.glia.widgets.chat.adapter.ChatAdapter;
     
    @@ -52,11 +54,13 @@ public int hashCode() {
             return Objects.hash(super.hashCode(), operatorProfileImgUrl, showChatHead, content, singleChoiceOptions, selectedChoiceIndex, choiceCardImageUrl);
         }
     
    +    @NonNull
         @Override
         public String toString() {
             return "OperatorMessageItem{" +
                     "operatorProfileImgUrl='" + operatorProfileImgUrl + '\'' +
                     ", showChatHead=" + showChatHead +
    +                ", chatItemId=" + getId() +
                     ", content='" + content + '\'' +
                     ", singleChoiceOptions=" + singleChoiceOptions +
                     ", selectedChoiceIndex=" + selectedChoiceIndex +
    diff --git a/widgetssdk/src/main/java/com/glia/widgets/chat/model/history/VisitorAttachmentItem.java b/widgetssdk/src/main/java/com/glia/widgets/chat/model/history/VisitorAttachmentItem.java
    index a7c62ba5a..43d194431 100644
    --- a/widgetssdk/src/main/java/com/glia/widgets/chat/model/history/VisitorAttachmentItem.java
    +++ b/widgetssdk/src/main/java/com/glia/widgets/chat/model/history/VisitorAttachmentItem.java
    @@ -3,6 +3,7 @@
     import androidx.annotation.NonNull;
     
     import com.glia.androidsdk.chat.AttachmentFile;
    +import com.glia.widgets.helper.Utils;
     
     import java.util.Objects;
     
    @@ -11,12 +12,15 @@ public class VisitorAttachmentItem extends ChatItem {
         public final AttachmentFile attachmentFile;
         public final boolean isFileExists;
         public final boolean isDownloading;
    +    public final boolean showDelivered;
     
    -    public VisitorAttachmentItem(String id, int viewType, AttachmentFile attachmentFile, boolean isFileExists, boolean isDownloading) {
    -        super(id, viewType);
    +    public VisitorAttachmentItem(String chatItemId, int viewType, AttachmentFile attachmentFile,
    +                                 boolean isFileExists, boolean isDownloading, boolean showDelivered) {
    +        super(chatItemId, viewType);
             this.attachmentFile = attachmentFile;
             this.isFileExists = isFileExists;
             this.isDownloading = isDownloading;
    +        this.showDelivered = showDelivered;
         }
     
         @Override
    @@ -25,23 +29,24 @@ public boolean equals(Object o) {
             if (o == null || getClass() != o.getClass()) return false;
             if (!super.equals(o)) return false;
             VisitorAttachmentItem that = (VisitorAttachmentItem) o;
    -        return Objects.equals(attachmentFile, that.attachmentFile) &&
    -                Objects.equals(isFileExists, that.isFileExists) &&
    -                Objects.equals(isDownloading, that.isDownloading);
    +        return isFileExists == that.isFileExists && isDownloading == that.isDownloading &&
    +                showDelivered == that.showDelivered && Objects.equals(attachmentFile, that.attachmentFile);
         }
     
         @Override
         public int hashCode() {
    -        return Objects.hash(super.hashCode(), attachmentFile, isFileExists, isDownloading);
    +        return Objects.hash(super.hashCode(), attachmentFile, isFileExists, isDownloading, showDelivered);
         }
     
         @NonNull
         @Override
         public String toString() {
             return "VisitorAttachmentItem{" +
    -                ", attachmentFile='" + attachmentFile + '\'' +
    -                ", isFileExists='" + isFileExists + '\'' +
    -                ", isDownloading='" + isDownloading + '\'' +
    -                +'}';
    +                "attachmentFile=" + Utils.toString(attachmentFile) +
    +                ", chatItemId=" + getId() +
    +                ", isFileExists=" + isFileExists +
    +                ", isDownloading=" + isDownloading +
    +                ", showDelivered=" + showDelivered +
    +                '}';
         }
     }
    diff --git a/widgetssdk/src/main/java/com/glia/widgets/chat/model/history/VisitorMessageItem.java b/widgetssdk/src/main/java/com/glia/widgets/chat/model/history/VisitorMessageItem.java
    index 53dbea153..96a362b90 100644
    --- a/widgetssdk/src/main/java/com/glia/widgets/chat/model/history/VisitorMessageItem.java
    +++ b/widgetssdk/src/main/java/com/glia/widgets/chat/model/history/VisitorMessageItem.java
    @@ -1,5 +1,7 @@
     package com.glia.widgets.chat.model.history;
     
    +import androidx.annotation.NonNull;
    +
     import com.glia.widgets.chat.adapter.ChatAdapter;
     
     import java.util.Objects;
    @@ -7,13 +9,11 @@
     public class VisitorMessageItem extends ChatItem {
         public final static String HISTORY_ID = "history_id";
         public final static String UNSENT_MESSAGE_ID = "unsent_message_id";
    -    private final String id;
         private final boolean showDelivered;
         private final String message;
     
         public VisitorMessageItem(String id, boolean showDelivered, String message) {
             super(id, ChatAdapter.VISITOR_MESSAGE_TYPE);
    -        this.id = id;
             this.showDelivered = showDelivered;
             this.message = message;
         }
    @@ -22,18 +22,15 @@ public String getMessage() {
             return message;
         }
     
    -    public String getId() {
    -        return id;
    -    }
    -
         public boolean isShowDelivered() {
             return showDelivered;
         }
     
    +    @NonNull
         @Override
         public String toString() {
             return "VisitorMessageItem{" +
    -                "id='" + id + '\'' +
    +                "chatItemId='" + getId() + '\'' +
                     ", showDelivered=" + showDelivered +
                     ", message='" + message + '\'' +
                     '}';
    @@ -46,12 +43,12 @@ public boolean equals(Object o) {
             if (!super.equals(o)) return false;
             VisitorMessageItem that = (VisitorMessageItem) o;
             return showDelivered == that.showDelivered &&
    -                id.equals(that.id) &&
    +                getId().equals(that.getId()) &&
                     Objects.equals(message, that.message);
         }
     
         @Override
         public int hashCode() {
    -        return Objects.hash(super.hashCode(), id, showDelivered, message);
    +        return Objects.hash(super.hashCode(), getId(), showDelivered, message);
         }
     }
    diff --git a/widgetssdk/src/main/java/com/glia/widgets/helper/Utils.java b/widgetssdk/src/main/java/com/glia/widgets/helper/Utils.java
    index 59c269b4b..75310fb0d 100644
    --- a/widgetssdk/src/main/java/com/glia/widgets/helper/Utils.java
    +++ b/widgetssdk/src/main/java/com/glia/widgets/helper/Utils.java
    @@ -18,6 +18,7 @@
     import androidx.annotation.AttrRes;
     import androidx.annotation.StyleableRes;
     
    +import com.glia.androidsdk.chat.AttachmentFile;
     import com.glia.widgets.Constants;
     import com.glia.widgets.GliaWidgets;
     import com.glia.widgets.R;
    @@ -705,4 +706,14 @@ public static boolean compareStringWithTrim(String a, String b) {
             if (a == null || b == null) return false;
             return a.trim().equals(b.trim());
         }
    +
    +    public static String toString(AttachmentFile attachmentFile) {
    +        return "{" +
    +                "id=" + attachmentFile.getId() +
    +                ", size=" + attachmentFile.getSize() +
    +                ", contentType=" + attachmentFile.getContentType() +
    +                ", isDeleted=" + attachmentFile.isDeleted() +
    +                ", name=" + attachmentFile.getName() +
    +                " }";
    +    }
     }
    diff --git a/widgetssdk/src/main/res/layout/chat_attachment_visitor_file_layout.xml b/widgetssdk/src/main/res/layout/chat_attachment_visitor_file_layout.xml
    index 0834716a7..8201a0828 100644
    --- a/widgetssdk/src/main/res/layout/chat_attachment_visitor_file_layout.xml
    +++ b/widgetssdk/src/main/res/layout/chat_attachment_visitor_file_layout.xml
    @@ -44,4 +44,18 @@
             app:layout_constraintStart_toStartOf="@id/start_guideline"
             app:layout_constraintTop_toTopOf="parent" />
     
    +    
     
    diff --git a/widgetssdk/src/main/res/layout/chat_attachment_visitor_image_layout.xml b/widgetssdk/src/main/res/layout/chat_attachment_visitor_image_layout.xml
    index bdd8b25d8..d805ff33d 100644
    --- a/widgetssdk/src/main/res/layout/chat_attachment_visitor_image_layout.xml
    +++ b/widgetssdk/src/main/res/layout/chat_attachment_visitor_image_layout.xml
    @@ -1,7 +1,6 @@
     
     
    @@ -30,4 +29,18 @@
             app:layout_constraintStart_toStartOf="@id/start_guideline"
             app:layout_constraintTop_toTopOf="parent" />
     
    +    
     
    
    From d31e1797f77920d605bc96cc55d63ec9dac432ef Mon Sep 17 00:00:00 2001
    From: Deniss Denissov 
    Date: Thu, 6 Jan 2022 12:51:21 +0200
    Subject: [PATCH 7/7] Version update to 1.7.0
    
    ---
     widgetssdk/build.gradle | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/widgetssdk/build.gradle b/widgetssdk/build.gradle
    index 212889a59..401b7da44 100644
    --- a/widgetssdk/build.gradle
    +++ b/widgetssdk/build.gradle
    @@ -7,8 +7,8 @@ android {
         defaultConfig {
             minSdkVersion 24
             targetSdkVersion 30
    -        versionCode 22
    -        versionName "1.6.17"
    +        versionCode 30
    +        versionName "1.7.0"
             testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
             consumerProguardFiles "consumer-rules.pro"
         }