From b5642c7b31ecfe91f5628d56e58ca1a35442de74 Mon Sep 17 00:00:00 2001 From: xuchengpu mac <1550540124@qq.com> Date: Fri, 17 Nov 2023 19:15:56 +0800 Subject: [PATCH 01/11] update version to 1.2.2 --- app/build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 184fe457..56cd18de 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { applicationId "io.agora.chatdemo" minSdkVersion 21 targetSdkVersion 34 - versionCode 11 - versionName "1.1.0" + versionCode 122 + versionName "1.2.2" multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -139,8 +139,8 @@ dependencies { implementation 'com.google.code.gson:gson:2.6.2' // Agora Chat Uikit - implementation 'io.agora.rtc:chat-uikit:1.1.0' - implementation 'io.agora.rtc:chat-callkit:1.1.0' + implementation 'io.agora.rtc:chat-uikit:1.2.2' + implementation 'io.agora.rtc:chat-callkit:1.2.2' // implementation project(path: ':chat-uikit') // implementation project(path: ':chat-callkit') // url preview From 8eac31a67bb8335e7321cf937113c1d5386efde1 Mon Sep 17 00:00:00 2001 From: xuchengpu mac <1550540124@qq.com> Date: Fri, 17 Nov 2023 20:53:41 +0800 Subject: [PATCH 02/11] update androidTestImplementation version --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 56cd18de..99d72c78 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -111,8 +111,8 @@ dependencies { implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1' testImplementation 'junit:junit:4.+' - androidTestImplementation 'androidx.test.ext:junit:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' //ViewModel and LiveData implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' // Google firebase cloud messaging From aebeabd145f7a8b69d54355eff7cdd87160e5681 Mon Sep 17 00:00:00 2001 From: xuchengpu mac <1550540124@qq.com> Date: Thu, 30 Nov 2023 19:57:05 +0800 Subject: [PATCH 03/11] Fixed an issue where audio and video activities were recovered when the suspended window returned to the desktop --- app/src/main/AndroidManifest.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f5bb1895..ac971a8a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -175,6 +175,7 @@ android:exported="false" android:configChanges="orientation|keyboardHidden|screenSize" android:label="@string/demo_activity_label_video_call" + android:taskAffinity=".SingleCallTask" android:launchMode="singleInstance" android:screenOrientation="portrait" /> From a7fa42cd780bbc22826bc38281267bff87275533 Mon Sep 17 00:00:00 2001 From: xuchengpu mac <1550540124@qq.com> Date: Thu, 30 Nov 2023 19:58:37 +0800 Subject: [PATCH 04/11] Fixed a bug where the initiator's personal information could not be displayed --- .../main/java/io/agora/chatdemo/av/DemoCallKitListener.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/io/agora/chatdemo/av/DemoCallKitListener.java b/app/src/main/java/io/agora/chatdemo/av/DemoCallKitListener.java index cd7fa8bf..da119b35 100644 --- a/app/src/main/java/io/agora/chatdemo/av/DemoCallKitListener.java +++ b/app/src/main/java/io/agora/chatdemo/av/DemoCallKitListener.java @@ -247,9 +247,9 @@ private void getUserIdByAgoraUid(int uId, String url, EaseCallGetUserAccountCall String uIdStr = it.next().toString(); int uid = Integer.valueOf(uIdStr).intValue(); String username = resToken.optString(uIdStr); - if (uid == uId) { + if (uid == uId||uid==0) { //Obtain information such as userName, profile picture, and nickname of the current user - userAccount=new EaseUserAccount(uid, username); + userAccount=new EaseUserAccount(uId, username); } } callback.onUserAccount(userAccount); From 5fa2ef057586cd19da94d4363e9b33cfd624a4dc Mon Sep 17 00:00:00 2001 From: xuchengpu mac <1550540124@qq.com> Date: Fri, 1 Dec 2023 16:26:03 +0800 Subject: [PATCH 05/11] The audio and video stack is not displayed --- app/src/main/AndroidManifest.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ac971a8a..61da85f7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -177,6 +177,7 @@ android:label="@string/demo_activity_label_video_call" android:taskAffinity=".SingleCallTask" android:launchMode="singleInstance" + android:excludeFromRecents="true" android:screenOrientation="portrait" /> Date: Wed, 13 Mar 2024 15:56:08 +0800 Subject: [PATCH 06/11] remove fpa --- app/src/main/java/io/agora/chatdemo/DemoHelper.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/java/io/agora/chatdemo/DemoHelper.java b/app/src/main/java/io/agora/chatdemo/DemoHelper.java index 824d895d..c03d14ae 100644 --- a/app/src/main/java/io/agora/chatdemo/DemoHelper.java +++ b/app/src/main/java/io/agora/chatdemo/DemoHelper.java @@ -170,8 +170,6 @@ private boolean initSDK(Context context) { // options.setIMServer("106.75.100.247"); // options.setImPort(6717); options.setUsingHttpsOnly(true); - // Use fpa by default - options.setFpaEnable(true); boolean hasAppkey = checkAgoraChatAppKey(context, options); // You can set your AppKey by options.setAppKey(appkey) if (!hasAppkey) { From d331e087a354f64c1041bb7ee5ff3fc2ae799c1c Mon Sep 17 00:00:00 2001 From: xuchengpu mac <1550540124@qq.com> Date: Thu, 14 Mar 2024 14:25:13 +0800 Subject: [PATCH 07/11] add ENABLE_AGORA for pck --- app/jni/Android.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/app/jni/Android.mk b/app/jni/Android.mk index 129fd8b7..f094fe4e 100644 --- a/app/jni/Android.mk +++ b/app/jni/Android.mk @@ -19,5 +19,6 @@ include $(CLEAR_VARS) PB_LITE=1 ENABLE_CALL=0 USE_SQLCIPHER=1 +ENABLE_AGORA=1 #libhyphenate.so include $(LOCAL_PATH)/../../../emclient-linux/Android.mk From 0a3d8552f2dd260b0b47ee853525707b288bd377 Mon Sep 17 00:00:00 2001 From: xuchengpu mac <1550540124@qq.com> Date: Sun, 7 Apr 2024 16:45:51 +0800 Subject: [PATCH 08/11] add pin message feature in Agora-Chat demo --- .../java/io/agora/chatdemo/DemoHelper.java | 5 - .../chatdemo/chat/ChatReportActivity.java | 2 +- .../chatdemo/chat/CustomChatFragment.java | 356 ++++++++++++++---- .../chat/PinListItemSpaceDecoration.java | 29 ++ .../chat/adapter/PinMessageListAdapter.java | 44 +++ .../pinmessage/PinDefaultViewHolder.java | 68 ++++ .../pinmessage/PinImageMessageViewHolder.java | 65 ++++ .../pinmessage/PinTextMessageViewHolder.java | 68 ++++ .../chat/viewmodel/ChatViewModel.java | 29 +- .../ConversationListFragment.java | 1 - .../chatdemo/general/dialog/SimpleDialog.java | 6 +- .../general/manager/UsersManager.java | 1 + .../repositories/EMChatManagerRepository.java | 52 +++ .../repositories/EMClientRepository.java | 4 +- .../chatdemo/general/widget/PinInfoView.java | 118 ++++++ .../widget/PinMessageListViewGroup.java | 164 ++++++++ .../drawable-xxhdpi/chat_item_menu_pin.png | Bin 0 -> 760 bytes .../res/drawable-xxhdpi/chat_pin_onlight.png | Bin 0 -> 1022 bytes .../drawable-xxhdpi/chat_pin_rectangle.png | Bin 0 -> 373 bytes .../res/drawable-xxhdpi/chat_pininfo_icon.png | Bin 0 -> 554 bytes .../drawable/shape_gray_ebebeb_corner_8.xml | 7 + .../drawable/shape_gray_f5f5f5_corner_8.xml | 7 + app/src/main/res/layout/pin_info_view.xml | 40 ++ .../layout/pin_message_list_view_group.xml | 46 +++ app/src/main/res/layout/pinlist_default.xml | 52 +++ app/src/main/res/layout/pinlist_image.xml | 53 +++ app/src/main/res/layout/pinlist_text.xml | 53 +++ app/src/main/res/values/colors.xml | 2 + app/src/main/res/values/ids.xml | 1 + app/src/main/res/values/strings.xml | 5 + 30 files changed, 1186 insertions(+), 92 deletions(-) create mode 100644 app/src/main/java/io/agora/chatdemo/chat/PinListItemSpaceDecoration.java create mode 100644 app/src/main/java/io/agora/chatdemo/chat/adapter/PinMessageListAdapter.java create mode 100644 app/src/main/java/io/agora/chatdemo/chat/viewholder/pinmessage/PinDefaultViewHolder.java create mode 100644 app/src/main/java/io/agora/chatdemo/chat/viewholder/pinmessage/PinImageMessageViewHolder.java create mode 100644 app/src/main/java/io/agora/chatdemo/chat/viewholder/pinmessage/PinTextMessageViewHolder.java create mode 100644 app/src/main/java/io/agora/chatdemo/general/widget/PinInfoView.java create mode 100644 app/src/main/java/io/agora/chatdemo/general/widget/PinMessageListViewGroup.java create mode 100644 app/src/main/res/drawable-xxhdpi/chat_item_menu_pin.png create mode 100644 app/src/main/res/drawable-xxhdpi/chat_pin_onlight.png create mode 100644 app/src/main/res/drawable-xxhdpi/chat_pin_rectangle.png create mode 100644 app/src/main/res/drawable-xxhdpi/chat_pininfo_icon.png create mode 100644 app/src/main/res/drawable/shape_gray_ebebeb_corner_8.xml create mode 100644 app/src/main/res/drawable/shape_gray_f5f5f5_corner_8.xml create mode 100644 app/src/main/res/layout/pin_info_view.xml create mode 100644 app/src/main/res/layout/pin_message_list_view_group.xml create mode 100644 app/src/main/res/layout/pinlist_default.xml create mode 100644 app/src/main/res/layout/pinlist_image.xml create mode 100644 app/src/main/res/layout/pinlist_text.xml diff --git a/app/src/main/java/io/agora/chatdemo/DemoHelper.java b/app/src/main/java/io/agora/chatdemo/DemoHelper.java index c03d14ae..9cdfd53a 100644 --- a/app/src/main/java/io/agora/chatdemo/DemoHelper.java +++ b/app/src/main/java/io/agora/chatdemo/DemoHelper.java @@ -163,12 +163,7 @@ public EMAREncryptUtils getEncryptUtils(){ private boolean initSDK(Context context) { // Set Chat Options ChatOptions options = initChatOptions(context); - // Configure custom rest server and im server -// options.setRestServer("a1-hsb.easemob.com"); -// options.setAppKey("easemob-demo#chatdemoui"); -// options.setIMServer("106.75.100.247"); -// options.setImPort(6717); options.setUsingHttpsOnly(true); boolean hasAppkey = checkAgoraChatAppKey(context, options); // You can set your AppKey by options.setAppKey(appkey) diff --git a/app/src/main/java/io/agora/chatdemo/chat/ChatReportActivity.java b/app/src/main/java/io/agora/chatdemo/chat/ChatReportActivity.java index 7707515d..4b94dfa5 100644 --- a/app/src/main/java/io/agora/chatdemo/chat/ChatReportActivity.java +++ b/app/src/main/java/io/agora/chatdemo/chat/ChatReportActivity.java @@ -140,7 +140,7 @@ public void initData(){ } } viewModel = new ViewModelProvider(this).get(ChatViewModel.class); - viewModel.getChatManagerObservable().observe(this,response->{ + viewModel.getReportMessageObservable().observe(this, response->{ parseResource(response, new OnResourceParseCallback() { @Override public void onSuccess(@Nullable Boolean data) { diff --git a/app/src/main/java/io/agora/chatdemo/chat/CustomChatFragment.java b/app/src/main/java/io/agora/chatdemo/chat/CustomChatFragment.java index 7dac0f92..aa0aa1f6 100644 --- a/app/src/main/java/io/agora/chatdemo/chat/CustomChatFragment.java +++ b/app/src/main/java/io/agora/chatdemo/chat/CustomChatFragment.java @@ -1,6 +1,7 @@ package io.agora.chatdemo.chat; import static io.agora.chat.uikit.menu.EaseChatType.SINGLE_CHAT; +import static io.agora.chatdemo.general.utils.ToastUtils.showToast; import android.Manifest; import android.app.Activity; @@ -14,7 +15,6 @@ import android.text.TextUtils; import android.text.TextWatcher; import android.text.style.ForegroundColorSpan; -import android.util.Log; import android.view.Gravity; import android.view.KeyEvent; import android.view.View; @@ -35,6 +35,8 @@ import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; +import com.google.android.gms.common.util.CollectionUtils; + import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; @@ -44,11 +46,13 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import io.agora.MessageListener; import io.agora.chat.ChatClient; import io.agora.chat.ChatMessage; import io.agora.chat.ChatRoom; import io.agora.chat.CustomMessageBody; import io.agora.chat.LocationMessageBody; +import io.agora.chat.MessagePinInfo; import io.agora.chat.TextMessageBody; import io.agora.chat.uikit.chat.EaseChatFragment; import io.agora.chat.uikit.chat.adapter.EaseMessageAdapter; @@ -65,15 +69,22 @@ import io.agora.chatdemo.R; import io.agora.chatdemo.chat.adapter.CustomMessageAdapter; import io.agora.chatdemo.chat.viewmodel.ChatViewModel; +import io.agora.chatdemo.general.callbacks.OnResourceParseCallback; import io.agora.chatdemo.general.constant.DemoConstant; import io.agora.chatdemo.general.dialog.AlertDialog; +import io.agora.chatdemo.general.dialog.SimpleDialog; import io.agora.chatdemo.general.enums.Status; import io.agora.chatdemo.general.interfaces.TranslationListener; import io.agora.chatdemo.general.livedatas.EaseEvent; import io.agora.chatdemo.general.livedatas.LiveDataBus; +import io.agora.chatdemo.general.net.Resource; import io.agora.chatdemo.general.permission.PermissionCompat; import io.agora.chatdemo.general.permission.PermissionsManager; import io.agora.chatdemo.general.utils.RecyclerViewUtils; +import io.agora.chatdemo.general.utils.ToastUtils; +import io.agora.chatdemo.general.utils.UIUtils; +import io.agora.chatdemo.general.widget.PinInfoView; +import io.agora.chatdemo.general.widget.PinMessageListViewGroup; import io.agora.chatdemo.group.GroupHelper; import io.agora.chatdemo.group.model.MemberAttributeBean; import io.agora.chatdemo.group.viewmodel.GroupDetailViewModel; @@ -82,7 +93,7 @@ import io.agora.chatdemo.me.TranslationSettingsActivity; import io.agora.util.EMLog; -public class CustomChatFragment extends EaseChatFragment { +public class CustomChatFragment extends EaseChatFragment implements MessageListener { private static final int REQUEST_CODE_STORAGE_PICTURE = 111; private static final int REQUEST_CODE_STORAGE_VIDEO = 112; private static final int REQUEST_CODE_STORAGE_FILE = 113; @@ -100,6 +111,7 @@ public class CustomChatFragment extends EaseChatFragment { , result -> onRequestResult(result, REQUEST_CODE_STORAGE_VIDEO)); private final ActivityResultLauncher requestFilePermission = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions() , result -> onRequestResult(result, REQUEST_CODE_STORAGE_FILE)); + private PinInfoView pinInfoView; @Override public void onCreate(@Nullable Bundle savedInstanceState) { @@ -109,8 +121,8 @@ public void onCreate(@Nullable Bundle savedInstanceState) { result -> { if (result.getResultCode() == Activity.RESULT_OK) { boolean enable = DemoHelper.getInstance().getModel().getDemandTranslationEnable(); - if (enable && !TextUtils.isEmpty(getPreferredLanguageCode())){ - translationMessage(translationMsg,getPreferredLanguageCode()); + if (enable && !TextUtils.isEmpty(getPreferredLanguageCode())) { + translationMessage(translationMsg, getPreferredLanguageCode()); } } } @@ -120,53 +132,130 @@ public void onCreate(@Nullable Bundle savedInstanceState) { @Override public void initData() { super.initData(); - groupDetailViewModel = new ViewModelProvider((AppCompatActivity)mContext).get(GroupDetailViewModel.class); - groupDetailViewModel.getFetchMemberAttributesObservable().observe(this,response ->{ - if(response == null || isDestroy) { + groupDetailViewModel = new ViewModelProvider((AppCompatActivity) mContext).get(GroupDetailViewModel.class); + groupDetailViewModel.getFetchMemberAttributesObservable().observe(this, response -> { + if (response == null || isDestroy) { return; } - if(response.status == Status.SUCCESS) { + if (response.status == Status.SUCCESS) { chatLayout.getChatMessageListLayout().refreshMessages(); } }); viewModel = new ViewModelProvider(this).get(ChatViewModel.class); - viewModel.getTranslationObservable().observe(this,response ->{ - if(response == null || isDestroy) { + viewModel.getTranslationObservable().observe(this, response -> { + if (response == null || isDestroy) { return; } - if(response.status == Status.SUCCESS) { + if (response.status == Status.SUCCESS) { chatLayout.getChatMessageListLayout().refreshMessages(); - }else { - EMLog.e("translationMessage","onError: " + response.errorCode + " - " + response.getMessage()); + } else { + EMLog.e("translationMessage", "onError: " + response.errorCode + " - " + response.getMessage()); } }); + viewModel.pinMessageObservable().observe(this, response -> { + parseResource(response, new OnResourceParseCallback() { + @Override + public void onSuccess(ChatMessage message) { + updatePinMessage(message,ChatClient.getInstance().getCurrentUser()); + } + + @Override + public void onError(int code, String message) { + super.onError(code, message); + showToast(message); + } + }); + }); LiveDataBus.get().with(DemoConstant.GROUP_MEMBER_ATTRIBUTE_CHANGE, EaseEvent.class).observe(getViewLifecycleOwner(), event -> { - if(event == null || isDestroy) { + if (event == null || isDestroy) { return; } chatLayout.getChatMessageListLayout().refreshMessages(); }); LiveDataBus.get().with(DemoConstant.MESSAGE_CHANGE_CHANGE, EaseEvent.class).observe(getViewLifecycleOwner(), event -> { - if(event == null || isDestroy) { + if (event == null || isDestroy) { return; } - if(event.isMessageChange()) { + if (event.isMessageChange()) { chatLayout.getChatMessageListLayout().refreshMessages(); } }); LiveDataBus.get().with(DemoConstant.EVENT_CHAT_MODEL_TO_NORMAL, EaseEvent.class).observe(this, event -> { - if(event == null || isDestroy) { + if (event == null || isDestroy) { return; } - if(event.type == EaseEvent.TYPE.NOTIFY && TextUtils.isEmpty(event.message)) { + if (event.type == EaseEvent.TYPE.NOTIFY && TextUtils.isEmpty(event.message)) { IChatTopExtendMenu chatTopExtendMenu = chatLayout.getChatInputMenu().getChatTopExtendMenu(); - if(chatTopExtendMenu instanceof EaseChatMultiSelectView) { + if (chatTopExtendMenu instanceof EaseChatMultiSelectView) { ((EaseChatMultiSelectView) chatTopExtendMenu).dismissSelectView(null); } titleBar.setVisibility(View.GONE); } }); + + viewModel.getPinMessageObservable().observe(this, response -> { + parseResource(response, new OnResourceParseCallback>() { + @Override + public void onSuccess(List messages) { + if (CollectionUtils.isEmpty(messages)) { + pinInfoView.setVisibility(View.GONE); + } else { + pinInfoView.setData(messages); + } + } + + @Override + public void onError(int code, String message) { + super.onError(code, message); + } + }); + }); + + if (chatType != SINGLE_CHAT) { + viewModel.getPinnedMessagesFromServer(conversationId); + } + } + + private void updatePinMessage(ChatMessage message,String operationUser) { + runOnUiThread(()->{ + boolean isPined = message.pinnedInfo()==null||TextUtils.isEmpty(message.pinnedInfo().operatorId()); + ToastUtils.showToast((isPined ? "unpin success" : "pin success")); + if(isPined){ + pinInfoView.removeData(message); + }else{ + pinInfoView.addData(message); + } + //insert pin message info in local + insertPinNotificationInLocal(message, operationUser); + chatLayout.getChatMessageListLayout().refreshToLatest(); + }); + } + + private void insertPinNotificationInLocal(ChatMessage msg,String operationUser) { + ChatMessage msgNotification = ChatMessage.createReceiveMessage(ChatMessage.Type.TXT); + String content; + if(msg.pinnedInfo()==null||TextUtils.isEmpty(msg.pinnedInfo().operatorId())) { + content = operationUser+" removed a pin message"; + }else{ + content = operationUser+" pinned a message"; + } + if(TextUtils.equals(operationUser, ChatClient.getInstance().getCurrentUser())){ + content = content.replace(operationUser, "You"); + } + TextMessageBody txtBody = new TextMessageBody(content); + msgNotification.addBody(txtBody); + msgNotification.setFrom(msg.getFrom()); + msgNotification.setTo(msg.getTo()); + msgNotification.setUnread(false); + msgNotification.setMsgTime(System.currentTimeMillis()); + msgNotification.setLocalTime(System.currentTimeMillis()); + msgNotification.setChatType(msg.getChatType()); + //Just to reuse the recall layout + msgNotification.setAttribute(EaseConstant.MESSAGE_TYPE_RECALL, true); + msgNotification.setStatus(ChatMessage.Status.SUCCESS); + msgNotification.setIsChatThreadMessage(msg.isChatThreadMessage()); + ChatClient.getInstance().chatManager().saveMessage(msgNotification); } @Override @@ -174,11 +263,11 @@ public void initListener() { super.initListener(); listenerRecyclerViewItemFinishLayout(); EditText editText = chatLayout.getChatInputMenu().getPrimaryMenu().getEditText(); - if (editText != null){ + if (editText != null) { editText.setOnKeyListener(new View.OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { - return removePickAt(v,keyCode,event); + return removePickAt(v, keyCode, event); } }); editText.addTextChangedListener(new TextWatcher() { @@ -189,20 +278,20 @@ public void beforeTextChanged(CharSequence s, int start, int count, int after) { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { - if(!chatLayout.getChatMessageListLayout().isGroupChat()) { + if (!chatLayout.getChatMessageListLayout().isGroupChat()) { return; } - if(count == 1 && "@".equals(String.valueOf(s.charAt(start)))){ + if (count == 1 && "@".equals(String.valueOf(s.charAt(start)))) { Bundle bundle = new Bundle(); bundle.putString(EaseConstant.EXTRA_CONVERSATION_ID, conversationId); PickAtUserDialogFragment fragment = new PickAtUserDialogFragment(); fragment.setPickAtSelectListener(username -> { - chatLayout.inputAtUsername(username,false); + chatLayout.inputAtUsername(username, false); }); fragment.setArguments(bundle); - if (getActivity() != null){ + if (getActivity() != null) { fragment.show(getActivity().getSupportFragmentManager(), "pick_at_user"); - if (getActivity() != null){ + if (getActivity() != null) { new Handler().postDelayed(new Runnable() { @Override public void run() { @@ -210,7 +299,7 @@ public void run() { editText.requestFocus(); imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); } - },200); + }, 200); } } } @@ -222,6 +311,53 @@ public void afterTextChanged(Editable editable) { } }); } + + if(pinInfoView!=null){ + pinInfoView.setOnItemClickListener(new PinMessageListViewGroup.OnItemClickListener() { + @Override + public void onItemClick(ChatMessage message) { + pinInfoView.restView(); + //click for pin message list + List messageList = chatLayout.getChatMessageListLayout().getMessageAdapter().getData(); + boolean isExist = false; + for (int i = 0; i < messageList.size(); i++) { + ChatMessage chatMessage = messageList.get(i); + if (chatMessage.getMsgId().equals(message.getMsgId())) { + isExist = true; + break; + } + } + if (!isExist) { + ToastUtils.showToast(getString(R.string.pin_skip_not_exist)); + }else{ + chatLayout.getChatMessageListLayout().moveToTarget(message); + } + } + }); + pinInfoView.setOnItemSubViewClickListener(new EaseMessageAdapter.OnItemSubViewClickListener() { + @Override + public void onItemSubViewClick(View view, int position) { + ChatMessage message=pinInfoView.getPinMessages().get(position); + showUnPinConfirmDialog(message); + } + }); + } + + ChatClient.getInstance().chatManager().addMessageListener(this); + } + + private void showUnPinConfirmDialog(ChatMessage message) { + new SimpleDialog.Builder(getActivity()) + .setTitle(R.string.unpin_confirm_message) + .showCancelButton(true) + .hideConfirmButton(false) + .setOnConfirmClickListener(R.string.dialog_btn_to_confirm, new SimpleDialog.OnConfirmClickListener() { + @Override + public void onConfirmClick(View view) { + viewModel.pinMessage(message, false); + } + }) + .show(); } @@ -234,28 +370,46 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat @Override public void initView() { super.initView(); + MenuItemBean pinItemBean = new MenuItemBean(0, R.id.action_chat_pin, 76, getResources().getString(R.string.ease_action_pin)); + pinItemBean.setResourceId(R.drawable.chat_item_menu_pin); MenuItemBean menuItemBean = new MenuItemBean(0, R.id.action_chat_report, 99, getResources().getString(R.string.ease_action_report)); menuItemBean.setResourceId(R.drawable.chat_item_menu_report); - MenuItemBean menuTranslationBean = new MenuItemBean(0, R.id.action_chat_translation,88, getResources().getString(R.string.ease_action_translation)); + MenuItemBean menuTranslationBean = new MenuItemBean(0, R.id.action_chat_translation, 88, getResources().getString(R.string.ease_action_translation)); menuTranslationBean.setResourceId(R.drawable.chat_item_menu_translation); - MenuItemBean menuReTranslationBean = new MenuItemBean(0, R.id.action_chat_re_translation,111, getResources().getString(R.string.ease_action_re_translation)); + MenuItemBean menuReTranslationBean = new MenuItemBean(0, R.id.action_chat_re_translation, 111, getResources().getString(R.string.ease_action_re_translation)); menuReTranslationBean.setResourceId(R.drawable.chat_item_menu_translation); + chatLayout.getMenuHelper().addItemMenu(pinItemBean); chatLayout.getMenuHelper().addItemMenu(menuItemBean); chatLayout.getMenuHelper().addItemMenu(menuTranslationBean); chatLayout.getMenuHelper().addItemMenu(menuReTranslationBean); chatLayout.setPresenter(new ChatCustomPresenter()); EaseMessageAdapter adapter = chatLayout.getChatMessageListLayout().getMessageAdapter(); - if (adapter instanceof CustomMessageAdapter){ - ((CustomMessageAdapter)adapter).setTranslationListener(new TranslationListener() { + if (adapter instanceof CustomMessageAdapter) { + ((CustomMessageAdapter) adapter).setTranslationListener(new TranslationListener() { @Override - public void onTranslationRetry(ChatMessage message,String languageCode) { - if (message.getBody() instanceof TextMessageBody){ - translationMessage(message,languageCode); + public void onTranslationRetry(ChatMessage message, String languageCode) { + if (message.getBody() instanceof TextMessageBody) { + translationMessage(message, languageCode); } } }); } + + if(chatType!=SINGLE_CHAT){ + pinInfoView = new PinInfoView(getContext()); + RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + pinInfoView.setVisibility(View.GONE); + chatLayout.addView(pinInfoView, layoutParams); + chatLayout.post(new Runnable() { + @Override + public void run() { + pinInfoView.setInnerLayoutMaxHeight(chatLayout.getHeight()- UIUtils.dp2px(getContext(), 145)); + } + }); + } } @Override @@ -290,14 +444,16 @@ public void onPreMenu(EasePopupWindowHelper helper, ChatMessage message) { helper.findItemVisible(R.id.action_chat_translation, false); helper.findItemVisible(R.id.action_chat_re_translation, false); } + helper.findItem(R.id.action_chat_pin).setTitle((message.pinnedInfo()==null||TextUtils.isEmpty(message.pinnedInfo().operatorId())) ? getString(R.string.ease_action_pin):getString(R.string.ease_action_unpin)); + helper.findItemVisible(R.id.action_chat_pin, chatType==SINGLE_CHAT?false:true); } @Override public boolean onMenuItemClick(MenuItemBean item, ChatMessage message) { - switch (item.getItemId()){ + switch (item.getItemId()) { case R.id.action_chat_report: if (message.status() == ChatMessage.Status.SUCCESS) - ChatReportActivity.actionStart(getActivity(),message.getMsgId()); + ChatReportActivity.actionStart(getActivity(), message.getMsgId()); break; case R.id.action_chat_select: showSelectModelTitle(); @@ -306,19 +462,22 @@ public boolean onMenuItemClick(MenuItemBean item, ChatMessage message) { case R.id.action_chat_translation: case R.id.action_chat_re_translation: translationMsg = message; - if (!TextUtils.isEmpty(getPreferredLanguageCode())){ + if (!TextUtils.isEmpty(getPreferredLanguageCode())) { boolean enable = DemoHelper.getInstance().getModel().getDemandTranslationEnable(); - if (enable){ - translationMessage(message,getPreferredLanguageCode()); + if (enable) { + translationMessage(message, getPreferredLanguageCode()); break; - }else { + } else { translationType = DemoConstant.TRANSLATION_DEMAND_ENABLE; } - }else { + } else { translationType = DemoConstant.TRANSLATION_NO_LANGUAGE; } showTranslationDialog(); break; + case R.id.action_chat_pin: + viewModel.pinMessage(message, message.pinnedInfo()==null||TextUtils.isEmpty(message.pinnedInfo().operatorId())); + break; } return super.onMenuItemClick(item, message); } @@ -334,17 +493,17 @@ public boolean onChatExtendMenuItemClick(View view, int itemId) { } break; case R.id.extend_item_picture: - if(!PermissionCompat.checkMediaPermission(mContext, requestImagePermission, Manifest.permission.READ_MEDIA_IMAGES)) { + if (!PermissionCompat.checkMediaPermission(mContext, requestImagePermission, Manifest.permission.READ_MEDIA_IMAGES)) { return true; } break; case R.id.extend_item_video: - if(!PermissionCompat.checkMediaPermission(mContext, requestVideoPermission, Manifest.permission.READ_MEDIA_VIDEO, Manifest.permission.CAMERA)) { + if (!PermissionCompat.checkMediaPermission(mContext, requestVideoPermission, Manifest.permission.READ_MEDIA_VIDEO, Manifest.permission.CAMERA)) { return true; } break; case R.id.extend_item_file: - if(!PermissionCompat.checkMediaPermission(mContext, requestFilePermission, Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_VIDEO)) { + if (!PermissionCompat.checkMediaPermission(mContext, requestFilePermission, Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_VIDEO)) { return true; } break; @@ -353,16 +512,16 @@ public boolean onChatExtendMenuItemClick(View view, int itemId) { } private void onRequestResult(Map result, int requestCode) { - if(result != null && result.size() > 0) { + if (result != null && result.size() > 0) { for (Map.Entry entry : result.entrySet()) { EMLog.e("chat", "onRequestResult: " + entry.getKey() + " " + entry.getValue()); } - if(PermissionCompat.getMediaAccess(mContext) != PermissionCompat.StorageAccess.Denied) { - if(requestCode == REQUEST_CODE_STORAGE_PICTURE) { + if (PermissionCompat.getMediaAccess(mContext) != PermissionCompat.StorageAccess.Denied) { + if (requestCode == REQUEST_CODE_STORAGE_PICTURE) { selectPicFromLocal(); - }else if(requestCode == REQUEST_CODE_STORAGE_VIDEO) { + } else if (requestCode == REQUEST_CODE_STORAGE_VIDEO) { selectVideoFromLocal(); - }else if(requestCode == REQUEST_CODE_STORAGE_FILE) { + } else if (requestCode == REQUEST_CODE_STORAGE_FILE) { selectFileFromLocal(); } } @@ -378,9 +537,9 @@ private void showSelectModelTitle() { titleBar.getIcon().setVisibility(View.VISIBLE); titleBar.getLeftLayout().setVisibility(View.GONE); ViewParent parent = titleBar.getTitle().getParent(); - if(parent instanceof ViewGroup) { + if (parent instanceof ViewGroup) { ViewGroup.LayoutParams params = ((ViewGroup) parent).getLayoutParams(); - if(params instanceof RelativeLayout.LayoutParams) { + if (params instanceof RelativeLayout.LayoutParams) { ((RelativeLayout.LayoutParams) params).leftMargin = (int) EaseUtils.dip2px(mContext, 12); } } @@ -390,9 +549,9 @@ public void onRightClick(View view) { LiveDataBus.get().with(DemoConstant.EVENT_CHAT_MODEL_TO_NORMAL).postValue(EaseEvent.create(DemoConstant.EVENT_CHAT_MODEL_TO_NORMAL, EaseEvent.TYPE.NOTIFY)); } }); - if(chatType != SINGLE_CHAT) { + if (chatType != SINGLE_CHAT) { boolean hasProvided = DemoHelper.getInstance().setGroupInfo(mContext, conversationId, titleBar.getTitle(), titleBar.getIcon()); - if(!hasProvided) { + if (!hasProvided) { setGroupInfo(); } } else { @@ -404,16 +563,16 @@ public void onRightClick(View view) { private void setGroupInfo() { String title = ""; - if(chatType == EaseChatType.GROUP_CHAT) { + if (chatType == EaseChatType.GROUP_CHAT) { title = GroupHelper.getGroupName(conversationId); titleBar.getIcon().setImageResource(R.drawable.icon); - }else if(chatType == EaseChatType.CHATROOM) { + } else if (chatType == EaseChatType.CHATROOM) { titleBar.getIcon().setImageResource(R.drawable.icon); ChatRoom room = ChatClient.getInstance().chatroomManager().getChatRoom(conversationId); - if(room == null) { + if (room == null) { return; } - title = TextUtils.isEmpty(room.getName()) ? conversationId : room.getName(); + title = TextUtils.isEmpty(room.getName()) ? conversationId : room.getName(); } titleBar.getTitle().setText(title); } @@ -500,22 +659,22 @@ public void onModifyMessageSuccess(ChatMessage messageModified) { } - private void translationMessage(ChatMessage message,String language){ + private void translationMessage(ChatMessage message, String language) { List list = new ArrayList<>(); list.add(language); - viewModel.translationMessage(message,list); + viewModel.translationMessage(message, list); } @Override public void addMsgAttrsBeforeSend(ChatMessage message) { super.addMsgAttrsBeforeSend(message); String[] autoLanguage = TranslationHelper.getLanguageByType(DemoConstant.TRANSLATION_TYPE_AUTO, conversationId); - if (!TextUtils.isEmpty(autoLanguage[0])){ - translationMessage(message,autoLanguage[0]); + if (!TextUtils.isEmpty(autoLanguage[0])) { + translationMessage(message, autoLanguage[0]); } } - private void setPickAtContentStyle(Editable editable){ + private void setPickAtContentStyle(Editable editable) { Pattern pattern = Pattern.compile("@([^\\s]+)"); Matcher matcher = pattern.matcher(editable); while (matcher.find()) { @@ -528,18 +687,18 @@ private void setPickAtContentStyle(Editable editable){ } } - private boolean removePickAt(View v, int keyCode, KeyEvent event){ + private boolean removePickAt(View v, int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN && v instanceof EditText) { - int selectionStart = ((EditText)v).getSelectionStart(); - int selectionEnd = ((EditText)v).getSelectionEnd(); - SpannableStringBuilder text = (SpannableStringBuilder) ((EditText)v).getText(); + int selectionStart = ((EditText) v).getSelectionStart(); + int selectionEnd = ((EditText) v).getSelectionEnd(); + SpannableStringBuilder text = (SpannableStringBuilder) ((EditText) v).getText(); ForegroundColorSpan[] spans = text.getSpans(0, text.length(), ForegroundColorSpan.class); for (ForegroundColorSpan span : spans) { int spanStart = text.getSpanStart(span); int spanEnd = text.getSpanEnd(span); if (selectionStart >= spanStart && selectionEnd <= spanEnd) { - if (spanStart != -1 && spanEnd != -1){ - text.delete(spanStart+1, spanEnd); + if (spanStart != -1 && spanEnd != -1) { + text.delete(spanStart + 1, spanEnd); } } } @@ -547,14 +706,16 @@ private boolean removePickAt(View v, int keyCode, KeyEvent event){ return false; } - private void showTranslationDialog(){ - if (translationType == 0){ return;} + private void showTranslationDialog() { + if (translationType == 0) { + return; + } translationDialog = new AlertDialog.Builder(mContext) .setContentView(R.layout.dialog_auto_translation) .setText(R.id.tv_content, translationType == DemoConstant.TRANSLATION_NO_LANGUAGE ? getString(R.string.translation_auto_about_info) - : getString(R.string.translation_unable) + : getString(R.string.translation_unable) ) .setText(R.id.btn_ok, getString(R.string.translation_setting)) .setLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) @@ -564,11 +725,11 @@ private void showTranslationDialog(){ @Override public void onClick(View v) { Intent starter; - if (translationType == DemoConstant.TRANSLATION_NO_LANGUAGE){ + if (translationType == DemoConstant.TRANSLATION_NO_LANGUAGE) { starter = new Intent(mContext, LanguageActivity.class); starter.putExtra(DemoConstant.TRANSLATION_TYPE, DemoConstant.TRANSLATION_TYPE_MESSAGE); starter.putExtra(DemoConstant.TRANSLATION_SELECT_MAX_COUNT, 1); - }else { + } else { starter = new Intent(mContext, TranslationSettingsActivity.class); } launcher.launch(starter); @@ -583,7 +744,7 @@ public void onClick(View v) { translationDialog.show(); } - private String getPreferredLanguageCode(){ + private String getPreferredLanguageCode() { String[] language = TranslationHelper.getLanguageByType(DemoConstant.TRANSLATION_TYPE_MESSAGE, ""); return language[0]; } @@ -597,8 +758,53 @@ public void onResume() { @Override public void onPause() { super.onPause(); - if(mContext != null && mContext.isFinishing()) { + if (mContext != null && mContext.isFinishing()) { isDestroy = true; } } + + /** + * Parse Resource + * + * @param response + * @param callback + * @param + */ + public void parseResource(Resource response, @NonNull OnResourceParseCallback callback) { + if (response == null) { + return; + } + if (response.status == Status.SUCCESS) { + callback.onHideLoading(); + callback.onSuccess(response.data); + } else if (response.status == Status.ERROR) { + callback.onHideLoading(); + callback.onError(response.errorCode, response.getMessage()); + } else if (response.status == Status.LOADING) { + callback.onLoading(response.data); + } + } + + @Override + public void onMessageReceived(List messages) { + + } + + @Override + public void onMessagePinChanged(String messageId, String conversationId, MessagePinInfo.PinOperation pinOperation, MessagePinInfo pinInfo) { + ChatMessage message = ChatClient.getInstance().chatManager().getMessage(messageId); + if(message!=null) { + updatePinMessage(message,pinInfo.operatorId()); + }else{ + viewModel.getPinnedMessagesFromServer(conversationId); + } + } + + + @Override + public void onDestroy() { + super.onDestroy(); + ChatClient.getInstance().chatManager().removeMessageListener(this); + } + } diff --git a/app/src/main/java/io/agora/chatdemo/chat/PinListItemSpaceDecoration.java b/app/src/main/java/io/agora/chatdemo/chat/PinListItemSpaceDecoration.java new file mode 100644 index 00000000..6cc676f9 --- /dev/null +++ b/app/src/main/java/io/agora/chatdemo/chat/PinListItemSpaceDecoration.java @@ -0,0 +1,29 @@ +package io.agora.chatdemo.chat; + +import android.graphics.Rect; +import android.view.View; + +import androidx.recyclerview.widget.RecyclerView; + +public class PinListItemSpaceDecoration extends RecyclerView.ItemDecoration { + private int space; + + public PinListItemSpaceDecoration(int space) { + this.space = space; + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + outRect.left = space; + outRect.right = space; + outRect.bottom = space; + + + if (parent.getChildAdapterPosition(view) == 0) { + outRect.top = space; + } else { + outRect.top = 0; + } + } +} + diff --git a/app/src/main/java/io/agora/chatdemo/chat/adapter/PinMessageListAdapter.java b/app/src/main/java/io/agora/chatdemo/chat/adapter/PinMessageListAdapter.java new file mode 100644 index 00000000..a8bf64ba --- /dev/null +++ b/app/src/main/java/io/agora/chatdemo/chat/adapter/PinMessageListAdapter.java @@ -0,0 +1,44 @@ +package io.agora.chatdemo.chat.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; + +import io.agora.chat.ChatMessage; +import io.agora.chat.uikit.adapter.EaseBaseRecyclerViewAdapter; +import io.agora.chatdemo.R; +import io.agora.chatdemo.chat.viewholder.pinmessage.PinDefaultViewHolder; +import io.agora.chatdemo.chat.viewholder.pinmessage.PinImageMessageViewHolder; +import io.agora.chatdemo.chat.viewholder.pinmessage.PinTextMessageViewHolder; + + +public class PinMessageListAdapter extends EaseBaseRecyclerViewAdapter { + + @Override + public int getItemNotEmptyViewType(int position) { + return mData.get(position).getType().ordinal(); + } + + @Override + public ViewHolder getViewHolder(ViewGroup parent, int viewType) { + ViewHolder viewHolder ; + switch (ChatMessage.Type.values()[viewType]) { + case TXT: + viewHolder=new PinTextMessageViewHolder(mItemSubViewListener,LayoutInflater.from(mContext).inflate(R.layout.pinlist_text, parent, false)); + break; + case IMAGE: + viewHolder=new PinImageMessageViewHolder(mItemSubViewListener,LayoutInflater.from(mContext).inflate(R.layout.pinlist_image, parent, false)); + break; + default: + viewHolder=new PinDefaultViewHolder(mItemSubViewListener,LayoutInflater.from(mContext).inflate(R.layout.pinlist_default, parent, false)); + break; + } + return viewHolder; + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + super.onBindViewHolder(holder, position); + } +} diff --git a/app/src/main/java/io/agora/chatdemo/chat/viewholder/pinmessage/PinDefaultViewHolder.java b/app/src/main/java/io/agora/chatdemo/chat/viewholder/pinmessage/PinDefaultViewHolder.java new file mode 100644 index 00000000..6d55031a --- /dev/null +++ b/app/src/main/java/io/agora/chatdemo/chat/viewholder/pinmessage/PinDefaultViewHolder.java @@ -0,0 +1,68 @@ +package io.agora.chatdemo.chat.viewholder.pinmessage; + + +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +import io.agora.chat.ChatMessage; +import io.agora.chat.FileMessageBody; +import io.agora.chat.uikit.adapter.EaseBaseRecyclerViewAdapter; +import io.agora.chatdemo.R; + +public class PinDefaultViewHolder extends EaseBaseRecyclerViewAdapter.ViewHolder { + private final EaseBaseRecyclerViewAdapter.OnItemSubViewClickListener mItemSubViewListener; + private TextView from; + private TextView content; + private TextView time; + private ImageView state; + public PinDefaultViewHolder(EaseBaseRecyclerViewAdapter.OnItemSubViewClickListener mItemSubViewListener, @NonNull View itemView) { + super(itemView); + this.mItemSubViewListener = mItemSubViewListener; + } + + @Override + public void initView(View itemView) { + super.initView(itemView); + from = findViewById(R.id.tv_from); + content = findViewById(R.id.tv_content); + time = findViewById(R.id.tv_time); + state=findViewById(R.id.iv_state); + } + + @Override + public void setData(ChatMessage message, int position) { + String operatorId = message.pinnedInfo().operatorId(); + long pinTime = message.pinnedInfo().pinTime(); + + from.setText(operatorId +" pinned "+message.getFrom()+"'s message"); + if(message.getType()== ChatMessage.Type.FILE) { + FileMessageBody body= (FileMessageBody) message.getBody(); + content.setText("[File]"+body.displayName()); + }else if(message.getType()== ChatMessage.Type.CUSTOM) { + content.setText("[Custom]"); + }else{ + content.setText("["+message.getType().name()+"]"); + } + + SimpleDateFormat sdf = new SimpleDateFormat("MM-dd, HH:mm"); + sdf.setTimeZone(TimeZone.getTimeZone("GMT+8")); + String formattedDate = sdf.format(new Date(pinTime)); + time.setText(formattedDate); + + state.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if(mItemSubViewListener!=null) { + mItemSubViewListener.onItemSubViewClick(v, position); + } + } + }); + } +} diff --git a/app/src/main/java/io/agora/chatdemo/chat/viewholder/pinmessage/PinImageMessageViewHolder.java b/app/src/main/java/io/agora/chatdemo/chat/viewholder/pinmessage/PinImageMessageViewHolder.java new file mode 100644 index 00000000..9dbcb0fb --- /dev/null +++ b/app/src/main/java/io/agora/chatdemo/chat/viewholder/pinmessage/PinImageMessageViewHolder.java @@ -0,0 +1,65 @@ +package io.agora.chatdemo.chat.viewholder.pinmessage; + + +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import com.bumptech.glide.Glide; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +import io.agora.chat.ChatMessage; +import io.agora.chat.ImageMessageBody; +import io.agora.chat.uikit.adapter.EaseBaseRecyclerViewAdapter; +import io.agora.chatdemo.R; + +public class PinImageMessageViewHolder extends EaseBaseRecyclerViewAdapter.ViewHolder { + private final EaseBaseRecyclerViewAdapter.OnItemSubViewClickListener mItemSubViewListener; + private TextView from; + private ImageView content; + private TextView time; + private ImageView state; + public PinImageMessageViewHolder(EaseBaseRecyclerViewAdapter.OnItemSubViewClickListener mItemSubViewListener, @NonNull View itemView) { + super(itemView); + this.mItemSubViewListener = mItemSubViewListener; + } + + @Override + public void initView(View itemView) { + super.initView(itemView); + from = findViewById(R.id.tv_from); + content = findViewById(R.id.iv_content); + time = findViewById(R.id.tv_time); + state=findViewById(R.id.iv_state); + } + + @Override + public void setData(ChatMessage message, int position) { + String operatorId = message.pinnedInfo().operatorId(); + long pinTime = message.pinnedInfo().pinTime(); + + from.setText(operatorId +" pinned "+message.getFrom()+"'s message"); + + ImageMessageBody body = (ImageMessageBody) message.getBody(); + Glide.with(itemView.getContext()).load(body.getRemoteUrl()).into(content); + + SimpleDateFormat sdf = new SimpleDateFormat("MM-dd, HH:mm"); + sdf.setTimeZone(TimeZone.getTimeZone("GMT+8")); + String formattedDate = sdf.format(new Date(pinTime)); + time.setText(formattedDate); + + state.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if(mItemSubViewListener!=null) { + mItemSubViewListener.onItemSubViewClick(v, position); + } + } + }); + } +} diff --git a/app/src/main/java/io/agora/chatdemo/chat/viewholder/pinmessage/PinTextMessageViewHolder.java b/app/src/main/java/io/agora/chatdemo/chat/viewholder/pinmessage/PinTextMessageViewHolder.java new file mode 100644 index 00000000..c713744f --- /dev/null +++ b/app/src/main/java/io/agora/chatdemo/chat/viewholder/pinmessage/PinTextMessageViewHolder.java @@ -0,0 +1,68 @@ +package io.agora.chatdemo.chat.viewholder.pinmessage; + + +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +import io.agora.chat.ChatMessage; +import io.agora.chat.TextMessageBody; +import io.agora.chat.uikit.adapter.EaseBaseRecyclerViewAdapter; +import io.agora.chatdemo.R; + +public class PinTextMessageViewHolder extends EaseBaseRecyclerViewAdapter.ViewHolder { + + private final EaseBaseRecyclerViewAdapter.OnItemSubViewClickListener mItemSubViewListener; + private TextView from; + private TextView content; + private TextView time; + private ImageView state; + + public PinTextMessageViewHolder(EaseBaseRecyclerViewAdapter.OnItemSubViewClickListener mItemSubViewListener, @NonNull View itemView) { + super(itemView); + this.mItemSubViewListener = mItemSubViewListener; + } + + @Override + public void initView(View itemView) { + super.initView(itemView); + from = findViewById(R.id.tv_from); + content = findViewById(R.id.tv_content); + time = findViewById(R.id.tv_time); + state=findViewById(R.id.iv_state); + + } + + @Override + public void setData(ChatMessage message, int position) { + String operatorId = message.pinnedInfo().operatorId(); + long pinTime = message.pinnedInfo().pinTime(); + + from.setText(operatorId +" pinned "+message.getFrom()+"'s message"); + + TextMessageBody body = (TextMessageBody) message.getBody(); + content.setText(body.getMessage()); + + SimpleDateFormat sdf = new SimpleDateFormat("MM-dd, HH:mm"); + sdf.setTimeZone(TimeZone.getTimeZone("GMT+8")); + String formattedDate = sdf.format(new Date(pinTime)); + time.setText(formattedDate); + + state.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if(mItemSubViewListener!=null) { + mItemSubViewListener.onItemSubViewClick(v, position); + } + } + }); + + + } +} diff --git a/app/src/main/java/io/agora/chatdemo/chat/viewmodel/ChatViewModel.java b/app/src/main/java/io/agora/chatdemo/chat/viewmodel/ChatViewModel.java index 7311714d..8684ec3f 100644 --- a/app/src/main/java/io/agora/chatdemo/chat/viewmodel/ChatViewModel.java +++ b/app/src/main/java/io/agora/chatdemo/chat/viewmodel/ChatViewModel.java @@ -29,9 +29,11 @@ public class ChatViewModel extends AndroidViewModel { private SingleSourceLiveData> makeConversationReadObservable; private SingleSourceLiveData>> getNoPushUsersObservable; private SingleSourceLiveData> setNoPushUsersObservable; - private SingleSourceLiveData> chatManagerObservable; + private SingleSourceLiveData> reportMessageObservable; private SingleSourceLiveData> removeMessagesObservable; private SingleSourceLiveData> translationMessagesObservable; + private SingleSourceLiveData> pinMessageObservable; + private SingleSourceLiveData>> getPinMessageObservable; public ChatViewModel(@NonNull Application application) { super(application); @@ -43,9 +45,11 @@ public ChatViewModel(@NonNull Application application) { getNoPushUsersObservable = new SingleSourceLiveData<>(); setNoPushUsersObservable = new SingleSourceLiveData<>(); presenceObservable = new SingleSourceLiveData<>(); - chatManagerObservable = new SingleSourceLiveData<>(); + reportMessageObservable = new SingleSourceLiveData<>(); removeMessagesObservable = new SingleSourceLiveData<>(); translationMessagesObservable = new SingleSourceLiveData<>(); + pinMessageObservable = new SingleSourceLiveData<>(); + getPinMessageObservable = new SingleSourceLiveData<>(); } public LiveData>> getPresenceObservable(){ return presenceObservable; @@ -56,8 +60,8 @@ public void fetchPresenceStatus(List userIds){ public LiveData> getChatRoomObservable() { return chatRoomObservable; } - public LiveData> getChatManagerObservable(){ - return chatManagerObservable; + public LiveData> getReportMessageObservable(){ + return reportMessageObservable; } public LiveData>> getNoPushUsersObservable() { return getNoPushUsersObservable; @@ -104,7 +108,7 @@ public LiveData> getMakeConversationReadObservable() { } public void reportMessage(String reportMsgId, String reportType, String reportReason ){ - chatManagerObservable.setSource(chatManagerRepository.reportMessage(reportMsgId,reportType,reportReason)); + reportMessageObservable.setSource(chatManagerRepository.reportMessage(reportMsgId,reportType,reportReason)); } public LiveData> getRemoveMessagesObservable() { @@ -129,4 +133,19 @@ public void removeMessagesFromServer(String conversationId, Conversation.Convers public void translationMessage(ChatMessage message,List targetLanguage){ translationMessagesObservable.setSource(chatManagerRepository.translationMessage(message,targetLanguage)); } + + public void pinMessage(ChatMessage message,boolean isPinned) { + pinMessageObservable.setSource(chatManagerRepository.pinMessage(message,isPinned)); + } + + public LiveData> pinMessageObservable(){ + return pinMessageObservable; + } + + public void getPinnedMessagesFromServer(String conversationId) { + getPinMessageObservable.setSource(chatManagerRepository.getPinnedMessagesFromServer(conversationId)); + } + public LiveData>> getPinMessageObservable(){ + return getPinMessageObservable; + } } diff --git a/app/src/main/java/io/agora/chatdemo/conversation/ConversationListFragment.java b/app/src/main/java/io/agora/chatdemo/conversation/ConversationListFragment.java index 3cfaa511..a72238f1 100644 --- a/app/src/main/java/io/agora/chatdemo/conversation/ConversationListFragment.java +++ b/app/src/main/java/io/agora/chatdemo/conversation/ConversationListFragment.java @@ -114,7 +114,6 @@ public void afterTextChanged(Editable s) { presenceView = titleBarLayout.findViewById(R.id.presence_view); if(presenceView != null) { presenceView.setVisibility(View.VISIBLE); - presenceView.setPresenceTextViewArrowVisible(true); presenceView.setNameTextViewVisibility(View.INVISIBLE); RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) presenceView.getLayoutParams(); params.setMargins(UIUtils.dp2px(mContext, 16), 0, 0, 0); diff --git a/app/src/main/java/io/agora/chatdemo/general/dialog/SimpleDialog.java b/app/src/main/java/io/agora/chatdemo/general/dialog/SimpleDialog.java index 6f1d523d..98b7029c 100644 --- a/app/src/main/java/io/agora/chatdemo/general/dialog/SimpleDialog.java +++ b/app/src/main/java/io/agora/chatdemo/general/dialog/SimpleDialog.java @@ -19,9 +19,9 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; -import androidx.appcompat.app.AppCompatActivity; import androidx.constraintlayout.widget.Group; import androidx.core.content.ContextCompat; +import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentTransaction; import java.lang.reflect.Field; @@ -256,12 +256,12 @@ public T getViewById(int viewId) { } public static class Builder { - public AppCompatActivity context; + public FragmentActivity context; private OnConfirmClickListener listener; private onCancelClickListener cancelClickListener; protected Bundle bundle; - public Builder(AppCompatActivity context) { + public Builder(FragmentActivity context) { this.context = context; this.bundle = new Bundle(); } diff --git a/app/src/main/java/io/agora/chatdemo/general/manager/UsersManager.java b/app/src/main/java/io/agora/chatdemo/general/manager/UsersManager.java index 78583b23..394fc92c 100644 --- a/app/src/main/java/io/agora/chatdemo/general/manager/UsersManager.java +++ b/app/src/main/java/io/agora/chatdemo/general/manager/UsersManager.java @@ -281,6 +281,7 @@ public void updateUserPresenceView(String username,EasePresenceView presenceView Presence presence = DemoHelper.getInstance().getPresences().get(username); if(presence!=null && presenceView != null) { presenceView.setVisibility(View.VISIBLE); + presenceView.setPresenceTextViewArrowVisible(true); presenceView.setPresenceData(getUserInfo(username).getAvatar(),presence); } } diff --git a/app/src/main/java/io/agora/chatdemo/general/repositories/EMChatManagerRepository.java b/app/src/main/java/io/agora/chatdemo/general/repositories/EMChatManagerRepository.java index 02ef2fd1..92e984ae 100644 --- a/app/src/main/java/io/agora/chatdemo/general/repositories/EMChatManagerRepository.java +++ b/app/src/main/java/io/agora/chatdemo/general/repositories/EMChatManagerRepository.java @@ -277,4 +277,56 @@ public void onError(int error, String errorMsg) { }.asLiveData(); } + public LiveData> pinMessage(@NonNull ChatMessage message,boolean isPined) { + return new NetworkOnlyResource() { + @Override + protected void createCall(@NonNull ResultCallBack> callBack) { + if(isPined) { + getChatManager().asyncPinMessage(message.getMsgId(), new CallBack() { + @Override + public void onSuccess() { + callBack.onSuccess(createLiveData(message)); + } + + @Override + public void onError(int code, String error) { + callBack.onError(code, error); + } + }); + }else{ + getChatManager().asyncUnPinMessage(message.getMsgId(), new CallBack() { + @Override + public void onSuccess() { + callBack.onSuccess(createLiveData(message)); + } + + @Override + public void onError(int code, String error) { + callBack.onError(code, error); + } + }); + } + + } + }.asLiveData(); + } + + public LiveData>> getPinnedMessagesFromServer(String conversationId) { + return new NetworkOnlyResource>() { + @Override + protected void createCall(@NonNull ResultCallBack>> callBack) { + getChatManager().asyncGetPinnedMessagesFromServer(conversationId, new ValueCallBack>() { + @Override + public void onSuccess(List value) { + callBack.onSuccess(createLiveData(value)); + } + + @Override + public void onError(int error, String errorMsg) { + callBack.onError(error,errorMsg); + } + }); + } + }.asLiveData(); + } } diff --git a/app/src/main/java/io/agora/chatdemo/general/repositories/EMClientRepository.java b/app/src/main/java/io/agora/chatdemo/general/repositories/EMClientRepository.java index d5c0cd9e..7448f6e3 100644 --- a/app/src/main/java/io/agora/chatdemo/general/repositories/EMClientRepository.java +++ b/app/src/main/java/io/agora/chatdemo/general/repositories/EMClientRepository.java @@ -41,7 +41,6 @@ import io.agora.chatdemo.general.net.ErrorCode; import io.agora.chatdemo.general.net.Resource; import io.agora.chatdemo.general.utils.CommonUtils; -import io.agora.chatdemo.sign.SignInActivity; import io.agora.cloud.HttpClientManager; import io.agora.cloud.HttpResponse; import io.agora.exceptions.ChatException; @@ -408,7 +407,8 @@ protected void createCall(@NonNull ResultCallBack> callBack) { ChatClient.getInstance().login(username, pwd, new CallBack() { @Override public void onSuccess() { - success(username, callBack); + DemoHelper.getInstance().getUsersManager().setCurrentUser(username); + success(pwd, callBack); } @Override diff --git a/app/src/main/java/io/agora/chatdemo/general/widget/PinInfoView.java b/app/src/main/java/io/agora/chatdemo/general/widget/PinInfoView.java new file mode 100644 index 00000000..7818670c --- /dev/null +++ b/app/src/main/java/io/agora/chatdemo/general/widget/PinInfoView.java @@ -0,0 +1,118 @@ +package io.agora.chatdemo.general.widget; + + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.RelativeLayout; + +import com.google.android.gms.common.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; + +import io.agora.chat.ChatMessage; +import io.agora.chat.uikit.adapter.EaseBaseRecyclerViewAdapter; +import io.agora.chatdemo.R; +import io.agora.util.EMLog; + +public class PinInfoView extends RelativeLayout { + + private List pinMessages=new ArrayList<>(); + private View primaryView; + private PinMessageListViewGroup pinMessageListView; + + public PinInfoView(Context context) { + super(context); + init(); + } + + public PinInfoView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + private void init() { + primaryView = LayoutInflater.from(getContext()).inflate(R.layout.pin_info_view, this, false); + addView(primaryView); + findViewById(R.id.tv_info2).setOnClickListener(v->{ + showPinListView(); + }); + + pinMessageListView = new PinMessageListViewGroup(getContext()); + RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + pinMessageListView.setVisibility(View.GONE); + addView(pinMessageListView, layoutParams); + } + + private void showPinListView() { + primaryView.setVisibility(GONE); + pinMessageListView.show(pinMessages); + } + private void showPrimaryView() { + setVisibility(VISIBLE); + primaryView.setVisibility(VISIBLE); + pinMessageListView.setVisibility(GONE); + } + + public void setData(List messages) { + pinMessages.clear(); + if(!CollectionUtils.isEmpty(messages)) { + pinMessages.addAll(0,messages); + } + setVisibility(VISIBLE); + } + + public void removeData(ChatMessage message) { + if(message!=null) { + for (int i = 0; i < pinMessages.size(); i++) { + if(pinMessages.get(i).getMsgId().equals(message.getMsgId())) { + pinMessages.remove(message); + break; + } + } + pinMessageListView.removeData(message); + if(pinMessages.isEmpty()) { + setVisibility(GONE); + } + } + } + + public void addData(ChatMessage message) { + if(message!=null) { + pinMessages.add(0,message); + } + showPrimaryView(); + } + + public void restView() { + primaryView.setVisibility(VISIBLE); + pinMessageListView.setVisibility(GONE); + } + + public List getPinMessages() { + return pinMessages; + } + + public void setOnItemClickListener(PinMessageListViewGroup.OnItemClickListener listener) { + pinMessageListView.setOnItemClickListener(listener); + } + + public void setOnItemSubViewClickListener(EaseBaseRecyclerViewAdapter.OnItemSubViewClickListener onItemSubViewClickListener) { + pinMessageListView.setOnItemSubViewClickListener(onItemSubViewClickListener); + } + + public void setInnerLayoutMaxHeight(int height) { + if(pinMessageListView!=null) { + EMLog.d("PinInfoView", "setInnerLayoutMaxHeight: " + height); + pinMessageListView.setConstraintLayoutMaxHeight(height); + } + } +} + + + diff --git a/app/src/main/java/io/agora/chatdemo/general/widget/PinMessageListViewGroup.java b/app/src/main/java/io/agora/chatdemo/general/widget/PinMessageListViewGroup.java new file mode 100644 index 00000000..779de9e4 --- /dev/null +++ b/app/src/main/java/io/agora/chatdemo/general/widget/PinMessageListViewGroup.java @@ -0,0 +1,164 @@ +package io.agora.chatdemo.general.widget; + +import android.content.Context; +import android.graphics.Color; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.constraintlayout.widget.ConstraintSet; +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.google.android.gms.common.util.CollectionUtils; + +import java.util.List; + +import io.agora.chat.ChatMessage; +import io.agora.chat.uikit.adapter.EaseBaseRecyclerViewAdapter; +import io.agora.chat.uikit.widget.EaseRecyclerView; +import io.agora.chatdemo.R; +import io.agora.chatdemo.chat.PinListItemSpaceDecoration; +import io.agora.chatdemo.chat.adapter.PinMessageListAdapter; + +public class PinMessageListViewGroup extends LinearLayout { + + private ConstraintLayout constraintLayout; + private EaseRecyclerView recyclerView; + private PinMessageListAdapter adapter; + private OnItemClickListener itemClickListener; + private TextView tvCount; + private View clBottom; + private EaseBaseRecyclerViewAdapter.OnItemSubViewClickListener itemSubViewClickListener; + + public PinMessageListViewGroup(Context context) { + super(context); + init(); + } + + public PinMessageListViewGroup(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + private void init() { + setClickable(true); + setBackgroundColor(Color.parseColor("#80000000")); + + constraintLayout = (ConstraintLayout) LayoutInflater.from(getContext()).inflate(R.layout.pin_message_list_view_group, this, false); + addView(constraintLayout); + + tvCount = findViewById(R.id.tv_count); + recyclerView = findViewById(R.id.rv_list); + clBottom = findViewById(R.id.cl_bottom); + + + adapter = new PinMessageListAdapter(); + + + int space = dpToPx(8); + PinListItemSpaceDecoration itemDecoration = new PinListItemSpaceDecoration(space); + recyclerView.addItemDecoration(itemDecoration); + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + + adapter.setOnItemClickListener(new io.agora.chat.uikit.interfaces.OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + if (itemClickListener != null) { + itemClickListener.onItemClick(adapter.getItem(position)); + } + } + }); + adapter.setOnItemSubViewClickListener(new EaseBaseRecyclerViewAdapter.OnItemSubViewClickListener() { + @Override + public void onItemSubViewClick(View view, int position) { + if (itemSubViewClickListener != null) { + itemSubViewClickListener.onItemSubViewClick(view, position); + } + } + }); + + recyclerView.setAdapter(adapter); + + } + + long startY = 0; + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + startY = (long) event.getY(); + break; + case MotionEvent.ACTION_UP: + if (!recyclerView.canScrollVertically(-1) && startY-event.getY()> 20) { + ((PinInfoView) getParent()).restView(); + return true; + } + if (event.getX() < 0 || event.getX() > getWidth() || + event.getY() < 0 || event.getY() > getHeight() + || event.getY() > clBottom.getTop()) { + ((PinInfoView) getParent()).restView(); + return true; + } + break; + } + return super.dispatchTouchEvent(event); + } + + public void setData(List data) { + tvCount.setText(String.valueOf(data.size()) + " Pin Message"); + adapter.setData(data); + } + + public void setOnItemClickListener(OnItemClickListener listener) { + this.itemClickListener = listener; + } + + public void setOnItemSubViewClickListener(EaseBaseRecyclerViewAdapter.OnItemSubViewClickListener listener) { + this.itemSubViewClickListener = listener; + } + + public void show(List messages) { + setVisibility(VISIBLE); + setData(messages); + } + + public void removeData(ChatMessage message) { + List messageList = adapter.getData(); + if (messageList != null && message != null) { + for (int i = 0; i < messageList.size(); i++) { + if (messageList.get(i).getMsgId().equals(message.getMsgId())) { + messageList.remove(message); + break; + } + } + adapter.notifyDataSetChanged(); + } + if (CollectionUtils.isEmpty(adapter.getData())) { + setVisibility(GONE); + } + } + + public void setConstraintLayoutMaxHeight(int height) { + ConstraintSet constraintSet = new ConstraintSet(); + constraintSet.clone(constraintLayout); + int recyclerViewId = R.id.rv_list; + int rvHeight = height - tvCount.getHeight() - clBottom.getHeight(); + constraintSet.constrainMaxHeight(recyclerViewId, rvHeight); + constraintSet.applyTo(constraintLayout); + } + + public interface OnItemClickListener { + void onItemClick(ChatMessage message); + } + + private int dpToPx(int dp) { + float density = getResources().getDisplayMetrics().density; + return (int) (dp * density + 0.5f); + } +} + diff --git a/app/src/main/res/drawable-xxhdpi/chat_item_menu_pin.png b/app/src/main/res/drawable-xxhdpi/chat_item_menu_pin.png new file mode 100644 index 0000000000000000000000000000000000000000..63482071474c8146c114dad523aa0eafde235024 GIT binary patch literal 760 zcmVFa|p=j-FJ?c!vgeyBTNH^a{!@}GsZ^40p< zk1Ts_jr+kbj=lM{uN}q*`LMd7O^1c~25yi~1zTeQtXH;37v#g*V8*@G;m~vT6IX{B zhH7W)ltcWr+6~7r3l8TEb5@}`oM)KNcKF{SpD>TrqEKt33B-nOgZn+L^7S*KjRu8r zNE2pz(9Wu60 zTweo@;c7&?S+4ZkklasDt7rbcg)$~gD+dzhO}YxxO_qBcrcwRyyWP~QF}|mL_O%7U zJMc_7Y#g}rT?Z28S$@%X9FLfR{z18VkSI_xW}rRF)q{lnC=UJFcmg)E-h+gF>3N6p zP>-1%Xy~$8PN>Js4s?t1Y>~MR(_DfwssmYy75kW#^>h1<0@ztThbKisdZzEcTEte093$hmhGr$WAgRw&dp@(Z&;;acPq=5Ne^eV*p<6y7anv}SwmP-hk9 zsdId$USsBJOf#jji;EQ&!XfSf+1T1(q4&5PxWU?4yn_4-w#K+Amc7P?w(h#Vpu+QC zsI_hNu#ZBF)ZlTT4KkKuwQ125D47B!Q=nuDluUt=DNr&6N~S=`6ewBlfo`AJR^k(W qS!r-sEMC-tv51I>h=_=YOye&{$l-{BxQvMa0000pB*akqtbeWaDyJ9oB(dHa)N=jWz?L z!+9TQ)H`ZyC$%vu-0bvf1fT_s)j*$s|FnC@-f&gWl5C7qB3s?V!(dfABf?@tr}T^&;%T|-TUB;qw5)vjoYZR zuCj4EHN-PJpT@SZCmXkwr|Az|g2QY6s|!}JD=Sb+S_G38tg);>T3C#(g{3D(UAA#D zsDcL4!tv-@KwVZOO(5a5*zkZNQo{B|S5G!xAu4zHXB`xg66T4j2F6Jx8^I9X0Y&7d zw0P?=N_yD{#;U5*T27du8W_Wn?3~Iie-{P?5`~-oscUYpY*aP^Gip{CoDCO@U|v?h zcYQ7>kSH7iGBCP!WFs7S1qvhz^C^>3*rKcksS5zBd0w)DL}3S^0JO+RmcS2{@Z=FD zvjCLIZe$TKr4nzVaMu?%gcLf>b{bQRM=J5MJFT%)Tn^7{nHTa^;-!QUrQal?vQJRF z_^x_tAtlV0rs3*{oU$=vm!OE0aP%CbKjqS8@>_cNNy7*fkzR)xKmPdp+Z@+f3`3n# zkF;=K9C9s<1<+}7ce%6Ed@kegkMag5) zC!0eGvX(Vbrqw@NT0_~mA9dDMR-pWPn8!0~N>;F;5}ftY!+^H?N8SY(L|RxzRut?h z3+s>yF3OjkGP}F*eP8>(d!FoFCP?*Q>usmMizwMeQK4M2;>o8jV{lcb)1eK~#buHL z_IaVZgv0NT8c8RS#>sh=QtV<%YrNr`2c3+0#qm;nmdSK%Q}Q9o!nzq#)VB$CZWdqg zy>RcNw+9)Mn58fH`zB99Z9e73rZZ+9NF}X)>%GrEeE1KrT&8_~6vDQ+hs{i~a1xmb sFOpw`{tNut!Pi?uN3R711qCmFU*wjrbM6~;C;$Ke07*qoM6N<$f?0;m`2YX_ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/chat_pin_rectangle.png b/app/src/main/res/drawable-xxhdpi/chat_pin_rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..74b1e28373bb69b192702c193310f5351a3c05aa GIT binary patch literal 373 zcmV-*0gC>KP){X%aLX9^ppO;IS%{z`^>S<$-uL7C{~vC{eu>BpOjg86J@>O_xOdZo zkFZ}`V0mI>o$|q?(%M~k637co)`WZw^Xav|-s!NJZux%!khxb* Tioq2a00000NkvXXu0mjf@adY0 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/chat_pininfo_icon.png b/app/src/main/res/drawable-xxhdpi/chat_pininfo_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9d966f0fd93005369b4a3e9f6203695409ffd91b GIT binary patch literal 554 zcmV+_0@eMAP)%gKW@lKsM+IWdrC0utC`XIss*ZumN-eYKtIf1QyRE&k6OBt_X_b zNt8&MCO(H3{}-95{7ysRh6qdIC7!s-lcDsBn)B9t(N8Si>IEI z)ia7+gG=S1k;qTfH}I4nqJ$yb)WB2k#B-fNfidC6jHlj=uoqGr1>weg82pJu#62=H zuKO%Z;JF^cp_kOywH@c1=Uks=@9kwm_-DA+d!a%>Yc=FZBV!uFrp*7EvMKouI>#@QApt-Lnl(>e*U&Pi szPhDl?CZ?&O<-ujrgNJ0Yn?}ZpK81R{#J207*qoM6N<$f>FKlvj6}9 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/shape_gray_ebebeb_corner_8.xml b/app/src/main/res/drawable/shape_gray_ebebeb_corner_8.xml new file mode 100644 index 00000000..7435f4ae --- /dev/null +++ b/app/src/main/res/drawable/shape_gray_ebebeb_corner_8.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_gray_f5f5f5_corner_8.xml b/app/src/main/res/drawable/shape_gray_f5f5f5_corner_8.xml new file mode 100644 index 00000000..51bef490 --- /dev/null +++ b/app/src/main/res/drawable/shape_gray_f5f5f5_corner_8.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/pin_info_view.xml b/app/src/main/res/layout/pin_info_view.xml new file mode 100644 index 00000000..c4a475be --- /dev/null +++ b/app/src/main/res/layout/pin_info_view.xml @@ -0,0 +1,40 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/pin_message_list_view_group.xml b/app/src/main/res/layout/pin_message_list_view_group.xml new file mode 100644 index 00000000..89f66640 --- /dev/null +++ b/app/src/main/res/layout/pin_message_list_view_group.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/pinlist_default.xml b/app/src/main/res/layout/pinlist_default.xml new file mode 100644 index 00000000..313a185b --- /dev/null +++ b/app/src/main/res/layout/pinlist_default.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/pinlist_image.xml b/app/src/main/res/layout/pinlist_image.xml new file mode 100644 index 00000000..0ad95f3d --- /dev/null +++ b/app/src/main/res/layout/pinlist_image.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/pinlist_text.xml b/app/src/main/res/layout/pinlist_text.xml new file mode 100644 index 00000000..0d0eac24 --- /dev/null +++ b/app/src/main/res/layout/pinlist_text.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 128d8f9a..379132dd 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -70,6 +70,8 @@ #979797 #999999 #E6E6E6 + #F5F5F5 + #EbEbEb #EBF2FF diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml index 8bf1a06a..f376d544 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -15,4 +15,5 @@ + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 17ad949e..92d2a273 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -403,6 +403,8 @@ %d/10 0/10 Report + Pin + UnPin Delete %1$d messages My Alias in Group @@ -444,5 +446,8 @@ Translation failed Retry Translated by Agora Chat View Original Text Translated by Agora Chat View Translation + Pin Message + Confirm to remove pinned message? + The quoted message does not exist \ No newline at end of file From beb78a0a72b976b043de301cd1386c108f7327d0 Mon Sep 17 00:00:00 2001 From: xuchengpu mac <1550540124@qq.com> Date: Sun, 7 Apr 2024 17:27:15 +0800 Subject: [PATCH 09/11] update version to 1.3.0 --- app/build.gradle | 1 - settings.gradle | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 4da01947..9b13c96f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -48,7 +48,6 @@ android { // // externalNativeBuild { // ndkBuild { -//// arguments "NDK_LIBS_OUT=libs", "all" // abiFilters "arm64-v8a","armeabi-v7a" // arguments '-j8' // } diff --git a/settings.gradle b/settings.gradle index 3d4a09ce..09cf7fd3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,4 +4,6 @@ include ':app' //include ':chat-callkit' //project(':chat-callkit').projectDir = new File('../AgoraChat-CallKit-android/chat-callkit') //include ':hyphenatechatsdk' -//project(':hyphenatechatsdk').projectDir = new File('../emclient-android/hyphenatechatsdk') \ No newline at end of file +//project(':hyphenatechatsdk').projectDir = new File('../emclient-android/hyphenatechatsdk') +//include ':ease-linux' +//project(':ease-linux').projectDir = new File('../emclient-linux') \ No newline at end of file From 35e4bf32ce8632f540f2d21cadab1f60b374a62c Mon Sep 17 00:00:00 2001 From: apex-wang <1746807718@qq.com> Date: Thu, 24 Oct 2024 18:34:12 +0800 Subject: [PATCH 10/11] add app-kotlin --- README.md | 55 ++- app-kotlin/.gitignore | 2 + app-kotlin/README.md | 292 +++++++++++++ app-kotlin/build.gradle.kts | 160 +++++++ app-kotlin/google-services.json | 39 ++ app-kotlin/jni/Android.mk | 24 + app-kotlin/jni/Application.mk | 6 + app-kotlin/keystore/sdkdemo.jks | Bin 0 -> 4999 bytes app-kotlin/proguard-rules.pro | 21 + app-kotlin/src/main/AndroidManifest.xml | 154 +++++++ .../io/agora/chatdemo/DemoApplication.kt | 57 +++ .../kotlin/io/agora/chatdemo/DemoHelper.kt | 156 +++++++ .../kotlin/io/agora/chatdemo/MainActivity.kt | 323 ++++++++++++++ .../io/agora/chatdemo/base/ActivityState.kt | 32 ++ .../agora/chatdemo/base/BaseDialogFragment.kt | 131 ++++++ .../agora/chatdemo/base/BaseInitActivity.kt | 38 ++ .../io/agora/chatdemo/base/BaseRepository.kt | 10 + .../base/UserActivityLifecycleCallbacks.kt | 104 +++++ .../kotlin/io/agora/chatdemo/bean/Language.kt | 26 ++ .../io/agora/chatdemo/bean/LoginResult.kt | 11 + .../io/agora/chatdemo/bean/PresenceData.kt | 34 ++ .../CallKitActivityLifecycleCallback.kt | 109 +++++ .../agora/chatdemo/callkit/CallKitManager.kt | 303 +++++++++++++ .../io/agora/chatdemo/callkit/CallUserInfo.kt | 40 ++ .../callkit/ChatVoiceCallViewHolder.kt | 48 ++ .../chatdemo/callkit/DemoCallKitListener.kt | 182 ++++++++ .../callkit/MultipleInviteViewHolder.kt | 6 + .../activity/CallMultipleBaseActivity.kt | 14 + .../activity/CallMultipleInviteActivity.kt | 131 ++++++ .../activity/CallSingleBaseActivity.kt | 24 + .../adapter/ConferenceInviteAdapter.kt | 44 ++ .../chatdemo/callkit/extensions/Activity.kt | 88 ++++ .../chatdemo/callkit/extensions/JSONObject.kt | 14 + .../fragment/ConferenceInviteFragment.kt | 81 ++++ .../ConferenceMemberSelectViewHolder.kt | 76 ++++ .../callkit/views/ChatRowConferenceInvite.kt | 39 ++ .../callkit/views/ChatRowVoiceCall.kt | 42 ++ .../io/agora/chatdemo/common/DemoConstant.kt | 26 ++ .../io/agora/chatdemo/common/DemoDataModel.kt | 330 ++++++++++++++ .../io/agora/chatdemo/common/ErrorCode.kt | 66 +++ .../agora/chatdemo/common/ListenersWrapper.kt | 148 +++++++ .../chatdemo/common/PreferenceManager.kt | 55 +++ .../io/agora/chatdemo/common/PresenceCache.kt | 31 ++ .../common/PushActivityLifecycleCallback.kt | 39 ++ .../io/agora/chatdemo/common/PushManager.kt | 86 ++++ .../chatdemo/common/dialog/SimpleDialog.kt | 269 ++++++++++++ .../fragment/DemoAgreementDialogFragment.kt | 96 ++++ .../dialog/fragment/DemoDialogFragment.kt | 382 ++++++++++++++++ .../chatdemo/common/extensions/String.kt | 32 ++ .../common/extensions/internal/Activity.kt | 10 + .../common/extensions/internal/ChatGroup.kt | 8 + .../common/extensions/internal/ChatOptions.kt | 27 ++ .../extensions/internal/ChatUserInfo.kt | 12 + .../common/extensions/internal/EditText.kt | 101 +++++ .../common/extensions/internal/SwitchView.kt | 9 + .../common/helper/DeveloperModeHelper.kt | 22 + .../common/helper/LocalNotifyHelper.kt | 50 +++ .../common/helper/MenuFilterHelper.kt | 27 ++ .../agora/chatdemo/common/room/AppDatabase.kt | 42 ++ .../chatdemo/common/room/dao/DemoUserDao.kt | 96 ++++ .../chatdemo/common/room/entity/DemoUser.kt | 21 + .../common/room/extensions/DbEntity.kt | 15 + .../common/room/viewmodel/BusUserViewModel.kt | 130 ++++++ .../suspend/ChatUserInfoManagerSuspend.kt | 64 +++ .../common/suspend/PresenceManagerSuspend.kt | 118 +++++ .../common/suspend/PushManagerSuspend.kt | 45 ++ .../io/agora/chatdemo/fcm/FCMMSGService.kt | 26 ++ .../presence/controller/PresenceController.kt | 135 ++++++ .../presence/interfaces/IPresenceRequest.kt | 38 ++ .../interfaces/IPresenceResultView.kt | 61 +++ .../repository/ChatPresenceRepository.kt | 39 ++ .../presence/utils/EasePresenceUtil.kt | 101 +++++ .../presence/viewmodel/PresenceViewModel.kt | 96 ++++ .../agora/chatdemo/interfaces/IAttachView.kt | 10 + .../agora/chatdemo/interfaces/IMainRequest.kt | 15 + .../chatdemo/interfaces/IMainResultView.kt | 16 + .../LanguageListItemSelectListener.kt | 8 + .../agora/chatdemo/page/chat/ChatActivity.kt | 48 ++ .../agora/chatdemo/page/chat/ChatFragment.kt | 130 ++++++ .../page/chat/CustomMessagesAdapter.kt | 52 +++ .../page/contact/ChatContactCheckActivity.kt | 59 +++ .../page/contact/ChatContactDetailActivity.kt | 156 +++++++ .../contact/ChatContactListFragmentEvent.kt | 121 ++++++ .../page/contact/ChatNewRequestsActivity.kt | 23 + .../conversation/ConversationListFragment.kt | 136 ++++++ .../page/group/ChatCreateGroupActivity.kt | 32 ++ .../page/group/ChatGroupDetailActivity.kt | 61 +++ .../page/group/repository/GroupRepository.kt | 32 ++ .../chatdemo/page/login/LoginActivity.kt | 61 +++ .../page/login/fragment/LoginFragment.kt | 281 ++++++++++++ .../page/login/fragment/ServerSetFragment.kt | 221 ++++++++++ .../page/login/viewModel/LoginViewModel.kt | 46 ++ .../page/me/CameraAndCroppingController.kt | 145 +++++++ .../page/me/activity/AboutActivity.kt | 65 +++ .../page/me/activity/CurrencyActivity.kt | 141 ++++++ .../me/activity/EditUserNicknameActivity.kt | 101 +++++ .../page/me/activity/FeaturesActivity.kt | 89 ++++ .../me/activity/LanguageSettingActivity.kt | 196 +++++++++ .../page/me/activity/NotifyActivity.kt | 104 +++++ .../me/activity/UserInformationActivity.kt | 409 ++++++++++++++++++ .../page/me/activity/WebViewActivity.kt | 77 ++++ .../page/me/fragment/AboutMeFragment.kt | 261 +++++++++++ .../chatdemo/page/splash/SplashActivity.kt | 106 +++++ .../splash/repository/ChatClientRepository.kt | 263 +++++++++++ .../page/splash/viewModel/SplashViewModel.kt | 15 + .../repository/ProfileInfoRepository.kt | 245 +++++++++++ .../chatdemo/repository/PushRepository.kt | 30 ++ .../io/agora/chatdemo/uikit/UIKitManager.kt | 148 +++++++ .../chatdemo/utils/CameraAndCropFileUtils.kt | 90 ++++ .../io/agora/chatdemo/utils/LanguageUtil.kt | 12 + .../io/agora/chatdemo/utils/ToastUtils.kt | 401 +++++++++++++++++ .../agora/chatdemo/viewmodel/MainViewModel.kt | 59 +++ .../viewmodel/ProfileInfoViewModel.kt | 65 +++ .../agora/chatdemo/viewmodel/PushViewModel.kt | 27 ++ .../src/main/res/anim/slide_in_from_left.xml | 14 + .../src/main/res/anim/slide_in_from_right.xml | 14 + .../src/main/res/anim/slide_out_to_left.xml | 14 + .../src/main/res/anim/slide_out_to_right.xml | 14 + .../demo_dialog_btn_text_color_selector.xml | 5 + .../res/color/demo_main_tab_text_selector.xml | 5 + .../main/res/drawable-hdpi/contact_title.png | Bin 0 -> 1920 bytes .../res/drawable-hdpi/conversation_title.png | Bin 0 -> 1091 bytes .../drawable-v24/ic_launcher_foreground.xml | 30 ++ .../main/res/drawable-xhdpi/contact_title.png | Bin 0 -> 2491 bytes .../res/drawable-xhdpi/conversation_title.png | Bin 0 -> 1350 bytes .../res/drawable-xhdpi/d_chat_voice_call.png | Bin 0 -> 554 bytes .../main/res/drawable-xhdpi/em_toast_fail.png | Bin 0 -> 1083 bytes .../res/drawable-xhdpi/em_toast_success.png | Bin 0 -> 1023 bytes .../res/drawable-xhdpi/splash_bg_dark.webp | Bin 0 -> 8410 bytes .../res/drawable-xhdpi/splash_bg_light.webp | Bin 0 -> 17812 bytes .../res/drawable-xxhdpi/contact_title.png | Bin 0 -> 3720 bytes .../drawable-xxhdpi/conversation_title.png | Bin 0 -> 2031 bytes .../res/drawable-xxhdpi/d_chat_voice_call.png | Bin 0 -> 788 bytes .../drawable-xxhdpi/demo_check_checked.png | Bin 0 -> 615 bytes .../drawable-xxhdpi/demo_check_uncheck.png | Bin 0 -> 461 bytes .../drawable-xxhdpi/ease_presence_away.png | Bin 0 -> 2021 bytes .../drawable-xxhdpi/ease_presence_busy.png | Bin 0 -> 1750 bytes .../drawable-xxhdpi/ease_presence_custom.png | Bin 0 -> 7033 bytes .../ease_presence_do_not_disturb.png | Bin 0 -> 2302 bytes .../drawable-xxhdpi/ease_presence_offline.png | Bin 0 -> 1299 bytes .../drawable-xxhdpi/ease_presence_online.png | Bin 0 -> 1361 bytes .../main/res/drawable-xxhdpi/icon_about.png | Bin 0 -> 1398 bytes .../res/drawable-xxhdpi/icon_currency.png | Bin 0 -> 1840 bytes .../res/drawable-xxhdpi/icon_information.png | Bin 0 -> 1600 bytes .../icon_login_attention_line.png | Bin 0 -> 807 bytes .../main/res/drawable-xxhdpi/icon_logo.png | Bin 0 -> 78402 bytes .../main/res/drawable-xxhdpi/icon_next.png | Bin 0 -> 455 bytes .../res/drawable-xxhdpi/icon_presence.png | Bin 0 -> 1953 bytes .../main/res/drawable-xxhdpi/icon_privacy.png | Bin 0 -> 1671 bytes .../drawable-xxhdpi/main_tab_conversation.png | Bin 0 -> 1568 bytes .../main_tab_conversation_selected.png | Bin 0 -> 1304 bytes .../res/drawable-xxhdpi/main_tab_friends.png | Bin 0 -> 1994 bytes .../main_tab_friends_selected.png | Bin 0 -> 1608 bytes .../main/res/drawable-xxhdpi/main_tab_me.png | Bin 0 -> 1284 bytes .../drawable-xxhdpi/main_tab_me_selected.png | Bin 0 -> 1061 bytes .../main/res/drawable-xxhdpi/phone_pick.png | Bin 0 -> 1138 bytes .../res/drawable-xxhdpi/sign_clear_icon.png | Bin 0 -> 1404 bytes .../src/main/res/drawable-xxhdpi/sign_eye.png | Bin 0 -> 1403 bytes .../res/drawable-xxhdpi/sign_eye_slash.png | Bin 0 -> 1499 bytes .../main/res/drawable-xxhdpi/video_call.png | Bin 0 -> 1474 bytes .../main/res/drawable-xxhdpi/video_camera.png | Bin 0 -> 827 bytes .../res/drawable-xxxhdpi/contact_title.png | Bin 0 -> 4919 bytes .../drawable-xxxhdpi/conversation_title.png | Bin 0 -> 2559 bytes .../main/res/drawable-xxxhdpi/icon_notify.png | Bin 0 -> 1476 bytes .../res/drawable/demo_cb_agreement_select.xml | 5 + .../demo_dialog_btn_left_selector.xml | 34 ++ .../demo_dialog_btn_right_selector.xml | 34 ++ .../res/drawable/demo_dialog_btn_selector.xml | 34 ++ .../main/res/drawable/demo_login_btn_bg.xml | 23 + .../main/res/drawable/demo_login_et_bg.xml | 8 + .../res/drawable/demo_login_version_bg.xml | 10 + .../demo_main_tab_conversation_selector.xml | 5 + .../demo_main_tab_friends_selector.xml | 5 + .../drawable/demo_main_tab_me_selector.xml | 5 + .../drawable/demo_main_unread_count_bg.xml | 7 + .../drawable/demo_switch_thumb_selector.xml | 19 + .../drawable/demo_switch_track_selector.xml | 20 + .../src/main/res/drawable/demo_toast_bg.xml | 7 + .../res/drawable/ic_launcher_background.xml | 170 ++++++++ .../main/res/layout/activity_main_layout.xml | 36 ++ .../main/res/layout/demo_activity_about.xml | 119 +++++ .../demo_activity_conference_invite.xml | 22 + .../res/layout/demo_activity_currency.xml | 46 ++ .../res/layout/demo_activity_features.xml | 82 ++++ .../res/layout/demo_activity_language.xml | 26 ++ .../main/res/layout/demo_activity_login.xml | 23 + .../layout/demo_activity_me_information.xml | 121 ++++++ .../demo_activity_me_information_edit.xml | 51 +++ .../main/res/layout/demo_activity_notify.xml | 35 ++ .../main/res/layout/demo_activity_webview.xml | 25 ++ .../src/main/res/layout/demo_badge_home.xml | 16 + .../res/layout/demo_fragment_about_me.xml | 146 +++++++ .../res/layout/demo_fragment_dialog_base.xml | 87 ++++ .../main/res/layout/demo_fragment_login.xml | 122 ++++++ .../layout/demo_fragment_middle_agreement.xml | 28 ++ .../res/layout/demo_fragment_server_set.xml | 101 +++++ .../demo_row_received_conference_invite.xml | 56 +++ .../layout/demo_row_received_voice_call.xml | 70 +++ .../demo_row_sent_conference_invite.xml | 80 ++++ .../res/layout/demo_row_sent_voice_call.xml | 94 ++++ .../main/res/layout/demo_splash_activity.xml | 34 ++ .../src/main/res/layout/demo_toast_layout.xml | 58 +++ .../res/layout/ease_layout_language_item.xml | 41 ++ .../main/res/menu/bottom_main_nav_menu.xml | 21 + .../src/main/res/menu/demo_chat_menu.xml | 15 + .../res/menu/demo_conference_invite_menu.xml | 12 + .../res/menu/demo_language_menu_confirm.xml | 11 + .../main/res/menu/demo_server_set_menu.xml | 11 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 6 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 6 + .../main/res/mipmap-hdpi/demo_launcher.png | Bin 0 -> 4008 bytes .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2898 bytes .../main/res/mipmap-mdpi/demo_launcher.png | Bin 0 -> 2724 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1772 bytes .../main/res/mipmap-xhdpi/demo_launcher.png | Bin 0 -> 6911 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3918 bytes .../main/res/mipmap-xxhdpi/demo_launcher.png | Bin 0 -> 10348 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5914 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7778 bytes .../src/main/res/values-night/colors.xml | 5 + .../src/main/res/values-night/styles.xml | 39 ++ .../src/main/res/values-night/themes.xml | 8 + app-kotlin/src/main/res/values/colors.xml | 28 ++ app-kotlin/src/main/res/values/dimens.xml | 22 + app-kotlin/src/main/res/values/ids.xml | 26 ++ app-kotlin/src/main/res/values/strings.xml | 154 +++++++ app-kotlin/src/main/res/values/styles.xml | 187 ++++++++ app-kotlin/src/main/res/values/themes.xml | 10 + app/build.gradle | 12 +- app/src/main/AndroidManifest.xml | 3 +- .../chatdemo/av/DemoCallKitListener.java | 2 +- .../io/agora/chatdemo/base/BaseActivity.java | 2 +- .../io/agora/chatdemo/base/BaseFragment.java | 4 +- .../io/agora/chatdemo/chat/ChatActivity.java | 8 +- .../io/agora/chatdemo/chat/ChatRowCall.java | 32 +- .../chatdemo/chat/CustomChatFragment.java | 22 +- .../chat/chatrow/ChatRowCustomTextView.java | 8 +- .../chatrow/ChatRowSystemNotification.java | 2 +- .../chatthread/ChatThreadActivity.java | 4 +- .../chatthread/ChatThreadCreateActivity.java | 6 +- .../chatthread/ChatThreadFragment.java | 11 +- .../contact/AddContactOrGroupFragment.java | 2 +- .../contact/SearchContactFragment.java | 2 +- .../ConversationListFragment.java | 4 +- .../general/manager/UsersManager.java | 2 +- .../chatdemo/general/models/PresenceData.java | 12 +- .../general/widget/EasePresenceView.java | 8 +- .../global/BottomSheetChildHelper.java | 2 +- .../group/adapter/HomeHeaderMenuAdapter.java | 2 +- .../fragments/BottomSheetMenuFragment.java | 2 +- .../fragments/GroupAllMembersFragment.java | 4 +- ...ultiplyVideoSelectMemberChildFragment.java | 2 +- .../chatdemo/me/CustomPresenceActivity.java | 4 +- .../java/io/agora/chatdemo/me/MeFragment.java | 4 +- .../chatdemo/me/SetPresenceFragment.java | 6 +- build.gradle | 51 --- build.gradle.kts | 29 ++ gradle.properties | 7 +- gradle/wrapper/gradle-wrapper.properties | 4 +- settings.gradle | 9 - settings.gradle.kts | 37 ++ 266 files changed, 13027 insertions(+), 170 deletions(-) create mode 100644 app-kotlin/.gitignore create mode 100644 app-kotlin/README.md create mode 100644 app-kotlin/build.gradle.kts create mode 100644 app-kotlin/google-services.json create mode 100644 app-kotlin/jni/Android.mk create mode 100644 app-kotlin/jni/Application.mk create mode 100644 app-kotlin/keystore/sdkdemo.jks create mode 100644 app-kotlin/proguard-rules.pro create mode 100644 app-kotlin/src/main/AndroidManifest.xml create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/DemoApplication.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/DemoHelper.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/MainActivity.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/base/ActivityState.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/base/BaseDialogFragment.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/base/BaseInitActivity.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/base/BaseRepository.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/base/UserActivityLifecycleCallbacks.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/bean/Language.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/bean/LoginResult.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/bean/PresenceData.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/callkit/CallKitActivityLifecycleCallback.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/callkit/CallKitManager.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/callkit/CallUserInfo.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/callkit/ChatVoiceCallViewHolder.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/callkit/DemoCallKitListener.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/callkit/MultipleInviteViewHolder.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/callkit/activity/CallMultipleBaseActivity.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/callkit/activity/CallMultipleInviteActivity.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/callkit/activity/CallSingleBaseActivity.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/callkit/adapter/ConferenceInviteAdapter.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/callkit/extensions/Activity.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/callkit/extensions/JSONObject.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/callkit/fragment/ConferenceInviteFragment.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/callkit/holder/ConferenceMemberSelectViewHolder.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/callkit/views/ChatRowConferenceInvite.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/callkit/views/ChatRowVoiceCall.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/DemoConstant.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/DemoDataModel.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/ErrorCode.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/ListenersWrapper.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/PreferenceManager.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/PresenceCache.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/PushActivityLifecycleCallback.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/PushManager.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/dialog/SimpleDialog.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/dialog/fragment/DemoAgreementDialogFragment.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/dialog/fragment/DemoDialogFragment.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/extensions/String.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/extensions/internal/Activity.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/extensions/internal/ChatGroup.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/extensions/internal/ChatOptions.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/extensions/internal/ChatUserInfo.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/extensions/internal/EditText.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/extensions/internal/SwitchView.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/helper/DeveloperModeHelper.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/helper/LocalNotifyHelper.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/helper/MenuFilterHelper.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/room/AppDatabase.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/room/dao/DemoUserDao.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/room/entity/DemoUser.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/room/extensions/DbEntity.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/room/viewmodel/BusUserViewModel.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/suspend/ChatUserInfoManagerSuspend.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/suspend/PresenceManagerSuspend.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/common/suspend/PushManagerSuspend.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/fcm/FCMMSGService.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/feature/presence/controller/PresenceController.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/feature/presence/interfaces/IPresenceRequest.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/feature/presence/interfaces/IPresenceResultView.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/feature/presence/repository/ChatPresenceRepository.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/feature/presence/utils/EasePresenceUtil.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/feature/presence/viewmodel/PresenceViewModel.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/interfaces/IAttachView.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/interfaces/IMainRequest.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/interfaces/IMainResultView.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/interfaces/LanguageListItemSelectListener.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/chat/ChatActivity.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/chat/ChatFragment.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/chat/CustomMessagesAdapter.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/contact/ChatContactCheckActivity.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/contact/ChatContactDetailActivity.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/contact/ChatContactListFragmentEvent.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/contact/ChatNewRequestsActivity.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/conversation/ConversationListFragment.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/group/ChatCreateGroupActivity.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/group/ChatGroupDetailActivity.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/group/repository/GroupRepository.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/login/LoginActivity.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/login/fragment/LoginFragment.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/login/fragment/ServerSetFragment.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/login/viewModel/LoginViewModel.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/me/CameraAndCroppingController.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/me/activity/AboutActivity.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/me/activity/CurrencyActivity.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/me/activity/EditUserNicknameActivity.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/me/activity/FeaturesActivity.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/me/activity/LanguageSettingActivity.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/me/activity/NotifyActivity.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/me/activity/UserInformationActivity.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/me/activity/WebViewActivity.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/me/fragment/AboutMeFragment.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/splash/SplashActivity.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/splash/repository/ChatClientRepository.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/page/splash/viewModel/SplashViewModel.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/repository/ProfileInfoRepository.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/repository/PushRepository.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/uikit/UIKitManager.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/utils/CameraAndCropFileUtils.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/utils/LanguageUtil.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/utils/ToastUtils.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/viewmodel/MainViewModel.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/viewmodel/ProfileInfoViewModel.kt create mode 100644 app-kotlin/src/main/kotlin/io/agora/chatdemo/viewmodel/PushViewModel.kt create mode 100644 app-kotlin/src/main/res/anim/slide_in_from_left.xml create mode 100644 app-kotlin/src/main/res/anim/slide_in_from_right.xml create mode 100644 app-kotlin/src/main/res/anim/slide_out_to_left.xml create mode 100644 app-kotlin/src/main/res/anim/slide_out_to_right.xml create mode 100644 app-kotlin/src/main/res/color/demo_dialog_btn_text_color_selector.xml create mode 100644 app-kotlin/src/main/res/color/demo_main_tab_text_selector.xml create mode 100644 app-kotlin/src/main/res/drawable-hdpi/contact_title.png create mode 100644 app-kotlin/src/main/res/drawable-hdpi/conversation_title.png create mode 100644 app-kotlin/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app-kotlin/src/main/res/drawable-xhdpi/contact_title.png create mode 100644 app-kotlin/src/main/res/drawable-xhdpi/conversation_title.png create mode 100644 app-kotlin/src/main/res/drawable-xhdpi/d_chat_voice_call.png create mode 100644 app-kotlin/src/main/res/drawable-xhdpi/em_toast_fail.png create mode 100644 app-kotlin/src/main/res/drawable-xhdpi/em_toast_success.png create mode 100644 app-kotlin/src/main/res/drawable-xhdpi/splash_bg_dark.webp create mode 100644 app-kotlin/src/main/res/drawable-xhdpi/splash_bg_light.webp create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/contact_title.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/conversation_title.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/d_chat_voice_call.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/demo_check_checked.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/demo_check_uncheck.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/ease_presence_away.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/ease_presence_busy.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/ease_presence_custom.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/ease_presence_do_not_disturb.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/ease_presence_offline.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/ease_presence_online.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/icon_about.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/icon_currency.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/icon_information.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/icon_login_attention_line.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/icon_logo.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/icon_next.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/icon_presence.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/icon_privacy.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/main_tab_conversation.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/main_tab_conversation_selected.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/main_tab_friends.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/main_tab_friends_selected.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/main_tab_me.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/main_tab_me_selected.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/phone_pick.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/sign_clear_icon.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/sign_eye.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/sign_eye_slash.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/video_call.png create mode 100644 app-kotlin/src/main/res/drawable-xxhdpi/video_camera.png create mode 100644 app-kotlin/src/main/res/drawable-xxxhdpi/contact_title.png create mode 100644 app-kotlin/src/main/res/drawable-xxxhdpi/conversation_title.png create mode 100644 app-kotlin/src/main/res/drawable-xxxhdpi/icon_notify.png create mode 100644 app-kotlin/src/main/res/drawable/demo_cb_agreement_select.xml create mode 100644 app-kotlin/src/main/res/drawable/demo_dialog_btn_left_selector.xml create mode 100644 app-kotlin/src/main/res/drawable/demo_dialog_btn_right_selector.xml create mode 100644 app-kotlin/src/main/res/drawable/demo_dialog_btn_selector.xml create mode 100644 app-kotlin/src/main/res/drawable/demo_login_btn_bg.xml create mode 100644 app-kotlin/src/main/res/drawable/demo_login_et_bg.xml create mode 100644 app-kotlin/src/main/res/drawable/demo_login_version_bg.xml create mode 100644 app-kotlin/src/main/res/drawable/demo_main_tab_conversation_selector.xml create mode 100644 app-kotlin/src/main/res/drawable/demo_main_tab_friends_selector.xml create mode 100644 app-kotlin/src/main/res/drawable/demo_main_tab_me_selector.xml create mode 100644 app-kotlin/src/main/res/drawable/demo_main_unread_count_bg.xml create mode 100644 app-kotlin/src/main/res/drawable/demo_switch_thumb_selector.xml create mode 100644 app-kotlin/src/main/res/drawable/demo_switch_track_selector.xml create mode 100644 app-kotlin/src/main/res/drawable/demo_toast_bg.xml create mode 100644 app-kotlin/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app-kotlin/src/main/res/layout/activity_main_layout.xml create mode 100644 app-kotlin/src/main/res/layout/demo_activity_about.xml create mode 100644 app-kotlin/src/main/res/layout/demo_activity_conference_invite.xml create mode 100644 app-kotlin/src/main/res/layout/demo_activity_currency.xml create mode 100644 app-kotlin/src/main/res/layout/demo_activity_features.xml create mode 100644 app-kotlin/src/main/res/layout/demo_activity_language.xml create mode 100644 app-kotlin/src/main/res/layout/demo_activity_login.xml create mode 100644 app-kotlin/src/main/res/layout/demo_activity_me_information.xml create mode 100644 app-kotlin/src/main/res/layout/demo_activity_me_information_edit.xml create mode 100644 app-kotlin/src/main/res/layout/demo_activity_notify.xml create mode 100644 app-kotlin/src/main/res/layout/demo_activity_webview.xml create mode 100644 app-kotlin/src/main/res/layout/demo_badge_home.xml create mode 100644 app-kotlin/src/main/res/layout/demo_fragment_about_me.xml create mode 100644 app-kotlin/src/main/res/layout/demo_fragment_dialog_base.xml create mode 100644 app-kotlin/src/main/res/layout/demo_fragment_login.xml create mode 100644 app-kotlin/src/main/res/layout/demo_fragment_middle_agreement.xml create mode 100644 app-kotlin/src/main/res/layout/demo_fragment_server_set.xml create mode 100644 app-kotlin/src/main/res/layout/demo_row_received_conference_invite.xml create mode 100644 app-kotlin/src/main/res/layout/demo_row_received_voice_call.xml create mode 100644 app-kotlin/src/main/res/layout/demo_row_sent_conference_invite.xml create mode 100644 app-kotlin/src/main/res/layout/demo_row_sent_voice_call.xml create mode 100644 app-kotlin/src/main/res/layout/demo_splash_activity.xml create mode 100644 app-kotlin/src/main/res/layout/demo_toast_layout.xml create mode 100644 app-kotlin/src/main/res/layout/ease_layout_language_item.xml create mode 100644 app-kotlin/src/main/res/menu/bottom_main_nav_menu.xml create mode 100644 app-kotlin/src/main/res/menu/demo_chat_menu.xml create mode 100644 app-kotlin/src/main/res/menu/demo_conference_invite_menu.xml create mode 100644 app-kotlin/src/main/res/menu/demo_language_menu_confirm.xml create mode 100644 app-kotlin/src/main/res/menu/demo_server_set_menu.xml create mode 100644 app-kotlin/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app-kotlin/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app-kotlin/src/main/res/mipmap-hdpi/demo_launcher.png create mode 100644 app-kotlin/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 app-kotlin/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 app-kotlin/src/main/res/mipmap-mdpi/demo_launcher.png create mode 100644 app-kotlin/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 app-kotlin/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 app-kotlin/src/main/res/mipmap-xhdpi/demo_launcher.png create mode 100644 app-kotlin/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 app-kotlin/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 app-kotlin/src/main/res/mipmap-xxhdpi/demo_launcher.png create mode 100644 app-kotlin/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 app-kotlin/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 app-kotlin/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 app-kotlin/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 app-kotlin/src/main/res/values-night/colors.xml create mode 100644 app-kotlin/src/main/res/values-night/styles.xml create mode 100644 app-kotlin/src/main/res/values-night/themes.xml create mode 100644 app-kotlin/src/main/res/values/colors.xml create mode 100644 app-kotlin/src/main/res/values/dimens.xml create mode 100644 app-kotlin/src/main/res/values/ids.xml create mode 100644 app-kotlin/src/main/res/values/strings.xml create mode 100644 app-kotlin/src/main/res/values/styles.xml create mode 100644 app-kotlin/src/main/res/values/themes.xml delete mode 100644 build.gradle create mode 100644 build.gradle.kts delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts diff --git a/README.md b/README.md index 397b50e7..2e0064f0 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,58 @@ # Agora chat demo -This repository will help you learn how to use Agora chat SDK to implement a simple chat android application, like whatsapp or wechat. +This repository will help you learn how to use Agora chat SDK to implement a simple android chat app, like whatsapp or wechat. With this sample app, you can: -- Login chat server +- Log in to the chat server - Start a chat -- Manage conversation list +- Manage the conversation list - Add contacts - Join group chats -- Join chat rooms -- Add your contacts to your blacklist -- Send various types of messages, Such as: text, expression, picture, voice, file and so on -- Logout +- Add your contacts to your block list +- Send various types of messages, such as text, emoji, image, voice and file messages +- Log out of the chat server ## Prerequisites -* Make sure you have made the preparations mentioned in the [Agora Chat SDK quickstart](https://docs.agora.io/en/agora-chat/get-started/get-started-sdk?platform=android). -* Prepare the development environment: - * Java Development Kit (JDK) - * Android Studio 3.6 or later + +- Make sure you have made the preparations mentioned in the [Agora Chat SDK quickstart](https://docs.agora.io/en/agora-chat/get-started/get-started-sdk?platform=android). +- Prepare the development environment: + - Java Development Kit (JDK) + - Android Studio Flamingo | 2022.2.1 or later + ## Run the sample project -Follow these steps to run the sample project:\ -### 1. Clone the repository to your local machine. +Follow these steps to run the sample project: + +### 1. Clone the repository to your local device + ```java git clone git@github.com:AgoraIO-Usecase/AgoraChat-android.git ``` -### 2. Open the Android project with Android Studio. +### 2. Open the Android project with Android Studio -### 3. Configure keys. -Set your appkey applied from [Agora Developer Console](https://console.agora.io/) before calling ChatClient#init(). -```java -ChatOptions options = new ChatOptions(); -// Set your appkey -options.setAppKey("Your appkey"); -... -//initialization -ChatClient.getInstance().init(applicationContext, options); +### 3. Configure keys + +Set your appkey obtained from the [Agora Console](https://console.agora.io/) before calling ChatClient#init(). + +```kotlin +val chatOptions = ChatOptions().apply { + // Set your appkey + appKey = "Your appkey" + ... +} +// initialization +ChatClient.getInstance().init(context, chatOptions) ``` + For details, see the [prerequisites](https://docs.agora.io/en/agora-chat/get-started/get-started-sdk?platform=android) in Agora Chat SDK Guide. ## Contact Us + - You can find full API document at [Document Center](https://docs.agora.io/en/agora-chat/overview/product-overview?platform=android) - You can file bugs about this demo at [issue](https://github.com/AgoraIO-Usecase/AgoraChat-android/issues) ## License + The MIT License (MIT). diff --git a/app-kotlin/.gitignore b/app-kotlin/.gitignore new file mode 100644 index 00000000..b61e41ab --- /dev/null +++ b/app-kotlin/.gitignore @@ -0,0 +1,2 @@ +/build +/src/google-services.json \ No newline at end of file diff --git a/app-kotlin/README.md b/app-kotlin/README.md new file mode 100644 index 00000000..68ad2da3 --- /dev/null +++ b/app-kotlin/README.md @@ -0,0 +1,292 @@ +# 快速开始 + +利用环信单群聊 UIKit,你可以轻松实现单群和群聊。本文介绍如何快速实现在单聊会话中发送消息。 + +## 前提条件 + +### 开发环境要求 + +- Android Studio 4.0 及以上 +- Gradle 4.10.x 及以上 +- targetVersion 26 及以上 +- Android SDK API 21 及以上 +- JDK 11 及以上 + +## 项目准备 + +下面将介绍将单群聊 UIKit 引入项目中的必要环境配置。 + +1. 用 **Android Studio** 创建一个[新的项目](https://developer.android.com/studio/projects/create-project),在 **Phone and Tablet** 标签选择 **Empty Views Activity**,**Minimum SDK** 选择 **API 21: Android 5.0 (Lollipop)**,**Language** 选择 **Kotlin**。创建项目成功后,请确保项目同步完成。 + +2. 检查工程是否引入 **mavenCentral** 仓库。 + + a. Gradle 7.0 之前 + 在 `/Gradle Scripts/build.gradle.kts(Project: )`文件内,检查是否有 **mavenCentral** 仓库。 + ```kotlin + buildscript { + repositories { + mavenCentral() + } + } + ``` + b. Gradle 7.0 之后 + 在 `/Gradle Scripts/settings.gradle.kts(Project Settings)`文件内,检查是否有 **mavenCentral** 仓库。 + ```kotlin + dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + mavenCentral() + } + } + ``` +3. 在项目中引入单群聊 UIKit + +从 GitHub 获取[单群聊 UIKit](https://github.com/easemob/chatuikit-android) 源码,按照下面的方式集成: + +- 在根目录 `settings.gradle.kts` 文件(/Gradle Scripts/settings.gradle.kts)中添加如下代码: + +```kotlin +include(":ease-im-kit") +project(":ease-im-kit").projectDir = File("../chatuikit-android/ease-im-kit") +``` + +- 在 app 的 `build.gradle.kts` 文件(/Gradle Scripts/build.gradle)中添加如下代码: + +```kotlin +//chatuikit-android +implementation(project(mapOf("path" to ":ease-im-kit"))) +``` + +4. 防止代码混淆 + +在 `/Gradle Scripts/proguard-rules.pro` 文件中添加如下代码: + + ``` + -keep class com.hyphenate.** {*;} + -dontwarn com.hyphenate.** + ``` +## 实现单聊发消息 + +这部分将介绍如何通过单群聊 UIKit 一步一步的实现单聊发送消息的。 + +### 创建页面相关 + +1. 打开 `app/res/values/strings.xml` 文件,并替换为如下内容: + +```xml + + quickstart + + [您申请的appkey] + + +``` +这里需要注意的是,需要将 **app_key** 替换为您申请的 appkey。 + +2. 打开 `app/res/layout/activity_main.xml` 文件,并替换为如下内容: + +```xml + + + + + + + +