diff --git a/.gitignore b/.gitignore
index 5adf4da..2a9c6d8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,10 @@
bin/
gen/
+/*/*/bin/
+/*/*/gen/
+
+
# Gradle files
.gradle/
build/
diff --git a/README.md b/README.md
index 0fe5089..2a89bc3 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,5 @@
# 美洽移动应用 SDK 3.0 for Android 开发文档
-[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.meiqia/meiqiasdk/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.meiqia/meiqiasdk)
-
## 目录
* [SDK 工作流程](#sdk-工作流程)
* [集成美洽 SDK](#集成美洽-sdk)
@@ -19,11 +17,12 @@
![流程图](https://camo.githubusercontent.com/348661458384df0b282af9d4c5d06101c5e8d4ae/68747470733a2f2f73332e636e2d6e6f7274682d312e616d617a6f6e6177732e636f6d2e636e2f706963732e6d65697169612e6275636b65742f64643430313336306261633364346162)
## 集成美洽 SDK
-### AndroidStudio
+### AndroidStudio [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.meiqia/meiqiasdk/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.meiqia/meiqiasdk)
-```groovy
-// required
-compile 'com.meiqia:meiqiasdk:3.0.0@aar'
+```
+// required
+// 「latestVersion」改成 maven central 徽章后面对应的版本号,例如3.0.3
+compile 'com.meiqia:meiqiasdk:latestVersion@aar'
// 在下面的依赖中,如果你的项目已经依赖过其中的组件,则不需要重复依赖
compile 'com.android.support:appcompat-v7:23.1.1'
@@ -34,7 +33,36 @@ compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
```
### Eclipse
-![GoHome](https://camo.githubusercontent.com/8caa3693b4268c095c001089313d687f647d551a/687474703a2f2f696d67322e77696b69612e6e6f636f6f6b69652e6e65742f5f5f636232303133303831393134323932382f6361726466696768742f696d616765732f7468756d622f352f35352f476f2d686f6d652d796f7572652d6472756e6b2e6a70672f35303070782d476f2d686f6d652d796f7572652d6472756e6b2e6a7067)
+
+1.拷贝 **/eclipse/MeiqiaSdk** 到工作空间并导入 Eclipse 中
+
+2.选中你自己的工程的根目录 -> 右键 -> 选择 Properties -> 选中 Android -> 点击 Library 右边的的 Add 按钮 -> 选中 MeiqiaSdk -> 点击 OK
+
+3.在你自己的工程的 AndroidManifest.xml 文件中添加以下权限
+
+```
+
+
+
+
+
+```
+
+4.在你自己的工程的 AndroidManifest.xml 文件的 application 结点下加入以下代码
+
+```
+
+
+
+```
+
+5.如果你自己的工程中已经添加了 **/eclipse/MeiqiaSdk/libs** 中的 jar 包,拷贝你自己的工程中对应的 jar 包替换 **/eclipse/MeiqiaSdk/libs** 中的 jar 包
## 使用美洽
@@ -46,14 +74,14 @@ MQManager.init(context, "Your Appkey", new OnInitCallBackOn() {
// 初始化成功
Toast.makeText(MainActivity.this, "init success", Toast.LENGTH_SHORT).show();
}
-
+
@Override
public void onFailure(int code, String message) {
// 初始化失败
Toast.makeText(MainActivity.this, "init failure", Toast.LENGTH_SHORT).show();
}
});
-```
+```
如果您不知道 Appkey ,请使用美洽管理员帐号登录 美洽,在「设置」 -> 「SDK」 菜单中查看。如下图:
![获取 Appkey](https://s3.cn-north-1.amazonaws.com.cn/pics.meiqia.bucket/8fbdaa6076d0b9d0)
@@ -65,7 +93,7 @@ MQManager.init(context, "Your Appkey", new OnInitCallBackOn() {
``` java
Intent intent = new Intent(MainActivity.this, MQConversationActivity.class);
startActivity(intent);
-```
+```
### 3.可选设置
* [绑定自定义 id 并设置上线](#绑定自定义-id-并设置上线)
* [绑定美洽 id 并设置上线](#绑定美洽-id-并设置上线)
@@ -90,7 +118,7 @@ MQManager mqManager = MQManager.getInstacne(context);
* @param onlineCallback 回调
*/
setCurrentClientOnline(final OnClientOnlineCallback onlineCallback)
-```
+```
### 绑定美洽 id 并设置上线
开发者可通过 [获取当前顾客的 id](#获取当前顾客的-id) 接口,取得顾客 id ,保存到开发者的服务端,以此来绑定美洽顾客和开发者用户系统。 如果开发者保存了美洽的顾客 id,可调用如下接口让其上线。调用此接口后,当前可用的顾客即为开发者传的顾客 id。
@@ -102,7 +130,7 @@ setCurrentClientOnline(final OnClientOnlineCallback onlineCallback)
* @param onlineCallback 回调接口
*/
setClientOnlineWithClientId(String mqClientId, final OnClientOnlineCallback onlineCallback)
-```
+```
MQConversationActivity.class 内部调用了此接口,所以可以直接简单的在 intent 中添加 CLIENT_ID,启动对话。
**Example:**
@@ -112,7 +140,7 @@ Intent intent = new Intent(DeveloperActivity.this, MQConversationActivity.class)
// 假设 meiqia_id 是美洽生成的顾客 id
intent.putExtra(MQConversationActivity.CLIENT_ID,"meiqia_id");
startActivity(intent);
-```
+```
### 绑定自定义 id 并设置上线
如果开发者不愿保存「美洽顾客 id」来绑定自己的用户系统,也可以将自己的用户 id当做参数,进行顾客的上线,美洽将会为开发者绑定一个顾客,下次开发者直接调用如下接口,就能让这个绑定的顾客上线。
@@ -127,7 +155,7 @@ startActivity(intent);
* @param onlineCallback 回调接口
*/
setClientOnlineWithCustomizedId(String customizedId, final OnClientOnlineCallback onlineCallback)
-```
+```
MQConversationActivity.class 内部调用了此接口,所以可以直接简单的在 intent 中添加 CUSTOMIZED_ID,启动对话。
**Example:**
@@ -137,7 +165,7 @@ Intent intent = new Intent(DeveloperActivity.this, MQConversationActivity.class)
// 假设 developer@dev.com 是开发者的用户 id
intent.putExtra(MQConversationActivity.CUSTOMIZED_ID,"developer@dev.com");
startActivity(intent);
-```
+```
### 指定客服或者分组
美洽默认会按照管理员设置的分配方式智能分配客服,但如果需要让来自 App 的顾客指定分配给某个客服或者某组客服。
@@ -149,7 +177,7 @@ startActivity(intent);
* @param groupId 指定分组的 id,不指定传 null
*/
setScheduledAgentOrGroupWithId(String agentId, String groupId)
-```
+```
**Example:**
``` java
@@ -158,11 +186,11 @@ MQManager.getInstance(DeveloperActivity.this).setScheduledAgentOrGroupWithId(age
// 启动界面
Intent intent = new Intent(DeveloperActivity.this, MQConversationActivity.class);
startActivity(intent);
-```
+```
**注意:**
- 该选项需要在用户上线前设置。
- - 客服组 ID 和客服 ID 可以通过管理员帐号在后台的「设置」中查看。
+ - 客服组 ID 和客服 ID 可以通过管理员帐号在后台的「设置」中查看。
![获取 客服 / 分组 ID](https://camo.githubusercontent.com/63eb2383e2dda083c17eeb16b360777c0e1b0ee9/68747470733a2f2f73332e636e2d6e6f7274682d312e616d617a6f6e6177732e636f6d2e636e2f706963732e6d65697169612e6275636b65742f38636465386235343439316332303365)
@@ -179,7 +207,7 @@ startActivity(intent);
* 如果设置了顾客离线,则客服发送的消息将会发送给开发者的推送服务器
*/
setClientOffline()
-```
+```
### 发送文字消息 / 图片消息 / 语音消息
``` java
@@ -204,7 +232,7 @@ sendMQPhotoMessage(String localPath, final OnMessageSendCallback onMessageSendCa
* @param onMessageSendCallback 消息状态回调
*/
sendMQVoiceMessage(String localPath, final OnMessageSendCallback onMessageSendCallback)
-```
+```
### 从服务器获取历史消息
``` java
/**
@@ -215,7 +243,7 @@ sendMQVoiceMessage(String localPath, final OnMessageSendCallback onMessageSendCa
* @param onGetMessageListCallback 回调
*/
getMQMessageFromService(final long lastMessageCreateOn, final int length, final OnGetMessageListCallback onGetMessageListCallback)
-```
+```
### 从本地获取历史消息
``` java
/**
@@ -226,7 +254,7 @@ getMQMessageFromService(final long lastMessageCreateOn, final int length, final
* @param onGetMessageListCallback 回调
*/
getMQMessageFromDatabase(final long lastMessageCreateOn, final int length, final OnGetMessageListCallback onGetMessageListCallback)
-```
+```
### 设置用户的设备唯一标识
``` java
/**
@@ -235,7 +263,7 @@ getMQMessageFromDatabase(final long lastMessageCreateOn, final int length, final
* @param token 唯一标识
*/
registerDeviceToken(String token, OkHttpUtils.OnRegisterDeviceTokenCallback onRegisterDeviceTokenCallback)
-```
+```
App 进入后台后,美洽推送给开发者服务端的消息数据格式中,会有 deviceToken 的字段。
美洽推送消息给开发者服务端的数据格式,可参考 [推送消息数据结构](#推送消息数据结构)。
@@ -249,13 +277,13 @@ App 进入后台后,美洽推送给开发者服务端的消息数据格式中
* @param onClientInfoCallback 回调
*/
setClientInfo(Map clientInfo, OnClientInfoCallback onClientInfoCallback)
-```
+```
功能效果展示:
![设置顾客信息效果图](https://camo.githubusercontent.com/97de68c05a61ac3e2465bb320d669baffa21cc75/68747470733a2f2f73332e636e2d6e6f7274682d312e616d617a6f6e6177732e636f6d2e636e2f706963732e6d65697169612e6275636b65742f36353565373233343334323363386637)
为了让客服能更准确帮助用户,开发者可上传不同用户的属性信息。示例如下:
-``` java
+``` java
Map info = new HashMap<>();
info.put("name", "富坚义博");
info.put("avatar", "https://s3.cn-north-1.amazonaws.com.cn/pics.meiqia.bucket/1dee88eabfbd7bd4");
@@ -265,7 +293,7 @@ info.put("技能1", "休刊");
info.put("技能2", "外出取材");
info.put("技能3", "打麻将");
MQManager.getInstance(context).setClientInfo(info, new OnClientInfoCallback());
-```
+```
以下字段是美洽定义好的,开发者可通过上方提到的接口,直接对下方的字段进行设置:
|Key|说明|
@@ -291,7 +319,7 @@ MQManager.getInstance(context).setClientInfo(info, new OnClientInfoCallback())
* @return 如果存在,返回当前客服信息;不存在,返回 null
*/
getCurrentAgent()
-```
+```
### 获取当前顾客的 id
``` java
/**
@@ -300,7 +328,7 @@ getCurrentAgent()
* @return 当前顾客 id
*/
getCurrentClientId()
-```
+```
### 获取一个新的顾客
``` java
/**
@@ -309,7 +337,7 @@ getCurrentClientId()
* @param onGetMQClientIdCallBack 回调
*/
createMQClient(OnGetMQClientIdCallBackOn onGetMQClientIdCallBack)
-```
+```
如果开发者想初始化一个新的顾客,可调用此接口。
该顾客没有任何历史记录及用户信息。
@@ -323,7 +351,7 @@ createMQClient(OnGetMQClientIdCallBackOn onGetMQClientIdCallBack)
* @param isRead 将替换的状态
*/
updateMessage(long messageId, boolean isRead)
-```
+```
### 结束当前对话
``` java
/**
@@ -332,7 +360,7 @@ updateMessage(long messageId, boolean isRead)
* @param onEndConversationCallback 回调
*/
endCurrentConversation(OnEndConversationCallback onEndConversationCallback)
-```
+```
### 给客服发送「正在输入」
``` java
/**
@@ -341,14 +369,14 @@ endCurrentConversation(OnEndConversationCallback onEndConversationCallback)
* @param content 正在输入的内容
*/
sendClientInputtingWithContent(String content)
-```
+```
### 开启美洽推送
``` java
/**
* App 退到后台时,需要开启美洽推送
*/
openMeiQiaRemotePushService()
-```
+```
参考 [消息推送](#消息推送)
### 关闭美洽推送
@@ -357,7 +385,7 @@ openMeiQiaRemotePushService()
* App 进入前台时,需要关闭美洽推送
*/
closeMeiQiaRemotePushService()
-```
+```
参考 [消息推送](#消息推送)
### 接收即时消息
@@ -369,7 +397,7 @@ public class MessageReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
//只接收当前应用的广播
String packageName = intent.getStringExtra("packageName");
-
+
if (context.getPackageName().equals(packageName)) {
// 获取 ACTION
final String action = intent.getAction();
@@ -382,12 +410,12 @@ public class MessageReceiver extends BroadcastReceiver {
MQMessage message = messageManager.getMQMessage(msgId);
// do something
}
-
+
// 客服正在输入
else if (MQMessageManager.ACTION_AGENT_INPUTTING.equals(action)) {
// do something
}
-
+
// 客服转接
else if (MQMessageManager.ACTION_AGENT_CHANGE_EVENT.equals(action)) {
// 获取转接后的客服
@@ -397,14 +425,14 @@ public class MessageReceiver extends BroadcastReceiver {
}
}
}
-```
+```
### 获取 SDK 版本号
``` java
/**
* 获取 SDK 版本号
*/
getMeiQiaSDKVersion()
-```
+```
## 消息推送
当前仅支持一种推送方案,即美洽服务端发送消息至开发者的服务端,开发者再推送消息到 App。
@@ -412,7 +440,7 @@ getMeiQiaSDKVersion()
推送消息将会发送至开发者的服务器。
设置服务器地址,请使用美洽管理员帐号登录 [美洽](http://www.meiqia.com),在「设置」 -> 「SDK」中设置。
-
+
![设置推送地址](https://s3.cn-north-1.amazonaws.com.cn/pics.meiqia.bucket/8fbdaa6076d0b9d0)
### 通知美洽服务端发送消息至开发者的服务端
@@ -421,15 +449,14 @@ getMeiQiaSDKVersion()
在 App 进入后台时,应该通知美洽服务端,让其将以后的消息推送给开发者提供的服务器地址,如下代码:
``` java
MQManager.getInstance(context).openMeiQiaRemotePushService();
-```
+```
### 关闭美洽推送
在 App 进入前台时,应该通知美洽服务端,让其将以后的消息发送给SDK,而不再推送给开发者提供的服务端,如下代码:
``` java
MQManager.getInstance(context).closeMeiQiaRemotePushService();
-```
+```
### 推送消息数据结构
(待补充)
当有消息需要推送时,美洽服务器会向开发者设置的服务器地址发送推送消息,方法类型为 POST,数据格式为 JSON 。
-
diff --git a/eclipse/MeiqiaSdk/AndroidManifest.xml b/eclipse/MeiqiaSdk/AndroidManifest.xml
new file mode 100644
index 0000000..0b6bf91
--- /dev/null
+++ b/eclipse/MeiqiaSdk/AndroidManifest.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/libs/Java-WebSocket-1.3.0.jar b/eclipse/MeiqiaSdk/libs/Java-WebSocket-1.3.0.jar
new file mode 100644
index 0000000..5263cfb
Binary files /dev/null and b/eclipse/MeiqiaSdk/libs/Java-WebSocket-1.3.0.jar differ
diff --git a/eclipse/MeiqiaSdk/libs/PhotoView-1.2.4.jar b/eclipse/MeiqiaSdk/libs/PhotoView-1.2.4.jar
new file mode 100644
index 0000000..e4d8809
Binary files /dev/null and b/eclipse/MeiqiaSdk/libs/PhotoView-1.2.4.jar differ
diff --git a/eclipse/MeiqiaSdk/libs/android-support-v4.jar b/eclipse/MeiqiaSdk/libs/android-support-v4.jar
new file mode 100644
index 0000000..aa0b1a5
Binary files /dev/null and b/eclipse/MeiqiaSdk/libs/android-support-v4.jar differ
diff --git a/eclipse/MeiqiaSdk/libs/meiqia-3.0.0.jar b/eclipse/MeiqiaSdk/libs/meiqia-3.0.0.jar
new file mode 100644
index 0000000..1c677b6
Binary files /dev/null and b/eclipse/MeiqiaSdk/libs/meiqia-3.0.0.jar differ
diff --git a/eclipse/MeiqiaSdk/libs/okhttp-2.7.0.jar b/eclipse/MeiqiaSdk/libs/okhttp-2.7.0.jar
new file mode 100644
index 0000000..3de98f0
Binary files /dev/null and b/eclipse/MeiqiaSdk/libs/okhttp-2.7.0.jar differ
diff --git a/eclipse/MeiqiaSdk/libs/okio-1.6.0.jar b/eclipse/MeiqiaSdk/libs/okio-1.6.0.jar
new file mode 100644
index 0000000..c87be59
Binary files /dev/null and b/eclipse/MeiqiaSdk/libs/okio-1.6.0.jar differ
diff --git a/eclipse/MeiqiaSdk/libs/universal-image-loader-1.9.5.jar b/eclipse/MeiqiaSdk/libs/universal-image-loader-1.9.5.jar
new file mode 100644
index 0000000..520dac3
Binary files /dev/null and b/eclipse/MeiqiaSdk/libs/universal-image-loader-1.9.5.jar differ
diff --git a/eclipse/MeiqiaSdk/proguard-project.txt b/eclipse/MeiqiaSdk/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/eclipse/MeiqiaSdk/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/eclipse/MeiqiaSdk/project.properties b/eclipse/MeiqiaSdk/project.properties
new file mode 100644
index 0000000..b2ef7dc
--- /dev/null
+++ b/eclipse/MeiqiaSdk/project.properties
@@ -0,0 +1,15 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-23
+android.library=true
diff --git a/eclipse/MeiqiaSdk/res/anim/mq_loading.xml b/eclipse/MeiqiaSdk/res/anim/mq_loading.xml
new file mode 100644
index 0000000..3111e2f
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/anim/mq_loading.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_edit_view.9.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_edit_view.9.png
new file mode 100644
index 0000000..39feb02
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_edit_view.9.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_edit_view_pressed.9.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_edit_view_pressed.9.png
new file mode 100644
index 0000000..7a972d5
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_edit_view_pressed.9.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_input_box.9.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_input_box.9.png
new file mode 100644
index 0000000..36fb3e3
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_input_box.9.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_msg_image_cover_left.9.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_msg_image_cover_left.9.png
new file mode 100644
index 0000000..5616c8a
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_msg_image_cover_left.9.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_msg_image_cover_right.9.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_msg_image_cover_right.9.png
new file mode 100644
index 0000000..4095514
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_msg_image_cover_right.9.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_msg_left.9.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_msg_left.9.png
new file mode 100644
index 0000000..00730e4
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_msg_left.9.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_msg_right.9.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_msg_right.9.png
new file mode 100644
index 0000000..b2e37af
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_msg_right.9.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_title.9.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_title.9.png
new file mode 100644
index 0000000..a416b34
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_title.9.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_transparent.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_transparent.png
new file mode 100644
index 0000000..a1d202b
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_bg_transparent.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_1.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_1.png
new file mode 100644
index 0000000..ab50176
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_1.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_10.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_10.png
new file mode 100644
index 0000000..67bdea9
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_10.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_11.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_11.png
new file mode 100644
index 0000000..77028dd
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_11.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_12.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_12.png
new file mode 100644
index 0000000..ec85f2d
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_12.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_13.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_13.png
new file mode 100644
index 0000000..6dd9b7e
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_13.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_14.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_14.png
new file mode 100644
index 0000000..119288f
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_14.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_15.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_15.png
new file mode 100644
index 0000000..c887934
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_15.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_16.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_16.png
new file mode 100644
index 0000000..70cfb06
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_16.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_17.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_17.png
new file mode 100644
index 0000000..4e7e69e
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_17.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_18.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_18.png
new file mode 100644
index 0000000..26bb670
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_18.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_19.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_19.png
new file mode 100644
index 0000000..9089bf1
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_19.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_2.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_2.png
new file mode 100644
index 0000000..2394c81
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_2.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_20.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_20.png
new file mode 100644
index 0000000..8620825
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_20.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_21.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_21.png
new file mode 100644
index 0000000..4ca66a2
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_21.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_22.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_22.png
new file mode 100644
index 0000000..5ec1b17
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_22.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_23.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_23.png
new file mode 100644
index 0000000..7a4c102
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_23.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_24.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_24.png
new file mode 100644
index 0000000..7a4c102
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_24.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_25.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_25.png
new file mode 100644
index 0000000..41031d7
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_25.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_26.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_26.png
new file mode 100644
index 0000000..fd78bc3
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_26.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_27.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_27.png
new file mode 100644
index 0000000..59856db
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_27.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_28.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_28.png
new file mode 100644
index 0000000..2d04d85
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_28.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_29.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_29.png
new file mode 100644
index 0000000..831aeb3
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_29.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_3.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_3.png
new file mode 100644
index 0000000..0c7e237
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_3.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_30.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_30.png
new file mode 100644
index 0000000..323f4a5
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_30.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_31.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_31.png
new file mode 100644
index 0000000..7aab68b
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_31.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_32.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_32.png
new file mode 100644
index 0000000..1c86536
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_32.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_33.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_33.png
new file mode 100644
index 0000000..5fba732
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_33.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_34.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_34.png
new file mode 100644
index 0000000..829abd0
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_34.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_35.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_35.png
new file mode 100644
index 0000000..e970d70
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_35.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_36.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_36.png
new file mode 100644
index 0000000..6d01720
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_36.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_4.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_4.png
new file mode 100644
index 0000000..0c9e68a
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_4.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_5.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_5.png
new file mode 100644
index 0000000..26bb670
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_5.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_6.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_6.png
new file mode 100644
index 0000000..92903ab
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_6.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_7.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_7.png
new file mode 100644
index 0000000..24e379f
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_7.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_8.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_8.png
new file mode 100644
index 0000000..ae266ba
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_8.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_9.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_9.png
new file mode 100644
index 0000000..749677c
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_9.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_delete.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_delete.png
new file mode 100644
index 0000000..f07386a
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_emoji_delete.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_back.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_back.png
new file mode 100644
index 0000000..472f3ee
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_back.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_camera_nor.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_camera_nor.png
new file mode 100644
index 0000000..4b8f861
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_camera_nor.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_camera_pressed.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_camera_pressed.png
new file mode 100644
index 0000000..ff3fcb8
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_camera_pressed.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_emoji_btn_nor.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_emoji_btn_nor.png
new file mode 100644
index 0000000..28941be
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_emoji_btn_nor.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_emoji_btn_pressed.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_emoji_btn_pressed.png
new file mode 100644
index 0000000..a243ecb
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_emoji_btn_pressed.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_keyboard_nor.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_keyboard_nor.png
new file mode 100644
index 0000000..09561ec
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_keyboard_nor.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_keyboard_pressed.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_keyboard_pressed.png
new file mode 100644
index 0000000..4a56171
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_keyboard_pressed.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_mid_record_mic_nor.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_mid_record_mic_nor.png
new file mode 100644
index 0000000..73e1e79
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_mid_record_mic_nor.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_mid_record_pressed.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_mid_record_pressed.png
new file mode 100644
index 0000000..3fa977d
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_mid_record_pressed.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_msg_failed.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_msg_failed.png
new file mode 100644
index 0000000..6e1fbc6
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_msg_failed.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_nor.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_nor.png
new file mode 100644
index 0000000..55313e2
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_nor.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_play.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_play.png
new file mode 100644
index 0000000..f97bf3b
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_play.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_bg.9.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_bg.9.png
new file mode 100644
index 0000000..78245ee
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_bg.9.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_cancel.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_cancel.png
new file mode 100644
index 0000000..930d8f1
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_cancel.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_0.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_0.png
new file mode 100644
index 0000000..0814b15
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_0.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_1.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_1.png
new file mode 100644
index 0000000..9d48d19
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_1.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_2.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_2.png
new file mode 100644
index 0000000..6b2b2f5
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_2.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_3.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_3.png
new file mode 100644
index 0000000..d6d4cb8
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_3.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_4.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_4.png
new file mode 100644
index 0000000..b064b4f
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_4.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_5.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_5.png
new file mode 100644
index 0000000..08f900f
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_5.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_6.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_6.png
new file mode 100644
index 0000000..0c36025
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_6.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_7.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_7.png
new file mode 100644
index 0000000..6342ac8
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_7.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_8.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_8.png
new file mode 100644
index 0000000..2e3c424
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pop_mic_8.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pressed.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pressed.png
new file mode 100644
index 0000000..b7ab858
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_pressed.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_stop.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_stop.png
new file mode 100644
index 0000000..4bbba15
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_ic_voice_stop.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_loading_1.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_loading_1.png
new file mode 100644
index 0000000..e01b9c6
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_loading_1.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_loading_2.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_loading_2.png
new file mode 100644
index 0000000..7053f99
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_loading_2.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_loading_3.png b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_loading_3.png
new file mode 100644
index 0000000..1285d6b
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/drawable-xhdpi/mq_loading_3.png differ
diff --git a/eclipse/MeiqiaSdk/res/drawable/mq_camera_btn_background.xml b/eclipse/MeiqiaSdk/res/drawable/mq_camera_btn_background.xml
new file mode 100644
index 0000000..63ad6d7
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/drawable/mq_camera_btn_background.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/eclipse/MeiqiaSdk/res/drawable/mq_emoji_btn_background.xml b/eclipse/MeiqiaSdk/res/drawable/mq_emoji_btn_background.xml
new file mode 100644
index 0000000..5bd4b8e
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/drawable/mq_emoji_btn_background.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/eclipse/MeiqiaSdk/res/drawable/mq_keyboard_btn_background.xml b/eclipse/MeiqiaSdk/res/drawable/mq_keyboard_btn_background.xml
new file mode 100644
index 0000000..6f85eb1
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/drawable/mq_keyboard_btn_background.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/eclipse/MeiqiaSdk/res/drawable/mq_leave_tip_gradient_line.xml b/eclipse/MeiqiaSdk/res/drawable/mq_leave_tip_gradient_line.xml
new file mode 100644
index 0000000..971cebf
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/drawable/mq_leave_tip_gradient_line.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/drawable/mq_red_circle.xml b/eclipse/MeiqiaSdk/res/drawable/mq_red_circle.xml
new file mode 100644
index 0000000..3226fff
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/drawable/mq_red_circle.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/drawable/mq_selector_emotion_indicator.xml b/eclipse/MeiqiaSdk/res/drawable/mq_selector_emotion_indicator.xml
new file mode 100644
index 0000000..15fcde3
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/drawable/mq_selector_emotion_indicator.xml
@@ -0,0 +1,14 @@
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/drawable/mq_selector_item_bottom.xml b/eclipse/MeiqiaSdk/res/drawable/mq_selector_item_bottom.xml
new file mode 100644
index 0000000..67ca78c
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/drawable/mq_selector_item_bottom.xml
@@ -0,0 +1,15 @@
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/drawable/mq_selector_item_bottom_left.xml b/eclipse/MeiqiaSdk/res/drawable/mq_selector_item_bottom_left.xml
new file mode 100644
index 0000000..2f24867
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/drawable/mq_selector_item_bottom_left.xml
@@ -0,0 +1,15 @@
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/drawable/mq_selector_item_bottom_right.xml b/eclipse/MeiqiaSdk/res/drawable/mq_selector_item_bottom_right.xml
new file mode 100644
index 0000000..fffba94
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/drawable/mq_selector_item_bottom_right.xml
@@ -0,0 +1,15 @@
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/drawable/mq_selector_item_center.xml b/eclipse/MeiqiaSdk/res/drawable/mq_selector_item_center.xml
new file mode 100644
index 0000000..6f4597d
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/drawable/mq_selector_item_center.xml
@@ -0,0 +1,13 @@
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/drawable/mq_selector_item_top.xml b/eclipse/MeiqiaSdk/res/drawable/mq_selector_item_top.xml
new file mode 100644
index 0000000..b495450
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/drawable/mq_selector_item_top.xml
@@ -0,0 +1,15 @@
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/drawable/mq_send_text_selector.xml b/eclipse/MeiqiaSdk/res/drawable/mq_send_text_selector.xml
new file mode 100644
index 0000000..9c9ea87
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/drawable/mq_send_text_selector.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/drawable/mq_shape_dialog_bg.xml b/eclipse/MeiqiaSdk/res/drawable/mq_shape_dialog_bg.xml
new file mode 100644
index 0000000..31d3797
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/drawable/mq_shape_dialog_bg.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/drawable/mq_voice_btn_background.xml b/eclipse/MeiqiaSdk/res/drawable/mq_voice_btn_background.xml
new file mode 100644
index 0000000..58af704
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/drawable/mq_voice_btn_background.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/eclipse/MeiqiaSdk/res/layout/mq_activity_conversation.xml b/eclipse/MeiqiaSdk/res/layout/mq_activity_conversation.xml
new file mode 100644
index 0000000..28defd4
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/layout/mq_activity_conversation.xml
@@ -0,0 +1,182 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/layout/mq_dialog_choose_pic.xml b/eclipse/MeiqiaSdk/res/layout/mq_dialog_choose_pic.xml
new file mode 100644
index 0000000..5fc469f
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/layout/mq_dialog_choose_pic.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/layout/mq_dialog_confirm.xml b/eclipse/MeiqiaSdk/res/layout/mq_dialog_confirm.xml
new file mode 100644
index 0000000..8864bb2
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/layout/mq_dialog_confirm.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/layout/mq_dialog_view_photo.xml b/eclipse/MeiqiaSdk/res/layout/mq_dialog_view_photo.xml
new file mode 100644
index 0000000..bbc7e7c
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/layout/mq_dialog_view_photo.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/layout/mq_edit_toolbar.xml b/eclipse/MeiqiaSdk/res/layout/mq_edit_toolbar.xml
new file mode 100644
index 0000000..6a61e6d
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/layout/mq_edit_toolbar.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/layout/mq_item_chat_left.xml b/eclipse/MeiqiaSdk/res/layout/mq_item_chat_left.xml
new file mode 100644
index 0000000..23786ff
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/layout/mq_item_chat_left.xml
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/layout/mq_item_chat_right.xml b/eclipse/MeiqiaSdk/res/layout/mq_item_chat_right.xml
new file mode 100644
index 0000000..a535e58
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/layout/mq_item_chat_right.xml
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/layout/mq_item_chat_time.xml b/eclipse/MeiqiaSdk/res/layout/mq_item_chat_time.xml
new file mode 100644
index 0000000..530c198
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/layout/mq_item_chat_time.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/layout/mq_item_emotion.xml b/eclipse/MeiqiaSdk/res/layout/mq_item_emotion.xml
new file mode 100644
index 0000000..1e91dd3
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/layout/mq_item_emotion.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/layout/mq_item_msg_tip.xml b/eclipse/MeiqiaSdk/res/layout/mq_item_msg_tip.xml
new file mode 100644
index 0000000..b3137e3
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/layout/mq_item_msg_tip.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/layout/mq_voice_pop.xml b/eclipse/MeiqiaSdk/res/layout/mq_voice_pop.xml
new file mode 100644
index 0000000..6261324
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/layout/mq_voice_pop.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/raw/mq_message.mp3 b/eclipse/MeiqiaSdk/res/raw/mq_message.mp3
new file mode 100644
index 0000000..fe4445b
Binary files /dev/null and b/eclipse/MeiqiaSdk/res/raw/mq_message.mp3 differ
diff --git a/eclipse/MeiqiaSdk/res/values/mq_colors.xml b/eclipse/MeiqiaSdk/res/values/mq_colors.xml
new file mode 100644
index 0000000..740749f
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/values/mq_colors.xml
@@ -0,0 +1,30 @@
+
+
+
+ #0f5a87
+
+
+ #ffffffff
+ #F7F8FA
+ #808080
+ #000000
+ #aaaaaa
+ #333333
+ #FFFFFF
+ #c9c9ce
+ #f6bc66
+ #e66b62
+ #E6E6E6
+ #43CAA9
+ #43CAA9
+ #00FFFFFF
+
+ #808080
+ #595959
+ @android:color/white
+
+ #01000000
+
+ #EFEFEF
+ @android:color/white
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/values/mq_dimens.xml b/eclipse/MeiqiaSdk/res/values/mq_dimens.xml
new file mode 100644
index 0000000..ccfd6c0
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/values/mq_dimens.xml
@@ -0,0 +1,19 @@
+
+
+ 48dp
+ 50dp
+ 18sp
+ 4dp
+ 8dp
+ 12dp
+ 16dp
+ 20dp
+ 24dp
+ 28dp
+ 32dp
+ 36dp
+ 40dp
+
+ 12sp
+ 16sp
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/values/mq_strings.xml b/eclipse/MeiqiaSdk/res/values/mq_strings.xml
new file mode 100644
index 0000000..c8ebb76
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/values/mq_strings.xml
@@ -0,0 +1,33 @@
+
+
+ 发送
+ 网络加载失败
+ 复制成功
+ 相机
+ 图库
+ 请选择图片来源:
+ 留言
+ 未知错误
+ 正在输入...
+ 请输入...
+ 正在分配客服...
+ 抱歉,现在没有客服人员在线\n你可以继续写下你的问题,我们会尽快回复
+ 当前设备不支持发送图片
+ SD卡不存在
+ 当前设别不支持语音
+ 上滑,取消发送
+ 取消发送
+ 录音失败,请检查是否有录音权限
+ 录音时间太短
+ 还可以再说 %1$d 秒
+ 今天
+ 昨天
+ 数据加载中...
+ 接下来由 %1$s 为你服务
+ 确定
+ 取消
+ 咨询客服需要以下权限
+ 1.访问设备上的照片\n2.录制音频
+ 您拒绝了咨询客服所需要的相关权限!
+ 返回
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/res/values/mq_styles.xml b/eclipse/MeiqiaSdk/res/values/mq_styles.xml
new file mode 100644
index 0000000..9dba633
--- /dev/null
+++ b/eclipse/MeiqiaSdk/res/values/mq_styles.xml
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/activity/MQConversationActivity.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/activity/MQConversationActivity.java
new file mode 100644
index 0000000..25ee758
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/activity/MQConversationActivity.java
@@ -0,0 +1,1104 @@
+package com.meiqia.meiqiasdk.activity;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.database.Cursor;
+import android.media.AudioManager;
+import android.media.SoundPool;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.MediaStore;
+import android.support.v4.widget.SwipeRefreshLayout;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.meiqia.core.MQManager;
+import com.meiqia.meiqiasdk.R;
+import com.meiqia.meiqiasdk.callback.OnClientOnlineCallback;
+import com.meiqia.meiqiasdk.callback.OnGetMessageListCallBack;
+import com.meiqia.meiqiasdk.callback.OnMessageSendCallback;
+import com.meiqia.meiqiasdk.controller.ControllerImpl;
+import com.meiqia.meiqiasdk.controller.MQController;
+import com.meiqia.meiqiasdk.controller.MediaRecordFunc;
+import com.meiqia.meiqiasdk.dialog.MQChoosePicDialog;
+import com.meiqia.meiqiasdk.dialog.MQViewPhotoDialog;
+import com.meiqia.meiqiasdk.model.Agent;
+import com.meiqia.meiqiasdk.model.AgentChangeMessage;
+import com.meiqia.meiqiasdk.model.BaseMessage;
+import com.meiqia.meiqiasdk.model.LeaveTipMessage;
+import com.meiqia.meiqiasdk.model.PhotoMessage;
+import com.meiqia.meiqiasdk.model.TextMessage;
+import com.meiqia.meiqiasdk.model.VoiceMessage;
+import com.meiqia.meiqiasdk.util.ErrorCode;
+import com.meiqia.meiqiasdk.util.MQChatAdapter;
+import com.meiqia.meiqiasdk.util.MQConfig;
+import com.meiqia.meiqiasdk.util.MQTimeUtils;
+import com.meiqia.meiqiasdk.util.MQUtils;
+import com.meiqia.meiqiasdk.widget.MQEditToolbar;
+import com.nostra13.universalimageloader.core.DisplayImageOptions;
+import com.nostra13.universalimageloader.core.ImageLoader;
+import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class MQConversationActivity extends Activity implements View.OnClickListener {
+ private static final String TAG = MQConversationActivity.class.getSimpleName();
+
+ public static final String CLIENT_ID = "clientId";
+ public static final String CUSTOMIZED_ID = "customizedId";
+
+ public static final int REQUEST_CODE_CAMERA = 0;
+ public static final int REQUEST_CODE_PHOTO = 1;
+ private static final int REQUEST_CODE_PERMISSIONS = 2;
+ private static int MESSAGE_PAGE_COUNT = 30; //消息每页加载数量
+ private static final long MIN_RECORD_INTERNAL_TIME = 800;
+
+ private static MQController controller;
+
+ // 控件
+ private View backBtn;
+ private TextView titleTv;
+ private ListView conversationListView;
+ private EditText inputEt;
+ private TextView voiceOrSendTv;
+ private Button emojiSelectBtn;
+ private Button photoSelectBtn;
+ private ProgressBar loadProgressBar;
+ private SwipeRefreshLayout swipeRefreshLayout;
+
+ // 语音
+ private View voiceHoldView;
+ private View voiceMicOnHoldViewIv;
+ private float voiceHoldViewPressMinY; // 录音控件触摸y坐标
+ private int recordState; // 标记录音的状态
+ private long recordStartTime;
+
+ // 用来标识 voiceOrSendTv 的状态
+ private static final int VOICE_STATE = 0;
+ private static final int SEND_STATE = 1;
+ private static final int KEYBOARD_STATE = 3;
+ private int voiceOrSendBtnState = VOICE_STATE;
+
+ private List chatMessageList = new ArrayList<>();
+ private MQChatAdapter chatMsgAdapter;
+ private MessageReceiver messageReceiver;
+ private NetworkChangeReceiver networkChangeReceiver;
+ // 改变title状态
+ private Handler mHandler;
+ // Sound
+ private SoundPool soundPool;
+
+ // 是否已经加载数据的标识
+ private boolean hasLoadData = false;
+ private boolean isPause;
+
+ private Agent currentAgent; // 当前客服
+ private MQConfig mqConfig;
+
+ private MQEditToolbar mEditToolbar;
+ private MQViewPhotoDialog mMQViewPhotoDialog;
+ private MQChoosePicDialog mMQChoosePicDialog;
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // 保持屏幕长亮
+ setContentView(R.layout.mq_activity_conversation);
+
+ init();
+ findViews();
+ setListeners();
+
+ // 初始化输入栏状态
+ changeInputStateToTextOrVoice();
+ // 注册广播
+ registerReceiver();
+
+ mEditToolbar.init(this, inputEt);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ // 设置顾客上线,请求分配客服
+ setClientOnline();
+ isPause = false;
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ isPause = true;
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ try {
+ unregisterReceiver(messageReceiver);
+ unregisterReceiver(networkChangeReceiver);
+ } catch (Exception e) {
+ //有些时候会出现未注册就取消注册的情况,暂时不知道为什么
+ }
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ //如果在表情选择的时候按下 Back 键,隐藏表情 panel
+ if (keyCode == KeyEvent.KEYCODE_BACK && mEditToolbar.isEmotionKeyboardVisible()) {
+ mEditToolbar.closeEmotionKeyboard();
+ return true;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ private void init() {
+ if (controller == null) {
+ controller = new ControllerImpl(this);
+ }
+ MQTimeUtils.init(this);
+ mqConfig = new MQConfig(this);
+ // 初始化 ImageLoader
+ DisplayImageOptions options = new DisplayImageOptions.Builder().cacheInMemory(true).cacheOnDisk(true).build();
+ ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(this).defaultDisplayImageOptions(options).build();
+ ImageLoader.getInstance().init(config);
+
+ // handler
+ mHandler = new Handler();
+ // sound
+ soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 5);
+ soundPool.load(this, R.raw.mq_message, 1);
+ }
+
+ private void findViews() {
+ backBtn = findViewById(R.id.back_rl);
+ conversationListView = (ListView) findViewById(R.id.messages_lv);
+ inputEt = (EditText) findViewById(R.id.input_et);
+ voiceHoldView = findViewById(R.id.voice_hold_view);
+ voiceMicOnHoldViewIv = findViewById(R.id.voice_mic_iv);
+ emojiSelectBtn = (Button) findViewById(R.id.emoji_select_btn);
+ mEditToolbar = (MQEditToolbar) findViewById(R.id.editToolbar);
+ voiceOrSendTv = (TextView) findViewById(R.id.voice_or_send_tv);
+ photoSelectBtn = (Button) findViewById(R.id.photo_select_btn);
+ loadProgressBar = (ProgressBar) findViewById(R.id.progressbar);
+ titleTv = (TextView) findViewById(R.id.title_tv);
+ swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout);
+ }
+
+ private void setListeners() {
+ backBtn.setOnClickListener(this);
+ voiceOrSendTv.setOnClickListener(this);
+ photoSelectBtn.setOnClickListener(this);
+ // 绑定 EditText 的监听器
+ inputEt.addTextChangedListener(inputTextWatcher);
+ // 表情
+ emojiSelectBtn.setOnClickListener(this);
+ // 对话列表,单击「隐藏键盘」、「表情 panel」
+ conversationListView.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View arg0, MotionEvent arg1) {
+ if (MotionEvent.ACTION_DOWN == arg1.getAction()) {
+ mEditToolbar.closeAllKeyboard();
+ }
+ return false;
+ }
+ });
+ // 添加长按复制功能
+ conversationListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
+ @Override
+ public boolean onItemLongClick(AdapterView> arg0, View arg1, int arg2, long arg3) {
+ String content = chatMessageList.get(arg2).getContent();
+ if (!TextUtils.isEmpty(content)) {
+ MQUtils.clip(MQConversationActivity.this, content);
+ MQUtils.show(MQConversationActivity.this, R.string.mq_copy_success);
+ return true;
+ }
+ return false;
+ }
+ });
+ // 下拉刷新
+ swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
+ @Override
+ public void onRefresh() {
+ loadMoreDataFromService();
+ }
+ });
+ // 录音
+ voiceHoldView.setOnTouchListener(new View.OnTouchListener() {
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ recordStartTime = System.currentTimeMillis();
+ if (!isSdcardAvailable()) {
+ return true;
+ }
+ voiceHoldViewPressMinY = event.getRawY();
+
+ // 处理用户连续点击
+ mHandler.removeCallbacks(startRecord);
+ mHandler.postDelayed(startRecord, 100);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (recordState == MediaRecordFunc.SUCCESS || recordState == MediaRecordFunc.CANCEL) {
+ // 判断用户是否主动取消录音
+ if (Math.abs(voiceHoldViewPressMinY - event.getRawY()) > MQUtils.dip2px(MQConversationActivity.this, 40)) {
+ MediaRecordFunc.getInstance(MQConversationActivity.this).showCancelContent();
+ recordState = MediaRecordFunc.CANCEL;
+ } else {
+ MediaRecordFunc.getInstance(MQConversationActivity.this).showUpThenCancelContent();
+ recordState = MediaRecordFunc.SUCCESS;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ long internalTime = System.currentTimeMillis() - recordStartTime;
+ if (internalTime <= MIN_RECORD_INTERNAL_TIME) {
+ mHandler.removeCallbacks(startRecord);
+ stopRecord();
+ // 连续点击一直提示太烦了
+ if (internalTime > 500) {
+ MQUtils.show(MQConversationActivity.this, R.string.mq_record_record_time_is_short);
+ }
+ return true;
+ }
+
+ if (recordState == MediaRecordFunc.SUCCESS || recordState == MediaRecordFunc.CANCEL) {
+ String voicePath = stopRecord();
+ if (!TextUtils.isEmpty(voicePath) && recordState == MediaRecordFunc.SUCCESS && MediaRecordFunc.isVoiceFileAvailable(MQConversationActivity.this, voicePath)) {
+ createAndPreSendVoiceMessage(voicePath);
+ }
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ stopRecord();
+ break;
+ }
+ return true;
+ }
+ });
+
+ // 语音倒计时监听
+ MediaRecordFunc.getInstance(MQConversationActivity.this).setOnCountDownListener(new MediaRecordFunc.OnCountDownListener() {
+ @Override
+ public void timeUp() {
+ String voicePath = stopRecord();
+ if (!TextUtils.isEmpty(voicePath) && recordState == MediaRecordFunc.SUCCESS) {
+ createAndPreSendVoiceMessage(voicePath);
+ }
+ }
+ });
+ }
+
+ private Runnable startRecord = new Runnable() {
+ @Override
+ public void run() {
+ voiceHoldView.setBackgroundResource(R.drawable.mq_bg_edit_view_pressed);
+ voiceMicOnHoldViewIv.setBackgroundResource(R.drawable.mq_ic_mid_record_pressed);
+ recordState = MediaRecordFunc.getInstance(MQConversationActivity.this).startRecordAndFile();
+ if (recordState == MediaRecordFunc.SUCCESS) {
+ MediaRecordFunc.getInstance(MQConversationActivity.this).showContent(MQConversationActivity.this, conversationListView);
+ } else {
+ MQUtils.show(MQConversationActivity.this, R.string.mq_record_failed);
+ voiceHoldView.setBackgroundResource(R.drawable.mq_bg_edit_view);
+ voiceMicOnHoldViewIv.setBackgroundResource(R.drawable.mq_ic_mid_record_mic_nor);
+ }
+ }
+ };
+
+ /**
+ * 停止录音
+ *
+ * @return 录音文件路径
+ */
+ private String stopRecord() {
+ String voicePath = MediaRecordFunc.getInstance(MQConversationActivity.this).stopRecordAndFile();
+ MediaRecordFunc.getInstance(MQConversationActivity.this).dismissContent();
+ voiceHoldView.setBackgroundResource(R.drawable.mq_bg_edit_view);
+ voiceMicOnHoldViewIv.setBackgroundResource(R.drawable.mq_ic_mid_record_mic_nor);
+ return voicePath;
+ }
+
+ /**
+ * 注册 controller
+ *
+ * @param controller
+ */
+ public static void registerController(MQController controller) {
+ MQConversationActivity.controller = controller;
+ }
+
+ /**
+ * 注册广播
+ */
+ private void registerReceiver() {
+ // 注册消息接收
+ messageReceiver = new MessageReceiver();
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(MQController.ACTION_AGENT_INPUTTING);
+ intentFilter.addAction(MQController.ACTION_NEW_MESSAGE_RECEIVED);
+ intentFilter.addAction(MQController.ACTION_CLIENT_IS_REDIRECTED_EVENT);
+ registerReceiver(messageReceiver, intentFilter);
+
+ // 网络监听
+ networkChangeReceiver = new NetworkChangeReceiver();
+ IntentFilter mFilter = new IntentFilter();
+ mFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ registerReceiver(networkChangeReceiver, mFilter);
+ }
+
+ /**
+ * 将 title 改为客服名字
+ *
+ * @param agentName 客服名
+ */
+ private void changeTitleToAgentName(String agentName) {
+ titleTv.setText(agentName);
+ }
+
+ /**
+ * 将 title 改为客服名字
+ *
+ * @param agent 客服实体
+ */
+ private void changeTitleToAgentName(Agent agent) {
+ if (agent != null) {
+ titleTv.setText(agent.getNickname());
+ } else {
+ changeTitleToNoAgentState();
+ }
+ }
+
+ /**
+ * 将 title 改为 正在输入
+ */
+ private void changeTitleToInputting() {
+ titleTv.setText(getResources().getString(R.string.mq_title_inputting));
+ }
+
+ /**
+ * 将 title 改为 正在分配客服
+ */
+ private void changeTitleToAllocatingAgent() {
+ titleTv.setText(getResources().getString(R.string.mq_allocate_agent));
+ }
+
+ /**
+ * 将 title 改为没有客服的状态
+ */
+ private void changeTitleToNoAgentState() {
+ titleTv.setText(getResources().getString(R.string.mq_title_leave_msg));
+ }
+
+ /**
+ * 将 title 改为没有网络状态
+ */
+ private void changeTitleToNetErrorState() {
+ titleTv.setText(getResources().getString(R.string.mq_title_net_not_work));
+ }
+
+ /**
+ * 将 title 改为未知错误状态
+ */
+ private void changeTitleToUnknownErrorState() {
+ titleTv.setText(getResources().getString(R.string.mq_title_unknown_error));
+ }
+
+ /**
+ * 添加 转接客服 的消息 Tip 到列表
+ *
+ * @param agentNickName 客服名字
+ */
+ private void addDirectAgentMessageTip(String agentNickName) {
+ titleTv.setText(agentNickName);
+ AgentChangeMessage agentChangeMessage = new AgentChangeMessage();
+ agentChangeMessage.setAgentNickname(agentNickName);
+ chatMsgAdapter.addMQMessage(agentChangeMessage);
+ }
+
+ private boolean isAddLeaveTip;
+
+ /**
+ * 添加 留言 的 Tip
+ */
+ private void addLeaveMessageTip() {
+ if (!isAddLeaveTip) {
+ titleTv.setText(getResources().getString(R.string.mq_title_leave_msg));
+ LeaveTipMessage leaveTip = new LeaveTipMessage();
+ //添加到当前消息的上一个位置
+ int position = chatMessageList.size();
+ if (position != 0) {
+ position = position - 1;
+ }
+ chatMsgAdapter.addMQMessage(leaveTip, position);
+ isAddLeaveTip = true;
+ }
+ }
+
+ /**
+ * 从列表移除 留言 的 Tip
+ */
+ private void removeLeaveMessageTip() {
+ Iterator chatItemViewBaseIterator = chatMessageList.iterator();
+ while (chatItemViewBaseIterator.hasNext()) {
+ BaseMessage baseMessage = chatItemViewBaseIterator.next();
+ if (baseMessage.getItemViewType() == BaseMessage.TYPE_TIP) {
+ chatItemViewBaseIterator.remove();
+ chatMsgAdapter.notifyDataSetChanged();
+ return;
+ }
+ }
+ isAddLeaveTip = false;
+ }
+
+ /**
+ * 将输入状态改为 可以发送文字和语音 的状态
+ */
+ private void changeInputStateToTextOrVoice() {
+ inputEt.setVisibility(View.VISIBLE);
+ emojiSelectBtn.setVisibility(View.VISIBLE);
+ voiceHoldView.setVisibility(View.GONE);
+ voiceMicOnHoldViewIv.setVisibility(View.GONE);
+ // 根据语音开关,显示隐藏右边的录音图标
+ if (mqConfig.getShowVoiceMessage()) {
+ voiceOrSendTv.setBackgroundResource(R.drawable.mq_voice_btn_background);
+ voiceOrSendTv.setText("");
+ voiceOrSendBtnState = VOICE_STATE;
+ } else {
+ voiceOrSendTv.setBackgroundResource(R.drawable.mq_bg_transparent);
+ voiceOrSendTv.setText(getResources().getString(R.string.mq_send));
+ voiceOrSendBtnState = SEND_STATE;
+ }
+ if (!TextUtils.isEmpty(inputEt.getText().toString())) {
+ changeInputStateToSend();
+ }
+ }
+
+ /**
+ * 将输入状态改为 可以录音 的状态
+ */
+ private void changeInputStateToRecord() {
+ inputEt.setVisibility(View.GONE);
+ emojiSelectBtn.setVisibility(View.GONE);
+ voiceOrSendTv.setBackgroundResource(R.drawable.mq_keyboard_btn_background);
+ voiceOrSendTv.setText("");
+ voiceHoldView.setVisibility(View.VISIBLE);
+ voiceMicOnHoldViewIv.setVisibility(View.VISIBLE);
+ voiceOrSendBtnState = KEYBOARD_STATE;
+ mEditToolbar.closeAllKeyboard();
+ }
+
+ /**
+ * 将输入状态改为 可以发送 的状态
+ */
+ private void changeInputStateToSend() {
+ inputEt.setVisibility(View.VISIBLE);
+ emojiSelectBtn.setVisibility(View.VISIBLE);
+ voiceHoldView.setVisibility(View.GONE);
+ voiceMicOnHoldViewIv.setVisibility(View.GONE);
+ voiceOrSendTv.setBackgroundResource(R.drawable.mq_bg_transparent);
+ voiceOrSendTv.setText(getResources().getString(R.string.mq_send));
+ voiceOrSendBtnState = SEND_STATE;
+ }
+
+ private void setCurrentAgent(Agent agent) {
+ this.currentAgent = agent;
+ }
+
+ /**
+ * 从服务器获取更多消息并加载
+ */
+ private void loadMoreDataFromService() {
+ // 最早消息的创建时间
+ long lastMessageCreateOn = System.currentTimeMillis();
+ if (chatMessageList.size() > 0) lastMessageCreateOn = chatMessageList.get(0).getCreatedOn();
+ // 获取该时间之前的消息
+ controller.getMessageFromService(lastMessageCreateOn, MESSAGE_PAGE_COUNT, new OnGetMessageListCallBack() {
+ @Override
+ public void onSuccess(final List messageList) {
+ // 根据设置,过滤语音消息
+ cleanVoiceMessage(messageList);
+ //添加时间戳
+ MQTimeUtils.refreshMQTimeItem(messageList);
+ chatMsgAdapter.loadMoreMessage(messageList);
+ conversationListView.setSelection(messageList.size());
+ swipeRefreshLayout.setRefreshing(false);
+ // 没有消息后,禁止下拉加载
+ if (messageList.size() == 0) {
+ swipeRefreshLayout.setEnabled(false);
+ }
+ }
+
+ @Override
+ public void onFailure(int code, String responseString) {
+ chatMsgAdapter.notifyDataSetChanged();
+ swipeRefreshLayout.setRefreshing(false);
+ }
+ });
+ }
+
+ /**
+ * 从数据库更多消息并加载
+ */
+ private void loadMoreDataFromDatabase() {
+ // 最早消息的创建时间
+ long lastMessageCreateOn = System.currentTimeMillis();
+ if (chatMessageList.size() > 0) lastMessageCreateOn = chatMessageList.get(0).getCreatedOn();
+ // 获取该时间之前的消息
+ controller.getMessagesFromDatabase(lastMessageCreateOn, MESSAGE_PAGE_COUNT, new OnGetMessageListCallBack() {
+ @Override
+ public void onSuccess(final List messageList) {
+ // 根据设置,过滤语音消息
+ cleanVoiceMessage(messageList);
+ //添加时间戳
+ MQTimeUtils.refreshMQTimeItem(messageList);
+ chatMsgAdapter.loadMoreMessage(messageList);
+ conversationListView.setSelection(messageList.size());
+ swipeRefreshLayout.setRefreshing(false);
+ // 没有消息后,禁止下拉加载
+ if (messageList.size() == 0) {
+ swipeRefreshLayout.setEnabled(false);
+ }
+ }
+
+ @Override
+ public void onFailure(int code, String responseString) {
+ chatMsgAdapter.notifyDataSetChanged();
+ swipeRefreshLayout.setRefreshing(false);
+ }
+ });
+ }
+
+ /**
+ * 设置顾客上线
+ */
+ private void setClientOnline() {
+ if (currentAgent == null) {
+ // Title 显示正在分配客服
+ changeTitleToAllocatingAgent();
+
+ // 从 intent 获取 clientId 和 customizedId
+ Intent intent = getIntent();
+ String clientId = null;
+ String customizedId = null;
+ if (intent != null) {
+ clientId = getIntent().getStringExtra(CLIENT_ID);
+ customizedId = getIntent().getStringExtra(CUSTOMIZED_ID);
+ }
+
+ // 上线
+ controller.setCurrentClientOnline(clientId, customizedId, new OnClientOnlineCallback() {
+
+ @Override
+ public void onSuccess(Agent agent, List conversationMessageList) {
+ setCurrentAgent(agent);
+ changeTitleToAgentName(agent);
+ removeLeaveMessageTip();
+
+ // 根据设置,过滤语音消息
+ cleanVoiceMessage(conversationMessageList);
+
+ //加载数据
+ chatMessageList.clear();
+ chatMessageList.addAll(conversationMessageList);
+ loadData();
+ }
+
+ @Override
+ public void onFailure(int code, String message) {
+ if (ErrorCode.NET_NOT_WORK == code) {
+ changeTitleToNetErrorState();
+ } else if (ErrorCode.NO_AGENT_ONLINE == code) {
+ setCurrentAgent(null);
+ changeTitleToNoAgentState();
+ } else {
+ changeTitleToUnknownErrorState();
+ }
+ //如果没有加载数据,则加载数据
+ if (!hasLoadData) {
+ getMessageDataFromDatabaseAndLoad();
+ }
+
+ }
+ });
+ } else {
+ changeTitleToAgentName(currentAgent);
+ }
+ }
+
+ /**
+ * 从数据库获取消息并加载
+ */
+ private void getMessageDataFromDatabaseAndLoad() {
+ // 从数据库获取数据
+ controller.getMessagesFromDatabase(System.currentTimeMillis(), MESSAGE_PAGE_COUNT, new OnGetMessageListCallBack() {
+
+ @Override
+ public void onSuccess(List messageList) {
+ // 根据设置,过滤语音消息
+ cleanVoiceMessage(messageList);
+
+ chatMessageList.addAll(messageList);
+ loadData();
+ }
+
+ @Override
+ public void onFailure(int code, String responseString) {
+
+ }
+ });
+ }
+
+ /**
+ * 加载消息到列表中
+ */
+ private void loadData() {
+ // 添加TimeItem
+ MQTimeUtils.refreshMQTimeItem(chatMessageList);
+ // 加载到UI
+ loadProgressBar.setVisibility(View.GONE);
+ // 将正在发送显示为已发送
+ for (BaseMessage message : chatMessageList) {
+ if (BaseMessage.STATE_SENDING.equals(message.getStatus())) {
+ message.setStatus(BaseMessage.STATE_ARRIVE);
+ }
+ }
+ chatMsgAdapter = new MQChatAdapter(MQConversationActivity.this, chatMessageList, conversationListView);
+ conversationListView.setAdapter(chatMsgAdapter);
+ conversationListView.setSelection(chatMsgAdapter.getCount() - 1);
+ hasLoadData = true;
+ }
+
+ @Override
+ public void onClick(View arg0) {
+ int id = arg0.getId();
+ if (id == R.id.back_rl) {
+ // 返回按钮
+
+ onBackPressed();
+ } else if (id == R.id.emoji_select_btn) {
+ // 表情按钮
+
+ mEditToolbar.toggleKeyboard();
+ } else if (id == R.id.voice_or_send_tv) {
+ // 发送按钮
+
+ if (!hasLoadData) {
+ Toast.makeText(this, R.string.mq_data_is_loading, Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ if (voiceOrSendBtnState == SEND_STATE) {
+ createAndSendTextMessage();
+ changeInputStateToTextOrVoice();
+ } else if (voiceOrSendBtnState == VOICE_STATE) {
+ changeInputStateToRecord();
+ MQUtils.closeKeyboard(MQConversationActivity.this);
+ } else if (voiceOrSendBtnState == KEYBOARD_STATE) {
+ changeInputStateToTextOrVoice();
+ mEditToolbar.changeToOriginalKeyboard();
+ }
+ } else if (id == R.id.photo_select_btn) {
+ // 选择图片按钮
+
+ showChoosePicDialog();
+ }
+ }
+
+ /**
+ * 显示选择图片对话框
+ */
+ private void showChoosePicDialog() {
+ if (!hasLoadData) {
+ Toast.makeText(this, R.string.mq_data_is_loading, Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ MQUtils.closeKeyboard(MQConversationActivity.this);
+
+ if (mMQChoosePicDialog == null) {
+ mMQChoosePicDialog = new MQChoosePicDialog(this);
+ }
+ mMQChoosePicDialog.show();
+ }
+
+ /**
+ * 创建并发送TextMessage。如果没有客服在线,发送离线消息
+ */
+ private void createAndSendTextMessage() {
+ //内容为空不发送
+ if (TextUtils.isEmpty(inputEt.getText())) {
+ inputEt.setText("");
+ return;
+ }
+ TextMessage message = new TextMessage(inputEt.getText().toString());
+ boolean isPreSendSuc = checkAndPreSend(message);
+ if (isPreSendSuc) {
+ sendMessage(message);
+ }
+ }
+
+ /**
+ * 创建并发送ImageMessage
+ *
+ * @param imageFile 需要上传的imageFile
+ */
+ private void createAndSendImageMessage(File imageFile) {
+ PhotoMessage imageMessage = new PhotoMessage();
+ imageMessage.setLocalPath(imageFile.getAbsolutePath());
+ boolean isPreSendSuc = checkAndPreSend(imageMessage);
+ if (isPreSendSuc) {
+ sendMessage(imageMessage);
+ }
+ }
+
+ /**
+ * 创建并发送语音消息
+ *
+ * @param voicePath 语音路径
+ */
+ private void createAndPreSendVoiceMessage(String voicePath) {
+ VoiceMessage voiceMessage = new VoiceMessage();
+ voiceMessage.setLocalPath(voicePath);
+ boolean isPreSendSuc = checkAndPreSend(voiceMessage);
+ if (isPreSendSuc) {
+ sendMessage(voiceMessage);
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ // 从 相机 获取的图片
+ if (requestCode == REQUEST_CODE_CAMERA && resultCode == Activity.RESULT_OK) {
+ File cameraPicFile = mMQChoosePicDialog.getCameraPicFile();
+ if (cameraPicFile != null) {
+ createAndSendImageMessage(cameraPicFile);
+ }
+ }
+
+ // 从 相册 获取的图片
+ if (requestCode == REQUEST_CODE_PHOTO && resultCode == Activity.RESULT_OK && null != data) {
+ String picturePath;
+ try {
+ Uri selectedImage = data.getData();
+ String[] filePathColumns = {MediaStore.Images.Media.DATA};
+ Cursor c = getContentResolver().query(selectedImage, filePathColumns, null, null, null);
+ c.moveToFirst();
+ int columnIndex = c.getColumnIndex(filePathColumns[0]);
+ picturePath = c.getString(columnIndex);
+ c.close();
+ } catch (Exception e) {
+ picturePath = data.getData().getPath();
+ }
+ // 获取图片并显示
+ File imageFile = new File(picturePath);
+ if (imageFile.exists()) {
+ createAndSendImageMessage(imageFile);
+ }
+ }
+
+ if (requestCode == REQUEST_CODE_CAMERA || requestCode == REQUEST_CODE_PHOTO) {
+ changeInputStateToTextOrVoice();
+ }
+ }
+
+ @Override
+ public void startActivity(Intent intent) {
+ // 如果当前系统中没有邮件客户端可供调用,程序会直接挂掉,系统抛出了ActivityNotFoundException
+ if (intent.toString().contains("mailto")) {
+ PackageManager pm = getPackageManager();
+ // The first Method
+ List activities = pm.queryIntentActivities(intent, 0);
+ if (activities == null || activities.size() == 0) {
+ // Do anything you like, or just return
+ return;
+ }
+ }
+ super.startActivity(intent);
+ }
+
+ /**
+ * 检查发送条件并且处理一些准备发送的状态
+ *
+ * @param message 待发送消息
+ * @return true,可以发送;false,不能发送
+ */
+ private boolean checkAndPreSend(BaseMessage message) {
+ // 数据还没有加载的时候
+ if (chatMsgAdapter == null) {
+ return false;
+ }
+ message.setStatus(BaseMessage.STATE_SENDING);
+ // 添加到对话列表
+ chatMessageList.add(message);
+ inputEt.setText("");
+ MQTimeUtils.refreshMQTimeItem(chatMessageList);
+ chatMsgAdapter.notifyDataSetChanged();
+ return true;
+ }
+
+ /**
+ * 发送消息
+ *
+ * @param message 消息
+ */
+ public void sendMessage(final BaseMessage message) {
+ // 状态改为「正在发送」
+ message.setStatus(BaseMessage.STATE_SENDING);
+
+ // 开始发送
+ controller.sendMessage(message, new OnMessageSendCallback() {
+ @Override
+ public void onSuccess(BaseMessage message, int state) {
+ // 刷新界面
+ chatMsgAdapter.notifyDataSetChanged();
+
+ // 客服不在线的时候,会自动发送留言消息,这个时候要添加一个 tip 到列表
+ if (ErrorCode.NO_AGENT_ONLINE == state) {
+ addLeaveMessageTip();
+ }
+ }
+
+ @Override
+ public void onFailure(BaseMessage failureMessage, int code, String failureInfo) {
+ chatMsgAdapter.notifyDataSetChanged();
+ }
+ });
+
+
+ // 滑动到底部
+ conversationListView.setSelection(conversationListView.getBottom());
+ }
+
+ // 监听EditText输入框数据到变化
+ private TextWatcher inputTextWatcher = new TextWatcher() {
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ // 向服务器发送一个正在输入的函数
+ if (!TextUtils.isEmpty(s)) {
+ inputting(s.toString());
+ changeInputStateToSend();
+ } else {
+ // 清空输入内容后,需要恢复状态
+ if (voiceOrSendBtnState == SEND_STATE) {
+ changeInputStateToTextOrVoice();
+ }
+ }
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+
+ }
+ };
+
+ /**
+ * 向服务器发送「顾客正在输入」的状态
+ *
+ * @param content 内容
+ */
+ private void inputting(String content) {
+ MQManager.getInstance(this).sendClientInputtingWithContent(content);
+ }
+
+ /**
+ * 过滤语音消息
+ *
+ * @param messageList 消息列表
+ */
+ private void cleanVoiceMessage(List messageList) {
+ if (!mqConfig.getShowVoiceMessage() && messageList.size() > 0) {
+ Iterator baseMessageIterator = messageList.iterator();
+ while (baseMessageIterator.hasNext()) {
+ BaseMessage baseMessage = baseMessageIterator.next();
+ if (BaseMessage.TYPE_CONTENT_VOICE.equals(baseMessage.getContentType())) {
+ baseMessageIterator.remove();
+ }
+ }
+ }
+ }
+
+ private class MessageReceiver extends com.meiqia.meiqiasdk.controller.MessageReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ super.onReceive(context, intent);
+ }
+
+ @Override
+ public void receiveNewMsg(BaseMessage message) {
+ MQConversationActivity.this.receiveNewMsg(message);
+ }
+
+ @Override
+ public void changeTitleToInputting() {
+ MQConversationActivity.this.changeTitleToInputting();
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ MQConversationActivity.this.changeTitleToAgentName(currentAgent);
+ }
+ }, 2000);
+
+ }
+
+ @Override
+ public void changeTitleToAgentName(String agentNickname) {
+ MQConversationActivity.this.changeTitleToAgentName(agentNickname);
+ }
+
+ @Override
+ public void addDirectAgentMessageTip(String agentNickname) {
+ MQConversationActivity.this.addDirectAgentMessageTip(agentNickname);
+ }
+
+ @Override
+ public void setCurrentAgent(Agent agent) {
+ MQConversationActivity.this.setCurrentAgent(agent);
+ }
+ }
+
+ /**
+ * 处理收到的新消息
+ *
+ * @param baseMessage 新消息
+ */
+ private void receiveNewMsg(BaseMessage baseMessage) {
+ if (chatMsgAdapter != null) {
+ chatMessageList.add(baseMessage);
+ MQTimeUtils.refreshMQTimeItem(chatMessageList);
+ chatMsgAdapter.notifyDataSetChanged();
+
+ int lastVisiblePosition = conversationListView.getLastVisiblePosition();
+ // -2 因为是先添加
+ if (lastVisiblePosition == (chatMsgAdapter.getCount() - 2)) {
+ conversationListView.setSelection(chatMsgAdapter.getCount() - 1); // 往下挪一截
+ }
+ // 在界面中播放声音
+ if (!isPause) {
+ AudioManager mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ if (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_SILENT) {
+ soundPool.play(1, 1, 1, 0, 0, 1);
+ }
+ }
+ }
+ }
+
+ /**
+ * 监听网络
+ */
+ private class NetworkChangeReceiver extends BroadcastReceiver {
+
+ private ConnectivityManager connectivityManager;
+ // 第一次进入的时候,会立即收到广播,需要避免以下
+ private boolean isFirstReceiveBroadcast = true;
+
+ @Override
+ public void onReceive(Context arg0, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+ connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo info = connectivityManager.getActiveNetworkInfo();
+ if (!isFirstReceiveBroadcast) {
+ // 有网络
+ if (info != null && info.isAvailable()) {
+ changeTitleToAgentName(currentAgent);
+ }
+ // 没有网络
+ else {
+ changeTitleToNetErrorState();
+ }
+ } else {
+ isFirstReceiveBroadcast = false;
+ }
+ }
+ }
+
+ }
+
+ private boolean isSdcardAvailable() {
+ boolean isSdcardAvailable = MQUtils.isSdcardAvailable();
+ if (!isSdcardAvailable) MQUtils.show(MQConversationActivity.this, R.string.mq_no_sdcard);
+ return isSdcardAvailable;
+ }
+
+ /**
+ * 展示图片,可以缩放
+ *
+ * @param picUrl 图片地址
+ */
+ public void displayPhoto(String picUrl) {
+ if (mMQViewPhotoDialog == null) {
+ mMQViewPhotoDialog = new MQViewPhotoDialog(this);
+ }
+ mMQViewPhotoDialog.show(picUrl);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ MQUtils.requestPermission(this, REQUEST_CODE_PERMISSIONS, getString(R.string.mq_runtime_permission_tip_content), new MQUtils.Delegate() {
+ @Override
+ public void onPermissionGranted() {
+ }
+
+ @Override
+ public void onPermissionDenied() {
+ MQUtils.show(MQConversationActivity.this, R.string.mq_permission_denied_tip);
+ finish();
+ }
+ }, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_PHONE_STATE);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ switch (requestCode) {
+ case REQUEST_CODE_PERMISSIONS:
+ MQUtils.handlePermissionResult(permissions, grantResults, new MQUtils.Delegate() {
+ @Override
+ public void onPermissionGranted() {
+ }
+
+ @Override
+ public void onPermissionDenied() {
+ MQUtils.show(MQConversationActivity.this, R.string.mq_permission_denied_tip);
+ }
+ });
+ break;
+ default:
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+ }
+}
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/callback/OnClientOnlineCallback.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/callback/OnClientOnlineCallback.java
new file mode 100644
index 0000000..001ac1c
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/callback/OnClientOnlineCallback.java
@@ -0,0 +1,11 @@
+package com.meiqia.meiqiasdk.callback;
+
+
+import com.meiqia.meiqiasdk.model.Agent;
+import com.meiqia.meiqiasdk.model.BaseMessage;
+
+import java.util.List;
+
+public interface OnClientOnlineCallback extends OnFailureCallBack {
+ void onSuccess(Agent agent, List messageList);
+}
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/callback/OnFailureCallBack.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/callback/OnFailureCallBack.java
new file mode 100644
index 0000000..9d5028d
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/callback/OnFailureCallBack.java
@@ -0,0 +1,5 @@
+package com.meiqia.meiqiasdk.callback;
+
+public interface OnFailureCallBack {
+ void onFailure(int code, String message);
+}
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/callback/OnGetMessageListCallBack.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/callback/OnGetMessageListCallBack.java
new file mode 100644
index 0000000..e46ae16
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/callback/OnGetMessageListCallBack.java
@@ -0,0 +1,12 @@
+package com.meiqia.meiqiasdk.callback;
+
+
+import com.meiqia.meiqiasdk.model.BaseMessage;
+
+import java.util.List;
+
+public interface OnGetMessageListCallBack extends OnFailureCallBack {
+
+ void onSuccess(List messageList);
+
+}
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/callback/OnMessageSendCallback.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/callback/OnMessageSendCallback.java
new file mode 100644
index 0000000..e4de535
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/callback/OnMessageSendCallback.java
@@ -0,0 +1,10 @@
+package com.meiqia.meiqiasdk.callback;
+
+
+import com.meiqia.meiqiasdk.model.BaseMessage;
+
+public interface OnMessageSendCallback {
+ void onSuccess(BaseMessage message, int state);
+
+ void onFailure(BaseMessage failureMessage, int code, String failureInfo);
+}
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/controller/ControllerImpl.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/controller/ControllerImpl.java
new file mode 100644
index 0000000..5e308e0
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/controller/ControllerImpl.java
@@ -0,0 +1,123 @@
+package com.meiqia.meiqiasdk.controller;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import com.meiqia.core.MQManager;
+import com.meiqia.core.bean.MQAgent;
+import com.meiqia.core.bean.MQMessage;
+import com.meiqia.core.callback.OnGetMessageListCallback;
+import com.meiqia.meiqiasdk.callback.OnClientOnlineCallback;
+import com.meiqia.meiqiasdk.callback.OnGetMessageListCallBack;
+import com.meiqia.meiqiasdk.callback.OnMessageSendCallback;
+import com.meiqia.meiqiasdk.model.Agent;
+import com.meiqia.meiqiasdk.model.BaseMessage;
+import com.meiqia.meiqiasdk.model.PhotoMessage;
+import com.meiqia.meiqiasdk.model.VoiceMessage;
+import com.meiqia.meiqiasdk.util.MQUtils;
+
+import java.util.List;
+
+public class ControllerImpl implements MQController {
+
+ public Context context;
+
+ public ControllerImpl(Context context) {
+ this.context = context;
+ }
+
+ @Override
+ public void sendMessage(final BaseMessage message, final OnMessageSendCallback onMessageSendCallback) {
+ // 发送回调
+ com.meiqia.core.callback.OnMessageSendCallback onMQMessageSendCallback = new com.meiqia.core.callback.OnMessageSendCallback() {
+ @Override
+ public void onSuccess(MQMessage mcMessage, int state) {
+ MQUtils.parseMQMessageIntoChatBase(mcMessage, message);
+ // 如果是语音消息,发送成功共重命名本地语音文件
+ // 默认的储存路径 MediaRecordFunc.VOICE_STORE_PATH
+ if (message instanceof VoiceMessage) {
+ MediaRecordFunc.renameVoiceFile((VoiceMessage) message, mcMessage.getId());
+ }
+ onMessageSendCallback.onSuccess(message, state);
+ }
+
+ @Override
+ public void onFailure(MQMessage failureMessage, int code, String response) {
+ MQUtils.parseMQMessageIntoChatBase(failureMessage, message);
+ onMessageSendCallback.onFailure(message, code, response);
+ }
+ };
+
+ // 开始发送
+ if (BaseMessage.TYPE_CONTENT_TEXT.equals(message.getContentType())) {
+ //转换 emoji 编码再发送
+ String content = message.getContent();
+ MQManager.getInstance(context).sendMQTextMessage(content, onMQMessageSendCallback);
+ } else if (BaseMessage.TYPE_CONTENT_PHOTO.equals(message.getContentType())) {
+ PhotoMessage photoMessage = (PhotoMessage) message;
+ MQManager.getInstance(context).sendMQPhotoMessage(photoMessage.getLocalPath(), onMQMessageSendCallback);
+ } else if (BaseMessage.TYPE_CONTENT_VOICE.equals(message.getContentType())) {
+ VoiceMessage voiceMessage = (VoiceMessage) message;
+ MQManager.getInstance(context).sendMQVoiceMessage(voiceMessage.getLocalPath(), onMQMessageSendCallback);
+ }
+ }
+
+ @Override
+ public void getMessageFromService(long lastMessageCreateOn, int length, final OnGetMessageListCallBack onGetMessageListCallBack) {
+ MQManager.getInstance(context).getMQMessageFromService(lastMessageCreateOn, length, new OnGetMessageListCallback() {
+ @Override
+ public void onSuccess(List mqMessageList) {
+ List messageList = MQUtils.parseMQMessageToChatBaseList(mqMessageList);
+ onGetMessageListCallBack.onSuccess(messageList);
+ }
+
+ @Override
+ public void onFailure(int code, String message) {
+ onGetMessageListCallBack.onFailure(code, message);
+ }
+ });
+ }
+
+ @Override
+ public void getMessagesFromDatabase(long lastMessageCreateOn, int length, final OnGetMessageListCallBack onGetMessageListCallBack) {
+ MQManager.getInstance(context).getMQMessageFromDatabase(lastMessageCreateOn, length, new OnGetMessageListCallback() {
+
+ @Override
+ public void onSuccess(List mqMessageList) {
+ List messageList = MQUtils.parseMQMessageToChatBaseList(mqMessageList);
+ onGetMessageListCallBack.onSuccess(messageList);
+ }
+
+ @Override
+ public void onFailure(int code, String message) {
+ onGetMessageListCallBack.onFailure(code, message);
+ }
+ });
+ }
+
+ @Override
+ public void setCurrentClientOnline(String clientId, String customizedId, final OnClientOnlineCallback onClientOnlineCallback) {
+ com.meiqia.core.callback.OnClientOnlineCallback onlineCallback = new com.meiqia.core.callback.OnClientOnlineCallback() {
+ @Override
+ public void onSuccess(MQAgent mqAgent, List conversationMessageList) {
+ Agent agent = MQUtils.parseMQAgentToAgent(mqAgent);
+ List messageList = MQUtils.parseMQMessageToChatBaseList(conversationMessageList);
+ onClientOnlineCallback.onSuccess(agent, messageList);
+ }
+
+ @Override
+ public void onFailure(int code, String message) {
+ onClientOnlineCallback.onFailure(code, message);
+ }
+ };
+
+ if (!TextUtils.isEmpty(clientId)) {
+ MQManager.getInstance(context).setClientOnlineWithClientId(clientId, onlineCallback);
+ } else if (!TextUtils.isEmpty(customizedId)) {
+ MQManager.getInstance(context).setClientOnlineWithCustomizedId(customizedId, onlineCallback);
+ } else {
+ MQManager.getInstance(context).setCurrentClientOnline(onlineCallback);
+ }
+ }
+
+}
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/controller/MQController.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/controller/MQController.java
new file mode 100644
index 0000000..a1452f5
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/controller/MQController.java
@@ -0,0 +1,37 @@
+package com.meiqia.meiqiasdk.controller;
+
+
+import com.meiqia.meiqiasdk.callback.OnClientOnlineCallback;
+import com.meiqia.meiqiasdk.callback.OnGetMessageListCallBack;
+import com.meiqia.meiqiasdk.callback.OnMessageSendCallback;
+import com.meiqia.meiqiasdk.model.BaseMessage;
+
+public interface MQController {
+
+ String ACTION_NEW_MESSAGE_RECEIVED = "new_msg_received_action";
+ String ACTION_AGENT_INPUTTING = "agent_inputting_action";
+ String ACTION_CLIENT_IS_REDIRECTED_EVENT = "agent_change_action";
+
+ void sendMessage(BaseMessage baseMessage, OnMessageSendCallback onMessageSendCallback);
+
+ /**
+ * 从服务器获取历史消息
+ *
+ * @param messageCreateOn 获取该日期之前的消息
+ * @param length 获取的消息长度
+ * @param onGetMessageListCallBack 回调
+ */
+ void getMessageFromService(final long messageCreateOn, final int length, final OnGetMessageListCallBack onGetMessageListCallBack);
+
+ /**
+ * 从本地服务器取历史消息
+ *
+ * @param lastMessageCreateOn 获取该日期之前的消息
+ * @param length 获取的消息长度
+ * @param onGetMessageListCallBack 回调
+ */
+ void getMessagesFromDatabase(final long lastMessageCreateOn, final int length, final OnGetMessageListCallBack onGetMessageListCallBack);
+
+ void setCurrentClientOnline(String clientId, String customizedId, OnClientOnlineCallback onClientOnlineCallback);
+
+}
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/controller/MediaRecordFunc.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/controller/MediaRecordFunc.java
new file mode 100644
index 0000000..e6fd698
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/controller/MediaRecordFunc.java
@@ -0,0 +1,378 @@
+package com.meiqia.meiqiasdk.controller;
+
+import android.app.Activity;
+import android.content.Context;
+import android.media.MediaPlayer;
+import android.media.MediaRecorder;
+import android.net.Uri;
+import android.os.Handler;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+
+import com.meiqia.core.callback.SimpleCallback;
+import com.meiqia.meiqiasdk.R;
+import com.meiqia.meiqiasdk.model.VoiceMessage;
+import com.meiqia.meiqiasdk.util.MQUtils;
+import com.squareup.okhttp.Callback;
+import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.Request;
+import com.squareup.okhttp.Response;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import okio.BufferedSink;
+import okio.Okio;
+
+public class MediaRecordFunc {
+
+ public static String VOICE_STORE_PATH;
+
+ public final static int SUCCESS = 1000;
+ public final static int E_NOSDCARD = 1001;
+ public final static int E_STATE_RECODING = 1002;
+ public final static int E_UNKOWN = 1003;
+ public final static int CANCEL = 1004;
+
+ private boolean isRecord = false;
+ private String tempVoiceFileName;
+
+ private MediaRecorder mMediaRecorder;
+ private OkHttpClient mOkHttpClient;
+ private Handler mHandler;
+ private List downloadTaskList;
+ private Context context;
+
+ private MediaRecordFunc(Context context) {
+ this.context = context;
+ mHandler = new Handler();
+ mOkHttpClient = new OkHttpClient();
+ downloadTaskList = new ArrayList<>();
+ File externalCacheDir = context.getExternalCacheDir();
+ if (externalCacheDir != null) {
+ VOICE_STORE_PATH = externalCacheDir.getAbsolutePath();
+ }
+ }
+
+ private static MediaRecordFunc mInstance;
+
+ public synchronized static MediaRecordFunc getInstance(Context context) {
+ if (mInstance == null) {
+ mInstance = new MediaRecordFunc(context);
+
+ }
+ return mInstance;
+ }
+
+ public int startRecordAndFile() {
+ //判断是否有外部存储设备sdcard
+ if (MQUtils.isSdcardAvailable() && !TextUtils.isEmpty(VOICE_STORE_PATH)) {
+ if (isRecord) {
+ return E_STATE_RECODING;
+ } else {
+ if (mMediaRecorder == null)
+ createMediaRecord();
+ try {
+ mMediaRecorder.prepare();
+ mMediaRecorder.start();
+ // 让录制状态为true
+ isRecord = true;
+ return SUCCESS;
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ return E_UNKOWN;
+ }
+ }
+
+ } else {
+ return E_NOSDCARD;
+ }
+ }
+
+
+ public String stopRecordAndFile() {
+ return close();
+ }
+
+ private void createMediaRecord() {
+ /* ①Initial:实例化MediaRecorder对象 */
+ mMediaRecorder = new MediaRecorder();
+ /* setAudioSource/setVedioSource*/
+ mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//设置麦克风
+ /* 设置输出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default
+ * THREE_GPP(3gp格式,H263视频/ARM音频编码)、MPEG-4、RAW_AMR(只支持音频且音频编码要求为AMR_NB)
+ */
+ mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);
+ /* 设置音频文件的编码:AAC/AMR_NB/AMR_MB/Default */
+ mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
+ /* 设置输出文件的路径 */
+ tempVoiceFileName = System.currentTimeMillis() + ".amr";
+ File file = new File(VOICE_STORE_PATH + "/" + tempVoiceFileName);
+ if (file.exists()) {
+ file.delete();
+ }
+ mMediaRecorder.setOutputFile(file.getAbsolutePath());
+ }
+
+
+ private String close() {
+ if (mMediaRecorder != null) {
+ isRecord = false;
+ try {
+ mMediaRecorder.stop();
+ mMediaRecorder.release();
+ } catch (Exception e) {
+ Log.e("meiqia", "MediaRecordFunc close error = " + e.toString());
+ return null;
+ } finally {
+ mMediaRecorder = null;
+ }
+ return VOICE_STORE_PATH + "/" + tempVoiceFileName;
+ }
+ return null;
+ }
+
+ private PopupWindow voicePop;
+ private ImageView popVoiceMicIv;
+ private TextView popVoiceTipTv;
+ private boolean isCancelRecord;
+ private int countDownCnt = -1;
+ private boolean isCountDown;
+ private OnCountDownListener onCountDownListener;
+
+ public void setOnCountDownListener(OnCountDownListener onCountDownListener) {
+ this.onCountDownListener = onCountDownListener;
+ }
+
+ /**
+ * 开启倒计时
+ */
+ private Runnable countDown = new Runnable() {
+ @Override
+ public void run() {
+ isCountDown = true;
+ isCancelRecord = false;
+ // 初始化计数
+ if (countDownCnt == -1) {
+ countDownCnt = 11;
+ }
+ countDownCnt--;
+ if (!isCancelRecord || isCountDown) {
+ popVoiceMicIv.setBackgroundResource(R.drawable.mq_ic_voice_pop_mic_0);
+ String countDownStr = context.getString(R.string.mq_record_count_down);
+ popVoiceTipTv.setText(String.format(countDownStr, countDownCnt));
+ }
+ mHandler.postDelayed(countDown, 1000);
+
+ // 时间到,强制停止
+ if (countDownCnt == 0) {
+ if (onCountDownListener != null) {
+ onCountDownListener.timeUp();
+ mHandler.removeCallbacks(countDown);
+ }
+ }
+ }
+ };
+
+ public void showContent(Activity activity, View popParentView) {
+ if (voicePop == null) {
+ View popContent = LayoutInflater.from(activity).inflate(R.layout.mq_voice_pop, null);
+ popVoiceMicIv = (ImageView) popContent.findViewById(R.id.mc_voice_pop_iv);
+ popVoiceTipTv = (TextView) popContent.findViewById(R.id.mc_voice_pop_tv);
+ voicePop = new PopupWindow(popContent, MQUtils.dip2px(activity, 180), MQUtils.dip2px(activity, 190));
+ voicePop.setAnimationStyle(android.R.style.Animation_Dialog);
+ }
+
+ if (voicePop != null && !voicePop.isShowing()) {
+ popVoiceMicIv.setBackgroundResource(R.drawable.mq_ic_voice_pop_mic_0);
+ popVoiceTipTv.setText(R.string.mq_record_up_and_cancel);
+ voicePop.showAtLocation(popParentView, Gravity.CENTER, 0, 0);
+ isCancelRecord = false;
+ isCountDown = false;
+ countDownCnt = -1;
+ updateMicStatus();
+ mHandler.postDelayed(countDown, 50000);
+ }
+ }
+
+ public void showUpThenCancelContent() {
+ if (voicePop != null && popVoiceTipTv != null && !isCountDown) {
+ popVoiceTipTv.setText(R.string.mq_record_up_and_cancel);
+ this.isCancelRecord = false;
+ }
+ }
+
+ public void showCancelContent() {
+ if (voicePop != null && popVoiceMicIv != null && popVoiceTipTv != null && !isCancelRecord) {
+ popVoiceMicIv.setBackgroundResource(R.drawable.mq_ic_voice_pop_cancel);
+ popVoiceTipTv.setText(R.string.mq_record_cancel);
+ this.isCancelRecord = true;
+ }
+ }
+
+ public void dismissContent() {
+ if (voicePop != null && voicePop.isShowing()) {
+ voicePop.dismiss();
+ }
+ mHandler.removeCallbacks(countDown);
+ }
+
+ /**
+ * 更新话筒状态 分贝是也就是相对响度 分贝的计算公式K=20lg(Vo/Vi) Vo当前振幅值 Vi基准值为600:我是怎么制定基准值的呢?
+ * 当20 * Math.log10(mMediaRecorder.getMaxAmplitude() /
+ * Vi)==0的时候vi就是我所需要的基准值
+ * 当我不对着麦克风说任何话的时候,测试获得的mMediaRecorder.getMaxAmplitude()值即为基准值。
+ * Log.i("mic_", "麦克风的基准值:" +
+ * mMediaRecorder.getMaxAmplitude());前提时不对麦克风说任何话
+ */
+ private int BASE = 500;
+ private int SPACE = 200;// 间隔取样时间
+
+ private void updateMicStatus() {
+ if (mMediaRecorder != null && popVoiceMicIv != null) {
+ // int vuSize = 10 * mMediaRecorder.getMaxAmplitude() /
+ // 32768;
+ int ratio = mMediaRecorder.getMaxAmplitude() / BASE;
+ int db = 0;// 分贝
+ if (ratio > 1) {
+ db = (int) (20 * Math.log10(ratio));
+ }
+ if (!isCancelRecord) {
+ switch (db / 4) {
+ case 0:
+ popVoiceMicIv.setBackgroundResource(R.drawable.mq_ic_voice_pop_mic_0);
+ break;
+ case 1:
+ popVoiceMicIv.setBackgroundResource(R.drawable.mq_ic_voice_pop_mic_1);
+ break;
+ case 2:
+ popVoiceMicIv.setBackgroundResource(R.drawable.mq_ic_voice_pop_mic_2);
+ break;
+ case 3:
+ popVoiceMicIv.setBackgroundResource(R.drawable.mq_ic_voice_pop_mic_3);
+ break;
+ case 4:
+ popVoiceMicIv.setBackgroundResource(R.drawable.mq_ic_voice_pop_mic_4);
+ break;
+ case 5:
+ popVoiceMicIv.setBackgroundResource(R.drawable.mq_ic_voice_pop_mic_5);
+ break;
+ case 6:
+ popVoiceMicIv.setBackgroundResource(R.drawable.mq_ic_voice_pop_mic_6);
+ break;
+ case 7:
+ popVoiceMicIv.setBackgroundResource(R.drawable.mq_ic_voice_pop_mic_7);
+ break;
+ case 8:
+ popVoiceMicIv.setBackgroundResource(R.drawable.mq_ic_voice_pop_mic_8);
+ break;
+ default:
+ popVoiceMicIv.setBackgroundResource(R.drawable.mq_ic_voice_pop_mic_8);
+ break;
+ }
+ }
+ if (voicePop.isShowing()) {
+ popVoiceMicIv.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ updateMicStatus();
+ }
+ }, SPACE);
+ }
+ }
+ }
+
+ public static int getDuration(Context context, String path) {
+ MediaPlayer mp = MediaPlayer.create(context, Uri.parse(path));
+ if (mp == null) {
+ return VoiceMessage.NO_DURATION;
+ }
+ int duration = mp.getDuration() / 1000;
+ // 修正
+ if (duration == 0) {
+ duration = 1;
+ }
+ return duration;
+ }
+
+ public static boolean isVoiceFileAvailable(Context context, String voiceFilePath) {
+ MediaPlayer mp = MediaPlayer.create(context, Uri.parse(voiceFilePath));
+ return mp != null;
+ }
+
+ public static void renameVoiceFile(VoiceMessage message, long id) {
+ String localPath = message.getLocalPath();
+ try {
+ File file = new File(localPath);
+ boolean isSuc = file.renameTo(new File(VOICE_STORE_PATH + "/" + id + ".amr"));
+ if (isSuc) {
+ message.setLocalPath(VOICE_STORE_PATH + "/" + id + ".amr");
+ }
+ Log.e("debug", "");
+ } catch (Exception e) {
+
+ }
+ }
+
+ /**
+ * 下载文件
+ *
+ * @param url 文件地址
+ * @param filePath 保存路径
+ * @param filename 文件名
+ * @param simpleCallback 回调
+ */
+ public void downloadVoice(String url, String filePath, final String filename, final SimpleCallback simpleCallback) {
+ if (TextUtils.isEmpty(url)) {
+ if (simpleCallback != null) simpleCallback.onFailure(0, "url is null");
+ return;
+ }
+ if (!downloadTaskList.contains(filename)) {
+ //避免重复下载
+ downloadTaskList.add(filename);
+
+ final File downloadedFile = new File(filePath, filename);
+ if (downloadedFile.exists()) {
+ try {
+ downloadedFile.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ Request request = new Request.Builder().url(url).build();
+ mOkHttpClient.newCall(request).enqueue(new Callback() {
+ @Override
+ public void onFailure(Request request, IOException e) {
+ if (simpleCallback != null) simpleCallback.onFailure(0, "download failed");
+ downloadTaskList.remove(filename);
+ }
+
+ @Override
+ public void onResponse(Response response) throws IOException {
+ if (response.isSuccessful()) {
+ BufferedSink sink = Okio.buffer(Okio.sink(downloadedFile));
+ sink.writeAll(response.body().source());
+ sink.close();
+ if (simpleCallback != null) simpleCallback.onSuccess();
+ } else {
+ if (simpleCallback != null) simpleCallback.onFailure(0, "download failed");
+ }
+ downloadTaskList.remove(filename);
+ }
+ });
+ }
+ }
+
+ public interface OnCountDownListener {
+ void timeUp();
+ }
+
+}
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/controller/MessageReceiver.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/controller/MessageReceiver.java
new file mode 100644
index 0000000..957026c
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/controller/MessageReceiver.java
@@ -0,0 +1,72 @@
+package com.meiqia.meiqiasdk.controller;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import com.meiqia.core.MQMessageManager;
+import com.meiqia.core.bean.MQAgent;
+import com.meiqia.core.bean.MQMessage;
+import com.meiqia.meiqiasdk.model.Agent;
+import com.meiqia.meiqiasdk.model.BaseMessage;
+import com.meiqia.meiqiasdk.util.MQUtils;
+
+public abstract class MessageReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ //只接收当前应用的广播
+ String packageName = intent.getStringExtra("packageName");
+
+ if (context.getPackageName().equals(packageName)) {
+ final String action = intent.getAction();
+ MQMessageManager messageManager = MQMessageManager.getInstance(context);
+ BaseMessage baseMessage;
+
+ // 接收新消息
+ if (MQMessageManager.ACTION_NEW_MESSAGE_RECEIVED.equals(action)) {
+ // 从 intent 获取消息 id
+ String msgId = intent.getStringExtra("msgId");
+ // 从 MCMessageManager 获取消息对象
+ MQMessage message = messageManager.getMQMessage(msgId);
+ if (message != null) {
+ //处理消息,并发送广播
+ baseMessage = MQUtils.parseMQMessageIntoChatBase(message);
+ receiveNewMsg(baseMessage);
+ }
+ }
+
+ // 客服正在输入
+ else if (MQMessageManager.ACTION_AGENT_INPUTTING.equals(action)) {
+ changeTitleToInputting();
+ }
+
+ // 客服转接
+ else if (MQMessageManager.ACTION_AGENT_CHANGE_EVENT.equals(action)) {
+ // 更新标题栏
+ MQAgent mqAgent = messageManager.getCurrentAgent();
+
+ // 如果顾客被转接,才添加 Tip
+ boolean isClientDirect = intent.getBooleanExtra("client_is_redirected", false);
+ if (isClientDirect) {
+ addDirectAgentMessageTip(mqAgent.getNickname());
+ }
+
+ changeTitleToAgentName(mqAgent.getNickname());
+ Agent agent = MQUtils.parseMQAgentToAgent(mqAgent);
+ setCurrentAgent(agent);
+ }
+ }
+ }
+
+ public abstract void receiveNewMsg(BaseMessage message);
+
+ public abstract void changeTitleToInputting();
+
+ public abstract void changeTitleToAgentName(String agentNickname);
+
+ public abstract void addDirectAgentMessageTip(String agentNickname);
+
+ public abstract void setCurrentAgent(Agent agent);
+
+}
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/dialog/MQChoosePicDialog.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/dialog/MQChoosePicDialog.java
new file mode 100644
index 0000000..ec53e75
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/dialog/MQChoosePicDialog.java
@@ -0,0 +1,70 @@
+package com.meiqia.meiqiasdk.dialog;
+
+import android.app.Dialog;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.meiqia.meiqiasdk.R;
+import com.meiqia.meiqiasdk.activity.MQConversationActivity;
+import com.meiqia.meiqiasdk.util.MQUtils;
+
+import java.io.File;
+
+public class MQChoosePicDialog extends Dialog implements View.OnClickListener {
+ private MQConversationActivity mConversationActivity;
+ private String mCameraPicPath;
+
+ public MQChoosePicDialog(MQConversationActivity conversationActivity) {
+ super(conversationActivity, R.style.MQDialog);
+ getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT);
+ setContentView(R.layout.mq_dialog_choose_pic);
+ findViewById(R.id.tv_choose_pic_camera).setOnClickListener(this);
+ findViewById(R.id.tv_choose_pic_gallery).setOnClickListener(this);
+
+ setCanceledOnTouchOutside(true);
+ setCancelable(true);
+ mConversationActivity = conversationActivity;
+ }
+
+ @Override
+ public void onClick(View v) {
+ dismiss();
+ if (v.getId() == R.id.tv_choose_pic_camera) {
+ Intent camera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+ File file = new File(MQUtils.getPicStorePath(mConversationActivity));
+ file.mkdirs();
+ String path = MQUtils.getPicStorePath(mConversationActivity) + "/" + System.currentTimeMillis() + ".jpg";
+ File imageFile = new File(path);
+ camera.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(imageFile));
+ mCameraPicPath = path;
+ try {
+ mConversationActivity.startActivityForResult(camera, MQConversationActivity.REQUEST_CODE_CAMERA);
+ } catch (Exception e) {
+ MQUtils.show(mConversationActivity, R.string.mq_photo_not_support);
+ }
+ } else if (v.getId() == R.id.tv_choose_pic_gallery) {
+ try {
+ mConversationActivity.startActivityForResult(new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI), MQConversationActivity.REQUEST_CODE_PHOTO);
+ } catch (Exception e) {
+ MQUtils.show(mConversationActivity, R.string.mq_photo_not_support);
+ }
+ }
+ }
+
+ public File getCameraPicFile() {
+ String sdState = Environment.getExternalStorageState();
+ if (!sdState.equals(Environment.MEDIA_MOUNTED)) {
+ return null;
+ }
+ File imageFile = new File(mCameraPicPath);
+ if (imageFile.exists()) {
+ return imageFile;
+ } else {
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/dialog/MQConfirmDialog.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/dialog/MQConfirmDialog.java
new file mode 100644
index 0000000..cdcc67c
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/dialog/MQConfirmDialog.java
@@ -0,0 +1,77 @@
+package com.meiqia.meiqiasdk.dialog;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.support.annotation.StringRes;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import com.meiqia.meiqiasdk.R;
+
+public class MQConfirmDialog extends Dialog implements View.OnClickListener {
+ private TextView mTitleTv;
+ private TextView mContentTv;
+ private OnDialogCallback mOnDialogCallback;
+
+ public MQConfirmDialog(Activity activity, @StringRes int titleResId, @StringRes int contentResId, OnDialogCallback onDialogCallback) {
+ this(activity, activity.getString(titleResId), activity.getString(contentResId), onDialogCallback);
+ }
+
+ public MQConfirmDialog(Activity activity, String title, String content, OnDialogCallback onDialogCallback) {
+ super(activity, R.style.MQDialog);
+ getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT);
+ setContentView(R.layout.mq_dialog_confirm);
+ mTitleTv = (TextView) findViewById(R.id.tv_comfirm_title);
+ mContentTv = (TextView) findViewById(R.id.tv_comfirm_content);
+
+
+ findViewById(R.id.tv_confirm_cancel).setOnClickListener(this);
+ findViewById(R.id.tv_confirm_confirm).setOnClickListener(this);
+ setOnCancelListener(new OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ mOnDialogCallback.onClickCancel();
+ }
+ });
+ setCanceledOnTouchOutside(true);
+ setCancelable(true);
+
+ mOnDialogCallback = onDialogCallback;
+ mTitleTv.setText(title);
+ mContentTv.setText(content);
+ }
+
+ @Override
+ public void onClick(View v) {
+ dismiss();
+ if (v.getId() == R.id.tv_confirm_cancel) {
+ mOnDialogCallback.onClickCancel();
+ } else if (v.getId() == R.id.tv_confirm_confirm) {
+ mOnDialogCallback.onClickConfirm();
+ }
+ }
+
+ public void setTitle(String title) {
+ mTitleTv.setText(title);
+ }
+
+ public void setTtitle(@StringRes int titleResId) {
+ mTitleTv.setText(titleResId);
+ }
+
+ public void setContent(String content) {
+ mContentTv.setText(content);
+ }
+
+ public void setContent(@StringRes int contentResId) {
+ mContentTv.setText(contentResId);
+ }
+
+ public interface OnDialogCallback {
+ void onClickConfirm();
+
+ void onClickCancel();
+ }
+}
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/dialog/MQViewPhotoDialog.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/dialog/MQViewPhotoDialog.java
new file mode 100644
index 0000000..cbbd845
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/dialog/MQViewPhotoDialog.java
@@ -0,0 +1,49 @@
+package com.meiqia.meiqiasdk.dialog;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+import com.meiqia.meiqiasdk.R;
+import com.meiqia.meiqiasdk.util.MQUtils;
+import com.nostra13.universalimageloader.core.ImageLoader;
+import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
+
+import uk.co.senab.photoview.PhotoViewAttacher;
+
+/**
+ * 作者:王浩 邮件:bingoogolapple@gmail.com
+ * 创建时间:15/12/22 下午4:30
+ * 描述:
+ */
+public class MQViewPhotoDialog extends Dialog {
+ private ImageView mPhotoIv;
+ private ImageLoader mImageLoader;
+
+ public MQViewPhotoDialog(Context context) {
+ super(context, R.style.MQDialog);
+ setContentView(R.layout.mq_dialog_view_photo);
+ getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, MQUtils.getScreenHeight(context) - MQUtils.getStatusBarHeight(context));
+ mPhotoIv = (ImageView) findViewById(R.id.iv_view_photo);
+ mImageLoader = ImageLoader.getInstance();
+ }
+
+ public void show(String url) {
+ mImageLoader.displayImage(url, mPhotoIv, new SimpleImageLoadingListener() {
+ @Override
+ public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
+ PhotoViewAttacher mAttacher = new PhotoViewAttacher(mPhotoIv);
+ mAttacher.setOnViewTapListener(new PhotoViewAttacher.OnViewTapListener() {
+ @Override
+ public void onViewTap(View view, float x, float y) {
+ dismiss();
+ }
+ });
+ }
+ });
+ show();
+ }
+}
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/model/Agent.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/model/Agent.java
new file mode 100644
index 0000000..d313eea
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/model/Agent.java
@@ -0,0 +1,141 @@
+package com.meiqia.meiqiasdk.model;
+
+public class Agent {
+
+ private int id;
+ private String avatar;
+ private String cellphone;
+ private String email;
+ private int enterprise_id;
+ private String token;
+ private String nickname;
+ private String public_cellphone;
+ private String public_email;
+ private String qq;
+ private String realname;
+ private String signature;
+ private String status;
+ private String telephone;
+ private String weixin;
+
+ public void setAvatar(String avatar) {
+ this.avatar = avatar;
+ }
+
+ public String getAvatar() {
+ return this.avatar;
+ }
+
+ public void setCellphone(String cellphone) {
+ this.cellphone = cellphone;
+ }
+
+ public String getCellphone() {
+ return this.cellphone;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public String getEmail() {
+ return this.email;
+ }
+
+ public void setEnterprise_id(int enterprise_id) {
+ this.enterprise_id = enterprise_id;
+ }
+
+ public int getEnterprise_id() {
+ return this.enterprise_id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public int getId() {
+ return this.id;
+ }
+
+ public void setNickname(String nickname) {
+ this.nickname = nickname;
+ }
+
+ public String getNickname() {
+ return this.nickname;
+ }
+
+ public void setPublic_cellphone(String public_cellphone) {
+ this.public_cellphone = public_cellphone;
+ }
+
+ public String getPublic_cellphone() {
+ return this.public_cellphone;
+ }
+
+ public void setPublic_email(String public_email) {
+ this.public_email = public_email;
+ }
+
+ public String getPublic_email() {
+ return this.public_email;
+ }
+
+ public void setQq(String qq) {
+ this.qq = qq;
+ }
+
+ public String getQq() {
+ return this.qq;
+ }
+
+ public void setRealname(String realname) {
+ this.realname = realname;
+ }
+
+ public String getRealname() {
+ return this.realname;
+ }
+
+ public void setSignature(String signature) {
+ this.signature = signature;
+ }
+
+ public String getSignature() {
+ return this.signature;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public String getStatus() {
+ return this.status;
+ }
+
+ public void setTelephone(String telephone) {
+ this.telephone = telephone;
+ }
+
+ public String getTelephone() {
+ return this.telephone;
+ }
+
+ public void setWeixin(String weixin) {
+ this.weixin = weixin;
+ }
+
+ public String getWeixin() {
+ return this.weixin;
+ }
+
+ public String getToken() {
+ return token;
+ }
+
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+}
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/model/AgentChangeMessage.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/model/AgentChangeMessage.java
new file mode 100644
index 0000000..877c77f
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/model/AgentChangeMessage.java
@@ -0,0 +1,9 @@
+package com.meiqia.meiqiasdk.model;
+
+public class AgentChangeMessage extends BaseMessage {
+
+ public AgentChangeMessage() {
+ setItemViewType(TYPE_TIP);
+ }
+
+}
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/model/BaseMessage.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/model/BaseMessage.java
new file mode 100644
index 0000000..1782fe8
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/model/BaseMessage.java
@@ -0,0 +1,125 @@
+package com.meiqia.meiqiasdk.model;
+
+public class BaseMessage {
+
+ public static final String TYPE_FROM_CLIENT = "client";
+ public static final String TYPE_FROM_AGENT = "agent";
+
+ public static final String TYPE_WELCOME = "welcome";
+ public static final String TYPE_ENDING = "ending";
+ public static final String TYPE_MESSAGE = "message";
+ public static final String TYPE_INTERNAL = "internal";
+ public static final String TYPE_REMARK = "remark";
+ public static final String TYPE_REPLY = "reply";
+
+ public static final String STATE_ARRIVE = "arrived";
+ public static final String STATE_SENDING = "sending";
+ public static final String STATE_FAILED = "failed";
+
+ public static final String TYPE_CONTENT_TEXT = "text";
+ public static final String TYPE_CONTENT_PHOTO = "photo";
+ public static final String TYPE_CONTENT_VOICE = "audio";
+
+ public static final int TYPE_CLIENT = 0;
+ public static final int TYPE_AGENT = 1;
+ public static final int TYPE_TIME = 2;
+ public static final int TYPE_TIP = 3;
+
+ public static final int MAX_TYPE = 4;
+ private long createdOn;
+ private String agentNickname;
+ private String status;
+ private long id;
+ private String contentType;
+ private String content;
+ private String avatar;
+ private boolean isRead;
+
+ public BaseMessage() {
+ this.createdOn = System.currentTimeMillis();
+ }
+
+ public int getItemViewType() {
+ return itemViewType;
+ }
+
+ public void setItemViewType(int itemViewType) {
+ this.itemViewType = itemViewType;
+ }
+
+ private int itemViewType;
+
+ public long getCreatedOn() {
+ return createdOn;
+ }
+
+ public void setCreatedOn(long createdOn) {
+ this.createdOn = createdOn;
+ }
+
+ public String getAgentNickname() {
+ return agentNickname;
+ }
+
+ public void setAgentNickname(String agentNickname) {
+ this.agentNickname = agentNickname;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getContentType() {
+ return contentType;
+ }
+
+ public void setContentType(String contentType) {
+ this.contentType = contentType;
+ }
+
+ public String getContent() {
+ return content;
+ }
+
+ public void setContent(String content) {
+ this.content = content;
+ }
+
+ public String getAvatar() {
+ return avatar;
+ }
+
+ public void setAvatar(String avatar) {
+ this.avatar = avatar;
+ }
+
+ public boolean isRead() {
+ return isRead;
+ }
+
+ public void setIsRead(boolean isRead) {
+ this.isRead = isRead;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof BaseMessage)) {
+ return false;
+ } else {
+ BaseMessage baseMessage = (BaseMessage) o;
+ return id == baseMessage.getId();
+ }
+ }
+}
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/model/LeaveTipMessage.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/model/LeaveTipMessage.java
new file mode 100644
index 0000000..fd375de
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/model/LeaveTipMessage.java
@@ -0,0 +1,9 @@
+package com.meiqia.meiqiasdk.model;
+
+public class LeaveTipMessage extends BaseMessage {
+
+ public LeaveTipMessage() {
+ setItemViewType(TYPE_TIP);
+ }
+
+}
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/model/PhotoMessage.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/model/PhotoMessage.java
new file mode 100644
index 0000000..e7c0704
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/model/PhotoMessage.java
@@ -0,0 +1,36 @@
+package com.meiqia.meiqiasdk.model;
+
+import com.meiqia.core.bean.MQMessage;
+
+public class PhotoMessage extends BaseMessage {
+
+ private String localPath;
+ private String url;
+
+ public PhotoMessage() {
+ setItemViewType(TYPE_CLIENT);
+ setContentType(MQMessage.TYPE_CONTENT_PHOTO);
+ }
+
+ public PhotoMessage(String url) {
+ this();
+ this.url = url;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public String getLocalPath() {
+ return localPath;
+ }
+
+ public void setLocalPath(String localPath) {
+ this.localPath = localPath;
+ }
+
+}
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/model/TextMessage.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/model/TextMessage.java
new file mode 100644
index 0000000..af9c19f
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/model/TextMessage.java
@@ -0,0 +1,15 @@
+package com.meiqia.meiqiasdk.model;
+
+import com.meiqia.core.bean.MQMessage;
+
+public class TextMessage extends BaseMessage {
+ public TextMessage() {
+ setItemViewType(TYPE_CLIENT);
+ setContentType(MQMessage.TYPE_CONTENT_TEXT);
+ }
+
+ public TextMessage(String content) {
+ this();
+ setContent(content);
+ }
+}
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/model/VoiceMessage.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/model/VoiceMessage.java
new file mode 100644
index 0000000..fbdf113
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/model/VoiceMessage.java
@@ -0,0 +1,46 @@
+package com.meiqia.meiqiasdk.model;
+
+import com.meiqia.core.bean.MQMessage;
+
+public class VoiceMessage extends BaseMessage {
+
+ public static final int NO_DURATION = -1;
+
+ private String localPath;
+ private String url;
+ private int duration = -1;
+
+ public VoiceMessage() {
+ setItemViewType(TYPE_CLIENT);
+ setContentType(MQMessage.TYPE_CONTENT_VOICE);
+ }
+
+ public VoiceMessage(String url) {
+ this();
+ this.url = url;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public String getLocalPath() {
+ return localPath;
+ }
+
+ public void setLocalPath(String localPath) {
+ this.localPath = localPath;
+ }
+
+ public int getDuration() {
+ return duration;
+ }
+
+ public void setDuration(int duration) {
+ this.duration = duration;
+ }
+}
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/util/ErrorCode.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/util/ErrorCode.java
new file mode 100644
index 0000000..558bc1b
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/util/ErrorCode.java
@@ -0,0 +1,11 @@
+package com.meiqia.meiqiasdk.util;
+
+public class ErrorCode {
+ public static final int INIT_FAILED = 19995;
+ public static final int FILE_NOT_FOUND = 19996;
+ public static final int CONV_END = 19997;
+ public static final int UNKNOWN = 20000;
+ public static final int NET_NOT_WORK = 19999;
+ public static final int NO_AGENT_ONLINE = 19998;
+ public static final int PARAMETER_ERROR = 20001;
+}
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/util/MQChatAdapter.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/util/MQChatAdapter.java
new file mode 100644
index 0000000..8864d95
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/util/MQChatAdapter.java
@@ -0,0 +1,448 @@
+package com.meiqia.meiqiasdk.util;
+
+import android.graphics.Bitmap;
+import android.media.MediaPlayer;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.style.ForegroundColorSpan;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.meiqia.core.MQManager;
+import com.meiqia.core.bean.MQMessage;
+import com.meiqia.core.callback.SimpleCallback;
+import com.meiqia.meiqiasdk.R;
+import com.meiqia.meiqiasdk.activity.MQConversationActivity;
+import com.meiqia.meiqiasdk.controller.MediaRecordFunc;
+import com.meiqia.meiqiasdk.model.AgentChangeMessage;
+import com.meiqia.meiqiasdk.model.BaseMessage;
+import com.meiqia.meiqiasdk.model.PhotoMessage;
+import com.meiqia.meiqiasdk.model.VoiceMessage;
+import com.meiqia.meiqiasdk.widget.CircleImageView;
+import com.meiqia.meiqiasdk.widget.RoundProgressBar;
+import com.nostra13.universalimageloader.core.ImageLoader;
+import com.nostra13.universalimageloader.core.assist.FailReason;
+import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
+
+import java.util.List;
+
+public class MQChatAdapter extends BaseAdapter {
+
+ private static final String TAG = MQChatAdapter.class.getSimpleName();
+
+ private MQConversationActivity mqConversationActivity;
+ private List mcMessageList;
+ private ListView listView;
+
+ // ImageLoader
+ private ImageLoader imageLoader;
+ private MediaRecordFunc mediaRecordFunc;
+
+ private final MediaPlayer mediaPlayer;
+ private int onClickPosition;
+ private int playingPosition;
+
+ public MQChatAdapter(MQConversationActivity mqConversationActivity, List mcMessageList, ListView listView) {
+ this.mqConversationActivity = mqConversationActivity;
+ this.mcMessageList = mcMessageList;
+ this.listView = listView;
+ this.imageLoader = ImageLoader.getInstance();
+ this.mediaPlayer = new MediaPlayer();
+ this.mediaRecordFunc = MediaRecordFunc.getInstance(mqConversationActivity);
+ }
+
+ public void addMQMessage(BaseMessage baseMessage) {
+ mcMessageList.add(baseMessage);
+ notifyDataSetChanged();
+ }
+
+ public void addMQMessage(BaseMessage baseMessage, int location) {
+ mcMessageList.add(location, baseMessage);
+ notifyDataSetChanged();
+ }
+
+ public void loadMoreMessage(List baseMessages) {
+ mcMessageList.addAll(0, baseMessages);
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return mcMessageList.get(position).getItemViewType();
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return BaseMessage.MAX_TYPE;
+ }
+
+ @Override
+ public int getCount() {
+ return mcMessageList.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return 0;
+ }
+
+ @Override
+ public View getView(final int position, View convertView, ViewGroup parent) {
+
+ final BaseMessage mcMessage = mcMessageList.get(position);
+ ViewHolder viewHolder = null;
+ TimeViewHolder timeViewHolder = null;
+ TipViewHolder tipViewHolder = null;
+
+ //根据 type 创建不同的 ViewHolder,并缓存
+ if (convertView == null) {
+ switch (getItemViewType(position)) {
+ case BaseMessage.TYPE_AGENT:
+ viewHolder = new ViewHolder();
+ convertView = LayoutInflater.from(mqConversationActivity).inflate(R.layout.mq_item_chat_left, null);
+ viewHolder.contentText = (TextView) convertView.findViewById(R.id.content_text);
+ viewHolder.contentImage = (ImageView) convertView.findViewById(R.id.content_pic);
+ viewHolder.contentImageRl = convertView.findViewById(R.id.content_pic_rl);
+ viewHolder.contentVoice = (TextView) convertView.findViewById(R.id.content_voice);
+ viewHolder.voiceImage = (ImageView) convertView.findViewById(R.id.pic_voice);
+ viewHolder.voiceRl = convertView.findViewById(R.id.content_voice_rl);
+ viewHolder.playProgressbar = (RoundProgressBar) convertView.findViewById(R.id.mc_play_progressbar);
+ viewHolder.usAvatar = (CircleImageView) convertView.findViewById(R.id.us_avatar_iv);
+ viewHolder.unreadCircle = convertView.findViewById(R.id.unread_view);
+ convertView.setTag(viewHolder);
+ break;
+ case BaseMessage.TYPE_CLIENT:
+ viewHolder = new ViewHolder();
+ convertView = LayoutInflater.from(mqConversationActivity).inflate(R.layout.mq_item_chat_right, null);
+ viewHolder.contentText = (TextView) convertView.findViewById(R.id.content_text);
+ viewHolder.contentImage = (ImageView) convertView.findViewById(R.id.content_pic);
+ viewHolder.contentImageRl = convertView.findViewById(R.id.content_pic_rl);
+ viewHolder.contentVoice = (TextView) convertView.findViewById(R.id.content_voice);
+ viewHolder.voiceImage = (ImageView) convertView.findViewById(R.id.pic_voice);
+ viewHolder.voiceRl = convertView.findViewById(R.id.content_voice_rl);
+ viewHolder.playProgressbar = (RoundProgressBar) convertView.findViewById(R.id.mc_play_progressbar);
+ viewHolder.sendingProgressBar = (ProgressBar) convertView.findViewById(R.id.progress_bar);
+ viewHolder.sendState = (ImageView) convertView.findViewById(R.id.send_state);
+ convertView.setTag(viewHolder);
+ break;
+ case BaseMessage.TYPE_TIME:
+ timeViewHolder = new TimeViewHolder();
+ convertView = LayoutInflater.from(mqConversationActivity).inflate(R.layout.mq_item_chat_time, null);
+ timeViewHolder.timeTv = (TextView) convertView.findViewById(R.id.timeTv);
+ convertView.setTag(timeViewHolder);
+ break;
+ case BaseMessage.TYPE_TIP:
+ tipViewHolder = new TipViewHolder();
+ convertView = LayoutInflater.from(mqConversationActivity).inflate(R.layout.mq_item_msg_tip, null, false);
+ tipViewHolder.contentTv = (TextView) convertView.findViewById(R.id.content_tv);
+ convertView.setTag(tipViewHolder);
+ break;
+ }
+ } else {
+ switch (getItemViewType(position)) {
+ case BaseMessage.TYPE_AGENT:
+ viewHolder = (ViewHolder) convertView.getTag();
+ break;
+ case BaseMessage.TYPE_CLIENT:
+ viewHolder = (ViewHolder) convertView.getTag();
+ break;
+ case BaseMessage.TYPE_TIME:
+ timeViewHolder = (TimeViewHolder) convertView.getTag();
+ break;
+ case BaseMessage.TYPE_TIP:
+ tipViewHolder = (TipViewHolder) convertView.getTag();
+ break;
+ }
+ }
+
+ //显示对话时间
+ if (getItemViewType(position) == BaseMessage.TYPE_TIME) {
+ timeViewHolder.timeTv.setText(MQTimeUtils.parseTime(mcMessage.getCreatedOn()));
+ }
+ //显示对话 Tip
+ else if (getItemViewType(position) == BaseMessage.TYPE_TIP) {
+ if (mcMessage instanceof AgentChangeMessage) {
+ setDirectionMessageContent(mcMessage.getAgentNickname(), tipViewHolder.contentTv);
+ } else {
+ tipViewHolder.contentTv.setText(R.string.mq_leave_msg_tips);
+ }
+ }
+ //显示消息:文字、图片、语音
+ else if (getItemViewType(position) == BaseMessage.TYPE_AGENT || getItemViewType(position) == BaseMessage.TYPE_CLIENT) {
+ // 文字
+ if (MQMessage.TYPE_CONTENT_TEXT.equals(mcMessage.getContentType())) {
+ viewHolder.contentText.setVisibility(View.VISIBLE);
+ viewHolder.contentImageRl.setVisibility(View.GONE);
+ viewHolder.voiceRl.setVisibility(View.GONE);
+ if (!TextUtils.isEmpty(mcMessage.getContent())) {
+ viewHolder.contentText.setText(MQEmotionUtil.getEmotionText(mqConversationActivity, mcMessage.getContent()));
+ }
+
+ }
+ // 图片
+ else if (MQMessage.TYPE_CONTENT_PHOTO.equals(mcMessage.getContentType())) {
+ viewHolder.contentText.setVisibility(View.GONE);
+ viewHolder.voiceRl.setVisibility(View.GONE);
+
+ String path = ((PhotoMessage) mcMessage).getLocalPath();
+ boolean isLocalImageExist = MQUtils.isFileExist(path);
+
+ String url;
+ if (isLocalImageExist) {
+ url = "file://" + ((PhotoMessage) mcMessage).getLocalPath();
+ } else {
+ url = ((PhotoMessage) mcMessage).getUrl();
+ }
+ imageLoader.displayImage(url, viewHolder.contentImage, new ImageLoadingListener() {
+
+ @Override
+ public void onLoadingStarted(String imageUri, View view) {
+
+ }
+
+ @Override
+ public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
+
+ }
+
+ @Override
+ public void onLoadingComplete(final String imageUri, View view, Bitmap loadedImage) {
+ if (loadedImage != null) {
+ if (listView.getLastVisiblePosition() == (listView.getCount() - 1)) {
+ listView.setSelection(getCount() - 1);
+ }
+
+ view.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View arg0) {
+ mqConversationActivity.displayPhoto(imageUri);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onLoadingCancelled(String imageUri, View view) {
+
+ }
+ });
+ viewHolder.contentImageRl.setVisibility(View.VISIBLE);
+
+ }
+ //语音
+ else if (MQMessage.TYPE_CONTENT_VOICE.equals(mcMessage.getContentType())) {
+ viewHolder.contentText.setVisibility(View.GONE);
+ viewHolder.contentImageRl.setVisibility(View.GONE);
+ viewHolder.voiceRl.setVisibility(View.VISIBLE);
+
+ final VoiceMessage voiceMessage = (VoiceMessage) mcMessage;
+ // 语音未读显示 小红点
+ if (viewHolder.unreadCircle != null) {
+ if (!voiceMessage.isRead()) {
+ viewHolder.unreadCircle.setVisibility(View.VISIBLE);
+ } else {
+ viewHolder.unreadCircle.setVisibility(View.GONE);
+ }
+ }
+ String path = voiceMessage.getLocalPath();
+ // 本地拼接
+ if (TextUtils.isEmpty(path)) {
+ path = MediaRecordFunc.VOICE_STORE_PATH + "/" + voiceMessage.getId() + ".amr";
+ voiceMessage.setLocalPath(path);
+ }
+ boolean isLocalVoiceFileExist = MQUtils.isFileExist(path);
+
+ // 填补缺失的duration
+ if (voiceMessage.getDuration() == VoiceMessage.NO_DURATION && isLocalVoiceFileExist) {
+ int duration = MediaRecordFunc.getDuration(mqConversationActivity, voiceMessage.getLocalPath());
+ voiceMessage.setDuration(duration);
+ }
+
+ // 如果没有设置录音时间,则显示空
+ String voiceDuration = voiceMessage.getDuration() == VoiceMessage.NO_DURATION ? " " : "" + voiceMessage.getDuration();
+ if (mcMessage.getItemViewType() == BaseMessage.TYPE_AGENT) {
+ viewHolder.contentVoice.setText(voiceDuration + "\"" + " ");
+ } else {
+ viewHolder.contentVoice.setText(" " + voiceDuration + "\"");
+ }
+
+ // 刷新录音播放状态
+ if (onClickPosition != position) {
+ viewHolder.voiceImage.setBackgroundResource(R.drawable.mq_ic_voice_play);
+ viewHolder.playProgressbar.stop();
+ }
+
+ viewHolder.voiceRl.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ onClickPosition = position;
+ playOrStopVoice(v, voiceMessage);
+ }
+ });
+ }
+ //显示客服头像
+ if (getItemViewType(position) == BaseMessage.TYPE_AGENT) {
+ imageLoader.displayImage(mcMessage.getAvatar(), viewHolder.usAvatar);
+ }
+ //显示发送状态:发送中、发送失败
+ else if (getItemViewType(position) == BaseMessage.TYPE_CLIENT) {
+ if (viewHolder.sendingProgressBar != null) {
+ if (MQMessage.STATE_SENDING.equals(mcMessage.getStatus())) {
+ viewHolder.sendingProgressBar.setVisibility(View.VISIBLE);
+ viewHolder.sendState.setVisibility(View.GONE);
+ } else if (MQMessage.STATE_ARRIVE.equals(mcMessage.getStatus())) {
+ viewHolder.sendingProgressBar.setVisibility(View.GONE);
+ viewHolder.sendState.setVisibility(View.GONE);
+ } else if (MQMessage.STATE_FAILED.equals(mcMessage.getStatus())) {
+ viewHolder.sendingProgressBar.setVisibility(View.GONE);
+ viewHolder.sendState.setVisibility(View.VISIBLE);
+ viewHolder.sendState.setBackgroundResource(R.drawable.mq_ic_msg_failed);
+ viewHolder.sendState.setOnClickListener(new FailedMessageOnClickListener(mcMessage));
+ viewHolder.sendState.setTag(mcMessage.getId());
+ }
+ }
+ }
+ }
+
+ return convertView;
+ }
+
+ private void setDirectionMessageContent(String agentNickName, TextView tipTv) {
+ if (agentNickName != null) {
+ String text = String.format(tipTv.getResources().getString(R.string.mq_direct_content), agentNickName);
+ int start = text.indexOf(agentNickName);
+ SpannableStringBuilder style = new SpannableStringBuilder(text);
+ style.setSpan(new ForegroundColorSpan(tipTv.getResources().getColor(R.color.mq_direct_agent_nickname_color)), start, start + agentNickName.length(), Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
+ tipTv.setText(style);
+ }
+ }
+
+ static class ViewHolder {
+ TextView contentText;
+ ImageView contentImage;
+ View contentImageRl;
+ TextView contentVoice;
+ ImageView voiceImage;
+ View voiceRl;
+ RoundProgressBar playProgressbar;
+ ProgressBar sendingProgressBar;
+ ImageView sendState;
+ CircleImageView usAvatar;
+ View unreadCircle;
+ }
+
+ static class TimeViewHolder {
+ TextView timeTv;
+ }
+
+ static class TipViewHolder {
+ TextView contentTv;
+ }
+
+ private class FailedMessageOnClickListener implements OnClickListener {
+
+ private BaseMessage failedMessage;
+
+ public FailedMessageOnClickListener(BaseMessage failedMessage) {
+ this.failedMessage = failedMessage;
+ }
+
+ @Override
+ public void onClick(View v) {
+ mqConversationActivity.sendMessage(failedMessage);
+ }
+
+ }
+
+ private void playOrStopVoice(View v, final VoiceMessage voiceMessage) {
+ // 设置已读状态
+ voiceMessage.setIsRead(true);
+ MQManager.getInstance(mqConversationActivity).updateMessage(voiceMessage.getId(), true);
+
+ final ImageView voiceImage = (ImageView) v.findViewById(R.id.pic_voice);
+ final RoundProgressBar playProgressBar = (RoundProgressBar) v.findViewById(R.id.mc_play_progressbar);
+ boolean isVoiceFileAvailable = MQUtils.isFileExist(voiceMessage.getLocalPath());
+ // 如果本地文件不可用,则从网络获取
+ if (!isVoiceFileAvailable) {
+ final String fileName = voiceMessage.getId() + ".amr";
+ final String filePath = MediaRecordFunc.VOICE_STORE_PATH;
+ MediaRecordFunc.getInstance(mqConversationActivity).downloadVoice(voiceMessage.getUrl(), filePath, fileName, new SimpleCallback() {
+ @Override
+ public void onSuccess() {
+ // 主线程回调
+ mqConversationActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ voiceMessage.setLocalPath(filePath + "/" + fileName);
+ notifyDataSetChanged();
+ }
+ });
+ }
+
+ @Override
+ public void onFailure(int code, String message) {
+
+ }
+ });
+ }
+ mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ voiceImage.setBackgroundResource(R.drawable.mq_ic_voice_play);
+ playProgressBar.stop();
+ }
+ });
+ try {
+ String localPath = voiceMessage.getLocalPath();
+ if (!mediaPlayer.isPlaying()) {
+ mediaPlayer.reset();
+ mediaPlayer.setDataSource(localPath);
+ mediaPlayer.prepare();// 缓冲
+ mediaPlayer.start();// 开始或恢复播放
+ voiceImage.setBackgroundResource(R.drawable.mq_ic_voice_stop);
+ playProgressBar.setMax(voiceMessage.getDuration() * 10);
+ playProgressBar.start();
+ playingPosition = onClickPosition;
+ } else {
+ // 再次点击,停止变为可播放按钮状态
+ if (playingPosition == onClickPosition) {
+ mediaPlayer.stop();
+ voiceImage.setBackgroundResource(R.drawable.mq_ic_voice_play);
+ playProgressBar.stop();
+ playingPosition = -1;
+ }
+ // 其它地方点击,播放,变为可停止状态
+ else {
+ mediaPlayer.reset();
+ mediaPlayer.setDataSource(localPath);
+ mediaPlayer.prepare();// 缓冲
+ mediaPlayer.start();// 开始或恢复播放
+ voiceImage.setBackgroundResource(R.drawable.mq_ic_voice_stop);
+ playProgressBar.start();
+ playingPosition = onClickPosition;
+ }
+ }
+ } catch (Exception e) {
+ playingPosition = -1;
+ }
+
+ notifyDataSetChanged();
+ }
+
+}
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/util/MQConfig.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/util/MQConfig.java
new file mode 100644
index 0000000..65b9b07
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/util/MQConfig.java
@@ -0,0 +1,37 @@
+package com.meiqia.meiqiasdk.util;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+
+public class MQConfig {
+
+ private SharedPreferences sp;
+ private SharedPreferences.Editor editor;
+
+ public MQConfig(Context context) {
+ sp = context.getSharedPreferences("MeiQia_Config", Context.MODE_PRIVATE);
+ }
+
+ private void putString(String key, String value) {
+ editor = sp.edit();
+ editor.putString(key, value);
+ editor.apply();
+ }
+
+ private void putBoolean(String key, boolean value) {
+ editor = sp.edit();
+ editor.putBoolean(key, value);
+ editor.apply();
+ }
+
+ public boolean getShowVoiceMessage() {
+ return sp.getBoolean("meiqia_show_voice_message", true);
+ }
+
+ public void setShowVoiceMessage(boolean value) {
+ putBoolean("meiqia_show_voice_message", value);
+ }
+
+}
+
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/util/MQEmotionUtil.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/util/MQEmotionUtil.java
new file mode 100644
index 0000000..d9afad2
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/util/MQEmotionUtil.java
@@ -0,0 +1,151 @@
+package com.meiqia.meiqiasdk.util;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.ImageSpan;
+
+import com.meiqia.meiqiasdk.R;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class MQEmotionUtil {
+ public static final String REGEX_EMOJI = ":[\u4e00-\u9fa5\\w]+:";
+ public static final String REGEX_WEBSITE = "([a-zA-Z]+://)?[a-zA-Z\\d-]+(\\.[a-zA-Z\\d-]+)*(\\.[a-zA-Z]+)+(:\\d+)?(/[\\w\\d\\.\\-~!@#$%^&*+?:_/=<>]*)?";
+ public static final String REGEX_GROUP = "(" + REGEX_EMOJI + ")|(" + REGEX_WEBSITE + ")";
+
+ private MQEmotionUtil() {
+ }
+
+ public static final Map sEmotionMap;
+
+ public static final String[] sEmotionKeyArr = new String[]{
+ ":smile:",
+ ":smiley:",
+ ":grinning:",
+ ":blush:",
+ ":relaxed:",
+ ":wink:",
+ ":heart_eyes:",
+ ":kissing_heart:",
+ ":kissing_closed_eyes:",
+ ":kissing:",
+ ":kissing_smiling_eyes:",
+ ":stuck_out_tongue_winking_eye:",
+ ":stuck_out_tongue_closed_eyes:",
+ ":stuck_out_tongue:",
+ ":flushed:",
+ ":grin:",
+ ":pensive:",
+ ":relieved:",
+ ":unamused:",
+ ":disappointed:",
+ ":persevere:",
+ ":cry:",
+ ":joy:",
+ ":sob:",
+ ":sleepy:",
+ ":disappointed_relieved:",
+ ":cold_sweat:",
+ ":sweat_smile:",
+ ":sweat:",
+ ":weary:",
+ ":tired_face:",
+ ":fearful:",
+ ":scream:",
+ ":angry:",
+ ":rage:",
+ ":dog:",
+ };
+
+ public static final int[] sEmotionValueArr = new int[]{
+ R.drawable.mq_emoji_1,
+ R.drawable.mq_emoji_2,
+ R.drawable.mq_emoji_3,
+ R.drawable.mq_emoji_4,
+ R.drawable.mq_emoji_5,
+ R.drawable.mq_emoji_6,
+ R.drawable.mq_emoji_7,
+ R.drawable.mq_emoji_8,
+ R.drawable.mq_emoji_9,
+ R.drawable.mq_emoji_10,
+ R.drawable.mq_emoji_11,
+ R.drawable.mq_emoji_12,
+ R.drawable.mq_emoji_13,
+ R.drawable.mq_emoji_14,
+ R.drawable.mq_emoji_15,
+ R.drawable.mq_emoji_16,
+ R.drawable.mq_emoji_17,
+ R.drawable.mq_emoji_18,
+ R.drawable.mq_emoji_19,
+ R.drawable.mq_emoji_20,
+ R.drawable.mq_emoji_21,
+ R.drawable.mq_emoji_22,
+ R.drawable.mq_emoji_23,
+ R.drawable.mq_emoji_24,
+ R.drawable.mq_emoji_25,
+ R.drawable.mq_emoji_26,
+ R.drawable.mq_emoji_27,
+ R.drawable.mq_emoji_28,
+ R.drawable.mq_emoji_29,
+ R.drawable.mq_emoji_30,
+ R.drawable.mq_emoji_31,
+ R.drawable.mq_emoji_32,
+ R.drawable.mq_emoji_33,
+ R.drawable.mq_emoji_34,
+ R.drawable.mq_emoji_35,
+ R.drawable.mq_emoji_36
+ };
+
+ static {
+ sEmotionMap = new HashMap<>();
+ int count = sEmotionKeyArr.length;
+ for (int i = 0; i < count; i++) {
+ sEmotionMap.put(sEmotionKeyArr[i], sEmotionValueArr[i]);
+ }
+ }
+
+ public static int getImgByName(String imgName) {
+ Integer integer = sEmotionMap.get(imgName);
+ return integer == null ? -1 : integer;
+ }
+
+ public static SpannableString getEmotionText(Context context, String source) {
+ SpannableString spannableString = new SpannableString(source);
+ Pattern pattern = Pattern.compile(REGEX_GROUP);
+ Matcher matcher = pattern.matcher(spannableString);
+ if (matcher.find()) {
+ matcher.reset();
+ }
+
+ while (matcher.find()) {
+ String emojiStr = matcher.group(1);
+ String websiteStr = matcher.group(2);
+ // 处理emoji表情
+ if (emojiStr != null) {
+ ImageSpan imageSpan = null;
+ int imgRes = getImgByName(emojiStr);
+ Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), imgRes);
+ if (bitmap != null) {
+ imageSpan = new ImageSpan(context, bitmap);
+ }
+ if (imageSpan != null) {
+ int start = matcher.start(1);
+ spannableString.setSpan(imageSpan, start, start + emojiStr.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ }
+ // 处理网址
+ if (websiteStr != null) {
+ int start = matcher.start(2);
+ spannableString.setSpan(new ForegroundColorSpan(context.getResources().getColor(R.color.mq_url_foreground)), start, start + websiteStr.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ }
+ return spannableString;
+ }
+}
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/util/MQTimeUtils.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/util/MQTimeUtils.java
new file mode 100644
index 0000000..2648360
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/util/MQTimeUtils.java
@@ -0,0 +1,103 @@
+package com.meiqia.meiqiasdk.util;
+
+import android.content.Context;
+
+import com.meiqia.meiqiasdk.R;
+import com.meiqia.meiqiasdk.model.BaseMessage;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+public class MQTimeUtils {
+ // 2分钟
+ private static final long TIME_INTERNAL_LIMIT = 120000;
+ private static final String MONTH_DAY = "M-d";
+ private static final String HOURS_MINUTE = "H:mm";
+ private static String TODAY = "today";
+ private static String YESTERDAY = "yesterday";
+
+ /**
+ * 必须先初始化,因为需要国际化处理
+ *
+ * @param context context
+ */
+ public static void init(Context context) {
+ //动态读取时间轴上的文字,方便国际化
+ TODAY = context.getResources().getString(R.string.mq_timeline_today);
+ YESTERDAY = context.getResources().getString(R.string.mq_timeline_yesterday);
+ }
+
+ public static void refreshMQTimeItem(List mcMessageList) {
+ // 从底部开始删除
+ for (int i = mcMessageList.size() - 1; i >= 0; i--) {
+ if (mcMessageList.get(i).getItemViewType() == BaseMessage.TYPE_TIME) {
+ mcMessageList.remove(i);
+ }
+ }
+ addMQTimeItem(mcMessageList);
+ }
+
+ private static void addMQTimeItem(List mcMessageList) {
+ // 从底部开始插入
+ for (int i = mcMessageList.size() - 1; i >= 0; i--) {
+ // 不是第一条消息
+ if (i != 0) {
+ long currentMsgTime = mcMessageList.get(i).getCreatedOn();
+ long previousMsgTime = mcMessageList.get(i - 1).getCreatedOn();
+ long difTime = currentMsgTime - previousMsgTime;
+ // 如果时间前面是 BaseMessage.TYPE_TIME,不添加时间
+ if (difTime > TIME_INTERNAL_LIMIT && mcMessageList.get(i).getItemViewType() != BaseMessage.TYPE_TIME) {
+ // 添加TimeItem
+ BaseMessage timeItem = new BaseMessage();
+ timeItem.setItemViewType(BaseMessage.TYPE_TIME);
+ // 设置Item类型
+ timeItem.setCreatedOn(currentMsgTime);
+ mcMessageList.add(i, timeItem);
+ }
+ }
+ }
+ }
+
+ public static String parseTime(long time) {
+ String timeStr;
+ Date curDates = new Date(time);
+ SimpleDateFormat simpleDateFormat = new SimpleDateFormat(HOURS_MINUTE, Locale.getDefault());
+ timeStr = simpleDateFormat.format(curDates);
+
+ // 今天
+ if (time > getTodayZeroTime()) {
+ timeStr = TODAY + " " + timeStr;
+ }
+ // 昨天
+ else if (time > getYesterdayZeroTime() && time < getTodayZeroTime()) {
+ timeStr = YESTERDAY + " " + timeStr;
+ }
+ // 昨天以前
+ else {
+ SimpleDateFormat formatters2 = new SimpleDateFormat(MONTH_DAY, Locale.getDefault());
+ timeStr = formatters2.format(curDates) + " " + timeStr;
+ }
+ return timeStr;
+ }
+
+ private static long getTodayZeroTime() {
+ Calendar cal = Calendar.getInstance();
+ cal.set(Calendar.HOUR_OF_DAY, 0);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MINUTE, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+ return cal.getTimeInMillis();
+ }
+
+ private static long getYesterdayZeroTime() {
+ Calendar cal = Calendar.getInstance();
+ cal.set(Calendar.HOUR_OF_DAY, -24);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MINUTE, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+ return cal.getTimeInMillis();
+ }
+}
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/util/MQUtils.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/util/MQUtils.java
new file mode 100644
index 0000000..99f8a9c
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/util/MQUtils.java
@@ -0,0 +1,394 @@
+package com.meiqia.meiqiasdk.util;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.Build;
+import android.os.Environment;
+import android.os.Handler;
+import android.support.annotation.StringRes;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import com.meiqia.core.bean.MQAgent;
+import com.meiqia.core.bean.MQMessage;
+import com.meiqia.meiqiasdk.R;
+import com.meiqia.meiqiasdk.dialog.MQConfirmDialog;
+import com.meiqia.meiqiasdk.model.Agent;
+import com.meiqia.meiqiasdk.model.BaseMessage;
+import com.meiqia.meiqiasdk.model.PhotoMessage;
+import com.meiqia.meiqiasdk.model.TextMessage;
+import com.meiqia.meiqiasdk.model.VoiceMessage;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class MQUtils {
+ private static Handler sHandler = new Handler();
+
+ public static void runInThread(Runnable task) {
+ new Thread(task).start();
+ }
+
+ public static void runInUIThread(Runnable task) {
+ sHandler.post(task);
+ }
+
+ public static void runInUIThread(Runnable task, long delayMillis) {
+ sHandler.postDelayed(task, delayMillis);
+ }
+
+ public static BaseMessage parseMQMessageIntoChatBase(MQMessage message, BaseMessage baseMessage) {
+ int itemType;
+ if (MQMessage.TYPE_FROM_CLIENT.equals(message.getFrom_type())) {
+ itemType = BaseMessage.TYPE_CLIENT;
+ } else {
+ itemType = BaseMessage.TYPE_AGENT;
+ }
+ baseMessage.setStatus(message.getStatus());
+ baseMessage.setItemViewType(itemType);
+ baseMessage.setContent(message.getContent());
+ baseMessage.setContentType(message.getContent_type());
+ baseMessage.setStatus(message.getStatus());
+ baseMessage.setId(message.getId());
+ baseMessage.setAgentNickname(message.getAgent_nickname());
+ baseMessage.setCreatedOn(message.getCreated_on());
+ baseMessage.setAvatar(message.getAvatar());
+ baseMessage.setIsRead(message.is_read());
+ if (MQMessage.TYPE_CONTENT_PHOTO.equals(message.getContent_type())) {
+ ((PhotoMessage) baseMessage).setUrl(message.getMedia_url());
+ } else if (MQMessage.TYPE_CONTENT_VOICE.equals(message.getContent_type())) {
+ ((VoiceMessage) baseMessage).setUrl(message.getMedia_url());
+ }
+ return baseMessage;
+ }
+
+ public static BaseMessage parseMQMessageIntoChatBase(MQMessage message) {
+ BaseMessage baseMessage;
+ int itemType;
+ if (MQMessage.TYPE_FROM_CLIENT.equals(message.getFrom_type())) {
+ itemType = BaseMessage.TYPE_CLIENT;
+ } else {
+ itemType = BaseMessage.TYPE_AGENT;
+ }
+ if (MQMessage.TYPE_CONTENT_PHOTO.equals(message.getContent_type())) {
+ baseMessage = new PhotoMessage(message.getMedia_url());
+ baseMessage.setContent("[photo]");
+ } else if (MQMessage.TYPE_CONTENT_VOICE.equals(message.getContent_type())) {
+ baseMessage = new VoiceMessage(message.getMedia_url());
+ baseMessage.setContent("[voice]");
+ } else {
+ baseMessage = new TextMessage(message.getContent());
+ baseMessage.setContent(message.getContent());
+ }
+ baseMessage.setStatus(message.getStatus());
+ baseMessage.setItemViewType(itemType);
+ baseMessage.setContentType(message.getContent_type());
+ baseMessage.setStatus(message.getStatus());
+ baseMessage.setId(message.getId());
+ baseMessage.setAgentNickname(message.getAgent_nickname());
+ baseMessage.setCreatedOn(message.getCreated_on());
+ baseMessage.setAvatar(message.getAvatar());
+ baseMessage.setIsRead(message.is_read());
+ return baseMessage;
+ }
+
+ /**
+ * 将 MQMessage 转换为 BaseMessage
+ *
+ * @param mqMessageList 待转换的消息
+ * @return 转换后的消息
+ */
+ public static List parseMQMessageToChatBaseList(List mqMessageList) {
+ List baseMessages = new ArrayList();
+ for (MQMessage mqMessage : mqMessageList) {
+ baseMessages.add(parseMQMessageIntoChatBase(mqMessage));
+ }
+ return baseMessages;
+ }
+
+ public static Agent parseMQAgentToAgent(MQAgent mqAgent) {
+ if (mqAgent == null) return null;
+ Agent agent = new Agent();
+ agent.setId(mqAgent.getId());
+ agent.setNickname(mqAgent.getNickname());
+ return agent;
+ }
+
+ /**
+ * 判断是否有外部存储设备sdcard
+ *
+ * @return true | false
+ */
+ public static boolean isSdcardAvailable() {
+ return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
+ }
+
+ /**
+ * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
+ */
+ public static int dip2px(Context context, float dpValue) {
+ final float scale = context.getResources().getDisplayMetrics().density;
+ return (int) (dpValue * scale + 0.5f);
+ }
+
+ public static boolean isFileExist(String filePath) {
+ boolean isFileExist;
+ try {
+ File file = new File(filePath);
+ isFileExist = file.exists();
+ } catch (Exception e) {
+ isFileExist = false;
+ }
+ return isFileExist;
+ }
+
+ public static String getPicStorePath(Context ctx) {
+ File file = ctx.getExternalFilesDir(null);
+ if (!file.exists()) {
+ file.mkdir();
+ }
+ File imageStoreFile = new File(file.getAbsolutePath() + "/mq");
+ if (!imageStoreFile.exists()) {
+ imageStoreFile.mkdir();
+ }
+ return imageStoreFile.getAbsolutePath();
+ }
+
+ /**
+ * 获取状态栏高度
+ *
+ * @param context
+ * @return
+ */
+ public static int getStatusBarHeight(Context context) {
+ Resources resources = context.getResources();
+ int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
+ return resources.getDimensionPixelSize(resourceId);
+ }
+
+ /**
+ * 获取屏幕高度
+ *
+ * @param context
+ * @return
+ */
+ public static int getScreenHeight(Context context) {
+ WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ DisplayMetrics dm = new DisplayMetrics();
+ windowManager.getDefaultDisplay().getMetrics(dm);
+ return dm.heightPixels;
+ }
+
+ /**
+ * 显示吐司
+ *
+ * @param context
+ * @param text
+ */
+ public static void show(Context context, CharSequence text) {
+ if (!TextUtils.isEmpty(text)) {
+ if (text.length() < 10) {
+ Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(context, text, Toast.LENGTH_LONG).show();
+ }
+ }
+ }
+
+ /**
+ * 显示吐司
+ *
+ * @param context
+ * @param resId
+ */
+ public static void show(Context context, @StringRes int resId) {
+ show(context, context.getResources().getString(resId));
+ }
+
+ /**
+ * 在子线程中显示吐司时使用该方法
+ *
+ * @param context
+ * @param text
+ */
+ public static void showSafe(final Context context, final CharSequence text) {
+ runInUIThread(new Runnable() {
+ @Override
+ public void run() {
+ show(context, text);
+ }
+ });
+ }
+
+ /**
+ * 在子线程中显示吐司时使用该方法
+ *
+ * @param context
+ * @param resId
+ */
+ public static void showSafe(Context context, @StringRes int resId) {
+ showSafe(context, context.getResources().getString(resId));
+ }
+
+ /**
+ * 拷贝文档到黏贴板
+ *
+ * @param text
+ */
+ public static void clip(Context context, String text) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+ android.text.ClipboardManager clipboardManager = (android.text.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
+ clipboardManager.setText(text);
+ } else {
+ ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
+ clipboardManager.setPrimaryClip(ClipData.newPlainText("mq_content", text));
+ }
+ }
+
+ /**
+ * 关闭activity中打开的键盘
+ *
+ * @param activity
+ */
+ public static void closeKeyboard(Activity activity) {
+ View view = activity.getWindow().peekDecorView();
+ if (view != null) {
+ InputMethodManager inputMethodManager = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
+ }
+ }
+
+ /**
+ * 关闭dialog中打开的键盘
+ *
+ * @param dialog
+ */
+ public static void closeKeyboard(Dialog dialog) {
+ View view = dialog.getWindow().peekDecorView();
+ if (view != null) {
+ InputMethodManager inputMethodManager = (InputMethodManager) dialog.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
+ }
+ }
+
+ /**
+ * 打开键盘
+ *
+ * @param context
+ * @param editText
+ */
+ public static void openKeyboard(final Context context, final EditText editText) {
+ runInUIThread(new Runnable() {
+ @Override
+ public void run() {
+ editText.requestFocus();
+ editText.setSelection(editText.getText().toString().length());
+ InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(editText, InputMethodManager.SHOW_FORCED);
+ }
+ }, 300);
+ }
+
+ /**
+ * 请求权限
+ *
+ * @param activity
+ * @param requestCode
+ * @param delegate
+ * @param permissionArr
+ */
+ public static void requestPermission(final Activity activity, final int requestCode, String msg, final Delegate delegate, String... permissionArr) {
+ List permissionsNeeded = new ArrayList<>();
+ final List permissionsList = new ArrayList<>();
+
+ for (String permission : permissionArr) {
+ if (!addPermission(activity, permissionsList, permission)) {
+ permissionsNeeded.add(permission.substring(permission.lastIndexOf(".") + 1));
+ }
+ }
+
+ if (permissionsList.size() > 0) {
+ if (permissionsNeeded.size() > 0) {
+ new MQConfirmDialog(activity, activity.getString(R.string.mq_runtime_permission_tip_title), msg, new MQConfirmDialog.OnDialogCallback() {
+ @Override
+ public void onClickConfirm() {
+ ActivityCompat.requestPermissions(activity, permissionsList.toArray(new String[permissionsList.size()]), requestCode);
+ }
+
+ @Override
+ public void onClickCancel() {
+ delegate.onPermissionDenied();
+ }
+ }).show();
+ return;
+ }
+ ActivityCompat.requestPermissions(activity, permissionsList.toArray(new String[permissionsList.size()]), requestCode);
+ return;
+ }
+ delegate.onPermissionGranted();
+ }
+
+ private static boolean addPermission(Activity activity, List permissionsList, String permission) {
+ if (ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) {
+ permissionsList.add(permission);
+ if (!ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 处理权限结果
+ *
+ * @param permissions
+ * @param grantResults
+ * @param delegate
+ * @param permissionArr
+ */
+ public static void handlePermissionResult(String[] permissions, int[] grantResults, Delegate delegate, String... permissionArr) {
+ Map perms = new HashMap<>();
+ for (String permission : permissionArr) {
+ perms.put(permission, PackageManager.PERMISSION_GRANTED);
+ }
+
+ for (int i = 0; i < permissions.length; i++) {
+ perms.put(permissions[i], grantResults[i]);
+ }
+
+ boolean granted = true;
+ for (String permission : permissionArr) {
+ if (perms.get(permission) != PackageManager.PERMISSION_GRANTED) {
+ granted = false;
+ break;
+ }
+ }
+
+ if (granted) {
+ delegate.onPermissionGranted();
+ } else {
+ delegate.onPermissionDenied();
+ }
+ }
+
+ public interface Delegate {
+ void onPermissionGranted();
+
+ void onPermissionDenied();
+ }
+}
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/widget/CircleImageView.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/widget/CircleImageView.java
new file mode 100644
index 0000000..09a35eb
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/widget/CircleImageView.java
@@ -0,0 +1,216 @@
+package com.meiqia.meiqiasdk.widget;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+public class CircleImageView extends ImageView {
+
+ private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
+
+ private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
+ private static final int COLORDRAWABLE_DIMENSION = 1;
+
+ private static final int DEFAULT_BORDER_WIDTH = 0;
+ private static final int DEFAULT_BORDER_COLOR = Color.BLACK;
+
+ private final RectF mDrawableRect = new RectF();
+ private final RectF mBorderRect = new RectF();
+
+ private final Matrix mShaderMatrix = new Matrix();
+ private final Paint mBitmapPaint = new Paint();
+ private final Paint mBorderPaint = new Paint();
+
+ private int mBorderColor = DEFAULT_BORDER_COLOR;
+ private int mBorderWidth = DEFAULT_BORDER_WIDTH;
+
+ private Bitmap mBitmap;
+ private BitmapShader mBitmapShader;
+ private int mBitmapWidth;
+ private int mBitmapHeight;
+
+ private float mDrawableRadius;
+ private float mBorderRadius;
+
+ private boolean mReady;
+ private boolean mSetupPending;
+
+ public CircleImageView(Context context) {
+ super(context);
+
+ init();
+ }
+
+ public CircleImageView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mBorderWidth = DEFAULT_BORDER_WIDTH;
+ mBorderColor = DEFAULT_BORDER_COLOR;
+ init();
+ }
+
+ private void init() {
+ super.setScaleType(SCALE_TYPE);
+ mReady = true;
+
+ if (mSetupPending) {
+ setup();
+ mSetupPending = false;
+ }
+ }
+
+ @Override
+ public ScaleType getScaleType() {
+ return SCALE_TYPE;
+ }
+
+ @Override
+ public void setScaleType(ScaleType scaleType) {
+ if (scaleType != SCALE_TYPE) {
+ throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (getDrawable() == null) {
+ return;
+ }
+
+ canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius, mBitmapPaint);
+ if (mBorderWidth != 0) {
+ canvas.drawCircle(getWidth() / 2, getHeight() / 2, mBorderRadius, mBorderPaint);
+ }
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ setup();
+ }
+
+ @Override
+ public void setImageBitmap(Bitmap bm) {
+ super.setImageBitmap(bm);
+ mBitmap = bm;
+ setup();
+ }
+
+ @Override
+ public void setImageDrawable(Drawable drawable) {
+ super.setImageDrawable(drawable);
+ mBitmap = getBitmapFromDrawable(drawable);
+ setup();
+ }
+
+ @Override
+ public void setImageResource(int resId) {
+ super.setImageResource(resId);
+ mBitmap = getBitmapFromDrawable(getDrawable());
+ setup();
+ }
+
+ @Override
+ public void setImageURI(Uri uri) {
+ super.setImageURI(uri);
+ mBitmap = getBitmapFromDrawable(getDrawable());
+ setup();
+ }
+
+ private Bitmap getBitmapFromDrawable(Drawable drawable) {
+ if (drawable == null) {
+ return null;
+ }
+
+ if (drawable instanceof BitmapDrawable) {
+ return ((BitmapDrawable) drawable).getBitmap();
+ }
+
+ try {
+ Bitmap bitmap;
+
+ if (drawable instanceof ColorDrawable) {
+ bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);
+ } else {
+ bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);
+ }
+
+ Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+ return bitmap;
+ } catch (OutOfMemoryError e) {
+ return null;
+ }
+ }
+
+ private void setup() {
+ if (!mReady) {
+ mSetupPending = true;
+ return;
+ }
+
+ if (mBitmap == null) {
+ return;
+ }
+
+ mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+
+ mBitmapPaint.setAntiAlias(true);
+ mBitmapPaint.setShader(mBitmapShader);
+
+ mBorderPaint.setStyle(Paint.Style.STROKE);
+ mBorderPaint.setAntiAlias(true);
+ mBorderPaint.setColor(mBorderColor);
+ mBorderPaint.setStrokeWidth(mBorderWidth);
+
+ mBitmapHeight = mBitmap.getHeight();
+ mBitmapWidth = mBitmap.getWidth();
+
+ mBorderRect.set(0, 0, getWidth(), getHeight());
+ mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2, (mBorderRect.width() - mBorderWidth) / 2);
+
+ mDrawableRect.set(mBorderWidth, mBorderWidth, mBorderRect.width() - mBorderWidth, mBorderRect.height() - mBorderWidth);
+ mDrawableRadius = Math.min(mDrawableRect.height() / 2, mDrawableRect.width() / 2);
+
+ updateShaderMatrix();
+ invalidate();
+ }
+
+ private void updateShaderMatrix() {
+ float scale;
+ float dx = 0;
+ float dy = 0;
+
+ mShaderMatrix.set(null);
+
+ if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
+ scale = mDrawableRect.height() / (float) mBitmapHeight;
+ dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
+ } else {
+ scale = mDrawableRect.width() / (float) mBitmapWidth;
+ dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
+ }
+
+ mShaderMatrix.setScale(scale, scale);
+ mShaderMatrix.postTranslate((int) (dx + 0.5f) + mBorderWidth, (int) (dy + 0.5f) + mBorderWidth);
+
+ mBitmapShader.setLocalMatrix(mShaderMatrix);
+ }
+
+}
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/widget/MQEditToolbar.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/widget/MQEditToolbar.java
new file mode 100644
index 0000000..a1e3b08
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/widget/MQEditToolbar.java
@@ -0,0 +1,324 @@
+package com.meiqia.meiqiasdk.widget;
+
+import android.app.Activity;
+import android.content.Context;
+import android.support.annotation.IdRes;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.EditText;
+import android.widget.GridView;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+
+import com.meiqia.meiqiasdk.R;
+import com.meiqia.meiqiasdk.util.MQEmotionUtil;
+import com.meiqia.meiqiasdk.util.MQUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 作者:王浩 邮件:bingoogolapple@gmail.com
+ * 创建时间:15/12/21 下午2:19
+ * 描述:
+ */
+public class MQEditToolbar extends RelativeLayout implements View.OnClickListener {
+ private static final int EMOTION_COLUMN = 7;
+ private static final int EMOTION_ROW = 4;
+ private static final int EMOTION_PAGE_SIZE = EMOTION_COLUMN * EMOTION_ROW - 1;
+
+ private LinearLayout mEmotionLl;
+ private ViewPager mEmotionVp;
+ private LinearLayout mIndicatorLl;
+ private ArrayList mIndicatorIvList;
+ private ArrayList mGridViewList;
+ private EditText mContentEt;
+ private Activity mActivity;
+
+ public MQEditToolbar(Context context) {
+ this(context, null);
+ }
+
+ public MQEditToolbar(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public MQEditToolbar(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ View.inflate(context, R.layout.mq_edit_toolbar, this);
+ initView();
+ setListener();
+ processLogic();
+ }
+
+ private void initView() {
+ mEmotionLl = getViewById(R.id.ll_edit_toolbar_emotion);
+ mEmotionVp = getViewById(R.id.vp_edit_toolbar_emotion);
+ mIndicatorLl = getViewById(R.id.ll_edit_toolbar_indicator);
+ }
+
+ private void setListener() {
+ mEmotionVp.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
+ @Override
+ public void onPageSelected(int position) {
+ for (int i = 0; i < mIndicatorIvList.size(); i++) {
+ mIndicatorIvList.get(i).setEnabled(false);
+ }
+ mIndicatorIvList.get(position).setEnabled(true);
+ }
+ });
+ }
+
+
+ private void processLogic() {
+ mIndicatorIvList = new ArrayList<>();
+ mGridViewList = new ArrayList<>();
+
+ int emotionPageCount = (MQEmotionUtil.sEmotionKeyArr.length - 1) / EMOTION_PAGE_SIZE + 1;
+
+ ImageView indicatorIv;
+ LinearLayout.LayoutParams indicatorIvLp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ int margin = MQUtils.dip2px(getContext(), 5);
+ indicatorIvLp.setMargins(margin, margin, margin, margin);
+ for (int i = 0; i < emotionPageCount; i++) {
+ indicatorIv = new ImageView(getContext());
+ indicatorIv.setLayoutParams(indicatorIvLp);
+ indicatorIv.setImageResource(R.drawable.mq_selector_emotion_indicator);
+ indicatorIv.setEnabled(false);
+ mIndicatorIvList.add(indicatorIv);
+ mIndicatorLl.addView(indicatorIv);
+
+ mGridViewList.add(getGridView(i));
+ }
+ mIndicatorIvList.get(0).setEnabled(true);
+ mEmotionVp.setAdapter(new EmotionPagerAdapter());
+ }
+
+ private GridView getGridView(int position) {
+ GridView gridView = new GridView(getContext());
+ gridView.setNumColumns(EMOTION_COLUMN);
+ gridView.setVerticalSpacing(MQUtils.dip2px(getContext(), 5));
+ gridView.setHorizontalSpacing(MQUtils.dip2px(getContext(), 5));
+ gridView.setOverScrollMode(OVER_SCROLL_NEVER);
+ gridView.setVerticalScrollBarEnabled(false);
+ gridView.setVerticalFadingEdgeEnabled(false);
+ gridView.setHorizontalScrollBarEnabled(false);
+ gridView.setHorizontalFadingEdgeEnabled(false);
+ gridView.setSelector(android.R.color.transparent);
+ gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ if (mContentEt != null) {
+ EmotionAdapter adapter = (EmotionAdapter) parent.getAdapter();
+ if (position == adapter.getCount() - 1) {
+ // 删除
+ mContentEt.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
+ } else {
+ // 插入表情
+ insertText(adapter.getItem(position));
+ }
+ }
+ }
+ });
+
+ int start = position * EMOTION_PAGE_SIZE;
+ List tempEmotionList = Arrays.asList(Arrays.copyOfRange(MQEmotionUtil.sEmotionKeyArr, start, start + EMOTION_PAGE_SIZE));
+ List emotionList = new ArrayList<>();
+ emotionList.addAll(tempEmotionList);
+ emotionList.add("");
+
+ gridView.setAdapter(new EmotionAdapter(emotionList));
+ return gridView;
+ }
+
+ /**
+ * 切换表情键盘和软键盘
+ */
+ public void toggleKeyboard() {
+ if (mContentEt != null) {
+ if (isEmotionKeyboardVisible()) {
+ changeToOriginalKeyboard();
+ } else {
+ changeToEmotionKeyboard();
+ }
+ }
+ }
+
+ /**
+ * 切换到表情键盘
+ */
+ public void changeToEmotionKeyboard() {
+ if (!mContentEt.isFocused()) {
+ mContentEt.requestFocus();
+ mContentEt.setSelection(mContentEt.getText().toString().length());
+ }
+
+ MQUtils.closeKeyboard(mActivity);
+
+ postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mEmotionLl.setVisibility(VISIBLE);
+ }
+ }, 300);
+ }
+
+ /**
+ * 切换到系统原始键盘
+ */
+ public void changeToOriginalKeyboard() {
+ closeEmotionKeyboard();
+ MQUtils.openKeyboard(getContext(), mContentEt);
+ }
+
+ /**
+ * 关闭表情键盘
+ */
+ public void closeEmotionKeyboard() {
+
+ mEmotionLl.setVisibility(GONE);
+ }
+
+ /**
+ * 关闭所有键盘
+ */
+ public void closeAllKeyboard() {
+ closeEmotionKeyboard();
+ MQUtils.closeKeyboard(mActivity);
+ }
+
+ /**
+ * @return
+ */
+ public boolean isEmotionKeyboardVisible() {
+ return mEmotionLl.getVisibility() == View.VISIBLE;
+ }
+
+ /**
+ * 在当前光标位置插入文本
+ *
+ * @param text
+ */
+ public void insertText(String text) {
+ int cursorPosition = mContentEt.getSelectionStart();
+ StringBuilder sb = new StringBuilder(mContentEt.getText());
+ sb.insert(cursorPosition, text);
+ mContentEt.setText(MQEmotionUtil.getEmotionText(getContext(), sb.toString()));
+ mContentEt.setSelection(cursorPosition + text.length());
+ }
+
+ public void init(Activity activity, EditText contentEt) {
+ mActivity = activity;
+ mContentEt = contentEt;
+ mContentEt.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (isEmotionKeyboardVisible()) {
+ closeEmotionKeyboard();
+ }
+ }
+ });
+ mContentEt.setOnFocusChangeListener(new OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (!hasFocus) {
+ closeAllKeyboard();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onClick(View view) {
+ }
+
+ class EmotionPagerAdapter extends PagerAdapter {
+
+ @Override
+ public int getCount() {
+ return mGridViewList.size();
+ }
+
+ @Override
+ public boolean isViewFromObject(View view, Object object) {
+ return view == object;
+ }
+
+ @Override
+ public void destroyItem(ViewGroup container, int position, Object object) {
+ container.removeView(mGridViewList.get(position));
+ }
+
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ container.addView(mGridViewList.get(position));
+ return mGridViewList.get(position);
+ }
+ }
+
+ class EmotionAdapter extends BaseAdapter {
+ private List mDatas;
+
+ public EmotionAdapter(List datas) {
+ mDatas = datas;
+ }
+
+ @Override
+ public int getCount() {
+ return mDatas == null ? 0 : mDatas.size();
+ }
+
+ @Override
+ public String getItem(int position) {
+ return mDatas.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return 0;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = View.inflate(getContext(), R.layout.mq_item_emotion, null);
+ }
+
+ ImageView iconIv = (ImageView) convertView;
+ if (position == getCount() - 1) {
+ iconIv.setImageResource(R.drawable.mq_emoji_delete);
+ iconIv.setVisibility(VISIBLE);
+ } else {
+ String key = mDatas.get(position);
+ if (TextUtils.isEmpty(key)) {
+ iconIv.setVisibility(INVISIBLE);
+ } else {
+ iconIv.setImageResource(MQEmotionUtil.getImgByName(key));
+ iconIv.setVisibility(VISIBLE);
+ }
+ }
+
+ return convertView;
+ }
+ }
+
+ /**
+ * 查找View
+ *
+ * @param id 控件的id
+ * @param View类型
+ * @return
+ */
+ protected VT getViewById(@IdRes int id) {
+ return (VT) findViewById(id);
+ }
+}
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/widget/RoundProgressBar.java b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/widget/RoundProgressBar.java
new file mode 100644
index 0000000..8cb5f7a
--- /dev/null
+++ b/eclipse/MeiqiaSdk/src/com/meiqia/meiqiasdk/widget/RoundProgressBar.java
@@ -0,0 +1,262 @@
+package com.meiqia.meiqiasdk.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.Typeface;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.meiqia.meiqiasdk.R;
+
+/**
+ * 仿iphone带进度的进度条,线程安全的View,可直接在线程中更新进度
+ *
+ * @author xiaanming
+ */
+public class RoundProgressBar extends View {
+ /**
+ * 画笔对象的引用
+ */
+ private Paint paint;
+
+ /**
+ * 圆环的颜色
+ */
+ private int roundColor;
+
+ /**
+ * 圆环进度的颜色
+ */
+ private int roundProgressColor;
+
+ /**
+ * 中间进度百分比的字符串的颜色
+ */
+ private int textColor;
+
+ /**
+ * 中间进度百分比的字符串的字体
+ */
+ private float textSize;
+
+ /**
+ * 圆环的宽度
+ */
+ private float roundWidth;
+
+ /**
+ * 最大进度
+ */
+ private int max;
+
+ /**
+ * 当前进度
+ */
+ private int progress;
+ /**
+ * 是否显示中间的进度
+ */
+ private boolean textIsDisplayable;
+
+ /**
+ * 进度的风格,实心或者空心
+ */
+ private int style;
+
+ private boolean isPlay;
+
+ public static final int STROKE = 0;
+ public static final int FILL = 1;
+
+ public RoundProgressBar(Context context) {
+ this(context, null);
+ }
+
+ public RoundProgressBar(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public RoundProgressBar(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ paint = new Paint();
+
+ roundColor = getResources().getColor(R.color.mq_play_circle_round);
+ roundProgressColor = getResources().getColor(R.color.mq_play_circle_progress);
+ textColor = Color.GREEN;
+ textSize = 15;
+ roundWidth = 0;
+ max = 100;
+ textIsDisplayable = false;
+ style = FILL;
+
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ /**
+ * 画最外层的大圆环
+ */
+ int centre = getWidth() / 2; // 获取圆心的x坐标
+ int radius = (int) (centre - roundWidth / 2); // 圆环的半径
+ paint.setColor(roundColor); // 设置圆环的颜色
+ paint.setStyle(Paint.Style.STROKE); // 设置空心
+ paint.setStrokeWidth(roundWidth); // 设置圆环的宽度
+ paint.setAntiAlias(true); // 消除锯齿
+ canvas.drawCircle(centre, centre, radius, paint); // 画出圆环
+
+ /**
+ * 画进度百分比
+ */
+ paint.setStrokeWidth(0);
+ paint.setColor(textColor);
+ paint.setTextSize(textSize);
+ paint.setTypeface(Typeface.DEFAULT_BOLD); // 设置字体
+ int percent = (int) (((float) progress / (float) max) * 100); // 中间的进度百分比,先转换成float在进行除法运算,不然都为0
+ float textWidth = paint.measureText(percent + "%"); // 测量字体宽度,我们需要根据字体的宽度设置在圆环中间
+
+ if (textIsDisplayable && percent != 0 && style == STROKE) {
+ canvas.drawText(percent + "%", centre - textWidth / 2, centre + textSize / 2, paint); // 画出进度百分比
+ }
+
+ /**
+ * 画圆弧 ,画圆环的进度
+ */
+
+ // 设置进度是实心还是空心
+ paint.setStrokeWidth(roundWidth); // 设置圆环的宽度
+ paint.setColor(roundProgressColor); // 设置进度的颜色
+ RectF oval = new RectF(centre - radius, centre - radius, centre + radius, centre + radius); // 用于定义的圆弧的形状和大小的界限
+
+ switch (style) {
+ case STROKE: {
+ paint.setStyle(Paint.Style.STROKE);
+ canvas.drawArc(oval, 0, 360 * progress / max, false, paint); // 根据进度画圆弧
+ break;
+ }
+ case FILL: {
+ paint.setStyle(Paint.Style.FILL_AND_STROKE);
+ if (progress != 0)
+ canvas.drawArc(oval, 0, 360 * progress / max, true, paint); // 根据进度画圆弧
+ break;
+ }
+ }
+
+ }
+
+ public synchronized int getMax() {
+ return max;
+ }
+
+ /**
+ * 设置进度的最大值
+ *
+ * @param max
+ */
+ public synchronized void setMax(int max) {
+ if (max < 0) {
+ throw new IllegalArgumentException("max not less than 0");
+ }
+ this.max = max;
+ }
+
+ /**
+ * 获取进度.需要同步
+ *
+ * @return
+ */
+ public synchronized int getProgress() {
+ return progress;
+ }
+
+ /**
+ * 设置进度,此为线程安全控件,由于考虑多线的问题,需要同步 刷新界面调用postInvalidate()能在非UI线程刷新
+ *
+ * @param progress
+ */
+ public synchronized void setProgress(int progress) {
+ if (progress < 0) {
+ throw new IllegalArgumentException("progress not less than 0");
+ }
+ if (progress > max) {
+ progress = max;
+ }
+ if (progress <= max) {
+ this.progress = progress;
+ postInvalidate();
+ }
+
+ }
+
+ public void start() {
+ if (!isPlay) {
+ isPlay = true;
+ new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+ while (isPlay && progress <= max) {
+ progress += 1;
+ setProgress(progress);
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }).start();
+ }
+ }
+
+ public void stop() {
+ setProgress(0);
+ isPlay = false;
+ }
+
+ public int getCricleColor() {
+ return roundColor;
+ }
+
+ public void setCricleColor(int cricleColor) {
+ this.roundColor = cricleColor;
+ }
+
+ public int getCricleProgressColor() {
+ return roundProgressColor;
+ }
+
+ public void setCricleProgressColor(int cricleProgressColor) {
+ this.roundProgressColor = cricleProgressColor;
+ }
+
+ public int getTextColor() {
+ return textColor;
+ }
+
+ public void setTextColor(int textColor) {
+ this.textColor = textColor;
+ }
+
+ public float getTextSize() {
+ return textSize;
+ }
+
+ public void setTextSize(float textSize) {
+ this.textSize = textSize;
+ }
+
+ public float getRoundWidth() {
+ return roundWidth;
+ }
+
+ public void setRoundWidth(float roundWidth) {
+ this.roundWidth = roundWidth;
+ }
+
+}
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdkDemo/AndroidManifest.xml b/eclipse/MeiqiaSdkDemo/AndroidManifest.xml
new file mode 100644
index 0000000..67ea5f9
--- /dev/null
+++ b/eclipse/MeiqiaSdkDemo/AndroidManifest.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdkDemo/ic_launcher-web.png b/eclipse/MeiqiaSdkDemo/ic_launcher-web.png
new file mode 100644
index 0000000..a18cbb4
Binary files /dev/null and b/eclipse/MeiqiaSdkDemo/ic_launcher-web.png differ
diff --git a/eclipse/MeiqiaSdkDemo/proguard-project.txt b/eclipse/MeiqiaSdkDemo/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/eclipse/MeiqiaSdkDemo/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/eclipse/MeiqiaSdkDemo/project.properties b/eclipse/MeiqiaSdkDemo/project.properties
new file mode 100644
index 0000000..5029e28
--- /dev/null
+++ b/eclipse/MeiqiaSdkDemo/project.properties
@@ -0,0 +1,15 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-23
+android.library.reference.1=../MeiqiaSdk
diff --git a/eclipse/MeiqiaSdkDemo/res/drawable-xxhdpi/ic_launcher.png b/eclipse/MeiqiaSdkDemo/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..67eec19
Binary files /dev/null and b/eclipse/MeiqiaSdkDemo/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/eclipse/MeiqiaSdkDemo/res/drawable-xxhdpi/ic_logo.png b/eclipse/MeiqiaSdkDemo/res/drawable-xxhdpi/ic_logo.png
new file mode 100644
index 0000000..c69a82b
Binary files /dev/null and b/eclipse/MeiqiaSdkDemo/res/drawable-xxhdpi/ic_logo.png differ
diff --git a/eclipse/MeiqiaSdkDemo/res/layout/activity_developer.xml b/eclipse/MeiqiaSdkDemo/res/layout/activity_developer.xml
new file mode 100644
index 0000000..7c5e22a
--- /dev/null
+++ b/eclipse/MeiqiaSdkDemo/res/layout/activity_developer.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdkDemo/res/layout/activity_main.xml b/eclipse/MeiqiaSdkDemo/res/layout/activity_main.xml
new file mode 100644
index 0000000..daed891
--- /dev/null
+++ b/eclipse/MeiqiaSdkDemo/res/layout/activity_main.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdkDemo/res/layout/dialog_input.xml b/eclipse/MeiqiaSdkDemo/res/layout/dialog_input.xml
new file mode 100644
index 0000000..ff94238
--- /dev/null
+++ b/eclipse/MeiqiaSdkDemo/res/layout/dialog_input.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdkDemo/res/values/colors.xml b/eclipse/MeiqiaSdkDemo/res/values/colors.xml
new file mode 100644
index 0000000..045e125
--- /dev/null
+++ b/eclipse/MeiqiaSdkDemo/res/values/colors.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/eclipse/MeiqiaSdkDemo/res/values/strings.xml b/eclipse/MeiqiaSdkDemo/res/values/strings.xml
new file mode 100644
index 0000000..795d9c7
--- /dev/null
+++ b/eclipse/MeiqiaSdkDemo/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ 美洽 SDK
+
diff --git a/eclipse/MeiqiaSdkDemo/res/values/styles.xml b/eclipse/MeiqiaSdkDemo/res/values/styles.xml
new file mode 100644
index 0000000..ff69ddc
--- /dev/null
+++ b/eclipse/MeiqiaSdkDemo/res/values/styles.xml
@@ -0,0 +1,12 @@
+
+
+
\ No newline at end of file
diff --git a/eclipse/MeiqiaSdkDemo/src/com/meiqia/meiqiasdk/demo/ApiSampleActivity.java b/eclipse/MeiqiaSdkDemo/src/com/meiqia/meiqiasdk/demo/ApiSampleActivity.java
new file mode 100644
index 0000000..18da31f
--- /dev/null
+++ b/eclipse/MeiqiaSdkDemo/src/com/meiqia/meiqiasdk/demo/ApiSampleActivity.java
@@ -0,0 +1,260 @@
+package com.meiqia.meiqiasdk.demo;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ClipData;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.text.ClipboardManager;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.meiqia.core.MQManager;
+import com.meiqia.core.callback.OnClientInfoCallback;
+import com.meiqia.core.callback.OnEndConversationCallback;
+import com.meiqia.core.callback.OnGetMQClientIdCallBackOn;
+import com.meiqia.meiqiasdk.activity.MQConversationActivity;
+import com.meiqia.meiqiasdk.controller.ControllerImpl;
+import com.meiqia.meiqiasdk.util.MQUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ApiSampleActivity extends Activity implements View.OnClickListener {
+
+ private TextView currentIdTv;
+ private View setCurrentIdOnlineBtn;
+ private View setInputIdOnlineBtn;
+ private View setCustomizedIdOnlineBtn;
+ private View getNewIdBtn;
+ private View setAgentTokenOnlineBtn;
+ private View setGroupTokenOnlineBtn;
+ private View setClientInfoBtn;
+ private View offlineClientBtn;
+ private View endConversationBtn;
+
+ private MQManager mqManager;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_developer);
+
+ mqManager = MQManager.getInstance(this);
+
+ findViews();
+ setListeners();
+ updateId();
+ }
+
+ private void findViews() {
+ currentIdTv = (TextView) findViewById(R.id.current_id_tv);
+ setCurrentIdOnlineBtn = findViewById(R.id.set_current_client_id_online_btn);
+ setInputIdOnlineBtn = findViewById(R.id.set_meiqia_client_id_online_btn);
+ setCustomizedIdOnlineBtn = findViewById(R.id.set_customised_id_online_btn);
+ getNewIdBtn = findViewById(R.id.get_new_meiqia_id_btn);
+ setAgentTokenOnlineBtn = findViewById(R.id.set_specified_agent_token_btn);
+ setGroupTokenOnlineBtn = findViewById(R.id.set_specified_agent_group_token_btn);
+ setClientInfoBtn = findViewById(R.id.set_client_info);
+ offlineClientBtn = findViewById(R.id.set_client_offline_btn);
+ endConversationBtn = findViewById(R.id.end_conversation_btn);
+ }
+
+ private void setListeners() {
+ setCurrentIdOnlineBtn.setOnClickListener(this);
+ setInputIdOnlineBtn.setOnClickListener(this);
+ setCustomizedIdOnlineBtn.setOnClickListener(this);
+ getNewIdBtn.setOnClickListener(this);
+ setAgentTokenOnlineBtn.setOnClickListener(this);
+ setGroupTokenOnlineBtn.setOnClickListener(this);
+ setClientInfoBtn.setOnClickListener(this);
+ offlineClientBtn.setOnClickListener(this);
+ endConversationBtn.setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View v) {
+ int id = v.getId();
+ switch (id) {
+ // 使用当前顾客上线
+ case R.id.set_current_client_id_online_btn:
+ MQConversationActivity.registerController(new ControllerImpl(this));
+ Intent intent = new Intent(ApiSampleActivity.this, MQConversationActivity.class);
+ startActivity(intent);
+ break;
+ // 使用指定 美洽顾客id 上线
+ case R.id.set_meiqia_client_id_online_btn:
+ showDialog("输入美洽 ID", new EditDialogOnClickListener() {
+ @Override
+ public void onInput(String clientId) {
+ if (!TextUtils.isEmpty(clientId)) {
+ Intent intent = new Intent(ApiSampleActivity.this, MQConversationActivity.class);
+ intent.putExtra(MQConversationActivity.CLIENT_ID, clientId);
+ startActivity(intent);
+ updateId();
+ }
+ }
+ });
+ break;
+ // 使用 开发者用户id 上线
+ case R.id.set_customised_id_online_btn:
+ showDialog("输入开发者用户 ID", new EditDialogOnClickListener() {
+ @Override
+ public void onInput(String customizedId) {
+ if (!TextUtils.isEmpty(customizedId)) {
+ Intent intent = new Intent(ApiSampleActivity.this, MQConversationActivity.class);
+ intent.putExtra(MQConversationActivity.CUSTOMIZED_ID, customizedId);
+ startActivity(intent);
+ updateId();
+ }
+ }
+ });
+ break;
+ // 获取一个新的美洽 ID
+ case R.id.get_new_meiqia_id_btn:
+ MQManager.getInstance(this).createMQClient(new OnGetMQClientIdCallBackOn() {
+ @Override
+ public void onSuccess(String mqClientId) {
+ toast("成功复制到剪贴板 :\n" + mqClientId);
+ if (!TextUtils.isEmpty(mqClientId)) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ android.content.ClipboardManager mClipboard = (android.content.ClipboardManager) ApiSampleActivity.this.getSystemService(Context.CLIPBOARD_SERVICE);
+ mClipboard.setPrimaryClip(ClipData.newPlainText("mq_content", mqClientId));
+ } else {
+ ClipboardManager mClipboard = (ClipboardManager) ApiSampleActivity.this.getSystemService(Context.CLIPBOARD_SERVICE);
+ mClipboard.setText(mqClientId);
+ }
+ }
+ }
+
+ @Override
+ public void onFailure(int code, String message) {
+ toast(message);
+ }
+ });
+ break;
+ // 指定客服分配上线
+ case R.id.set_specified_agent_token_btn:
+ showDialog("输入指定客服 ID", new EditDialogOnClickListener() {
+ @Override
+ public void onInput(String agentId) {
+ if (!TextUtils.isEmpty(agentId)) {
+ MQManager.getInstance(ApiSampleActivity.this).setScheduledAgentOrGroupWithId(agentId, "");
+ Intent intent = new Intent(ApiSampleActivity.this, MQConversationActivity.class);
+ startActivity(intent);
+ updateId();
+ }
+ }
+ });
+ break;
+ // 指定客服分组分配上线
+ case R.id.set_specified_agent_group_token_btn:
+ showDialog("输入指定分组 ID", new EditDialogOnClickListener() {
+ @Override
+ public void onInput(String groupId) {
+ if (!TextUtils.isEmpty(groupId)) {
+ MQManager.getInstance(ApiSampleActivity.this).setScheduledAgentOrGroupWithId("", groupId);
+ Intent intent = new Intent(ApiSampleActivity.this, MQConversationActivity.class);
+ startActivity(intent);
+ }
+ }
+ });
+ break;
+ // 上传自定义信息
+ case R.id.set_client_info:
+ final Map info = new HashMap<>();
+ info.put("name", "富坚义博");
+ info.put("avatar", "https://s3.cn-north-1.amazonaws.com.cn/pics.meiqia.bucket/1dee88eabfbd7bd4");
+ info.put("sex", "男");
+ info.put("tel", "111111");
+ info.put("技能1", "休刊");
+ info.put("技能2", "外出取材");
+ info.put("技能3", "打麻将");
+ AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this);
+ alertBuilder.setTitle("上传自定义信息");
+ alertBuilder.setMessage("avatar -> https://s3.cn-north-1.amazonaws.com.cn/pics.meiqia.bucket/1dee88eabfbd7bd4\n" +
+ "name -> 富坚义博\n" +
+ "技能1 -> 休刊\n" +
+ "sex -> 男\n" +
+ "tel -> 111111\n" +
+ "技能2 -> 外出取材\n" +
+ "技能3 -> 打麻将");
+ AlertDialog dialog = alertBuilder.create();
+ dialog.setButton(DialogInterface.BUTTON_POSITIVE, "确定", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ MQManager.getInstance(ApiSampleActivity.this).setClientInfo(info, new OnClientInfoCallback() {
+ @Override
+ public void onSuccess() {
+ toast("set client info success");
+ }
+
+ @Override
+ public void onFailure(int code, String message) {
+ toast("set client info failed");
+ }
+ });
+ }
+ });
+ dialog.show();
+ break;
+ // 设置顾客离线
+ case R.id.set_client_offline_btn:
+ MQManager.getInstance(this).setClientOffline();
+ break;
+ // 结束当前对话
+ case R.id.end_conversation_btn:
+ MQManager.getInstance(this).endCurrentConversation(new OnEndConversationCallback() {
+ @Override
+ public void onSuccess() {
+ toast("endCurrentConversation success");
+ }
+
+ @Override
+ public void onFailure(int code, String message) {
+ toast("endCurrentConversation failed:\n" + message);
+ }
+ });
+ break;
+ }
+ }
+
+ private void updateId() {
+ currentIdTv.setText(mqManager.getCurrentClientId());
+ }
+
+ private void toast(String content) {
+ Toast.makeText(ApiSampleActivity.this, content, Toast.LENGTH_LONG).show();
+ }
+
+ private void showDialog(String title, final EditDialogOnClickListener editDialogOnClickListener) {
+ final Dialog inputDialog = new Dialog(this, R.style.MQDialog);
+ inputDialog.setCancelable(true);
+ inputDialog.setContentView(R.layout.dialog_input);
+ TextView titleTv = (TextView) inputDialog.findViewById(R.id.tv_input_title);
+ titleTv.setText(title);
+ final EditText valueEt = (EditText) inputDialog.findViewById(R.id.et_input_value);
+ inputDialog.findViewById(R.id.tv_input_confirm).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ MQUtils.closeKeyboard(inputDialog);
+ inputDialog.dismiss();
+ editDialogOnClickListener.onInput(valueEt.getText().toString());
+ }
+ });
+ inputDialog.show();
+ MQUtils.openKeyboard(this, valueEt);
+ }
+
+ public interface EditDialogOnClickListener {
+ void onInput(String input);
+ }
+
+}
diff --git a/eclipse/MeiqiaSdkDemo/src/com/meiqia/meiqiasdk/demo/MainActivity.java b/eclipse/MeiqiaSdkDemo/src/com/meiqia/meiqiasdk/demo/MainActivity.java
new file mode 100644
index 0000000..3114b7a
--- /dev/null
+++ b/eclipse/MeiqiaSdkDemo/src/com/meiqia/meiqiasdk/demo/MainActivity.java
@@ -0,0 +1,54 @@
+package com.meiqia.meiqiasdk.demo;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Toast;
+
+import com.meiqia.core.MQManager;
+import com.meiqia.core.callback.OnInitCallBackOn;
+import com.meiqia.meiqiasdk.activity.MQConversationActivity;
+
+public class MainActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ // 替换成自己的key
+ String meiqiaKey = "a71c257c80dfe883d92a64dca323ec20";
+
+ MQManager.init(this, meiqiaKey, new OnInitCallBackOn() {
+ @Override
+ public void onSuccess() {
+ Toast.makeText(MainActivity.this, "init success", Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onFailure(int code, String message) {
+ Toast.makeText(MainActivity.this, "int failure", Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ /**
+ * 咨询客服
+ *
+ * @param v
+ */
+ public void conversation(View v) {
+ startActivity(new Intent(MainActivity.this, MQConversationActivity.class));
+ }
+
+ /**
+ * 开发者功能
+ *
+ * @param v
+ */
+ public void developer(View v) {
+ startActivity(new Intent(MainActivity.this, ApiSampleActivity.class));
+ }
+
+}
\ No newline at end of file