diff --git a/app/build.gradle b/app/build.gradle index b7d6aeba..9b13c96f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { applicationId "io.agora.chatdemo" minSdkVersion 21 targetSdkVersion 34 - versionCode 12 - versionName "1.2.0" + versionCode 130 + versionName "1.3.0" multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -48,7 +48,6 @@ android { // // externalNativeBuild { // ndkBuild { -//// arguments "NDK_LIBS_OUT=libs", "all" // abiFilters "arm64-v8a","armeabi-v7a" // arguments '-j8' // } @@ -111,8 +110,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 @@ -139,8 +138,8 @@ dependencies { implementation 'com.google.code.gson:gson:2.6.2' // Agora Chat Uikit - implementation 'io.agora.rtc:chat-uikit:1.2.0' - implementation 'io.agora.rtc:chat-callkit:1.2.0' + implementation 'io.agora.rtc:chat-uikit:1.3.0' + implementation 'io.agora.rtc:chat-callkit:1.3.0' // implementation project(path: ':chat-uikit') // implementation project(path: ':chat-callkit') // url preview 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 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f5bb1895..61da85f7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -175,14 +175,18 @@ android:exported="false" android:configChanges="orientation|keyboardHidden|screenSize" android:label="@string/demo_activity_label_video_call" + android:taskAffinity=".SingleCallTask" android:launchMode="singleInstance" + android:excludeFromRecents="true" android:screenOrientation="portrait" /> { + 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 00000000..63482071 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/chat_item_menu_pin.png differ diff --git a/app/src/main/res/drawable-xxhdpi/chat_pin_onlight.png b/app/src/main/res/drawable-xxhdpi/chat_pin_onlight.png new file mode 100644 index 00000000..168700d7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/chat_pin_onlight.png differ 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 00000000..74b1e283 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/chat_pin_rectangle.png differ 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 00000000..9d966f0f Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/chat_pininfo_icon.png differ 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 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