From bd55e2021f8e525507d60341ac4c50a21dfe77b3 Mon Sep 17 00:00:00 2001 From: Francisco Fortes Date: Tue, 8 Oct 2024 13:25:58 +0000 Subject: [PATCH] Pull request #80: Ffortes MCA-3945 realTimeChatCustomisation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge in MML/infobip-mobile-messaging-flutter from ffortes-MCA-3945-realTimeChatCustomisation to main Squashed commit of the following: commit 5d8b488c9be622d330183958ab068525b1d741f8 Author: Jakub Dzubak Date: Tue Oct 8 08:27:15 2024 +0200 MCA-4155 InfobipMobilemessagingPlugin class structure reorganisation commit 7f0b5352b7cf3e4a017675dd0a6b90bec9a223e3 Merge: 8c006aa cd96e11 Author: Matuš Tokar Date: Mon Oct 7 15:15:43 2024 +0200 Merge remote-tracking branch 'origin/ffortes-MCA-3945-realTimeChatCustomisation' into ffortes-MCA-3945-realTimeChatCustomisation commit 8c006aa17fb0941b7d243c6c5e8e1edad32e4dcc Author: Matuš Tokar Date: Mon Oct 7 15:15:33 2024 +0200 setWidgetTheme commit cd96e112b61c88ff8201115a266d89d41033ab6c Author: Francisco Fortes Date: Mon Oct 7 15:03:54 2024 +0200 Podfile avoiding code signing commit 1cd553a9aedb8d8b97b2c544abd46a92e83db740 Author: Francisco Fortes Date: Fri Oct 4 15:48:14 2024 +0200 iOS native target increased commit 19e5a7039bb9f9c825e30fe133f442469136da71 Author: Matuš Tokar Date: Fri Oct 4 15:33:27 2024 +0200 android review fixes commit d4470e0873cc9251b05386f87b45de3c9335f2c0 Author: Francisco Fortes Date: Thu Oct 3 16:27:53 2024 +0200 added static setWidgetTheme commit de0ee7cda4c2115a09da8af21b80a648e373f39a Author: Matuš Tokar Date: Wed Oct 2 16:10:07 2024 +0200 indent commit a9701398b7dc50fb56a56e03992d388d75664fac Author: Matuš Tokar Date: Wed Oct 2 15:16:32 2024 +0200 review fixes commit 7b78cf720e4ed0d613ef14438df867bdb20899d3 Author: Francisco Fortes Date: Tue Oct 1 13:19:56 2024 +0200 removed obsolete customTheme commit 19030675dbfe090c459e6cf7e78d560edb81da32 Author: Francisco Fortes Date: Tue Oct 1 11:08:53 2024 +0200 review changes commit 280b56bb5211e726587d985b8487fec4330ed761 Author: Jakub Dzubak Date: Tue Oct 1 07:54:40 2024 +0200 MCA-4155 raw msg event commit f05d8fe4326d7f0b71234cde1eac970fd0578ea0 Merge: f076c05 9bf095e Author: Matuš Tokar Date: Mon Sep 30 15:58:23 2024 +0200 Merge remote-tracking branch 'origin/ffortes-MCA-3945-realTimeChatCustomisation' into ffortes-MCA-3945-realTimeChatCustomisation commit f076c0524fab25499e46d4911866fe4bcd39213b Author: Matuš Tokar Date: Mon Sep 30 15:57:52 2024 +0200 new android version commit 9bf095e60b6d526ae38dcd1db7032102a3ac9b26 Author: Francisco Fortes Date: Mon Sep 30 09:22:39 2024 +0200 cleanup commit 77e9b9b9ca13c8c5208a037d84b171b570d53157 Author: Matuš Tokar Date: Thu Sep 26 16:25:41 2024 +0200 replace activity for context commit e0521e7a4c5e2787ece8b963e1522b0b8cfcc01c Author: Matuš Tokar Date: Thu Sep 26 13:55:33 2024 +0200 android part wip commit 9e22eed022e8cc2d2d99cc8c2c962bfffefaad3a Author: Matuš Tokar Date: Wed Sep 25 17:29:33 2024 +0200 android part wip commit 2db19e97108caa26cd5c8e6e2996f91d4e901559 Author: Matuš Tokar Date: Wed Sep 25 14:06:39 2024 +0200 remove chatInputPlaceholderColor commit 6ddaa64e248e6762abf9584e05ccdf65c69577d6 Author: Francisco Fortes Date: Wed Sep 25 13:17:18 2024 +0200 fixed chatStatusBarIconsColorMode ... and 8 more commits --- android/build.gradle | 2 +- .../flutter/chat/ChatCustomization.java | 512 ++++++++++++++++++ .../flutter/chat/ChatPlatformView.java | 5 + .../flutter/chat/ChatViewEvent.java | 1 + .../flutter/common/ErrorCodes.java | 3 +- .../InfobipMobilemessagingPlugin.java | 444 ++++++++------- example/ios/Podfile | 2 + example/lib/chat_customization.dart | 105 ++-- example/lib/main.dart | 10 - example/lib/screens/homepage.dart | 10 + example/pubspec.lock | 2 +- ios/Classes/Configuration.swift | 14 +- ios/Classes/CustomisationUtils.swift | 127 ----- ios/Classes/CustomizationUtils.swift | 188 +++++++ .../SwiftInfobipMobilemessagingPlugin.swift | 51 +- ios/infobip_mobilemessaging.podspec | 10 +- lib/infobip_mobilemessaging.dart | 9 + lib/models/chat_view_event.dart | 5 + lib/models/configuration.dart | 144 ++++- lib/models/ios_chat_settings.dart | 1 + test/json_test.dart | 4 - test/utils/models_examples.dart | 9 - 22 files changed, 1236 insertions(+), 422 deletions(-) create mode 100644 android/src/main/java/org/infobip/plugins/mobilemessaging/flutter/chat/ChatCustomization.java delete mode 100644 ios/Classes/CustomisationUtils.swift create mode 100644 ios/Classes/CustomizationUtils.swift diff --git a/android/build.gradle b/android/build.gradle index 1b3407d..5844761 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -30,7 +30,7 @@ android { def withWebRTCUI = getRootProjectProperty('withWebRTCUI', false) dependencies { - def mmVersion = '12.10.0' + def mmVersion = '12.12.1' //flutter and mm dependencies clash implementation ("org.jetbrains.kotlin:kotlin-stdlib-jdk8") { version { diff --git a/android/src/main/java/org/infobip/plugins/mobilemessaging/flutter/chat/ChatCustomization.java b/android/src/main/java/org/infobip/plugins/mobilemessaging/flutter/chat/ChatCustomization.java new file mode 100644 index 0000000..51785cb --- /dev/null +++ b/android/src/main/java/org/infobip/plugins/mobilemessaging/flutter/chat/ChatCustomization.java @@ -0,0 +1,512 @@ +package org.infobip.plugins.mobilemessaging.flutter.chat; + +import android.app.Activity; +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.content.res.AssetManager; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.graphics.Color; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.util.Log; +import androidx.annotation.Nullable; +import org.infobip.mobile.messaging.MobileMessaging; +import org.infobip.mobile.messaging.MobileMessagingCore; +import org.infobip.mobile.messaging.NotificationSettings; +import org.infobip.mobile.messaging.app.ActivityLifecycleMonitor; +import org.infobip.mobile.messaging.chat.InAppChat; +import org.infobip.mobile.messaging.chat.view.styles.InAppChatInputViewStyle; +import org.infobip.mobile.messaging.chat.view.styles.InAppChatStyle; +import org.infobip.mobile.messaging.chat.view.styles.InAppChatTheme; +import org.infobip.mobile.messaging.chat.view.styles.InAppChatToolbarStyle; +import org.infobip.mobile.messaging.interactive.NotificationAction; +import org.infobip.mobile.messaging.interactive.NotificationCategory; +import org.infobip.mobile.messaging.storage.SQLiteMessageStore; + +import java.io.IOException; +import java.util.List; + +import io.flutter.FlutterInjector; +import io.flutter.embedding.engine.loader.FlutterLoader; + +public class ChatCustomization { + private static final String TAG = "MobileMessagingFlutter"; + // StatusBar + private String chatStatusBarBackgroundColor; + private String chatStatusBarIconsColorMode; + // Toolbar + private ToolbarCustomization chatToolbar; + private ToolbarCustomization attachmentPreviewToolbar; + private String attachmentPreviewSaveMenuItemIcon; + private String attachmentPreviewMenuItemsIconTint; + + // NetworkError + private String networkErrorText; + private String networkErrorTextColor; + private String networkErrorTextAppearance; + private String networkErrorLabelBackgroundColor; + // Chat + private String chatBackgroundColor; + private String chatProgressBarColor; + // Input + private String chatInputTextAppearance; + private String chatInputTextColor; + private String chatInputBackgroundColor; + private String chatInputHintText; + private String chatInputHintTextColor; + private String chatInputAttachmentIcon; + private String chatInputAttachmentIconTint; + private String chatInputAttachmentBackgroundDrawable; + private String chatInputAttachmentBackgroundColor; + private String chatInputSendIcon; + private String chatInputSendIconTint; + private String chatInputSendBackgroundDrawable; + private String chatInputSendBackgroundColor; + private String chatInputSeparatorLineColor; + private boolean chatInputSeparatorLineVisible; + private String chatInputCursorColor; + + + public static class ToolbarCustomization { + private String titleTextAppearance; + private String titleTextColor; + private String titleText; + private boolean titleCentered; + private String backgroundColor; + private String navigationIcon; + private String navigationIconTint; + private String subtitleTextAppearance; // android only + private String subtitleTextColor; // android only + private String subtitleText; // android only + private boolean subtitleCentered; // android only + + public String getTitleTextAppearance() { + return titleTextAppearance; + } + + public void setTitleTextAppearance(String titleTextAppearance) { + this.titleTextAppearance = titleTextAppearance; + } + + public String getTitleTextColor() { + return titleTextColor; + } + + public void setTitleTextColor(String titleTextColor) { + this.titleTextColor = titleTextColor; + } + + public String getTitleText() { + return titleText; + } + + public void setTitleText(String titleText) { + this.titleText = titleText; + } + + public boolean isTitleCentered() { + return titleCentered; + } + + public void setTitleCentered(boolean titleCentered) { + this.titleCentered = titleCentered; + } + + public String getBackgroundColor() { + return backgroundColor; + } + + public void setBackgroundColor(String backgroundColor) { + this.backgroundColor = backgroundColor; + } + + public String getNavigationIcon() { + return navigationIcon; + } + + public void setNavigationIcon(String navigationIcon) { + this.navigationIcon = navigationIcon; + } + + public String getNavigationIconTint() { + return navigationIconTint; + } + + public void setNavigationIconTint(String navigationIconTint) { + this.navigationIconTint = navigationIconTint; + } + + public String getSubtitleTextAppearance() { + return subtitleTextAppearance; + } + + public void setSubtitleTextAppearance(String subtitleTextAppearance) { + this.subtitleTextAppearance = subtitleTextAppearance; + } + + public String getSubtitleTextColor() { + return subtitleTextColor; + } + + public void setSubtitleTextColor(String subtitleTextColor) { + this.subtitleTextColor = subtitleTextColor; + } + + public String getSubtitleText() { + return subtitleText; + } + + public void setSubtitleText(String subtitleText) { + this.subtitleText = subtitleText; + } + + public boolean isSubtitleCentered() { + return subtitleCentered; + } + + public void setSubtitleCentered(boolean subtitleCentered) { + this.subtitleCentered = subtitleCentered; + } + } + + public InAppChatTheme createTheme(Context context) { + FlutterLoader loader = FlutterInjector.instance().flutterLoader(); + + InAppChatToolbarStyle toolbarStyle = new InAppChatToolbarStyle.Builder() + .setStatusBarBackgroundColor(parseColor(chatStatusBarBackgroundColor)) + .setLightStatusBarIcons(chatStatusBarIconsColorMode == "light") + .setToolbarBackgroundColor(parseColor(chatToolbar.backgroundColor)) + .setNavigationIcon(loadDrawable(chatToolbar.navigationIcon, loader, context)) + .setNavigationIconTint(parseColor(chatToolbar.navigationIconTint)) + .setTitleTextAppearance(getResId(context.getResources(), chatToolbar.titleTextAppearance, context.getPackageName())) + .setTitleTextColor(parseColor(chatToolbar.titleTextColor)) + .setTitleText(chatToolbar.titleText) + .setIsTitleCentered(chatToolbar.titleCentered) + .setSubtitleTextAppearance(getResId(context.getResources(), chatToolbar.subtitleTextAppearance, context.getPackageName())) + .setSubtitleTextColor(parseColor(chatToolbar.subtitleTextColor)) + .setSubtitleText(chatToolbar.subtitleText) + .setIsSubtitleCentered(chatToolbar.subtitleCentered) + .build(); + + InAppChatToolbarStyle attachmentToolbarStyle = new InAppChatToolbarStyle.Builder() + .setStatusBarBackgroundColor(parseColor(chatStatusBarBackgroundColor)) + .setLightStatusBarIcons(chatStatusBarIconsColorMode == "light") + .setToolbarBackgroundColor(parseColor(attachmentPreviewToolbar.backgroundColor)) + .setNavigationIcon(loadDrawable(attachmentPreviewToolbar.navigationIcon, loader, context)) + .setNavigationIconTint(parseColor(attachmentPreviewToolbar.navigationIconTint)) + .setSaveAttachmentMenuItemIcon(loadDrawable(attachmentPreviewSaveMenuItemIcon, loader, context)) + .setMenuItemsIconTint(parseColor(attachmentPreviewMenuItemsIconTint)) + .setTitleTextAppearance(getResId(context.getResources(), attachmentPreviewToolbar.titleTextAppearance, context.getPackageName())) + .setTitleTextColor(parseColor(attachmentPreviewToolbar.titleTextColor)) + .setTitleText(attachmentPreviewToolbar.titleText) + .setIsTitleCentered(attachmentPreviewToolbar.titleCentered) + .setSubtitleTextAppearance(getResId(context.getResources(), attachmentPreviewToolbar.subtitleTextAppearance, context.getPackageName())) + .setSubtitleTextColor(parseColor(attachmentPreviewToolbar.subtitleTextColor)) + .setSubtitleText(attachmentPreviewToolbar.subtitleText) + .setIsSubtitleCentered(attachmentPreviewToolbar.subtitleCentered) + .build(); + + InAppChatStyle chatStyle = new InAppChatStyle.Builder() + .setBackgroundColor(parseColor(chatBackgroundColor)) + .setProgressBarColor(parseColor(chatProgressBarColor)) + .setNetworkConnectionText(networkErrorText) + .setNetworkConnectionTextAppearance(getResId(context.getResources(), networkErrorTextAppearance, context.getPackageName())) + .setNetworkConnectionTextColor(parseColor(networkErrorTextColor)) + .setNetworkConnectionLabelBackgroundColor(parseColor(networkErrorLabelBackgroundColor)) + .build(); + + InAppChatInputViewStyle.Builder inputViewStyleBuilder = new InAppChatInputViewStyle.Builder() + .setTextAppearance(getResId(context.getResources(), chatInputTextAppearance, context.getPackageName())) + .setTextColor(parseColor(chatInputTextColor)) + .setBackgroundColor(parseColor(chatInputBackgroundColor)) + .setHintText(chatInputHintText) + .setHintTextColor(parseColor(chatInputHintTextColor)) + .setAttachmentIcon(loadDrawable(chatInputAttachmentIcon, loader, context)) + .setAttachmentBackgroundDrawable(loadDrawable(chatInputAttachmentBackgroundDrawable, loader, context)) + .setAttachmentBackgroundColor(parseColor(chatInputAttachmentBackgroundColor)) + .setSendIcon(loadDrawable(chatInputSendIcon, loader, context)) + .setSendBackgroundDrawable(loadDrawable(chatInputSendBackgroundDrawable, loader, context)) + .setSendBackgroundColor(parseColor(chatInputSendBackgroundColor)) + .setSeparatorLineColor( parseColor(chatInputSeparatorLineColor)) + .setIsSeparatorLineVisible(chatInputSeparatorLineVisible) + .setCursorColor(parseColor(chatInputCursorColor)); + + @Nullable Integer inputAttachmentIconTint = parseColor(chatInputAttachmentIconTint); + if (inputAttachmentIconTint != null) { + inputViewStyleBuilder.setAttachmentIconTint(ColorStateList.valueOf(inputAttachmentIconTint)); + } + @Nullable Integer inputSendIconTint = parseColor(chatInputSendIconTint); + if (inputSendIconTint != null) { + inputViewStyleBuilder.setSendIconTint(ColorStateList.valueOf(inputSendIconTint)); + } + + return new InAppChatTheme( + toolbarStyle, + attachmentToolbarStyle, + chatStyle, + inputViewStyleBuilder.build() + ); + } + + private @Nullable Integer parseColor(@Nullable String color) { + @Nullable Integer result = null; + if (color == null) { + return result; + } + try { + result = Integer.valueOf(Color.parseColor(color)); + } catch (IllegalArgumentException e) { + Log.e(TAG, "parseColor: " + color + e.getMessage()); + } + return result; + } + + /** + * Gets resource ID + * + * @param res the resources where to look for + * @param resPath the name of the resource + * @param packageName name of the package where the resource should be searched for + * @return resource identifier or 0 if not found + */ + private @Nullable Integer getResId(Resources res, String resPath, String packageName) { + try { + int resId = res.getIdentifier(resPath, "mipmap", packageName); + if (resId == 0) { + resId = res.getIdentifier(resPath, "drawable", packageName); + } + if (resId == 0) { + resId = res.getIdentifier(resPath, "raw", packageName); + } + if (resId == 0) { + resId = res.getIdentifier(resPath, "style", packageName); + } + return Integer.valueOf(resId); + } catch (Exception e) { + Log.e(TAG, "getResId: " + resPath + e.getMessage()); + return null; + } + } + + private @Nullable Drawable loadDrawable(String drawableSrc, FlutterLoader loader, Context context) { + AssetManager assets = context.getAssets(); + try (AssetFileDescriptor fileDescriptor = assets.openFd(loader.getLookupKeyForAsset(drawableSrc))) { + return new BitmapDrawable(context.getResources(), fileDescriptor.createInputStream()); + } catch (IOException e) { + Log.e(TAG, "loadDrawable: " + drawableSrc + e.getMessage()); + return null; + } + + } + + // Getters and Setters + public String getChatStatusBarBackgroundColor() { + return chatStatusBarBackgroundColor; + } + + public void setChatStatusBarBackgroundColor(String chatStatusBarBackgroundColor) { + this.chatStatusBarBackgroundColor = chatStatusBarBackgroundColor; + } + + public String getChatStatusBarIconsColorMode() { + return chatStatusBarIconsColorMode; + } + + public void setChatStatusBarIconsColorMode(String chatStatusBarIconsColorMode) { + this.chatStatusBarIconsColorMode = chatStatusBarIconsColorMode; + } + + public ToolbarCustomization getAttachmentPreviewToolbar() { + return attachmentPreviewToolbar; + } + + + public void setAttachmentPreviewToolbar(ToolbarCustomization attachmentPreviewToolbar) { + this.attachmentPreviewToolbar = attachmentPreviewToolbar; + } + + public ToolbarCustomization getChatToolbar() { + return chatToolbar; + } + + public void setChatToolbar(ToolbarCustomization chatToolbar) { + this.chatToolbar = chatToolbar; + } + + public String getNetworkErrorText() { + return networkErrorText; + } + + public void setNetworkErrorText(String networkErrorText) { + this.networkErrorText = networkErrorText; + } + + public String getNetworkErrorTextColor() { + return networkErrorTextColor; + } + + public void setNetworkErrorTextColor(String networkErrorTextColor) { + this.networkErrorTextColor = networkErrorTextColor; + } + + public String getNetworkErrorTextAppearance() { + return networkErrorTextAppearance; + } + + public void setNetworkErrorTextAppearance(String networkErrorTextAppearance) { + this.networkErrorTextAppearance = networkErrorTextAppearance; + } + + public String getNetworkErrorLabelBackgroundColor() { + return networkErrorLabelBackgroundColor; + } + + public void setNetworkErrorLabelBackgroundColor(String networkErrorLabelBackgroundColor) { + this.networkErrorLabelBackgroundColor = networkErrorLabelBackgroundColor; + } + + public String getChatBackgroundColor() { + return chatBackgroundColor; + } + + public void setChatBackgroundColor(String chatBackgroundColor) { + this.chatBackgroundColor = chatBackgroundColor; + } + + public String getChatProgressBarColor() { + return chatProgressBarColor; + } + + public void setChatProgressBarColor(String chatProgressBarColor) { + this.chatProgressBarColor = chatProgressBarColor; + } + + public String getChatInputTextAppearance() { + return chatInputTextAppearance; + } + + public void setChatInputTextAppearance(String chatInputTextAppearance) { + this.chatInputTextAppearance = chatInputTextAppearance; + } + + public String getChatInputTextColor() { + return chatInputTextColor; + } + + public void setChatInputTextColor(String chatInputTextColor) { + this.chatInputTextColor = chatInputTextColor; + } + + public String getChatInputBackgroundColor() { + return chatInputBackgroundColor; + } + + public void setChatInputBackgroundColor(String chatInputBackgroundColor) { + this.chatInputBackgroundColor = chatInputBackgroundColor; + } + + public String getChatInputHintText() { + return chatInputHintText; + } + + public void setChatInputHintText(String chatInputHintText) { + this.chatInputHintText = chatInputHintText; + } + + public String getChatInputHintTextColor() { + return chatInputHintTextColor; + } + + public void setChatInputHintTextColor(String chatInputHintTextColor) { + this.chatInputHintTextColor = chatInputHintTextColor; + } + + public String getChatInputAttachmentIcon() { + return chatInputAttachmentIcon; + } + + public void setChatInputAttachmentIcon(String chatInputAttachmentIcon) { + this.chatInputAttachmentIcon = chatInputAttachmentIcon; + } + + public String getChatInputAttachmentIconTint() { + return chatInputAttachmentIconTint; + } + + public void setChatInputAttachmentIconTint(String chatInputAttachmentIconTint) { + this.chatInputAttachmentIconTint = chatInputAttachmentIconTint; + } + + public String getChatInputAttachmentBackgroundDrawable() { + return chatInputAttachmentBackgroundDrawable; + } + + public void setChatInputAttachmentBackgroundDrawable(String chatInputAttachmentBackgroundDrawable) { + this.chatInputAttachmentBackgroundDrawable = chatInputAttachmentBackgroundDrawable; + } + + public String getChatInputAttachmentBackgroundColor() { + return chatInputAttachmentBackgroundColor; + } + + public void setChatInputAttachmentBackgroundColor(String chatInputAttachmentBackgroundColor) { + this.chatInputAttachmentBackgroundColor = chatInputAttachmentBackgroundColor; + } + + public String getChatInputSendIcon() { + return chatInputSendIcon; + } + + public void setChatInputSendIcon(String chatInputSendIcon) { + this.chatInputSendIcon = chatInputSendIcon; + } + + public String getChatInputSendIconTint() { + return chatInputSendIconTint; + } + + public void setChatInputSendIconTint(String chatInputSendIconTint) { + this.chatInputSendIconTint = chatInputSendIconTint; + } + + public String getChatInputSendBackgroundDrawable() { + return chatInputSendBackgroundDrawable; + } + + public void setChatInputSendBackgroundDrawable(String chatInputSendBackgroundDrawable) { + this.chatInputSendBackgroundDrawable = chatInputSendBackgroundDrawable; + } + + public String getChatInputSendBackgroundColor() { + return chatInputSendBackgroundColor; + } + + public void setChatInputSendBackgroundColor(String chatInputSendBackgroundColor) { + this.chatInputSendBackgroundColor = chatInputSendBackgroundColor; + } + + public String getChatInputSeparatorLineColor() { + return chatInputSeparatorLineColor; + } + + public void setChatInputSeparatorLineColor(String chatInputSeparatorLineColor) { + this.chatInputSeparatorLineColor = chatInputSeparatorLineColor; + } + + public boolean isChatInputSeparatorLineVisible() { + return chatInputSeparatorLineVisible; + } + + public void setChatInputSeparatorLineVisible(boolean chatInputSeparatorLineVisible) { + this.chatInputSeparatorLineVisible = chatInputSeparatorLineVisible; + } + + public String getChatInputCursorColor() { + return chatInputCursorColor; + } + + public void setChatInputCursorColor(String chatInputCursorColor) { + this.chatInputCursorColor = chatInputCursorColor; + } +} \ No newline at end of file diff --git a/android/src/main/java/org/infobip/plugins/mobilemessaging/flutter/chat/ChatPlatformView.java b/android/src/main/java/org/infobip/plugins/mobilemessaging/flutter/chat/ChatPlatformView.java index 7ae9da0..e7a391c 100644 --- a/android/src/main/java/org/infobip/plugins/mobilemessaging/flutter/chat/ChatPlatformView.java +++ b/android/src/main/java/org/infobip/plugins/mobilemessaging/flutter/chat/ChatPlatformView.java @@ -238,6 +238,11 @@ private void isMultithread(final MethodChannel.Result result) { private InAppChatFragment.EventsListener createEventsListener() { return new InAppChatFragment.EventsListener() { + @Override + public void onChatRawMessageReceived(@NonNull String rawMessage) { + eventHandler.sendEvent(ChatViewEvent.EVENT_CHAT_RAW_MESSAGE_RECEIVED, rawMessage); + } + @Override public void onChatWidgetThemeChanged(@NonNull String widgetThemeName) { eventHandler.sendEvent(ChatViewEvent.EVENT_WIDGET_THEME_CHANGED, widgetThemeName); diff --git a/android/src/main/java/org/infobip/plugins/mobilemessaging/flutter/chat/ChatViewEvent.java b/android/src/main/java/org/infobip/plugins/mobilemessaging/flutter/chat/ChatViewEvent.java index 3273f59..e6835b8 100644 --- a/android/src/main/java/org/infobip/plugins/mobilemessaging/flutter/chat/ChatViewEvent.java +++ b/android/src/main/java/org/infobip/plugins/mobilemessaging/flutter/chat/ChatViewEvent.java @@ -14,5 +14,6 @@ public class ChatViewEvent { public static final String EVENT_CHAT_DISCONNECTED = "chatView.chatDisconnected"; public static final String EVENT_CHAT_RECONNECTED = "chatView.chatReconnected"; public static final String EVENT_EXIT_CHAT_PRESSED = "chatView.exitChatPressed"; + public static final String EVENT_CHAT_RAW_MESSAGE_RECEIVED = "chatView.chatRawMessageReceived"; } diff --git a/android/src/main/java/org/infobip/plugins/mobilemessaging/flutter/common/ErrorCodes.java b/android/src/main/java/org/infobip/plugins/mobilemessaging/flutter/common/ErrorCodes.java index 8ea939e..f074ea5 100644 --- a/android/src/main/java/org/infobip/plugins/mobilemessaging/flutter/common/ErrorCodes.java +++ b/android/src/main/java/org/infobip/plugins/mobilemessaging/flutter/common/ErrorCodes.java @@ -12,7 +12,8 @@ public enum ErrorCodes { CONTEXTUAL_METADATA_ERROR("CONTEXTUAL_METADATA_ERROR"), WEBRTCUI_ERROR("WEBRTCUI_ERROR"), INBOX_ERROR("INBOX_ERROR"), - CHAT_VIEW_ERROR("CHAT_VIEW_ERROR"); + CHAT_VIEW_ERROR("CHAT_VIEW_ERROR"), + SET_WIDGET_THEME_ERROR("SET_WIDGET_THEME_ERROR"); private final String code; diff --git a/android/src/main/java/org/infobip/plugins/mobilemessaging/flutter/infobip_mobilemessaging/InfobipMobilemessagingPlugin.java b/android/src/main/java/org/infobip/plugins/mobilemessaging/flutter/infobip_mobilemessaging/InfobipMobilemessagingPlugin.java index 9c69288..850d666 100644 --- a/android/src/main/java/org/infobip/plugins/mobilemessaging/flutter/infobip_mobilemessaging/InfobipMobilemessagingPlugin.java +++ b/android/src/main/java/org/infobip/plugins/mobilemessaging/flutter/infobip_mobilemessaging/InfobipMobilemessagingPlugin.java @@ -1,11 +1,5 @@ package org.infobip.plugins.mobilemessaging.flutter.infobip_mobilemessaging; -import static androidx.core.content.ContextCompat.RECEIVER_NOT_EXPORTED; - -import static org.infobip.plugins.mobilemessaging.flutter.common.LibraryEvent.broadcastEventMap; -import static org.infobip.plugins.mobilemessaging.flutter.common.LibraryEvent.messageBroadcastEventMap; -import static org.infobip.plugins.mobilemessaging.flutter.infobip_mobilemessaging.WebRTCUI.defaultWebrtcError; - import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; @@ -18,11 +12,6 @@ import android.src.main.java.org.infobip.plugins.mobilemessaging.flutter.common.StreamHandler; import android.util.Log; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; - import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; @@ -54,6 +43,8 @@ import org.infobip.mobile.messaging.storage.MessageStore; import org.infobip.mobile.messaging.util.DateTimeUtil; import org.infobip.mobile.messaging.util.PreferenceHelper; +import org.infobip.plugins.mobilemessaging.flutter.chat.ChatCustomization; +import org.infobip.plugins.mobilemessaging.flutter.chat.ChatViewFactory; import org.infobip.plugins.mobilemessaging.flutter.common.ConfigCache; import org.infobip.plugins.mobilemessaging.flutter.common.Configuration; import org.infobip.plugins.mobilemessaging.flutter.common.ErrorCodes; @@ -63,7 +54,6 @@ import org.infobip.plugins.mobilemessaging.flutter.common.PermissionsRequestManager; import org.infobip.plugins.mobilemessaging.flutter.common.PersonalizationCtx; import org.infobip.plugins.mobilemessaging.flutter.common.UserJson; -import org.infobip.plugins.mobilemessaging.flutter.chat.ChatViewFactory; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -77,6 +67,10 @@ import java.util.Map; import java.util.Objects; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; @@ -89,6 +83,10 @@ import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.PluginRegistry; +import static org.infobip.plugins.mobilemessaging.flutter.common.LibraryEvent.broadcastEventMap; +import static org.infobip.plugins.mobilemessaging.flutter.common.LibraryEvent.messageBroadcastEventMap; +import static org.infobip.plugins.mobilemessaging.flutter.infobip_mobilemessaging.WebRTCUI.defaultWebrtcError; + /** * InfobipMobilemessagingPlugin */ @@ -242,69 +240,18 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result case "setChatPushBody": setChatPushBody(call, result); break; + case "setChatCustomization": + setChatCustomization(call, result); + break; + case "setWidgetTheme": + setWidgetTheme(call, result); + break; default: result.notImplemented(); break; } } - private void disableCalls(MethodChannel.Result result) { - webRTCUI.disableCalls(() -> result.success(null), defaultWebrtcError(result)); - } - - private void enableCalls(MethodCall call, MethodChannel.Result result) { - Object args = call.arguments; - String identity = null; - if (args != null) { - identity = args.toString(); - } - webRTCUI.enableCalls(identity, () -> result.success(null), defaultWebrtcError(result)); - } - - private void enableChatCalls(MethodChannel.Result result) { - webRTCUI.enableChatCalls(() -> result.success(null), defaultWebrtcError(result)); - } - - private void init(MethodCall call, final MethodChannel.Result result) { - Log.d(TAG, "init"); - - final Configuration configuration = new Gson().fromJson(call.arguments.toString(), Configuration.class); - ConfigCache.getInstance().setConfiguration(configuration); - - final InitHelper initHelper = new InitHelper(configuration, activity); - MobileMessaging.Builder builder = initHelper.configurationBuilder(); - - PreferenceHelper.saveString(activity.getApplicationContext(), - MobileMessagingProperty.SYSTEM_DATA_VERSION_POSTFIX, - "flutter " + configuration.getPluginVersion()); - - builder.build(new MobileMessaging.InitListener() { - @SuppressLint("MissingPermission") - @Override - public void onSuccess() { - - if (configuration.getNotificationCategories() != null) { - NotificationCategory categories[] = initHelper.notificationCategoriesFromConfiguration(configuration.getNotificationCategories()); - if (categories.length > 0) { - MobileInteractive.getInstance(activity.getApplication()).setNotificationCategories(categories); - } - } - - // init method is called from WebView when activity is running - // so we can safely claim that we are in foreground - initHelper.setForeground(); - - result.success("OK"); - } - - @Override - public void onError(InternalSdkError e, @Nullable Integer googleErrorCode) { - Log.e(TAG, "Cannot start SDK: " + e.get() + " errorCode: " + googleErrorCode); - result.error(googleErrorCode.toString(), e.get(), e); - } - }); - } - //region ActivityAware @Override public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { @@ -357,7 +304,6 @@ private void notifyActivityObservers(Activity activity) { //endregion //region ServiceAware - @Override public void onAttachedToService(@NonNull ServicePluginBinding binding) { Log.d(TAG, "onAttachedToService"); @@ -367,7 +313,6 @@ public void onAttachedToService(@NonNull ServicePluginBinding binding) { public void onDetachedFromService() { Log.d(TAG, "onDetachedFromService"); } - //endregion //region Broadcast events @@ -417,24 +362,30 @@ public void onReceive(Context context, Intent intent) { if (Event.TOKEN_RECEIVED.getKey().equals(intent.getAction())) { data = intent.getStringExtra(BroadcastParameter.EXTRA_CLOUD_TOKEN); broadcastHandler.sendEvent(event, data); - } else if (Event.REGISTRATION_CREATED.getKey().equals(intent.getAction())) { + } + else if (Event.REGISTRATION_CREATED.getKey().equals(intent.getAction())) { data = intent.getStringExtra(BroadcastParameter.EXTRA_INFOBIP_ID); broadcastHandler.sendEvent(event, data); - } else if (InAppChatEvent.CHAT_VIEW_CHANGED.getKey().equals(intent.getAction())) { + } + else if (InAppChatEvent.CHAT_VIEW_CHANGED.getKey().equals(intent.getAction())) { data = intent.getStringExtra(BroadcastParameter.EXTRA_CHAT_VIEW); broadcastHandler.sendEvent(event, data); - } else if (InAppChatEvent.LIVECHAT_REGISTRATION_ID_UPDATED.getKey().equals(intent.getAction())) { + } + else if (InAppChatEvent.LIVECHAT_REGISTRATION_ID_UPDATED.getKey().equals(intent.getAction())) { data = intent.getStringExtra(BroadcastParameter.EXTRA_LIVECHAT_REGISTRATION_ID); broadcastHandler.sendEvent(event, data); - } else if (InAppChatEvent.UNREAD_MESSAGES_COUNTER_UPDATED.getKey().equals(intent.getAction())) { + } + else if (InAppChatEvent.UNREAD_MESSAGES_COUNTER_UPDATED.getKey().equals(intent.getAction())) { int unreadMessagesCount = intent.getIntExtra(BroadcastParameter.EXTRA_UNREAD_CHAT_MESSAGES_COUNT, 0); data = String.valueOf(unreadMessagesCount); broadcastHandler.sendEvent(event, data); - } else if (InAppChatEvent.IN_APP_CHAT_AVAILABILITY_UPDATED.getKey().equals(intent.getAction())) { + } + else if (InAppChatEvent.IN_APP_CHAT_AVAILABILITY_UPDATED.getKey().equals(intent.getAction())) { boolean isChatAvailable = intent.getBooleanExtra(BroadcastParameter.EXTRA_IS_CHAT_AVAILABLE, false); data = String.valueOf(isChatAvailable); broadcastHandler.sendEvent(event, data); - } else { + } + else { broadcastHandler.sendEvent(event); } } @@ -470,7 +421,8 @@ private void registerReceiver() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { ContextCompat.registerReceiver(activity.getApplicationContext(), commonLibraryBroadcastReceiver, intentFilter, ContextCompat.RECEIVER_NOT_EXPORTED); - } else { + } + else { LocalBroadcastManager.getInstance(activity).registerReceiver(commonLibraryBroadcastReceiver, intentFilter); } @@ -481,70 +433,59 @@ private void registerReceiver() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { ContextCompat.registerReceiver(activity.getApplicationContext(), messageBroadcastReceiver, messageIntentFilter, ContextCompat.RECEIVER_NOT_EXPORTED); - } else { + } + else { LocalBroadcastManager.getInstance(activity).registerReceiver(messageBroadcastReceiver, messageIntentFilter); } } //endregion - /** - * Creates json from a message object - * - * @param message message object - * @return message json - */ - private static JSONObject messageToJSON(Message message) { - try { - return new JSONObject() - .putOpt("messageId", message.getMessageId()) - .putOpt("title", message.getTitle()) - .putOpt("body", message.getBody()) - .putOpt("sound", message.getSound()) - .putOpt("vibrate", message.isVibrate()) - .putOpt("icon", message.getIcon()) - .putOpt("silent", message.isSilent()) - .putOpt("category", message.getCategory()) - .putOpt("from", message.getFrom()) - .putOpt("receivedTimestamp", message.getReceivedTimestamp()) - .putOpt("customPayload", message.getCustomPayload()) - .putOpt("contentUrl", message.getContentUrl()) - .putOpt("seen", message.getSeenTimestamp() != 0) - .putOpt("seenDate", message.getSeenTimestamp()) - .putOpt("chat", message.isChatMessage()) - .putOpt("browserUrl", message.getBrowserUrl()) - .putOpt("deeplink", message.getDeeplink()) - .putOpt("inAppOpenTitle", message.getInAppOpenTitle()) - .putOpt("inAppDismissTitle", message.getInAppDismissTitle()); - } catch (JSONException e) { - Log.w(TAG, "Cannot convert message to JSON: " + e.getMessage()); - return null; - } + //region Mobile Messaging + private MobileMessaging mobileMessaging() { + return MobileMessaging.getInstance(this.activity.getApplicationContext()); } - /** - * Creates array of json objects from list of messages - * - * @param messages list of messages - * @return array of jsons representing messages - */ - private static JSONArray messagesToJSONArray(@NonNull Message messages[]) { - JSONArray array = new JSONArray(); - for (Message message : messages) { - JSONObject json = messageToJSON(message); - if (json == null) { - continue; + private void init(MethodCall call, final MethodChannel.Result result) { + Log.d(TAG, "init"); + + final Configuration configuration = new Gson().fromJson(call.arguments.toString(), Configuration.class); + ConfigCache.getInstance().setConfiguration(configuration); + + final InitHelper initHelper = new InitHelper(configuration, activity); + MobileMessaging.Builder builder = initHelper.configurationBuilder(); + + PreferenceHelper.saveString(activity.getApplicationContext(), + MobileMessagingProperty.SYSTEM_DATA_VERSION_POSTFIX, + "flutter " + configuration.getPluginVersion()); + + builder.build(new MobileMessaging.InitListener() { + @SuppressLint("MissingPermission") + @Override + public void onSuccess() { + + if (configuration.getNotificationCategories() != null) { + NotificationCategory categories[] = initHelper.notificationCategoriesFromConfiguration(configuration.getNotificationCategories()); + if (categories.length > 0) { + MobileInteractive.getInstance(activity.getApplication()).setNotificationCategories(categories); + } + } + + // init method is called from WebView when activity is running + // so we can safely claim that we are in foreground + initHelper.setForeground(); + + result.success("OK"); } - array.put(json); - } - return array; - } - private MobileMessaging mobileMessaging() { - return MobileMessaging.getInstance(this.activity.getApplicationContext()); + @Override + public void onError(InternalSdkError e, @Nullable Integer googleErrorCode) { + Log.e(TAG, "Cannot start SDK: " + e.get() + " errorCode: " + googleErrorCode); + result.error(googleErrorCode.toString(), e.get(), e); + } + }); } - /*User Profile Management*/ - + //region User Profile Management public void saveUser(MethodCall call, final MethodChannel.Result result) { try { JSONObject jsonObject = new JSONObject(call.arguments.toString()); @@ -566,7 +507,8 @@ private MobileMessaging.ResultListener userResultListener(final MethodChan public void onResult(org.infobip.mobile.messaging.mobileapi.Result result) { if (result.isSuccess()) { resultCallbacks.success(UserJson.toJSON(result.getData()).toString()); - } else { + } + else { resultCallbacks.error(result.getError().getCode(), result.getError().getMessage(), result.getError().toString()); } } @@ -600,7 +542,8 @@ private MobileMessaging.ResultListener installationResultListener( public void onResult(org.infobip.mobile.messaging.mobileapi.Result result) { if (result.isSuccess()) { resultCallbacks.success(InstallationJson.toJSON(result.getData()).toString()); - } else { + } + else { resultCallbacks.error(result.getError().getCode(), result.getError().getMessage(), result.getError().toString()); } } @@ -620,7 +563,8 @@ public void personalize(MethodCall call, final MethodChannel.Result resultCallba public void onResult(org.infobip.mobile.messaging.mobileapi.Result result) { if (result.isSuccess()) { resultCallbacks.success(UserJson.toJSON(result.getData()).toString()); - } else { + } + else { resultCallbacks.error(result.getError().getCode(), result.getError().getMessage(), result.getError().toString()); } } @@ -643,7 +587,8 @@ public void depersonalize(final MethodChannel.Result resultCallbacks) { public void onResult(Result result) { if (result.isSuccess()) { resultCallbacks.success(depersonalizeStates.get(result.getData())); - } else { + } + else { resultCallbacks.error(result.getError().getCode(), result.getError().getMessage(), result.getError().toString()); } } @@ -677,10 +622,6 @@ public void setInstallationAsPrimary(MethodCall call, final MethodChannel.Result mobileMessaging().setInstallationAsPrimary(pushRegistrationId, primary, installationsResultListener(result)); } - public void showChat(MethodCall call, final MethodChannel.Result result) { - InAppChat.getInstance(activity.getApplication()).inAppChatScreen().show(); - } - public void submitEvent(MethodCall call, final MethodChannel.Result result) { try { JSONObject jsonObject = new JSONObject(call.arguments.toString()); @@ -704,38 +645,6 @@ public void submitEventImmediately(MethodCall call, final MethodChannel.Result r } } - private void getMessageCounter(final MethodChannel.Result result) { - result.success(InAppChat.getInstance(activity.getApplication()).getMessageCounter()); - } - - private void resetMessageCounter() { - InAppChat.getInstance(activity.getApplication()).resetMessageCounter(); - } - - private void setLanguage(MethodCall call, final MethodChannel.Result result) { - String language = call.arguments.toString(); - if (language == null || language.isEmpty()) { - result.error(ErrorCodes.SET_LANGUAGE_ERROR.getErrorCode(), "Cannot set in app chat language.", null); - return; - } - InAppChat.getInstance(activity.getApplication()).setLanguage(language); - } - - private void sendContextualData(MethodCall call, final MethodChannel.Result result) { - String data = call.argument("data"); - Boolean allMultiThreadStrategy = call.argument("allMultiThreadStrategy"); - if (data == null || data.isEmpty() || allMultiThreadStrategy == null) { - result.error(ErrorCodes.CONTEXTUAL_METADATA_ERROR.getErrorCode(), "Cannot resolve data or allMultiThreadStrategy from arguments", null); - return; - } - InAppChat.getInstance(activity.getApplication()).sendContextualData(data, allMultiThreadStrategy); - } - - private void setJwt(MethodCall call) { - String jwt = call.arguments.toString(); - InAppChat.getInstance(activity.getApplication()).setJwtProvider(() -> jwt); - } - private synchronized void defaultMessageStorage_find(MethodCall call, final MethodChannel.Result result) { String messageId = call.arguments.toString(); MessageStore messageStore = MobileMessaging.getInstance(activity.getApplicationContext()).getMessageStore(); @@ -800,7 +709,8 @@ private MobileMessaging.ResultListener customEventResultListener(fi public void onResult(org.infobip.mobile.messaging.mobileapi.Result result) { if (result.isSuccess()) { resultCallbacks.success("Success"); - } else { + } + else { resultCallbacks.error(result.getError().getCode(), result.getError().getMessage(), result.getError().toString()); } } @@ -814,14 +724,13 @@ private MobileMessaging.ResultListener> installationsResultLi public void onResult(org.infobip.mobile.messaging.mobileapi.Result, MobileMessagingError> result) { if (result.isSuccess()) { resultCallbacks.success("Success"); - } else { + } + else { resultCallbacks.error(result.getError().getCode(), result.getError().getMessage(), result.getError().toString()); } } }; } - /*User Profile Management End */ - static void cleanupJsonMapForClient(Map customAttributes, JSONObject jsonObject) throws JSONException { jsonObject.remove("map"); @@ -832,6 +741,58 @@ static void cleanupJsonMapForClient(Map customAttr } } + /** + * Creates json from a message object + * + * @param message message object + * @return message json + */ + private static JSONObject messageToJSON(Message message) { + try { + return new JSONObject() + .putOpt("messageId", message.getMessageId()) + .putOpt("title", message.getTitle()) + .putOpt("body", message.getBody()) + .putOpt("sound", message.getSound()) + .putOpt("vibrate", message.isVibrate()) + .putOpt("icon", message.getIcon()) + .putOpt("silent", message.isSilent()) + .putOpt("category", message.getCategory()) + .putOpt("from", message.getFrom()) + .putOpt("receivedTimestamp", message.getReceivedTimestamp()) + .putOpt("customPayload", message.getCustomPayload()) + .putOpt("contentUrl", message.getContentUrl()) + .putOpt("seen", message.getSeenTimestamp() != 0) + .putOpt("seenDate", message.getSeenTimestamp()) + .putOpt("chat", message.isChatMessage()) + .putOpt("browserUrl", message.getBrowserUrl()) + .putOpt("deeplink", message.getDeeplink()) + .putOpt("inAppOpenTitle", message.getInAppOpenTitle()) + .putOpt("inAppDismissTitle", message.getInAppDismissTitle()); + } catch (JSONException e) { + Log.w(TAG, "Cannot convert message to JSON: " + e.getMessage()); + return null; + } + } + + /** + * Creates array of json objects from list of messages + * + * @param messages list of messages + * @return array of jsons representing messages + */ + private static JSONArray messagesToJSONArray(@NonNull Message messages[]) { + JSONArray array = new JSONArray(); + for (Message message : messages) { + JSONObject json = messageToJSON(message); + if (json == null) { + continue; + } + array.put(json); + } + return array; + } + /** * Creates new json object based on message bundle * @@ -874,17 +835,19 @@ static CustomEvent fromJSON(JSONObject json) { return customEvent; } } + //endregion + //region PermissionsRequester for Post Notifications Permission public void registerForAndroidRemoteNotifications() { Log.w(TAG, "calling register"); if (activity != null) { permissionsRequestManager.isRequiredPermissionsGranted(activity, this); - } else { + } + else { Log.e(TAG, "Cannot register for remote notifications, activity does not exist"); } } - // PermissionsRequester for Post Notifications Permission @Override public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (requestCode == PermissionsRequestManager.REQ_CODE_POST_NOTIFICATIONS_PERMISSIONS) { @@ -921,7 +884,9 @@ public int permissionsNotGrantedDialogTitle() { public int permissionsNotGrantedDialogMessage() { return org.infobip.mobile.messaging.resources.R.string.mm_post_notifications_settings_message; } + //endregion + //region Inbox private void fetchInboxMessages(MethodCall call, MethodChannel.Result result) { try { String token = call.argument("token"); @@ -991,7 +956,8 @@ private MobileMessaging.ResultListener inboxResultListener(final MethodCh public void onResult(org.infobip.mobile.messaging.mobileapi.Result result) { if (result.isSuccess()) { resultCallbacks.success(InboxJson.toJSON(result.getData()).toString()); - } else { + } + else { resultCallbacks.error(result.getError().getCode(), result.getError().getMessage(), result.getError().toString()); } } @@ -1010,26 +976,6 @@ private void setInboxMessagesSeen(MethodCall call, MethodChannel.Result result) } } - private void setChatPushTitle(MethodCall call, MethodChannel.Result result) { - try { - @Nullable String title = call.arguments(); - InAppChat.getInstance(activity.getApplication()).setChatPushTitle(title); - } catch (Exception e) { - Log.d(TAG, "Failed setting chat push title"); - result.error(e.getMessage(), e.getMessage(), e.getLocalizedMessage()); - } - } - - private void setChatPushBody(MethodCall call, MethodChannel.Result result) { - try { - @Nullable String body = call.arguments(); - InAppChat.getInstance(activity.getApplication()).setChatPushBody(body); - } catch (Exception e) { - Log.d(TAG, "Failed setting chat push body"); - result.error(e.getMessage(), e.getMessage(), e.getLocalizedMessage()); - } - } - @NonNull private MobileMessaging.ResultListener setSeenResultListener(final MethodChannel.Result resultCallbacks) { return new MobileMessaging.ResultListener() { @@ -1037,7 +983,8 @@ private MobileMessaging.ResultListener setSeenResultListener(final Met public void onResult(org.infobip.mobile.messaging.mobileapi.Result result) { if (result.isSuccess()) { resultCallbacks.success(Arrays.toString(result.getData())); - } else { + } + else { resultCallbacks.error(result.getError().getCode(), result.getError().getMessage(), result.getError().toString()); } } @@ -1070,4 +1017,103 @@ private static String[] resolveStringArray(JSONArray args) throws JSONException return array; } + //endregion + //endregion + + //region InAppChat + public void showChat(MethodCall call, final MethodChannel.Result result) { + InAppChat.getInstance(activity.getApplication()).inAppChatScreen().show(); + } + + private void getMessageCounter(final MethodChannel.Result result) { + result.success(InAppChat.getInstance(activity.getApplication()).getMessageCounter()); + } + + private void resetMessageCounter() { + InAppChat.getInstance(activity.getApplication()).resetMessageCounter(); + } + + private void setLanguage(MethodCall call, final MethodChannel.Result result) { + String language = call.arguments.toString(); + if (language == null || language.isEmpty()) { + result.error(ErrorCodes.SET_LANGUAGE_ERROR.getErrorCode(), "Cannot set in app chat language.", null); + return; + } + InAppChat.getInstance(activity.getApplication()).setLanguage(language); + } + + private void sendContextualData(MethodCall call, final MethodChannel.Result result) { + String data = call.argument("data"); + Boolean allMultiThreadStrategy = call.argument("allMultiThreadStrategy"); + if (data == null || data.isEmpty() || allMultiThreadStrategy == null) { + result.error(ErrorCodes.CONTEXTUAL_METADATA_ERROR.getErrorCode(), "Cannot resolve data or allMultiThreadStrategy from arguments", null); + return; + } + InAppChat.getInstance(activity.getApplication()).sendContextualData(data, allMultiThreadStrategy); + } + + private void setJwt(MethodCall call) { + String jwt = call.arguments.toString(); + InAppChat.getInstance(activity.getApplication()).setJwtProvider(() -> jwt); + } + + private void setChatPushTitle(MethodCall call, MethodChannel.Result result) { + try { + @Nullable String title = call.arguments(); + InAppChat.getInstance(activity.getApplication()).setChatPushTitle(title); + } catch (Exception e) { + Log.d(TAG, "Failed setting chat push title"); + result.error(e.getMessage(), e.getMessage(), e.getLocalizedMessage()); + } + } + + private void setChatPushBody(MethodCall call, MethodChannel.Result result) { + try { + @Nullable String body = call.arguments(); + InAppChat.getInstance(activity.getApplication()).setChatPushBody(body); + } catch (Exception e) { + Log.d(TAG, "Failed setting chat push body"); + result.error(e.getMessage(), e.getMessage(), e.getLocalizedMessage()); + } + } + + private void setChatCustomization(MethodCall call, MethodChannel.Result result) { + try { + ChatCustomization customization = new Gson().fromJson(call.arguments.toString(), ChatCustomization.class); + InAppChat.getInstance(activity.getApplication()).setTheme(customization.createTheme(activity)); + } catch (Exception e) { + Log.d(TAG, "Failed to set customization", e); + result.error(e.getMessage(), e.getMessage(), e.getLocalizedMessage()); + } + } + + private void setWidgetTheme(MethodCall call, final MethodChannel.Result result) { + String widgetTheme = call.arguments.toString(); + if (widgetTheme == null || widgetTheme.isEmpty()) { + result.error(ErrorCodes.SET_WIDGET_THEME_ERROR.getErrorCode(), "Cannot set in app chat widget theme. Widget theme is null or empty.", null); + return; + } + InAppChat.getInstance(activity.getApplication()).setWidgetTheme(widgetTheme); + } + //endregion + + //region WebRtcUi + private void disableCalls(MethodChannel.Result result) { + webRTCUI.disableCalls(() -> result.success(null), defaultWebrtcError(result)); + } + + private void enableCalls(MethodCall call, MethodChannel.Result result) { + Object args = call.arguments; + String identity = null; + if (args != null) { + identity = args.toString(); + } + webRTCUI.enableCalls(identity, () -> result.success(null), defaultWebrtcError(result)); + } + + private void enableChatCalls(MethodChannel.Result result) { + webRTCUI.enableChatCalls(() -> result.success(null), defaultWebrtcError(result)); + } + //endregion + } diff --git a/example/ios/Podfile b/example/ios/Podfile index d8faa09..2ea3eba 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -57,6 +57,8 @@ post_install do |installer| xcconfig = File.read(xcconfig_path) xcconfig_mod = xcconfig.gsub(/DT_TOOLCHAIN_DIR/, "TOOLCHAIN_DIR") File.open(xcconfig_path, "w") { |file| file << xcconfig_mod } + config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO' + end end diff --git a/example/lib/chat_customization.dart b/example/lib/chat_customization.dart index 63fdd59..7d712ac 100644 --- a/example/lib/chat_customization.dart +++ b/example/lib/chat_customization.dart @@ -1,55 +1,58 @@ library chat_customization; import 'package:infobip_mobilemessaging/models/configuration.dart' as mmconfiguration; -Object customTheme = mmconfiguration.InAppChatCustomization( - //toolbar - toolbarTitle: 'Chat', - toolbarTitleColor: '#FFFFFF', - toolbarTintColor: '#FF33F3', - toolbarBackgroundColor: '#673AB7', - //chat - widgetTheme: 'dark', - chatBackgroundColor: '#D1C4E9', - noConnectionAlertTextColor: '#FFFFFF', - noConnectionAlertBackgroundColor: '#212121', - //input - chatInputSeparatorVisible: true, - attachmentButtonIcon: 'assets/ic_add_circle.png', - sendButtonIcon: 'assets/ic_send.png', - sendButtonTintColor: '#9E9E9E', - chatInputPlaceholderColor: '#757575', - chatInputCursorColor: '#9E9E9E', - chatInputBackgroundColor: '#D1C4E9', - android: mmconfiguration.AndroidInAppChatCustomization( - //status bar - chatStatusBarColorLight: true, - chatStatusBarBackgroundColor: '#673AB7', - //toolbar - chatNavigationIcon: 'assets/ic_back.png', - chatNavigationIconTint: '#FFFFFF', - chatSubtitleText: '#1', - chatSubtitleTextColor: '#FFFFFF', - chatSubtitleTextAppearanceRes: 'TextAppearance_AppCompat_Subtitle', - chatSubtitleCentered: true, - chatTitleTextAppearanceRes: 'TextAppearance_AppCompat_Title', - chatTitleCentered: true, - chatMenuItemsIconTint: '#FFFFFF', - chatMenuItemSaveAttachmentIcon: 'assets/ic_download.png', - //chat - chatProgressBarColor: '#9E9E9E', - chatNetworkConnectionErrorText: 'Offline', - chatNetworkConnectionErrorTextAppearanceRes: 'TextAppearance_AppCompat_Small', - //input - chatInputSeparatorLineColor: '#BDBDBD', - chatInputHintText: 'Message', - chatInputTextColor: '#212121', - chatInputTextAppearance: 'TextAppearance_AppCompat', - chatInputAttachmentIconTint: '#9E9E9E', - chatInputAttachmentBackgroundColor: '#673AB7', - chatInputAttachmentBackgroundDrawable: 'assets/ic_circle.png', - chatInputSendIconTint: '#9E9E9E', - chatInputSendBackgroundColor: '#673AB7', - chatInputSendBackgroundDrawable: 'assets/ic_circle.png', - ), - ios: mmconfiguration.IOSInAppChatCustomization(initialHeight: 50) +Object customBranding = mmconfiguration.ChatCustomization( + chatStatusBarBackgroundColor: "#673AB7", + chatStatusBarIconsColorMode: "dark", + chatToolbar: mmconfiguration.ToolbarCustomization( + titleTextAppearance: 'TextAppearance_AppCompat_Title', + titleTextColor: '#FFFFFF', + titleText: 'Some new title', + titleCentered: true, + backgroundColor: '#673AB7', + navigationIcon: 'assets/ic_back.png', + navigationIconTint: '#FFFFFF', + subtitleTextAppearance: 'TextAppearance_AppCompat_Subtitle', + subtitleTextColor: '#FFFFFF', + subtitleText: '#1', + subtitleCentered: true, + ), + attachmentPreviewToolbar: mmconfiguration.ToolbarCustomization( + titleTextAppearance: 'TextAppearance_AppCompat_Title', + titleTextColor: '#212121', + titleText: 'Attachment preview', + titleCentered: true, + backgroundColor: '#673AB7', + navigationIcon: 'assets/ic_back.png', + navigationIconTint: '#FFFFFF', + subtitleTextAppearance: 'TextAppearance_AppCompat_Subtitle', + subtitleTextColor: '#FFFFFF', + subtitleText: 'Attachment preview subtitle', + subtitleCentered: false, + ), + attachmentPreviewToolbarSaveMenuItemIcon: 'assets/ic_download.png', + attachmentPreviewToolbarMenuItemsIconTint: '#9E9E9E', + + networkErrorText: 'Network error', + networkErrorTextColor: '#FFFFFF', + networkErrorLabelBackgroundColor: '#212121', + + chatBackgroundColor: '#FFFFFF', + chatProgressBarColor: '#9E9E9E', + chatInputTextAppearance: 'TextAppearance_AppCompat', + chatInputTextColor: '#212121', + chatInputBackgroundColor: '#D1C4E9', + chatInputHintText: 'Input Message', + chatInputHintTextColor: '#212121', + chatInputAttachmentIcon: 'assets/ic_add_circle.png', + chatInputAttachmentIconTint: '#9E9E9E', + chatInputAttachmentBackgroundDrawable: 'assets/ic_circle.png', + chatInputAttachmentBackgroundColor: '#673AB7', + chatInputSendIcon: 'assets/ic_send.png', + chatInputSendIconTint: '#9E9E9E', + chatInputSendBackgroundDrawable: 'assets/ic_circle.png', + chatInputSendBackgroundColor: '#673AB7', + chatInputSeparatorLineColor: '#BDBDBD', + chatInputSeparatorLineVisible: true, + chatInputCursorColor: '#9E9E9E', ); \ No newline at end of file diff --git a/example/lib/main.dart b/example/lib/main.dart index 31b49a3..fed3bfd 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:infobip_mobilemessaging/infobip_mobilemessaging.dart'; import 'package:infobip_mobilemessaging/models/configuration.dart' as mmconfiguration; -import 'package:infobip_mobilemessaging/models/ios_chat_settings.dart'; import 'screens/homepage.dart'; import 'widgets/page.dart'; @@ -46,8 +45,6 @@ class _MyAppState extends State { withoutRegisteringForRemoteNotifications: false), webRTCUI: mmconfiguration.WebRTCUI( configurationId: 'Your WEBRTC push configuration id'), - // Comment out to apply In-app chat customization - //inAppChatCustomization: chatCustomization.customTheme, )); // Comment out to automatically enable WebRTC // try { @@ -56,13 +53,6 @@ class _MyAppState extends State { // } catch (err) { // print('Calls enable error: $err'); // } - InfobipMobilemessaging.setupiOSChatSettings(IOSChatSettings( - title: 'Flutter Example Chat', - sendButtonColor: '#ff5722', - navigationBarItemsColor: '#8DFF33', - navigationBarColor: '#c41c00', - navigationBarTitleColor: '#000000', - )); } @override diff --git a/example/lib/screens/homepage.dart b/example/lib/screens/homepage.dart index c675c01..3e9a8f0 100644 --- a/example/lib/screens/homepage.dart +++ b/example/lib/screens/homepage.dart @@ -9,6 +9,8 @@ import 'package:infobip_mobilemessaging/models/library_event.dart'; import 'package:infobip_mobilemessaging/models/message.dart'; import 'package:infobip_mobilemessaging/models/user_data.dart'; +import '../chat_customization.dart' as chatCustomization; + import '../main.dart'; import '../utils/language.dart'; import '../widgets/demo_tile.dart'; @@ -358,6 +360,14 @@ class _HomePageState extends State { InfobipMobilemessaging.showChat(); }, ), + ListTile( + title: const Text('Show chat screen customized'), + onTap: () { + InfobipMobilemessaging.setChatCustomization(chatCustomization.customBranding); + InfobipMobilemessaging.setWidgetTheme('dark'); + InfobipMobilemessaging.showChat(); + }, + ), ListTile( title: const Text('Send Contextual Data and Show chat screen'), onTap: () { diff --git a/example/pubspec.lock b/example/pubspec.lock index 8ab93d3..1831e2a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -81,7 +81,7 @@ packages: path: ".." relative: true source: path - version: "6.4.0" + version: "6.4.1" intl: dependency: "direct main" description: diff --git a/ios/Classes/Configuration.swift b/ios/Classes/Configuration.swift index 99a0596..d0e1465 100644 --- a/ios/Classes/Configuration.swift +++ b/ios/Classes/Configuration.swift @@ -31,7 +31,7 @@ class Configuration { static let withoutRegisteringForRemoteNotifications = "withoutRegisteringForRemoteNotifications" static let webRTCUI = "webRTCUI" static let configurationId = "configurationId" - static let customisation = "inAppChatCustomization" + static let customization = "inAppChatCustomization" } let appCode: String @@ -49,8 +49,8 @@ class Configuration { let categories: [MMNotificationCategory]? let webViewSettings: [String: AnyObject]? let withoutRegisteringForRemoteNotifications: Bool - let customisation: Customisation? - + let customization: Customization? + init?(rawConfig: [String: AnyObject]) { guard let appCode = rawConfig[Configuration.Keys.applicationCode] as? String, let ios = rawConfig["iosSettings"] as? [String: AnyObject] else @@ -58,14 +58,14 @@ class Configuration { return nil } - if let rawConfig = rawConfig[Configuration.Keys.customisation] as? [String: Any], + if let rawConfig = rawConfig[Configuration.Keys.customization] as? [String: Any], let jsonObject = try? JSONSerialization.data( withJSONObject: rawConfig ), - let customisation = try? JSONDecoder().decode(Customisation.self, from: jsonObject) { - self.customisation = customisation + let customization = try? JSONDecoder().decode(Customization.self, from: jsonObject) { + self.customization = customization } else { - self.customisation = nil + self.customization = nil } self.webRTCUI = rawConfig[Configuration.Keys.webRTCUI] as? [String: Any] diff --git a/ios/Classes/CustomisationUtils.swift b/ios/Classes/CustomisationUtils.swift deleted file mode 100644 index f6c9aae..0000000 --- a/ios/Classes/CustomisationUtils.swift +++ /dev/null @@ -1,127 +0,0 @@ -// -// CustomisationUtils.swift -// infobip_mobilemessaging_flutter_plugin -// -// Created by Maksym Svitlovskyi on 16.08.2023. -// - -import Foundation -import MobileMessaging -import Flutter -import UIKit - -struct Customisation: Decodable { - var toolbarTitle: String? - var sendButtonTintColor: String? - var toolbarTintColor: String? - var toolbarBackgroundColor: String? - var toolbarTitleColor: String? - var chatBackgroundColor: String? - var noConnectionAlertTextColor: String? - var noConnectionAlertBackgroundColor: String? - var chatInputPlaceholderTextColor: String? - var chatInputCursorColor: String? - var widgetTheme: String? - var sendButtonIcon: String? - var attachmentButtonIcon: String? - var chatInputSeparatorVisible: Bool? - - var ios: IOSSpecificCustomisation? -} - -struct IOSSpecificCustomisation: Decodable { - var attachmentPreviewBarsColor: String? - var attachmentPreviewItemsColor: String? - var textContainerTopMargin: CGFloat? - var textContainerLeftPadding: CGFloat? - var textContainerCornerRadius: CGFloat? - var textViewTopMargin: CGFloat? - var placeholderHeight: CGFloat? - var placeholderSideMargin: CGFloat? - var buttonHeight: CGFloat? - var buttonTouchableOverlap: CGFloat? - var buttonRightMargin: CGFloat? - var utilityButtonWidth: CGFloat? - var utilityButtonBottomMargin: CGFloat? - var initialHeight: CGFloat? - var mainFont: String? - var charCountFont: String? -} - -class CustomisationUtils { - - func setup(customisation: Customisation, with registrar: FlutterPluginRegistrar, in settings: MMChatSettings) { - setNotNil(&settings.title, customisation.toolbarTitle) - setNotNil(&settings.sendButtonTintColor, customisation.sendButtonTintColor?.toColor()) - setNotNil(&settings.navBarItemsTintColor, customisation.toolbarTintColor?.toColor()) - setNotNil(&settings.navBarColor, customisation.toolbarBackgroundColor?.toColor()) - setNotNil(&settings.navBarTitleColor, customisation.toolbarTitleColor?.toColor()) - setNotNil(&settings.backgroundColor, customisation.chatBackgroundColor?.toColor()) - setNotNil(&settings.widgetTheme, customisation.widgetTheme) - setNotNil(&settings.errorLabelTextColor, customisation.noConnectionAlertTextColor?.toColor()) - setNotNil(&settings.errorLabelBackgroundColor, customisation.noConnectionAlertBackgroundColor?.toColor()) - setNotNil(&settings.advancedSettings.mainPlaceholderTextColor, customisation.chatInputPlaceholderTextColor?.toColor()) - setNotNil(&settings.advancedSettings.typingIndicatorColor, customisation.chatInputCursorColor?.toColor()) - setNotNil(&settings.attachmentPreviewBarsColor, customisation.ios?.attachmentPreviewBarsColor?.toColor()) - setNotNil(&settings.attachmentPreviewItemsColor, customisation.ios?.attachmentPreviewItemsColor?.toColor()) - - setNotNil(&settings.advancedSettings.sendButtonIcon, getImage(with: customisation.sendButtonIcon, with: registrar)) - setNotNil(&settings.advancedSettings.attachmentButtonIcon, getImage(with: customisation.attachmentButtonIcon, with: registrar)) - - if let chatInputSeparatorVisible = customisation.chatInputSeparatorVisible { - settings.advancedSettings.isLineSeparatorHidden = !chatInputSeparatorVisible - } - - setNotNil(&settings.advancedSettings.textContainerTopMargin, customisation.ios?.textContainerTopMargin) - setNotNil(&settings.advancedSettings.textContainerLeftPadding, customisation.ios?.textContainerLeftPadding) - setNotNil(&settings.advancedSettings.textContainerCornerRadius, customisation.ios?.textContainerCornerRadius) - setNotNil(&settings.advancedSettings.textViewTopMargin, customisation.ios?.textViewTopMargin) - setNotNil(&settings.advancedSettings.placeholderHeight, customisation.ios?.placeholderHeight) - setNotNil(&settings.advancedSettings.placeholderSideMargin, customisation.ios?.placeholderSideMargin) - setNotNil(&settings.advancedSettings.buttonHeight, customisation.ios?.buttonHeight) - setNotNil(&settings.advancedSettings.buttonTouchableOverlap, customisation.ios?.buttonTouchableOverlap) - setNotNil(&settings.advancedSettings.buttonRightMargin, customisation.ios?.buttonRightMargin) - setNotNil(&settings.advancedSettings.utilityButtonWidth, customisation.ios?.utilityButtonWidth) - setNotNil(&settings.advancedSettings.utilityButtonBottomMargin, customisation.ios?.utilityButtonBottomMargin) - setNotNil(&settings.advancedSettings.initialHeight, customisation.ios?.initialHeight) - - if let mainFontSize = settings.advancedSettings.mainFont?.pointSize, - let newFontPath = customisation.ios?.mainFont, - let font = getFont(with: newFontPath, with: registrar, size: mainFontSize) { - settings.advancedSettings.mainFont = font - } - - if let mainFontSize = settings.advancedSettings.charCountFont?.pointSize, - let newFontPath = customisation.ios?.charCountFont, - let font = getFont(with: newFontPath, with: registrar, size: mainFontSize) { - settings.advancedSettings.charCountFont = font - } - } - - func getImage(with name: String?, with registrar: FlutterPluginRegistrar) -> UIImage? { - guard let name = name else { return nil } - let bundleName = registrar.lookupKey(forAsset: name) - guard let bundlePath = Bundle.main.path(forResource: bundleName, ofType: nil) else { return nil } - return UIImage(named: bundlePath) - } - - func getFont(with path: String?, with registrar: FlutterPluginRegistrar, size: CGFloat) -> UIFont? { - guard let path = path else { return nil } - let fontKey = registrar.lookupKey(forAsset: path) - guard let bundlePath = Bundle.main.path(forResource: fontKey, ofType: nil) else { return nil } - - guard let data = NSData(contentsOfFile: bundlePath) else { return nil } - guard let dataProvider = CGDataProvider(data: data) else { return nil } - guard let fontReference = CGFont(dataProvider) else { return nil } - - guard let fontName = URL(string: path)?.deletingPathExtension().lastPathComponent else { return nil } - - var errorReference: Unmanaged? - CTFontManagerRegisterGraphicsFont(fontReference, &errorReference) - return UIFont(name: fontName, size: size) - } - - func setNotNil(_ forVariable: inout T, _ value:T?) { - if let value = value { forVariable = value } - } -} diff --git a/ios/Classes/CustomizationUtils.swift b/ios/Classes/CustomizationUtils.swift new file mode 100644 index 0000000..8743d55 --- /dev/null +++ b/ios/Classes/CustomizationUtils.swift @@ -0,0 +1,188 @@ +// +// CustomizationUtils.swift +// infobip_mobilemessaging_flutter_plugin +// +// Created by Maksym Svitlovskyi on 16.08.2023. +// + +import Foundation +import MobileMessaging +import Flutter +import UIKit + +struct ToolbarCustomization: Decodable { + var titleTextAppearance: String? + var titleTextColor: String? + var titleText: String? + var backgroundColor: String? + var navigationIcon: String? + var navigationIconTint: String? +} + +struct ChatCustomization: Decodable { + var chatStatusBarBackgroundColor: String? + var chatStatusBarIconsColorMode: String? + var chatToolbar: ToolbarCustomization? + var attachmentPreviewToolbar: ToolbarCustomization? + var attachmentPreviewToolbarMenuItemsIconTint: String? + var attachmentPreviewToolbarSaveMenuItemIcon: String? + var chatBackgroundColor: String? + var chatProgressBarColor: String? + var chatInputTextAppearance: String? + var chatInputTextColor: String? + var chatInputBackgroundColor: String? + var chatInputHintText: String? + var chatInputHintTextColor: String? + var chatInputAttachmentIcon: String? + var chatInputAttachmentIconTint: String? + var chatInputAttachmentBackgroundDrawable: String? + var chatInputAttachmentBackgroundColor: String? + var chatInputSendIcon: String? + var chatInputSendIconTint: String? + var chatInputSendBackgroundDrawable: String? + var chatInputSendBackgroundColor: String? + var chatInputSeparatorLineColor: String? + var chatInputSeparatorLineVisible: Bool? + var chatInputCursorColor: String? + var networkErrorTextColor: String? + var networkErrorLabelBackgroundColor: String? +} + +// Deprecated, to be replaced entirely by ChatCustomization +struct Customization: Decodable { + var toolbarTitle: String? + var sendButtonTintColor: String? + var toolbarTintColor: String? + var toolbarBackgroundColor: String? + var toolbarTitleColor: String? + var chatBackgroundColor: String? + var noConnectionAlertTextColor: String? + var noConnectionAlertBackgroundColor: String? + var chatInputPlaceholderTextColor: String? + var chatInputCursorColor: String? + var widgetTheme: String? + var sendButtonIcon: String? + var attachmentButtonIcon: String? + var chatInputSeparatorVisible: Bool? + + var ios: IOSSpecificCustomization? +} + +// Deprecated, to be replaced entirely by ChatCustomization +struct IOSSpecificCustomization: Decodable { + var attachmentPreviewBarsColor: String? + var attachmentPreviewItemsColor: String? + var textContainerTopMargin: CGFloat? + var textContainerLeftPadding: CGFloat? + var textContainerCornerRadius: CGFloat? + var textViewTopMargin: CGFloat? + var placeholderHeight: CGFloat? + var placeholderSideMargin: CGFloat? + var buttonHeight: CGFloat? + var buttonTouchableOverlap: CGFloat? + var buttonRightMargin: CGFloat? + var utilityButtonWidth: CGFloat? + var utilityButtonBottomMargin: CGFloat? + var initialHeight: CGFloat? + var mainFont: String? + var charCountFont: String? +} + +class CustomizationUtils { + func setup(customization: ChatCustomization, with registrar: FlutterPluginRegistrar, in settings: MMChatSettings) { + setNotNil(&settings.navBarColor, customization.chatToolbar?.backgroundColor?.toColor()) + setNotNil(&settings.navBarTitleColor, customization.chatToolbar?.titleTextColor?.toColor()) + setNotNil(&settings.navBarItemsTintColor, customization.chatToolbar?.navigationIconTint?.toColor()) + setNotNil(&settings.title, customization.chatToolbar?.titleText) + setNotNil(&settings.attachmentPreviewBarsColor, customization.attachmentPreviewToolbar?.backgroundColor?.toColor()) + setNotNil(&settings.attachmentPreviewItemsColor, customization.attachmentPreviewToolbar?.navigationIconTint?.toColor()) + + setNotNil(&settings.backgroundColor, customization.chatBackgroundColor?.toColor()) + setNotNil(&settings.advancedSettings.mainTextColor, customization.chatInputTextColor?.toColor()) + setNotNil(&settings.advancedSettings.textInputBackgroundColor, customization.chatInputBackgroundColor?.toColor()) + setNotNil(&settings.advancedSettings.attachmentButtonIcon, getImage(with: customization.chatInputAttachmentIcon, with: registrar)) + setNotNil(&settings.advancedSettings.sendButtonIcon, getImage(with: customization.chatInputSendIcon, with: registrar)) + setNotNil(&settings.sendButtonTintColor, customization.chatInputSendIconTint?.toColor()) + setNotNil(&settings.chatInputSeparatorLineColor, customization.chatInputSeparatorLineColor?.toColor()) + setNotNil(&settings.advancedSettings.isLineSeparatorHidden, customization.chatInputSeparatorLineVisible) + setNotNil(&settings.advancedSettings.typingIndicatorColor, customization.chatInputCursorColor?.toColor()) + setNotNil(&settings.errorLabelTextColor, customization.networkErrorTextColor?.toColor()) + setNotNil(&settings.errorLabelBackgroundColor, customization.networkErrorLabelBackgroundColor?.toColor()) + setNotNil(&settings.advancedSettings.mainPlaceholderTextColor, customization.chatInputHintTextColor?.toColor()) + } + + func setup(customization: Customization, with registrar: FlutterPluginRegistrar, in settings: MMChatSettings) { + setNotNil(&settings.title, customization.toolbarTitle) + setNotNil(&settings.sendButtonTintColor, customization.sendButtonTintColor?.toColor()) + setNotNil(&settings.navBarItemsTintColor, customization.toolbarTintColor?.toColor()) + setNotNil(&settings.navBarColor, customization.toolbarBackgroundColor?.toColor()) + setNotNil(&settings.navBarTitleColor, customization.toolbarTitleColor?.toColor()) + setNotNil(&settings.backgroundColor, customization.chatBackgroundColor?.toColor()) + setNotNil(&settings.widgetTheme, customization.widgetTheme) + setNotNil(&settings.errorLabelTextColor, customization.noConnectionAlertTextColor?.toColor()) + setNotNil(&settings.errorLabelBackgroundColor, customization.noConnectionAlertBackgroundColor?.toColor()) + setNotNil(&settings.advancedSettings.mainPlaceholderTextColor, customization.chatInputPlaceholderTextColor?.toColor()) + setNotNil(&settings.advancedSettings.typingIndicatorColor, customization.chatInputCursorColor?.toColor()) + setNotNil(&settings.attachmentPreviewBarsColor, customization.ios?.attachmentPreviewBarsColor?.toColor()) + setNotNil(&settings.attachmentPreviewItemsColor, customization.ios?.attachmentPreviewItemsColor?.toColor()) + + setNotNil(&settings.advancedSettings.sendButtonIcon, getImage(with: customization.sendButtonIcon, with: registrar)) + setNotNil(&settings.advancedSettings.attachmentButtonIcon, getImage(with: customization.attachmentButtonIcon, with: registrar)) + + if let chatInputSeparatorVisible = customization.chatInputSeparatorVisible { + settings.advancedSettings.isLineSeparatorHidden = !chatInputSeparatorVisible + } + + setNotNil(&settings.advancedSettings.textContainerTopMargin, customization.ios?.textContainerTopMargin) + setNotNil(&settings.advancedSettings.textContainerLeftPadding, customization.ios?.textContainerLeftPadding) + setNotNil(&settings.advancedSettings.textContainerCornerRadius, customization.ios?.textContainerCornerRadius) + setNotNil(&settings.advancedSettings.textViewTopMargin, customization.ios?.textViewTopMargin) + setNotNil(&settings.advancedSettings.placeholderHeight, customization.ios?.placeholderHeight) + setNotNil(&settings.advancedSettings.placeholderSideMargin, customization.ios?.placeholderSideMargin) + setNotNil(&settings.advancedSettings.buttonHeight, customization.ios?.buttonHeight) + setNotNil(&settings.advancedSettings.buttonTouchableOverlap, customization.ios?.buttonTouchableOverlap) + setNotNil(&settings.advancedSettings.buttonRightMargin, customization.ios?.buttonRightMargin) + setNotNil(&settings.advancedSettings.utilityButtonWidth, customization.ios?.utilityButtonWidth) + setNotNil(&settings.advancedSettings.utilityButtonBottomMargin, customization.ios?.utilityButtonBottomMargin) + setNotNil(&settings.advancedSettings.initialHeight, customization.ios?.initialHeight) + + if let mainFontSize = settings.advancedSettings.mainFont?.pointSize, + let newFontPath = customization.ios?.mainFont, + let font = getFont(with: newFontPath, with: registrar, size: mainFontSize) { + settings.advancedSettings.mainFont = font + } + + if let mainFontSize = settings.advancedSettings.charCountFont?.pointSize, + let newFontPath = customization.ios?.charCountFont, + let font = getFont(with: newFontPath, with: registrar, size: mainFontSize) { + settings.advancedSettings.charCountFont = font + } + } + + func getImage(with name: String?, with registrar: FlutterPluginRegistrar) -> UIImage? { + guard let name = name else { return nil } + let bundleName = registrar.lookupKey(forAsset: name) + guard let bundlePath = Bundle.main.path(forResource: bundleName, ofType: nil) else { return nil } + return UIImage(named: bundlePath) + } + + func getFont(with path: String?, with registrar: FlutterPluginRegistrar, size: CGFloat) -> UIFont? { + guard let path = path else { return nil } + let fontKey = registrar.lookupKey(forAsset: path) + guard let bundlePath = Bundle.main.path(forResource: fontKey, ofType: nil) else { return nil } + + guard let data = NSData(contentsOfFile: bundlePath) else { return nil } + guard let dataProvider = CGDataProvider(data: data) else { return nil } + guard let fontReference = CGFont(dataProvider) else { return nil } + + guard let fontName = URL(string: path)?.deletingPathExtension().lastPathComponent else { return nil } + + var errorReference: Unmanaged? + CTFontManagerRegisterGraphicsFont(fontReference, &errorReference) + return UIFont(name: fontName, size: size) + } + + func setNotNil(_ forVariable: inout T, _ value:T?) { + if let value = value { forVariable = value } + } +} diff --git a/ios/Classes/SwiftInfobipMobilemessagingPlugin.swift b/ios/Classes/SwiftInfobipMobilemessagingPlugin.swift index c0add7e..06161f9 100644 --- a/ios/Classes/SwiftInfobipMobilemessagingPlugin.swift +++ b/ios/Classes/SwiftInfobipMobilemessagingPlugin.swift @@ -103,8 +103,12 @@ public class SwiftInfobipMobilemessagingPlugin: NSObject, FlutterPlugin { cleanup(result: result) } else if call.method == "setupiOSChatSettings" { setupiOSChatSettings(call: call, result: result) + } else if call.method == "setChatCustomization" { + setChatCustomization(call: call, result: result) } else if call.method == "setLanguage" { setLanguage(call: call, result: result) + } else if call.method == "setWidgetTheme" { + setWidgetTheme(call: call, result: result) } else if call.method == "sendContextualData" { sendContextualData(call: call, result: result) } else if call.method == "setJwt" { @@ -236,15 +240,22 @@ public class SwiftInfobipMobilemessagingPlugin: NSObject, FlutterPlugin { return result(Constants.resultSuccess) }) - if let customisation = configuration.customisation { - setupCustomisation(customisation: customisation) + if let customization = configuration.customization { + setupCustomization(customization: customization) } } - - func setupCustomisation(customisation: Customisation) { + + func setupChatCustomization(customization: ChatCustomization) { + let settings = MMChatSettings.sharedInstance + if let controller = self.controller { + CustomizationUtils().setup(customization: customization, with: controller, in: settings) + } + } + + func setupCustomization(customization: Customization) { let settings = MMChatSettings.sharedInstance if let controller = self.controller { - CustomisationUtils().setup(customisation: customisation, with: controller, in: settings) + CustomizationUtils().setup(customization: customization, with: controller, in: settings) } } @@ -456,6 +467,7 @@ public class SwiftInfobipMobilemessagingPlugin: NSObject, FlutterPlugin { } func setupiOSChatSettings(call: FlutterMethodCall, result: @escaping FlutterResult) { + MMLogWarn("[InAppChat] is deprecated. Please use setChatCustomization instead") guard let jsonString = call.arguments as? String, let chatSettings = convertStringToDictionary(text: jsonString) else { return result( @@ -466,7 +478,23 @@ public class SwiftInfobipMobilemessagingPlugin: NSObject, FlutterPlugin { MMChatSettings.settings.configureWith(rawConfig: chatSettings) } - + + func setChatCustomization(call: FlutterMethodCall, result: @escaping FlutterResult) { + guard let jsonString = call.arguments as? String, + let json = jsonString.toJSON() as? [String: AnyObject], + let jsonObject = try? JSONSerialization.data( + withJSONObject: json + ), + let customization = try? JSONDecoder().decode(ChatCustomization.self, from: jsonObject) else { + return result( + FlutterError( code: "invalidConfig", + message: "Error parsing Configuration", + details: "Error parsing Configuration" )) + } + + setupChatCustomization(customization: customization) + } + func setLanguage(call: FlutterMethodCall, result: @escaping FlutterResult) { guard let localeString = call.arguments as? String else { return result( @@ -496,6 +524,17 @@ public class SwiftInfobipMobilemessagingPlugin: NSObject, FlutterPlugin { } } } + + func setWidgetTheme(call: FlutterMethodCall, result: @escaping FlutterResult) { + guard let theme = call.arguments as? String else { + return result( + FlutterError( code: "invalidTheme", + message: "Error parsing Theme string", + details: "Error parsing Theme string" )) + } + MMChatSettings.sharedInstance.widgetTheme = theme + return result(Constants.resultSuccess) + } func setJwt(call: FlutterMethodCall, result: @escaping FlutterResult) { guard let jwt = call.arguments as? String else { diff --git a/ios/infobip_mobilemessaging.podspec b/ios/infobip_mobilemessaging.podspec index 61b3230..cbc0b2b 100644 --- a/ios/infobip_mobilemessaging.podspec +++ b/ios/infobip_mobilemessaging.podspec @@ -21,12 +21,12 @@ A new flutter plugin project. s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.dependency "MobileMessaging/Core", "12.10.0" - s.dependency "MobileMessaging/Geofencing", "12.10.0" - s.dependency "MobileMessaging/InAppChat", "12.10.0" - s.dependency "MobileMessaging/Inbox", "12.10.0" + s.dependency "MobileMessaging/Core", "12.13.2" + s.dependency "MobileMessaging/Geofencing", "12.13.2" + s.dependency "MobileMessaging/InAppChat", "12.13.2" + s.dependency "MobileMessaging/Inbox", "12.13.2" if defined?($WebRTCUIEnabled) - s.dependency "MobileMessaging/WebRTCUI", "12.10.0" + s.dependency "MobileMessaging/WebRTCUI", "12.13.2" end s.platform = :ios, '12.0' diff --git a/lib/infobip_mobilemessaging.dart b/lib/infobip_mobilemessaging.dart index d65260b..be972d8 100644 --- a/lib/infobip_mobilemessaging.dart +++ b/lib/infobip_mobilemessaging.dart @@ -147,6 +147,11 @@ class InfobipMobilemessaging { await _channel.invokeMethod('showChat', shouldBePresentedModallyIOS); } + static Future setChatCustomization(Object settings) async { + await _channel.invokeMethod('setChatCustomization', jsonEncode(settings)); + } + + @deprecated static Future setupiOSChatSettings(IOSChatSettings settings) async { if (Platform.isIOS) { await _channel.invokeMethod('setupiOSChatSettings', jsonEncode(settings.toJson())); @@ -171,6 +176,10 @@ class InfobipMobilemessaging { await _channel.invokeMethod('setLanguage', language); } + static setWidgetTheme(String widgetTheme) async { + await _channel.invokeMethod('setWidgetTheme', widgetTheme); + } + static void sendContextualData(String data, bool allMultiThreadStrategy) async { await _channel.invokeMethod('sendContextualData', {'data': data, 'allMultiThreadStrategy': allMultiThreadStrategy}); } diff --git a/lib/models/chat_view_event.dart b/lib/models/chat_view_event.dart index ff4b778..49ff8b4 100644 --- a/lib/models/chat_view_event.dart +++ b/lib/models/chat_view_event.dart @@ -38,6 +38,11 @@ class ChatViewEvent { /// It is available only for Android platform. static const String attachmentPreviewOpened = "chatView.attachmentPreviewOpened"; + /// Called for every new message in the chat. It provides raw message data. + /// It is available only for Android platform. + static const String chatRawMessageReceived = + "chatView.chatRawMessageReceived"; + ChatViewEvent({ required this.eventName, this.payload, diff --git a/lib/models/configuration.dart b/lib/models/configuration.dart index bb1c719..68952cc 100644 --- a/lib/models/configuration.dart +++ b/lib/models/configuration.dart @@ -236,6 +236,147 @@ class WebRTCUI { Map toJson() => {'configurationId': configurationId}; } +class ToolbarCustomization { + final String? titleTextAppearance; + final String? titleTextColor; + final String? titleText; + final bool? titleCentered; + final String? backgroundColor; + final String? navigationIcon; + final String? navigationIconTint; + final String? subtitleTextAppearance; // android only + final String? subtitleTextColor; // android only + final String? subtitleText; // android only + final bool? subtitleCentered; // android only + + ToolbarCustomization({ + this.titleTextAppearance, + this.titleTextColor, + this.titleText, + this.titleCentered, + this.backgroundColor, + this.navigationIcon, + this.navigationIconTint, + this.subtitleTextAppearance, + this.subtitleTextColor, + this.subtitleText, + this.subtitleCentered, + }); + + Map toJson() => { + 'titleTextAppearance': titleTextAppearance, + 'titleTextColor': titleTextColor, + 'titleText': titleText, + 'titleCentered': titleCentered, + 'backgroundColor': backgroundColor, + 'navigationIcon': navigationIcon, + 'navigationIconTint': navigationIconTint, + 'subtitleTextAppearance': subtitleTextAppearance, + 'subtitleTextColor':subtitleTextColor, + 'subtitleText': subtitleText, + 'subtitleCentered': subtitleCentered, + }; +} + +class ChatCustomization { + // StatusBar + final String? chatStatusBarBackgroundColor; + final String? chatStatusBarIconsColorMode; + // Toolbar + final ToolbarCustomization? chatToolbar; + final ToolbarCustomization? attachmentPreviewToolbar; + final String? attachmentPreviewToolbarSaveMenuItemIcon; + final String? attachmentPreviewToolbarMenuItemsIconTint; + // NetworkError + final String? networkErrorText; + final String? networkErrorTextColor; + final String? networkErrorTextAppearance; + final String? networkErrorLabelBackgroundColor; + // Chat + final String? chatBackgroundColor; + final String? chatProgressBarColor; + // Input + final String? chatInputTextAppearance; + final String? chatInputTextColor; + final String? chatInputBackgroundColor; + final String? chatInputHintText; + final String? chatInputHintTextColor; + final String? chatInputAttachmentIcon; + final String? chatInputAttachmentIconTint; + final String? chatInputAttachmentBackgroundDrawable; + final String? chatInputAttachmentBackgroundColor; + final String? chatInputSendIcon; + final String? chatInputSendIconTint; + final String? chatInputSendBackgroundDrawable; + final String? chatInputSendBackgroundColor; + final String? chatInputSeparatorLineColor; + final bool? chatInputSeparatorLineVisible; + final String? chatInputCursorColor; + + ChatCustomization({ + this.chatStatusBarBackgroundColor, + this.chatStatusBarIconsColorMode, + this.chatToolbar, + this.attachmentPreviewToolbar, + this.attachmentPreviewToolbarSaveMenuItemIcon, + this.attachmentPreviewToolbarMenuItemsIconTint, + this.networkErrorText, + this.networkErrorTextColor, + this.networkErrorTextAppearance, + this.networkErrorLabelBackgroundColor, + this.chatBackgroundColor, + this.chatProgressBarColor, + this.chatInputTextAppearance, + this.chatInputTextColor, + this.chatInputBackgroundColor, + this.chatInputHintText, + this.chatInputHintTextColor, + this.chatInputAttachmentIcon, + this.chatInputAttachmentIconTint, + this.chatInputAttachmentBackgroundDrawable, + this.chatInputAttachmentBackgroundColor, + this.chatInputSendIcon, + this.chatInputSendIconTint, + this.chatInputSendBackgroundDrawable, + this.chatInputSendBackgroundColor, + this.chatInputSeparatorLineColor, + this.chatInputSeparatorLineVisible, + this.chatInputCursorColor, + }); + + Map toJson() => { + 'chatStatusBarBackgroundColor': chatStatusBarBackgroundColor, + 'chatStatusBarIconsColorMode': chatStatusBarIconsColorMode, + 'chatToolbar': chatToolbar, + 'attachmentPreviewToolbar': attachmentPreviewToolbar, + 'attachmentPreviewToolbarSaveMenuItemIcon': attachmentPreviewToolbarSaveMenuItemIcon, + 'attachmentPreviewToolbarMenuItemsIconTint': attachmentPreviewToolbarMenuItemsIconTint, + 'networkErrorText': networkErrorText, + 'networkErrorTextColor': networkErrorTextColor, + 'networkErrorTextAppearance': networkErrorTextAppearance, + 'networkErrorLabelBackgroundColor': networkErrorLabelBackgroundColor, + 'chatBackgroundColor': chatBackgroundColor, + 'chatProgressBarColor': chatProgressBarColor, + 'chatInputTextAppearance': chatInputTextAppearance, + 'chatInputTextColor': chatInputTextColor, + 'chatInputBackgroundColor': chatInputBackgroundColor, + 'chatInputHintText': chatInputHintText, + 'chatInputHintTextColor': chatInputHintTextColor, + 'chatInputAttachmentIcon': chatInputAttachmentIcon, + 'chatInputAttachmentIconTint': chatInputAttachmentIconTint, + 'chatInputAttachmentBackgroundDrawable': chatInputAttachmentBackgroundDrawable, + 'chatInputAttachmentBackgroundColor': chatInputAttachmentBackgroundColor, + 'chatInputSendIcon': chatInputSendIcon, + 'chatInputSendIconTint': chatInputSendIconTint, + 'chatInputSendBackgroundDrawable': chatInputSendBackgroundDrawable, + 'chatInputSendBackgroundColor': chatInputSendBackgroundColor, + 'chatInputSeparatorLineColor': chatInputSeparatorLineColor, + 'chatInputSeparatorLineVisible': chatInputSeparatorLineVisible, + 'chatInputCursorColor': chatInputCursorColor, + }; +} + +@deprecated class InAppChatCustomization { final String? toolbarTitle; final String? toolbarTitleColor; @@ -398,6 +539,7 @@ class AndroidInAppChatCustomization { }; } +@deprecated class IOSInAppChatCustomization { final String? attachmentPreviewBarsColor; final String? attachmentPreviewItemsColor; @@ -453,4 +595,4 @@ class IOSInAppChatCustomization { 'mainFont': mainFont, 'charCountFont': charCountFont }; -} +} \ No newline at end of file diff --git a/lib/models/ios_chat_settings.dart b/lib/models/ios_chat_settings.dart index badf007..718434d 100644 --- a/lib/models/ios_chat_settings.dart +++ b/lib/models/ios_chat_settings.dart @@ -1,3 +1,4 @@ +@deprecated class IOSChatSettings { final String? title; final String? sendButtonColor; diff --git a/test/json_test.dart b/test/json_test.dart index de1785c..33969a4 100644 --- a/test/json_test.dart +++ b/test/json_test.dart @@ -21,10 +21,6 @@ void main() { installationPrimaryExampleJson); }); - test('IOSChatSettings', () { - expect(iosChatSettingsModelExample.toJson(), iosChatSettingsExampleJson); - }); - test('UserIdentity', () { expect(userIdentityModelExample.toJson(), userIdentityExampleJson); }); diff --git a/test/utils/models_examples.dart b/test/utils/models_examples.dart index 5b0008b..230d001 100644 --- a/test/utils/models_examples.dart +++ b/test/utils/models_examples.dart @@ -1,6 +1,5 @@ import 'package:infobip_mobilemessaging/models/configuration.dart'; import 'package:infobip_mobilemessaging/models/installation.dart'; -import 'package:infobip_mobilemessaging/models/ios_chat_settings.dart'; import 'package:infobip_mobilemessaging/models/message.dart'; import 'package:infobip_mobilemessaging/models/personalize_context.dart'; import 'package:infobip_mobilemessaging/models/user_data.dart'; @@ -87,14 +86,6 @@ InstallationPrimary get installationPrimaryModelExample => InstallationPrimary( true, ); -IOSChatSettings get iosChatSettingsModelExample => IOSChatSettings( - title: 'title', - sendButtonColor: 'green', - navigationBarItemsColor: 'red', - navigationBarColor: 'blue', - navigationBarTitleColor: 'yellow', - ); - UserIdentity get userIdentityModelExample => UserIdentity( phones: [ '123',