userList, Exception exception);
11 | }
12 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/LeancloudArgsConverter.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin;
2 |
3 | /**
4 | * Created by ruirui on 2019/1/28.
5 | */
6 |
7 | import com.alibaba.fastjson.JSON;
8 | import com.alibaba.fastjson.JSONObject;
9 |
10 | import io.flutter.plugin.common.MethodCall;
11 | import io.flutter.plugin.common.MethodChannel;
12 |
13 | class LeancloudArgsConverter {
14 | static JSONObject getAVQueryJsonObject(MethodCall call, MethodChannel.Result result) {
15 | String key = "avQuery";
16 | Object arg = call.argument("avQuery");
17 | if (arg == null) {
18 | result.error("missing-arg", "Arg '" + key + "' can't be null, set empty value. PLEASE FIX IT!", null);
19 | return null;
20 | } else {
21 | return JSON.parseObject(arg.toString());
22 | }
23 | }
24 |
25 | static String getStringValue(MethodCall call, MethodChannel.Result result, String key) {
26 | Object arg = call.argument(key);
27 | if (arg == null) {
28 | result.error("missing-arg", "Arg '" + key + "' can't be null, set empty value. PLEASE FIX IT!", null);
29 | return "";
30 | } else {
31 | return arg.toString();
32 | }
33 | }
34 |
35 | static int getIntValue(MethodCall call, MethodChannel.Result result, String key) {
36 | Object arg = call.argument(key);
37 | if (arg == null) {
38 | result.error("missing-arg", "Arg '" + key + "' can't be null, set 0 value. PLEASE FIX IT!", null);
39 | return 0;
40 | } else {
41 | return (int) arg;
42 | }
43 | }
44 |
45 | }
46 |
47 |
48 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/LeancloudFunction.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin;
2 |
3 | /**
4 | * Created by ruirui on 2019/1/28.
5 | */
6 |
7 | import android.content.Context;
8 | import android.graphics.Bitmap;
9 | import android.graphics.BitmapFactory;
10 |
11 | import com.avos.avoscloud.AVException;
12 | import com.avos.avoscloud.AVFile;
13 | import com.avos.avoscloud.AVInstallation;
14 | import com.avos.avoscloud.AVLogger;
15 | import com.avos.avoscloud.AVOSCloud;
16 | import com.avos.avoscloud.SaveCallback;
17 | import com.avos.avoscloud.im.v2.AVIMClient;
18 | import com.avos.avoscloud.im.v2.AVIMException;
19 | import com.avos.avoscloud.im.v2.callback.AVIMClientCallback;
20 |
21 | import io.flutter.plugin.common.MethodCall;
22 | import io.flutter.plugin.common.MethodChannel;
23 |
24 |
25 | class LeancloudFunction {
26 |
27 |
28 | static void initialize(MethodCall call, MethodChannel.Result result, Context context) {
29 | LCChatKit.getInstance().setProfileProvider(CustomUserProvider.getInstance());
30 | AVOSCloud.setDebugLogEnabled(true);
31 | String appId = LeancloudArgsConverter.getStringValue(call, result, "appId");
32 | String appKey = LeancloudArgsConverter.getStringValue(call, result, "appKey");
33 | LCChatKit.getInstance().init(context, appId, appKey);
34 | AVIMClient.setAutoOpen(true);
35 | AVInstallation.getCurrentInstallation().saveInBackground(new SaveCallback() {
36 | public void done(AVException e) {
37 | if (e == null) {
38 | // 保存成功
39 | String installationId = AVInstallation.getCurrentInstallation().getInstallationId();
40 | System.out.println("--- " + installationId);
41 | } else {
42 | // 保存失败,输出错误信息
43 | System.out.println("failed to save installation.");
44 | }
45 | }
46 | });
47 |
48 | }
49 |
50 | /**
51 | * Setup log level must be before call initialize function
52 | *
53 | * The call must be include args:
54 | * level --> OFF(0), ERROR(1), WARNING(2), INFO(3), DEBUG(4), VERBOSE(5), ALL(6);
55 | *
56 | * @param call MethodCall from LeancloudFlutterPlugin.onMethodCall function
57 | * @param result MethodChannel.Result from LeancloudFlutterPlugin.onMethodCall function
58 | */
59 | static void setLogLevel(MethodCall call, MethodChannel.Result result) {
60 | int level_int = LeancloudArgsConverter.getIntValue(call, result, "level");
61 | // AVLogger.Level level = AVLogger.Level.OFF;
62 | int level = AVLogger.LOG_LEVEL_NONE;
63 | switch (level_int) {
64 | case 0:
65 | // already assigned to this value
66 | break;
67 | case 1:
68 | level = AVLogger.LOG_LEVEL_ERROR;
69 | break;
70 | case 2:
71 | level = AVLogger.LOG_LEVEL_WARNING;
72 | break;
73 | case 3:
74 | level = AVLogger.LOG_LEVEL_INFO;
75 | break;
76 | case 4:
77 | level = AVLogger.LOG_LEVEL_DEBUG;
78 | break;
79 | case 5:
80 | level = AVLogger.LOG_LEVEL_VERBOSE;
81 | break;
82 | default:
83 | break;
84 | }
85 | AVOSCloud.setLogLevel(level);
86 | }
87 |
88 | static void onLoginClick(MethodCall call, final MethodChannel.Result result) {
89 | String clientId = (String) call.arguments;
90 | LCChatKit.getInstance().open(clientId, new AVIMClientCallback() {
91 | @Override
92 | public void done(AVIMClient avimClient, AVIMException e) {
93 | if (null == e) {
94 | System.out.println("帐号登陆即时通讯成功");
95 | result.success(true);
96 |
97 |
98 | } else {
99 | System.out.println("帐号登陆即时通讯失败");
100 | result.success(false);
101 | }
102 | }
103 | });
104 | }
105 |
106 | static void signoutClick() {
107 | LCChatKit.getInstance().close(new AVIMClientCallback() {
108 | @Override
109 | public void done(AVIMClient client, AVIMException e) {
110 | if (e == null) {
111 | System.out.println("即时通讯账号退出成功");
112 | //登出成功
113 | }
114 | }
115 | });
116 | }
117 |
118 | static void uploadFile(MethodCall call, final MethodChannel.Result result) {
119 | final String path = LeancloudArgsConverter.getStringValue(call, result, "filePath");
120 | String fileName = LeancloudArgsConverter.getStringValue(call, result, "fileName");
121 | try {
122 | final AVFile LcFile = AVFile.withAbsoluteLocalPath(fileName, path);//fileName文件名要有后缀名
123 | System.out.println(LcFile.getSize() + "");
124 | LcFile.saveInBackground(new SaveCallback() {
125 | @Override
126 | public void done(AVException e) {
127 | if (e == null) {
128 | System.out.println("保存成功");
129 | result.success(LcFile.getObjectId());
130 | } else {
131 | System.out.println("保存失败: " + e.getMessage());
132 | result.notImplemented();
133 | }
134 | }
135 | });
136 |
137 | } catch (Exception e) {
138 | System.out.println("保存失败:" + e.getMessage());
139 | result.notImplemented();
140 | }
141 | }
142 |
143 |
144 | }
145 |
146 |
147 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/cache/LCIMConversationItem.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.cache;
2 |
3 | import com.alibaba.fastjson.JSONObject;
4 |
5 | import com.sisi.imleancloudplugin.utils.LCIMLogUtils;
6 |
7 | /**
8 | * Created by wli on 16/3/8.
9 | * 会话 item,包含三个属性,ConversatoinId,unreadCount,updateTime
10 | */
11 | class LCIMConversationItem implements Comparable {
12 | private static final String ITEM_KEY_CONVCERSATION_ID = "conversation_id";
13 | private static final String ITEM_KEY_UNDATE_TIME = "upadte_time";
14 | public String conversationId = "";
15 | public long updateTime = 0;
16 |
17 | public LCIMConversationItem() {
18 | }
19 |
20 | public LCIMConversationItem(String conversationId) {
21 | this.conversationId = conversationId;
22 | }
23 |
24 | public String toJsonString() {
25 | JSONObject jsonObject = new JSONObject();
26 | jsonObject.put(ITEM_KEY_CONVCERSATION_ID, conversationId);
27 | jsonObject.put(ITEM_KEY_UNDATE_TIME, updateTime);
28 | return jsonObject.toJSONString();
29 | }
30 |
31 | public static LCIMConversationItem fromJsonString(String json) {
32 | LCIMConversationItem item = new LCIMConversationItem();
33 | JSONObject jsonObject = null;
34 | try {
35 | jsonObject = JSONObject.parseObject(json);
36 | item.conversationId = jsonObject.getString(ITEM_KEY_CONVCERSATION_ID);
37 | item.updateTime = jsonObject.getLong(ITEM_KEY_UNDATE_TIME);
38 | } catch (Exception e) {
39 | LCIMLogUtils.logException(e);
40 | }
41 | return item;
42 | }
43 |
44 | @Override
45 | public int compareTo(Object another) {
46 | return (int) (((LCIMConversationItem) another).updateTime - updateTime);
47 | }
48 | }
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/cache/LCIMConversationItemCache.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.cache;
2 |
3 | import android.content.Context;
4 | import android.text.TextUtils;
5 |
6 | import com.avos.avoscloud.AVCallback;
7 | import com.avos.avoscloud.AVException;
8 |
9 | import java.util.ArrayList;
10 | import java.util.Arrays;
11 | import java.util.HashMap;
12 | import java.util.List;
13 | import java.util.Map;
14 | import java.util.SortedSet;
15 | import java.util.TreeSet;
16 |
17 | /**
18 | * Created by wli on 16/2/26.
19 | * 缓存未读消息数量
20 | *
21 | * 流程
22 | * 1、初始化时从 db 里同步数据到缓存
23 | * 2、插入数据时先更新缓存,在更新 db
24 | * 3、获取的话只从缓存里读取数据
25 | */
26 | public class LCIMConversationItemCache {
27 |
28 | private final String CONVERSATION_ITEM_TABLE_NAME = "ConversationItem";
29 |
30 | private Map conversationItemMap;
31 | private LCIMLocalStorage conversationItemDBHelper;
32 |
33 | private LCIMConversationItemCache() {
34 | conversationItemMap = new HashMap();
35 | }
36 |
37 | private static LCIMConversationItemCache conversationItemCache;
38 |
39 | public static synchronized LCIMConversationItemCache getInstance() {
40 | if (null == conversationItemCache) {
41 | conversationItemCache = new LCIMConversationItemCache();
42 | }
43 | return conversationItemCache;
44 | }
45 |
46 | /**
47 | * 因为只有在第一次的时候需要设置 Context 以及 clientId,所以单独拎出一个函数主动调用初始化
48 | * 避免 getInstance 传入过多参数
49 | * 因为需要同步数据,所以此处需要有回调
50 | */
51 | public synchronized void initDB(Context context, String clientId, AVCallback callback) {
52 | conversationItemDBHelper = new LCIMLocalStorage(context, clientId, CONVERSATION_ITEM_TABLE_NAME);
53 | conversationItemMap.clear();
54 | syncData(callback);
55 | }
56 |
57 | /**
58 | * 删除该 Conversation 未读数量的缓存
59 | *
60 | * @param convid 不能为空
61 | */
62 | public synchronized void deleteConversation(String convid) {
63 | if (!TextUtils.isEmpty(convid)) {
64 | conversationItemMap.remove(convid);
65 | conversationItemDBHelper.deleteData(Arrays.asList(convid));
66 | }
67 | }
68 |
69 | /**
70 | * 缓存该 Conversastoin,默认未读数量为 0
71 | *
72 | * @param convId 不能为空
73 | */
74 | public synchronized void insertConversation(String convId) {
75 | if (!TextUtils.isEmpty(convId)) {
76 | LCIMConversationItem item = getConversationItemFromMap(convId);
77 | item.updateTime = System.currentTimeMillis();
78 | syncToCache(item);
79 | }
80 | }
81 |
82 | /**
83 | * 缓存该 conversation
84 | * @param convId conversationId
85 | * @param milliSeconds 指定该 conversation 更新的时间,用于排序
86 | */
87 | public synchronized void insertConversation(String convId, long milliSeconds) {
88 | if (!TextUtils.isEmpty(convId) && milliSeconds >= 0) {
89 | LCIMConversationItem item = getConversationItemFromMap(convId);
90 | item.updateTime = milliSeconds;
91 | syncToCache(item);
92 | }
93 | }
94 |
95 | /**
96 | * 获得排序后的 Conversation Id list,根据本地更新时间降序排列
97 | *
98 | * @return
99 | */
100 | public synchronized List getSortedConversationList() {
101 | List idList = new ArrayList<>();
102 | SortedSet sortedSet = new TreeSet<>();
103 | sortedSet.addAll(conversationItemMap.values());
104 | for (LCIMConversationItem item : sortedSet) {
105 | idList.add(item.conversationId);
106 | }
107 | return idList;
108 | }
109 |
110 | public synchronized void cleanup() {
111 | conversationItemDBHelper.deleteAllData();
112 | }
113 |
114 | /**
115 | * 同步 db 数据到内存中
116 | */
117 | private void syncData(final AVCallback callback) {
118 | conversationItemDBHelper.getIds(new AVCallback>() {
119 | @Override
120 | protected void internalDone0(final List idList, AVException e) {
121 | conversationItemDBHelper.getData(idList, new AVCallback>() {
122 | @Override
123 | protected void internalDone0(final List dataList, AVException e) {
124 | if (null != dataList) {
125 | for (int i = 0; i < dataList.size(); i++) {
126 | LCIMConversationItem conversationItem = LCIMConversationItem.fromJsonString(dataList.get(i));
127 | conversationItemMap.put(conversationItem.conversationId, conversationItem);
128 | }
129 | }
130 | callback.internalDone(e);
131 | }
132 | });
133 | }
134 | });
135 | }
136 |
137 | /**
138 | * 从 map 中获取 ConversationItem,如缓存中没有,则 new 一个新实例返回
139 | *
140 | * @param convId
141 | * @return
142 | */
143 | private LCIMConversationItem getConversationItemFromMap(String convId) {
144 | if (conversationItemMap.containsKey(convId)) {
145 | return conversationItemMap.get(convId);
146 | }
147 | return new LCIMConversationItem(convId);
148 | }
149 |
150 | /**
151 | * 存储未读消息数量到内存
152 | */
153 | private void syncToCache(LCIMConversationItem item) {
154 | if (null != item) {
155 | conversationItemMap.put(item.conversationId, item);
156 | conversationItemDBHelper.insertData(item.conversationId, item.toJsonString());
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/cache/LCIMLocalCacheUtils.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.cache;
2 |
3 |
4 | import android.os.AsyncTask;
5 | import android.text.TextUtils;
6 |
7 | import okhttp3.Call;
8 | import okhttp3.OkHttpClient;
9 | import okhttp3.Request;
10 | import okhttp3.Response;
11 |
12 | import java.io.Closeable;
13 | import java.io.File;
14 | import java.io.FileOutputStream;
15 | import java.io.IOException;
16 | import java.io.InputStream;
17 | import java.util.ArrayList;
18 | import java.util.Arrays;
19 | import java.util.HashMap;
20 | import java.util.HashSet;
21 | import java.util.Map;
22 | import java.util.Set;
23 |
24 | /**
25 | * Created by wli on 15/9/29.
26 | * 用于下载文件,会主动合并重复的下载
27 | */
28 | public class LCIMLocalCacheUtils {
29 |
30 | /**
31 | * 用于记录 DownLoadCallback,如果对于同一个 url 有多个请求,则下载完后应该执行所有回调
32 | * 此变量就是用于记录这些请求
33 | */
34 | private static Map> downloadCallBackMap;
35 |
36 | /**
37 | * 判断当前 url 是否正在下载,如果已经在下载,则没有必要再去做请求
38 | */
39 | private static Set isDownloadingFile;
40 |
41 | /**
42 | * OkHttpClient 的 实例,官方不建议创建多个,所以这里搞了一个 static 实例
43 | */
44 | private static OkHttpClient okHttpClient;
45 |
46 | static {
47 | downloadCallBackMap = new HashMap>();
48 | isDownloadingFile = new HashSet();
49 | okHttpClient = new OkHttpClient();
50 | }
51 |
52 | private static synchronized void addDownloadCallback(String path, DownLoadCallback callback) {
53 | if (null != callback) {
54 | if (downloadCallBackMap.containsKey(path)) {
55 | downloadCallBackMap.get(path).add(callback);
56 | } else {
57 | downloadCallBackMap.put(path, new ArrayList(Arrays.asList(callback)));
58 | }
59 | }
60 | }
61 |
62 | private static synchronized void executeDownloadCallBack(String path, Exception e) {
63 | if (downloadCallBackMap.containsKey(path)) {
64 | ArrayList callbacks = downloadCallBackMap.get(path);
65 | downloadCallBackMap.remove(path);
66 | for (DownLoadCallback callback : callbacks) {
67 | callback.done(e);
68 | }
69 | }
70 | }
71 |
72 | /**
73 | * 异步下载文件到指定位置
74 | *
75 | * @param url 需要下载远程地址
76 | * @param localPath 下载到本地的文件存放的位置
77 | */
78 | public static void downloadFileAsync(final String url, final String localPath) {
79 | downloadFileAsync(url, localPath, false);
80 | }
81 |
82 | /**
83 | * 异步下载文件到指定位置
84 | *
85 | * @param url 需要下载远程地址
86 | * @param localPath 下载到本地的文件存放的位置
87 | * @param overlay 是否覆盖原文件
88 | */
89 | public static void downloadFileAsync(final String url, final String localPath, boolean overlay) {
90 | downloadFile(url, localPath, overlay, null);
91 | }
92 |
93 | /**
94 | * 异步下载文件到指定位置
95 | *
96 | * @param url 需要下载远程地址
97 | * @param localPath 下载到本地的文件存放的位置
98 | * @param overlay 是否覆盖原文件
99 | * @param callback 下载完后的回调
100 | */
101 | public static void downloadFile(final String url, final String localPath,
102 | boolean overlay, final DownLoadCallback callback) {
103 | if (TextUtils.isEmpty(url) || TextUtils.isEmpty(localPath)) {
104 | throw new IllegalArgumentException("url or localPath can not be null");
105 | } else if (!overlay && isFileExist(localPath)) {
106 | if (null != callback) {
107 | callback.done(null);
108 | }
109 | } else {
110 | addDownloadCallback(url, callback);
111 | if (!isDownloadingFile.contains(url)) {
112 | new AsyncTask() {
113 | @Override
114 | protected Exception doInBackground(Void... params) {
115 | return downloadWithOKHttp(url, localPath);
116 | }
117 |
118 | @Override
119 | protected void onPostExecute(Exception e) {
120 | executeDownloadCallBack(url, e);
121 | isDownloadingFile.remove(url);
122 | }
123 | }.execute();
124 | }
125 | }
126 | }
127 |
128 | private static Exception downloadWithOKHttp(String url, String localPath) {
129 | File file = new File(localPath);
130 | Exception result = null;
131 | Call call = okHttpClient.newCall(new Request.Builder().url(url).get().build());
132 | FileOutputStream outputStream = null;
133 | InputStream inputStream = null;
134 | try {
135 | Response response = call.execute();
136 | if (response.code() == 200) {
137 | outputStream = new FileOutputStream(file);
138 | inputStream = response.body().byteStream();
139 | byte[] buffer = new byte[4096];
140 | int len;
141 | while ((len = inputStream.read(buffer)) != -1) {
142 | outputStream.write(buffer, 0, len);
143 | }
144 | } else {
145 | result = new Exception("response code is " + response.code());
146 | }
147 | } catch (IOException e) {
148 | result = e;
149 | if (file.exists()) {
150 | file.delete();
151 | }
152 | } finally {
153 | closeQuietly(inputStream);
154 | closeQuietly(outputStream);
155 | }
156 | return result;
157 | }
158 |
159 | private static void closeQuietly(Closeable closeable) {
160 | try {
161 | closeable.close();
162 | } catch (Exception e) {
163 | }
164 | }
165 |
166 | private static boolean isFileExist(String localPath) {
167 | File file = new File(localPath);
168 | return file.exists();
169 | }
170 |
171 |
172 | public static class DownLoadCallback {
173 | public void done(Exception e) {
174 | }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/cache/LCIMLocalStorage.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.cache;
2 |
3 | import android.content.ContentValues;
4 | import android.content.Context;
5 | import android.database.Cursor;
6 | import android.database.sqlite.SQLiteDatabase;
7 | import android.database.sqlite.SQLiteOpenHelper;
8 | import android.os.Handler;
9 | import android.os.HandlerThread;
10 | import android.text.TextUtils;
11 |
12 | import com.avos.avoscloud.AVCallback;
13 | import com.avos.avoscloud.AVUtils;
14 |
15 | import java.util.ArrayList;
16 | import java.util.Arrays;
17 | import java.util.List;
18 |
19 | import com.sisi.imleancloudplugin.utils.LCIMLogUtils;
20 |
21 | /**
22 | * Created by wli on 16/2/25.
23 | * key value 形式的存储,只能存储 String,其他数据也要转化成 String 存储
24 | * 因为忽略了具体数据格式,所以更新具体属性的时候必须更新整条记录
25 | *
26 | * 因为最终读与写的操作都是在 readDbThread 线程中进行,所以不需要考虑线程安全问题
27 | */
28 | class LCIMLocalStorage extends SQLiteOpenHelper {
29 |
30 | /**
31 | * db 的名字,加前缀避免与用户自己的逻辑冲突
32 | */
33 | private static final String DB_NAME_PREFIX = "LeanCloudChatKit_DB";
34 |
35 | /**
36 | * 具体 id 的 key,文本、主键、不能为空
37 | */
38 | private static final String TABLE_KEY_ID = "id";
39 |
40 | /**
41 | * 具体内容的 key,文本(非文本的可以通过转化成 json 存进来)
42 | */
43 | private static final String TABLE_KEY_CONTENT = "content";
44 |
45 | private static final String SQL_CREATE_TABLE = "CREATE TABLE IF NOT EXISTS %s(" +
46 | TABLE_KEY_ID + " TEXT PRIMARY KEY NOT NULL, " +
47 | TABLE_KEY_CONTENT + " TEXT " +
48 | ")";
49 | private static final String SQL_DROP_TABLE = "DROP TABLE IF EXISTS %s";
50 |
51 | private static final int DB_VERSION = 1;
52 |
53 | private String tableName;
54 |
55 | private HandlerThread readDbThread;
56 | private Handler readDbHandler;
57 |
58 | public LCIMLocalStorage(Context context, String clientId, String tableName) {
59 | super(context, DB_NAME_PREFIX, null, DB_VERSION);
60 |
61 | if (TextUtils.isEmpty(tableName)) {
62 | throw new IllegalArgumentException("tableName can not be null");
63 | }
64 | if (TextUtils.isEmpty(clientId)) {
65 | throw new IllegalArgumentException("clientId can not be null");
66 | }
67 |
68 | final String md5ClientId = AVUtils.md5(clientId);
69 | this.tableName = tableName + "_" + md5ClientId;
70 |
71 | createTable();
72 |
73 | readDbThread = new HandlerThread("LCIMLocalStorageReadThread");
74 | readDbThread.start();
75 | readDbHandler = new Handler(readDbThread.getLooper());
76 | }
77 |
78 | @Override
79 | public void onCreate(SQLiteDatabase db) {
80 | db.execSQL(String.format(SQL_CREATE_TABLE, tableName));
81 | }
82 |
83 | @Override
84 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
85 | if (!isIgnoreUpgrade()) {
86 | db.execSQL(String.format(SQL_DROP_TABLE, tableName));
87 | onCreate(db);
88 | }
89 | }
90 |
91 | /**
92 | * 因为 onCreate 为初始化 db 的时候才调用的,所以多表的情况下需要主动调用此函数来创建表
93 | */
94 | private void createTable() {
95 | getWritableDatabase().execSQL(String.format(SQL_CREATE_TABLE, tableName));
96 | }
97 |
98 | protected boolean isIgnoreUpgrade() {
99 | return true;
100 | }
101 |
102 | /**
103 | * 获取所有的 Key 值
104 | *
105 | * @param callback 获取后会执行此回调
106 | */
107 | public void getIds(final AVCallback> callback) {
108 | if (null != callback) {
109 | readDbHandler.post(new Runnable() {
110 | @Override
111 | public void run() {
112 | callback.internalDone(getIdsSync(), null);
113 | }
114 | });
115 | }
116 | }
117 |
118 | /**
119 | * 根据 key 值获对应的 values
120 | * 注意:并不保证回调 data 与 id 顺序一致
121 | *
122 | * @param ids 需要的获取数据的 key
123 | * @param callback 获取后会执行此回调
124 | */
125 | public void getData(final List ids, final AVCallback> callback) {
126 | if (null != callback) {
127 | if (null != ids && ids.size() > 0) {
128 | readDbHandler.post(new Runnable() {
129 | @Override
130 | public void run() {
131 | callback.internalDone(getDataSync(ids), null);
132 | }
133 | });
134 | } else {
135 | callback.internalDone(null, null);
136 | }
137 | }
138 | }
139 |
140 | /**
141 | * 插入数据,注意 idList 与 valueList 是一一对应的
142 | *
143 | * @param idList
144 | * @param valueList
145 | */
146 | public void insertData(final List idList, final List valueList) {
147 | if (null != idList && null != valueList && idList.size() == valueList.size()) {
148 | readDbHandler.post(new Runnable() {
149 | @Override
150 | public void run() {
151 | insertSync(idList, valueList);
152 | }
153 | });
154 | }
155 | }
156 |
157 | /**
158 | * 插入数据,注意 id 与 value 是对应的
159 | *
160 | * @param id
161 | * @param value
162 | */
163 | public void insertData(String id, String value) {
164 | if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(value)) {
165 | insertData(Arrays.asList(id), Arrays.asList(value));
166 | }
167 | }
168 |
169 | /**
170 | * 删除数据
171 | *
172 | * @param ids
173 | */
174 | public void deleteData(final List ids) {
175 | if (null != ids && !ids.isEmpty()) {
176 | readDbHandler.post(new Runnable() {
177 | @Override
178 | public void run() {
179 | deleteSync(ids);
180 | }
181 | });
182 | }
183 | }
184 |
185 | public void deleteAllData() {
186 | readDbHandler.post(new Runnable() {
187 | @Override
188 | public void run() {
189 | SQLiteDatabase db = getWritableDatabase();
190 | db.delete(tableName, null, null);
191 | }
192 | });
193 | }
194 |
195 | /**
196 | * 获取 key 值,此为同步方法
197 | */
198 | private List getIdsSync() {
199 | String queryString = "SELECT " + TABLE_KEY_ID + " FROM " + tableName;
200 | SQLiteDatabase database = getReadableDatabase();
201 | Cursor cursor = database.rawQuery(queryString, null);
202 | List dataList = new ArrayList<>();
203 | while (cursor.moveToNext()) {
204 | dataList.add(cursor.getString(cursor.getColumnIndex(TABLE_KEY_ID)));
205 | }
206 | cursor.close();
207 | return dataList;
208 | }
209 |
210 | /**
211 | * 获取数据,此为同步方法
212 | * 注意:并不保证回调 data 与 id 顺序一致
213 | */
214 | private List getDataSync(List ids) {
215 | String queryString = "SELECT * FROM " + tableName;
216 | if (null != ids && !ids.isEmpty()) {
217 | queryString += (" WHERE " + TABLE_KEY_ID + " in ('" + AVUtils.joinCollection(ids, "','") + "')");
218 | }
219 |
220 | SQLiteDatabase database = getReadableDatabase();
221 | Cursor cursor = database.rawQuery(queryString, null);
222 | List dataList = new ArrayList<>();
223 | while (cursor.moveToNext()) {
224 | dataList.add(cursor.getString(cursor.getColumnIndex(TABLE_KEY_CONTENT)));
225 | }
226 | cursor.close();
227 | return dataList;
228 | }
229 |
230 | /**
231 | * 插入数据,此为同步方法
232 | */
233 | private void insertSync(List idList, List valueList) {
234 | if(idList.size() != valueList.size()) {
235 | LCIMLogUtils.i("idList.size is not equal to valueList.size");
236 | }
237 | SQLiteDatabase db = getWritableDatabase();
238 | db.beginTransaction();
239 | for (int i = 0; i < valueList.size(); i++) {
240 | ContentValues values = new ContentValues();
241 | values.put(TABLE_KEY_ID, idList.get(i));
242 | values.put(TABLE_KEY_CONTENT, valueList.get(i));
243 | db.insertWithOnConflict(tableName, null, values, SQLiteDatabase.CONFLICT_REPLACE);
244 | }
245 | db.setTransactionSuccessful();
246 | db.endTransaction();
247 | }
248 |
249 | /**
250 | * 输出数据,此为同步方法
251 | */
252 | private void deleteSync(List ids) {
253 | if (null != ids && !ids.isEmpty()) {
254 | String queryString = joinListWithApostrophe(ids);
255 | getWritableDatabase().delete(tableName, TABLE_KEY_ID + " in (" + queryString + ")", null);
256 | }
257 | }
258 |
259 | private static String joinListWithApostrophe(List strList) {
260 | String queryString = TextUtils.join("','", strList);
261 | if (!TextUtils.isEmpty(queryString)) {
262 | queryString = "'" + queryString + "'";
263 | }
264 | return queryString;
265 | }
266 | }
267 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/cache/LCIMProfileCache.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.cache;
2 |
3 | import android.content.Context;
4 |
5 | import com.alibaba.fastjson.JSONObject;
6 | import com.avos.avoscloud.AVCallback;
7 | import com.avos.avoscloud.AVException;
8 |
9 | import java.util.ArrayList;
10 | import java.util.Arrays;
11 | import java.util.HashMap;
12 | import java.util.List;
13 | import java.util.Map;
14 |
15 | import com.sisi.imleancloudplugin.LCChatKit;
16 | import com.sisi.imleancloudplugin.LCChatKitUser;
17 | import com.sisi.imleancloudplugin.LCChatProfileProvider;
18 | import com.sisi.imleancloudplugin.LCChatProfilesCallBack;
19 |
20 |
21 | /**
22 | * Created by wli on 16/2/25.
23 | * 用户信息缓存
24 | * 流程:
25 | * 1、如果内存中有则从内存中获取
26 | * 2、如果内存中没有则从 db 中获取
27 | * 3、如果 db 中没有则通过调用开发者设置的回调 LCChatProfileProvider.fetchProfiles 来获取
28 | * 同时获取到的数据会缓存到内存与 db
29 | */
30 | public class LCIMProfileCache {
31 |
32 | private static final String USER_NAME = "user_name";
33 | private static final String USER_AVATAR = "user_avatar";
34 | private static final String USER_ID = "user_id";
35 |
36 | private Map userMap;
37 | private LCIMLocalStorage profileDBHelper;
38 |
39 | private LCIMProfileCache() {
40 | userMap = new HashMap<>();
41 | }
42 |
43 | private static LCIMProfileCache profileCache;
44 |
45 | public static synchronized LCIMProfileCache getInstance() {
46 | if (null == profileCache) {
47 | profileCache = new LCIMProfileCache();
48 | }
49 | return profileCache;
50 | }
51 |
52 | /**
53 | * 因为只有在第一次的时候需要设置 Context 以及 clientId,所以单独拎出一个函数主动调用初始化
54 | * 避免 getInstance 传入过多参数
55 | *
56 | * @param context
57 | * @param clientId
58 | */
59 | public synchronized void initDB(Context context, String clientId) {
60 | profileDBHelper = new LCIMLocalStorage(context, clientId, "ProfileCache");
61 | }
62 |
63 | /**
64 | * 根据 id 获取用户信息
65 | * 先从缓存中获取,若没有再调用用户回调获取
66 | *
67 | * @param id
68 | * @param callback
69 | */
70 | public synchronized void getCachedUser(final String id, final AVCallback callback) {
71 | getCachedUsers(Arrays.asList(id), new AVCallback>() {
72 | @Override
73 | protected void internalDone0(List lcimUserProfiles, AVException e) {
74 | LCChatKitUser LCChatKitUser =
75 | (null != lcimUserProfiles && !lcimUserProfiles.isEmpty() ? lcimUserProfiles.get(0) : null);
76 | callback.internalDone(LCChatKitUser, e);
77 | }
78 | });
79 | }
80 |
81 | /**
82 | * 获取多个用户的信息
83 | * 先从缓存中获取,若没有再调用用户回调获取
84 | *
85 | * @param idList
86 | * @param callback
87 | */
88 | public synchronized void getCachedUsers(List idList, final AVCallback> callback) {
89 | if (null != callback) {
90 | if (null == idList || idList.isEmpty()) {
91 | callback.internalDone(null, new AVException(new Throwable("idList is empty!")));
92 | } else {
93 | final List profileList = new ArrayList();
94 | final List unCachedIdList = new ArrayList();
95 |
96 | for (String id : idList) {
97 | if (userMap.containsKey(id)) {
98 | profileList.add(userMap.get(id));
99 | } else {
100 | unCachedIdList.add(id);
101 | }
102 | }
103 |
104 | if (unCachedIdList.isEmpty()) {
105 | callback.internalDone(profileList, null);
106 | } else if (null != profileDBHelper) {
107 | profileDBHelper.getData(idList, new AVCallback>() {
108 | @Override
109 | protected void internalDone0(List strings, AVException e) {
110 | if (null != strings && !strings.isEmpty() && strings.size() == unCachedIdList.size()) {
111 | List profileList = new ArrayList();
112 | for (String data : strings) {
113 | LCChatKitUser userProfile = getUserProfileFromJson(data);
114 | if (null != userProfile) {
115 | userMap.put(userProfile.getUserId(), userProfile);
116 | profileList.add(userProfile);
117 | }
118 | }
119 | callback.internalDone(profileList, null);
120 | } else {
121 | getProfilesFromProvider(unCachedIdList, profileList, callback);
122 | }
123 | }
124 | });
125 | } else {
126 | getProfilesFromProvider(unCachedIdList, profileList, callback);
127 | }
128 | }
129 | }
130 | }
131 |
132 | /**
133 | * 根据 id 通过开发者设置的回调获取用户信息
134 | *
135 | * @param idList
136 | * @param callback
137 | */
138 | private void getProfilesFromProvider(List idList, final List profileList,
139 | final AVCallback> callback) {
140 | LCChatProfileProvider profileProvider = LCChatKit.getInstance().getProfileProvider();
141 | if (null != profileProvider) {
142 | profileProvider.fetchProfiles(idList, new LCChatProfilesCallBack() {
143 | @Override
144 | public void done(List userList, Exception e) {
145 | if (null != userList) {
146 | for (LCChatKitUser userProfile : userList) {
147 | cacheUser(userProfile);
148 | }
149 | }
150 | profileList.addAll(userList);
151 | callback.internalDone(profileList, null != e ? new AVException(e) : null);
152 | }
153 | });
154 | } else {
155 | callback.internalDone(null, new AVException(new Throwable("please setProfileProvider first!")));
156 | }
157 | }
158 |
159 | /**
160 | * 根据 id 获取用户名
161 | *
162 | * @param id
163 | * @param callback
164 | */
165 | public void getUserName(String id, final AVCallback callback) {
166 | getCachedUser(id, new AVCallback() {
167 | @Override
168 | protected void internalDone0(LCChatKitUser userProfile, AVException e) {
169 | String userName = (null != userProfile ? userProfile.getName() : null);
170 | callback.internalDone(userName, e);
171 | }
172 | });
173 | }
174 |
175 | /**
176 | * 根据 id 获取用户头像
177 | *
178 | * @param id
179 | * @param callback
180 | */
181 | public void getUserAvatar(String id, final AVCallback callback) {
182 | getCachedUser(id, new AVCallback() {
183 | @Override
184 | protected void internalDone0(LCChatKitUser userProfile, AVException e) {
185 | String avatarUrl = (null != userProfile ? userProfile.getAvatarUrl() : null);
186 | callback.internalDone(avatarUrl, e);
187 | }
188 | });
189 | }
190 |
191 | /**
192 | * 内存中是否包相关 LCChatKitUser 的信息
193 | *
194 | * @param id
195 | * @return
196 | */
197 | public synchronized boolean hasCachedUser(String id) {
198 | return userMap.containsKey(id);
199 | }
200 |
201 | /**
202 | * 缓存 LCChatKitUser 信息,更新缓存同时也更新 db
203 | * 如果开发者 LCChatKitUser 信息变化,可以通过调用此方法刷新缓存
204 | *
205 | * @param userProfile
206 | */
207 | public synchronized void cacheUser(LCChatKitUser userProfile) {
208 | if (null != userProfile && null != profileDBHelper) {
209 | userMap.put(userProfile.getUserId(), userProfile);
210 | profileDBHelper.insertData(userProfile.getUserId(), getStringFormUserProfile(userProfile));
211 | }
212 | }
213 |
214 | /**
215 | * 从 db 中的 String 解析出 LCChatKitUser
216 | *
217 | * @param str
218 | * @return
219 | */
220 | private LCChatKitUser getUserProfileFromJson(String str) {
221 | try {
222 | JSONObject jsonObject = JSONObject.parseObject(str);
223 | String userName = jsonObject.getString(USER_NAME);
224 | String userId = jsonObject.getString(USER_ID);
225 | String userAvatar = jsonObject.getString(USER_AVATAR);
226 | return new LCChatKitUser(userId, userName, userAvatar);
227 | } catch (Exception e) {
228 | }
229 | return null;
230 | }
231 |
232 | /**
233 | * LCChatKitUser 转换成 json String
234 | *
235 | * @param userProfile
236 | * @return
237 | */
238 | private String getStringFormUserProfile(LCChatKitUser userProfile) {
239 | JSONObject jsonObject = new JSONObject();
240 | jsonObject.put(USER_NAME, userProfile.getName());
241 | jsonObject.put(USER_AVATAR, userProfile.getAvatarUrl());
242 | jsonObject.put(USER_ID, userProfile.getUserId());
243 | return jsonObject.toJSONString();
244 | }
245 | }
246 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/event/LCIMConnectionChangeEvent.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.event;
2 |
3 | /**
4 | * Created by wli on 15/12/16.
5 | * 网络连接状态变化的事件
6 | */
7 | public class LCIMConnectionChangeEvent {
8 | public boolean isConnect;
9 |
10 | public LCIMConnectionChangeEvent(boolean isConnect) {
11 | this.isConnect = isConnect;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/event/LCIMConversationItemLongClickEvent.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.event;
2 |
3 | import com.avos.avoscloud.im.v2.AVIMConversation;
4 |
5 | /**
6 | * Created by wli on 16/9/14.
7 | */
8 | public class LCIMConversationItemLongClickEvent {
9 | public AVIMConversation conversation;
10 |
11 | public LCIMConversationItemLongClickEvent(AVIMConversation conversation) {
12 | this.conversation = conversation;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/event/LCIMConversationReadStatusEvent.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.event;
2 |
3 | /**
4 | * Created by wli on 2017/4/25.
5 | */
6 |
7 | public class LCIMConversationReadStatusEvent {
8 | public String conversationId;
9 | }
10 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/event/LCIMIMTypeMessageEvent.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.event;
2 |
3 | import com.avos.avoscloud.im.v2.AVIMConversation;
4 | import com.avos.avoscloud.im.v2.AVIMTypedMessage;
5 |
6 | /**
7 | * Created by wli on 15/8/23.
8 | * 收到 AVIMTypedMessage 消息后的事件
9 | */
10 | public class LCIMIMTypeMessageEvent {
11 | public AVIMTypedMessage message;
12 | public AVIMConversation conversation;
13 | }
14 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/event/LCIMInputBottomBarEvent.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.event;
2 |
3 | /**
4 | * Created by wli on 15/7/29.
5 | * InputBottomBar 相关的 EventBus 事件
6 | */
7 | public class LCIMInputBottomBarEvent {
8 |
9 | public static final int INPUTBOTTOMBAR_IMAGE_ACTION = 0;
10 | public static final int INPUTBOTTOMBAR_CAMERA_ACTION = 1;
11 | public static final int INPUTBOTTOMBAR_LOCATION_ACTION = 2;
12 | public static final int INPUTBOTTOMBAR_SEND_TEXT_ACTION = 3;
13 | public static final int INPUTBOTTOMBAR_SEND_AUDIO_ACTION = 4;
14 |
15 | public int eventAction;
16 | public Object tag;
17 |
18 | public LCIMInputBottomBarEvent(int action, Object tag) {
19 | eventAction = action;
20 | this.tag = tag;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/event/LCIMInputBottomBarLocationClickEvent.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.event;
2 |
3 | /**
4 | * Created by wli on 15/9/20.
5 | * inputbottombar 里边的点击地理位置,触发此事件
6 | * 其实这些 item 都可以放到一个 event 处理,因为兼容以前的逻辑,暂时分开
7 | */
8 | public class LCIMInputBottomBarLocationClickEvent extends LCIMInputBottomBarEvent {
9 | public LCIMInputBottomBarLocationClickEvent(int action, Object tag) {
10 | super(action, tag);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/event/LCIMInputBottomBarRecordEvent.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.event;
2 |
3 | /**
4 | * Created by wli on 15/7/29.
5 | * InputBottomBar 录音事件,录音完成时触发
6 | */
7 | public class LCIMInputBottomBarRecordEvent extends LCIMInputBottomBarEvent {
8 |
9 | /**
10 | * 录音本地路径
11 | */
12 | public String audioPath;
13 |
14 | /**
15 | * 录音长度
16 | */
17 | public int audioDuration;
18 |
19 | public LCIMInputBottomBarRecordEvent(int action, String path, int duration, Object tag) {
20 | super(action, tag);
21 | audioDuration = duration;
22 | audioPath = path;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/event/LCIMInputBottomBarTextEvent.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.event;
2 |
3 | /**
4 | * Created by wli on 15/7/29.
5 | * InputBottomBar 发送文本事件
6 | */
7 | public class LCIMInputBottomBarTextEvent extends LCIMInputBottomBarEvent {
8 |
9 | /**
10 | * 发送的文本内容
11 | */
12 | public String sendContent;
13 |
14 | public LCIMInputBottomBarTextEvent(int action, String content, Object tag) {
15 | super(action, tag);
16 | sendContent = content;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/event/LCIMLocationItemClickEvent.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.event;
2 |
3 | import com.avos.avoscloud.im.v2.AVIMMessage;
4 |
5 | /**
6 | * Created by wli on 15/9/20.
7 | * 聊天时地理位置 item 点击时触发该事件
8 | * 其实这些 item 都可以放到一个 event 处理,因为兼容以前的逻辑,暂时分开
9 | */
10 | public class LCIMLocationItemClickEvent {
11 | public AVIMMessage message;
12 | }
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/event/LCIMMemberLetterEvent.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.event;
2 |
3 | /**
4 | * Created by wli on 15/8/24.
5 | */
6 | public class LCIMMemberLetterEvent {
7 | public Character letter;
8 | }
9 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/event/LCIMMemberSelectedChangeEvent.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.event;
2 |
3 | import com.sisi.imleancloudplugin.LCChatKitUser;
4 |
5 | public class LCIMMemberSelectedChangeEvent {
6 | public LCChatKitUser member;
7 | public boolean isSelected;
8 | }
9 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/event/LCIMMessageResendEvent.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.event;
2 |
3 | import com.avos.avoscloud.im.v2.AVIMMessage;
4 |
5 | /**
6 | * Created by wli on 16/2/23.
7 | * 聊天页面,重新发送消息的事件
8 | */
9 | public class LCIMMessageResendEvent {
10 | public AVIMMessage message;
11 | }
12 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/event/LCIMMessageUpdateEvent.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.event;
2 |
3 | import com.avos.avoscloud.im.v2.AVIMMessage;
4 |
5 | /**
6 | * Created by fengjunwen on 2017/11/16.
7 | */
8 |
9 | public class LCIMMessageUpdateEvent {
10 | public AVIMMessage message;
11 | }
12 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/event/LCIMMessageUpdatedEvent.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.event;
2 |
3 | import com.avos.avoscloud.im.v2.AVIMMessage;
4 |
5 | /**
6 | * Created by fengjunwen on 2017/11/16.
7 | */
8 |
9 | public class LCIMMessageUpdatedEvent {
10 | public AVIMMessage message;
11 |
12 | public LCIMMessageUpdatedEvent(AVIMMessage message) {
13 | this.message = message;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/event/LCIMOfflineMessageCountChangeEvent.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.event;
2 |
3 | import com.avos.avoscloud.im.v2.AVIMConversation;
4 | import com.avos.avoscloud.im.v2.AVIMMessage;
5 |
6 | /**
7 | * Created by wli on 16/3/7.
8 | * 离线消息数量发生变化的事件
9 | */
10 | public class LCIMOfflineMessageCountChangeEvent {
11 | public AVIMConversation conversation;
12 | public AVIMMessage lastMessage;
13 | private LCIMOfflineMessageCountChangeEvent() {
14 | ;
15 | }
16 | public LCIMOfflineMessageCountChangeEvent(AVIMConversation conversation, AVIMMessage lastMessage) {
17 | this.conversation = conversation;
18 | this.lastMessage = lastMessage;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/handler/LCIMClientEventHandler.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.handler;
2 |
3 | import com.avos.avoscloud.im.v2.AVIMClient;
4 | import com.avos.avoscloud.im.v2.AVIMClientEventHandler;
5 | import com.avos.avoscloud.PushService;
6 |
7 | import com.sisi.imleancloudplugin.ImLeancloudPlugin;
8 | import com.sisi.imleancloudplugin.event.LCIMConnectionChangeEvent;
9 | import com.sisi.imleancloudplugin.utils.LCIMLogUtils;
10 | import de.greenrobot.event.EventBus;
11 |
12 |
13 | /**
14 | * Created by wli on 15/12/16.
15 | * 与网络相关的 handler
16 | * 注意,此 handler 并不是网络状态通知,而是当前 client 的连接状态
17 | *
18 | *
19 | */
20 |
21 |
22 | public class LCIMClientEventHandler extends AVIMClientEventHandler {
23 |
24 | private static LCIMClientEventHandler eventHandler;
25 |
26 | public static synchronized LCIMClientEventHandler getInstance() {
27 | if (null == eventHandler) {
28 | eventHandler = new LCIMClientEventHandler();
29 | }
30 | return eventHandler;
31 | }
32 |
33 | private LCIMClientEventHandler() {
34 | }
35 |
36 |
37 | private volatile boolean connect = false;
38 |
39 | /**
40 | * 是否连上聊天服务
41 | *
42 | * @return
43 | */
44 | public boolean isConnect() {
45 | return connect;
46 | }
47 |
48 | public void setConnectAndNotify(boolean isConnect) {
49 | connect = isConnect;
50 | // EventBus.getDefault().post(new LCIMConnectionChangeEvent(connect));
51 | }
52 |
53 | @Override
54 | public void onConnectionPaused(AVIMClient avimClient) {
55 | // setConnectAndNotify(false);
56 | }
57 |
58 | @Override
59 | public void onConnectionResume(AVIMClient avimClient) {
60 | //setConnectAndNotify(true);
61 | ImLeancloudPlugin.instance.channel.invokeMethod("onConnectionResume",true);
62 |
63 | }
64 |
65 | @Override
66 | public void onClientOffline(AVIMClient avimClient, int i) {
67 | LCIMLogUtils.d("client " + avimClient.getClientId() + " is offline!");
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/handler/LCIMConversationHandler.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.handler;
2 |
3 | import com.avos.avoscloud.im.v2.AVIMClient;
4 | import com.avos.avoscloud.im.v2.AVIMMessage;
5 | import com.avos.avoscloud.im.v2.AVIMConversation;
6 | import com.avos.avoscloud.im.v2.AVIMConversationEventHandler;
7 |
8 | import java.util.List;
9 |
10 |
11 | import com.sisi.imleancloudplugin.ImLeancloudPlugin;
12 | import com.sisi.imleancloudplugin.cache.LCIMConversationItemCache;
13 | import com.sisi.imleancloudplugin.event.LCIMConversationReadStatusEvent;
14 |
15 |
16 | /**
17 | * Created by wli on 15/12/1.
18 | * 和 Conversation 相关的事件的 handler
19 | * 需要应用主动调用 AVIMMessageManager.setConversationEventHandler
20 | * 关于回调会何时执行可以参见 https://leancloud.cn/docs/realtime_guide-android.html#添加其他成员
21 | */
22 | public class LCIMConversationHandler extends AVIMConversationEventHandler {
23 |
24 | private static LCIMConversationHandler eventHandler;
25 |
26 | public static synchronized LCIMConversationHandler getInstance() {
27 | if (null == eventHandler) {
28 | eventHandler = new LCIMConversationHandler();
29 | }
30 | return eventHandler;
31 | }
32 |
33 | private LCIMConversationHandler() {
34 | }
35 |
36 | @Override
37 | public void onUnreadMessagesCountUpdated(AVIMClient client, AVIMConversation conversation) {
38 | LCIMConversationItemCache.getInstance().insertConversation(conversation.getConversationId());
39 | AVIMMessage lastMessage = conversation.getLastMessage();
40 | System.out.println("LCIMConversationHandler#onUnreadMessagesCountUpdated conv=" + conversation.getConversationId() + ", lastMsg: " + lastMessage.getContent());
41 | System.out.println(lastMessage.getContent());
42 | ImLeancloudPlugin.instance.unRead(conversation);
43 | }
44 |
45 | @Override
46 | public void onLastDeliveredAtUpdated(AVIMClient client, AVIMConversation conversation) {
47 | System.out.println("onLastDeliveredAtUpdated");
48 | System.out.println(conversation.getConversationId());
49 | System.out.println(conversation.getName());
50 | System.out.println(conversation.getMembers());
51 | ImLeancloudPlugin.instance.onLastDeliveredAtUpdated(conversation);
52 | //LCIMConversationReadStatusEvent event = new LCIMConversationReadStatusEvent();
53 | // event.conversationId = conversation.getConversationId();
54 | }
55 |
56 | @Override
57 | public void onLastReadAtUpdated(AVIMClient client, AVIMConversation conversation) {
58 | System.out.println("onLastReadAtUpdated");
59 | System.out.println(conversation.getLastReadAt());
60 | System.out.println(conversation.getConversationId());
61 | System.out.println(conversation.getName());
62 | System.out.println(conversation.getMembers());
63 | ImLeancloudPlugin.instance.onLastReadAtUpdated(conversation);
64 |
65 |
66 | // LCIMConversationReadStatusEvent event = new LCIMConversationReadStatusEvent();
67 | // event.conversationId = conversation.getConversationId();
68 |
69 |
70 | }
71 |
72 | @Override
73 | public void onMemberLeft(AVIMClient client, AVIMConversation conversation, List members, String kickedBy) {
74 | // 因为不同用户需求不同,此处暂不做默认处理,如有需要,用户可以通过自定义 Handler 实现
75 | }
76 |
77 | @Override
78 | public void onMemberJoined(AVIMClient client, AVIMConversation conversation, List members, String invitedBy) {
79 | }
80 |
81 | @Override
82 | public void onKicked(AVIMClient client, AVIMConversation conversation, String kickedBy) {
83 | }
84 |
85 | @Override
86 | public void onInvited(AVIMClient client, AVIMConversation conversation, String operator) {
87 | }
88 |
89 | @Override
90 | public void onMessageRecalled(AVIMClient client, AVIMConversation conversation, AVIMMessage message) {
91 |
92 | }
93 |
94 | @Override
95 | public void onMessageUpdated(AVIMClient client, AVIMConversation conversation, AVIMMessage message) {
96 |
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/handler/LCIMMessageHandler.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.handler;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 |
6 | import com.avos.avoscloud.AVCallback;
7 | import com.avos.avoscloud.AVException;
8 | import com.avos.avoscloud.im.v2.AVIMClient;
9 | import com.avos.avoscloud.im.v2.AVIMConversation;
10 | import com.avos.avoscloud.im.v2.AVIMTypedMessage;
11 | import com.avos.avoscloud.im.v2.AVIMTypedMessageHandler;
12 | import com.avos.avoscloud.im.v2.messages.AVIMTextMessage;
13 |
14 | import com.sisi.imleancloudplugin.LCChatKit;
15 | import com.sisi.imleancloudplugin.LCChatKitUser;
16 | import com.sisi.imleancloudplugin.ImLeancloudPlugin;
17 | import com.sisi.imleancloudplugin.R;
18 | import com.sisi.imleancloudplugin.cache.LCIMConversationItemCache;
19 | import com.sisi.imleancloudplugin.cache.LCIMProfileCache;
20 | import com.sisi.imleancloudplugin.event.LCIMIMTypeMessageEvent;
21 | import com.sisi.imleancloudplugin.utils.LCIMConstants;
22 | import com.sisi.imleancloudplugin.utils.LCIMLogUtils;
23 | import com.sisi.imleancloudplugin.utils.LCIMNotificationUtils;
24 |
25 | import java.util.HashMap;
26 | import java.util.Map;
27 |
28 | import de.greenrobot.event.EventBus;
29 |
30 |
31 |
32 | /**
33 | * Created by zhangxiaobo on 15/4/20.
34 | * AVIMTypedMessage 的 handler,socket 过来的 AVIMTypedMessage 都会通过此 handler 与应用交互
35 | * 需要应用主动调用 AVIMMessageManager.registerMessageHandler 来注册
36 | * 当然,自定义的消息也可以通过这种方式来处理
37 | */
38 | public class LCIMMessageHandler extends AVIMTypedMessageHandler {
39 |
40 | private Context context;
41 |
42 | public LCIMMessageHandler(Context context) {
43 | this.context = context.getApplicationContext();
44 | }
45 |
46 | @Override
47 | public void onMessage(AVIMTypedMessage message, AVIMConversation conversation, AVIMClient client) {
48 | if (message == null || message.getMessageId() == null) {
49 | LCIMLogUtils.d("may be SDK Bug, message or message id is null");
50 | return;
51 | }
52 |
53 | if (LCChatKit.getInstance().getCurrentUserId() == null) {
54 | LCIMLogUtils.d("selfId is null, please call LCChatKit.open!");
55 | client.close(null);
56 | } else {
57 | if (!client.getClientId().equals(LCChatKit.getInstance().getCurrentUserId())) {
58 | client.close(null);
59 | } else {
60 | if (LCIMNotificationUtils.isShowNotification(conversation.getConversationId())) {
61 | sendNotification(message, conversation);
62 | }
63 | LCIMConversationItemCache.getInstance().insertConversation(message.getConversationId());
64 | if (!message.getFrom().equals(client.getClientId())) {
65 | System.out.println(message.getContent());
66 | ImLeancloudPlugin.instance.onReceiveMessage(message);
67 | // message.getConversationId(),message.getContent(),message.getFrom(),message.getTimestamp()
68 | sendEvent(message, conversation);
69 | }
70 | }
71 | }
72 | }
73 |
74 | @Override
75 | public void onMessageReceipt(AVIMTypedMessage message, AVIMConversation conversation, AVIMClient client) {
76 | super.onMessageReceipt(message, conversation, client);
77 | }
78 |
79 | /**
80 | * 发送消息到来的通知事件
81 | *
82 | * @param message
83 | * @param conversation
84 | */
85 | private void sendEvent(AVIMTypedMessage message, AVIMConversation conversation) {
86 | LCIMIMTypeMessageEvent event = new LCIMIMTypeMessageEvent();
87 | event.message = message;
88 | event.conversation = conversation;
89 | EventBus.getDefault().post(event);
90 | }
91 |
92 | private void sendNotification(final AVIMTypedMessage message, final AVIMConversation conversation) {
93 | if (null != conversation && null != message) {
94 |
95 | final String notificationContent = message instanceof AVIMTextMessage ?
96 | ((AVIMTextMessage) message).getText() : "不支持的消息类型";
97 | // ImLeancloudPlugin.instance.onReceiveMessage();
98 | // System.out.println(notificationContent);
99 |
100 |
101 | LCIMProfileCache.getInstance().getCachedUser(message.getFrom(), new AVCallback() {
102 | @Override
103 | protected void internalDone0(LCChatKitUser userProfile, AVException e) {
104 | if (e != null) {
105 | LCIMLogUtils.logException(e);
106 | } else if (null != userProfile) {
107 | String title = userProfile.getName();
108 | //Intent intent = getIMNotificationIntent(conversation.getConversationId(), message.getFrom());
109 | //LCIMNotificationUtils.showNotification(context, title, notificationContent, null, intent);
110 | System.out.println(title);
111 | System.out.println(notificationContent);
112 | }
113 | }
114 | });
115 | }
116 | }
117 |
118 | /**
119 | * 点击 notification 触发的 Intent
120 | * 注意要设置 package 已经 Category,避免多 app 同时引用 lib 造成消息干扰
121 | * @param conversationId
122 | * @param peerId
123 | * @return
124 | */
125 | private Intent getIMNotificationIntent(String conversationId, String peerId) {
126 | Intent intent = new Intent();
127 | intent.setAction(LCIMConstants.CHAT_NOTIFICATION_ACTION);
128 | intent.putExtra(LCIMConstants.CONVERSATION_ID, conversationId);
129 | intent.putExtra(LCIMConstants.PEER_ID, peerId);
130 | intent.setPackage(context.getPackageName());
131 | intent.addCategory(Intent.CATEGORY_DEFAULT);
132 | return intent;
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/utils/LCIMAudioHelper.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.utils;
2 |
3 | import android.media.MediaPlayer;
4 |
5 | import java.io.IOException;
6 |
7 | /**
8 | * Created by lzw on 14/12/19.
9 | * 语音播放相关的 helper 类
10 | */
11 | public class LCIMAudioHelper {
12 | private static LCIMAudioHelper audioHelper;
13 | private MediaPlayer mediaPlayer;
14 | private AudioFinishCallback finishCallback;
15 | private String audioPath;
16 | private boolean onceStart = false;
17 |
18 | private LCIMAudioHelper() {
19 | mediaPlayer = new MediaPlayer();
20 | }
21 |
22 | public static synchronized LCIMAudioHelper getInstance() {
23 | if (audioHelper == null) {
24 | audioHelper = new LCIMAudioHelper();
25 | }
26 | return audioHelper;
27 | }
28 |
29 | /**
30 | * 获取当前语音的文件地址
31 | *
32 | * @return
33 | */
34 | public String getAudioPath() {
35 | return audioPath;
36 | }
37 |
38 | /**
39 | * 停止播放
40 | */
41 | public void stopPlayer() {
42 | if (mediaPlayer != null) {
43 | tryRunFinishCallback();
44 | mediaPlayer.stop();
45 | mediaPlayer.reset();
46 | }
47 | }
48 |
49 | /**
50 | * 暂停播放
51 | */
52 | public void pausePlayer() {
53 | if (mediaPlayer != null) {
54 | tryRunFinishCallback();
55 | mediaPlayer.pause();
56 | }
57 | }
58 |
59 | /**
60 | * 判断当前是否正在播放
61 | *
62 | * @return
63 | */
64 | public boolean isPlaying() {
65 | return mediaPlayer.isPlaying();
66 | }
67 |
68 | /**
69 | * 重新播放
70 | */
71 | public void restartPlayer() {
72 | if (mediaPlayer != null && mediaPlayer.isPlaying() == false) {
73 | mediaPlayer.start();
74 | }
75 | }
76 |
77 | public void addFinishCallback(AudioFinishCallback callback) {
78 | finishCallback = callback;
79 | }
80 |
81 | /**
82 | * 播放语音文件
83 | *
84 | * @param path
85 | */
86 | public synchronized void playAudio(String path) {
87 | if (onceStart) {
88 | mediaPlayer.reset();
89 | }
90 | tryRunFinishCallback();
91 | audioPath = path;
92 | try {
93 | mediaPlayer.setDataSource(path);
94 | mediaPlayer.prepare();
95 | mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
96 | @Override
97 | public void onCompletion(MediaPlayer mp) {
98 | tryRunFinishCallback();
99 | }
100 | });
101 | mediaPlayer.start();
102 | onceStart = true;
103 | } catch (IOException e) {
104 | }
105 | }
106 |
107 | private void tryRunFinishCallback() {
108 | if (finishCallback != null) {
109 | finishCallback.onFinish();
110 | }
111 | }
112 |
113 | public interface AudioFinishCallback {
114 | void onFinish();
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/utils/LCIMConstants.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.utils;
2 |
3 | /**
4 | * Created by wli on 16/2/29.
5 | * 所有常量值均放到此类里边
6 | */
7 | public class LCIMConstants {
8 | private static final String LEANMESSAGE_CONSTANTS_PREFIX = "cn.leancloud.chatkit.";
9 |
10 | private static String getPrefixConstant(String str) {
11 | return LEANMESSAGE_CONSTANTS_PREFIX + str;
12 | }
13 |
14 | /**
15 | * 参数传递的 key 值,表示对方的 id,跳转到 LCIMConversationActivity 时可以设置
16 | */
17 | public static final String PEER_ID = getPrefixConstant("peer_id");
18 |
19 | /**
20 | * 参数传递的 key 值,表示回话 id,跳转到 LCIMConversationActivity 时可以设置
21 | */
22 | public static final String CONVERSATION_ID = getPrefixConstant("conversation_id");
23 |
24 | /**
25 | * LCIMConversationActivity 中头像点击事件发送的 action
26 | */
27 | public static final String AVATAR_CLICK_ACTION = getPrefixConstant("avatar_click_action");
28 |
29 | /**
30 | * LCIMConversationListFragment item 点击事件
31 | * 如果开发者不想跳转到 LCIMConversationActivity,可以在 Mainfest 里接管该事件
32 | */
33 | public static final String CONVERSATION_ITEM_CLICK_ACTION = getPrefixConstant("conversation_item_click_action");
34 |
35 | public static final String LCIM_LOG_TAG = getPrefixConstant("lcim_log_tag");
36 |
37 |
38 | // LCIMImageActivity
39 | public static final String IMAGE_LOCAL_PATH = getPrefixConstant("image_local_path");
40 | public static final String IMAGE_URL = getPrefixConstant("image_url");
41 |
42 | public static final String CHAT_NOTIFICATION_ACTION = getPrefixConstant("chat_notification_action");
43 | }
44 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/utils/LCIMConversationUtils.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.utils;
2 |
3 | import android.text.TextUtils;
4 |
5 | import com.avos.avoscloud.AVCallback;
6 | import com.avos.avoscloud.AVException;
7 | import com.avos.avoscloud.im.v2.AVIMConversation;
8 |
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | import com.sisi.imleancloudplugin.LCChatKit;
13 | import com.sisi.imleancloudplugin.LCChatKitUser;
14 | import com.sisi.imleancloudplugin.cache.LCIMProfileCache;
15 |
16 | /**
17 | * Created by wli on 16/3/2.
18 | * 和 Conversation 相关的 Util 类
19 | */
20 | public class LCIMConversationUtils {
21 |
22 | /**
23 | * 获取会话名称
24 | * 优先级:
25 | * 1、AVIMConersation name 属性
26 | * 2、单聊:对方用户名
27 | * 群聊:成员用户名合并
28 | *
29 | * @param conversation
30 | * @param callback
31 | */
32 | public static void getConversationName(final AVIMConversation conversation, final AVCallback callback) {
33 | if (null == callback) {
34 | return;
35 | }
36 | if (null == conversation) {
37 | callback.internalDone(null, new AVException(new Throwable("conversation can not be null!")));
38 | return;
39 | }
40 | if (conversation.isTemporary()) {
41 | callback.internalDone(conversation.getName(), null);
42 | } else if (conversation.isTransient()) {
43 | callback.internalDone(conversation.getName(), null);
44 | } else if (2 == conversation.getMembers().size()) {
45 | String peerId = getConversationPeerId(conversation);
46 | LCIMProfileCache.getInstance().getUserName(peerId, callback);
47 | } else {
48 | if (!TextUtils.isEmpty(conversation.getName())) {
49 | callback.internalDone(conversation.getName(), null);
50 | } else {
51 | LCIMProfileCache.getInstance().getCachedUsers(conversation.getMembers(), new AVCallback>() {
52 | @Override
53 | protected void internalDone0(List lcimUserProfiles, AVException e) {
54 | List nameList = new ArrayList();
55 | if (null != lcimUserProfiles) {
56 | for (LCChatKitUser userProfile : lcimUserProfiles) {
57 | nameList.add(userProfile.getName());
58 | }
59 | }
60 | callback.internalDone(TextUtils.join(",", nameList), e);
61 | }
62 | });
63 | }
64 | }
65 | }
66 |
67 | /**
68 | * 获取单聊会话的 icon
69 | * 单聊:对方用户的头像
70 | * 群聊:返回 null
71 | *
72 | * @param conversation
73 | * @param callback
74 | */
75 | public static void getConversationPeerIcon(final AVIMConversation conversation, AVCallback callback) {
76 | if (null != conversation && !conversation.isTransient() && !conversation.getMembers().isEmpty()) {
77 | String peerId = getConversationPeerId(conversation);
78 | if (1 == conversation.getMembers().size()) {
79 | peerId = conversation.getMembers().get(0);
80 | }
81 | LCIMProfileCache.getInstance().getUserAvatar(peerId, callback);
82 | } else if (null != conversation) {
83 | callback.internalDone("", null);
84 | } else {
85 | callback.internalDone(null, new AVException(new Throwable("cannot find icon!")));
86 | }
87 | }
88 |
89 | /**
90 | * 获取 “对方” 的用户 id,只对单聊有效,群聊返回空字符串
91 | *
92 | * @param conversation
93 | * @return
94 | */
95 | private static String getConversationPeerId(AVIMConversation conversation) {
96 | if (null != conversation && 2 == conversation.getMembers().size()) {
97 | String currentUserId = LCChatKit.getInstance().getCurrentUserId();
98 | String firstMemeberId = conversation.getMembers().get(0);
99 | return conversation.getMembers().get(firstMemeberId.equals(currentUserId) ? 1 : 0);
100 | }
101 | return "";
102 | }
103 | }
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/utils/LCIMLogUtils.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.utils;
2 |
3 | import android.util.Log;
4 |
5 | import com.avos.avoscloud.AVOSCloud;
6 |
7 | /**
8 | * Created by wli on 16/4/6.
9 | */
10 | public class LCIMLogUtils {
11 | public static final String LOGTAG = "LCChatKit";
12 | public static boolean debugEnabled = false;
13 |
14 | static {
15 | debugEnabled = AVOSCloud.isDebugLogEnabled();
16 | }
17 |
18 | public LCIMLogUtils() {
19 | }
20 |
21 | private static String getDebugInfo() {
22 | Throwable stack = new Throwable().fillInStackTrace();
23 | StackTraceElement[] trace = stack.getStackTrace();
24 | int n = 2;
25 | return trace[n].getClassName() + " " + trace[n].getMethodName() + "()" + ":" + trace[n].getLineNumber() +
26 | " ";
27 | }
28 |
29 | private static String getLogInfoByArray(String[] infos) {
30 | StringBuilder sb = new StringBuilder();
31 | for (String info : infos) {
32 | sb.append(info);
33 | sb.append(" ");
34 | }
35 | return sb.toString();
36 | }
37 |
38 | public static void i(String... s) {
39 | if (debugEnabled) {
40 | Log.i(LOGTAG, getDebugInfo() + getLogInfoByArray(s));
41 | }
42 | }
43 |
44 | public static void e(String... s) {
45 | if (debugEnabled) {
46 | Log.e(LOGTAG, getDebugInfo() + getLogInfoByArray(s));
47 | }
48 | }
49 |
50 | public static void d(String... s) {
51 | if (debugEnabled) {
52 | Log.d(LOGTAG, getDebugInfo() + getLogInfoByArray(s));
53 | }
54 | }
55 |
56 | public static void v(String... s) {
57 | if (debugEnabled) {
58 | Log.v(LOGTAG, getDebugInfo() + getLogInfoByArray(s));
59 | }
60 | }
61 |
62 | public static void w(String... s) {
63 | if (debugEnabled) {
64 | Log.w(LOGTAG, getDebugInfo() + getLogInfoByArray(s));
65 | }
66 | }
67 |
68 | public static void logException(Throwable tr) {
69 | if (debugEnabled) {
70 | Log.e(LOGTAG, getDebugInfo(), tr);
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/utils/LCIMNotificationUtils.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.utils;
2 |
3 | import android.app.Notification;
4 | import android.app.NotificationManager;
5 | import android.app.PendingIntent;
6 | import android.content.ContentResolver;
7 | import android.content.Context;
8 | import android.content.Intent;
9 | import android.net.Uri;
10 |
11 | import java.util.LinkedList;
12 | import java.util.List;
13 |
14 | import android.support.v4.app.NotificationCompat;
15 |
16 | /**
17 | * Created by wli on 15/8/26.
18 | * Notification 相关的 Util
19 | */
20 | public class LCIMNotificationUtils {
21 |
22 | private static int lastNotificationId = 0;
23 |
24 | /**
25 | * tag list,用来标记是否应该展示 Notification
26 | * 比如已经在聊天页面了,实际就不应该再弹出 notification
27 | */
28 | private static List notificationTagList = new LinkedList();
29 |
30 | /**
31 | * 添加 tag 到 tag list,在 MessageHandler 弹出 notification 前会判断是否与此 tag 相等
32 | * 若相等,则不弹,反之,则弹出
33 | *
34 | * @param tag
35 | */
36 | public static void addTag(String tag) {
37 | if (!notificationTagList.contains(tag)) {
38 | notificationTagList.add(tag);
39 | }
40 | }
41 |
42 | /**
43 | * 在 tag list 中 remove 该 tag
44 | *
45 | * @param tag
46 | */
47 | public static void removeTag(String tag) {
48 | notificationTagList.remove(tag);
49 | }
50 |
51 | /**
52 | * 判断是否应该弹出 notification
53 | * 判断标准是该 tag 是否包含在 tag list 中
54 | *
55 | * @param tag
56 | * @return
57 | */
58 | public static boolean isShowNotification(String tag) {
59 | return !notificationTagList.contains(tag);
60 | }
61 |
62 | public static void showNotification(Context context, String title, String content, Intent intent) {
63 | showNotification(context, title, content, null, intent);
64 | }
65 |
66 | public static void showNotification(Context context, String title, String content, String sound, Intent intent) {
67 | PendingIntent contentIntent = PendingIntent.getActivity(context, 0, intent, 0);
68 | NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context)
69 | .setSmallIcon(context.getApplicationInfo().icon)
70 | .setContentTitle(title).setAutoCancel(true).setContentIntent(contentIntent)
71 | .setDefaults(Notification.DEFAULT_VIBRATE | Notification.DEFAULT_SOUND)
72 | .setContentText(content);
73 | NotificationManager manager =
74 | (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
75 | Notification notification = mBuilder.build();
76 | if (sound != null && sound.trim().length() > 0) {
77 | notification.sound = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + sound);
78 | }
79 | lastNotificationId = (lastNotificationId > 10000 ? 0 : lastNotificationId + 1);
80 | manager.notify(lastNotificationId, notification);
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/utils/LCIMPathUtils.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.utils;
2 |
3 | import android.content.Context;
4 | import android.os.Environment;
5 | import android.text.TextUtils;
6 |
7 | import java.io.File;
8 |
9 | /**
10 | * Created by lzw on 15/4/26.
11 | */
12 | public class LCIMPathUtils {
13 |
14 | private static boolean isExternalStorageWritable() {
15 | String state = Environment.getExternalStorageState();
16 | return Environment.MEDIA_MOUNTED.equals(state);
17 | }
18 |
19 | /**
20 | * 有 sdcard 的时候,小米是 /storage/sdcard0/Android/data/com.avoscloud.chat/cache/
21 | * 无 sdcard 的时候,小米是 /data/data/com.avoscloud.chat/cache
22 | * 依赖于包名。所以不同应用使用该库也没问题,要有点理想。
23 | *
24 | * @return
25 | */
26 | private static File getAvailableCacheDir(Context context) {
27 | if (isExternalStorageWritable()) {
28 | return context.getExternalCacheDir();
29 | } else {
30 | // 只有此应用才能访问。拍照的时候有问题,因为拍照的应用写入不了该文件
31 | return context.getCacheDir();
32 | }
33 | }
34 |
35 | public static String getAudioCachePath(Context context, String id) {
36 | return (TextUtils.isEmpty(id) ? null : new File(getAvailableCacheDir(context), id).getAbsolutePath());
37 | }
38 |
39 | /**
40 | * 录音保存的地址
41 | *
42 | * @return
43 | */
44 | public static String getRecordPathByCurrentTime(Context context) {
45 | return new File(getAvailableCacheDir(context), "record_" + System.currentTimeMillis()).getAbsolutePath();
46 | }
47 |
48 | /**
49 | * 拍照保存的地址
50 | *
51 | * @return
52 | */
53 | public static String getPicturePathByCurrentTime(Context context) {
54 | String path = new File(getAvailableCacheDir(context), "picture_" + System.currentTimeMillis() + ".jpg").getAbsolutePath();
55 | return path;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/android/src/main/java/com/sisi/imleancloudplugin/utils/LCIMSoftInputUtils.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudplugin.utils;
2 |
3 | import android.content.Context;
4 | import android.view.View;
5 | import android.view.inputmethod.InputMethodManager;
6 |
7 | /**
8 | * Created by wli on 15/7/29.
9 | * 关于软键盘的 Util 类
10 | */
11 | public class LCIMSoftInputUtils {
12 |
13 | /**
14 | * 如果当前键盘已经显示,则隐藏
15 | * 如果当前键盘未显示,则显示
16 | *
17 | * @param context
18 | */
19 | public static void toggleSoftInput(Context context) {
20 | InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
21 | imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
22 | }
23 |
24 | /**
25 | * 弹出键盘
26 | *
27 | * @param context
28 | * @param view
29 | */
30 | public static void showSoftInput(Context context, View view) {
31 | if (view != null) {
32 | InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
33 | imm.showSoftInput(view, 0);
34 | }
35 | }
36 |
37 | /**
38 | * 隐藏键盘
39 | *
40 | * @param context
41 | * @param view
42 | */
43 | public static void hideSoftInput(Context context, View view) {
44 | if (view != null) {
45 | InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
46 | imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.lock
4 | *.log
5 | *.pyc
6 | *.swp
7 | .DS_Store
8 | .atom/
9 | .buildlog/
10 | .history
11 | .svn/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # Visual Studio Code related
20 | .vscode/
21 |
22 | # Flutter/Dart/Pub related
23 | **/doc/api/
24 | .dart_tool/
25 | .flutter-plugins
26 | .packages
27 | .pub-cache/
28 | .pub/
29 | build/
30 |
31 | # Android related
32 | **/android/**/gradle-wrapper.jar
33 | **/android/.gradle
34 | **/android/captures/
35 | **/android/gradlew
36 | **/android/gradlew.bat
37 | **/android/local.properties
38 | **/android/**/GeneratedPluginRegistrant.java
39 |
40 | # iOS/XCode related
41 | **/ios/**/*.mode1v3
42 | **/ios/**/*.mode2v3
43 | **/ios/**/*.moved-aside
44 | **/ios/**/*.pbxuser
45 | **/ios/**/*.perspectivev3
46 | **/ios/**/*sync/
47 | **/ios/**/.sconsign.dblite
48 | **/ios/**/.tags*
49 | **/ios/**/.vagrant/
50 | **/ios/**/DerivedData/
51 | **/ios/**/Icon?
52 | **/ios/**/Pods/
53 | **/ios/**/.symlinks/
54 | **/ios/**/profile
55 | **/ios/**/xcuserdata
56 | **/ios/.generated/
57 | **/ios/Flutter/App.framework
58 | **/ios/Flutter/Flutter.framework
59 | **/ios/Flutter/Generated.xcconfig
60 | **/ios/Flutter/app.flx
61 | **/ios/Flutter/app.zip
62 | **/ios/Flutter/flutter_assets/
63 | **/ios/ServiceDefinitions.json
64 | **/ios/Runner/GeneratedPluginRegistrant.*
65 |
66 | # Exceptions to above rules.
67 | !**/ios/**/default.mode1v3
68 | !**/ios/**/default.mode2v3
69 | !**/ios/**/default.pbxuser
70 | !**/ios/**/default.perspectivev3
71 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
72 |
--------------------------------------------------------------------------------
/example/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 5391447fae6209bb21a89e6a5a6583cac1af9b4b
8 | channel: stable
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # im_leancloud_plugin_example
2 |
3 | Demonstrates how to use the im_leancloud_plugin plugin.
4 |
5 | ## Getting Started
6 |
7 | This project is a starting point for a Flutter application.
8 |
9 | A few resources to get you started if this is your first Flutter project:
10 |
11 | - [Lab: Write your first Flutter app](https://flutter.io/docs/get-started/codelab)
12 | - [Cookbook: Useful Flutter samples](https://flutter.io/docs/cookbook)
13 |
14 | For help getting started with Flutter, view our
15 | [online documentation](https://flutter.io/docs), which offers tutorials,
16 | samples, guidance on mobile development, and a full API reference.
17 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
26 |
27 | android {
28 | compileSdkVersion 27
29 |
30 | lintOptions {
31 | disable 'InvalidPackage'
32 | }
33 |
34 | defaultConfig {
35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
36 | applicationId "com.sisi.imleancloudpluginexample"
37 | minSdkVersion 16
38 | targetSdkVersion 27
39 | versionCode flutterVersionCode.toInteger()
40 | versionName flutterVersionName
41 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
42 | }
43 |
44 | buildTypes {
45 | release {
46 | // TODO: Add your own signing config for the release build.
47 | // Signing with the debug keys for now, so `flutter run --release` works.
48 | signingConfig signingConfigs.debug
49 | }
50 | }
51 | }
52 |
53 | flutter {
54 | source '../..'
55 | }
56 |
57 | dependencies {
58 | testImplementation 'junit:junit:4.12'
59 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
60 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
61 | implementation ('com.android.support:support-v4:27.1.1')
62 | implementation 'de.greenrobot:eventbus:2.4.0'
63 | // LeanCloud 基础包
64 | implementation ('cn.leancloud.android:avoscloud-sdk:4.7.9')
65 | // 推送与即时通讯需要的包
66 | implementation ('cn.leancloud.android:avoscloud-push:4.7.9@aar'){transitive = true}
67 | }
68 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
20 |
24 |
25 |
26 |
33 |
37 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/sisi/imleancloudpluginexample/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.sisi.imleancloudpluginexample;
2 |
3 | import android.os.Bundle;
4 |
5 | import com.avos.avoscloud.PushService;
6 | import com.avos.avoscloud.im.v2.AVIMClient;
7 |
8 | import io.flutter.app.FlutterActivity;
9 | import io.flutter.plugins.GeneratedPluginRegistrant;
10 |
11 | public class MainActivity extends FlutterActivity {
12 | @Override
13 | protected void onCreate(Bundle savedInstanceState) {
14 | super.onCreate(savedInstanceState);
15 | GeneratedPluginRegistrant.registerWith(this);
16 | //AVIMClient.setAutoOpen(true);
17 | //PushService.setDefaultPushCallback(this, MainActivity.class);
18 | //PushService.setAutoWakeUp(true);
19 | //PushService.setDefaultChannelId(this, "default");
20 |
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/app_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/android/app/src/main/res/drawable/app_icon.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/coworker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/android/app/src/main/res/drawable/coworker.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/food.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/android/app/src/main/res/drawable/food.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/me.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/android/app/src/main/res/drawable/me.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/sample_large_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/android/app/src/main/res/drawable/sample_large_icon.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/secondary_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/android/app/src/main/res/drawable/secondary_icon.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/start.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/android/app/src/main/res/mipmap-xhdpi/start.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/start.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/android/app/src/main/res/mipmap-xxhdpi/start.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/start.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/android/app/src/main/res/mipmap-xxxhdpi/start.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/raw/slow_spring_board.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/android/app/src/main/res/raw/slow_spring_board.mp3
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 | maven {
6 | url "http://mvn.leancloud.cn/nexus/content/repositories/public"
7 | }
8 | }
9 |
10 | dependencies {
11 | classpath 'com.android.tools.build:gradle:3.2.1'
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | google()
18 | jcenter()
19 | maven {
20 | url "http://mvn.leancloud.cn/nexus/content/repositories/public"
21 | }
22 | }
23 | }
24 |
25 | rootProject.buildDir = '../build'
26 | subprojects {
27 | project.buildDir = "${rootProject.buildDir}/${project.name}"
28 | }
29 | subprojects {
30 | project.evaluationDependsOn(':app')
31 | }
32 |
33 | task clean(type: Delete) {
34 | delete rootProject.buildDir
35 | }
36 |
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 |
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
7 |
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
4 |
5 | def plugins = new Properties()
6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
7 | if (pluginsFile.exists()) {
8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
9 | }
10 |
11 | plugins.each { name, path ->
12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
13 | include ":$name"
14 | project(":$name").projectDir = pluginDirectory
15 | }
16 |
--------------------------------------------------------------------------------
/example/assets/images/845c14ddd16f0af82b65a96c1fb318d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/assets/images/845c14ddd16f0af82b65a96c1fb318d.png
--------------------------------------------------------------------------------
/example/assets/images/c01b71ecef8d87d43d0d3f44aa9ef7b.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/assets/images/c01b71ecef8d87d43d0d3f44aa9ef7b.jpg
--------------------------------------------------------------------------------
/example/assets/images/chat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/assets/images/chat.png
--------------------------------------------------------------------------------
/example/assets/images/d2e8ab961b0e55fe856ff89e382a266.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/assets/images/d2e8ab961b0e55fe856ff89e382a266.png
--------------------------------------------------------------------------------
/example/assets/images/rchat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/assets/images/rchat.png
--------------------------------------------------------------------------------
/example/assets/images/start.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/assets/images/start.png
--------------------------------------------------------------------------------
/example/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 8.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
56 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
75 |
77 |
83 |
84 |
85 |
86 |
88 |
89 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface AppDelegate : FlutterAppDelegate
5 |
6 | @end
7 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.m:
--------------------------------------------------------------------------------
1 | #include "AppDelegate.h"
2 | #include "GeneratedPluginRegistrant.h"
3 |
4 | @implementation AppDelegate
5 |
6 | - (BOOL)application:(UIApplication *)application
7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
8 | [GeneratedPluginRegistrant registerWithRegistry:self];
9 | // Override point for customization after application launch.
10 | return [super application:application didFinishLaunchingWithOptions:launchOptions];
11 | }
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | im_leancloud_plugin_example
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | $(FLUTTER_BUILD_NAME)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UIViewControllerBasedStatusBarAppearance
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/example/ios/Runner/main.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 | #import "AppDelegate.h"
4 |
5 | int main(int argc, char* argv[]) {
6 | @autoreleasepool {
7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/example/lib/contact2.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'dart:convert';
3 | import 'user.dart';
4 | import 'sql/message.dart';
5 | import 'dart:async';
6 | import 'talk2.dart';
7 | import 'login.dart';
8 | import 'package:im_leancloud_plugin/im_leancloud_plugin.dart';
9 | import 'package:shared_preferences/shared_preferences.dart';
10 |
11 | class contact2 extends StatefulWidget {
12 | @override
13 | contact2State createState() => new contact2State();
14 | }
15 |
16 | class contact2State extends State {
17 | final TextEditingController EditingController = new TextEditingController();
18 | final TextEditingController textEditingController =
19 | new TextEditingController();
20 | final ScrollController listScrollController = new ScrollController();
21 | final StreamController> _streamController =
22 | StreamController>();
23 | ImLeancloudPlugin ImleancloudPlugin = ImLeancloudPlugin.getInstance();
24 | // List contents=List();
25 | final FocusNode focusNode = new FocusNode();
26 | List conversations = [];
27 |
28 | @override
29 | void initState() {
30 | initPlatformState();
31 | getcurrentUserConversation();
32 | super.initState();
33 | }
34 |
35 |
36 | Future onLoginClick(String currentUser) async {
37 | ImLeancloudPlugin ImleancloudPlugin = ImLeancloudPlugin.getInstance();
38 | ImleancloudPlugin.onLoginClick(currentUser);
39 | // bool islogin = await ImleancloudPlugin.onLoginClick(currentUser);
40 | // if (islogin) {
41 | // User.isloginLcchat = true;
42 | // }
43 | }
44 |
45 | Future getcurrentUserConversation() async {
46 | String jsonconversations =
47 | await ImleancloudPlugin.conversationList(User.currentUser);
48 | print(jsonconversations);
49 | List lconversation = json.decode(jsonconversations);
50 | conversations = lconversation;
51 | _streamController.sink.add(conversations);
52 | }
53 |
54 | // Platform messages are asynchronous, so we initialize in an async method.
55 | Future initPlatformState() async {
56 | ImleancloudPlugin.addEventHandler(
57 | //接收实时消息
58 | onReceiveMessage: (Map message) async {
59 | String content = json.decode(message['content'])['_lctext'];
60 | Message onReceiveMessage =
61 | new Message(content, message['getfrom'], message['conversationId']);
62 | },
63 | //网络状态重新连接
64 | onConnectionResume: (isResume) async {
65 | print(isResume);
66 | },
67 | //未读消息状态发生变化
68 | unRead: (Map unreadmessage) async {
69 | int unreadcount = unreadmessage['unreadcount'];
70 | String unReadConversationId = unreadmessage['conversationId'];
71 | print('unreadcount:$unreadcount');
72 | print('unReadConversationId:$unReadConversationId');
73 | String messages = await ImleancloudPlugin.queryUnreadMessages(
74 | unReadConversationId, unreadcount);
75 | print(messages);
76 | },
77 | );
78 | }
79 |
80 | Future savesqlConversation(String username) async {
81 | print(username);
82 | String conversationId =
83 | await ImleancloudPlugin.getConversation(User.currentUser, username);
84 | print('savesqlConversation:$conversationId');
85 | textEditingController.clear();
86 | getcurrentUserConversation();
87 | }
88 |
89 | String conversationName(List members) {
90 | if (members.length > 1) {
91 | if (User.currentUser == members[0]) {
92 | return members[1];
93 | } else {
94 | return members[0];
95 | }
96 | } else {
97 | return members[0];
98 | }
99 | }
100 |
101 | Future test1(String username) async {
102 | await savesqlConversation(username);
103 | getcurrentUserConversation();
104 | }
105 |
106 | int _lastClickTime = 0;
107 | bool btnShow = true;
108 |
109 | Future onBackPress() async {
110 | int nowTime = new DateTime.now().microsecondsSinceEpoch;
111 | if (_lastClickTime != 0 && nowTime - _lastClickTime > 1500) {
112 | return new Future.value(true);
113 | } else {
114 | _lastClickTime = new DateTime.now().microsecondsSinceEpoch;
115 | new Future.delayed(const Duration(milliseconds: 1500), () {
116 | _lastClickTime = 0;
117 | });
118 | return new Future.value(false);
119 | }
120 | }
121 |
122 | Future signout() async {
123 | SharedPreferences prefs = await SharedPreferences.getInstance();
124 | prefs.remove('currentUser');
125 | Navigator.push(
126 | context, MaterialPageRoute(builder: (context) => LoginPage()));
127 | await ImleancloudPlugin.signoutClick();
128 | }
129 |
130 | @override
131 | Widget build(BuildContext context) {
132 | return new Scaffold(
133 | appBar: AppBar(
134 | title: const Text('聊天测试程序'),
135 | actions: [
136 | IconButton(
137 | onPressed: signout,
138 | icon: Icon(Icons.delete),
139 | )
140 | ],
141 | ),
142 | body: WillPopScope(
143 | child: Column(
144 | children: [
145 | buildInput(),
146 | buildListConversation(),
147 | ],
148 | ),
149 | onWillPop: onBackPress,
150 | ),
151 | );
152 | }
153 |
154 | Widget buildInput() {
155 | return Container(
156 | child: Row(
157 | children: [
158 | // Edit text
159 | Flexible(
160 | child: Container(
161 | // margin: new EdgeInsets.symmetric(horizontal: 1.0),
162 | child: TextField(
163 | style: TextStyle(color: Colors.black54, fontSize: 18.0),
164 | controller: textEditingController,
165 | decoration: InputDecoration.collapsed(
166 | hintText: '输入发送对象',
167 | hintStyle: TextStyle(color: Colors.black38),
168 | ),
169 | focusNode: focusNode,
170 | //maxLines: 9,
171 | ),
172 | ),
173 | ),
174 |
175 | // Button send message
176 | Material(
177 | child: new Container(
178 | margin: new EdgeInsets.symmetric(horizontal: 8.0),
179 | child: new IconButton(
180 | icon: new Icon(Icons.send),
181 | onPressed: () => test1(textEditingController.text),
182 | color: Colors.blue,
183 | ),
184 | ),
185 | color: Colors.white,
186 | ),
187 | ],
188 | ),
189 | width: double.infinity,
190 | height: 50.0,
191 | decoration: new BoxDecoration(
192 | border: new Border(
193 | top: new BorderSide(color: Colors.black54, width: 0.5)),
194 | color: Colors.white),
195 | );
196 | }
197 |
198 | Widget buildListConversation() {
199 | return Flexible(
200 | child: StreamBuilder(
201 | stream: _streamController.stream,
202 | builder: (context, snapshot) {
203 | if (!snapshot.hasData) {
204 | return Center(
205 | child: CircularProgressIndicator(
206 | valueColor: AlwaysStoppedAnimation(Colors.blue)));
207 | } else {
208 | return ListView.builder(
209 | padding: EdgeInsets.all(10.0),
210 | //itemBuilder: (context, index) => buildItem(
211 | // index, snapshot.data[snapshot.data.length - index]),
212 | itemBuilder: (context, index) =>
213 | buildItem(index, snapshot.data[index]),
214 | itemCount: snapshot.data.length,
215 | //reverse: true,
216 | controller: listScrollController,
217 | );
218 | }
219 | },
220 | ),
221 | );
222 | }
223 |
224 | Widget buildItem(int index, Map detail) {
225 | String convesationname = conversationName(detail['getMembers']);
226 | return ListTile(
227 | title: Text(convesationname),
228 | onTap: () {
229 | Navigator.push(
230 | context,
231 | MaterialPageRoute(
232 | builder: (context) =>
233 | talk2(detail['conversationId'], convesationname)));
234 | });
235 | }
236 |
237 | @override
238 | void dispose() {
239 | super.dispose();
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/example/lib/custome_router.dart:
--------------------------------------------------------------------------------
1 | //路由动画设计
2 | import 'package:flutter/material.dart';
3 | class CustomeRout extends PageRouteBuilder {
4 | final Widget widget;
5 | CustomeRout(this.widget,double x)
6 | : super(
7 | transitionDuration: Duration(milliseconds: 600),
8 | pageBuilder: (
9 | BuildContext context,
10 | Animation animation1,
11 | Animation animation2,
12 | ) {
13 | return widget;
14 | },
15 | transitionsBuilder: (BuildContext context,
16 | Animation animation1,
17 | Animation animation2,
18 | Widget child) {
19 | return SlideTransition(
20 | position: Tween(
21 | begin: Offset(x, 0.0), end: Offset(0.0, 0.0))
22 | .animate(CurvedAnimation(
23 | parent: animation1, curve: Curves.fastOutSlowIn)),
24 | child: child,
25 | );
26 | });
27 | }
28 |
--------------------------------------------------------------------------------
/example/lib/login.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:shared_preferences/shared_preferences.dart';
3 | import 'package:im_leancloud_plugin/im_leancloud_plugin.dart';
4 | import 'user.dart';
5 |
6 | class LoginPage extends StatefulWidget {
7 | @override
8 | State createState() {
9 | return LoginPageState();
10 | }
11 | }
12 |
13 | class LoginPageState extends State {
14 | TextEditingController _usernameController = TextEditingController();
15 | TextEditingController _pwdController = TextEditingController();
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return Scaffold(
20 | backgroundColor: Colors.white,
21 | body: Center(
22 | child: loginBody(),
23 | ),
24 | );
25 | }
26 |
27 | loginBody() => SingleChildScrollView(
28 | child: Column(
29 | mainAxisAlignment: MainAxisAlignment.spaceAround,
30 | children: [loginHeader(), loginFields()],
31 | ),
32 | );
33 |
34 | loginHeader() => Column(
35 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
36 | children: [
37 | FlutterLogo(
38 | colors: Colors.green,
39 | size: 80.0,
40 | ),
41 | SizedBox(
42 | height: 30.0,
43 | ),
44 | Text(
45 | "欢迎使用Leancloud",
46 | style: TextStyle(fontWeight: FontWeight.w700, color: Colors.green),
47 | ),
48 | SizedBox(
49 | height: 5.0,
50 | ),
51 | Text(
52 | "登陆并继续",
53 | style: TextStyle(color: Colors.grey),
54 | ),
55 | ],
56 | );
57 |
58 | loginFields() => Container(
59 | child: Column(
60 | mainAxisAlignment: MainAxisAlignment.spaceAround,
61 | mainAxisSize: MainAxisSize.min,
62 | children: [
63 | Container(
64 | padding: EdgeInsets.symmetric(vertical: 16.0, horizontal: 30.0),
65 | child: TextField(
66 | controller: _usernameController,
67 | maxLines: 1,
68 | decoration: InputDecoration(
69 | hintText: "请输入用户名",
70 | labelText: "用户名",
71 | ),
72 | ),
73 | ),
74 | SizedBox(
75 | height: 30.0,
76 | ),
77 | Container(
78 | padding: EdgeInsets.symmetric(vertical: 0.0, horizontal: 30.0),
79 | width: double.infinity,
80 | child: RaisedButton(
81 | padding: EdgeInsets.all(12.0),
82 | shape: StadiumBorder(),
83 | child: Text(
84 | "登陆",
85 | style: TextStyle(color: Colors.white),
86 | ),
87 | color: Colors.green,
88 | onPressed: () {
89 | login(context);
90 | },
91 | ),
92 | ),
93 | ],
94 | ),
95 | );
96 |
97 | saveCurrentUser(String currentUser) async {
98 | SharedPreferences prefs = await SharedPreferences.getInstance();
99 | await prefs.setString('currentUser', currentUser);
100 | User.currentUser = currentUser;
101 | }
102 |
103 | Future onLoginClick(String currentUser) async {
104 | ImLeancloudPlugin ImleancloudPlugin = ImLeancloudPlugin.getInstance();
105 | // ImleancloudPlugin.onLoginClick(currentUser);
106 | bool islogin = await ImleancloudPlugin.onLoginClick(currentUser);
107 | if (islogin) {
108 | User.isloginLcchat = true;
109 | }
110 | }
111 |
112 | void login(BuildContext context) async {
113 | await saveCurrentUser(_usernameController.text);
114 | onLoginClick(_usernameController.text);
115 | Navigator.of(context).pushReplacementNamed('/contact');
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'dart:async';
3 | import 'dart:convert';
4 | import 'package:flutter/services.dart';
5 | import 'package:flutter/cupertino.dart';
6 | import 'package:flutter_local_notifications/flutter_local_notifications.dart';
7 | import 'package:im_leancloud_plugin/im_leancloud_plugin.dart';
8 | import 'package:shared_preferences/shared_preferences.dart';
9 | import 'sql/conversation.dart';
10 | import 'sql/sql.dart';
11 | import 'sql/message.dart';
12 | import 'login.dart';
13 | import 'contact.dart';
14 | import 'user.dart';
15 | //import 'contact2.dart';
16 |
17 | void main() {
18 | runApp(new MaterialApp(
19 | title: 'LeanCloud 即时通讯',
20 | theme: new ThemeData(
21 | primarySwatch: Colors.blue,
22 | ),
23 | routes: {
24 | '/contact': (BuildContext context) => contact(),
25 | '/login': (BuildContext context) => LoginPage(),
26 | },
27 | home: new MyApp(),
28 | ));
29 | }
30 |
31 | class MyApp extends StatefulWidget {
32 | @override
33 | _MyAppState createState() => _MyAppState();
34 | }
35 |
36 | class _MyAppState extends State {
37 | bool logined = false;
38 | String _platformVersion = 'Unknown';
39 | ImLeancloudPlugin ImleancloudPlugin = ImLeancloudPlugin.getInstance();
40 | sql db = new sql();
41 | ConversationSqlite dbc = new ConversationSqlite();
42 | var flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
43 |
44 | @override
45 | void initState() {
46 | localNotification();
47 | initApp();
48 | super.initState();
49 | }
50 |
51 | Future _islogin() async {
52 | SharedPreferences prefs = await SharedPreferences.getInstance();
53 | String currentUser = prefs.getString('currentUser');
54 | if (currentUser == null) {
55 | Navigator.of(context).pushReplacementNamed('/login');
56 | } else {
57 | User.currentUser = currentUser;
58 | logined = true;
59 | await dbc.getcurrentUserConversation(currentUser);
60 | }
61 | }
62 |
63 | //这个和消息状态栏通知有关
64 | void localNotification() {
65 | var initializationSettingsAndroid =
66 | new AndroidInitializationSettings('app_icon');
67 | var initializationSettingsIOS = new IOSInitializationSettings(
68 | onDidReceiveLocalNotification: onDidRecieveLocalNotification);
69 | var initializationSettings = new InitializationSettings(
70 | initializationSettingsAndroid, initializationSettingsIOS);
71 | flutterLocalNotificationsPlugin.initialize(initializationSettings,
72 | onSelectNotification: onSelectNotification);
73 | }
74 |
75 | //这个和消息状态栏通知有关
76 | Future _showNotification(String getfrom, String content) async {
77 | var androidPlatformChannelSpecifics = new AndroidNotificationDetails(
78 | 'your channel id', 'your channel name', 'your channel description',
79 | importance: Importance.Max, priority: Priority.High);
80 | var iOSPlatformChannelSpecifics = new IOSNotificationDetails();
81 | var platformChannelSpecifics = new NotificationDetails(
82 | androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
83 | await flutterLocalNotificationsPlugin
84 | .show(0, getfrom, content, platformChannelSpecifics, payload: 'item x');
85 | }
86 |
87 | Future onLoginClick() async {
88 | if (logined) {
89 | // await ImleancloudPlugin.onLoginClick(User.currentUser);
90 | bool logining = await ImleancloudPlugin.onLoginClick(User.currentUser);
91 | if (logining) {
92 | User.isloginLcchat = true;
93 | }
94 | print('网络登陆状态:$logining');
95 | Navigator.of(context).pushReplacementNamed('/contact');
96 | }
97 | }
98 |
99 | Future initApp() async {
100 | await _islogin();
101 | await initLeancloud();
102 | initPlatformState();
103 | onLoginClick();
104 | }
105 |
106 | void initLeancloud() {
107 | String appId = ""; //输入设置当前Leancloud appId
108 | String appKey = ""; //输入设置当前Leancloud appKey
109 | ImleancloudPlugin.initialize(appId, appKey);
110 | }
111 |
112 | void conversationRead() {
113 | ImleancloudPlugin.conversationRead();
114 | }
115 |
116 | //保存新的会话
117 | Future saveNewconversation(
118 | String conversationId, String username) async {
119 | bool isconversationExist = await dbc.conversationExist(username);
120 | if (isconversationExist == false) {
121 | Conversation conversation =
122 | new Conversation(User.currentUser, conversationId, username);
123 | await dbc.insert(conversation);
124 | } else {
125 | print('会话已经存在');
126 | }
127 | await dbc.close();
128 | }
129 |
130 | //获取接收过来的会话里对方用户名,只有两人会话的情况
131 | String conversationName(List members) {
132 | if (members.length > 1) {
133 | if (User.currentUser == members[0]) {
134 | return members[1];
135 | } else {
136 | return members[0];
137 | }
138 | } else {
139 | return members[0];
140 | }
141 | }
142 |
143 | //处理未读消息
144 | Future Unreadconversation(String jsonUnread) async {
145 | Map mapUnread = json.decode(jsonUnread);
146 | List members = mapUnread['getMembers'];
147 | String username = conversationName(members);
148 | String conversationId = mapUnread['conversationId'];
149 | await saveNewconversation(conversationId, username);
150 | _showNotification(username, '有未读消息');
151 | }
152 |
153 | //更新LastDelivered
154 | Future updateLastDelivered(String jsonLastDelivered) async {
155 | Map mapLastdelivered = json.decode(jsonLastDelivered);
156 | String conversationId = mapLastdelivered['conversationId'];
157 | int LastDelivered = mapLastdelivered['LastDelivered'];
158 | await dbc.updateLastDelivered(conversationId, LastDelivered);
159 | dbc.close();
160 | }
161 |
162 | // Platform messages are asynchronous, so we initialize in an async method.
163 | Future initPlatformState() async {
164 | String platformVersion;
165 | // Platform messages may fail, so we use a try/catch PlatformException.
166 | try {
167 | platformVersion = await ImLeancloudPlugin.platformVersion;
168 | ImleancloudPlugin.addEventHandler(
169 | //接收实时消息
170 | onReceiveMessage: (Map message) async {
171 | print('这是main文件里面传来的消息');
172 | String content = json.decode(message['content'])['_lctext'];
173 | Message onReceiveMessage = new Message(
174 | content, message['getfrom'], message['conversationId']);
175 | _showNotification(message['getfrom'], content);
176 | int res = await db.saveMessage(onReceiveMessage);
177 | print(res);
178 | saveNewconversation(message['conversationId'], message['getfrom']);
179 | await dbc.sequenceConversation(
180 | message['conversationId'], message['Timestamp']);
181 | dbc.close();
182 | },
183 | //网络状态重新连接
184 | onConnectionResume: (isResume) async {
185 | print(isResume);
186 | },
187 | //未读消息状态发生变化
188 | unRead: (Map unreadmessage) async {
189 | print(unreadmessage);
190 | String jsonUnread = unreadmessage['unRead'];
191 | await dbc.getcurrentUserConversation;
192 | await Unreadconversation(jsonUnread);
193 | },
194 | onLastReadAtUpdated: (Map lastreadat) async {
195 | print('更新最后读取时间');
196 | print(lastreadat);
197 | },
198 | onLastDeliveredAtUpdated: (Map lastdeliver) async {
199 | print('更新会话列表');
200 | print(lastdeliver);
201 | String jsonLastDelivered = lastdeliver['LastDeliveredAt'];
202 | updateLastDelivered(jsonLastDelivered);
203 | },
204 | );
205 | } on PlatformException {
206 | platformVersion = '获取平台版本失败';
207 | }
208 |
209 | // If the widget was removed from the tree while the asynchronous platform
210 | // message was in flight, we want to discard the reply rather than calling
211 | // setState to update our non-existent appearance.
212 | if (!mounted) return;
213 | }
214 |
215 | @override
216 | Widget build(BuildContext context) {
217 | return Scaffold(
218 | body: Center(
219 | child: Image.asset('assets/images/start.png'),
220 | ),
221 | );
222 | }
223 |
224 | Future onSelectNotification(String payload) async {
225 | if (payload != null) {
226 | debugPrint('notification payload: ' + payload);
227 | }
228 |
229 | await Navigator.push(
230 | context,
231 | new MaterialPageRoute(builder: (context) => new SecondScreen(payload)),
232 | );
233 | }
234 |
235 | Future onDidRecieveLocalNotification(
236 | int id, String title, String body, String payload) async {
237 | // display a dialog with the notification details, tap ok to go to another page
238 | showDialog(
239 | context: context,
240 | builder: (BuildContext context) => new CupertinoAlertDialog(
241 | title: new Text(title),
242 | content: new Text(body),
243 | actions: [
244 | CupertinoDialogAction(
245 | isDefaultAction: true,
246 | child: new Text('Ok'),
247 | onPressed: () async {
248 | Navigator.of(context, rootNavigator: true).pop();
249 | await Navigator.push(
250 | context,
251 | new MaterialPageRoute(
252 | builder: (context) => new SecondScreen(payload),
253 | ),
254 | );
255 | },
256 | )
257 | ],
258 | ),
259 | );
260 | }
261 | }
262 |
263 | class SecondScreen extends StatefulWidget {
264 | final String payload;
265 | SecondScreen(this.payload);
266 | @override
267 | State createState() => new SecondScreenState();
268 | }
269 |
270 | class SecondScreenState extends State {
271 | String _payload;
272 | @override
273 | void initState() {
274 | super.initState();
275 | _payload = widget.payload;
276 | }
277 |
278 | @override
279 | Widget build(BuildContext context) {
280 | return new Scaffold(
281 | appBar: new AppBar(
282 | title: new Text("Second Screen with payload: " + _payload),
283 | ),
284 | body: new Center(
285 | child: new RaisedButton(
286 | onPressed: () {
287 | Navigator.pop(context);
288 | },
289 | child: new Text('Go back!'),
290 | ),
291 | ),
292 | );
293 | }
294 | }
295 |
--------------------------------------------------------------------------------
/example/lib/refresh_list_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'dart:async';
3 |
4 | typedef Future RefreshListViewOnRefreshCallback();
5 | typedef void LoadMoreCallback(); // 滑动到达底部了
6 |
7 | /// 传进来的ListView初始化时,必须指定一个Controller
8 | class RefreshListView extends StatefulWidget {
9 | RefreshListView(
10 | {Key key,
11 | @required this.listView,
12 | this.onRefreshCallback,
13 | this.offset,
14 | this.loadMoreCallback,
15 | this.pullToRefresh = true}): super(key: key);
16 |
17 | ListView listView;
18 | double offset = 0.0; // 距离底部还有多少距离时开始刷新,默认为0,要完全到底部才开始刷新
19 | bool pullToRefresh = true; // 是否开启下拉刷新, 默认为true
20 |
21 | RefreshListViewOnRefreshCallback onRefreshCallback;
22 | LoadMoreCallback loadMoreCallback;
23 |
24 | @override
25 | State createState() {
26 | return RefreshListViewState();
27 | }
28 | }
29 |
30 | class RefreshListViewState extends State {
31 | final GlobalKey refreshIndicatorKey =
32 | new GlobalKey();
33 |
34 | void beginRefresh() {
35 | refreshIndicatorKey.currentState.show();
36 | }
37 |
38 | @override
39 | void initState() {
40 | // 监听ListView是否滑动到底部
41 | widget.listView.controller.addListener(() {
42 |
43 | if (widget.listView.controller.position.pixels ==
44 | widget.listView.controller.position.maxScrollExtent) {
45 |
46 | // 滑动到最底部了
47 | if (widget.loadMoreCallback != null) {
48 | widget.loadMoreCallback();
49 | }
50 | }
51 | });
52 |
53 | super.initState();
54 | }
55 |
56 | @override
57 | Widget build(BuildContext context) {
58 | if (widget.pullToRefresh) {
59 | // 开启下拉刷新时,onRefreshCallback不能为空
60 | // assert(widget.onRefreshCallback != null);
61 | return RefreshIndicator(
62 | key: refreshIndicatorKey,
63 | child: widget.listView,
64 | onRefresh: widget.onRefreshCallback,
65 | );
66 | } else {
67 | return widget.listView;
68 | }
69 | }
70 | }
71 |
72 |
73 |
--------------------------------------------------------------------------------
/example/lib/sql/conversation.dart:
--------------------------------------------------------------------------------
1 | import 'package:path/path.dart';
2 | import 'package:sqflite/sqflite.dart';
3 | import '../user.dart';
4 |
5 | final String tableConversation = 'conversation';
6 | final String columnId = '_id';
7 | final String columnCurrentUser = 'currentUser';
8 | final String columnConversationId = 'conversationId';
9 | final String columnUsername = 'username';
10 | final String columnLastReadAt = 'LastReadAt';
11 | final String columnUpdateAt = 'UpdateAt';
12 | final String columLastDelivered = 'LastDelivered';
13 |
14 | class Conversation {
15 | String currentUser;
16 | String conversationId;
17 | String username;
18 | int LastReadAt;
19 | int UpdateAt;
20 | int LastDelivered;
21 | int id;
22 |
23 | Conversation(this.currentUser, this.conversationId, this.username,
24 | {this.LastReadAt, this.UpdateAt, this.LastDelivered, this.id});
25 |
26 | Conversation.fromMap(Map obj) {
27 | this.currentUser = obj[columnCurrentUser];
28 | this.conversationId = obj[columnConversationId];
29 | this.username = obj[columnUsername];
30 | this.LastReadAt = obj[columnLastReadAt];
31 | this.UpdateAt = obj[columnUpdateAt];
32 | this.LastDelivered = obj[columLastDelivered];
33 | this.id = obj[columnId];
34 | }
35 |
36 | Map toMap() {
37 | var map = {
38 | columnCurrentUser: this.currentUser,
39 | columnConversationId: this.conversationId,
40 | columnUsername: this.username,
41 | };
42 |
43 | if (LastReadAt != null) {
44 | map[columnLastReadAt] = this.LastReadAt;
45 | } else {
46 | map[columnLastReadAt] = 0;
47 | }
48 |
49 | if (UpdateAt != null) {
50 | map[columnUpdateAt] = this.UpdateAt;
51 | } else {
52 | map[columnUpdateAt] = 0;
53 | }
54 |
55 | if (LastDelivered != null) {
56 | map[columLastDelivered] = this.LastDelivered;
57 | } else {
58 | map[columLastDelivered] = 0;
59 | }
60 |
61 | if (id != null) {
62 | map[columnId] = this.id;
63 | }
64 |
65 | return map;
66 | }
67 |
68 | //对Map列表进行排序,按照键值key从大到小排序
69 | static List