package com.netease.nim.uikit.session.module.input;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.SystemClock;
import android.text.Editable;
import android.text.InputType;
import android.text.Spannable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.Chronometer;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.alibaba.fastjson.JSONObject;
import com.netease.nim.uikit.NimUIKit;
import com.netease.nim.uikit.R;
import com.netease.nim.uikit.common.ui.dialog.EasyAlertDialogHelper;
import com.netease.nim.uikit.common.util.log.LogUtil;
import com.netease.nim.uikit.common.util.string.StringUtil;
import com.netease.nim.uikit.recent.AitHelper;
import com.netease.nim.uikit.session.SessionCustomization;
import com.netease.nim.uikit.session.actions.BaseAction;
import com.netease.nim.uikit.session.emoji.EmoticonPickerView;
import com.netease.nim.uikit.session.emoji.IEmoticonSelectedListener;
import com.netease.nim.uikit.session.emoji.MoonUtil;
import com.netease.nim.uikit.session.module.Container;
import com.netease.nim.uikit.team.model.TeamExtras;
import com.netease.nim.uikit.team.model.TeamRequestCode;
import com.netease.nimlib.sdk.NIMClient;
import com.netease.nimlib.sdk.media.record.AudioRecorder;
import com.netease.nimlib.sdk.media.record.IAudioRecordCallback;
import com.netease.nimlib.sdk.media.record.RecordType;
import com.netease.nimlib.sdk.msg.MessageBuilder;
import com.netease.nimlib.sdk.msg.MsgService;
import com.netease.nimlib.sdk.msg.attachment.MsgAttachment;
import com.netease.nimlib.sdk.msg.constant.SessionTypeEnum;
import com.netease.nimlib.sdk.msg.model.CustomNotification;
import com.netease.nimlib.sdk.msg.model.CustomNotificationConfig;
import com.netease.nimlib.sdk.msg.model.IMMessage;
import com.netease.nimlib.sdk.team.model.TeamMember;
import java.io.File;
import java.util.List;
/**
* 底部文本编辑,语音等模块
* Created by hzxuwen on 2015/6/16.
*/
public class InputPanel implements IEmoticonSelectedListener, IAudioRecordCallback {
private static final String TAG = "MsgSendLayout";
private static final int SHOW_LAYOUT_DELAY = 200;
protected Container container;
protected View view;
protected Handler uiHandler;
protected View actionPanelBottomLayout; // 更多布局
protected LinearLayout messageActivityBottomLayout;
protected EditText messageEditText;// 文本消息编辑框
protected Button audioRecordBtn; // 录音按钮
protected View audioAnimLayout; // 录音动画布局
protected FrameLayout textAudioSwitchLayout; // 切换文本,语音按钮布局
protected View switchToTextButtonInInputBar;// 文本消息选择按钮
protected View switchToAudioButtonInInputBar;// 语音消息选择按钮
protected View moreFuntionButtonInInputBar;// 更多消息选择按钮
protected View sendMessageButtonInInputBar;// 发送消息按钮
protected View emojiButtonInInputBar;// 发送消息按钮
protected View messageInputBar;
private SessionCustomization customization;
// 表情
protected EmoticonPickerView emoticonPickerView; // 贴图表情控件
// 语音
protected AudioRecorder audioMessageHelper;
private Chronometer time;
private TextView timerTip;
private LinearLayout timerTipContainer;
private boolean started = false;
private boolean cancelled = false;
private boolean touched = false; // 是否按着
private boolean isKeyboardShowed = true; // 是否显示键盘
// state
private boolean actionPanelBottomLayoutHasSetup = false;
private boolean isTextAudioSwitchShow = true;
// adapter
private List<BaseAction> actions;
// data
private long typingTime = 0;
// message edit watcher
private MessageEditWatcher watcher;
public InputPanel(Container container, View view, List<BaseAction> actions, boolean isTextAudioSwitchShow) {
this.container = container;
this.view = view;
this.actions = actions;
this.uiHandler = new Handler();
this.isTextAudioSwitchShow = isTextAudioSwitchShow;
init();
}
public InputPanel(Container container, View view, List<BaseAction> actions) {
this(container, view, actions, true);
}
public void onPause() {
// 停止录音
if (audioMessageHelper != null) {
onEndAudioRecord(true);
}
}
public void onDestroy() {
// release
if (audioMessageHelper != null) {
audioMessageHelper.destroyAudioRecorder();
}
}
public boolean collapse(boolean immediately) {
boolean respond = (emoticonPickerView != null && emoticonPickerView.getVisibility() == View.VISIBLE
|| actionPanelBottomLayout != null && actionPanelBottomLayout.getVisibility() == View.VISIBLE);
hideAllInputLayout(immediately);
return respond;
}
public void setWatcher(MessageEditWatcher watcher) {
this.watcher = watcher;
}
private void init() {
initViews();
initInputBarListener();
initTextEdit();
initAudioRecordButton();
restoreText(false);
for (int i = 0; i < actions.size(); ++i) {
actions.get(i).setIndex(i);
actions.get(i).setContainer(container);
}
}
public void setCustomization(SessionCustomization customization) {
this.customization = customization;
if (customization != null) {
emoticonPickerView.setWithSticker(customization.withSticker);
}
}
public void reload(Container container, SessionCustomization customization) {
this.container = container;
setCustomization(customization);
}
private void initViews() {
// input bar
messageActivityBottomLayout = (LinearLayout) view.findViewById(R.id.messageActivityBottomLayout);
messageInputBar = view.findViewById(R.id.textMessageLayout);
switchToTextButtonInInputBar = view.findViewById(R.id.buttonTextMessage);
switchToAudioButtonInInputBar = view.findViewById(R.id.buttonAudioMessage);
moreFuntionButtonInInputBar = view.findViewById(R.id.buttonMoreFuntionInText);
emojiButtonInInputBar = view.findViewById(R.id.emoji_button);
sendMessageButtonInInputBar = view.findViewById(R.id.buttonSendMessage);
messageEditText = (EditText) view.findViewById(R.id.editTextMessage);
// 语音
audioRecordBtn = (Button) view.findViewById(R.id.audioRecord);
audioAnimLayout = view.findViewById(R.id.layoutPlayAudio);
time = (Chronometer) view.findViewById(R.id.timer);
timerTip = (TextView) view.findViewById(R.id.timer_tip);
timerTipContainer = (LinearLayout) view.findViewById(R.id.timer_tip_container);
// 表情
emoticonPickerView = (EmoticonPickerView) view.findViewById(R.id.emoticon_picker_view);
// 显示录音按钮
switchToTextButtonInInputBar.setVisibility(View.GONE);
switchToAudioButtonInInputBar.setVisibility(View.VISIBLE);
// 文本录音按钮切换布局
textAudioSwitchLayout = (FrameLayout) view.findViewById(R.id.switchLayout);
if (isTextAudioSwitchShow) {
textAudioSwitchLayout.setVisibility(View.VISIBLE);
} else {
textAudioSwitchLayout.setVisibility(View.GONE);
}
}
private void initInputBarListener() {
switchToTextButtonInInputBar.setOnClickListener(clickListener);
switchToAudioButtonInInputBar.setOnClickListener(clickListener);
emojiButtonInInputBar.setOnClickListener(clickListener);
sendMessageButtonInInputBar.setOnClickListener(clickListener);
moreFuntionButtonInInputBar.setOnClickListener(clickListener);
}
private void initTextEdit() {
messageEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
messageEditText.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
switchToTextLayout(true);
}
return false;
}
});
messageEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
messageEditText.setHint("");
checkSendButtonEnable(messageEditText);
}
});
messageEditText.addTextChangedListener(new TextWatcher() {
private int start;
private int count;
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
this.start = start;
this.count = count;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable s) {
checkSendButtonEnable(messageEditText);
MoonUtil.replaceEmoticons(container.activity, s, start, count);
if (watcher != null) {
watcher.afterTextChanged(s, start, count);
}
int editEnd = messageEditText.getSelectionEnd();
messageEditText.removeTextChangedListener(this);
while (StringUtil.counterChars(s.toString()) > 5000 && editEnd > 0) {
s.delete(editEnd - 1, editEnd);
editEnd--;
}
messageEditText.setSelection(editEnd);
messageEditText.addTextChangedListener(this);
sendTypingCommand();
}
});
}
/**
* 发送“正在输入”通知
*/
private void sendTypingCommand() {
if (container.account.equals(NimUIKit.getAccount())) {
return;
}
if (container.sessionType == SessionTypeEnum.Team || container.sessionType == SessionTypeEnum.ChatRoom) {
return;
}
if (System.currentTimeMillis() - typingTime > 5000L) {
typingTime = System.currentTimeMillis();
CustomNotification command = new CustomNotification();
command.setSessionId(container.account);
command.setSessionType(container.sessionType);
CustomNotificationConfig config = new CustomNotificationConfig();
config.enablePush = false;
config.enableUnreadCount = false;
command.setConfig(config);
JSONObject json = new JSONObject();
json.put("id", "1");
command.setContent(json.toString());
NIMClient.getService(MsgService.class).sendCustomNotification(command);
}
}
/**
* ************************* 键盘布局切换 *******************************
*/
private View.OnClickListener clickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (v == switchToTextButtonInInputBar) {
switchToTextLayout(true);// 显示文本发送的布局
} else if (v == sendMessageButtonInInputBar) {
onTextMessageSendButtonPressed();
} else if (v == switchToAudioButtonInInputBar) {
switchToAudioLayout();
} else if (v == moreFuntionButtonInInputBar) {
toggleActionPanelLayout();
} else if (v == emojiButtonInInputBar) {
toggleEmojiLayout();
}
}
};
// 点击edittext,切换键盘和更多布局
private void switchToTextLayout(boolean needShowInput) {
hideEmojiLayout();
hideActionPanelLayout();
audioRecordBtn.setVisibility(View.GONE);
messageEditText.setVisibility(View.VISIBLE);
switchToTextButtonInInputBar.setVisibility(View.GONE);
switchToAudioButtonInInputBar.setVisibility(View.VISIBLE);
messageInputBar.setVisibility(View.VISIBLE);
if (needShowInput) {
uiHandler.postDelayed(showTextRunnable, SHOW_LAYOUT_DELAY);
} else {
hideInputMethod();
}
}
// 发送文本消息
private void onTextMessageSendButtonPressed() {
String text = messageEditText.getText().toString();
IMMessage textMessage = createTextMessage(text);
if (container.proxy.sendMessage(textMessage)) {
restoreText(true);
}
}
protected IMMessage createTextMessage(String text) {
return MessageBuilder.createTextMessage(container.account, container.sessionType, text);
}
// 切换成音频,收起键盘,按钮切换成键盘
private void switchToAudioLayout() {
messageEditText.setVisibility(View.GONE);
audioRecordBtn.setVisibility(View.VISIBLE);
hideInputMethod();
hideEmojiLayout();
hideActionPanelLayout();
switchToAudioButtonInInputBar.setVisibility(View.GONE);
switchToTextButtonInInputBar.setVisibility(View.VISIBLE);
}
// 点击“+”号按钮,切换更多布局和键盘
private void toggleActionPanelLayout() {
if (actionPanelBottomLayout == null || actionPanelBottomLayout.getVisibility() == View.GONE) {
showActionPanelLayout();
} else {
hideActionPanelLayout();
}
}
// 点击表情,切换到表情布局
private void toggleEmojiLayout() {
if (emoticonPickerView == null || emoticonPickerView.getVisibility() == View.GONE) {
showEmojiLayout();
} else {
hideEmojiLayout();
}
}
// 隐藏表情布局
private void hideEmojiLayout() {
uiHandler.removeCallbacks(showEmojiRunnable);
if (emoticonPickerView != null) {
emoticonPickerView.setVisibility(View.GONE);
}
}
// 隐藏更多布局
private void hideActionPanelLayout() {
uiHandler.removeCallbacks(showMoreFuncRunnable);
if (actionPanelBottomLayout != null) {
actionPanelBottomLayout.setVisibility(View.GONE);
}
}
// 隐藏键盘布局
private void hideInputMethod() {
isKeyboardShowed = false;
uiHandler.removeCallbacks(showTextRunnable);
InputMethodManager imm = (InputMethodManager) container.activity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(messageEditText.getWindowToken(), 0);
messageEditText.clearFocus();
}
// 隐藏语音布局
private void hideAudioLayout() {
audioRecordBtn.setVisibility(View.GONE);
messageEditText.setVisibility(View.VISIBLE);
switchToTextButtonInInputBar.setVisibility(View.VISIBLE);
switchToAudioButtonInInputBar.setVisibility(View.GONE);
}
// 显示表情布局
private void showEmojiLayout() {
hideInputMethod();
hideActionPanelLayout();
hideAudioLayout();
messageEditText.requestFocus();
uiHandler.postDelayed(showEmojiRunnable, 200);
emoticonPickerView.setVisibility(View.VISIBLE);
emoticonPickerView.show(this);
container.proxy.onInputPanelExpand();
}
// 初始化更多布局
private void addActionPanelLayout() {
if (actionPanelBottomLayout == null) {
View.inflate(container.activity, R.layout.nim_message_activity_actions_layout, messageActivityBottomLayout);
actionPanelBottomLayout = view.findViewById(R.id.actionsLayout);
actionPanelBottomLayoutHasSetup = false;
}
initActionPanelLayout();
}
// 显示键盘布局
private void showInputMethod(EditText editTextMessage) {
editTextMessage.requestFocus();
//如果已经显示,则继续操作时不需要把光标定位到最后
if (!isKeyboardShowed) {
editTextMessage.setSelection(editTextMessage.getText().length());
isKeyboardShowed = true;
}
InputMethodManager imm = (InputMethodManager) container.activity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(editTextMessage, 0);
container.proxy.onInputPanelExpand();
}
// 显示更多布局
private void showActionPanelLayout() {
addActionPanelLayout();
hideEmojiLayout();
hideInputMethod();
uiHandler.postDelayed(showMoreFuncRunnable, SHOW_LAYOUT_DELAY);
container.proxy.onInputPanelExpand();
}
// 初始化具体more layout中的项目
private void initActionPanelLayout() {
if (actionPanelBottomLayoutHasSetup) {
return;
}
ActionsPanel.init(view, actions);
actionPanelBottomLayoutHasSetup = true;
}
private Runnable showEmojiRunnable = new Runnable() {
@Override
public void run() {
emoticonPickerView.setVisibility(View.VISIBLE);
}
};
private Runnable showMoreFuncRunnable = new Runnable() {
@Override
public void run() {
actionPanelBottomLayout.setVisibility(View.VISIBLE);
}
};
private Runnable showTextRunnable = new Runnable() {
@Override
public void run() {
showInputMethod(messageEditText);
}
};
private void restoreText(boolean clearText) {
if (clearText) {
messageEditText.setText("");
}
checkSendButtonEnable(messageEditText);
}
/**
* 显示发送或更多
*
* @param editText
*/
private void checkSendButtonEnable(EditText editText) {
String textMessage = editText.getText().toString();
if (!TextUtils.isEmpty(StringUtil.removeBlanks(textMessage)) && editText.hasFocus()) {
moreFuntionButtonInInputBar.setVisibility(View.GONE);
sendMessageButtonInInputBar.setVisibility(View.VISIBLE);
} else {
sendMessageButtonInInputBar.setVisibility(View.GONE);
moreFuntionButtonInInputBar.setVisibility(View.VISIBLE);
}
}
/**
* *************** IEmojiSelectedListener ***************
*/
@Override
public void onEmojiSelected(String key) {
Editable mEditable = messageEditText.getText();
if (key.equals("/DEL")) {
messageEditText.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
} else {
int start = messageEditText.getSelectionStart();
int end = messageEditText.getSelectionEnd();
start = (start < 0 ? 0 : start);
end = (start < 0 ? 0 : end);
mEditable.replace(start, end, key);
}
}
private Runnable hideAllInputLayoutRunnable;
@Override
public void onStickerSelected(String category, String item) {
Log.i("InputPanel", "onStickerSelected, category =" + category + ", sticker =" + item);
if (customization != null) {
MsgAttachment attachment = customization.createStickerAttachment(category, item);
IMMessage stickerMessage = MessageBuilder.createCustomMessage(container.account, container.sessionType, "贴图消息", attachment);
container.proxy.sendMessage(stickerMessage);
}
}
/**
* 隐藏所有输入布局
*/
private void hideAllInputLayout(boolean immediately) {
if (hideAllInputLayoutRunnable == null) {
hideAllInputLayoutRunnable = new Runnable() {
@Override
public void run() {
hideInputMethod();
hideActionPanelLayout();
hideEmojiLayout();
}
};
}
long delay = immediately ? 0 : ViewConfiguration.getDoubleTapTimeout();
uiHandler.postDelayed(hideAllInputLayoutRunnable, delay);
}
/**
* ****************************** 语音 ***********************************
*/
private void initAudioRecordButton() {
audioRecordBtn.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
touched = true;
initAudioRecord();
onStartAudioRecord();
} 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) {
touched = true;
cancelAudioRecord(isCancelled(v, event));
}
return false;
}
});
}
// 上滑取消录音判断
private static boolean isCancelled(View view, MotionEvent event) {
int[] location = new int[2];
view.getLocationOnScreen(location);
if (event.getRawX() < location[0] || event.getRawX() > location[0] + view.getWidth()
|| event.getRawY() < location[1] - 40) {
return true;
}
return false;
}
/**
* 初始化AudioRecord
*/
private void initAudioRecord() {
if (audioMessageHelper == null) {
audioMessageHelper = new AudioRecorder(container.activity, RecordType.AAC, AudioRecorder.DEFAULT_MAX_AUDIO_RECORD_TIME_SECOND, this);
}
}
/**
* 开始语音录制
*/
private void onStartAudioRecord() {
container.activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
audioMessageHelper.startRecord();
cancelled = false;
}
/**
* 结束语音录制
*
* @param cancel
*/
private void onEndAudioRecord(boolean cancel) {
started = false;
container.activity.getWindow().setFlags(0, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
audioMessageHelper.completeRecord(cancel);
audioRecordBtn.setText(R.string.record_audio);
audioRecordBtn.setBackgroundResource(R.drawable.nim_message_input_edittext_box);
stopAudioRecordAnim();
}
/**
* 取消语音录制
*
* @param cancel
*/
private void cancelAudioRecord(boolean cancel) {
// reject
if (!started) {
return;
}
// no change
if (cancelled == cancel) {
return;
}
cancelled = cancel;
updateTimerTip(cancel);
}
/**
* 正在进行语音录制和取消语音录制,界面展示
*
* @param cancel
*/
private void updateTimerTip(boolean cancel) {
if (cancel) {
timerTip.setText(R.string.recording_cancel_tip);
timerTipContainer.setBackgroundResource(R.drawable.nim_cancel_record_red_bg);
} else {
timerTip.setText(R.string.recording_cancel);
timerTipContainer.setBackgroundResource(0);
}
}
/**
* 开始语音录制动画
*/
private void playAudioRecordAnim() {
audioAnimLayout.setVisibility(View.VISIBLE);
time.setBase(SystemClock.elapsedRealtime());
time.start();
}
/**
* 结束语音录制动画
*/
private void stopAudioRecordAnim() {
audioAnimLayout.setVisibility(View.GONE);
time.stop();
time.setBase(SystemClock.elapsedRealtime());
}
// 录音状态回调
@Override
public void onRecordReady() {
}
@Override
public void onRecordStart(File audioFile, RecordType recordType) {
started = true;
if (!touched) {
return;
}
audioRecordBtn.setText(R.string.record_audio_end);
audioRecordBtn.setBackgroundResource(R.drawable.nim_message_input_edittext_box_pressed);
updateTimerTip(false); // 初始化语音动画状态
playAudioRecordAnim();
}
@Override
public void onRecordSuccess(File audioFile, long audioLength, RecordType recordType) {
IMMessage audioMessage = MessageBuilder.createAudioMessage(container.account, container.sessionType, audioFile, audioLength);
container.proxy.sendMessage(audioMessage);
}
@Override
public void onRecordFail() {
if (started) {
Toast.makeText(container.activity, R.string.recording_error, Toast.LENGTH_SHORT).show();
}
}
@Override
public void onRecordCancel() {
}
@Override
public void onRecordReachedMaxTime(final int maxTime) {
stopAudioRecordAnim();
EasyAlertDialogHelper.createOkCancelDiolag(container.activity, "", container.activity.getString(R.string.recording_max_time), false, new EasyAlertDialogHelper.OnDialogActionListener() {
@Override
public void doCancelAction() {
}
@Override
public void doOkAction() {
audioMessageHelper.handleEndRecord(true, maxTime);
}
}).show();
}
public boolean isRecording() {
return audioMessageHelper != null && audioMessageHelper.isRecording();
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK) {
return;
}
// 从@返回
if (requestCode == TeamRequestCode.REQUEST_TEAM_AIT_MEMBER) {
insertAitMember(data);
return;
}
int index = (requestCode << 16) >> 24;
if (index != 0) {
index--;
if (index < 0 | index >= actions.size()) {
LogUtil.d(TAG, "request code out of actions' range");
return;
}
BaseAction action = actions.get(index);
if (action != null) {
action.onActivityResult(requestCode & 0xff, resultCode, data);
}
}
}
private void insertAitMember(Intent data) {
TeamMember member = (TeamMember) data.getSerializableExtra(TeamExtras.RESULT_EXTRA_DATA);
if (member == null) {
return;
}
// insert account
int start = messageEditText.getSelectionStart();
String account = member.getAccount() + " ";
if (start < 0 || start >= messageEditText.length()) {
messageEditText.append(account);
} else {
messageEditText.getEditableText().insert(start, account);// 光标所在位置插入文字
}
// 替换成昵称
String aitName = AitHelper.getAitName(member) + " ";
Editable editable = messageEditText.getText();
aitName = "@" + aitName;
start--;
editable.setSpan(AitHelper.getInputAitSpan(aitName, messageEditText.getTextSize()),
start, start + account.length() + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// 显示键盘
uiHandler.postDelayed(showTextRunnable, SHOW_LAYOUT_DELAY);
}
}