私聊改造:新增快速发送图片功能

This commit is contained in:
huangjian
2023-01-12 18:34:48 +08:00
parent 69f0d76330
commit dee4f873b4
18 changed files with 494 additions and 261 deletions

View File

@@ -20,6 +20,7 @@ import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.Chronometer;
import android.widget.EditText;
import android.widget.FrameLayout;
@@ -27,7 +28,11 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.alibaba.fastjson.JSONObject;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.effective.android.panel.PanelSwitchHelper;
import com.effective.android.panel.interfaces.PanelHeightMeasurer;
import com.effective.android.panel.interfaces.listener.OnPanelChangeListener;
@@ -35,6 +40,8 @@ import com.effective.android.panel.view.panel.IPanelView;
import com.effective.android.panel.view.panel.PanelView;
import com.mango.moshen.R;
import com.mango.moshen.ui.im.actions.GiftAction;
import com.mango.moshen.ui.im.adpter.PhotoPreviewAdapter;
import com.mango.xchat_android_library.utils.ListUtils;
import com.mango.xchat_android_library.utils.SingleToastUtil;
import com.mango.xchat_android_library.utils.SizeUtils;
import com.netease.nim.uikit.api.NimUIKit;
@@ -48,10 +55,14 @@ import com.netease.nim.uikit.business.session.emoji.EmoticonPickerView;
import com.netease.nim.uikit.business.session.emoji.IEmoticonSelectedListener;
import com.netease.nim.uikit.business.session.emoji.MoonUtil;
import com.netease.nim.uikit.business.session.event.ActiveEvent;
import com.netease.nim.uikit.business.session.helper.SendImageHelper;
import com.netease.nim.uikit.business.session.module.Container;
import com.netease.nim.uikit.business.session.module.input.ActionsPanel;
import com.netease.nim.uikit.business.session.module.input.NimAudioChatEvent;
import com.netease.nim.uikit.common.antispam.AntiSpamEvent;
import com.netease.nim.uikit.common.media.picker.PickImageHelper;
import com.netease.nim.uikit.common.media.picker.model.AlbumInfo;
import com.netease.nim.uikit.common.media.picker.model.PhotoInfo;
import com.netease.nim.uikit.common.ui.dialog.EasyAlertDialogHelper;
import com.netease.nim.uikit.common.util.AntiSpamUtil;
import com.netease.nim.uikit.common.util.log.LogUtil;
@@ -73,8 +84,17 @@ import com.netease.nimlib.sdk.msg.model.IMMessage;
import org.greenrobot.eventbus.EventBus;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.Scheduler;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
/**
* 底部文本编辑,语音等模块
* Created by hzxuwen on 2015/6/16.
@@ -84,11 +104,10 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
private static final String TAG = "MsgSendLayout";
private static final int SHOW_LAYOUT_DELAY = 0;
private final List<PhotoInfo> selectPhotos = new ArrayList<>();
protected Container container;
protected View view;
protected Handler uiHandler;
protected EditText messageEditText;// 文本消息编辑框
protected Button audioRecordBtn; // 录音按钮
protected View audioAnimLayout; // 录音动画布局
@@ -96,7 +115,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
protected View switchToTextButtonInInputBar;// 文本消息选择按钮
protected View switchToAudioButtonInInputBar;// 语音消息选择按钮
protected View sendMessageButtonInInputBar;// 发送消息按钮
protected View emojiButtonInInputBar;// 发送消息按钮
protected ImageView emojiButtonInInputBar;// 发送消息按钮
protected View messageInputBar;
// 表情
protected EmoticonPickerView emoticonPickerView; // 贴图表情控件
@@ -110,14 +129,13 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
private boolean started = false;
private boolean cancelled = false;
private boolean touched = false; // 是否按着
private boolean isKeyboardShowed = true; // 是否显示键盘
private boolean isKeyboardShowed = false; // 是否显示键盘
private boolean isTextAudioSwitchShow; // 是否展示左侧语音按钮
// adapter
private List<BaseAction> actions;
// data
private long typingTime = 0;
private TextWatcher aitTextWatcher;
private volatile boolean disable;
private boolean isChat;
private PanelSwitchHelper mHelper;
private final Runnable showTextRunnable = new Runnable() {
@@ -129,6 +147,12 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
private ImageView ivImage;
private ImageView ivCamera;
private ImageView ivGift;
private CheckBox cbOrigin;
private TextView tvSendImage;
private RecyclerView rvPreviewPhotos;
private PhotoPreviewAdapter photoPreviewAdapter;
private int previewPhotoNum;
private TextView tvUserPhoto;
/**
* ************************* 键盘布局切换 *******************************
*/
@@ -152,7 +176,6 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
}
mHelper.resetState();
} else if (v == ivGift) {
ivGift.postDelayed(() -> {
for (BaseAction action : actions) {
if (action instanceof GiftAction) {
@@ -162,7 +185,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
}
}, !mHelper.isResetState() ? 300 : 0);
mHelper.resetState();
} else if (v == ivImage) {
} else if (v == tvUserPhoto) {
for (BaseAction action : actions) {
if (action instanceof PhotoAction) {
action.onClick();
@@ -170,6 +193,29 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
}
}
mHelper.resetState();
} else if (v == tvSendImage) {
if (!isChat) {
SingleToastUtil.showToast("暂未达到可发起私聊等级");
return;
}
if (photoPreviewAdapter != null) {
for (BaseAction action : actions) {
if (action instanceof PhotoAction) {
SendImageHelper.sendPhotos(container.activity, (PhotoAction) action, selectPhotos, cbOrigin.isChecked());
selectPhotos.clear();
for (PhotoInfo info : photoPreviewAdapter.getData()) {
info.setIndex(0);
info.setChoose(false);
}
previewPhotoNum = 0;
tvSendImage.setEnabled(false);
photoPreviewAdapter.notifyDataSetChanged();
break;
}
}
mHelper.resetState();
}
}
}
};
@@ -192,8 +238,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
int[] location = new int[2];
view.getLocationOnScreen(location);
return event.getRawX() < location[0] || event.getRawX() > location[0] + view.getWidth()
|| event.getRawY() < location[1] - 40;
return event.getRawX() < location[0] || event.getRawX() > location[0] + view.getWidth() || event.getRawY() < location[1] - 40;
}
public void onPause() {
@@ -211,7 +256,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
}
public boolean collapse(boolean immediately) {
boolean respond = !mHelper.isResetState();
boolean respond = mHelper.isPanelState() || mHelper.isKeyboardState() || isKeyboardShowed;
mHelper.resetState();
return respond;
}
@@ -225,61 +270,56 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
initInputBarListener();
initTextEdit();
initAudioRecordButton();
initPanelSwitchHelper();
restoreText(false);
for (int i = 0; i < actions.size(); ++i) {
actions.get(i).setIndex(i);
actions.get(i).setContainer(container);
}
}
if (disable) {
disableButtons();
}
private void initPanelSwitchHelper() {
if (mHelper == null) {
mHelper = new PanelSwitchHelper.Builder(container.activity)
//可选
.addKeyboardStateListener((visible, height) -> {
Log.d(TAG, "系统键盘是否可见 : " + visible + " 高度为:" + height);
})
//可选
.addEditTextFocusChangeListener((view, hasFocus) -> {
Log.d(TAG, "输入框是否获得焦点 : " + hasFocus);
})
//可选
.addViewClickListener(view -> {
if (view == null) return;
if (view.getId() == R.id.emoji_button) {
switchToTextLayout(false);
}
Log.d(TAG, "点击了View : " + view);
})
//可选
.addPanelChangeListener(new OnPanelChangeListener() {
@Override
public void onKeyboard() {
Log.d(TAG, "唤起系统输入法");
isKeyboardShowed = true;
container.proxy.onInputPanelExpand();
emojiButtonInInputBar.setImageResource(R.drawable.nim_message_input_emotion_pressed);
}
@Override
public void onNone() {
Log.d(TAG, "隐藏所有面板");
isKeyboardShowed = false;
emojiButtonInInputBar.setImageResource(R.drawable.nim_message_input_emotion_pressed);
}
@Override
public void onPanel(IPanelView view) {
Log.d(TAG, "唤起面板 : " + view);
container.proxy.onInputPanelExpand();
if (view.getBindingTriggerViewId() == R.id.emoji_button) {
emojiButtonInInputBar.setImageResource(R.drawable.nim_message_input_keyboard);
emoticonPickerView.show(InputPanel.this);
} else if (view.getBindingTriggerViewId() == R.id.iv_image) {
emojiButtonInInputBar.setImageResource(R.drawable.nim_message_input_emotion_pressed);
initPreviewPhotos();
}
}
@Override
public void onPanelSizeChange(IPanelView panelView, boolean portrait, int oldWidth, int oldHeight, int width, int height) {
}
})
.addPanelHeightMeasurer(new PanelHeightMeasurer() {
}).addPanelHeightMeasurer(new PanelHeightMeasurer() {
@Override
public boolean synchronizeKeyboardHeight() {
return false;
@@ -294,8 +334,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
public int getPanelTriggerId() {
return R.id.iv_image;
}
})
.addPanelHeightMeasurer(new PanelHeightMeasurer() {
}).addPanelHeightMeasurer(new PanelHeightMeasurer() {
@Override
public boolean synchronizeKeyboardHeight() {
return false;
@@ -310,8 +349,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
public int getPanelTriggerId() {
return R.id.emoji_button;
}
})
.logTrack(true)//output log
}).logTrack(false)//output log
.build();
messageListView.setPanelSwitchHelper(mHelper);
@@ -319,6 +357,61 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
}
@SuppressLint("CheckResult")
private void initPreviewPhotos() {
if (photoPreviewAdapter != null) {
photoPreviewAdapter.notifyDataSetChanged();
return;
}
rvPreviewPhotos.setAdapter(photoPreviewAdapter = new PhotoPreviewAdapter());
rvPreviewPhotos.setItemAnimator(null);
rvPreviewPhotos.setLayoutManager(new LinearLayoutManager(container.activity, LinearLayoutManager.HORIZONTAL, false));
photoPreviewAdapter.setOnItemClickListener((adapter, view, position) -> {
PhotoInfo photoInfo = photoPreviewAdapter.getItem(position);
if (photoInfo != null) {
if (photoInfo.isChoose()) {
photoInfo.setChoose(false);
int tempIndex = photoInfo.getIndex();
for (PhotoInfo info : photoPreviewAdapter.getData()) {
if (info.getIndex() > tempIndex) {
info.setIndex(info.getIndex() - 1);
}
}
photoInfo.setIndex(0);
previewPhotoNum--;
selectPhotos.remove(tempIndex - 1);
photoPreviewAdapter.notifyDataSetChanged();
} else {
if (previewPhotoNum >= 9) {
SingleToastUtil.showToast("一次最多发送9张图片!");
return;
}
previewPhotoNum++;
photoInfo.setChoose(true);
photoInfo.setIndex(previewPhotoNum);
selectPhotos.add(photoInfo);
photoPreviewAdapter.notifyItemChanged(position);
}
tvSendImage.setEnabled(previewPhotoNum > 0);
}
});
Observable.just(PickImageHelper.getAllMediaPhotos(container.activity, 40))
.subscribeOn(Schedulers.io())
.flatMap(albumInfos -> {
List<PhotoInfo> photoInfos = new ArrayList<>();
for (AlbumInfo albumInfo : albumInfos) {
if (!ListUtils.isListEmpty(albumInfo.getList())) {
photoInfos.addAll(albumInfo.getList());
}
}
return Observable.just(photoInfos);
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(photoInfos -> photoPreviewAdapter.setNewData(photoInfos));
}
public void setCustomization(SessionCustomization customization) {
this.customization = customization;
if (customization != null) {
@@ -331,6 +424,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
setCustomization(customization);
}
@SuppressLint("CheckResult")
private void initViews() {
messageInputBar = view.findViewById(R.id.textMessageLayout);
switchToTextButtonInInputBar = view.findViewById(R.id.buttonTextMessage);
@@ -348,8 +442,6 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
// 表情
emoticonPickerView = view.findViewById(R.id.emoticon_picker_view);
emoticonPickerView.show(this);
// 显示录音按钮
switchToTextButtonInInputBar.setVisibility(View.GONE);
switchToAudioButtonInInputBar.setVisibility(View.VISIBLE);
@@ -369,27 +461,13 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
ivCamera.setOnClickListener(clickListener);
ivGift.setOnClickListener(clickListener);
}
public void disableButtons() {
if (messageEditText != null && audioRecordBtn != null &&
switchToTextButtonInInputBar != null &&
switchToAudioButtonInInputBar != null &&
emojiButtonInInputBar != null) {
messageEditText.setEnabled(false);
messageEditText.setHint("禁言中,快找管理员解除禁言吧!");
messageEditText.setHintTextColor(Color.parseColor("#999999"));
audioRecordBtn.setEnabled(false);
switchToTextButtonInInputBar.setEnabled(false);
switchToAudioButtonInInputBar.setEnabled(false);
emojiButtonInInputBar.setEnabled(false);
} else {
setDisable(true);
}
}
public void setDisable(boolean disable) {
this.disable = disable;
rvPreviewPhotos = view.findViewById(R.id.rv_preview_photos);
tvSendImage = view.findViewById(R.id.tv_send_image);
tvSendImage.setEnabled(false);
tvUserPhoto = view.findViewById(R.id.tv_user_photo);
cbOrigin = view.findViewById(R.id.cb_origin);
tvSendImage.setOnClickListener(clickListener);
tvUserPhoto.setOnClickListener(clickListener);
}
private void initInputBarListener() {
@@ -405,9 +483,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
//当actionId == XX_SEND 或者 XX_DONE时都触发
//或者event.getKeyCode == ENTER 且 event.getAction == ACTION_DOWN时也触发
//注意这是一定要判断event != null。因为在某些输入法上会返回null。
if (actionId == EditorInfo.IME_ACTION_SEND
|| actionId == EditorInfo.IME_ACTION_DONE
|| (event != null && KeyEvent.KEYCODE_ENTER == event.getKeyCode() && KeyEvent.ACTION_DOWN == event.getAction())) {
if (actionId == EditorInfo.IME_ACTION_SEND || actionId == EditorInfo.IME_ACTION_DONE || (event != null && KeyEvent.KEYCODE_ENTER == event.getKeyCode() && KeyEvent.ACTION_DOWN == event.getAction())) {
//处理事件
onTextMessageSendButtonPressed();
return true;
@@ -540,7 +616,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
// 切换成音频,收起键盘,按钮切换成键盘
private void switchToAudioLayout() {
if (!isChat) {
SingleToastUtil.showToast("等级不够");
SingleToastUtil.showToast("暂未达到可发起私聊等级");
return;
}
Log.e(TAG, "switchToAudioLayout: ");
@@ -559,10 +635,8 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
// 隐藏键盘布局
public void hideInputMethod() {
isKeyboardShowed = false;
uiHandler.removeCallbacks(showTextRunnable);
InputMethodManager imm = (InputMethodManager) container.activity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(messageEditText.getWindowToken(), 0);
mHelper.resetState();
messageEditText.clearFocus();
}
@@ -572,9 +646,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
//如果已经显示,则继续操作时不需要把光标定位到最后
if (!isKeyboardShowed) {
editTextMessage.setSelection(editTextMessage.getText().length());
isKeyboardShowed = true;
}
mHelper.toKeyboardState();
container.proxy.onInputPanelExpand();
}
@@ -594,7 +666,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
private void checkSendButtonEnable(EditText editText) {
String textMessage = editText.getText().toString();
setEditTextState();
if (!TextUtils.isEmpty(StringUtil.removeBlanks(textMessage)) && editText.hasFocus()) {
if (!TextUtils.isEmpty(StringUtil.removeBlanks(textMessage))) {
sendMessageButtonInInputBar.setVisibility(View.VISIBLE);
} else {
sendMessageButtonInInputBar.setVisibility(View.GONE);
@@ -631,8 +703,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
@Override
public void onTextAdd(String content, int start, int length) {
if (messageEditText.getVisibility() != View.VISIBLE ||
(emoticonPickerView != null && emoticonPickerView.getVisibility() == View.VISIBLE)) {
if (messageEditText.getVisibility() != View.VISIBLE || (emoticonPickerView != null && emoticonPickerView.getVisibility() == View.VISIBLE)) {
switchToTextLayout(true);
} else {
uiHandler.postDelayed(showTextRunnable, SHOW_LAYOUT_DELAY);
@@ -666,8 +737,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
touched = true;
initAudioRecord();
onStartAudioRecord();
} else if (event.getAction() == MotionEvent.ACTION_CANCEL
|| event.getAction() == MotionEvent.ACTION_UP) {
} else if (event.getAction() == MotionEvent.ACTION_CANCEL || event.getAction() == MotionEvent.ACTION_UP) {
touched = false;
onEndAudioRecord(isCancelled(v, event));
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
@@ -693,8 +763,7 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
* 开始语音录制
*/
private void onStartAudioRecord() {
container.activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
container.activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
audioMessageHelper.startRecord();
cancelled = false;
}
@@ -845,15 +914,6 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
}
}
public void reloadActions(List<BaseAction> actions) {
this.actions = actions;
for (int i = 0; i < this.actions.size(); ++i) {
this.actions.get(i).setIndex(i);
this.actions.get(i).setContainer(container);
}
ActionsPanel.init(view, actions);
}
public void setLimitLevel(boolean isChat, String msg) {
this.isChat = isChat;
setEditTextState();
@@ -866,12 +926,22 @@ public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallba
messageEditText.setFocusableInTouchMode(false);
messageEditText.setEnabled(false);
sendMessageButtonInInputBar.setEnabled(false);
emojiButtonInInputBar.setEnabled(false);
switchToAudioButtonInInputBar.setEnabled(false);
ivImage.setEnabled(false);
ivCamera.setEnabled(false);
ivGift.setEnabled(false);
} else {
messageEditText.setHint("请输入消息");
messageEditText.setFocusable(true);
messageEditText.setFocusableInTouchMode(true);
messageEditText.setEnabled(true);
sendMessageButtonInInputBar.setEnabled(true);
emojiButtonInInputBar.setEnabled(true);
switchToAudioButtonInInputBar.setEnabled(true);
ivImage.setEnabled(true);
ivCamera.setEnabled(true);
ivGift.setEnabled(true);
}
}

View File

@@ -0,0 +1,29 @@
package com.mango.moshen.ui.im.adpter
import android.widget.ImageView
import android.widget.TextView
import com.bumptech.glide.signature.ObjectKey
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.BaseViewHolder
import com.mango.core.utils.LogUtils
import com.mango.moshen.R
import com.mango.moshen.ui.utils.ImageLoadUtilsV2
import com.netease.nim.uikit.common.media.picker.model.PhotoInfo
import com.netease.nim.uikit.support.glide.GlideApp
class PhotoPreviewAdapter :
BaseQuickAdapter<PhotoInfo, BaseViewHolder>(R.layout.item_message_photo) {
override fun convert(helper: BaseViewHolder, item: PhotoInfo) {
val imageView = helper.getView<ImageView>(R.id.iv_item_photo)
GlideApp.with(imageView)
.load(item.filePath)
.placeholder(R.drawable.default_cover)
.signature(ObjectKey(item))
.into(imageView)
val textView = helper.getView<TextView>(R.id.tv_item_number)
textView.isSelected = item.isChoose
textView.text = if (item.isChoose) "${item.index}" else ""
}
}

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<shape android:shape="oval">
<solid android:color="#5FCCE4" />
<stroke android:width="1dp" android:color="@color/white" />
</shape>
</item>
<item>
<shape android:shape="oval">
<solid android:color="@color/black_transparent_30" />
<stroke android:width="1dp" android:color="@color/white" />
</shape>
</item>
</selector>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="95dp"
android:layout_height="138dp"
android:layout_marginStart="5dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="5dp"
android:layout_marginBottom="8dp">
<com.mango.moshen.common.widget.RectRoundImageView
android:id="@+id/iv_item_photo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
app:borderRadius="4dp"
app:type="round" />
<TextView
android:id="@+id/tv_item_number"
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_gravity="top|end"
android:layout_marginTop="6dp"
android:layout_marginEnd="6dp"
android:background="@drawable/shape_circle_photo_preview_bg"
android:gravity="center"
android:includeFontPadding="false"
android:textColor="@color/white"
android:textSize="@dimen/sp_10"
tools:text="1" />
</FrameLayout>

View File

@@ -1,21 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center">
android:orientation="vertical">
<TextView
android:id="@+id/nim_message_item_text_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="phone|email|web"
android:gravity="center_vertical|left"
android:includeFontPadding="false"
android:lineSpacingExtra="3dip"
android:maxWidth="222dp"
android:text="这是选择图片的玩意"
android:textColor="@color/text_title_white"
android:textSize="@dimen/dp_13" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_preview_photos"
android:layout_width="match_parent"
android:layout_height="154dp"
android:background="@color/color_f4f4fa" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="46dp"
android:background="@color/white"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingBottom="16dp">
<TextView
android:id="@+id/tv_user_photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="相册"
android:textColor="@color/color_1A1A1A"
android:textSize="14sp" />
<CheckBox
android:id="@+id/cb_origin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:button="@null"
android:drawableStart="@drawable/selector_check_box_collect"
android:drawablePadding="3dp"
android:text="原图"
android:textColor="@color/color_1A1A1A"
android:textSize="14sp" />
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:id="@+id/tv_send_image"
android:layout_width="56dp"
android:layout_height="22dp"
android:layout_marginEnd="16dp"
android:background="@drawable/common_btn_bg"
android:gravity="center"
android:text="发送"
android:textColor="@color/white"
android:textSize="10sp" />
</LinearLayout>
</LinearLayout>

View File

@@ -107,7 +107,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_height="32dp"
android:gravity="center_vertical"
android:orientation="horizontal">
@@ -120,19 +120,19 @@
<ImageView
android:id="@+id/buttonAudioMessage"
android:layout_width="30dp"
android:layout_height="30dp"
android:background="@drawable/nim_message_button_bottom_audio_selector"
android:layout_width="32dp"
android:layout_height="32dp"
android:contentDescription="@string/empty"
android:scaleType="center" />
android:scaleType="fitXY"
android:src="@drawable/nim_message_input_voice_pressed" />
<ImageView
android:id="@+id/buttonTextMessage"
android:layout_width="30dp"
android:layout_height="30dp"
android:background="@drawable/nim_message_button_bottom_text_selector"
android:layout_width="32dp"
android:layout_height="32dp"
android:contentDescription="@string/empty"
android:scaleType="center"
android:scaleType="fitXY"
android:src="@drawable/nim_message_input_keyboard"
android:visibility="gone" />
</FrameLayout>
@@ -140,7 +140,7 @@
<Button
android:id="@+id/audioRecord"
android:layout_width="0dp"
android:layout_height="30dp"
android:layout_height="32dp"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/bottom_component_margin_horizontal"
android:layout_weight="1"
@@ -153,11 +153,12 @@
<EditText
android:id="@+id/editTextMessage"
android:layout_width="0dp"
android:layout_height="30dp"
android:layout_height="32dp"
android:layout_marginEnd="@dimen/bottom_component_margin_horizontal"
android:layout_weight="1"
android:autoLink="web|email|phone"
android:background="@drawable/bg_message_input"
android:gravity="center_vertical"
android:hint="@string/message_hint"
android:imeOptions="actionSend"
android:inputType="text"
@@ -172,12 +173,12 @@
<ImageView
android:id="@+id/emoji_button"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="@dimen/bottom_component_margin_horizontal"
android:background="@drawable/nim_message_button_bottom_emoji_selector"
android:contentDescription="@string/empty"
android:scaleType="center" />
android:scaleType="fitXY"
android:src="@drawable/nim_message_input_emotion_pressed" />
<TextView
android:id="@+id/buttonSendMessage"
@@ -187,6 +188,7 @@
android:background="@drawable/common_btn_bg"
android:contentDescription="@string/empty"
android:gravity="center"
android:enabled="false"
android:text="@string/send"
android:textColor="@color/white"
android:textSize="@dimen/sp_12"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -3,6 +3,6 @@
<item android:drawable="@drawable/nim_message_input_emotion_pressed" android:state_pressed="true"/>
<item android:drawable="@drawable/nim_message_input_emotion_pressed" android:state_focused="true"/>
<item android:drawable="@drawable/nim_message_input_emotion"/>
<item android:drawable="@drawable/nim_message_input_emotion_pressed"/>
</selector>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/color_grey_eaeaea" />
<corners android:topLeftRadius="15dp"
android:topRightRadius="15dp"
android:bottomRightRadius="15dp"
android:bottomLeftRadius="15dp"/>
<corners android:topLeftRadius="8dp"
android:topRightRadius="8dp"
android:bottomRightRadius="8dp"
android:bottomLeftRadius="8dp"/>
</shape>

View File

@@ -28,7 +28,7 @@ public class PhotoAction extends PickImageAction {
}
@Override
protected void onPicked(File file) {
public void onPicked(File file) {
IMMessage message;
if (getContainer() != null && getContainer().sessionType == SessionTypeEnum.ChatRoom) {
message = ChatRoomMessageBuilder.createChatRoomImageMessage(getAccount(), file, file.getName());

View File

@@ -54,7 +54,6 @@ public class EmoticonPickerView extends LinearLayout implements IEmoticonCategor
}
};
private int categoryIndex;
private Handler uiHandler;
public EmoticonPickerView(Context context) {
super(context);
@@ -75,7 +74,6 @@ public class EmoticonPickerView extends LinearLayout implements IEmoticonCategor
private void init(Context context) {
this.context = context;
this.uiHandler = new Handler(context.getMainLooper());
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.nim_emoji_layout, this);
}
@@ -222,7 +220,7 @@ public class EmoticonPickerView extends LinearLayout implements IEmoticonCategor
@Override
public void run() {
if (scrollView.getChildAt(0).getWidth() == 0) {
uiHandler.postDelayed(this, 100);
scrollView.postDelayed(this, 100);
}
int x = -1;
View child = tabView.getChildAt(index);
@@ -236,7 +234,7 @@ public class EmoticonPickerView extends LinearLayout implements IEmoticonCategor
}
}
};
uiHandler.postDelayed(runnable, 100);
scrollView.postDelayed(runnable, 100);
}

View File

@@ -8,6 +8,7 @@ import android.text.TextUtils;
import android.widget.Toast;
import com.netease.nim.uikit.R;
import com.netease.nim.uikit.business.session.actions.PhotoAction;
import com.netease.nim.uikit.business.session.constant.Extras;
import com.netease.nim.uikit.common.media.picker.model.PhotoInfo;
import com.netease.nim.uikit.common.media.picker.model.PickerContract;
@@ -24,10 +25,6 @@ import java.util.ArrayList;
import java.util.List;
public class SendImageHelper {
public interface Callback {
void sendImage(File file, boolean isOrig);
}
public static void sendImageAfterPreviewPhotoActivityResult(Intent data, Callback callback) {
final ArrayList<String> selectedImageFileList = data.getStringArrayListExtra(Extras.EXTRA_SCALED_IMAGE_LIST);
final ArrayList<String> origSelectedImageFileList = data.getStringArrayListExtra(Extras.EXTRA_ORIG_IMAGE_LIST);
@@ -88,6 +85,16 @@ public class SendImageHelper {
}
}
public static void sendPhotos(Context context, PhotoAction action, List<PhotoInfo> photos, boolean isOrig) {
for (PhotoInfo photoInfo : photos) {
new SendImageTask(context, isOrig, photoInfo, (file, isOrig1) -> action.onPicked(file)).execute();
}
}
public interface Callback {
void sendImage(File file, boolean isOrig);
}
// 从相册选择图片进行发送(Added by NYB)
public static class SendImageTask extends AsyncTask<Void, Void, File> {

View File

@@ -128,4 +128,12 @@ public abstract class MsgViewHolderThumbBase extends MsgViewHolderBase {
}
protected abstract String thumbFromSourceFile(String path);
protected int leftBackground() {
return 0;
}
protected int rightBackground() {
return 0;
}
}

View File

@@ -2,10 +2,18 @@ package com.netease.nim.uikit.common.media.picker;
import android.app.Activity;
import android.content.Context;
import android.database.Cursor;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log;
import com.netease.nim.uikit.R;
import com.netease.nim.uikit.business.session.module.input.NimImageActionEvent;
import com.netease.nim.uikit.common.media.dao.MediaDAO;
import com.netease.nim.uikit.common.media.picker.activity.PickImageActivity;
import com.netease.nim.uikit.common.media.picker.model.AlbumInfo;
import com.netease.nim.uikit.common.media.picker.model.PhotoInfo;
import com.netease.nim.uikit.common.media.picker.util.ThumbnailsUtil;
import com.netease.nim.uikit.common.ui.dialog.CustomAlertDialog;
import com.netease.nim.uikit.common.util.storage.StorageType;
import com.netease.nim.uikit.common.util.storage.StorageUtil;
@@ -13,11 +21,18 @@ import com.netease.nim.uikit.common.util.string.StringUtil;
import org.greenrobot.eventbus.EventBus;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* Created by huangjun on 2015/9/22.
*/
public class PickImageHelper {
public static final String FILE_PREFIX = "file://";
/**
* 打开图片选择器
*/
@@ -72,6 +87,112 @@ public class PickImageHelper {
EventBus.getDefault().post(event);
}
public static ArrayList<AlbumInfo> getAllMediaPhotos(Context context, int limit) {
getAllMediaThumbnails(context);
ArrayList<AlbumInfo> albumInfolist = new ArrayList<>();
int num = 0;
Cursor cursorPhotos = null;
try {
cursorPhotos = MediaDAO.getAllMediaPhotos(context);
HashMap<String, AlbumInfo> hash = new HashMap<>();
AlbumInfo albumInfo;
PhotoInfo photoInfo;
if (cursorPhotos != null && cursorPhotos.moveToFirst()) {
do {
int index = 0;
int _id = cursorPhotos.getInt(cursorPhotos.getColumnIndex(MediaStore.Images.Media._ID));
String path = cursorPhotos.getString(cursorPhotos.getColumnIndex(MediaStore.Images.Media.DATA));
String album = cursorPhotos.getString(cursorPhotos.getColumnIndex(MediaStore.Images.Media.BUCKET_DISPLAY_NAME));
long size = cursorPhotos.getLong(cursorPhotos.getColumnIndex(MediaStore.Images.Media.SIZE));
if (!isValidImageFile(path)) {
Log.d("PICKER", "it is not a vaild path:" + path);
continue;
}
num++;
List<PhotoInfo> photoList = new ArrayList<>();
photoInfo = new PhotoInfo();
if (hash.containsKey(album)) {
albumInfo = hash.remove(album);
if (albumInfolist.contains(albumInfo))
index = albumInfolist.indexOf(albumInfo);
photoInfo.setImageId(_id);
photoInfo.setFilePath(FILE_PREFIX + path);
photoInfo.setAbsolutePath(path);
photoInfo.setSize(size);
albumInfo.getList().add(photoInfo);
albumInfolist.set(index, albumInfo);
hash.put(album, albumInfo);
} else {
albumInfo = new AlbumInfo();
photoList.clear();
photoInfo.setImageId(_id);
photoInfo.setFilePath(FILE_PREFIX + path);
photoInfo.setAbsolutePath(path);
photoInfo.setSize(size);
photoList.add(photoInfo);
albumInfo.setImageId(_id);
albumInfo.setFilePath(FILE_PREFIX + path);
albumInfo.setAbsolutePath(path);
albumInfo.setAlbumName(album);
albumInfo.setList(photoList);
albumInfolist.add(albumInfo);
hash.put(album, albumInfo);
}
} while (cursorPhotos.moveToNext() && num < limit);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (cursorPhotos != null) {
cursorPhotos.close();
}
} catch (Exception e) {
}
}
return albumInfolist;
}
private static void getAllMediaThumbnails(Context context) {
ThumbnailsUtil.clear();
Cursor cursorThumb = null;
try {
cursorThumb = MediaDAO.getAllMediaThumbnails(context);
if (cursorThumb != null && cursorThumb.moveToFirst()) {
int imageID;
String imagePath;
do {
imageID = cursorThumb.getInt(cursorThumb.getColumnIndex(MediaStore.Images.Thumbnails.IMAGE_ID));
imagePath = cursorThumb.getString(cursorThumb.getColumnIndex(MediaStore.Images.Thumbnails.DATA));
ThumbnailsUtil.put(imageID, FILE_PREFIX + imagePath);
} while (cursorThumb.moveToNext());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (cursorThumb != null) {
cursorThumb.close();
}
} catch (Exception e) {
}
}
}
private static boolean isValidImageFile(String filePath) {
if (TextUtils.isEmpty(filePath)) {
return false;
}
File imageFile = new File(filePath);
return imageFile.exists();
}
public static class PickImageOption {
/**
* 图片选择器标题

View File

@@ -1,13 +1,8 @@
package com.netease.nim.uikit.common.media.picker.fragment;
import android.app.Activity;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.MediaStore;
import android.provider.MediaStore.Images.Thumbnails;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -19,33 +14,21 @@ import android.widget.TextView;
import com.netease.nim.uikit.R;
import com.netease.nim.uikit.common.fragment.TFragment;
import com.netease.nim.uikit.common.media.dao.MediaDAO;
import com.netease.nim.uikit.common.media.picker.PickImageHelper;
import com.netease.nim.uikit.common.media.picker.adapter.PickerAlbumAdapter;
import com.netease.nim.uikit.common.media.picker.model.AlbumInfo;
import com.netease.nim.uikit.common.media.picker.model.PhotoInfo;
import com.netease.nim.uikit.common.media.picker.util.ThumbnailsUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class PickerAlbumFragment extends TFragment implements OnItemClickListener {
public interface OnAlbumItemClickListener {
public void OnAlbumItemClick(AlbumInfo info);
}
private OnAlbumItemClickListener onAlbumItemClickListener;
private LinearLayout loadingLay;
private TextView loadingTips;
private TextView loadingEmpty;
private ListView albumListView;
public static final String FILE_PREFIX = "file://";
private List<AlbumInfo> albumInfolist = new ArrayList<AlbumInfo>();
private List<AlbumInfo> albumInfolist = new ArrayList<>();
private PickerAlbumAdapter albumAdapter;
public PickerAlbumFragment() {
@@ -90,13 +73,31 @@ public class PickerAlbumFragment extends TFragment implements OnItemClickListene
new ImageScanAsyncTask().execute();
}
private void getAllMediaPhotos() {
if (albumInfolist == null) {
albumInfolist = new ArrayList<>();
} else {
albumInfolist.clear();
}
albumInfolist.addAll(PickImageHelper.getAllMediaPhotos(getActivity(),Integer.MAX_VALUE));
}
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int position, long arg3) {
onAlbumItemClickListener.OnAlbumItemClick(albumInfolist.get(position));
}
public interface OnAlbumItemClickListener {
void OnAlbumItemClick(AlbumInfo info);
}
private class ImageScanAsyncTask extends AsyncTask<Void, Void, Object> {
@Override
protected Object doInBackground(Void... params) {
getAllMediaThumbnails();
getAllMediaPhotos();
return null;
}
@@ -117,118 +118,4 @@ public class PickerAlbumFragment extends TFragment implements OnItemClickListene
}
}
}
private void getAllMediaThumbnails() {
ThumbnailsUtil.clear();
Cursor cursorThumb = null;
try {
cursorThumb = MediaDAO.getAllMediaThumbnails(getActivity());
if (cursorThumb != null && cursorThumb.moveToFirst()) {
int imageID;
String imagePath;
do {
imageID = cursorThumb.getInt(cursorThumb.getColumnIndex(Thumbnails.IMAGE_ID));
imagePath = cursorThumb.getString(cursorThumb.getColumnIndex(Thumbnails.DATA));
ThumbnailsUtil.put(imageID, FILE_PREFIX + imagePath);
} while (cursorThumb.moveToNext());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (cursorThumb != null) {
cursorThumb.close();
}
} catch (Exception e) {
}
}
}
private void getAllMediaPhotos() {
if (albumInfolist == null) {
albumInfolist = new ArrayList<AlbumInfo>();
} else {
albumInfolist.clear();
}
Cursor cursorPhotos = null;
try {
cursorPhotos = MediaDAO.getAllMediaPhotos(getActivity());
HashMap<String, AlbumInfo> hash = new HashMap<String, AlbumInfo>();
AlbumInfo albumInfo = null;
PhotoInfo photoInfo = null;
if (cursorPhotos != null && cursorPhotos.moveToFirst()) {
do {
int index = 0;
int _id = cursorPhotos.getInt(cursorPhotos.getColumnIndex(MediaStore.Images.Media._ID));
String path = cursorPhotos.getString(cursorPhotos.getColumnIndex(MediaStore.Images.Media.DATA));
String album = cursorPhotos.getString(cursorPhotos.getColumnIndex(MediaStore.Images.Media.BUCKET_DISPLAY_NAME));
long size = cursorPhotos.getLong(cursorPhotos.getColumnIndex(MediaStore.Images.Media.SIZE));
if (!isValidImageFile(path)) {
Log.d("PICKER", "it is not a vaild path:" + path);
continue;
}
List<PhotoInfo> photoList = new ArrayList<PhotoInfo>();
photoInfo = new PhotoInfo();
if (hash.containsKey(album)) {
albumInfo = hash.remove(album);
if (albumInfolist.contains(albumInfo))
index = albumInfolist.indexOf(albumInfo);
photoInfo.setImageId(_id);
photoInfo.setFilePath(FILE_PREFIX + path);
photoInfo.setAbsolutePath(path);
photoInfo.setSize(size);
albumInfo.getList().add(photoInfo);
albumInfolist.set(index, albumInfo);
hash.put(album, albumInfo);
} else {
albumInfo = new AlbumInfo();
photoList.clear();
photoInfo.setImageId(_id);
photoInfo.setFilePath(FILE_PREFIX + path);
photoInfo.setAbsolutePath(path);
photoInfo.setSize(size);
photoList.add(photoInfo);
albumInfo.setImageId(_id);
albumInfo.setFilePath(FILE_PREFIX + path);
albumInfo.setAbsolutePath(path);
albumInfo.setAlbumName(album);
albumInfo.setList(photoList);
albumInfolist.add(albumInfo);
hash.put(album, albumInfo);
}
} while (cursorPhotos.moveToNext());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (cursorPhotos != null) {
cursorPhotos.close();
}
} catch (Exception e) {
}
}
}
private boolean isValidImageFile(String filePath) {
if (TextUtils.isEmpty(filePath)) {
return false;
}
File imageFile = new File(filePath);
if (imageFile.exists()) {
return true;
}
return false;
}
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int position, long arg3) {
onAlbumItemClickListener.OnAlbumItemClick(albumInfolist.get(position));
}
}

View File

@@ -11,6 +11,7 @@ public class PhotoInfo implements Serializable {
private String absolutePath;
private long size;
private boolean choose = false;
private int index;
public int getImageId() {
return imageId;
@@ -51,4 +52,24 @@ public class PhotoInfo implements Serializable {
public void setSize(long size) {
this.size = size;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
@Override
public String toString() {
return "PhotoInfo{" +
"imageId=" + imageId +
", filePath='" + filePath + '\'' +
", absolutePath='" + absolutePath + '\'' +
", size=" + size +
", choose=" + choose +
", index=" + index +
'}';
}
}