├── .gitignore ├── .idea ├── libraries │ ├── Dart_SDK.xml │ ├── Flutter_Plugins.xml │ └── Flutter_for_Android.xml ├── misc.xml ├── modules.xml ├── runConfigurations │ └── example_lib_main_dart.xml └── workspace.xml ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── sisi │ └── imleancloudplugin │ ├── CustomUserProvider.java │ ├── ImLeancloudPlugin.java │ ├── LCChatKit.java │ ├── LCChatKitUser.java │ ├── LCChatProfileProvider.java │ ├── LCChatProfilesCallBack.java │ ├── LeancloudArgsConverter.java │ ├── LeancloudFunction.java │ ├── LeancloudMessage.java │ ├── cache │ ├── LCIMConversationItem.java │ ├── LCIMConversationItemCache.java │ ├── LCIMLocalCacheUtils.java │ ├── LCIMLocalStorage.java │ └── LCIMProfileCache.java │ ├── event │ ├── LCIMConnectionChangeEvent.java │ ├── LCIMConversationItemLongClickEvent.java │ ├── LCIMConversationReadStatusEvent.java │ ├── LCIMIMTypeMessageEvent.java │ ├── LCIMInputBottomBarEvent.java │ ├── LCIMInputBottomBarLocationClickEvent.java │ ├── LCIMInputBottomBarRecordEvent.java │ ├── LCIMInputBottomBarTextEvent.java │ ├── LCIMLocationItemClickEvent.java │ ├── LCIMMemberLetterEvent.java │ ├── LCIMMemberSelectedChangeEvent.java │ ├── LCIMMessageResendEvent.java │ ├── LCIMMessageUpdateEvent.java │ ├── LCIMMessageUpdatedEvent.java │ └── LCIMOfflineMessageCountChangeEvent.java │ ├── handler │ ├── LCIMClientEventHandler.java │ ├── LCIMConversationHandler.java │ └── LCIMMessageHandler.java │ └── utils │ ├── LCIMAudioHelper.java │ ├── LCIMConstants.java │ ├── LCIMConversationUtils.java │ ├── LCIMLogUtils.java │ ├── LCIMNotificationUtils.java │ ├── LCIMPathUtils.java │ └── LCIMSoftInputUtils.java ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── sisi │ │ │ │ └── imleancloudpluginexample │ │ │ │ └── MainActivity.java │ │ │ └── res │ │ │ ├── drawable │ │ │ ├── app_icon.png │ │ │ ├── coworker.png │ │ │ ├── food.png │ │ │ ├── launch_background.xml │ │ │ ├── me.png │ │ │ ├── sample_large_icon.png │ │ │ └── secondary_icon.png │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── start.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── start.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── start.png │ │ │ ├── raw │ │ │ └── slow_spring_board.mp3 │ │ │ └── values │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── assets │ └── images │ │ ├── 845c14ddd16f0af82b65a96c1fb318d.png │ │ ├── c01b71ecef8d87d43d0d3f44aa9ef7b.jpg │ │ ├── chat.png │ │ ├── d2e8ab961b0e55fe856ff89e382a266.png │ │ ├── rchat.png │ │ └── start.png ├── ios │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ └── contents.xcworkspacedata │ └── Runner │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── main.m ├── lib │ ├── contact.dart │ ├── contact2.dart │ ├── custome_router.dart │ ├── login.dart │ ├── main.dart │ ├── refresh_list_view.dart │ ├── sql │ │ ├── conversation.dart │ │ ├── message.dart │ │ └── sql.dart │ ├── talk.dart │ ├── talk2.dart │ └── user.dart ├── pubspec.yaml └── test │ └── widget_test.dart ├── im_leancloud_plugin.iml ├── im_leancloud_plugin_android.iml ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── ImLeancloudPlugin.h │ └── ImLeancloudPlugin.m └── im_leancloud_plugin.podspec ├── lib ├── AVIMMessage.dart └── im_leancloud_plugin.dart └── pubspec.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | pubspec.lock 7 | 8 | build/ 9 | -------------------------------------------------------------------------------- /.idea/libraries/Dart_SDK.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/libraries/Flutter_Plugins.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/libraries/Flutter_for_Android.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/runConfigurations/example_lib_main_dart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.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: plugin 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | TODO: Add your license here. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # im_leancloud_flutter_plugin 2 | 3 | im flutter plugin for leancloud. 4 | 5 | Only support Android!!! 6 | 7 | ## Getting Started 8 | 9 | This project is a starting point for a Flutter 10 | [plug-in package](https://flutter.io/developing-packages/), 11 | a specialized package that includes platform-specific implementation code for 12 | Android. 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 | 18 | ## Application screenshot 19 | 20 | ![image](https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/master/example/assets/images/d2e8ab961b0e55fe856ff89e382a266.png) 21 | 22 | ![image](https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/master/example/assets/images/845c14ddd16f0af82b65a96c1fb318d.png) 23 | 24 | ![image](https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/master/example/assets/images/c01b71ecef8d87d43d0d3f44aa9ef7b.jpg) 25 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.sisi.imleancloudplugin' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | repositories { 6 | google() 7 | jcenter() 8 | maven { 9 | url "http://mvn.leancloud.cn/nexus/content/repositories/public" 10 | } 11 | } 12 | 13 | dependencies { 14 | classpath 'com.android.tools.build:gradle:3.2.1' 15 | } 16 | } 17 | 18 | rootProject.allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | maven { 23 | url "http://mvn.leancloud.cn/nexus/content/repositories/public" 24 | } 25 | 26 | } 27 | } 28 | 29 | apply plugin: 'com.android.library' 30 | 31 | android { 32 | compileSdkVersion 27 33 | 34 | defaultConfig { 35 | minSdkVersion 16 36 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 37 | } 38 | lintOptions { 39 | disable 'InvalidPackage' 40 | } 41 | } 42 | dependencies { 43 | implementation ('com.android.support:support-v4:27.1.1') 44 | implementation 'de.greenrobot:eventbus:2.4.0' 45 | 46 | // LeanCloud 基础包 47 | implementation ('cn.leancloud.android:avoscloud-sdk:4.7.9') 48 | 49 | // 推送与即时通讯需要的包 50 | implementation ('cn.leancloud.android:avoscloud-push:4.7.9@aar'){transitive = true} 51 | } 52 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'im_leancloud_plugin' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/java/com/sisi/imleancloudplugin/CustomUserProvider.java: -------------------------------------------------------------------------------- 1 | package com.sisi.imleancloudplugin; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.sisi.imleancloudplugin.LCChatKitUser; 7 | import com.sisi.imleancloudplugin.LCChatProfileProvider; 8 | import com.sisi.imleancloudplugin.LCChatProfilesCallBack; 9 | 10 | /** 11 | * Created by wli on 15/12/4. 12 | * 实现自定义用户体系 13 | */ 14 | public class CustomUserProvider implements LCChatProfileProvider { 15 | 16 | private static CustomUserProvider customUserProvider; 17 | 18 | public synchronized static CustomUserProvider getInstance() { 19 | if (null == customUserProvider) { 20 | customUserProvider = new CustomUserProvider(); 21 | } 22 | return customUserProvider; 23 | } 24 | 25 | private CustomUserProvider() { 26 | } 27 | 28 | private static List partUsers = new ArrayList(); 29 | 30 | // 此数据均为 fake,仅供参考 31 | static { 32 | partUsers.add(new LCChatKitUser("Tom", "Tom", "http://www.avatarsdb.com/avatars/tom_and_jerry2.jpg")); 33 | partUsers.add(new LCChatKitUser("Jerry", "Jerry", "http://www.avatarsdb.com/avatars/jerry.jpg")); 34 | partUsers.add(new LCChatKitUser("Harry", "Harry", "http://www.avatarsdb.com/avatars/young_harry.jpg")); 35 | partUsers.add(new LCChatKitUser("William", "William", "http://www.avatarsdb.com/avatars/william_shakespeare.jpg")); 36 | partUsers.add(new LCChatKitUser("Bob", "Bob", "http://www.avatarsdb.com/avatars/bath_bob.jpg")); 37 | } 38 | 39 | @Override 40 | public void fetchProfiles(List list, LCChatProfilesCallBack callBack) { 41 | List userList = new ArrayList(); 42 | for (String userId : list) { 43 | for (LCChatKitUser user : partUsers) { 44 | if (user.getUserId().equals(userId)) { 45 | userList.add(user); 46 | break; 47 | } 48 | } 49 | } 50 | callBack.done(userList, null); 51 | } 52 | 53 | @Override 54 | public List getAllUsers() { 55 | return partUsers; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /android/src/main/java/com/sisi/imleancloudplugin/ImLeancloudPlugin.java: -------------------------------------------------------------------------------- 1 | package com.sisi.imleancloudplugin; 2 | 3 | import android.content.Context; 4 | 5 | import com.alibaba.fastjson.JSON; 6 | import com.avos.avoscloud.im.v2.AVIMConversation; 7 | import com.avos.avoscloud.im.v2.AVIMTypedMessage; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | import io.flutter.plugin.common.MethodCall; 13 | import io.flutter.plugin.common.MethodChannel; 14 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler; 15 | import io.flutter.plugin.common.MethodChannel.Result; 16 | import io.flutter.plugin.common.PluginRegistry.Registrar; 17 | 18 | 19 | /** 20 | * ImLeancloudPlugin 21 | */ 22 | public class ImLeancloudPlugin implements MethodCallHandler { 23 | private static Context _applicationContext; 24 | 25 | /** 26 | * Plugin registration. 27 | */ 28 | public static void registerWith(Registrar registrar) { 29 | final MethodChannel channel = new MethodChannel(registrar.messenger(), "im_leancloud_plugin"); 30 | channel.setMethodCallHandler(new ImLeancloudPlugin(registrar, channel)); 31 | _applicationContext = registrar.context().getApplicationContext(); 32 | } 33 | 34 | public static ImLeancloudPlugin instance; 35 | private final Registrar registrar; 36 | public final MethodChannel channel; 37 | 38 | private ImLeancloudPlugin(Registrar registrar, MethodChannel channel) { 39 | this.registrar = registrar; 40 | this.channel = channel; 41 | instance = this; 42 | } 43 | 44 | 45 | @Override 46 | public void onMethodCall(MethodCall call, Result result) { 47 | 48 | switch (call.method) { 49 | case "getPlatformVersion": 50 | result.success("Android " + android.os.Build.VERSION.RELEASE); 51 | break; 52 | case "initialize": 53 | LeancloudFunction.initialize(call, result, _applicationContext); 54 | break; 55 | case "onLoginClick": 56 | LeancloudFunction.onLoginClick(call, result); 57 | break; 58 | case "setLogLevel": 59 | LeancloudFunction.setLogLevel(call, result); 60 | break; 61 | case "uploadFile": 62 | LeancloudFunction.uploadFile(call, result); 63 | break; 64 | case "getConversation": 65 | LeancloudMessage.getConversation(call, result); 66 | break; 67 | case "sendText": 68 | LeancloudMessage.sendText(call, result); 69 | break; 70 | case "sendImage": 71 | LeancloudMessage.sendImage(call, result); 72 | break; 73 | case "sendAudio": 74 | LeancloudMessage.sendAudio(call, result); 75 | break; 76 | case "sendVideo": 77 | LeancloudMessage.sendVideo(call, result); 78 | break; 79 | case "conversationRead": 80 | LeancloudMessage.conversationRead(); 81 | break; 82 | case "queryUnreadMessages": 83 | LeancloudMessage.queryUnreadMessages(call, result); 84 | break; 85 | case "queryHistoryMessages": 86 | LeancloudMessage.queryHistoryMessages(call, result); 87 | break; 88 | case "signoutClick": 89 | LeancloudFunction.signoutClick(); 90 | break; 91 | case "conversationList": 92 | LeancloudMessage.conversationList(call, result); 93 | break; 94 | default: 95 | result.notImplemented(); 96 | } 97 | 98 | 99 | } 100 | 101 | //接收消息 102 | public void onReceiveMessage(AVIMTypedMessage message) { 103 | 104 | Map notification = new HashMap<>(); 105 | notification.put("conversationId", message.getConversationId()); 106 | notification.put("content", message.getContent()); 107 | notification.put("getfrom", message.getFrom()); 108 | notification.put("Timestamp",message.getTimestamp()); 109 | notification.put("MessageType",message.getMessageType()); 110 | ImLeancloudPlugin.instance.channel.invokeMethod("onReceiveMessage", notification); 111 | 112 | } 113 | 114 | //unRead传送未读的会话以及数目 115 | public void unRead(AVIMConversation conv) { 116 | Map convmap = new HashMap<>(); 117 | convmap.put("conversationId", conv.getConversationId()); 118 | convmap.put("UnreadMessagesCount", conv.getUnreadMessagesCount()); 119 | convmap.put("getMembers", conv.getMembers()); 120 | convmap.put("getName", conv.getName()); 121 | String jsonconversation = JSON.toJSONString(convmap); 122 | 123 | 124 | Map unReadmap = new HashMap<>(); 125 | unReadmap.put("unRead", jsonconversation); 126 | ImLeancloudPlugin.instance.channel.invokeMethod("unRead", unReadmap); 127 | } 128 | 129 | //用于判断是否已读 130 | public void onLastReadAtUpdated(AVIMConversation conv) { 131 | Map convmap = new HashMap<>(); 132 | convmap.put("conversationId", conv.getConversationId()); 133 | convmap.put("getMembers", conv.getMembers()); 134 | convmap.put("LastReadAt", conv.getLastReadAt()); 135 | convmap.put("getName", conv.getName()); 136 | String jsonconversation = JSON.toJSONString(convmap); 137 | 138 | Map LastReadAtmap = new HashMap<>(); 139 | LastReadAtmap.put("LastReadAt", jsonconversation); 140 | ImLeancloudPlugin.instance.channel.invokeMethod("onLastReadAtUpdated", LastReadAtmap); 141 | } 142 | //更新会话接收时间 143 | public void onLastDeliveredAtUpdated(AVIMConversation conv) { 144 | Map convmap = new HashMap<>(); 145 | convmap.put("conversationId", conv.getConversationId()); 146 | convmap.put("getMembers", conv.getMembers()); 147 | convmap.put("LastDelivered", conv.getLastDeliveredAt()); 148 | convmap.put("getName", conv.getName()); 149 | String jsonconversation = JSON.toJSONString(convmap); 150 | 151 | Map LastDeliveredmap = new HashMap<>(); 152 | LastDeliveredmap.put("LastDeliveredAt", jsonconversation); 153 | ImLeancloudPlugin.instance.channel.invokeMethod("onLastDeliveredAtUpdated", LastDeliveredmap); 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /android/src/main/java/com/sisi/imleancloudplugin/LCChatKit.java: -------------------------------------------------------------------------------- 1 | package com.sisi.imleancloudplugin; 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 | import com.avos.avoscloud.AVOSCloud; 9 | import com.avos.avoscloud.AVUtils; 10 | import com.avos.avoscloud.SignatureFactory; 11 | import com.avos.avoscloud.im.v2.AVIMClient; 12 | import com.avos.avoscloud.im.v2.AVIMOptions; 13 | import com.avos.avoscloud.im.v2.AVIMException; 14 | import com.avos.avoscloud.im.v2.AVIMMessageManager; 15 | import com.avos.avoscloud.im.v2.AVIMTypedMessage; 16 | import com.avos.avoscloud.im.v2.callback.AVIMClientCallback; 17 | 18 | import com.sisi.imleancloudplugin.cache.LCIMConversationItemCache; 19 | import com.sisi.imleancloudplugin.cache.LCIMProfileCache; 20 | import com.sisi.imleancloudplugin.handler.LCIMClientEventHandler; 21 | import com.sisi.imleancloudplugin.handler.LCIMConversationHandler; 22 | import com.sisi.imleancloudplugin.handler.LCIMMessageHandler; 23 | 24 | /** 25 | * Created by wli on 16/2/2. 26 | * LeanCloudChatKit 的管理类 27 | */ 28 | public final class LCChatKit { 29 | 30 | private static LCChatKit lcChatKit; 31 | private LCChatProfileProvider profileProvider; 32 | private String currentUserId; 33 | 34 | private LCChatKit() { 35 | } 36 | 37 | public static synchronized LCChatKit getInstance() { 38 | if (null == lcChatKit) { 39 | lcChatKit = new LCChatKit(); 40 | } 41 | return lcChatKit; 42 | } 43 | 44 | /** 45 | * 初始化 LeanCloudChatKit,此函数要在 Application 的 onCreate 中调用 46 | * 47 | * @param context 48 | * @param appId 49 | * @param appKey 50 | */ 51 | public void init(Context context, String appId, String appKey) { 52 | if (TextUtils.isEmpty(appId)) { 53 | throw new IllegalArgumentException("appId can not be empty!"); 54 | } 55 | if (TextUtils.isEmpty(appKey)) { 56 | throw new IllegalArgumentException("appKey can not be empty!"); 57 | } 58 | 59 | AVOSCloud.initialize(context.getApplicationContext(), appId, appKey); 60 | //AVIMClient.setAutoOpen(true); 61 | //PushService.setAutoWakeUp(true); 62 | //PushService.setDefaultChannelId(context.getApplicationContext(), "default"); 63 | 64 | // 消息处理 handler 65 | AVIMMessageManager.registerMessageHandler(AVIMTypedMessage.class, new LCIMMessageHandler(context)); 66 | 67 | // 与网络相关的 handler 68 | AVIMClient.setClientEventHandler(LCIMClientEventHandler.getInstance()); 69 | AVIMOptions.getGlobalOptions().setResetConnectionWhileBroken(true); 70 | 71 | // 和 Conversation 相关的事件的 handler 72 | AVIMMessageManager.setConversationEventHandler(LCIMConversationHandler.getInstance()); 73 | 74 | AVIMClient.setUnreadNotificationEnabled(true); 75 | 76 | // 默认设置为离线消息仅推送数量 77 | AVIMClient.setOfflineMessagePush(true); 78 | } 79 | 80 | /** 81 | * 设置用户体系 82 | * 83 | * @param profileProvider 84 | */ 85 | public void setProfileProvider(LCChatProfileProvider profileProvider) { 86 | this.profileProvider = profileProvider; 87 | } 88 | 89 | /** 90 | * 获取当前的用户体系 91 | * 92 | * @return 93 | */ 94 | public LCChatProfileProvider getProfileProvider() { 95 | return profileProvider; 96 | } 97 | 98 | /** 99 | * 设置签名工厂 100 | * 101 | * @param signatureFactory 102 | */ 103 | public void setSignatureFactory(SignatureFactory signatureFactory) { 104 | AVIMClient.setSignatureFactory(signatureFactory); 105 | } 106 | 107 | /** 108 | * 开启实时聊天 109 | * 110 | * @param userId 111 | * @param callback 112 | */ 113 | public void open(final String userId, final AVIMClientCallback callback) { 114 | open(userId, null, callback); 115 | } 116 | 117 | /** 118 | * 开启实时聊天 119 | * 120 | * @param userId 实时聊天的 clientId 121 | * @param tag 单点登录标示 122 | * @param callback 123 | */ 124 | public void open(final String userId, String tag, final AVIMClientCallback callback) { 125 | if (TextUtils.isEmpty(userId)) { 126 | throw new IllegalArgumentException("userId 不能为空!"); 127 | } 128 | if (null == callback) { 129 | throw new IllegalArgumentException("callback 不能为空!"); 130 | } 131 | 132 | AVIMClientCallback openCallback = new AVIMClientCallback() { 133 | @Override 134 | public void done(final AVIMClient avimClient, AVIMException e) { 135 | if (null == e) { 136 | currentUserId = userId; 137 | LCIMProfileCache.getInstance().initDB(AVOSCloud.applicationContext, userId); 138 | LCIMConversationItemCache.getInstance().initDB(AVOSCloud.applicationContext, userId, new AVCallback() { 139 | @Override 140 | protected void internalDone0(Object o, AVException e) { 141 | callback.internalDone(avimClient, e); 142 | } 143 | }); 144 | } else { 145 | callback.internalDone(avimClient, e); 146 | } 147 | } 148 | }; 149 | 150 | if (AVUtils.isBlankContent(tag)) { 151 | AVIMClient.getInstance(userId).open(openCallback); 152 | } else { 153 | AVIMClient.getInstance(userId, tag).open(openCallback); 154 | } 155 | } 156 | 157 | /** 158 | * 关闭实时聊天 159 | * 160 | * @param callback 161 | */ 162 | public void close(final AVIMClientCallback callback) { 163 | AVIMClient.getInstance(currentUserId).close(new AVIMClientCallback() { 164 | @Override 165 | public void done(AVIMClient avimClient, AVIMException e) { 166 | currentUserId = null; 167 | LCIMConversationItemCache.getInstance().cleanup(); 168 | if (null != callback) { 169 | callback.internalDone(avimClient, e); 170 | } 171 | } 172 | }); 173 | } 174 | 175 | /** 176 | * 获取当前的实时聊天的用户 177 | * 178 | * @return 179 | */ 180 | public String getCurrentUserId() { 181 | return currentUserId; 182 | } 183 | 184 | /** 185 | * 获取当前的 AVIMClient 实例 186 | * 187 | * @return 188 | */ 189 | public AVIMClient getClient() { 190 | if (!TextUtils.isEmpty(currentUserId)) { 191 | return AVIMClient.getInstance(currentUserId); 192 | 193 | 194 | } 195 | return null; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /android/src/main/java/com/sisi/imleancloudplugin/LCChatKitUser.java: -------------------------------------------------------------------------------- 1 | package com.sisi.imleancloudplugin; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | /** 7 | * Created by wli on 16/2/2. 8 | * LCChatKit 中的用户类,仅包含三个变量,暂不支持继承扩展 9 | */ 10 | public final class LCChatKitUser implements Parcelable { 11 | private String userId; 12 | private String avatarUrl; 13 | private String name; 14 | 15 | public LCChatKitUser(String userId, String userName, String avatarUrl) { 16 | this.userId = userId; 17 | this.avatarUrl = avatarUrl; 18 | this.name = userName; 19 | } 20 | 21 | public LCChatKitUser() { 22 | 23 | } 24 | public void setUserId(String userId) { 25 | this.userId = userId; 26 | } 27 | 28 | public void setAvatarUrl(String avatarUrl) { 29 | this.avatarUrl = avatarUrl; 30 | } 31 | 32 | public void setName(String name) { 33 | this.name = name; 34 | } 35 | 36 | public String getUserId() { 37 | return userId; 38 | } 39 | 40 | public String getAvatarUrl() { 41 | return avatarUrl; 42 | } 43 | 44 | public String getName() { 45 | return null == name? "" : name; 46 | } 47 | 48 | @Override 49 | public int hashCode() { 50 | int hashCode = (null == userId) ? 0 : userId.hashCode(); 51 | hashCode = 73 * hashCode + ((null == avatarUrl) ? 0 : avatarUrl.hashCode()); 52 | hashCode = 73 * hashCode + ((null == name) ? 0 : name.hashCode()); 53 | return hashCode; 54 | } 55 | 56 | @Override 57 | public boolean equals(Object obj) { 58 | if (null == obj || !(obj instanceof LCChatKitUser)){ 59 | return false; 60 | } 61 | if (obj == this) { 62 | return true; 63 | } 64 | LCChatKitUser other = (LCChatKitUser) obj; 65 | if (null != this.userId && !this.userId.equals(other.userId)) { 66 | return false; 67 | } else if (null == this.userId && null != other.userId) { 68 | return false; 69 | } 70 | if (null != this.avatarUrl && !this.avatarUrl.equals(other.avatarUrl)) { 71 | return false; 72 | } else if (null == this.avatarUrl && null != other.avatarUrl) { 73 | return false; 74 | } 75 | if (null != this.name && !this.name.equals(other.name)) { 76 | return false; 77 | } else if (null == this.name && null != other.name) { 78 | return false; 79 | } 80 | return true; 81 | } 82 | 83 | public int describeContents() { 84 | return 0; 85 | } 86 | public void writeToParcel(Parcel dest, int flags) { 87 | dest.writeString(this.userId); 88 | dest.writeString(this.avatarUrl); 89 | dest.writeString(this.name); 90 | } 91 | public static final Creator CREATOR = new Creator() { 92 | @Override 93 | public LCChatKitUser createFromParcel(Parcel source) { 94 | String userId = source.readString(); 95 | String avatarUrl = source.readString(); 96 | String name = source.readString(); 97 | return new LCChatKitUser(userId, name, avatarUrl); 98 | } 99 | 100 | @Override 101 | public LCChatKitUser[] newArray(int size) { 102 | return new LCChatKitUser[0]; 103 | } 104 | }; 105 | } 106 | -------------------------------------------------------------------------------- /android/src/main/java/com/sisi/imleancloudplugin/LCChatProfileProvider.java: -------------------------------------------------------------------------------- 1 | package com.sisi.imleancloudplugin; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by wli on 16/2/2. 7 | * 用户体系的接口,开发者需要实现此接口来接入 LCChatKit 8 | */ 9 | public interface LCChatProfileProvider { 10 | void fetchProfiles(List userIdList, LCChatProfilesCallBack profilesCallBack); 11 | List getAllUsers(); 12 | } 13 | -------------------------------------------------------------------------------- /android/src/main/java/com/sisi/imleancloudplugin/LCChatProfilesCallBack.java: -------------------------------------------------------------------------------- 1 | package com.sisi.imleancloudplugin; 2 | 3 | 4 | import java.util.List; 5 | 6 | /** 7 | * Created by wli on 16/2/2. 8 | */ 9 | public interface LCChatProfilesCallBack { 10 | public void done(List 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> SortListMaps( 70 | List> list, String key) { 71 | if (list.length < 2) { 72 | return list; 73 | } else { 74 | //获取比较的标准(参考)值 75 | Map pivot = list[0]; 76 | int refer = pivot[key]; 77 | var less = >[]; 78 | var greater = >[]; 79 | for (int i = 1; i < list.length; i++) { 80 | int compare = list[i][key]; 81 | if (compare >= refer) { 82 | print(i); 83 | //从大到小排序,if (i[key] <= pivot[key])从小到大 84 | less.add(list[i]); 85 | } else { 86 | greater.add(list[i]); 87 | } 88 | } 89 | //使用递归的方式,对less 和 greater 再进行排序,最终返回排序好的集合 90 | return SortListMaps(less, key) + [pivot] + SortListMaps(greater, key); 91 | } 92 | } 93 | } 94 | 95 | class ConversationSqlite { 96 | Database db; 97 | static List conversationList = []; 98 | static List> convmaps=[]; 99 | openSqlite() async { 100 | // 获取数据库文件的存储路径 101 | var databasesPath = await getDatabasesPath(); 102 | String path = join(databasesPath, 'demo.db'); 103 | 104 | //根据数据库文件路径和数据库版本号创建数据库表 105 | db = await openDatabase(path, version: 1, 106 | onCreate: (Database db, int version) async { 107 | await db.execute(''' 108 | CREATE TABLE $tableConversation ( 109 | $columnId INTEGER PRIMARY KEY, 110 | $columnCurrentUser TEXT, 111 | $columnConversationId TEXT, 112 | $columnUsername TEXT, 113 | $columnLastReadAt INTEGER, 114 | $columnUpdateAt INTEGER, 115 | $columLastDelivered INTEGER) 116 | '''); 117 | }); 118 | } 119 | 120 | // 插入一条会话数据 121 | Future insert(Conversation conversation) async { 122 | await openSqlite(); 123 | conversation.id = await db.insert(tableConversation, conversation.toMap()); 124 | return conversation; 125 | } 126 | 127 | // 获取当前用户会话列表 128 | Future> getcurrentUserConversation( 129 | String currentUser) async { 130 | await openSqlite(); 131 | List maps = await db.query(tableConversation, 132 | columns: [ 133 | columnId, 134 | columnCurrentUser, 135 | columnConversationId, 136 | columnUsername, 137 | columnLastReadAt, 138 | columnUpdateAt, 139 | columLastDelivered 140 | ], 141 | where: '$columnCurrentUser = ?', 142 | whereArgs: [currentUser]); 143 | if (maps.length > 0) { 144 | maps = Conversation.SortListMaps(maps, columnUpdateAt); //按照会话时间排序 145 | print(maps); 146 | convmaps = maps; 147 | //conversationList.clear(); 148 | List conversations = new List(); 149 | for (int i = 0; i < maps.length; i++) { 150 | // conversationList.add(maps[i][columnUsername]); 151 | conversations.add(Conversation.fromMap(maps[i])); 152 | } 153 | return conversations; 154 | } 155 | return null; 156 | } 157 | 158 | // 判断会话是否存在 159 | Future conversationExist(String username) async { 160 | bool isExist = false; 161 | if (convmaps.length == 0 || convmaps == null) { 162 | return false; 163 | } else { 164 | for (var i in convmaps) { 165 | if (i['username'] == username) { 166 | isExist = true; 167 | break; 168 | } 169 | } 170 | } 171 | return isExist; 172 | } 173 | 174 | //根据conversationId从存储的会话列表里获取某个会话 175 | Future> getConversation(String conversationId) async { 176 | print('根据conversatonId获取会话'); 177 | print(convmaps); 178 | Map conv; 179 | if (convmaps.length > 0 || convmaps != null) { 180 | for (var i in convmaps) { 181 | if (conversationId == i['conversationId']) { 182 | conv = i; 183 | break; 184 | } 185 | } 186 | } 187 | print(conv); 188 | return conv; 189 | } 190 | 191 | //更新会话时间updateAt,排序会话列表 192 | Future sequenceConversation(String conversationId, int UpdateAt) async { 193 | print('更新会话时间updateAt,排序会话列表'); 194 | Map conv = await getConversation(conversationId); 195 | Map convmap = { 196 | '_id': conv['_id'], 197 | 'currentUser': User.currentUser, 198 | 'conversationId': conv['conversationId'], 199 | 'username': conv['username'], 200 | 'LastReadAt': conv['LastReadAt'], 201 | 'UpdateAt': UpdateAt, 202 | 'LastDelivered': conv['LastDelivered'], 203 | }; 204 | print(convmap); 205 | await update(convmap); 206 | } 207 | 208 | //更新LastReadAt,为已读功能准备 209 | Future updateLastReadAt(String conversationId, int LastReadAt) async { 210 | Map conv = await getConversation(conversationId); 211 | Map convmap = { 212 | '_id': conv['_id'], 213 | 'currentUser': User.currentUser, 214 | 'conversationId': conv['conversationId'], 215 | 'username': conv['username'], 216 | 'LastReadAt': LastReadAt, 217 | 'UpdateAt': conv['UpdateAt'], 218 | 'LastDelivered': conv['LastDelivered'], 219 | }; 220 | print(convmap); 221 | await update(convmap); 222 | } 223 | 224 | //更新LastDelivered,更新对方接收时间 225 | Future updateLastDelivered( 226 | String conversationId, int LastDelivered) async { 227 | print('更新LastDelivered,更新对方接收时间'); 228 | Map conv = await getConversation(conversationId); 229 | Map convmap = { 230 | '_id': conv['_id'], 231 | 'currentUser': User.currentUser, 232 | 'conversationId': conv['conversationId'], 233 | 'username': conv['username'], 234 | 'LastReadAt': conv['LastReadAt'], 235 | 'UpdateAt': conv['UpdateAt'], 236 | 'LastDelivered': LastDelivered, 237 | }; 238 | print(convmap); 239 | await update(convmap); 240 | } 241 | 242 | //删除会话消息 243 | Future deleteConversation(int id) async { 244 | await openSqlite(); 245 | return await db 246 | .delete(tableConversation, where: '$columnId = ?', whereArgs: [id]); 247 | } 248 | 249 | // 更新会话信息 250 | Future update(Map conversation) async { 251 | await openSqlite(); 252 | print('传入update数据库的数据'); 253 | print(conversation); 254 | print('${conversation['_id']}'); 255 | return await db.update(tableConversation, conversation, 256 | where: '$columnId = ?', whereArgs: [conversation[columnId]]); 257 | } 258 | 259 | // 记得及时关闭数据库,防止内存泄漏 260 | close() async { 261 | await db.close(); 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /example/lib/sql/message.dart: -------------------------------------------------------------------------------- 1 | class Message { 2 | String content; 3 | String conversationId; 4 | String messagefrom; 5 | String messageId; 6 | String MessageStatus;//枚举: 7 | int id; 8 | int timestamp; 9 | int deliveredAt; 10 | int updateAt; 11 | 12 | Message(this.content, this.messagefrom, this.conversationId); 13 | 14 | Message.map(Map obj) { 15 | this.content = obj['content']; 16 | this.messagefrom = obj['messagefrom']; 17 | this.conversationId = obj['conversationId']; 18 | this.MessageStatus=obj['MessageStatus']; 19 | } 20 | 21 | Map toMap() { 22 | var map = new Map(); 23 | map['content'] = this.content; 24 | map['messagefrom'] = this.messagefrom; 25 | map['conversationId'] = this.conversationId; 26 | map['MessageStatus']=this.MessageStatus; 27 | return map; 28 | } 29 | 30 | void setMessageId(int id) { 31 | this.id = id; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /example/lib/sql/sql.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io' as io; 3 | import 'message.dart'; 4 | import 'package:path/path.dart'; 5 | import 'package:path_provider/path_provider.dart'; 6 | import 'package:sqflite/sqflite.dart'; 7 | 8 | class sql { 9 | static final sql _instance = new sql.internal(); 10 | factory sql() => _instance; 11 | static Database _db; 12 | 13 | Future get db async { 14 | if (_db != null) return _db; 15 | _db = await initDb(); 16 | return _db; 17 | } 18 | 19 | sql.internal(); 20 | 21 | initDb() async { 22 | io.Directory documentsDirectory = await getApplicationDocumentsDirectory(); 23 | String path = join(documentsDirectory.path, "message.db"); 24 | var theDb = await openDatabase(path, version: 1, onCreate: _onCreate); 25 | return theDb; 26 | } 27 | 28 | void _onCreate(Database db, int version) async { 29 | // When creating the db, create the table 30 | await db.execute( 31 | "CREATE TABLE Message(id INTEGER PRIMARY KEY,content TEXT, messagefrom TEXT,conversationId TEXT)"); 32 | } 33 | 34 | Future saveMessage(Message message) async { 35 | var dbClient = await db; 36 | int res = await dbClient.insert("Message", message.toMap()); 37 | return res; 38 | } 39 | 40 | Future> getMessage() async { 41 | var dbClient = await db; 42 | List list = await dbClient.rawQuery('SELECT * FROM Message'); 43 | List messages = new List(); 44 | for (int i = 0; i < list.length; i++) { 45 | var message = new Message(list[i]["content"], list[i]["messagefrom"], 46 | list[i]["conversationId"]); 47 | message.setMessageId(list[i]["id"]); 48 | messages.add(message); 49 | } 50 | print(messages.length); 51 | return messages; 52 | } 53 | 54 | Future> getMessageFrom(String conversationId) async { 55 | var dbClient = await db; 56 | List list = await dbClient.query("Message", 57 | columns: ["content", "messagefrom", "conversationId"], 58 | where: '"conversationId" = ?', 59 | whereArgs: [conversationId]); 60 | List messages = new List(); 61 | for (int i = 0; i < list.length; i++) { 62 | var message = new Message(list[i]["content"], list[i]["messagefrom"], 63 | list[i]['conversationId']); 64 | message.setMessageId(list[i]["id"]); 65 | messages.add(message); 66 | } 67 | print(messages.length); 68 | return messages; 69 | } 70 | 71 | Future deleteUsers(Message message) async { 72 | var dbClient = await db; 73 | int res = await dbClient 74 | .rawDelete('DELETE FROM Message WHERE id = ?', [message.id]); 75 | return res; 76 | } 77 | 78 | Future update(Message message) async { 79 | var dbClient = await db; 80 | int res = await dbClient.update("Message", message.toMap(), 81 | where: "id = ?", whereArgs: [message.id]); 82 | return res > 0 ? true : false; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /example/lib/talk.dart: -------------------------------------------------------------------------------- 1 | //使用自建的消息缓存,详情查看sql文件夹。 2 | import 'package:flutter/material.dart'; 3 | import 'sql/conversation.dart'; 4 | import 'dart:convert'; 5 | import 'dart:async'; 6 | import 'sql/sql.dart'; 7 | import 'sql/message.dart'; 8 | import 'package:im_leancloud_plugin/im_leancloud_plugin.dart'; 9 | import 'package:shared_preferences/shared_preferences.dart'; 10 | 11 | class talk extends StatefulWidget { 12 | String conversationId; 13 | String username; 14 | talk(this.conversationId, this.username); 15 | 16 | @override 17 | talkState createState() => new talkState(); 18 | } 19 | 20 | class talkState extends State { 21 | bool isLoading = true; 22 | final TextEditingController textEditingController = 23 | new TextEditingController(); 24 | final ScrollController listScrollController = new ScrollController(); 25 | final StreamController> _streamController = 26 | StreamController>(); 27 | List contents = List(); 28 | final FocusNode focusNode = new FocusNode(); 29 | sql db = new sql(); 30 | ConversationSqlite dbc = new ConversationSqlite(); 31 | List messages; 32 | bool isopendTalk; 33 | 34 | Future saveNewconversation( 35 | String conversationId, String username) async { 36 | SharedPreferences prefs = await SharedPreferences.getInstance(); 37 | String currentUser = prefs.getString('currentUser'); 38 | bool isconversationExist = await dbc.conversationExist(username); 39 | if (isconversationExist == false) { 40 | Conversation conversation = 41 | new Conversation(currentUser, conversationId, username); 42 | await dbc.insert(conversation); 43 | } else { 44 | print('会话已经存在'); 45 | } 46 | } 47 | 48 | Future getConversationId(String username) async { 49 | ImLeancloudPlugin ImleancloudPlugin = ImLeancloudPlugin.getInstance(); 50 | SharedPreferences prefs = await SharedPreferences.getInstance(); 51 | String currentUser = prefs.getString('currentUser'); 52 | String conversationId = 53 | await ImleancloudPlugin.getConversation(currentUser, username); 54 | print(conversationId); 55 | } 56 | 57 | Future onReceiveMessage() async { 58 | ImLeancloudPlugin ImleancloudPlugin = ImLeancloudPlugin.getInstance(); 59 | ImleancloudPlugin.addEventHandler( 60 | onReceiveMessage: (Map message) async { 61 | String content = json.decode(message['content'])['_lctext']; 62 | Message onReceiveMessage = 63 | new Message(content, message['getfrom'], message['conversationId']); 64 | int res = await db.saveMessage(onReceiveMessage); 65 | getMessage(); 66 | if (widget.conversationId == message['conversationId'] && isopendTalk) { 67 | conversationRead(); 68 | } 69 | saveNewconversation(message['conversationId'], message['getfrom']); 70 | }, 71 | ); 72 | } 73 | 74 | void sendText(String content, String conversationId) { 75 | ImLeancloudPlugin ImleancloudPlugin = ImLeancloudPlugin.getInstance(); 76 | ImleancloudPlugin.sendText(content, conversationId); 77 | } 78 | 79 | void conversationRead() { 80 | ImLeancloudPlugin ImleancloudPlugin = ImLeancloudPlugin.getInstance(); 81 | ImleancloudPlugin.conversationRead(); 82 | } 83 | 84 | Future saveMessage(String sendContent) async { 85 | Message message = new Message(sendContent, 'meself', widget.conversationId); 86 | int res = await db.saveMessage(message); 87 | print(res); 88 | textEditingController.clear(); 89 | } 90 | 91 | Future getMessage() async { 92 | messages = await db.getMessageFrom(widget.conversationId); 93 | _streamController.sink.add(messages); 94 | } 95 | 96 | Future test(String sendContent) async { 97 | await saveMessage(sendContent); 98 | sendText(sendContent, widget.conversationId); 99 | getMessage(); 100 | } 101 | 102 | Future onBackPress() async { 103 | Navigator.pop(context); 104 | isopendTalk = false; 105 | return Future.value(false); 106 | } 107 | 108 | Future inittalk() async { 109 | await getConversationId(widget.username); 110 | conversationRead(); 111 | } 112 | 113 | @override 114 | void initState() { 115 | isopendTalk = true; 116 | inittalk(); 117 | onReceiveMessage(); 118 | getMessage(); 119 | super.initState(); 120 | } 121 | 122 | @override 123 | void dispose() { 124 | super.dispose(); 125 | } 126 | 127 | @override 128 | Widget build(BuildContext context) { 129 | return new Scaffold( 130 | appBar: new AppBar( 131 | title: new Text(widget.username), 132 | ), 133 | body: WillPopScope( 134 | child: Stack( 135 | children: [ 136 | Column( 137 | children: [ 138 | buildListMessage(), 139 | buildInput(), 140 | ], 141 | ), 142 | // buildLoading() 143 | ], 144 | ), 145 | onWillPop: onBackPress, 146 | ), 147 | ); 148 | } 149 | 150 | Widget buildLoading() { 151 | return Positioned( 152 | child: isLoading 153 | ? Container( 154 | child: Center( 155 | child: CircularProgressIndicator( 156 | valueColor: AlwaysStoppedAnimation(Colors.black54)), 157 | ), 158 | color: Colors.white.withOpacity(0.8), 159 | ) 160 | : Container(), 161 | ); 162 | } 163 | 164 | Widget buildInput() { 165 | return Container( 166 | child: Row( 167 | children: [ 168 | // Button send image 169 | Material( 170 | child: new Container( 171 | margin: new EdgeInsets.symmetric(horizontal: 1.0), 172 | child: new IconButton( 173 | icon: new Icon(Icons.add), 174 | onPressed: () { 175 | print('添加富媒体消息'); 176 | //...... 177 | }, 178 | color: Colors.black54, 179 | ), 180 | ), 181 | color: Colors.white, 182 | ), 183 | Material( 184 | child: new Container( 185 | margin: new EdgeInsets.symmetric(horizontal: 1.0), 186 | child: new IconButton( 187 | icon: new Icon(Icons.image), 188 | onPressed: () { 189 | print('获得图片'); 190 | //...... 191 | }, 192 | color: Colors.black54, 193 | ), 194 | ), 195 | color: Colors.white, 196 | ), 197 | 198 | // Edit text 199 | Flexible( 200 | child: Container( 201 | // margin: new EdgeInsets.symmetric(horizontal: 1.0), 202 | child: TextField( 203 | style: TextStyle(color: Colors.black54, fontSize: 18.0), 204 | controller: textEditingController, 205 | decoration: InputDecoration.collapsed( 206 | hintText: '发消息', 207 | hintStyle: TextStyle(color: Colors.black38), 208 | ), 209 | focusNode: focusNode, 210 | // maxLines: ?, 211 | ), 212 | ), 213 | ), 214 | 215 | // Button send message 216 | Material( 217 | child: new Container( 218 | margin: new EdgeInsets.symmetric(horizontal: 8.0), 219 | child: new IconButton( 220 | icon: new Icon(Icons.send), 221 | onPressed: () => test(textEditingController.text), 222 | color: Colors.blue, 223 | ), 224 | ), 225 | color: Colors.white, 226 | ), 227 | ], 228 | ), 229 | width: double.infinity, 230 | height: 50.0, 231 | decoration: new BoxDecoration( 232 | border: new Border( 233 | top: new BorderSide(color: Colors.black54, width: 0.5)), 234 | color: Colors.white), 235 | ); 236 | } 237 | 238 | Widget buildListMessage() { 239 | return Flexible( 240 | child: StreamBuilder( 241 | stream: _streamController.stream, 242 | builder: (context, snapshot) { 243 | if (!snapshot.hasData) { 244 | return Center( 245 | child: CircularProgressIndicator( 246 | valueColor: AlwaysStoppedAnimation(Colors.blue))); 247 | } else { 248 | return ListView.builder( 249 | padding: EdgeInsets.all(10.0), 250 | itemBuilder: (context, index) => buildItem(index, 251 | snapshot.data[snapshot.data.length - index - 1].toMap()), 252 | itemCount: snapshot.data.length, 253 | reverse: true, 254 | controller: listScrollController, 255 | ); 256 | } 257 | }, 258 | ), 259 | ); 260 | } 261 | 262 | Widget buildItem(int index, Map detail) { 263 | return Padding( 264 | padding: widget.username == detail['messagefrom'] 265 | ? const EdgeInsets.only(left: 10) 266 | : const EdgeInsets.only(right: 0), 267 | child: Container( 268 | alignment: widget.username == detail['messagefrom'] 269 | ? Alignment.centerLeft 270 | : Alignment.centerRight, 271 | margin: EdgeInsets.only( 272 | left: 10, 273 | right: 30, 274 | top: 10, 275 | ), 276 | child: Container( 277 | decoration: BoxDecoration( 278 | image: DecorationImage( 279 | image: widget.username == detail['messagefrom'] 280 | ? AssetImage('assets/images/chat.png') 281 | : AssetImage('assets/images/rchat.png'), 282 | centerSlice: Rect.fromLTWH(15, 10, 20, 3), 283 | ), 284 | ), 285 | constraints: BoxConstraints( 286 | minWidth: 1.0, 287 | maxWidth: 270.0, 288 | minHeight: 1.0, 289 | ), 290 | padding: EdgeInsets.fromLTRB(20.0, 10.0, 15.0, 15.0), 291 | child: Text( 292 | '${detail['content']}', 293 | style: widget.username == detail['messagefrom'] 294 | ? TextStyle( 295 | fontSize: 16.0, 296 | fontWeight: FontWeight.w400, 297 | color: Colors.white) 298 | : TextStyle( 299 | fontSize: 16.0, 300 | fontWeight: FontWeight.w400, 301 | color: Colors.black), 302 | ), 303 | ), 304 | ), 305 | ); 306 | //Text('${detail['content']}'); 307 | //..... 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /example/lib/user.dart: -------------------------------------------------------------------------------- 1 | class User{ 2 | static String currentUser; 3 | static bool isloginLcchat=false; 4 | User(); 5 | } -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: im_leancloud_plugin_example 2 | description: Demonstrates how to use the im_leancloud_plugin plugin. 3 | publish_to: 'none' 4 | 5 | environment: 6 | sdk: ">=2.0.0-dev.68.0 <3.0.0" 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | # The following adds the Cupertino Icons font to your application. 13 | # Use with the CupertinoIcons class for iOS style icons. 14 | cupertino_icons: ^0.1.2 15 | path: ^1.6.2 16 | sqflite: ^0.12.2+1 17 | path_provider: ^0.4.1 18 | shared_preferences: ^0.4.3 19 | http: ^0.12.0 20 | flutter_local_notifications: ^0.4.5 21 | connectivity: ^0.4.2 22 | 23 | dev_dependencies: 24 | flutter_test: 25 | sdk: flutter 26 | 27 | im_leancloud_plugin: 28 | path: ../ 29 | 30 | # For information on the generic Dart part of this file, see the 31 | # following page: https://www.dartlang.org/tools/pub/pubspec 32 | 33 | # The following section is specific to Flutter. 34 | flutter: 35 | 36 | # The following line ensures that the Material Icons font is 37 | # included with your application, so that you can use the icons in 38 | # the material Icons class. 39 | uses-material-design: true 40 | 41 | assets: 42 | - assets/images/chat.png 43 | - assets/images/rchat.png 44 | - assets/images/start.png 45 | # To add assets to your application, add an assets section, like this: 46 | # assets: 47 | # - images/a_dot_burr.jpeg 48 | # - images/a_dot_ham.jpeg 49 | 50 | # An image asset can refer to one or more resolution-specific "variants", see 51 | # https://flutter.io/assets-and-images/#resolution-aware. 52 | 53 | # For details regarding adding assets from package dependencies, see 54 | # https://flutter.io/assets-and-images/#from-packages 55 | 56 | # To add custom fonts to your application, add a fonts section here, 57 | # in this "flutter" section. Each entry in this list should have a 58 | # "family" key with the font family name, and a "fonts" key with a 59 | # list giving the asset and other descriptors for the font. For 60 | # example: 61 | # fonts: 62 | # - family: Schyler 63 | # fonts: 64 | # - asset: fonts/Schyler-Regular.ttf 65 | # - asset: fonts/Schyler-Italic.ttf 66 | # style: italic 67 | # - family: Trajan Pro 68 | # fonts: 69 | # - asset: fonts/TrajanPro.ttf 70 | # - asset: fonts/TrajanPro_Bold.ttf 71 | # weight: 700 72 | # 73 | # For details regarding fonts from package dependencies, 74 | # see https://flutter.io/custom-fonts/#from-packages 75 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:im_leancloud_plugin_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Verify Platform version', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MaterialApp()); 17 | 18 | // Verify that platform version is retrieved. 19 | expect( 20 | find.byWidgetPredicate( 21 | (Widget widget) => widget is Text && 22 | widget.data.startsWith('Running on:'), 23 | ), 24 | findsOneWidget, 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /im_leancloud_plugin.iml: -------------------------------------------------------------------------------- 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 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /im_leancloud_plugin_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verysi/im_leancloud_flutter_plugin/e4a3616753bc2390d6aa7ef67ba9bfa2246cbee9/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/ImLeancloudPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface ImLeancloudPlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /ios/Classes/ImLeancloudPlugin.m: -------------------------------------------------------------------------------- 1 | #import "ImLeancloudPlugin.h" 2 | 3 | @implementation ImLeancloudPlugin 4 | + (void)registerWithRegistrar:(NSObject*)registrar { 5 | FlutterMethodChannel* channel = [FlutterMethodChannel 6 | methodChannelWithName:@"im_leancloud_plugin" 7 | binaryMessenger:[registrar messenger]]; 8 | ImLeancloudPlugin* instance = [[ImLeancloudPlugin alloc] init]; 9 | [registrar addMethodCallDelegate:instance channel:channel]; 10 | } 11 | 12 | - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { 13 | if ([@"getPlatformVersion" isEqualToString:call.method]) { 14 | result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]); 15 | } else { 16 | result(FlutterMethodNotImplemented); 17 | } 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /ios/im_leancloud_plugin.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 3 | # 4 | Pod::Spec.new do |s| 5 | s.name = 'im_leancloud_plugin' 6 | s.version = '0.0.1' 7 | s.summary = 'im plugin for leancloud.' 8 | s.description = <<-DESC 9 | im plugin for leancloud. 10 | DESC 11 | s.homepage = 'http://example.com' 12 | s.license = { :file => '../LICENSE' } 13 | s.author = { 'Your Company' => 'email@example.com' } 14 | s.source = { :path => '.' } 15 | s.source_files = 'Classes/**/*' 16 | s.public_header_files = 'Classes/**/*.h' 17 | s.dependency 'Flutter' 18 | 19 | s.ios.deployment_target = '8.0' 20 | end 21 | 22 | -------------------------------------------------------------------------------- /lib/AVIMMessage.dart: -------------------------------------------------------------------------------- 1 | class AVIMMessage { 2 | String content; 3 | String conversationId; 4 | String from; 5 | String messageId; 6 | int timestamp; 7 | int deliveredAt; 8 | int updateAt; 9 | } 10 | -------------------------------------------------------------------------------- /lib/im_leancloud_plugin.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'dart:io'; 5 | import 'package:platform/platform.dart'; 6 | 7 | typedef Future EventHandler(Map event); 8 | 9 | class ImLeancloudPlugin { 10 | static const MethodChannel _channel = 11 | const MethodChannel('im_leancloud_plugin'); 12 | 13 | final Platform _platform; 14 | final MethodChannel _channel2; 15 | 16 | @visibleForTesting 17 | ImLeancloudPlugin.private(MethodChannel channel, Platform platform) 18 | : _channel2 = channel, 19 | _platform = platform; 20 | 21 | /// Singleton property 22 | static ImLeancloudPlugin _instancePlugin = new ImLeancloudPlugin.private( 23 | const MethodChannel('im_leancloud_plugin'), const LocalPlatform()); 24 | 25 | /// Get plugin instance 26 | static ImLeancloudPlugin getInstance() { 27 | return _instancePlugin; 28 | } 29 | 30 | var _logLevel = 0; 31 | 32 | static Future get platformVersion async { 33 | final String version = await _channel.invokeMethod('getPlatformVersion'); 34 | return version; 35 | } 36 | 37 | /// Initialize the Native SDK 38 | void initialize(String appId, String appKey) { 39 | _channel.setMethodCallHandler(_handler); 40 | var args = { 41 | 'appId': appId, 42 | 'appKey': appKey, 43 | }; 44 | _channel.invokeMethod('initialize', args); 45 | } 46 | 47 | Future onLoginClick(String args) async { 48 | // bool isloginLcchat=await _channel.invokeMethod('onLoginClick', args); 49 | // return isloginLcchat; 50 | 51 | bool isloginLcchat = await _channel.invokeMethod('onLoginClick', args); 52 | return isloginLcchat; 53 | } 54 | //上传文件,文件名fileName需加上后缀名 55 | Future uploadFile(String filePath, String fileName) async { 56 | var args = { 57 | 'filePath': filePath, 58 | 'fileName': fileName, 59 | }; 60 | String fileId = 61 | await _channel.invokeMethod('uploadFile', args); 62 | print('fileId:$fileId'); 63 | return fileId; 64 | } 65 | 66 | Future getConversation(String currentUser, String username) async { 67 | var args = { 68 | 'currentUser': currentUser, 69 | 'username': username, 70 | }; 71 | String conversationId = 72 | await _channel.invokeMethod('getConversation', args); 73 | return conversationId; 74 | } 75 | 76 | Future conversationList(String currentUser) async { 77 | var args = { 78 | 'currentUser': currentUser, 79 | }; 80 | String conversations = 81 | await _channel.invokeMethod('conversationList', args); 82 | return conversations; 83 | } 84 | 85 | //消息发送成功,返回'sendsuccess',失败返回'sendfalse' 86 | Future sendText(String content, String conversationId) async { 87 | var args = { 88 | 'content': content, 89 | 'conversationId': conversationId, 90 | }; 91 | String sendresult = await _channel.invokeMethod('sendText', args); 92 | print('sendresult:$sendresult'); 93 | return sendresult; 94 | } 95 | 96 | Future sendImage(String imagePath, String conversationId) async { 97 | var args = { 98 | 'imagePath': imagePath, 99 | 'conversationId': conversationId, 100 | }; 101 | 102 | String sendresult = await _channel.invokeMethod('sendImage', args); 103 | print('sendresult:$sendresult'); 104 | return sendresult; 105 | } 106 | 107 | Future sendAudio(String audioPath, String conversationId) async { 108 | var args = { 109 | 'audioPath': audioPath, 110 | 'conversationId': conversationId, 111 | }; 112 | 113 | String sendresult = await _channel.invokeMethod('sendAudio', args); 114 | return sendresult; 115 | } 116 | 117 | Future sendVideo(String videoPath, String conversationId) async { 118 | var args = { 119 | 'audioPath': videoPath, 120 | 'conversationId': conversationId, 121 | }; 122 | 123 | String sendresult = await _channel.invokeMethod('sendVideo', args); 124 | return sendresult; 125 | } 126 | 127 | void conversationRead() { 128 | _channel.invokeMethod('conversationRead'); 129 | } 130 | 131 | /// Setup log level must be before called initialize function 132 | /// The call must be include args: 133 | /// [level] --> OFF(0), ERROR(1), WARNING(2), INFO(3), DEBUG(4), VERBOSE(5), ALL(6); 134 | /// Leancloud logger level 135 | /// iOS only have ON or OFF. So when you set OFF, it's OFF. When you set another logger level, it's ON. 136 | void setLogLevel(LeancloudLoggerLevel level) { 137 | switch (level) { 138 | case LeancloudLoggerLevel.OFF: 139 | this._logLevel = 0; 140 | break; 141 | case LeancloudLoggerLevel.ERROR: 142 | this._logLevel = 1; 143 | break; 144 | case LeancloudLoggerLevel.WARNING: 145 | this._logLevel = 2; 146 | break; 147 | case LeancloudLoggerLevel.INFO: 148 | this._logLevel = 3; 149 | break; 150 | case LeancloudLoggerLevel.DEBUG: 151 | this._logLevel = 4; 152 | break; 153 | case LeancloudLoggerLevel.VERBOSE: 154 | this._logLevel = 5; 155 | break; 156 | } 157 | var args = { 158 | 'level': this._logLevel, 159 | }; 160 | _channel.invokeMethod('setLogLevel', args); 161 | } 162 | 163 | Future queryUnreadMessages( 164 | String conversationId, int unreadcount) async { 165 | var args = { 166 | 'conversationId': conversationId, 167 | 'unreadcount': unreadcount, 168 | }; 169 | String messages = await _channel.invokeMethod('queryUnreadMessages', args); 170 | return messages; 171 | } 172 | 173 | Future queryHistoryMessages(String conversationId, String messageId, 174 | int Timestamp, int pageSize) async { 175 | var args = { 176 | 'conversationId': conversationId, 177 | 'messageId': messageId, 178 | 'Timestamp': Timestamp, 179 | 'pageSize': pageSize, 180 | }; 181 | String historymessages = 182 | await _channel.invokeMethod('queryHistoryMessages', args); 183 | return historymessages; 184 | } 185 | 186 | Future signoutClick() async { 187 | await _channel.invokeMethod('signoutClick'); 188 | } 189 | 190 | EventHandler _onReceiveMessage; 191 | EventHandler _onConnectionResume; 192 | EventHandler _unRead; 193 | EventHandler _onLastReadAtUpdated; 194 | EventHandler _onLastDeliveredAtUpdated; 195 | 196 | void addEventHandler({ 197 | EventHandler onReceiveMessage, 198 | EventHandler onConnectionResume, 199 | EventHandler unRead, 200 | EventHandler onLastReadAtUpdated, 201 | EventHandler onLastDeliveredAtUpdated, 202 | }) { 203 | _onReceiveMessage = onReceiveMessage; 204 | _onConnectionResume = onConnectionResume; 205 | _unRead = unRead; 206 | _onLastReadAtUpdated = onLastReadAtUpdated; 207 | _onLastDeliveredAtUpdated = onLastDeliveredAtUpdated; 208 | } 209 | 210 | Future _handler(MethodCall call) { 211 | print("handle mehtod call ${call.method} ${call.arguments}"); 212 | String method = call.method; 213 | switch (method) { 214 | case 'onReceiveMessage': 215 | return _onReceiveMessage(call.arguments.cast()); 216 | break; 217 | case 'onConnectionResume': 218 | return _onConnectionResume(call.arguments.cast()); 219 | break; 220 | case 'unRead': 221 | return _unRead(call.arguments.cast()); 222 | break; 223 | case 'onLastReadAtUpdated': 224 | return _onLastReadAtUpdated(call.arguments.cast()); 225 | break; 226 | case 'onLastDeliveredAtUpdated': 227 | return _onLastDeliveredAtUpdated( 228 | call.arguments.cast()); 229 | break; 230 | default: 231 | print('没收到来自平台的回调'); 232 | } 233 | } 234 | } 235 | 236 | enum LeancloudLoggerLevel { OFF, ERROR, WARNING, INFO, DEBUG, VERBOSE } 237 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: im_leancloud_plugin 2 | description: im plugin for leancloud. 3 | version: 0.0.1 4 | author: 5 | homepage: 6 | 7 | environment: 8 | sdk: ">=2.0.0-dev.68.0 <3.0.0" 9 | 10 | dependencies: 11 | platform: ^2.2.0 12 | 13 | flutter: 14 | sdk: flutter 15 | 16 | # For information on the generic Dart part of this file, see the 17 | # following page: https://www.dartlang.org/tools/pub/pubspec 18 | 19 | # The following section is specific to Flutter. 20 | flutter: 21 | # This section identifies this Flutter project as a plugin project. 22 | # The androidPackage and pluginClass identifiers should not ordinarily 23 | # be modified. They are used by the tooling to maintain consistency when 24 | # adding or updating assets for this project. 25 | plugin: 26 | androidPackage: com.sisi.imleancloudplugin 27 | pluginClass: ImLeancloudPlugin 28 | 29 | # To add assets to your plugin package, add an assets section, like this: 30 | # assets: 31 | # - images/a_dot_burr.jpeg 32 | # - images/a_dot_ham.jpeg 33 | # 34 | # For details regarding assets in packages, see 35 | # https://flutter.io/assets-and-images/#from-packages 36 | # 37 | # An image asset can refer to one or more resolution-specific "variants", see 38 | # https://flutter.io/assets-and-images/#resolution-aware. 39 | 40 | # To add custom fonts to your plugin package, add a fonts section here, 41 | # in this "flutter" section. Each entry in this list should have a 42 | # "family" key with the font family name, and a "fonts" key with a 43 | # list giving the asset and other descriptors for the font. For 44 | # example: 45 | # fonts: 46 | # - family: Schyler 47 | # fonts: 48 | # - asset: fonts/Schyler-Regular.ttf 49 | # - asset: fonts/Schyler-Italic.ttf 50 | # style: italic 51 | # - family: Trajan Pro 52 | # fonts: 53 | # - asset: fonts/TrajanPro.ttf 54 | # - asset: fonts/TrajanPro_Bold.ttf 55 | # weight: 700 56 | # 57 | # For details regarding fonts in packages, see 58 | # https://flutter.io/custom-fonts/#from-packages 59 | --------------------------------------------------------------------------------