├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── cn │ └── leancloud │ └── plugin │ ├── ClientStatusListener.java │ ├── Common.java │ ├── DefaultClientEventHandler.java │ ├── DefaultConversationEventHandler.java │ ├── DefaultMessageHandler.java │ ├── DefaultSignatureFactory.java │ ├── Exception.java │ ├── IMEventNotification.java │ ├── LeanCloudMessageCodec.java │ └── LeancloudPlugin.java ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ │ └── cn │ │ │ │ │ └── leancloud │ │ │ │ │ └── leancloud_plugin_example │ │ │ │ │ ├── MainActivity.java │ │ │ │ │ └── MyApplication.java │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── assets │ ├── test.jpg │ ├── test.mp3 │ ├── test.mp4 │ ├── test.png │ └── test.zip ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── 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 │ │ └── Runner-Bridging-Header.h ├── lib │ └── main.dart ├── pubspec.lock ├── pubspec.yaml └── test │ └── widget_test.dart ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── LeancloudPlugin.h │ ├── LeancloudPlugin.m │ └── SwiftLeancloudPlugin.swift └── leancloud_official_plugin.podspec ├── lib ├── leancloud_plugin.dart └── src │ ├── client.dart │ ├── conversation.dart │ ├── message.dart │ └── query.dart ├── pubspec.lock ├── pubspec.yaml └── test └── leancloud_plugin_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | .last_build_id 2 | .DS_Store 3 | .dart_tool/ 4 | 5 | .packages 6 | .pub/ 7 | 8 | .idea/ 9 | *.iml 10 | build/ 11 | -------------------------------------------------------------------------------- /.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: 27321ebbad34b0a3fafe99fac037102196d655ff 8 | channel: stable 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.1 2 | 3 | * Update dependencies 4 | * Java SDK >= *8.1.5* 5 | 6 | ## 1.0.0 7 | 8 | * Support null-safety 9 | * Update dependencies 10 | * Swift SDK >= *17.10.0* 11 | * Java SDK >= *8.1.4* 12 | 13 | ## 1.0.0-beta.13 14 | 15 | * Update dependencies 16 | * Java SDK >= *8.1.1* 17 | 18 | ## 1.0.0-beta.12 19 | 20 | * Update dependencies 21 | * Swift SDK >= *17.6.7* 22 | * Java SDK >= *7.1.1* 23 | 24 | ## 1.0.0-beta.11 25 | 26 | * Support blocking members 27 | * Support muting members 28 | 29 | ## 1.0.0-beta.10 30 | 31 | * Fix some bug on the platform android 32 | * Update dependencies 33 | 34 | ## 1.0.0-beta.9 35 | 36 | * Update dependencies 37 | 38 | ## 1.0.0-beta.8 39 | 40 | * Support using message-id and message-timestamp to update sent message 41 | 42 | ## 1.0.0-beta.7 43 | 44 | * Fix register channel on Android 45 | 46 | ## 1.0.0-beta.6 47 | 48 | * Add timestamp and date of last message properties for conversation 49 | * Add transient property for message 50 | * Fix transient message update unread message count 51 | * Java SDK >= *6.5.9* 52 | * Fix multiple instance of conversation 53 | * Swift SDK >= *17.5.7* 54 | 55 | ## 1.0.0-beta.5 56 | 57 | * Fix message ACK error handling not right 58 | * Swift SDK >= *17.5.6* 59 | * Java SDK >= *6.5.8* 60 | 61 | ## 1.0.0-beta.4 62 | 63 | * Fix not support custom typed message 64 | * Swift SDK >= *17.5.5* 65 | * Java SDK >= *6.5.3* 66 | * Fix not contain last message when query with `includeLastMessage` option on Android 67 | * Java SDK >= *6.5.3* 68 | 69 | ## 1.0.0-beta.3 70 | 71 | * Add APIs for query of `Conversation` 72 | 73 | ## 1.0.0-beta.2 74 | 75 | * Add property `status: MessageStatus` to `Message` 76 | 77 | ## 1.0.0-beta.1 78 | 79 | * Implements LeanCloud RTM Flutter SDK 80 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # leancloud_official_plugin 2 | 3 | An official flutter plugin for [LeanCloud](https://www.leancloud.cn) real-time message service based on [LeanCloud-Swift-SDK](https://github.com/leancloud/swift-sdk) and [LeanCloud-Java-SDK](https://github.com/leancloud/java-unified-sdk). 4 | 5 | ## Flutter Getting Started 6 | 7 | This project is a starting point for a Flutter [plug-in package](https://flutter.dev/docs/development/packages-and-plugins), 8 | a specialized package that includes platform-specific implementation code for Android and iOS. 9 | 10 | For help getting started with Flutter, 11 | view [online documentation](https://flutter.dev/docs), 12 | which offers tutorials, samples, guidance on mobile development, and a full API reference. 13 | 14 | ## Usage 15 | 16 | ### Adding dependency 17 | 18 | 1. Following this [document](https://flutter.dev/docs/development/packages-and-plugins/using-packages) to add **leancloud_official_plugin** to your app, like this: 19 | 20 | ``` 21 | dependencies: 22 | leancloud_official_plugin: '>=x.y.z <(x+1).0.0' # Recommend using up-to-next-major policy. 23 | ``` 24 | 25 | 2. Using [Gradle](https://gradle.org/) and [CocoaPods](https://cocoapods.org) to add platform-specific dependencies. 26 | 27 | * Using *CocoaPods* in *terminal* 28 | * do `$ cd ios/` 29 | * then `$ pod update` or `$ pod install --repo-update` 30 | * *Gradle* 31 | * [reference](https://leancloud.cn/docs/sdk_setup-java.html#hash260111001) 32 | 33 | ### Initialization 34 | 35 | 1. import `package:leancloud_official_plugin/leancloud_plugin.dart` in `lib/main.dart` of your project, like this: 36 | ```dart 37 | import 'package:leancloud_official_plugin/leancloud_plugin.dart'; 38 | ``` 39 | 40 | 2. import `cn.leancloud.LeanCloud`, `cn.leancloud.LCLogger` and `cn.leancloud.im.LCIMOptions` in `YourApplication.java` of your project, then set up ***ID***, ***Key*** and ***URL***, like this: 41 | ```java 42 | import io.flutter.app.FlutterApplication; 43 | import cn.leancloud.LeanCloud; 44 | import cn.leancloud.LCLogger; 45 | import cn.leancloud.im.LCIMOptions; 46 | 47 | public class YourApplication extends FlutterApplication { 48 | @Override 49 | public void onCreate() { 50 | super.onCreate(); 51 | LCIMOptions.getGlobalOptions().setUnreadNotificationEnabled(true); 52 | LeanCloud.setLogLevel(LCLogger.Level.DEBUG); 53 | LeanCloud.initialize(this, YOUR_LC_APP_ID, YOUR_LC_APP_KEY, YOUR_LC_SERVER_URL); 54 | } 55 | } 56 | ``` 57 | 58 | 3. import `LeanCloud` in `AppDelegate.swift` of your project, then set up ***ID***, ***Key*** and ***URL***, like this: 59 | ```swift 60 | import Flutter 61 | import LeanCloud 62 | 63 | @UIApplicationMain 64 | @objc class AppDelegate: FlutterAppDelegate { 65 | override func application( 66 | _ application: UIApplication, 67 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 68 | ) -> Bool { 69 | do { 70 | LCApplication.logLevel = .all 71 | try LCApplication.default.set( 72 | id: YOUR_LC_APP_ID, 73 | key: YOUR_LC_APP_KEY, 74 | serverURL: YOUR_LC_SERVER_URL) 75 | GeneratedPluginRegistrant.register(with: self) 76 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 77 | } catch { 78 | fatalError("\(error)") 79 | } 80 | } 81 | } 82 | ``` 83 | 84 | ### Push setup (optional) 85 | 86 | Due to different push service in iOS and Android, the setup-code should be wrote in native platform. 87 | it's optional, so if you no need of push service, you can ignore this section. 88 | 89 | * iOS 90 | 91 | ```swift 92 | import Flutter 93 | import LeanCloud 94 | import UserNotifications 95 | 96 | @UIApplicationMain 97 | @objc class AppDelegate: FlutterAppDelegate { 98 | override func application( 99 | _ application: UIApplication, 100 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 101 | ) -> Bool { 102 | do { 103 | LCApplication.logLevel = .all 104 | try LCApplication.default.set( 105 | id: YOUR_LC_APP_ID, 106 | key: YOUR_LC_APP_KEY, 107 | serverURL: YOUR_LC_SERVER_URL) 108 | GeneratedPluginRegistrant.register(with: self) 109 | /* 110 | register APNs to access token, like this: 111 | */ 112 | UNUserNotificationCenter.current().getNotificationSettings { (settings) in 113 | switch settings.authorizationStatus { 114 | case .authorized: 115 | DispatchQueue.main.async { 116 | UIApplication.shared.registerForRemoteNotifications() 117 | } 118 | case .notDetermined: 119 | UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound]) { (granted, error) in 120 | if granted { 121 | DispatchQueue.main.async { 122 | UIApplication.shared.registerForRemoteNotifications() 123 | } 124 | } 125 | } 126 | default: 127 | break 128 | } 129 | } 130 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 131 | } catch { 132 | fatalError("\(error)") 133 | } 134 | } 135 | 136 | override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { 137 | super.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken) 138 | /* 139 | set APNs deviceToken and Team ID. 140 | */ 141 | LCApplication.default.currentInstallation.set( 142 | deviceToken: deviceToken, 143 | apnsTeamId: YOUR_APNS_TEAM_ID) 144 | /* 145 | save to LeanCloud. 146 | */ 147 | LCApplication.default.currentInstallation.save { (result) in 148 | switch result { 149 | case .success: 150 | break 151 | case .failure(error: let error): 152 | print(error) 153 | } 154 | } 155 | } 156 | 157 | override func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { 158 | super.application(application, didFailToRegisterForRemoteNotificationsWithError: error) 159 | print(error) 160 | } 161 | } 162 | ``` 163 | 164 | * Android 165 | 166 | [reference](https://leancloud.cn/docs/android_push_guide.html) 167 | 168 | ## Sample Code 169 | 170 | After initialization, you can write some sample code and run it to check whether initializing success, like this: 171 | 172 | ### Open 173 | 174 | ```dart 175 | // new an IM client 176 | Client client = Client(id: CLIENT_ID); 177 | // open it 178 | await client.open(); 179 | ``` 180 | 181 | ### Query Conversations 182 | 183 | ```dart 184 | // the ID of the conversation instance list 185 | List objectIDs = [...]; 186 | // new query from an opened client 187 | ConversationQuery query = client.conversationQuery(); 188 | // set query condition 189 | query.whereContainedIn( 190 | 'objectId', 191 | objectIDs, 192 | ); 193 | query.limit = objectIDs.length; 194 | // do the query 195 | List conversations = await query.find(); 196 | ``` 197 | -------------------------------------------------------------------------------- /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 'cn.leancloud.plugin' 2 | version '1.0' 3 | 4 | buildscript { 5 | repositories { 6 | google() 7 | jcenter() 8 | maven { 9 | url "https://oss.sonatype.org/content/groups/public/" 10 | } 11 | mavenLocal() 12 | } 13 | 14 | dependencies { 15 | classpath 'com.android.tools.build:gradle:3.5.0' 16 | } 17 | } 18 | 19 | rootProject.allprojects { 20 | repositories { 21 | google() 22 | jcenter() 23 | maven { 24 | url "https://oss.sonatype.org/content/groups/public/" 25 | } 26 | mavenLocal() 27 | } 28 | } 29 | 30 | apply plugin: 'com.android.library' 31 | 32 | android { 33 | compileSdkVersion 28 34 | 35 | defaultConfig { 36 | minSdkVersion 16 37 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 38 | } 39 | lintOptions { 40 | disable 'InvalidPackage' 41 | } 42 | } 43 | 44 | dependencies { 45 | implementation('cn.leancloud:realtime-android:8.1.5') { 46 | exclude group: 'cn.leancloud', module: 'realtime-core' 47 | } 48 | implementation('cn.leancloud:storage-android:8.1.5') { 49 | exclude group: 'cn.leancloud', module: 'storage-core' 50 | } 51 | implementation 'cn.leancloud:realtime-core:8.1.5' 52 | implementation 'cn.leancloud:storage-core:8.1.5' 53 | implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' 54 | 55 | androidTestImplementation 'androidx.test:runner:1.1.1' 56 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 57 | } 58 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Dec 24 17:40:45 CST 2020 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-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'leancloud_official_plugin' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/java/cn/leancloud/plugin/ClientStatusListener.java: -------------------------------------------------------------------------------- 1 | package cn.leancloud.plugin; 2 | 3 | import cn.leancloud.im.v2.LCIMClient; 4 | 5 | public interface ClientStatusListener { 6 | void onDisconnected(LCIMClient client); 7 | void onResumed(LCIMClient client); 8 | void onOffline(LCIMClient client, int code); 9 | } 10 | -------------------------------------------------------------------------------- /android/src/main/java/cn/leancloud/plugin/Common.java: -------------------------------------------------------------------------------- 1 | package cn.leancloud.plugin; 2 | 3 | 4 | import cn.leancloud.im.v2.callback.LCIMConversationIterableResult; 5 | import cn.leancloud.json.JSONObject; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import cn.leancloud.LCException; 11 | import cn.leancloud.im.Signature; 12 | import cn.leancloud.im.v2.LCIMClient; 13 | import cn.leancloud.im.v2.LCIMConversation; 14 | import cn.leancloud.im.v2.LCIMException; 15 | import cn.leancloud.im.v2.LCIMMessage; 16 | import cn.leancloud.im.v2.LCIMMessageInterval; 17 | import cn.leancloud.im.v2.LCIMMessageOption; 18 | import cn.leancloud.im.v2.LCIMMessageInterval.MessageIntervalBound; 19 | import cn.leancloud.utils.StringUtil; 20 | import io.flutter.plugin.common.MethodCall; 21 | 22 | public class Common { 23 | public static final String Method_Close_Client = "closeClient"; 24 | public static final String Method_Open_Client = "openClient"; 25 | public static final String Method_Create_Conversation = "createConversation"; 26 | public static final String Method_Fetch_Conversation = "getConversation"; 27 | public static final String Method_Query_Conversation = "queryConversation"; 28 | public static final String Method_Send_Message = "sendMessage"; 29 | public static final String Method_Read_Message = "readMessage"; 30 | public static final String Method_Patch_Message = "patchMessage"; 31 | public static final String Method_Get_Message_Receipt = "fetchReceiptTimestamp"; 32 | public static final String Method_Query_Message = "queryMessage"; 33 | public static final String Method_Query_Block_Members = "queryBlockedMembers"; 34 | public static final String Method_Query_Mute_Members = "queryMutedMembers"; 35 | public static final String Method_Update_Members = "updateMembers"; 36 | public static final String Method_Update_Block_Members = "updateBlockMembers"; 37 | public static final String Method_Update_Mute_Members = "updateMuteMembers"; 38 | public static final String Method_Mute_Conversation = "muteToggle"; 39 | public static final String Method_Update_Conversation = "updateData"; 40 | public static final String Method_Query_Member_Count = "countMembers"; 41 | 42 | public static final String Method_Client_Offline = "onSessionClose"; 43 | public static final String Method_Client_Disconnected = "onSessionDisconnect"; 44 | public static final String Method_Client_Resumed = "onSessionResume"; 45 | public static final String Method_Client_Opened = "onSessionOpen"; 46 | 47 | public static final String Method_Message_Received = "onMessageReceive"; 48 | public static final String Method_Message_Receipted = "onMessageReceipt"; 49 | public static final String Method_Message_Updated = "onMessagePatch"; 50 | 51 | public static final String Method_Conv_Member_Updated = "onConversationMembersUpdate"; 52 | public static final String Method_Conv_Updated = "onConversationDataUpdate"; 53 | public static final String Method_Conv_UnreadCount_Updated = "onUnreadMessageCountUpdate"; 54 | public static final String Method_Conv_LastReceipt_Timestamp_Updated = "onLastReceiptTimestampUpdate"; 55 | 56 | public static final String Method_Sign_SessionOpen = "onSignSessionOpen"; 57 | public static final String Method_Sign_Conversation = "onSignConversation"; 58 | 59 | public static final String Param_Client_Id = "clientId"; 60 | public static final String Param_ReOpen = "r"; 61 | public static final String Param_Client_Tag = "tag"; 62 | public static final String Param_Signature = "signRegistry"; 63 | public static final String Param_Sign_SessionOpen = "sessionOpen"; 64 | public static final String Param_Sign_Conversation = "conversation"; 65 | public static final String Param_Conv_Type = "conv_type"; 66 | public static final String Param_Conv_Members = "m"; 67 | public static final String Param_Conv_Name = "name"; 68 | public static final String Param_Conv_Attributes = "attr"; 69 | public static final String Param_Conv_TTL = "ttl"; 70 | public static final String Param_Conv_Id = "conversationId"; 71 | 72 | public static final String Param_Conv_MaxACK_Timestamp = "maxAckTimestamp"; 73 | public static final String Param_Conv_MaxRead_Timestamp = "maxReadTimestamp"; 74 | 75 | public static final String Param_Unread_Mention = "unreadMessageMention"; 76 | 77 | public static final String Param_Conv_Operation = "op"; 78 | public static final String Param_Conv_Data = "data"; 79 | public static final String Param_RawData = "rawData"; 80 | public static final String Param_Count = "count"; 81 | public static final String Param_Mention = "mention"; 82 | public static final String Param_From = "from"; 83 | 84 | public static final String Param_Timestamp = "t"; 85 | public static final String Param_Flag_Read = "read"; 86 | public static final String Param_Patch_Code = "patchCode"; 87 | public static final String Param_Patch_Reason = "patchReason"; 88 | public static final String Param_Members = "members"; 89 | public static final String Param_Operator = "initBy"; 90 | public static final String Param_Update_Time = "udate"; 91 | 92 | public static final String Param_Query_Where = "where"; 93 | public static final String Param_Query_Sort = "sort"; 94 | public static final String Param_Query_Limit = "limit"; 95 | public static final String Param_Query_Skip = "skip"; 96 | public static final String Param_Query_Flag = "flag"; 97 | public static final String Param_Query_Temp_List = "tempConvIds"; 98 | 99 | public static final String Param_Query_Start = "start"; 100 | public static final String Param_Query_End = "end"; 101 | public static final String Param_Query_Direction = "direction"; 102 | public static final String Param_Query_MsgType = "type"; 103 | public static final String Param_Query_Next = "next"; 104 | 105 | public static final String Param_Message_Old = "oldMessage"; 106 | public static final String Param_Message_New = "newMessage"; 107 | public static final String Param_Message_Raw = "message"; 108 | public static final String Param_Message_Options = "options"; 109 | public static final String Param_Message_File = "file"; 110 | public static final String Param_Message_Id = "id"; 111 | public static final String Param_Message_Recall = "recall"; 112 | public static final String Param_Message_Transient = "transient"; 113 | 114 | public static final String Param_File_Path = "path"; 115 | public static final String Param_File_Data = "data"; 116 | public static final String Param_File_Url = "url"; 117 | public static final String Param_File_Format = "format"; 118 | public static final String Param_File_Name = "name"; 119 | 120 | public static final String Param_Sign_TargetIds = "targetIds"; 121 | public static final String Param_Sign_Action = "action"; 122 | 123 | public static final String Param_Code = "code"; 124 | public static final String Param_Error = "error"; 125 | 126 | public static final int Conv_Type_Unique = 0; 127 | public static final int Conv_Type_Common = 1; 128 | public static final int Conv_Type_Transient = 2; 129 | public static final int Conv_Type_Temporary = 4; 130 | 131 | public static final String Conv_Operation_Mute = "mute"; 132 | public static final String Conv_Operation_Unmute = "unmute"; 133 | public static final String Conv_Operation_Add = "add"; 134 | public static final String Conv_Operation_Remove = "remove"; 135 | public static final String Conv_Operation_Block = "block"; 136 | public static final String Conv_Operation_Unblock = "unblock"; 137 | 138 | public static T getMethodParam(MethodCall call, String key) { 139 | if (call.hasArgument(key)) { 140 | return call.argument(key); 141 | } 142 | return null; 143 | } 144 | 145 | public static boolean getParamBoolean(MethodCall call, String key) { 146 | if (call.hasArgument(key)) { 147 | return call.argument(key); 148 | } 149 | return false; 150 | } 151 | 152 | public static int getParamInt(MethodCall call, String key) { 153 | if (call.hasArgument(key)) { 154 | return call.argument(key); 155 | } 156 | return 0; 157 | } 158 | 159 | public static String getParamString(MethodCall call, String key) { 160 | if (call.hasArgument(key)) { 161 | return call.argument(key); 162 | } 163 | return null; 164 | } 165 | 166 | public static Signature getMethodSignature(MethodCall call, String key) { 167 | Map param = getMethodParam(call, key); 168 | if (null == param) { 169 | return null; 170 | } 171 | Signature result = new Signature(); 172 | result.setSignature((String) param.get("s")); 173 | result.setNonce((String) param.get("n")); 174 | result.setTimestamp((long) param.get("t")); 175 | return result; 176 | } 177 | 178 | public static Map wrapException(int errorCode, String message) { 179 | LCException exception = new LCException(errorCode, message); 180 | return wrapException(exception); 181 | } 182 | 183 | public static Map wrapException(LCException ex) { 184 | if (null == ex) { 185 | return new HashMap<>(); 186 | } 187 | Map error = new HashMap<>(); 188 | if (ex instanceof LCIMException) { 189 | error.put("code", String.valueOf(((LCIMException) ex).getAppCode())); 190 | } else { 191 | error.put("code", String.valueOf(ex.getCode())); 192 | } 193 | error.put("message", ex.getMessage()); 194 | if (null != ex.getCause()) { 195 | error.put("details", ex.getCause().getMessage()); 196 | } 197 | Map result = new HashMap<>(); 198 | result.put("error", error); 199 | return result; 200 | } 201 | 202 | public static Map wrapSuccessResponse(Map result) { 203 | Map response = new HashMap<>(); 204 | if (null != result) { 205 | response.put("success", result); 206 | } 207 | return response; 208 | } 209 | 210 | public static Map wrapSuccessResponse(List> resultList) { 211 | Map response = new HashMap<>(); 212 | if (null != resultList) { 213 | response.put("success", resultList); 214 | } 215 | return response; 216 | } 217 | 218 | public static Map wrapSuccessResponse(int result) { 219 | Map response = new HashMap<>(); 220 | response.put("success", result); 221 | return response; 222 | } 223 | 224 | public static Map wrapClient(LCIMClient client) { 225 | HashMap result = new HashMap<>(); 226 | if (null != client) { 227 | result.put("clientId", client.getClientId()); 228 | } 229 | return result; 230 | } 231 | 232 | public static Map wrapMessage(LCIMMessage message) { 233 | Map result = new HashMap<>(); 234 | if (null == message) { 235 | return result; 236 | } 237 | 238 | result = message.dumpRawData(); 239 | return result; 240 | } 241 | 242 | public static LCIMMessage parseMessage(Map rawData) { 243 | if (null == rawData) { 244 | return null; 245 | } 246 | LCIMMessage message = LCIMMessage.parseJSON(rawData); 247 | return message; 248 | } 249 | 250 | public static LCIMMessageOption parseMessageOption(Map data) { 251 | if (null == data || data.isEmpty()) { 252 | return null; 253 | } 254 | LCIMMessageOption option = new LCIMMessageOption(); 255 | if (data.containsKey("will")) { 256 | option.setWill((boolean) data.get("will")); 257 | } 258 | if (data.containsKey("receipt")) { 259 | option.setReceipt((boolean) data.get("receipt")); 260 | } 261 | if (data.containsKey("priority")) { 262 | int priority = (int) data.get("priority"); 263 | option.setPriority(LCIMMessageOption.MessagePriority.getProiority(priority)); 264 | } 265 | if (data.containsKey("pushData")) { 266 | option.setPushDataEx((Map) data.get("pushData")); 267 | } 268 | return option; 269 | } 270 | 271 | public static MessageIntervalBound parseMessageIntervalBound(Map data) { 272 | if (null == data) { 273 | return null; 274 | } 275 | String messageId = (String) data.get("id"); 276 | long timestamp = (long) data.get("timestamp"); 277 | boolean closed = (boolean) data.get("close"); 278 | return LCIMMessageInterval.createBound(messageId, timestamp, closed); 279 | } 280 | 281 | public static Map wrapConversation(LCIMConversation conversation) { 282 | Map result = new HashMap<>(); 283 | if (null == conversation) { 284 | return result; 285 | } 286 | 287 | result = conversation.dumpRawData(); 288 | if (result.containsKey("conv_type") && result.containsKey("uniqueId") && !result.containsKey("unique")) { 289 | if (1 == (int) result.get("conv_type") && !StringUtil.isEmpty((String) result.get("uniqueId"))) { 290 | result.put("unique", true); 291 | } 292 | } 293 | 294 | return result; 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /android/src/main/java/cn/leancloud/plugin/DefaultClientEventHandler.java: -------------------------------------------------------------------------------- 1 | package cn.leancloud.plugin; 2 | 3 | import cn.leancloud.im.v2.LCIMClient; 4 | import cn.leancloud.im.v2.LCIMClientEventHandler; 5 | 6 | public class DefaultClientEventHandler extends LCIMClientEventHandler { 7 | private ClientStatusListener listener; 8 | 9 | public DefaultClientEventHandler(ClientStatusListener listener) { 10 | this.listener = listener; 11 | } 12 | 13 | /** 14 | * 实现本方法以处理网络断开事件 15 | * 16 | * @param client 17 | * @since 3.0 18 | */ 19 | public void onConnectionPaused(LCIMClient client) { 20 | if (null != this.listener) { 21 | this.listener.onDisconnected(client); 22 | } 23 | } 24 | 25 | /** 26 | * 实现本方法以处理网络恢复事件 27 | * 28 | * @since 3.0 29 | * @param client 30 | */ 31 | 32 | public void onConnectionResume(LCIMClient client) { 33 | if (null != this.listener) { 34 | this.listener.onResumed(client); 35 | } 36 | } 37 | 38 | /** 39 | * 实现本方法以处理当前登录被踢下线的情况 40 | * 41 | * 42 | * @param client 43 | * @param code 状态码说明被踢下线的具体原因 44 | */ 45 | 46 | public void onClientOffline(LCIMClient client, int code) { 47 | if (null != this.listener) { 48 | this.listener.onOffline(client, code); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /android/src/main/java/cn/leancloud/plugin/DefaultConversationEventHandler.java: -------------------------------------------------------------------------------- 1 | package cn.leancloud.plugin; 2 | 3 | import cn.leancloud.json.JSONObject; 4 | 5 | import java.text.SimpleDateFormat; 6 | import java.util.Arrays; 7 | import java.util.Date; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | import cn.leancloud.im.v2.LCIMClient; 13 | import cn.leancloud.im.v2.LCIMConversation; 14 | import cn.leancloud.im.v2.LCIMConversationEventHandler; 15 | import cn.leancloud.im.v2.LCIMMessage; 16 | import cn.leancloud.im.v2.conversation.LCIMConversationMemberInfo; 17 | import cn.leancloud.utils.StringUtil; 18 | 19 | public class DefaultConversationEventHandler extends LCIMConversationEventHandler { 20 | private static final String Member_Event_Self_Joined = "joined"; 21 | private static final String Member_Event_Self_Left = "left"; 22 | private static final String Member_Event_Other_Joined = "members-joined"; 23 | private static final String Member_Event_Other_Left = "members-left"; 24 | 25 | private static final String Member_Event_Self_Muted = "muted"; 26 | private static final String Member_Event_Self_Unmuted = "unmuted"; 27 | private static final String Member_Event_Other_Muted = "members-muted"; 28 | private static final String Member_Event_Other_Unmuted = "members-unmuted"; 29 | 30 | private static final String Member_Event_Self_Blocked = "blocked"; 31 | private static final String Member_Event_Self_Unblocked = "unblocked"; 32 | private static final String Member_Event_Other_Blocked = "members-blocked"; 33 | private static final String Member_Event_Other_Unblocked = "members-unblocked"; 34 | private IMEventNotification listener; 35 | 36 | 37 | public DefaultConversationEventHandler(IMEventNotification listener) { 38 | this.listener = listener; 39 | } 40 | 41 | /** 42 | * 实现本方法以处理聊天对话中的参与者离开事件 43 | * 44 | * @param client 45 | * @param conversation 46 | * @param members 离开的参与者 47 | * @param kickedBy 离开事件的发动者,有可能是离开的参与者本身 48 | * @since 3.0 49 | */ 50 | 51 | public void onMemberLeft(LCIMClient client, LCIMConversation conversation, 52 | List members, String kickedBy) { 53 | LOGGER.d("Notification --- memberLeft. conversation:" + conversation.getConversationId()); 54 | if (null != this.listener) { 55 | Map param = new HashMap<>(); 56 | param.put(Common.Param_Client_Id, client.getClientId()); 57 | param.put(Common.Param_Conv_Id, conversation.getConversationId()); 58 | param.put(Common.Param_Conv_Operation, Member_Event_Other_Left); 59 | param.put(Common.Param_Conv_Members, members); 60 | param.put(Common.Param_Members, conversation.getMembers()); 61 | param.put(Common.Param_Operator, kickedBy); 62 | param.put(Common.Param_Update_Time, StringUtil.stringFromDate(new Date())); 63 | this.listener.notify(Common.Method_Conv_Member_Updated, param); 64 | } 65 | } 66 | 67 | /** 68 | * 实现本方法以处理聊天对话中的参与者加入事件 69 | * 70 | * @param client 71 | * @param conversation 72 | * @param members 加入的参与者 73 | * @param invitedBy 加入事件的邀请人,有可能是加入的参与者本身 74 | * @since 3.0 75 | */ 76 | 77 | public void onMemberJoined(LCIMClient client, LCIMConversation conversation, 78 | List members, String invitedBy) { 79 | LOGGER.d("Notification --- memberJoined. conversation:" + conversation.getConversationId()); 80 | if (null != this.listener) { 81 | Map param = new HashMap<>(); 82 | param.put(Common.Param_Client_Id, client.getClientId()); 83 | param.put(Common.Param_Conv_Id, conversation.getConversationId()); 84 | param.put(Common.Param_Conv_Operation, Member_Event_Other_Joined); 85 | param.put(Common.Param_Conv_Members, members); 86 | param.put(Common.Param_Members, conversation.getMembers()); 87 | param.put(Common.Param_Operator, invitedBy); 88 | param.put(Common.Param_Update_Time, StringUtil.stringFromDate(new Date())); 89 | this.listener.notify(Common.Method_Conv_Member_Updated, param); 90 | } 91 | } 92 | 93 | /** 94 | * 实现本方法来处理当前用户被踢出某个聊天对话事件 95 | * 96 | * @param client 97 | * @param conversation 98 | * @param kickedBy 踢出你的人 99 | * @since 3.0 100 | */ 101 | public void onKicked(LCIMClient client, LCIMConversation conversation, String kickedBy) { 102 | LOGGER.d("Notification --- " + " you are kicked from conversation:" 103 | + conversation.getConversationId() + " by " + kickedBy); 104 | if (null != this.listener) { 105 | Map param = new HashMap<>(); 106 | param.put(Common.Param_Client_Id, client.getClientId()); 107 | param.put(Common.Param_Conv_Id, conversation.getConversationId()); 108 | param.put(Common.Param_Conv_Operation, Member_Event_Self_Left); 109 | param.put(Common.Param_Conv_Members, Arrays.asList(client.getClientId())); 110 | param.put(Common.Param_Members, conversation.getMembers()); 111 | param.put(Common.Param_Operator, kickedBy); 112 | param.put(Common.Param_Update_Time, StringUtil.stringFromDate(new Date())); 113 | this.listener.notify(Common.Method_Conv_Member_Updated, param); 114 | } 115 | } 116 | 117 | /** 118 | * 实现本方法来处理当前用户被邀请到某个聊天对话事件 119 | * 120 | * @param client 121 | * @param conversation 被邀请的聊天对话 122 | * @param operator 邀请你的人 123 | * @since 3.0 124 | */ 125 | public void onInvited(LCIMClient client, LCIMConversation conversation, String operator) { 126 | LOGGER.d("Notification --- " + " you are invited to conversation:" 127 | + conversation.getConversationId() + " by " + operator); 128 | if (null != this.listener) { 129 | Map param = new HashMap<>(); 130 | param.put(Common.Param_Client_Id, client.getClientId()); 131 | param.put(Common.Param_Conv_Id, conversation.getConversationId()); 132 | param.put(Common.Param_Conv_Operation, Member_Event_Self_Joined); 133 | param.put(Common.Param_Conv_Members, Arrays.asList(client.getClientId())); 134 | param.put(Common.Param_Members, conversation.getMembers()); 135 | param.put(Common.Param_Operator, operator); 136 | param.put(Common.Param_Update_Time, StringUtil.stringFromDate(new Date())); 137 | this.listener.notify(Common.Method_Conv_Member_Updated, param); 138 | } 139 | } 140 | 141 | /** 142 | * 当前用户被禁言通知处理函数 143 | * @param client 聊天客户端 144 | * @param conversation 对话 145 | * @param operator 操作者 id 146 | */ 147 | public void onMuted(LCIMClient client, LCIMConversation conversation, String operator) { 148 | LOGGER.d("Notification --- " + " you are muted by " + operator ); 149 | if (null != this.listener) { 150 | Map param = new HashMap<>(); 151 | param.put(Common.Param_Client_Id, client.getClientId()); 152 | param.put(Common.Param_Conv_Id, conversation.getConversationId()); 153 | param.put(Common.Param_Conv_Operation, Member_Event_Self_Muted); 154 | param.put(Common.Param_Members, conversation.getMembers()); 155 | param.put(Common.Param_Operator, operator); 156 | param.put(Common.Param_Update_Time, StringUtil.stringFromDate(new Date())); 157 | this.listener.notify(Common.Method_Conv_Member_Updated, param); 158 | } 159 | } 160 | 161 | /** 162 | * 当前用户被解除禁言通知处理函数 163 | * @param client 聊天客户端 164 | * @param conversation 对话 165 | * @param operator 操作者 id 166 | */ 167 | public void onUnmuted(LCIMClient client, LCIMConversation conversation, String operator) { 168 | LOGGER.d("Notification --- " + " you are unmuted by " + operator ); 169 | if (null != this.listener) { 170 | Map param = new HashMap<>(); 171 | param.put(Common.Param_Client_Id, client.getClientId()); 172 | param.put(Common.Param_Conv_Id, conversation.getConversationId()); 173 | param.put(Common.Param_Conv_Operation, Member_Event_Self_Unmuted); 174 | param.put(Common.Param_Members, conversation.getMembers()); 175 | param.put(Common.Param_Operator, operator); 176 | param.put(Common.Param_Update_Time, StringUtil.stringFromDate(new Date())); 177 | this.listener.notify(Common.Method_Conv_Member_Updated, param); 178 | } 179 | } 180 | 181 | /** 182 | * 聊天室成员被禁言通知处理函数 183 | * @param client 聊天客户端 184 | * @param conversation 对话 185 | * @param members 成员列表 186 | * @param operator 操作者 id 187 | */ 188 | public void onMemberMuted(LCIMClient client, LCIMConversation conversation, List members, String operator){ 189 | LOGGER.d("Notification --- " + operator + " muted members: " + StringUtil.join(", ", members)); 190 | if (null != this.listener) { 191 | Map param = new HashMap<>(); 192 | param.put(Common.Param_Client_Id, client.getClientId()); 193 | param.put(Common.Param_Conv_Id, conversation.getConversationId()); 194 | param.put(Common.Param_Conv_Operation, Member_Event_Other_Muted); 195 | param.put(Common.Param_Conv_Members, members); 196 | param.put(Common.Param_Members, conversation.getMembers()); 197 | param.put(Common.Param_Operator, operator); 198 | param.put(Common.Param_Update_Time, StringUtil.stringFromDate(new Date())); 199 | this.listener.notify(Common.Method_Conv_Member_Updated, param); 200 | } 201 | } 202 | 203 | /** 204 | * 聊天室成员被解除禁言通知处理函数 205 | * @param client 聊天客户端 206 | * @param conversation 对话 207 | * @param members 成员列表 208 | * @param operator 操作者 id 209 | */ 210 | public void onMemberUnmuted(LCIMClient client, LCIMConversation conversation, 211 | List members, String operator){ 212 | LOGGER.d("Notification --- " + operator + " unmuted members: " + StringUtil.join(", ", members)); 213 | if (null != this.listener) { 214 | Map param = new HashMap<>(); 215 | param.put(Common.Param_Client_Id, client.getClientId()); 216 | param.put(Common.Param_Conv_Id, conversation.getConversationId()); 217 | param.put(Common.Param_Conv_Operation, Member_Event_Other_Unmuted); 218 | param.put(Common.Param_Conv_Members, members); 219 | param.put(Common.Param_Members, conversation.getMembers()); 220 | param.put(Common.Param_Operator, operator); 221 | param.put(Common.Param_Update_Time, StringUtil.stringFromDate(new Date())); 222 | this.listener.notify(Common.Method_Conv_Member_Updated, param); 223 | } 224 | } 225 | 226 | /** 227 | * 当前用户被加入黑名单通知处理函数 228 | * @param client 聊天客户端 229 | * @param conversation 对话 230 | * @param operator 操作者 id 231 | */ 232 | public void onBlocked(LCIMClient client, LCIMConversation conversation, String operator) { 233 | LOGGER.d("Notification --- " + " you are blocked by " + operator ); 234 | if (null != this.listener) { 235 | Map param = new HashMap<>(); 236 | param.put(Common.Param_Client_Id, client.getClientId()); 237 | param.put(Common.Param_Conv_Id, conversation.getConversationId()); 238 | param.put(Common.Param_Conv_Operation, Member_Event_Self_Blocked); 239 | param.put(Common.Param_Members, conversation.getMembers()); 240 | param.put(Common.Param_Operator, operator); 241 | param.put(Common.Param_Update_Time, StringUtil.stringFromDate(new Date())); 242 | this.listener.notify(Common.Method_Conv_Member_Updated, param); 243 | } 244 | } 245 | 246 | /** 247 | * 当前用户被移出黑名单通知处理函数 248 | * @param client 聊天客户端 249 | * @param conversation 对话 250 | * @param operator 操作者 id 251 | */ 252 | public void onUnblocked(LCIMClient client, LCIMConversation conversation, String operator) { 253 | LOGGER.d("Notification --- " + " you are unblocked by " + operator ); 254 | if (null != this.listener) { 255 | Map param = new HashMap<>(); 256 | param.put(Common.Param_Client_Id, client.getClientId()); 257 | param.put(Common.Param_Conv_Id, conversation.getConversationId()); 258 | param.put(Common.Param_Conv_Operation, Member_Event_Self_Unblocked); 259 | param.put(Common.Param_Members, conversation.getMembers()); 260 | param.put(Common.Param_Operator, operator); 261 | param.put(Common.Param_Update_Time, StringUtil.stringFromDate(new Date())); 262 | this.listener.notify(Common.Method_Conv_Member_Updated, param); 263 | } 264 | } 265 | 266 | /** 267 | * 聊天室成员被加入黑名单通知处理函数 268 | * @param client 聊天客户端 269 | * @param conversation 对话 270 | * @param members 成员列表 271 | * @param operator 操作者 id 272 | */ 273 | public void onMemberBlocked(LCIMClient client, LCIMConversation conversation, 274 | List members, String operator){ 275 | LOGGER.d("Notification --- " + operator + " blocked members: " + StringUtil.join(", ", members)); 276 | if (null != this.listener) { 277 | Map param = new HashMap<>(); 278 | param.put(Common.Param_Client_Id, client.getClientId()); 279 | param.put(Common.Param_Conv_Id, conversation.getConversationId()); 280 | param.put(Common.Param_Conv_Operation, Member_Event_Other_Blocked); 281 | param.put(Common.Param_Conv_Members, members); 282 | param.put(Common.Param_Members, conversation.getMembers()); 283 | param.put(Common.Param_Operator, operator); 284 | param.put(Common.Param_Update_Time, StringUtil.stringFromDate(new Date())); 285 | this.listener.notify(Common.Method_Conv_Member_Updated, param); 286 | } 287 | } 288 | 289 | /** 290 | * 聊天室成员被移出黑名单通知处理函数 291 | * @param client 聊天客户端 292 | * @param conversation 对话 293 | * @param members 成员列表 294 | * @param operator 操作者 id 295 | */ 296 | public void onMemberUnblocked(LCIMClient client, LCIMConversation conversation, 297 | List members, String operator){ 298 | LOGGER.d("Notification --- " + operator + " unblocked members: " + StringUtil.join(", ", members)); 299 | if (null != this.listener) { 300 | Map param = new HashMap<>(); 301 | param.put(Common.Param_Client_Id, client.getClientId()); 302 | param.put(Common.Param_Conv_Id, conversation.getConversationId()); 303 | param.put(Common.Param_Conv_Operation, Member_Event_Other_Unblocked); 304 | param.put(Common.Param_Conv_Members, members); 305 | param.put(Common.Param_Members, conversation.getMembers()); 306 | param.put(Common.Param_Operator, operator); 307 | param.put(Common.Param_Update_Time, StringUtil.stringFromDate(new Date())); 308 | this.listener.notify(Common.Method_Conv_Member_Updated, param); 309 | } 310 | } 311 | 312 | /** 313 | * 实现本地方法来处理未读消息数量的通知 314 | * @param client 315 | * @param conversation 316 | */ 317 | public void onUnreadMessagesCountUpdated(LCIMClient client, LCIMConversation conversation) { 318 | LOGGER.d("Notification --- unReadCount was updated. conversationId: " + conversation.getConversationId()); 319 | if (null != this.listener) { 320 | HashMap param = new HashMap<>(); 321 | param.put(Common.Param_Client_Id, client.getClientId()); 322 | param.put(Common.Param_Conv_Id, conversation.getConversationId()); 323 | param.put(Common.Param_Count, conversation.getUnreadMessagesCount()); 324 | param.put(Common.Param_Mention, conversation.unreadMessagesMentioned()); 325 | if (conversation.getUnreadMessagesCount() > 0 && null != conversation.getLastMessage()) { 326 | param.put(Common.Param_Message_Raw, Common.wrapMessage(conversation.getLastMessage())); 327 | } 328 | this.listener.notify(Common.Method_Conv_UnreadCount_Updated, param); 329 | } 330 | } 331 | 332 | /** 333 | * 实现本地方法来处理对方已经接收消息的通知 334 | */ 335 | public void onLastDeliveredAtUpdated(LCIMClient client, LCIMConversation conversation) { 336 | LOGGER.d("Notification --- lastDeliveredAt was updated. conversationId: " + conversation.getConversationId()); 337 | if (null != this.listener) { 338 | HashMap param = new HashMap<>(); 339 | param.put(Common.Param_Conv_Id, conversation.getConversationId()); 340 | param.put(Common.Param_Client_Id, client.getClientId()); 341 | param.put(Common.Param_Conv_MaxACK_Timestamp, conversation.getLastDeliveredAt()); 342 | this.listener.notify(Common.Method_Conv_LastReceipt_Timestamp_Updated, param); 343 | } 344 | } 345 | 346 | /** 347 | * 实现本地方法来处理对方已经阅读消息的通知 348 | */ 349 | public void onLastReadAtUpdated(LCIMClient client, LCIMConversation conversation) { 350 | LOGGER.d("Notification --- lastReadAt was updated. conversationId: " + conversation.getConversationId()); 351 | if (null != this.listener) { 352 | HashMap param = new HashMap<>(); 353 | param.put(Common.Param_Conv_Id, conversation.getConversationId()); 354 | param.put(Common.Param_Client_Id, client.getClientId()); 355 | param.put(Common.Param_Conv_MaxRead_Timestamp, conversation.getLastReadAt()); 356 | this.listener.notify(Common.Method_Conv_LastReceipt_Timestamp_Updated, param); 357 | } 358 | } 359 | 360 | /** 361 | * 实现本地方法来处理消息的更新事件 362 | * @param client 363 | * @param conversation 364 | * @param message 365 | */ 366 | public void onMessageUpdated(LCIMClient client, LCIMConversation conversation, LCIMMessage message) { 367 | LOGGER.d("Notification --- message was updated. messageId: " + message.getMessageId()); 368 | if (null != this.listener) { 369 | HashMap param = new HashMap<>(); 370 | param.put(Common.Param_Client_Id, client.getClientId()); 371 | param.put(Common.Param_Conv_Id, conversation.getConversationId()); 372 | param.put(Common.Param_Message_Raw, Common.wrapMessage(message)); 373 | //TODO: add patchCode and patchReason. 374 | this.listener.notify(Common.Method_Message_Updated, param); 375 | } 376 | } 377 | 378 | /** 379 | * 实现本地方法来处理消息的撤回事件 380 | * @param client 381 | * @param conversation 382 | * @param message 383 | */ 384 | public void onMessageRecalled(LCIMClient client, LCIMConversation conversation, LCIMMessage message) { 385 | LOGGER.d("Notification --- message was recalled. messageId: " + message.getMessageId()); 386 | if (null != this.listener) { 387 | HashMap param = new HashMap<>(); 388 | param.put(Common.Param_Client_Id, client.getClientId()); 389 | param.put(Common.Param_Conv_Id, conversation.getConversationId()); 390 | param.put(Common.Param_Message_Raw, Common.wrapMessage(message)); 391 | param.put(Common.Param_Message_Recall, true); 392 | this.listener.notify(Common.Method_Message_Updated, param); 393 | } 394 | } 395 | 396 | /** 397 | * 对话成员信息变更通知。 398 | * 常见的有:某成员权限发生变化(如,被设为管理员等)。 399 | * @param client 通知关联的 LCIMClient 400 | * @param conversation 通知关联的对话 401 | * @param memberInfo 变更后的成员信息 402 | * @param updatedProperties 发生变更的属性列表(当前固定为 "role") 403 | * @param operator 操作者 id 404 | */ 405 | public void onMemberInfoUpdated(LCIMClient client, LCIMConversation conversation, 406 | LCIMConversationMemberInfo memberInfo, List updatedProperties, String operator) { 407 | LOGGER.d("Notification --- " + operator + " updated memberInfo: " + memberInfo.toString()); 408 | } 409 | 410 | /** 411 | * 对话自身属性变更通知 412 | * 413 | * @param client 414 | * @param conversation 415 | * @param attr 416 | * @param operator 417 | */ 418 | public void onInfoChanged(LCIMClient client, LCIMConversation conversation, JSONObject attr, 419 | String operator) { 420 | LOGGER.d("Notification --- " + operator + " by member: " + operator + ", changedTo: " + attr.toJSONString()); 421 | if (null != this.listener) { 422 | Map param = new HashMap<>(); 423 | param.put(Common.Param_Client_Id, client.getClientId()); 424 | param.put(Common.Param_Conv_Id, conversation.getConversationId()); 425 | param.put(Common.Param_Operator, operator); 426 | param.put(Common.Param_Conv_Attributes, attr); 427 | param.put(Common.Param_RawData, Common.wrapConversation(conversation)); 428 | param.put(Common.Param_Update_Time, StringUtil.stringFromDate(new Date())); 429 | this.listener.notify(Common.Method_Conv_Updated, param); 430 | } 431 | } 432 | } 433 | -------------------------------------------------------------------------------- /android/src/main/java/cn/leancloud/plugin/DefaultMessageHandler.java: -------------------------------------------------------------------------------- 1 | package cn.leancloud.plugin; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import cn.leancloud.im.v2.LCIMClient; 7 | import cn.leancloud.im.v2.LCIMConversation; 8 | import cn.leancloud.im.v2.LCIMMessage; 9 | import cn.leancloud.im.v2.LCIMMessageHandler; 10 | 11 | public class DefaultMessageHandler extends LCIMMessageHandler { 12 | 13 | private IMEventNotification listener; 14 | 15 | public DefaultMessageHandler(IMEventNotification listener) { 16 | this.listener = listener; 17 | } 18 | 19 | /** 20 | * 重载此方法来处理接收消息 21 | * 22 | * @param message 23 | * @param conversation 24 | * @param client 25 | */ 26 | @Override 27 | public void onMessage(LCIMMessage message, LCIMConversation conversation, LCIMClient client) { 28 | if (null != this.listener) { 29 | HashMap param = new HashMap<>(); 30 | param.put(Common.Param_Client_Id, client.getClientId()); 31 | param.put(Common.Param_Conv_Id, conversation.getConversationId()); 32 | Map msgData = Common.wrapMessage(message); 33 | msgData.put(Common.Param_Client_Id, client.getClientId()); 34 | msgData.put(Common.Param_Conv_Id, conversation.getConversationId()); 35 | param.put(Common.Param_Message_Raw, msgData); 36 | this.listener.notify(Common.Method_Message_Received, param); 37 | } 38 | } 39 | 40 | /** 41 | * 重载此方法来处理消息回执 42 | * 43 | * @param message 44 | * @param conversation 45 | * @param client 46 | */ 47 | @Override 48 | public void onMessageReceipt(LCIMMessage message, LCIMConversation conversation, LCIMClient client) { 49 | if (null != this.listener) { 50 | HashMap param = new HashMap<>(); 51 | param.put(Common.Param_Client_Id, client.getClientId()); 52 | param.put(Common.Param_Conv_Id, conversation.getConversationId()); 53 | param.put(Common.Param_Message_Id, message.getMessageId()); 54 | param.put(Common.Param_Timestamp, message.getDeliveredAt()); 55 | param.put(Common.Param_Flag_Read, false); 56 | this.listener.notify(Common.Method_Message_Receipted, param); 57 | } 58 | } 59 | 60 | /** 61 | * 重载此方法来处理消息回执 62 | * 63 | * @param message 64 | * @param operator 65 | * @param conversation 66 | * @param client 67 | */ 68 | @Override 69 | public void onMessageReceiptEx(LCIMMessage message, String operator, LCIMConversation conversation, LCIMClient client) { 70 | if (null != this.listener) { 71 | HashMap param = new HashMap<>(); 72 | param.put(Common.Param_Client_Id, client.getClientId()); 73 | param.put(Common.Param_Conv_Id, conversation.getConversationId()); 74 | param.put(Common.Param_Message_Id, message.getMessageId()); 75 | param.put(Common.Param_Timestamp, message.getDeliveredAt()); 76 | param.put(Common.Param_From, operator); 77 | param.put(Common.Param_Flag_Read, false); 78 | this.listener.notify(Common.Method_Message_Receipted, param); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /android/src/main/java/cn/leancloud/plugin/DefaultSignatureFactory.java: -------------------------------------------------------------------------------- 1 | package cn.leancloud.plugin; 2 | 3 | import android.util.Log; 4 | 5 | import java.util.List; 6 | import java.util.concurrent.ConcurrentHashMap; 7 | 8 | import cn.leancloud.im.Signature; 9 | import cn.leancloud.im.SignatureFactory; 10 | 11 | public class DefaultSignatureFactory implements SignatureFactory { 12 | private final static String TAG = DefaultSignatureFactory.class.getSimpleName(); 13 | private static final DefaultSignatureFactory _instance = new DefaultSignatureFactory(); 14 | public static DefaultSignatureFactory getInstance() { 15 | return _instance; 16 | } 17 | 18 | private ConcurrentHashMap sessionSignSettings = new ConcurrentHashMap<>(); 19 | private ConcurrentHashMap conversationSignSettings = new ConcurrentHashMap<>(); 20 | private DefaultSignatureFactory() { 21 | ; 22 | } 23 | 24 | public void registerSignedClient(String clientId, boolean enableSessionSign, 25 | boolean enableConversationSign, SignatureFactory signatureFactory) { 26 | if (enableSessionSign && null != signatureFactory) { 27 | sessionSignSettings.put(clientId, signatureFactory); 28 | } else { 29 | sessionSignSettings.remove(clientId); 30 | } 31 | 32 | if (enableConversationSign && null != signatureFactory) { 33 | conversationSignSettings.put(clientId, signatureFactory); 34 | } else { 35 | conversationSignSettings.remove(clientId); 36 | } 37 | } 38 | 39 | public Signature createSignature(String peerId, List watchIds) throws SignatureException { 40 | if (sessionSignSettings.containsKey(peerId)) { 41 | return sessionSignSettings.get(peerId).createSignature(peerId, watchIds); 42 | } else { 43 | Log.d(TAG, "not found session signature factory for clientId: " + peerId); 44 | } 45 | return null; 46 | } 47 | 48 | public Signature createConversationSignature(String conversationId, String clientId, 49 | List targetIds, String action) throws SignatureException { 50 | if (conversationSignSettings.containsKey(clientId)) { 51 | return conversationSignSettings.get(clientId).createConversationSignature(conversationId, clientId, targetIds, action); 52 | } else { 53 | Log.d(TAG, "not found conversation signature factory for clientId: " + clientId); 54 | } 55 | return null; 56 | } 57 | 58 | public Signature createBlacklistSignature(String clientId, String conversationId, List memberIds, 59 | String action) throws SignatureException { 60 | return null; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /android/src/main/java/cn/leancloud/plugin/Exception.java: -------------------------------------------------------------------------------- 1 | package cn.leancloud.plugin; 2 | 3 | import cn.leancloud.LCException; 4 | 5 | public class Exception { 6 | public static final int ErrorCode_Invalid_Parameter = LCException.INVALID_PARAMETER; 7 | public static final String ErrorMsg_Invalid_ClientId = "Client id is null or invalid."; 8 | 9 | public static final String ErrorMsg_Invalid_ConversationId = "Conversation id is null or invalid."; 10 | } 11 | -------------------------------------------------------------------------------- /android/src/main/java/cn/leancloud/plugin/IMEventNotification.java: -------------------------------------------------------------------------------- 1 | package cn.leancloud.plugin; 2 | 3 | import io.flutter.plugin.common.MethodChannel; 4 | 5 | public interface IMEventNotification { 6 | void notify(String method, Object param); 7 | void notifyWithResult(String method, Object param, MethodChannel.Result callback); 8 | } 9 | -------------------------------------------------------------------------------- /android/src/main/java/cn/leancloud/plugin/LeanCloudMessageCodec.java: -------------------------------------------------------------------------------- 1 | package cn.leancloud.plugin; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.math.BigDecimal; 5 | import java.math.BigInteger; 6 | import java.nio.charset.Charset; 7 | 8 | import cn.leancloud.utils.LCUtils; 9 | import io.flutter.plugin.common.StandardMessageCodec; 10 | 11 | public class LeanCloudMessageCodec extends StandardMessageCodec { 12 | private static final Charset UTF8 = Charset.forName("UTF8"); 13 | private static final byte INT = 3; 14 | private static final byte LONG = 4; 15 | private static final byte BIGINT = 5; 16 | private static final byte DOUBLE = 6; 17 | 18 | @Override 19 | protected void writeValue(ByteArrayOutputStream stream, Object value) { 20 | if (value instanceof Number) { 21 | if (value instanceof Integer || value instanceof Short || value instanceof Byte) { 22 | stream.write(INT); 23 | writeInt(stream, ((Number) value).intValue()); 24 | } else if (value instanceof Long) { 25 | stream.write(LONG); 26 | writeLong(stream, (long) value); 27 | } else if (value instanceof Float || value instanceof Double) { 28 | stream.write(DOUBLE); 29 | writeAlignment(stream, 8); 30 | writeDouble(stream, ((Number) value).doubleValue()); 31 | } else if (value instanceof BigInteger) { 32 | stream.write(BIGINT); 33 | writeBytes(stream, 34 | ((BigInteger) value).toString(16).getBytes(UTF8)); 35 | } else if (value instanceof BigDecimal){ 36 | stream.write(DOUBLE); 37 | writeAlignment(stream, 8); 38 | double newValue = LCUtils.normalize2Double(2, (BigDecimal) value); 39 | writeDouble(stream, newValue); 40 | } else { 41 | throw new IllegalArgumentException("Unsupported Number type: " + value.getClass()); 42 | } 43 | } else { 44 | super.writeValue(stream, value); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | -------------------------------------------------------------------------------- /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: 27321ebbad34b0a3fafe99fac037102196d655ff 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # leancloud_plugin_example 2 | 3 | Demonstrates how to use the 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.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /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 29 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 "cn.leancloud.leancloud_plugin_example" 37 | minSdkVersion 16 38 | targetSdkVersion 29 39 | multiDexEnabled true 40 | versionCode flutterVersionCode.toInteger() 41 | versionName flutterVersionName 42 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 43 | } 44 | 45 | signingConfigs { 46 | release { 47 | storeFile file("../release-key.keystore") 48 | storePassword '123456' 49 | keyAlias 'rtm-sample' 50 | keyPassword '123456' 51 | } 52 | } 53 | 54 | buildTypes { 55 | release { 56 | // TODO: Add your own signing config for the release build. 57 | // Signing with the debug keys for now, so `flutter run --release` works. 58 | signingConfig signingConfigs.release 59 | } 60 | } 61 | } 62 | 63 | flutter { 64 | source '../..' 65 | } 66 | 67 | dependencies { 68 | implementation('cn.leancloud:realtime-android:8.1.5') { 69 | exclude group: 'cn.leancloud', module: 'realtime-core' 70 | } 71 | implementation('cn.leancloud:storage-android:8.1.5') { 72 | exclude group: 'cn.leancloud', module: 'storage-core' 73 | } 74 | implementation 'cn.leancloud:realtime-core:8.1.5' 75 | implementation 'cn.leancloud:storage-core:8.1.5' 76 | implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' 77 | 78 | testImplementation 'junit:junit:4.12' 79 | androidTestImplementation 'androidx.test:runner:1.1.1' 80 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 81 | } 82 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 22 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 49 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/cn/leancloud/leancloud_plugin_example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package cn.leancloud.leancloud_plugin_example; 2 | 3 | import androidx.annotation.NonNull; 4 | import io.flutter.embedding.android.FlutterActivity; 5 | import io.flutter.embedding.engine.FlutterEngine; 6 | import io.flutter.plugins.GeneratedPluginRegistrant; 7 | 8 | public class MainActivity extends FlutterActivity { 9 | @Override 10 | public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { 11 | GeneratedPluginRegistrant.registerWith(flutterEngine); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/cn/leancloud/leancloud_plugin_example/MyApplication.java: -------------------------------------------------------------------------------- 1 | package cn.leancloud.leancloud_plugin_example; 2 | 3 | import android.app.NotificationManager; 4 | import android.os.Build; 5 | 6 | import cn.leancloud.LCLogger; 7 | import cn.leancloud.LeanCloud; 8 | import cn.leancloud.im.LCIMOptions; 9 | import cn.leancloud.push.PushService; 10 | import io.flutter.app.FlutterApplication; 11 | 12 | public class MyApplication extends FlutterApplication { 13 | // private static final String LC_App_Id = "s0g5kxj7ajtf6n2wt8fqty18p25gmvgrh7b430iuugsde212"; 14 | // private static final String LC_App_Key = "hc7jpfubg5vaurjlezxhfr1t9pqb9w8tfw0puz1g83vl9nwz"; 15 | // private static final String LC_Server_Url = "https://s0g5kxj7.lc-cn-n1-shared.com"; 16 | 17 | private static final String LC_App_Id = "heQFQ0SwoQqiI3gEAcvKXjeR-gzGzoHsz"; 18 | private static final String LC_App_Key = "lNSjPPPDohJjYMJcQSxi9qAm"; 19 | private static final String LC_Server_Url = "https://heqfq0sw.lc-cn-n1-shared.com"; 20 | 21 | private static final String defaultChannelId = "lc-default"; 22 | private static final String defaultChannelName = "leancloud"; 23 | @Override 24 | public void onCreate() { 25 | super.onCreate(); 26 | LeanCloud.setLogLevel(LCLogger.Level.DEBUG); 27 | LeanCloud.initialize(this, LC_App_Id, LC_App_Key, LC_Server_Url); 28 | LCIMOptions.getGlobalOptions().setUnreadNotificationEnabled(true); 29 | 30 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 31 | PushService.createNotificationChannel(this, defaultChannelId, defaultChannelName, 32 | "leancloud notification", NotificationManager.IMPORTANCE_DEFAULT, 33 | false, 0, false, null); 34 | PushService.setDefaultChannelId(this, defaultChannelId); 35 | } 36 | 37 | PushService.setDefaultPushCallback(this, MainActivity.class); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/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/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/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/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | maven { 6 | url "https://oss.sonatype.org/content/groups/public/" 7 | } 8 | mavenLocal() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:3.6.3' 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | google() 19 | jcenter() 20 | maven { 21 | url "https://oss.sonatype.org/content/groups/public/" 22 | } 23 | mavenLocal() 24 | } 25 | } 26 | 27 | rootProject.buildDir = '../build' 28 | subprojects { 29 | project.buildDir = "${rootProject.buildDir}/${project.name}" 30 | } 31 | subprojects { 32 | project.evaluationDependsOn(':app') 33 | } 34 | 35 | task clean(type: Delete) { 36 | delete rootProject.buildDir 37 | } 38 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jan 19 15:48:26 CST 2021 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-5.6.4-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/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/example/assets/test.jpg -------------------------------------------------------------------------------- /example/assets/test.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/example/assets/test.mp3 -------------------------------------------------------------------------------- /example/assets/test.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/example/assets/test.mp4 -------------------------------------------------------------------------------- /example/assets/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/example/assets/test.png -------------------------------------------------------------------------------- /example/assets/test.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/example/assets/test.zip -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '11.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (5.6.4) 3 | - Flutter (1.0.0) 4 | - LeanCloud/Foundation (17.10.1): 5 | - Alamofire (~> 5.4) 6 | - LeanCloud/RTM-no-local-storage (17.10.1): 7 | - LeanCloud/Foundation (= 17.10.1) 8 | - SwiftProtobuf (~> 1.18) 9 | - leancloud_official_plugin (0.0.1): 10 | - Flutter 11 | - LeanCloud/RTM-no-local-storage (~> 17.10) 12 | - SwiftProtobuf (1.20.3) 13 | 14 | DEPENDENCIES: 15 | - Flutter (from `Flutter`) 16 | - leancloud_official_plugin (from `.symlinks/plugins/leancloud_official_plugin/ios`) 17 | 18 | SPEC REPOS: 19 | trunk: 20 | - Alamofire 21 | - LeanCloud 22 | - SwiftProtobuf 23 | 24 | EXTERNAL SOURCES: 25 | Flutter: 26 | :path: Flutter 27 | leancloud_official_plugin: 28 | :path: ".symlinks/plugins/leancloud_official_plugin/ios" 29 | 30 | SPEC CHECKSUMS: 31 | Alamofire: 4e95d97098eacb88856099c4fc79b526a299e48c 32 | Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 33 | LeanCloud: e6de4b8129ab68be7f70a9b77c4304f71c5a1a2c 34 | leancloud_official_plugin: 6f30dc57a7f181193f511355c17519a61dce033d 35 | SwiftProtobuf: b02b5075dcf60c9f5f403000b3b0c202a11b6ae1 36 | 37 | PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 38 | 39 | COCOAPODS: 1.11.3 40 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 5B62D9051314E458D1E051E1 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2E752737E4BCA2E62191F7DE /* Pods_Runner.framework */; }; 13 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 14 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 15 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 16 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = ""; 24 | dstSubfolderSpec = 10; 25 | files = ( 26 | ); 27 | name = "Embed Frameworks"; 28 | runOnlyForDeploymentPostprocessing = 0; 29 | }; 30 | /* End PBXCopyFilesBuildPhase section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 34 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 35 | 2E752737E4BCA2E62191F7DE /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 37 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 38 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 39 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 40 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 41 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 42 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 44 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 45 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 46 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 47 | 9A5733979B4C4FF27D4BAD66 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 48 | DE26B43FE178EDA42F0555E7 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 49 | FC64139027B8A29C2FD6FE57 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | 5B62D9051314E458D1E051E1 /* Pods_Runner.framework in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 04E8BBC6B9C8134DEE0F3D4B /* Frameworks */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 2E752737E4BCA2E62191F7DE /* Pods_Runner.framework */, 68 | ); 69 | name = Frameworks; 70 | sourceTree = ""; 71 | }; 72 | 9740EEB11CF90186004384FC /* Flutter */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 76 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 77 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 78 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 79 | ); 80 | name = Flutter; 81 | sourceTree = ""; 82 | }; 83 | 97C146E51CF9000F007C117D = { 84 | isa = PBXGroup; 85 | children = ( 86 | 9740EEB11CF90186004384FC /* Flutter */, 87 | 97C146F01CF9000F007C117D /* Runner */, 88 | 97C146EF1CF9000F007C117D /* Products */, 89 | B887DA81D3B5BFF2E89D0C48 /* Pods */, 90 | 04E8BBC6B9C8134DEE0F3D4B /* Frameworks */, 91 | ); 92 | sourceTree = ""; 93 | }; 94 | 97C146EF1CF9000F007C117D /* Products */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 97C146EE1CF9000F007C117D /* Runner.app */, 98 | ); 99 | name = Products; 100 | sourceTree = ""; 101 | }; 102 | 97C146F01CF9000F007C117D /* Runner */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 106 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 107 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 108 | 97C147021CF9000F007C117D /* Info.plist */, 109 | 97C146F11CF9000F007C117D /* Supporting Files */, 110 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 111 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 112 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 113 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 114 | ); 115 | path = Runner; 116 | sourceTree = ""; 117 | }; 118 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | ); 122 | name = "Supporting Files"; 123 | sourceTree = ""; 124 | }; 125 | B887DA81D3B5BFF2E89D0C48 /* Pods */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | 9A5733979B4C4FF27D4BAD66 /* Pods-Runner.debug.xcconfig */, 129 | DE26B43FE178EDA42F0555E7 /* Pods-Runner.release.xcconfig */, 130 | FC64139027B8A29C2FD6FE57 /* Pods-Runner.profile.xcconfig */, 131 | ); 132 | path = Pods; 133 | sourceTree = ""; 134 | }; 135 | /* End PBXGroup section */ 136 | 137 | /* Begin PBXNativeTarget section */ 138 | 97C146ED1CF9000F007C117D /* Runner */ = { 139 | isa = PBXNativeTarget; 140 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 141 | buildPhases = ( 142 | 6B23FA458C9273BDF2722130 /* [CP] Check Pods Manifest.lock */, 143 | 9740EEB61CF901F6004384FC /* Run Script */, 144 | 97C146EA1CF9000F007C117D /* Sources */, 145 | 97C146EB1CF9000F007C117D /* Frameworks */, 146 | 97C146EC1CF9000F007C117D /* Resources */, 147 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 148 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 149 | D44A42A1B2617DD80126FC6A /* [CP] Embed Pods Frameworks */, 150 | ); 151 | buildRules = ( 152 | ); 153 | dependencies = ( 154 | ); 155 | name = Runner; 156 | productName = Runner; 157 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 158 | productType = "com.apple.product-type.application"; 159 | }; 160 | /* End PBXNativeTarget section */ 161 | 162 | /* Begin PBXProject section */ 163 | 97C146E61CF9000F007C117D /* Project object */ = { 164 | isa = PBXProject; 165 | attributes = { 166 | LastUpgradeCheck = 1300; 167 | ORGANIZATIONNAME = "The Chromium Authors"; 168 | TargetAttributes = { 169 | 97C146ED1CF9000F007C117D = { 170 | CreatedOnToolsVersion = 7.3.1; 171 | DevelopmentTeam = Y6X8P44TM5; 172 | LastSwiftMigration = 1100; 173 | }; 174 | }; 175 | }; 176 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 177 | compatibilityVersion = "Xcode 3.2"; 178 | developmentRegion = en; 179 | hasScannedForEncodings = 0; 180 | knownRegions = ( 181 | en, 182 | Base, 183 | ); 184 | mainGroup = 97C146E51CF9000F007C117D; 185 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 186 | projectDirPath = ""; 187 | projectRoot = ""; 188 | targets = ( 189 | 97C146ED1CF9000F007C117D /* Runner */, 190 | ); 191 | }; 192 | /* End PBXProject section */ 193 | 194 | /* Begin PBXResourcesBuildPhase section */ 195 | 97C146EC1CF9000F007C117D /* Resources */ = { 196 | isa = PBXResourcesBuildPhase; 197 | buildActionMask = 2147483647; 198 | files = ( 199 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 200 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 201 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 202 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | }; 206 | /* End PBXResourcesBuildPhase section */ 207 | 208 | /* Begin PBXShellScriptBuildPhase section */ 209 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 210 | isa = PBXShellScriptBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | ); 214 | inputPaths = ( 215 | ); 216 | name = "Thin Binary"; 217 | outputPaths = ( 218 | ); 219 | runOnlyForDeploymentPostprocessing = 0; 220 | shellPath = /bin/sh; 221 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin\n"; 222 | }; 223 | 6B23FA458C9273BDF2722130 /* [CP] Check Pods Manifest.lock */ = { 224 | isa = PBXShellScriptBuildPhase; 225 | buildActionMask = 2147483647; 226 | files = ( 227 | ); 228 | inputFileListPaths = ( 229 | ); 230 | inputPaths = ( 231 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 232 | "${PODS_ROOT}/Manifest.lock", 233 | ); 234 | name = "[CP] Check Pods Manifest.lock"; 235 | outputFileListPaths = ( 236 | ); 237 | outputPaths = ( 238 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 239 | ); 240 | runOnlyForDeploymentPostprocessing = 0; 241 | shellPath = /bin/sh; 242 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 243 | showEnvVarsInLog = 0; 244 | }; 245 | 9740EEB61CF901F6004384FC /* Run Script */ = { 246 | isa = PBXShellScriptBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | ); 250 | inputPaths = ( 251 | ); 252 | name = "Run Script"; 253 | outputPaths = ( 254 | ); 255 | runOnlyForDeploymentPostprocessing = 0; 256 | shellPath = /bin/sh; 257 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; 258 | }; 259 | D44A42A1B2617DD80126FC6A /* [CP] Embed Pods Frameworks */ = { 260 | isa = PBXShellScriptBuildPhase; 261 | buildActionMask = 2147483647; 262 | files = ( 263 | ); 264 | inputPaths = ( 265 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", 266 | "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", 267 | "${BUILT_PRODUCTS_DIR}/LeanCloud/LeanCloud.framework", 268 | "${BUILT_PRODUCTS_DIR}/SwiftProtobuf/SwiftProtobuf.framework", 269 | "${BUILT_PRODUCTS_DIR}/leancloud_official_plugin/leancloud_official_plugin.framework", 270 | ); 271 | name = "[CP] Embed Pods Frameworks"; 272 | outputPaths = ( 273 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", 274 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LeanCloud.framework", 275 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftProtobuf.framework", 276 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/leancloud_official_plugin.framework", 277 | ); 278 | runOnlyForDeploymentPostprocessing = 0; 279 | shellPath = /bin/sh; 280 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 281 | showEnvVarsInLog = 0; 282 | }; 283 | /* End PBXShellScriptBuildPhase section */ 284 | 285 | /* Begin PBXSourcesBuildPhase section */ 286 | 97C146EA1CF9000F007C117D /* Sources */ = { 287 | isa = PBXSourcesBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 291 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 292 | ); 293 | runOnlyForDeploymentPostprocessing = 0; 294 | }; 295 | /* End PBXSourcesBuildPhase section */ 296 | 297 | /* Begin PBXVariantGroup section */ 298 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 299 | isa = PBXVariantGroup; 300 | children = ( 301 | 97C146FB1CF9000F007C117D /* Base */, 302 | ); 303 | name = Main.storyboard; 304 | sourceTree = ""; 305 | }; 306 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 307 | isa = PBXVariantGroup; 308 | children = ( 309 | 97C147001CF9000F007C117D /* Base */, 310 | ); 311 | name = LaunchScreen.storyboard; 312 | sourceTree = ""; 313 | }; 314 | /* End PBXVariantGroup section */ 315 | 316 | /* Begin XCBuildConfiguration section */ 317 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 318 | isa = XCBuildConfiguration; 319 | buildSettings = { 320 | ALWAYS_SEARCH_USER_PATHS = NO; 321 | CLANG_ANALYZER_NONNULL = YES; 322 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 323 | CLANG_CXX_LIBRARY = "libc++"; 324 | CLANG_ENABLE_MODULES = YES; 325 | CLANG_ENABLE_OBJC_ARC = YES; 326 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 327 | CLANG_WARN_BOOL_CONVERSION = YES; 328 | CLANG_WARN_COMMA = YES; 329 | CLANG_WARN_CONSTANT_CONVERSION = YES; 330 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 331 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 332 | CLANG_WARN_EMPTY_BODY = YES; 333 | CLANG_WARN_ENUM_CONVERSION = YES; 334 | CLANG_WARN_INFINITE_RECURSION = YES; 335 | CLANG_WARN_INT_CONVERSION = YES; 336 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 337 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 338 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 339 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 340 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 341 | CLANG_WARN_STRICT_PROTOTYPES = YES; 342 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 343 | CLANG_WARN_UNREACHABLE_CODE = YES; 344 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 345 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 346 | COPY_PHASE_STRIP = NO; 347 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 348 | ENABLE_NS_ASSERTIONS = NO; 349 | ENABLE_STRICT_OBJC_MSGSEND = YES; 350 | GCC_C_LANGUAGE_STANDARD = gnu99; 351 | GCC_NO_COMMON_BLOCKS = YES; 352 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 353 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 354 | GCC_WARN_UNDECLARED_SELECTOR = YES; 355 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 356 | GCC_WARN_UNUSED_FUNCTION = YES; 357 | GCC_WARN_UNUSED_VARIABLE = YES; 358 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 359 | MTL_ENABLE_DEBUG_INFO = NO; 360 | SDKROOT = iphoneos; 361 | SUPPORTED_PLATFORMS = iphoneos; 362 | TARGETED_DEVICE_FAMILY = "1,2"; 363 | VALIDATE_PRODUCT = YES; 364 | }; 365 | name = Profile; 366 | }; 367 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 368 | isa = XCBuildConfiguration; 369 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 370 | buildSettings = { 371 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 372 | CLANG_ENABLE_MODULES = YES; 373 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 374 | DEVELOPMENT_TEAM = Y6X8P44TM5; 375 | ENABLE_BITCODE = NO; 376 | FRAMEWORK_SEARCH_PATHS = ( 377 | "$(inherited)", 378 | "$(PROJECT_DIR)/Flutter", 379 | ); 380 | INFOPLIST_FILE = Runner/Info.plist; 381 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 382 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 383 | LIBRARY_SEARCH_PATHS = ( 384 | "$(inherited)", 385 | "$(PROJECT_DIR)/Flutter", 386 | ); 387 | PRODUCT_BUNDLE_IDENTIFIER = cn.leancloud.leancloudPluginExample; 388 | PRODUCT_NAME = "$(TARGET_NAME)"; 389 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 390 | SWIFT_VERSION = 5.0; 391 | VERSIONING_SYSTEM = "apple-generic"; 392 | }; 393 | name = Profile; 394 | }; 395 | 97C147031CF9000F007C117D /* Debug */ = { 396 | isa = XCBuildConfiguration; 397 | buildSettings = { 398 | ALWAYS_SEARCH_USER_PATHS = NO; 399 | CLANG_ANALYZER_NONNULL = YES; 400 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 401 | CLANG_CXX_LIBRARY = "libc++"; 402 | CLANG_ENABLE_MODULES = YES; 403 | CLANG_ENABLE_OBJC_ARC = YES; 404 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 405 | CLANG_WARN_BOOL_CONVERSION = YES; 406 | CLANG_WARN_COMMA = YES; 407 | CLANG_WARN_CONSTANT_CONVERSION = YES; 408 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 409 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 410 | CLANG_WARN_EMPTY_BODY = YES; 411 | CLANG_WARN_ENUM_CONVERSION = YES; 412 | CLANG_WARN_INFINITE_RECURSION = YES; 413 | CLANG_WARN_INT_CONVERSION = YES; 414 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 415 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 416 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 417 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 418 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 419 | CLANG_WARN_STRICT_PROTOTYPES = YES; 420 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 421 | CLANG_WARN_UNREACHABLE_CODE = YES; 422 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 423 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 424 | COPY_PHASE_STRIP = NO; 425 | DEBUG_INFORMATION_FORMAT = dwarf; 426 | ENABLE_STRICT_OBJC_MSGSEND = YES; 427 | ENABLE_TESTABILITY = YES; 428 | GCC_C_LANGUAGE_STANDARD = gnu99; 429 | GCC_DYNAMIC_NO_PIC = NO; 430 | GCC_NO_COMMON_BLOCKS = YES; 431 | GCC_OPTIMIZATION_LEVEL = 0; 432 | GCC_PREPROCESSOR_DEFINITIONS = ( 433 | "DEBUG=1", 434 | "$(inherited)", 435 | ); 436 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 437 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 438 | GCC_WARN_UNDECLARED_SELECTOR = YES; 439 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 440 | GCC_WARN_UNUSED_FUNCTION = YES; 441 | GCC_WARN_UNUSED_VARIABLE = YES; 442 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 443 | MTL_ENABLE_DEBUG_INFO = YES; 444 | ONLY_ACTIVE_ARCH = YES; 445 | SDKROOT = iphoneos; 446 | TARGETED_DEVICE_FAMILY = "1,2"; 447 | }; 448 | name = Debug; 449 | }; 450 | 97C147041CF9000F007C117D /* Release */ = { 451 | isa = XCBuildConfiguration; 452 | buildSettings = { 453 | ALWAYS_SEARCH_USER_PATHS = NO; 454 | CLANG_ANALYZER_NONNULL = YES; 455 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 456 | CLANG_CXX_LIBRARY = "libc++"; 457 | CLANG_ENABLE_MODULES = YES; 458 | CLANG_ENABLE_OBJC_ARC = YES; 459 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 460 | CLANG_WARN_BOOL_CONVERSION = YES; 461 | CLANG_WARN_COMMA = YES; 462 | CLANG_WARN_CONSTANT_CONVERSION = YES; 463 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 464 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 465 | CLANG_WARN_EMPTY_BODY = YES; 466 | CLANG_WARN_ENUM_CONVERSION = YES; 467 | CLANG_WARN_INFINITE_RECURSION = YES; 468 | CLANG_WARN_INT_CONVERSION = YES; 469 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 470 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 471 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 472 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 473 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 474 | CLANG_WARN_STRICT_PROTOTYPES = YES; 475 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 476 | CLANG_WARN_UNREACHABLE_CODE = YES; 477 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 478 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 479 | COPY_PHASE_STRIP = NO; 480 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 481 | ENABLE_NS_ASSERTIONS = NO; 482 | ENABLE_STRICT_OBJC_MSGSEND = YES; 483 | GCC_C_LANGUAGE_STANDARD = gnu99; 484 | GCC_NO_COMMON_BLOCKS = YES; 485 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 486 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 487 | GCC_WARN_UNDECLARED_SELECTOR = YES; 488 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 489 | GCC_WARN_UNUSED_FUNCTION = YES; 490 | GCC_WARN_UNUSED_VARIABLE = YES; 491 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 492 | MTL_ENABLE_DEBUG_INFO = NO; 493 | SDKROOT = iphoneos; 494 | SUPPORTED_PLATFORMS = iphoneos; 495 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 496 | TARGETED_DEVICE_FAMILY = "1,2"; 497 | VALIDATE_PRODUCT = YES; 498 | }; 499 | name = Release; 500 | }; 501 | 97C147061CF9000F007C117D /* Debug */ = { 502 | isa = XCBuildConfiguration; 503 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 504 | buildSettings = { 505 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 506 | CLANG_ENABLE_MODULES = YES; 507 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 508 | DEVELOPMENT_TEAM = Y6X8P44TM5; 509 | ENABLE_BITCODE = NO; 510 | FRAMEWORK_SEARCH_PATHS = ( 511 | "$(inherited)", 512 | "$(PROJECT_DIR)/Flutter", 513 | ); 514 | INFOPLIST_FILE = Runner/Info.plist; 515 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 516 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 517 | LIBRARY_SEARCH_PATHS = ( 518 | "$(inherited)", 519 | "$(PROJECT_DIR)/Flutter", 520 | ); 521 | PRODUCT_BUNDLE_IDENTIFIER = cn.leancloud.leancloudPluginExample; 522 | PRODUCT_NAME = "$(TARGET_NAME)"; 523 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 524 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 525 | SWIFT_VERSION = 5.0; 526 | VERSIONING_SYSTEM = "apple-generic"; 527 | }; 528 | name = Debug; 529 | }; 530 | 97C147071CF9000F007C117D /* Release */ = { 531 | isa = XCBuildConfiguration; 532 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 533 | buildSettings = { 534 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 535 | CLANG_ENABLE_MODULES = YES; 536 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 537 | DEVELOPMENT_TEAM = Y6X8P44TM5; 538 | ENABLE_BITCODE = NO; 539 | FRAMEWORK_SEARCH_PATHS = ( 540 | "$(inherited)", 541 | "$(PROJECT_DIR)/Flutter", 542 | ); 543 | INFOPLIST_FILE = Runner/Info.plist; 544 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 545 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 546 | LIBRARY_SEARCH_PATHS = ( 547 | "$(inherited)", 548 | "$(PROJECT_DIR)/Flutter", 549 | ); 550 | PRODUCT_BUNDLE_IDENTIFIER = cn.leancloud.leancloudPluginExample; 551 | PRODUCT_NAME = "$(TARGET_NAME)"; 552 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 553 | SWIFT_VERSION = 5.0; 554 | VERSIONING_SYSTEM = "apple-generic"; 555 | }; 556 | name = Release; 557 | }; 558 | /* End XCBuildConfiguration section */ 559 | 560 | /* Begin XCConfigurationList section */ 561 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 562 | isa = XCConfigurationList; 563 | buildConfigurations = ( 564 | 97C147031CF9000F007C117D /* Debug */, 565 | 97C147041CF9000F007C117D /* Release */, 566 | 249021D3217E4FDB00AE95B9 /* Profile */, 567 | ); 568 | defaultConfigurationIsVisible = 0; 569 | defaultConfigurationName = Release; 570 | }; 571 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 572 | isa = XCConfigurationList; 573 | buildConfigurations = ( 574 | 97C147061CF9000F007C117D /* Debug */, 575 | 97C147071CF9000F007C117D /* Release */, 576 | 249021D4217E4FDB00AE95B9 /* Profile */, 577 | ); 578 | defaultConfigurationIsVisible = 0; 579 | defaultConfigurationName = Release; 580 | }; 581 | /* End XCConfigurationList section */ 582 | }; 583 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 584 | } 585 | -------------------------------------------------------------------------------- /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 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | import LeanCloud 4 | 5 | @UIApplicationMain 6 | @objc class AppDelegate: FlutterAppDelegate { 7 | override func application( 8 | _ application: UIApplication, 9 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 10 | ) -> Bool { 11 | do { 12 | let app = defaultApp 13 | // let app = signatureApp 14 | LCApplication.logLevel = .all 15 | try LCApplication.default.set( 16 | id: app.id, 17 | key: app.key, 18 | serverURL: app.serverURL) 19 | GeneratedPluginRegistrant.register(with: self) 20 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 21 | } catch { 22 | fatalError("\(error)") 23 | } 24 | } 25 | } 26 | 27 | struct AppInfo { 28 | let id: String 29 | let key: String 30 | let serverURL: String 31 | } 32 | 33 | let defaultApp = AppInfo( 34 | id: "heQFQ0SwoQqiI3gEAcvKXjeR-gzGzoHsz", 35 | key: "lNSjPPPDohJjYMJcQSxi9qAm", 36 | serverURL: "https://heqfq0sw.lc-cn-n1-shared.com" 37 | ) 38 | 39 | let signatureApp = AppInfo( 40 | id: "s0g5kxj7ajtf6n2wt8fqty18p25gmvgrh7b430iuugsde212", 41 | key: "hc7jpfubg5vaurjlezxhfr1t9pqb9w8tfw0puz1g83vl9nwz", 42 | serverURL: "https://s0g5kxj7.lc-cn-n1-shared.com" 43 | ) 44 | -------------------------------------------------------------------------------- /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/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/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/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/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/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/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/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/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/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/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/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/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/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/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/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/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/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/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/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/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/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/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/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/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/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/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/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/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/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/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/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/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 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | 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 | CADisableMinimumFrameDurationOnPhone 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.9.0" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.2.1" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.3.1" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.1" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.16.0" 46 | crypto: 47 | dependency: "direct dev" 48 | description: 49 | name: crypto 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "3.0.1" 53 | cupertino_icons: 54 | dependency: "direct main" 55 | description: 56 | name: cupertino_icons 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.0.3" 60 | fake_async: 61 | dependency: transitive 62 | description: 63 | name: fake_async 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "1.3.1" 67 | flutter: 68 | dependency: "direct main" 69 | description: flutter 70 | source: sdk 71 | version: "0.0.0" 72 | flutter_test: 73 | dependency: "direct dev" 74 | description: flutter 75 | source: sdk 76 | version: "0.0.0" 77 | http: 78 | dependency: "direct dev" 79 | description: 80 | name: http 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "0.13.3" 84 | http_parser: 85 | dependency: transitive 86 | description: 87 | name: http_parser 88 | url: "https://pub.dartlang.org" 89 | source: hosted 90 | version: "4.0.0" 91 | intl: 92 | dependency: transitive 93 | description: 94 | name: intl 95 | url: "https://pub.dartlang.org" 96 | source: hosted 97 | version: "0.17.0" 98 | leancloud_official_plugin: 99 | dependency: "direct dev" 100 | description: 101 | path: ".." 102 | relative: true 103 | source: path 104 | version: "1.0.1" 105 | matcher: 106 | dependency: transitive 107 | description: 108 | name: matcher 109 | url: "https://pub.dartlang.org" 110 | source: hosted 111 | version: "0.12.12" 112 | material_color_utilities: 113 | dependency: transitive 114 | description: 115 | name: material_color_utilities 116 | url: "https://pub.dartlang.org" 117 | source: hosted 118 | version: "0.1.5" 119 | meta: 120 | dependency: transitive 121 | description: 122 | name: meta 123 | url: "https://pub.dartlang.org" 124 | source: hosted 125 | version: "1.8.0" 126 | path: 127 | dependency: transitive 128 | description: 129 | name: path 130 | url: "https://pub.dartlang.org" 131 | source: hosted 132 | version: "1.8.2" 133 | pedantic: 134 | dependency: transitive 135 | description: 136 | name: pedantic 137 | url: "https://pub.dartlang.org" 138 | source: hosted 139 | version: "1.11.1" 140 | sky_engine: 141 | dependency: transitive 142 | description: flutter 143 | source: sdk 144 | version: "0.0.99" 145 | source_span: 146 | dependency: transitive 147 | description: 148 | name: source_span 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "1.9.0" 152 | stack_trace: 153 | dependency: transitive 154 | description: 155 | name: stack_trace 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "1.10.0" 159 | stream_channel: 160 | dependency: transitive 161 | description: 162 | name: stream_channel 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "2.1.0" 166 | string_scanner: 167 | dependency: transitive 168 | description: 169 | name: string_scanner 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "1.1.1" 173 | term_glyph: 174 | dependency: transitive 175 | description: 176 | name: term_glyph 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "1.2.1" 180 | test_api: 181 | dependency: transitive 182 | description: 183 | name: test_api 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "0.4.12" 187 | typed_data: 188 | dependency: transitive 189 | description: 190 | name: typed_data 191 | url: "https://pub.dartlang.org" 192 | source: hosted 193 | version: "1.3.0" 194 | vector_math: 195 | dependency: transitive 196 | description: 197 | name: vector_math 198 | url: "https://pub.dartlang.org" 199 | source: hosted 200 | version: "2.1.2" 201 | sdks: 202 | dart: ">=2.17.0-0 <3.0.0" 203 | flutter: ">=2.0.0" 204 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: leancloud_plugin_example 2 | description: Demonstrates how to use the leancloud_plugin plugin. 3 | publish_to: 'none' 4 | 5 | environment: 6 | sdk: ">=2.12.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: ">=1.0.3 <2.0.0" 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | http: ">=0.13.3 <1.0.0" 20 | crypto: ">=3.0.1 <4.0.0" 21 | 22 | leancloud_official_plugin: 23 | path: ../ 24 | 25 | # For information on the generic Dart part of this file, see the 26 | # following page: https://dart.dev/tools/pub/pubspec 27 | 28 | # The following section is specific to Flutter. 29 | flutter: 30 | assets: 31 | - assets/ 32 | 33 | # The following line ensures that the Material Icons font is 34 | # included with your application, so that you can use the icons in 35 | # the material Icons class. 36 | uses-material-design: true 37 | 38 | # To add assets to your application, add an assets section, like this: 39 | # assets: 40 | # - images/a_dot_burr.jpeg 41 | # - images/a_dot_ham.jpeg 42 | 43 | # An image asset can refer to one or more resolution-specific "variants", see 44 | # https://flutter.dev/assets-and-images/#resolution-aware. 45 | 46 | # For details regarding adding assets from package dependencies, see 47 | # https://flutter.dev/assets-and-images/#from-packages 48 | 49 | # To add custom fonts to your application, add a fonts section here, 50 | # in this "flutter" section. Each entry in this list should have a 51 | # "family" key with the font family name, and a "fonts" key with a 52 | # list giving the asset and other descriptors for the font. For 53 | # example: 54 | # fonts: 55 | # - family: Schyler 56 | # fonts: 57 | # - asset: fonts/Schyler-Regular.ttf 58 | # - asset: fonts/Schyler-Italic.ttf 59 | # style: italic 60 | # - family: Trajan Pro 61 | # fonts: 62 | # - asset: fonts/TrajanPro.ttf 63 | # - asset: fonts/TrajanPro_Bold.ttf 64 | # weight: 700 65 | # 66 | # For details regarding fonts from package dependencies, 67 | # see https://flutter.dev/custom-fonts/#from-packages 68 | -------------------------------------------------------------------------------- /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: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(MyApp()); 17 | 18 | // Verify that platform version is retrieved. 19 | expect( 20 | find.byWidgetPredicate( 21 | (Widget widget) => 22 | widget is Text && widget.data!.startsWith('Running on:'), 23 | ), 24 | findsOneWidget, 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /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 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/LeancloudPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface LeancloudPlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /ios/Classes/LeancloudPlugin.m: -------------------------------------------------------------------------------- 1 | #import "LeancloudPlugin.h" 2 | #if __has_include() 3 | #import 4 | #else 5 | // Support project import fallback if the generated compatibility header 6 | // is not copied when this plugin is created as a library. 7 | // https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 8 | #import "leancloud_official_plugin-Swift.h" 9 | #endif 10 | 11 | @implementation LeancloudPlugin 12 | + (void)registerWithRegistrar:(NSObject*)registrar { 13 | [SwiftLeancloudPlugin registerWithRegistrar:registrar]; 14 | } 15 | @end 16 | -------------------------------------------------------------------------------- /ios/leancloud_official_plugin.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint leancloud_plugin.podspec' to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'leancloud_official_plugin' 7 | s.version = '0.0.1' 8 | s.summary = 'A new flutter plugin project.' 9 | s.description = <<-DESC 10 | A new flutter plugin project. 11 | DESC 12 | s.homepage = 'http://example.com' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Your Company' => 'email@example.com' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.dependency 'Flutter' 18 | s.dependency 'LeanCloud/RTM-no-local-storage', '~> 17.10' 19 | s.platform = :ios, '11.0' 20 | 21 | # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. 22 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } 23 | s.swift_version = '5.0' 24 | end 25 | -------------------------------------------------------------------------------- /lib/leancloud_plugin.dart: -------------------------------------------------------------------------------- 1 | library leancloud_plugin; 2 | 3 | import 'dart:convert'; 4 | import 'dart:typed_data'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:intl/intl.dart'; 7 | 8 | part 'src/client.dart'; 9 | part 'src/conversation.dart'; 10 | part 'src/message.dart'; 11 | part 'src/query.dart'; 12 | -------------------------------------------------------------------------------- /lib/src/client.dart: -------------------------------------------------------------------------------- 1 | part of leancloud_plugin; 2 | 3 | class _Bridge with _Utilities { 4 | static final _Bridge _singleton = _Bridge._internal(); 5 | 6 | factory _Bridge() { 7 | return _Bridge._singleton; 8 | } 9 | 10 | final MethodChannel channel = const MethodChannel('leancloud_plugin'); 11 | final Map clientMap = {}; 12 | 13 | _Bridge._internal() { 14 | channel.setMethodCallHandler((call) async { 15 | final Map args = call.arguments; 16 | final Client? client = clientMap[args['clientId']!]; 17 | if (client == null) { 18 | return {}; 19 | } 20 | switch (call.method) { 21 | case 'onSessionOpen': 22 | if (client.onOpened != null) { 23 | client.onOpened!( 24 | client: client, 25 | ); 26 | } 27 | break; 28 | case 'onSessionResume': 29 | if (client.onResuming != null) { 30 | client.onResuming!( 31 | client: client, 32 | ); 33 | } 34 | break; 35 | case 'onSessionDisconnect': 36 | if (client.onDisconnected != null) { 37 | RTMException? e; 38 | if (isFailure(args)) { 39 | e = errorFrom(args); 40 | } 41 | client.onDisconnected!( 42 | client: client, 43 | exception: e, 44 | ); 45 | } 46 | break; 47 | case 'onSessionClose': 48 | if (client.onClosed != null) { 49 | client.onClosed!( 50 | client: client, 51 | exception: errorFrom(args), 52 | ); 53 | } 54 | break; 55 | case 'onConversationMembersUpdate': 56 | case 'onConversationDataUpdate': 57 | case 'onUnreadMessageCountUpdate': 58 | case 'onLastReceiptTimestampUpdate': 59 | case 'onMessageReceive': 60 | case 'onMessagePatch': 61 | case 'onMessageReceipt': 62 | client._processConversationEvent( 63 | method: call.method, 64 | args: args, 65 | ); 66 | break; 67 | case 'onSignSessionOpen': 68 | if (client._openSignatureHandler != null) { 69 | final Signature sign = await client._openSignatureHandler!( 70 | client: client, 71 | ); 72 | return {'sign': sign._toMap()}; 73 | } 74 | break; 75 | case 'onSignConversation': 76 | if (client._conversationSignatureHandler != null) { 77 | Conversation? conversation; 78 | final String? conversationID = args['conversationId']; 79 | if (conversationID != null) { 80 | conversation = await client._getConversation( 81 | conversationID: conversationID, 82 | ); 83 | } 84 | final Signature sign = await client._conversationSignatureHandler!( 85 | client: client, 86 | conversation: conversation, 87 | targetIDs: args['targetIds'], 88 | action: args['action'], 89 | ); 90 | return {'sign': sign._toMap()}; 91 | } 92 | break; 93 | default: 94 | break; 95 | } 96 | return {}; 97 | }); 98 | } 99 | } 100 | 101 | mixin _Utilities { 102 | bool isFailure(Map result) => result['error'] != null; 103 | 104 | RTMException errorFrom(Map result) { 105 | final Map error = result['error']; 106 | return RTMException( 107 | code: error['code'].toString(), 108 | message: error['message'], 109 | details: error['details']); 110 | } 111 | 112 | Future call({ 113 | required String method, 114 | required Map arguments, 115 | }) async { 116 | final Map result = await _Bridge().channel.invokeMethod( 117 | method, 118 | arguments, 119 | ); 120 | if (isFailure(result)) { 121 | throw errorFrom(result); 122 | } 123 | return result['success']; 124 | } 125 | 126 | static final DateFormat isoDateFormat = DateFormat( 127 | "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", 128 | 'en_US', 129 | ); 130 | 131 | DateTime? parseIsoString(String? isoString) { 132 | DateTime? date; 133 | if (isoString != null) { 134 | date = _Utilities.isoDateFormat.parseStrict(isoString); 135 | } 136 | return date; 137 | } 138 | 139 | DateTime? parseMilliseconds(int? milliseconds) { 140 | DateTime? date; 141 | if (milliseconds != null) { 142 | date = DateTime.fromMillisecondsSinceEpoch(milliseconds); 143 | } 144 | return date; 145 | } 146 | } 147 | 148 | /// Exception of RTM Plugin. 149 | class RTMException implements Exception { 150 | /// The code of the [RTMException], it will never be `null`. 151 | final String code; 152 | 153 | /// The reason of the [RTMException], it is optional. 154 | final String? message; 155 | 156 | /// The supplementary information of the [RTMException], it is optional. 157 | final dynamic details; 158 | 159 | /// To create a [RTMException], [code] is needed. 160 | RTMException({ 161 | required this.code, 162 | this.message, 163 | this.details, 164 | }); 165 | 166 | @override 167 | String toString() => '\nLC.RTM.Exception(' 168 | '\n code: $code,' 169 | '\n essage: $message,' 170 | '\n details: $details,' 171 | '\n)'; 172 | } 173 | 174 | /// IM Signature of RTM Plugin. 175 | class Signature { 176 | /// The signature of the [Signature]. 177 | final String signature; 178 | 179 | /// The timestamp of the [Signature], unit is millisecond. 180 | final int timestamp; 181 | 182 | /// The nonce of the [Signature]. 183 | final String nonce; 184 | 185 | /// To create a [Signature], the unit of [timestamp] is millisecond. 186 | Signature({ 187 | required this.signature, 188 | required this.timestamp, 189 | required this.nonce, 190 | }); 191 | 192 | @override 193 | String toString() => '\nLC.RTM.Signature(' 194 | '\n signature: $signature,' 195 | '\n timestamp: $timestamp,' 196 | '\n nonce: $nonce' 197 | '\n)'; 198 | 199 | Map _toMap() => { 200 | 's': signature, 201 | 't': timestamp, 202 | 'n': nonce, 203 | }; 204 | } 205 | 206 | /// IM Client of RTM Plugin. 207 | class Client with _Utilities { 208 | /// The ID of the [Client], it should not be `null`. 209 | final String id; 210 | 211 | /// The tag of the [Client]. it is optional. 212 | final String? tag; 213 | 214 | /// The map of the [Conversation]s which belong to the [Client] in memory, the key is [Conversation.id]. 215 | final Map conversationMap = {}; 216 | 217 | /// The reopened event of the [client]. 218 | void Function({ 219 | required Client client, 220 | })? onOpened; 221 | 222 | /// The resuming event of the [client]. 223 | void Function({ 224 | required Client client, 225 | })? onResuming; 226 | 227 | /// The disconnected event of the [client], [exception] is optional. 228 | /// 229 | /// This event occurs, for example, when network of local environment unavailable. 230 | void Function({ 231 | required Client client, 232 | RTMException? exception, 233 | })? onDisconnected; 234 | 235 | /// The closed event of the [client], [exception] will never be `null`. 236 | /// 237 | /// This event occurs, for example, [client] has been logged off by server. 238 | void Function({ 239 | required Client client, 240 | required RTMException exception, 241 | })? onClosed; 242 | 243 | /// The [client] has been invited to the [conversation]. 244 | /// 245 | /// [byClientID] means who did it. 246 | /// [atDate] means when did it. 247 | void Function({ 248 | required Client client, 249 | required Conversation conversation, 250 | String? byClientID, 251 | DateTime? atDate, 252 | })? onInvited; 253 | 254 | /// The [client] has been kicked from the [conversation]. 255 | /// 256 | /// [byClientID] means who did it. 257 | /// [atDate] means when did it. 258 | void Function({ 259 | required Client client, 260 | required Conversation conversation, 261 | String? byClientID, 262 | DateTime? atDate, 263 | })? onKicked; 264 | 265 | /// Some [members] have joined to the [conversation]. 266 | /// 267 | /// [byClientID] means who did it. 268 | /// [atDate] means when did it. 269 | void Function({ 270 | required Client client, 271 | required Conversation conversation, 272 | List? members, 273 | String? byClientID, 274 | DateTime? atDate, 275 | })? onMembersJoined; 276 | 277 | /// Some [members] have left from the [conversation]. 278 | /// 279 | /// [byClientID] means who did it. 280 | /// [atDate] means when did it. 281 | void Function({ 282 | required Client client, 283 | required Conversation conversation, 284 | List? members, 285 | String? byClientID, 286 | DateTime? atDate, 287 | })? onMembersLeft; 288 | 289 | /// Current client be blocked from the [conversation]. 290 | /// 291 | /// [byClientID] means who did it. 292 | /// [atDate] means when did it. 293 | void Function({ 294 | required Client client, 295 | required Conversation conversation, 296 | String? byClientID, 297 | DateTime? atDate, 298 | })? onBlocked; 299 | 300 | /// Current client be unblocked from the [conversation]. 301 | /// 302 | /// [byClientID] means who did it. 303 | /// [atDate] means when did it. 304 | void Function({ 305 | required Client client, 306 | required Conversation conversation, 307 | String? byClientID, 308 | DateTime? atDate, 309 | })? onUnblocked; 310 | 311 | /// Some [members] have blocked from the [conversation]. 312 | /// 313 | /// [byClientID] means who did it. 314 | /// [atDate] means when did it. 315 | void Function({ 316 | required Client client, 317 | required Conversation conversation, 318 | List? members, 319 | String? byClientID, 320 | DateTime? atDate, 321 | })? onMembersBlocked; 322 | 323 | /// Some [members] have unblocked from the [conversation]. 324 | /// 325 | /// [byClientID] means who did it. 326 | /// [atDate] means when did it. 327 | void Function({ 328 | required Client client, 329 | required Conversation conversation, 330 | List? members, 331 | String? byClientID, 332 | DateTime? atDate, 333 | })? onMembersUnBlocked; 334 | 335 | /// Current client be muted from the [conversation]. 336 | /// 337 | /// [byClientID] means who did it. 338 | /// [atDate] means when did it. 339 | void Function({ 340 | required Client client, 341 | required Conversation conversation, 342 | String? byClientID, 343 | DateTime? atDate, 344 | })? onMuted; 345 | 346 | /// Current client be unmuted from the [conversation]. 347 | /// 348 | /// [byClientID] means who did it. 349 | /// [atDate] means when did it. 350 | void Function({ 351 | required Client client, 352 | required Conversation conversation, 353 | String? byClientID, 354 | DateTime? atDate, 355 | })? onUnmuted; 356 | 357 | /// Some [members] have muted from the [conversation]. 358 | /// 359 | /// [byClientID] means who did it. 360 | /// [atDate] means when did it. 361 | void Function({ 362 | required Client client, 363 | required Conversation conversation, 364 | List? members, 365 | String? byClientID, 366 | DateTime? atDate, 367 | })? onMembersMuted; 368 | 369 | /// Some [members] have unmuted from the [conversation]. 370 | /// 371 | /// [byClientID] means who did it. 372 | /// [atDate] means when did it. 373 | void Function({ 374 | required Client client, 375 | required Conversation conversation, 376 | List? members, 377 | String? byClientID, 378 | DateTime? atDate, 379 | })? onMembersUnMuted; 380 | 381 | /// The attributes of the [conversation] has been updated. 382 | /// 383 | /// [updatingAttributes] means which attributes to be updated. 384 | /// [updatedAttributes] means result of updating. 385 | /// [byClientID] means who did it. 386 | /// [atDate] means when did it. 387 | void Function({ 388 | required Client client, 389 | required Conversation conversation, 390 | Map? updatingAttributes, 391 | Map? updatedAttributes, 392 | String? byClientID, 393 | DateTime? atDate, 394 | })? onInfoUpdated; 395 | 396 | /// The [Conversation.unreadMessageCount] of the [conversation] has been updated. 397 | void Function({ 398 | required Client client, 399 | required Conversation conversation, 400 | })? onUnreadMessageCountUpdated; 401 | 402 | /// The [Conversation.lastReadAt] of the [conversation] has been updated. 403 | void Function({ 404 | required Client client, 405 | required Conversation conversation, 406 | })? onLastReadAtUpdated; 407 | 408 | /// The [Conversation.lastDeliveredAt] of the [conversation] has been updated. 409 | void Function({ 410 | required Client client, 411 | required Conversation conversation, 412 | })? onLastDeliveredAtUpdated; 413 | 414 | /// [conversation] has a [message]. 415 | /// 416 | /// If [message] is new one, the [Conversation.lastMessage] of [conversation] will be updated. 417 | void Function({ 418 | required Client client, 419 | required Conversation conversation, 420 | required Message message, 421 | })? onMessage; 422 | 423 | /// The sent message in [conversation] has been updated to [updatedMessage]. 424 | /// 425 | /// If [patchCode] or [patchReason] not `null`, means the sent message was updated due to special reason. 426 | void Function({ 427 | required Client client, 428 | required Conversation conversation, 429 | required Message updatedMessage, 430 | int? patchCode, 431 | String? patchReason, 432 | })? onMessageUpdated; 433 | 434 | /// The sent message in the [conversation] has been recalled(updated to [recalledMessage]). 435 | void Function({ 436 | required Client client, 437 | required Conversation conversation, 438 | required RecalledMessage recalledMessage, 439 | })? onMessageRecalled; 440 | 441 | /// The sent message(ID is [messageID]) that send to [conversation] with [receipt] option, has been delivered to the client(ID is [toClientID]). 442 | /// 443 | /// [atDate] means when it occurred. 444 | void Function({ 445 | required Client client, 446 | required Conversation conversation, 447 | String? messageID, 448 | String? toClientID, 449 | DateTime? atDate, 450 | })? onMessageDelivered; 451 | 452 | /// The sent message(ID is [messageID]) that send to [conversation] with [receipt] option, has been read by the client(ID is [toClientID]). 453 | /// 454 | /// [atDate] means when it occurred. 455 | void Function({ 456 | required Client client, 457 | required Conversation conversation, 458 | String? messageID, 459 | String? byClientID, 460 | DateTime? atDate, 461 | })? onMessageRead; 462 | 463 | final Future Function({ 464 | required Client client, 465 | })? _openSignatureHandler; 466 | 467 | final Future Function({ 468 | required Client client, 469 | Conversation? conversation, 470 | List? targetIDs, 471 | String? action, 472 | })? _conversationSignatureHandler; 473 | 474 | /// To create an IM [Client] with an [Client.id] and an optional [Client.tag]. 475 | /// 476 | /// You can implement below signature handlers as required to enable the feature about signature. 477 | /// * [openSignatureHandler] is a handler for [Client.open]. 478 | /// * [conversationSignatureHandler] is a handler for the functions about [Conversation], details as below: 479 | /// * When [action] is `create`, means [Client.createConversation] or [Client.createChatRoom] is invoked. 480 | /// * When [action] is `invite`, means [Conversation.join] or [Conversation.addMembers] is invoked. 481 | /// * When [action] is `kick`, means [Conversation.quit] or [Conversation.removeMembers] is invoked. 482 | Client({ 483 | required this.id, 484 | this.tag, 485 | Future Function({ 486 | required Client client, 487 | })? 488 | openSignatureHandler, 489 | Future Function({ 490 | required Client client, 491 | Conversation? conversation, 492 | List? targetIDs, 493 | String? action, 494 | })? 495 | conversationSignatureHandler, 496 | }) : _openSignatureHandler = openSignatureHandler, 497 | _conversationSignatureHandler = conversationSignatureHandler; 498 | 499 | /// To start IM service. 500 | /// 501 | /// If [Client] init with a valid [Client.tag] and open with non-[reconnect], it will force other clients that has the same [Client.id] and [Client.tag] into closed. 502 | /// If [reconnect] is `true` and this client has been closed by other, the result of this action is a [RTMException], default is `false`. 503 | Future open({ 504 | bool reconnect = false, 505 | }) async { 506 | _Bridge().clientMap[id] = this; 507 | var args = { 508 | 'clientId': id, 509 | 'r': reconnect, 510 | 'signRegistry': { 511 | 'sessionOpen': (_openSignatureHandler != null), 512 | 'conversation': (_conversationSignatureHandler != null), 513 | }, 514 | }; 515 | if (tag != null) { 516 | args['tag'] = tag!; 517 | } 518 | await call( 519 | method: 'openClient', 520 | arguments: args, 521 | ); 522 | } 523 | 524 | /// To end IM service. 525 | Future close() async { 526 | await call( 527 | method: 'closeClient', 528 | arguments: { 529 | 'clientId': id, 530 | }, 531 | ); 532 | _Bridge().clientMap.remove(id); 533 | } 534 | 535 | /// To create a normal [Conversation]. 536 | /// 537 | /// [isUnique] is a special parameter, default is `true`, it affects the creation behavior and property [Conversation.isUnique]. 538 | /// * When it is `true` and the relevant unique [Conversation] not exists in the server, this method will create a new unique [Conversation]. 539 | /// * When it is `true` and the relevant unique [Conversation] exists in the server, this method will return that existing unique [Conversation]. 540 | /// * When it is `false`, this method always create a new non-unique [Conversation]. 541 | /// 542 | /// [members] is the [Conversation.members]. 543 | /// [name] is the [Conversation.name]. 544 | /// [attributes] is the [Conversation.attributes]. 545 | /// 546 | /// Returns an instance of [Conversation]. 547 | Future createConversation({ 548 | bool isUnique = true, 549 | required Set members, 550 | String? name, 551 | Map? attributes, 552 | }) async { 553 | return await _createConversation( 554 | type: _ConversationType.normal, 555 | isUnique: isUnique, 556 | members: members, 557 | name: name, 558 | attributes: attributes, 559 | ); 560 | } 561 | 562 | /// To create a new [ChatRoom]. 563 | /// 564 | /// [name] is the [Conversation.name]. 565 | /// [attributes] is the [Conversation.attributes]. 566 | /// 567 | /// Returns an instance of [ChatRoom]. 568 | Future createChatRoom({ 569 | String? name, 570 | Map? attributes, 571 | }) async { 572 | return await _createConversation( 573 | type: _ConversationType.transient, 574 | name: name, 575 | attributes: attributes, 576 | ); 577 | } 578 | 579 | /// To create a new [TemporaryConversation]. 580 | /// 581 | /// [members] is the [Conversation.members]. 582 | /// [timeToLive] is the [TemporaryConversation.timeToLive]. 583 | /// 584 | /// Returns an instance of [TemporaryConversation]. 585 | Future createTemporaryConversation({ 586 | required Set members, 587 | int? timeToLive, 588 | }) async { 589 | return await _createConversation( 590 | type: _ConversationType.temporary, 591 | members: members, 592 | ttl: timeToLive, 593 | ); 594 | } 595 | 596 | /// To create a new [ConversationQuery]. 597 | ConversationQuery conversationQuery() => 598 | ConversationQuery._from(client: this); 599 | 600 | Future _createConversation({ 601 | required _ConversationType type, 602 | bool? isUnique, 603 | Set? members, 604 | String? name, 605 | Map? attributes, 606 | int? ttl, 607 | }) async { 608 | assert(type != _ConversationType.system); 609 | var args = { 610 | 'clientId': id, 611 | 'conv_type': (isUnique ?? false) ? 0 : (type.index + 1), 612 | }; 613 | if (type != _ConversationType.transient) { 614 | final Set memberSet = members ?? Set(); 615 | memberSet.add(id); 616 | args['m'] = memberSet.toList(); 617 | } 618 | if (name != null) { 619 | args['name'] = name; 620 | } 621 | if (attributes != null) { 622 | args['attr'] = attributes; 623 | } 624 | if (ttl != null) { 625 | args['ttl'] = ttl; 626 | } 627 | final Map rawData = await call( 628 | method: 'createConversation', 629 | arguments: args, 630 | ); 631 | final String conversationID = rawData['objectId']; 632 | Conversation? conversation = conversationMap[conversationID]; 633 | if (conversation != null) { 634 | conversation._rawData = rawData; 635 | } else { 636 | conversation = Conversation._newInstance( 637 | client: this, 638 | rawData: rawData, 639 | ); 640 | conversationMap[conversationID] = conversation; 641 | } 642 | return conversation as T; 643 | } 644 | 645 | Future _getConversation({ 646 | required String conversationID, 647 | }) async { 648 | Conversation? existedConversation = conversationMap[conversationID]; 649 | if (existedConversation != null) { 650 | return existedConversation; 651 | } 652 | var args = { 653 | 'clientId': id, 654 | 'conversationId': conversationID, 655 | }; 656 | final Map rawData = await call( 657 | method: 'getConversation', 658 | arguments: args, 659 | ); 660 | Conversation? conversation = conversationMap[conversationID]; 661 | if (conversation != null) { 662 | conversation._rawData = rawData; 663 | } else { 664 | conversation = Conversation._newInstance( 665 | client: this, 666 | rawData: rawData, 667 | ); 668 | conversationMap[conversationID] = conversation; 669 | } 670 | return conversation; 671 | } 672 | 673 | Future _processConversationEvent({ 674 | required String method, 675 | required Map args, 676 | }) async { 677 | final Conversation conversation = await _getConversation( 678 | conversationID: args['conversationId'], 679 | ); 680 | switch (method) { 681 | case 'onConversationMembersUpdate': 682 | conversation._membersUpdate(args); 683 | break; 684 | case 'onConversationDataUpdate': 685 | conversation._dataUpdate(args); 686 | break; 687 | case 'onUnreadMessageCountUpdate': 688 | conversation._unreadMessageCountUpdate(args); 689 | break; 690 | case 'onLastReceiptTimestampUpdate': 691 | conversation._lastReceiptTimestampUpdate(args); 692 | break; 693 | case 'onMessageReceive': 694 | conversation._messageReceive(args); 695 | break; 696 | case 'onMessagePatch': 697 | conversation._messagePatch(args); 698 | break; 699 | case 'onMessageReceipt': 700 | conversation._messageReceipt(args); 701 | break; 702 | default: 703 | break; 704 | } 705 | } 706 | } 707 | -------------------------------------------------------------------------------- /lib/src/message.dart: -------------------------------------------------------------------------------- 1 | part of leancloud_plugin; 2 | 3 | /// The status for [Message]. 4 | enum MessageStatus { 5 | /// means fail to send. 6 | failed, 7 | 8 | /// initial state. 9 | none, 10 | 11 | /// means in sending. 12 | sending, 13 | 14 | /// means have been sent successfully, [Message.sentTimestamp] will not be `null`. 15 | sent, 16 | 17 | /// means have been delivered to other successfully, [Message.deliveredTimestamp] will not be `null`. 18 | delivered, 19 | 20 | /// means have been read by other successfully, [Message.readTimestamp] will not be `null`. 21 | read, 22 | } 23 | 24 | /// IM Message of RTM Plugin. 25 | class Message with _Utilities { 26 | /// The [Conversation.id] of the [Conversation] which the [Message] belong to. 27 | String? get conversationID => _conversationID; 28 | 29 | /// The ID of the [Message]. 30 | String? get id => _id; 31 | 32 | /// The status of the [Message]. 33 | MessageStatus get status { 34 | var value = _status; 35 | if (value == MessageStatus.sent) { 36 | if (readTimestamp != null) { 37 | value = MessageStatus.read; 38 | } else if (deliveredTimestamp != null) { 39 | value = MessageStatus.delivered; 40 | } 41 | } 42 | return value; 43 | } 44 | 45 | /// The timestamp when send the [Message], unit is millisecond. 46 | int? get sentTimestamp => _timestamp; 47 | 48 | /// The date representation of the [Message.sentTimestamp]. 49 | DateTime? get sentDate => parseMilliseconds(_timestamp); 50 | 51 | /// The [Client.id] of the [Client] who send the [Message]. 52 | String? get fromClientID => _fromClientID; 53 | 54 | /// The timestamp when update the [Message], unit is millisecond. 55 | int? get patchedTimestamp => _patchedTimestamp; 56 | 57 | /// The date representation of the [Message.patchedTimestamp]. 58 | DateTime? get patchedDate => parseMilliseconds(_patchedTimestamp); 59 | 60 | /// The timestamp when the [Message] has been delivered to other. 61 | int? deliveredTimestamp; 62 | 63 | /// The date representation of the [Message.deliveredTimestamp]. 64 | DateTime? get deliveredDate => parseMilliseconds(deliveredTimestamp); 65 | 66 | /// The timestamp when the [Message] has been read by other. 67 | int? readTimestamp; 68 | 69 | /// The date representation of the [Message.readTimestamp]. 70 | DateTime? get readDate => parseMilliseconds(readTimestamp); 71 | 72 | /// Whether all members in the [Conversation] are mentioned by the [Message]. 73 | bool? mentionAll; 74 | 75 | /// The members in the [Conversation] mentioned by the [Message]. 76 | List? mentionMembers; 77 | 78 | /// The string content of the [Message]. 79 | /// 80 | /// If [Message.binaryContent] exists, [Message.stringContent] will be covered by it. 81 | String? stringContent; 82 | 83 | /// The binary content of the [Message]. 84 | Uint8List? binaryContent; 85 | 86 | /// Indicates whether this [Message] is transient. 87 | bool get isTransient => _transient ?? false; 88 | 89 | String? _conversationID; 90 | String? _id; 91 | String? _fromClientID; 92 | String? _currentClientID; 93 | int? _timestamp; 94 | int? _patchedTimestamp; 95 | bool? _transient; 96 | bool? _will; 97 | MessageStatus _status = MessageStatus.none; 98 | 99 | /// To create a new [Message]. 100 | Message(); 101 | 102 | static Message _instanceFrom( 103 | Map rawData, 104 | ) { 105 | Message message = Message(); 106 | final Map? typeMsgData = rawData['typeMsgData']; 107 | String? jsonString; 108 | if (typeMsgData != null) { 109 | final int? typeIndex = typeMsgData['_lctype']; 110 | if (typeIndex != null) { 111 | final TypedMessage Function()? constructor = 112 | TypedMessage._classMap[typeIndex]; 113 | if (constructor != null) { 114 | message = constructor(); 115 | } else { 116 | jsonString = jsonEncode(typeMsgData); 117 | } 118 | } 119 | } 120 | message._loadMap(rawData); 121 | if (jsonString != null) { 122 | message.stringContent = jsonString; 123 | } 124 | message._status = MessageStatus.sent; 125 | return message; 126 | } 127 | 128 | Map _toMap() { 129 | var map = {}; 130 | if (_currentClientID != null) { 131 | map['clientId'] = _currentClientID; 132 | } 133 | if (_conversationID != null) { 134 | map['conversationId'] = _conversationID; 135 | } 136 | if (_id != null) { 137 | map['id'] = _id; 138 | } 139 | if (_fromClientID != null) { 140 | map['from'] = _fromClientID; 141 | } 142 | if (_timestamp != null) { 143 | map['timestamp'] = _timestamp; 144 | } 145 | if (_patchedTimestamp != null) { 146 | map['patchTimestamp'] = _patchedTimestamp; 147 | } 148 | if (_transient != null) { 149 | map['transient'] = _transient; 150 | } 151 | if (deliveredTimestamp != null) { 152 | map['ackAt'] = deliveredTimestamp; 153 | } 154 | if (readTimestamp != null) { 155 | map['readAt'] = readTimestamp; 156 | } 157 | if (mentionAll != null) { 158 | map['mentionAll'] = mentionAll; 159 | } 160 | if (mentionMembers != null) { 161 | map['mentionPids'] = mentionMembers; 162 | } 163 | if (binaryContent != null) { 164 | map['binaryMsg'] = binaryContent; 165 | } else if (stringContent != null) { 166 | map['msg'] = stringContent; 167 | } else if (this is TypedMessage) { 168 | final Map typedMessageRawData = (this as TypedMessage).rawData; 169 | typedMessageRawData['_lctype'] = (this as TypedMessage).type; 170 | map['typeMsgData'] = typedMessageRawData; 171 | } 172 | return map; 173 | } 174 | 175 | void _loadMap(Map data) { 176 | _currentClientID = data['clientId']; 177 | _conversationID = data['conversationId']; 178 | _id = data['id']; 179 | _fromClientID = data['from']; 180 | _timestamp = data['timestamp']; 181 | _patchedTimestamp = data['patchTimestamp']; 182 | final int? ackAt = data['ackAt']; 183 | if (ackAt != null) { 184 | deliveredTimestamp = ackAt; 185 | } 186 | final int? readAt = data['readAt']; 187 | if (readAt != null) { 188 | readTimestamp = readAt; 189 | } 190 | mentionAll = data['mentionAll']; 191 | mentionMembers = data['mentionPids']; 192 | _transient = data['transient']; 193 | stringContent = data['msg']; 194 | binaryContent = data['binaryMsg']; 195 | if (this is TypedMessage) { 196 | (this as TypedMessage)._rawData = data['typeMsgData']; 197 | } 198 | } 199 | } 200 | 201 | /// IM Typed Message of RTM Plugin. 202 | class TypedMessage extends Message { 203 | /// Using [int] to enumerate type of the [TypedMessage]. 204 | int get type => 0; 205 | 206 | /// The custom typed message should be registered before use it. 207 | /// 208 | /// You can register constructor of your custom typed message like this: 209 | /// ``` 210 | /// class YourCustomTypedMessage extends TypedMessage { 211 | /// @override 212 | /// int get type => 1; 213 | /// 214 | /// YourCustomTypedMessage() : super(); 215 | /// } 216 | /// 217 | /// TypedMessage.register(() => YourCustomTypedMessage()); 218 | /// ``` 219 | /// 220 | /// ***Important:*** 221 | /// [TypedMessage.type] of your custom typed message should be a positive number. 222 | static void register( 223 | TypedMessage Function() constructor, 224 | ) { 225 | var instance = constructor(); 226 | if (instance.type < 1) { 227 | throw ArgumentError( 228 | 'type should be a positive number', 229 | ); 230 | } 231 | TypedMessage._classMap[instance.type] = constructor; 232 | } 233 | 234 | static final Map _classMap = { 235 | TypedMessage().type: () => TypedMessage(), 236 | TextMessage().type: () => TextMessage(), 237 | ImageMessage().type: () => ImageMessage(), 238 | AudioMessage().type: () => AudioMessage(), 239 | VideoMessage().type: () => VideoMessage(), 240 | LocationMessage().type: () => LocationMessage(), 241 | FileMessage().type: () => FileMessage(), 242 | RecalledMessage().type: () => RecalledMessage(), 243 | }; 244 | 245 | /// To create a new [TypedMessage]. 246 | TypedMessage() : super(); 247 | 248 | /// The raw data of the [TypedMessage]. 249 | Map get rawData => _rawData; 250 | 251 | /// The default getter for text of the [TypedMessage]. 252 | String? get text => rawData['_lctext']; 253 | 254 | /// The default setter for text of the [TypedMessage]. 255 | set text(String? value) => rawData['_lctext'] = value; 256 | 257 | /// The default getter for attributes of the [TypedMessage]. 258 | Map? get attributes => rawData['_lcattrs']; 259 | 260 | /// The default setter for attributes of the [TypedMessage]. 261 | set attributes(Map? value) => rawData['_lcattrs'] = value; 262 | 263 | Map _rawData = {}; 264 | } 265 | 266 | /// IM Text Message of RTM Plugin. 267 | class TextMessage extends TypedMessage { 268 | @override 269 | int get type => -1; 270 | 271 | /// To create a new [TextMessage]. 272 | TextMessage() : super(); 273 | 274 | /// To create a new [TextMessage] with [text] content. 275 | TextMessage.from({ 276 | required String text, 277 | }) { 278 | this.text = text; 279 | } 280 | } 281 | 282 | /// IM Image Message of RTM Plugin. 283 | class ImageMessage extends FileMessage { 284 | @override 285 | int get type => -2; 286 | 287 | /// The width of the image file, unit is pixel. 288 | double? get width { 289 | double? width; 290 | final Map? metaDataMap = _metaDataMap; 291 | if (metaDataMap != null) { 292 | width = metaDataMap['width']?.toDouble(); 293 | } 294 | return width; 295 | } 296 | 297 | /// The height of the image file, unit is pixel. 298 | double? get height { 299 | double? height; 300 | final Map? metaDataMap = _metaDataMap; 301 | if (metaDataMap != null) { 302 | height = metaDataMap['height']?.toDouble(); 303 | } 304 | return height; 305 | } 306 | 307 | /// To create a new [ImageMessage]. 308 | ImageMessage() : super(); 309 | 310 | /// To create a new [ImageMessage] from [path] or [binaryData] or [url]. 311 | /// 312 | /// [path] is for the local path of the local image file. 313 | /// [binaryData] is for the binary data of the local image file. 314 | /// [url] is for the URL of the remote image file. 315 | /// [format] is for the [FileMessage.format], it is optional. 316 | /// [name] is optional, if provide, the [FileMessage.url] will has a [name] suffix. 317 | /// 318 | /// ***Important:*** 319 | /// You must provide only one of parameters in [path], [binaryData] and [url]. 320 | ImageMessage.from({ 321 | String? path, 322 | Uint8List? binaryData, 323 | String? url, 324 | String? format, 325 | String? name, 326 | }) : super.from( 327 | path: path, 328 | binaryData: binaryData, 329 | url: url, 330 | format: format, 331 | name: name, 332 | ); 333 | } 334 | 335 | /// IM Audio Message of RTM Plugin. 336 | class AudioMessage extends FileMessage { 337 | @override 338 | int get type => -3; 339 | 340 | /// The duration of the audio file, unit is second. 341 | double? get duration { 342 | double? duration; 343 | final Map? metaDataMap = _metaDataMap; 344 | if (metaDataMap != null) { 345 | duration = metaDataMap['duration']?.toDouble(); 346 | } 347 | return duration; 348 | } 349 | 350 | /// To create a new [AudioMessage]. 351 | AudioMessage() : super(); 352 | 353 | /// To create a new [AudioMessage] from [path] or [binaryData] or [url]. 354 | /// 355 | /// [path] is for the local path of the local audio file. 356 | /// [binaryData] is for the binary data of the local audio file. 357 | /// [url] is for the URL of the remote audio file. 358 | /// [format] is for the [FileMessage.format], it is optional. 359 | /// [name] is optional, if provide, the [FileMessage.url] will has a [name] suffix. 360 | /// 361 | /// ***Important:*** 362 | /// You must provide only one of parameters in [path], [binaryData] and [url]. 363 | AudioMessage.from({ 364 | String? path, 365 | Uint8List? binaryData, 366 | String? url, 367 | String? format, 368 | String? name, 369 | }) : super.from( 370 | path: path, 371 | binaryData: binaryData, 372 | url: url, 373 | format: format, 374 | name: name, 375 | ); 376 | } 377 | 378 | /// IM Video Message of RTM Plugin. 379 | class VideoMessage extends FileMessage { 380 | @override 381 | int get type => -4; 382 | 383 | /// The duration of the video file, unit is second. 384 | double? get duration { 385 | double? duration; 386 | final Map? metaDataMap = _metaDataMap; 387 | if (metaDataMap != null) { 388 | duration = metaDataMap['duration']?.toDouble(); 389 | } 390 | return duration; 391 | } 392 | 393 | /// To create a new [VideoMessage]. 394 | VideoMessage() : super(); 395 | 396 | /// To create a new [VideoMessage] from [path] or [binaryData] or [url]. 397 | /// 398 | /// [path] is for the local path of the local video file. 399 | /// [binaryData] is for the binary data of the local video file. 400 | /// [url] is for the URL of the remote video file. 401 | /// [format] is for the [FileMessage.format], it is optional. 402 | /// [name] is optional, if provide, the [FileMessage.url] will has a [name] suffix. 403 | /// 404 | /// ***Important:*** 405 | /// You must provide only one of parameters in [path], [binaryData] and [url]. 406 | VideoMessage.from({ 407 | String? path, 408 | Uint8List? binaryData, 409 | String? url, 410 | String? format, 411 | String? name, 412 | }) : super.from( 413 | path: path, 414 | binaryData: binaryData, 415 | url: url, 416 | format: format, 417 | name: name, 418 | ); 419 | } 420 | 421 | /// IM Location Message of RTM Plugin. 422 | class LocationMessage extends TypedMessage { 423 | @override 424 | int get type => -5; 425 | 426 | /// The latitude of the geolocation. 427 | double? get latitude { 428 | double? latitude; 429 | final Map? locationMap = _locationMap; 430 | if (locationMap != null) { 431 | latitude = locationMap['latitude']?.toDouble(); 432 | } 433 | return latitude; 434 | } 435 | 436 | /// The longitude of the geolocation. 437 | double? get longitude { 438 | double? longitude; 439 | final Map? locationMap = _locationMap; 440 | if (locationMap != null) { 441 | longitude = locationMap['longitude']?.toDouble(); 442 | } 443 | return longitude; 444 | } 445 | 446 | /// To create a new [LocationMessage]. 447 | LocationMessage() : super(); 448 | 449 | /// To create a new [LocationMessage] with [latitude] and [longitude]. 450 | LocationMessage.from({ 451 | required double latitude, 452 | required double longitude, 453 | }) { 454 | _locationMap = { 455 | 'latitude': latitude, 456 | 'longitude': longitude, 457 | }; 458 | } 459 | 460 | Map? get _locationMap => rawData['_lcloc']; 461 | set _locationMap(Map? value) => rawData['_lcloc'] = value; 462 | } 463 | 464 | /// IM File Message of RTM Plugin. 465 | class FileMessage extends TypedMessage { 466 | @override 467 | int get type => -6; 468 | 469 | /// The URL of the file. 470 | String? get url { 471 | String? url; 472 | final Map? fileMap = _fileMap; 473 | if (fileMap != null) { 474 | url = fileMap['url']; 475 | } 476 | return url; 477 | } 478 | 479 | /// The format extension of the file. 480 | String? get format { 481 | String? format; 482 | final Map? metaDataMap = _metaDataMap; 483 | if (metaDataMap != null) { 484 | format = metaDataMap['format']; 485 | } 486 | return format; 487 | } 488 | 489 | /// The size of the file, unit is byte. 490 | double? get size { 491 | double? size; 492 | final Map? metaDataMap = _metaDataMap; 493 | if (metaDataMap != null) { 494 | size = metaDataMap['size']?.toDouble(); 495 | } 496 | return size; 497 | } 498 | 499 | /// To create a new [FileMessage]. 500 | FileMessage() : super(); 501 | 502 | /// To create a new [FileMessage] from [path] or [binaryData] or [url]. 503 | /// 504 | /// [path] is for the local path of the local file. 505 | /// [binaryData] is for the binary data of the local file. 506 | /// [url] is for the URL of the remote file. 507 | /// [format] is for the [FileMessage.format], it is optional. 508 | /// [name] is optional, if provide, the [FileMessage.url] will has a [name] suffix. 509 | /// 510 | /// ***Important:*** 511 | /// You must provide only one of parameters in [path], [binaryData] and [url]. 512 | FileMessage.from({ 513 | String? path, 514 | Uint8List? binaryData, 515 | String? url, 516 | String? format, 517 | String? name, 518 | }) { 519 | int count = 0; 520 | if (path != null) { 521 | count += 1; 522 | } 523 | if (binaryData != null) { 524 | count += 1; 525 | } 526 | if (url != null) { 527 | count += 1; 528 | } 529 | if (count != 1) { 530 | throw ArgumentError( 531 | 'must provide only one of parameters in [path], [binaryData] and [url].', 532 | ); 533 | } 534 | _filePath = path; 535 | _fileData = binaryData; 536 | _fileUrl = url; 537 | _fileFormat = format; 538 | _fileName = name; 539 | } 540 | 541 | String? _filePath; 542 | Uint8List? _fileData; 543 | String? _fileUrl; 544 | String? _fileFormat; 545 | String? _fileName; 546 | 547 | Map? get _fileMap => rawData['_lcfile']; 548 | 549 | Map? get _metaDataMap { 550 | Map? metaData; 551 | final Map? fileMap = _fileMap; 552 | if (fileMap != null) { 553 | metaData = fileMap['metaData']; 554 | } 555 | return metaData; 556 | } 557 | } 558 | 559 | /// IM Recalled Message of RTM Plugin. 560 | class RecalledMessage extends TypedMessage { 561 | @override 562 | int get type => -127; 563 | 564 | /// To create a new [RecalledMessage]. 565 | RecalledMessage() : super(); 566 | } 567 | -------------------------------------------------------------------------------- /lib/src/query.dart: -------------------------------------------------------------------------------- 1 | part of leancloud_plugin; 2 | 3 | /// IM Conversation Query of RTM Plugin. 4 | class ConversationQuery with _Utilities { 5 | /// Which [Client] that the [ConversationQuery] belongs to. 6 | final Client client; 7 | 8 | /// The [String] representation of the where condition. 9 | /// 10 | /// If you want to query [Conversation] by [Conversation.id], can set it like this: 11 | /// ``` 12 | /// query.whereString = jsonEncode({ 13 | /// 'objectId': conversationID, 14 | /// }); 15 | /// ``` 16 | /// 17 | /// ***Important:*** 18 | /// If you set this value, it will overwrite the condition you set with where method, like [ConversationQuery.whereEqualTo] and so on. 19 | /// The default value is `'{"m": clientID}'`, the `clientID` is [ConversationQuery.client.id], it means [Conversation.members] contains `clientID`. 20 | String? whereString; 21 | 22 | /// The order by the key of [Conversation]. 23 | /// 24 | /// ***Important:*** 25 | /// If you set this value, it will overwrite the condition you set with order method, like [ConversationQuery.orderByAscending] and so on. 26 | /// The default value is `-lm`, means the timestamp of the [Conversation.lastMessage] from newest to oldest. 27 | String? sort; 28 | 29 | /// The max count of the query result, default is `10`. 30 | int? limit; 31 | 32 | /// The offset of the query, default is `0`. 33 | int? skip; 34 | 35 | /// Whether the queried [Conversation]s not contain [Conversation.members], default is `false`. 36 | bool? excludeMembers; 37 | 38 | /// Whether the queried [Conversation]s contain [Conversation.lastMessage], default is `false`. 39 | bool? includeLastMessage; 40 | 41 | _LCCompositionalCondition condition = _LCCompositionalCondition(); 42 | 43 | /// The value corresponding to [key] is equal to [value], or the array corresponding to [key] contains [value]. 44 | ConversationQuery whereEqualTo( 45 | String key, 46 | dynamic value, 47 | ) { 48 | condition.whereEqualTo(key, value); 49 | return this; 50 | } 51 | 52 | /// The value corresponding to [key] is not equal to [value], or the array corresponding to [key] does not contain [value]. 53 | ConversationQuery whereNotEqualTo( 54 | String key, 55 | dynamic value, 56 | ) { 57 | condition.whereNotEqualTo(key, value); 58 | return this; 59 | } 60 | 61 | /// [values] contains value corresponding to [key], or [values] contains at least one element in the array corresponding to [key]. 62 | ConversationQuery whereContainedIn( 63 | String key, 64 | List values, 65 | ) { 66 | condition.whereContainedIn(key, values); 67 | return this; 68 | } 69 | 70 | /// [values] does not contain value corresponding to [key], or the field corresponding to [key] does not exist. 71 | ConversationQuery whereNotContainedIn( 72 | String key, 73 | List values, 74 | ) { 75 | condition.whereNotContainedIn(key, values); 76 | return this; 77 | } 78 | 79 | /// The array corresponding to [key] contains all elements in [values]. 80 | ConversationQuery whereContainsAll( 81 | String key, 82 | List values, 83 | ) { 84 | condition.whereContainsAll(key, values); 85 | return this; 86 | } 87 | 88 | /// The field corresponding to [key] exists. 89 | ConversationQuery whereExists( 90 | String key, 91 | ) { 92 | condition.whereExists(key); 93 | return this; 94 | } 95 | 96 | /// The field corresponding to [key] does not exist. 97 | ConversationQuery whereDoesNotExist( 98 | String key, 99 | ) { 100 | condition.whereDoesNotExist(key); 101 | return this; 102 | } 103 | 104 | /// The size of the array corresponding to [key] is equal to [size]. 105 | ConversationQuery whereSizeEqualTo( 106 | String key, 107 | int size, 108 | ) { 109 | condition.whereSizeEqualTo(key, size); 110 | return this; 111 | } 112 | 113 | /// The value corresponding to [key] is greater than [value]. 114 | ConversationQuery whereGreaterThan( 115 | String key, 116 | dynamic value, 117 | ) { 118 | condition.whereGreaterThan(key, value); 119 | return this; 120 | } 121 | 122 | /// The value corresponding to [key] is greater than or equal to [value]. 123 | ConversationQuery whereGreaterThanOrEqualTo( 124 | String key, 125 | dynamic value, 126 | ) { 127 | condition.whereGreaterThanOrEqualTo(key, value); 128 | return this; 129 | } 130 | 131 | /// The value corresponding to [key] is less than [value]. 132 | ConversationQuery whereLessThan( 133 | String key, 134 | dynamic value, 135 | ) { 136 | condition.whereLessThan(key, value); 137 | return this; 138 | } 139 | 140 | /// The value corresponding to [key] is less than or equal to [value]. 141 | ConversationQuery whereLessThanOrEqualTo( 142 | String key, 143 | dynamic value, 144 | ) { 145 | condition.whereLessThanOrEqualTo(key, value); 146 | return this; 147 | } 148 | 149 | /// The string corresponding to [key] has a [prefix]. 150 | ConversationQuery whereStartsWith( 151 | String key, 152 | String prefix, 153 | ) { 154 | condition.whereStartsWith(key, prefix); 155 | return this; 156 | } 157 | 158 | /// The string corresponding to [key] has a [suffix]. 159 | ConversationQuery whereEndsWith( 160 | String key, 161 | String suffix, 162 | ) { 163 | condition.whereEndsWith(key, suffix); 164 | return this; 165 | } 166 | 167 | /// The string corresponding to [key] has a [subString]. 168 | ConversationQuery whereContains( 169 | String key, 170 | String subString, 171 | ) { 172 | condition.whereContains(key, subString); 173 | return this; 174 | } 175 | 176 | /// The string corresponding to [key] matches [regex]. 177 | /// [modifiers] reference: 178 | /// * chinese: https://leancloud.cn/docs/rest_api.html#hash840042035 179 | /// * english: https://docs.mongodb.com/manual/reference/operator/query/regex/#op._S_options 180 | ConversationQuery whereMatches( 181 | String key, 182 | String regex, { 183 | String? modifiers, 184 | }) { 185 | condition.whereMatches(key, regex, modifiers); 186 | return this; 187 | } 188 | 189 | /// The ascending order by the value corresponding to [key], support for multi-field sorting with comma. 190 | ConversationQuery orderByAscending( 191 | String key, 192 | ) { 193 | condition.orderByAscending(key); 194 | return this; 195 | } 196 | 197 | /// The descending order by the value corresponding to [key], support for multi-field sorting with comma. 198 | ConversationQuery orderByDescending( 199 | String key, 200 | ) { 201 | condition.orderByDecending(key); 202 | return this; 203 | } 204 | 205 | /// Adding a ascending order by the value corresponding to [key] to the order. 206 | ConversationQuery addAscendingOrder( 207 | String key, 208 | ) { 209 | condition.addAscendingOrder(key); 210 | return this; 211 | } 212 | 213 | /// Adding a descending order by the value corresponding to [key] to the order. 214 | ConversationQuery addDescendingOrder( 215 | String key, 216 | ) { 217 | condition.addDescendingOrder(key); 218 | return this; 219 | } 220 | 221 | /// Performs a logical AND operation on an array of one or more queries. 222 | static ConversationQuery and( 223 | List queries, 224 | ) { 225 | if (queries.length < 1) { 226 | throw ArgumentError.notNull( 227 | 'queries', 228 | ); 229 | } 230 | ConversationQuery compositionQuery = ConversationQuery._from( 231 | client: queries.first.client, 232 | ); 233 | for (var query in queries) { 234 | if (query.client != compositionQuery.client) { 235 | throw ArgumentError( 236 | 'ConversationQuery.client inconsistency', 237 | ); 238 | } 239 | compositionQuery.condition.add(query.condition); 240 | } 241 | return compositionQuery; 242 | } 243 | 244 | /// Performs a logical OR operation on an array of one or more queries. 245 | static ConversationQuery or( 246 | List queries, 247 | ) { 248 | if (queries.length < 1) { 249 | throw ArgumentError.notNull( 250 | 'queries', 251 | ); 252 | } 253 | ConversationQuery compositionQuery = ConversationQuery._from( 254 | client: queries.first.client, 255 | ); 256 | compositionQuery.condition = _LCCompositionalCondition( 257 | composition: _LCCompositionalCondition.Or, 258 | ); 259 | for (var query in queries) { 260 | if (query.client != compositionQuery.client) { 261 | throw ArgumentError( 262 | 'ConversationQuery.client inconsistency', 263 | ); 264 | } 265 | compositionQuery.condition.add(query.condition); 266 | } 267 | return compositionQuery; 268 | } 269 | 270 | ConversationQuery._from({ 271 | required this.client, 272 | }); 273 | 274 | /// To find the [Conversation]. 275 | /// 276 | /// Returns a [List] of the [Conversation]. 277 | /// 278 | /// ***Important:*** 279 | /// If you want to find [TemporaryConversation], should use [ConversationQuery.findTemporaryConversations]. 280 | Future> find() async { 281 | return await _find(); 282 | } 283 | 284 | /// To find the [TemporaryConversation] by IDs. 285 | /// 286 | /// [temporaryConversationIDs] should not be empty and more than `100`. 287 | /// 288 | /// Returns a [List] of the [TemporaryConversation]. 289 | Future> findTemporaryConversations({ 290 | required List temporaryConversationIDs, 291 | }) async { 292 | if (temporaryConversationIDs.isEmpty || 293 | temporaryConversationIDs.length > 100) { 294 | throw ArgumentError( 295 | 'temporaryConversationIDs.length should in [1...100].', 296 | ); 297 | } 298 | return await _find( 299 | temporaryConversationIDs: temporaryConversationIDs, 300 | ); 301 | } 302 | 303 | Future> _find({ 304 | List? temporaryConversationIDs, 305 | }) async { 306 | bool isIncludeLastMessage = includeLastMessage ?? false; 307 | final List results = await call( 308 | method: 'queryConversation', 309 | arguments: _parameters( 310 | temporaryConversationIDs: temporaryConversationIDs, 311 | ), 312 | ); 313 | return _handleResults( 314 | results, 315 | isIncludeLastMessage, 316 | ); 317 | } 318 | 319 | Map _parameters({ 320 | List? temporaryConversationIDs, 321 | }) { 322 | Map args = { 323 | 'clientId': client.id, 324 | }; 325 | if (temporaryConversationIDs != null) { 326 | args['tempConvIds'] = temporaryConversationIDs; 327 | args['limit'] = temporaryConversationIDs.length; 328 | } else { 329 | Map params = condition._buildParams(); 330 | if (whereString != null) { 331 | args['where'] = whereString; 332 | } else if (params['where'] != null) { 333 | args['where'] = params['where']; 334 | } 335 | if (sort != null) { 336 | args['sort'] = sort; 337 | } else if (params['order'] != null) { 338 | args['sort'] = params['order']; 339 | } 340 | if (skip != null) { 341 | args['skip'] = skip; 342 | } 343 | if (limit != null) { 344 | args['limit'] = limit; 345 | } 346 | } 347 | int flag = 0; 348 | if (excludeMembers ?? false) { 349 | flag ^= 1; 350 | } 351 | if (includeLastMessage ?? false) { 352 | flag ^= 2; 353 | } 354 | if (flag > 0) { 355 | args['flag'] = flag; 356 | } 357 | return args; 358 | } 359 | 360 | List _handleResults( 361 | List results, 362 | bool isIncludeLastMessage, 363 | ) { 364 | List conversations = []; 365 | for (var item in results) { 366 | final String? conversationID = item['objectId']; 367 | if (conversationID != null) { 368 | Conversation? conversation = client.conversationMap[conversationID]; 369 | if (conversation != null) { 370 | conversation._rawData = item; 371 | } else { 372 | conversation = Conversation._newInstance( 373 | client: client, 374 | rawData: item, 375 | ); 376 | client.conversationMap[conversationID] = conversation; 377 | } 378 | if (isIncludeLastMessage) { 379 | dynamic msg = item['msg']; 380 | if (msg is Map) { 381 | conversation._updateLastMessage( 382 | message: Message._instanceFrom( 383 | msg, 384 | ), 385 | ); 386 | } 387 | } 388 | conversations.add(conversation as T); 389 | } 390 | } 391 | return conversations; 392 | } 393 | } 394 | 395 | abstract class _LCQueryCondition { 396 | bool equals(_LCQueryCondition other); 397 | 398 | Map encode(); 399 | } 400 | 401 | class _LCCompositionalCondition extends _LCQueryCondition { 402 | static const String And = '\$and'; 403 | static const String Or = '\$or'; 404 | 405 | String composition; 406 | 407 | List<_LCQueryCondition> conditionList = []; 408 | 409 | List? orderByList; 410 | 411 | _LCCompositionalCondition({ 412 | this.composition = And, 413 | }); 414 | 415 | void whereEqualTo(String key, dynamic value) { 416 | add(_LCEqualCondition(key, value)); 417 | } 418 | 419 | void whereNotEqualTo(String key, dynamic value) { 420 | addOperation(key, '\$ne', value); 421 | } 422 | 423 | void whereContainedIn(String key, Iterable values) { 424 | addOperation(key, '\$in', values); 425 | } 426 | 427 | void whereNotContainedIn(String key, Iterable values) { 428 | addOperation(key, '\$nin', values); 429 | } 430 | 431 | void whereContainsAll(String key, Iterable values) { 432 | addOperation(key, '\$all', values); 433 | } 434 | 435 | void whereExists(String key) { 436 | addOperation(key, '\$exists', true); 437 | } 438 | 439 | void whereDoesNotExist(String key) { 440 | addOperation(key, '\$exists', false); 441 | } 442 | 443 | void whereSizeEqualTo(String key, int size) { 444 | addOperation(key, '\$size', size); 445 | } 446 | 447 | void whereGreaterThan(String key, dynamic value) { 448 | addOperation(key, '\$gt', value); 449 | } 450 | 451 | void whereGreaterThanOrEqualTo(String key, dynamic value) { 452 | addOperation(key, '\$gte', value); 453 | } 454 | 455 | void whereLessThan(String key, dynamic value) { 456 | addOperation(key, '\$lt', value); 457 | } 458 | 459 | void whereLessThanOrEqualTo(String key, dynamic value) { 460 | addOperation(key, '\$lte', value); 461 | } 462 | 463 | void whereStartsWith(String key, String prefix) { 464 | addOperation(key, '\$regex', '^$prefix.*'); 465 | } 466 | 467 | void whereEndsWith(String key, String suffix) { 468 | addOperation(key, '\$regex', '.*$suffix\$'); 469 | } 470 | 471 | void whereContains(String key, String subString) { 472 | addOperation(key, '\$regex', '.*$subString.*'); 473 | } 474 | 475 | void whereMatches(String key, String regex, String? modifiers) { 476 | Map value = { 477 | '\$regex': regex, 478 | }; 479 | if (modifiers != null) { 480 | value['\$options'] = modifiers; 481 | } 482 | add(_LCEqualCondition(key, value)); 483 | } 484 | 485 | void whereMatchesQuery(String key, ConversationQuery query) { 486 | Map inQuery = { 487 | 'where': query.condition, 488 | }; 489 | addOperation(key, '\$inQuery', inQuery); 490 | } 491 | 492 | void whereDoesNotMatchQuery(String key, ConversationQuery query) { 493 | Map inQuery = { 494 | 'where': query.condition, 495 | }; 496 | addOperation(key, '\$notInQuery', inQuery); 497 | } 498 | 499 | void orderByAscending(String key) { 500 | orderByList = []; 501 | orderByList?.add(key); 502 | } 503 | 504 | void orderByDecending(String key) { 505 | orderByAscending('-$key'); 506 | } 507 | 508 | void addAscendingOrder(String key) { 509 | if (orderByList == null) { 510 | orderByList = []; 511 | } 512 | orderByList?.add(key); 513 | } 514 | 515 | void addDescendingOrder(String key) { 516 | addAscendingOrder('-$key'); 517 | } 518 | 519 | void addOperation(String key, String op, dynamic value) { 520 | _LCOperationCondition cond = _LCOperationCondition(key, op, value); 521 | add(cond); 522 | } 523 | 524 | void add(_LCQueryCondition cond) { 525 | conditionList.removeWhere((item) => item.equals(cond)); 526 | conditionList.add(cond); 527 | } 528 | 529 | @override 530 | bool equals(_LCQueryCondition other) { 531 | return false; 532 | } 533 | 534 | @override 535 | Map encode() { 536 | if (conditionList.length == 0) { 537 | return {}; 538 | } 539 | if (conditionList.length == 1) { 540 | return conditionList[0].encode(); 541 | } 542 | return { 543 | composition: _LCEncoder.encodeList(conditionList), 544 | }; 545 | } 546 | 547 | Map _buildParams() { 548 | Map result = {}; 549 | if (conditionList.length > 0) { 550 | result['where'] = jsonEncode(encode()); 551 | } 552 | if (orderByList != null && orderByList!.length > 0) { 553 | result['order'] = orderByList!.join(','); 554 | } 555 | return result; 556 | } 557 | } 558 | 559 | class _LCEqualCondition extends _LCQueryCondition { 560 | String key; 561 | dynamic value; 562 | 563 | _LCEqualCondition(this.key, this.value); 564 | 565 | @override 566 | bool equals(_LCQueryCondition other) { 567 | if (other is _LCEqualCondition) { 568 | return key == other.key; 569 | } 570 | return false; 571 | } 572 | 573 | @override 574 | Map encode() { 575 | return { 576 | key: _LCEncoder.encode(value), 577 | }; 578 | } 579 | } 580 | 581 | class _LCOperationCondition extends _LCQueryCondition { 582 | String key; 583 | String op; 584 | dynamic value; 585 | 586 | _LCOperationCondition(this.key, this.op, this.value); 587 | 588 | @override 589 | bool equals(_LCQueryCondition other) { 590 | if (other is _LCOperationCondition) { 591 | return key == other.key && op == other.op; 592 | } 593 | return false; 594 | } 595 | 596 | @override 597 | Map encode() { 598 | return { 599 | key: { 600 | op: _LCEncoder.encode(value), 601 | } 602 | }; 603 | } 604 | } 605 | 606 | class _LCEncoder with _Utilities { 607 | static dynamic encode(dynamic object) { 608 | if (object is DateTime) { 609 | return encodeDateTime(object); 610 | } 611 | if (object is Uint8List) { 612 | return encodeBytes(object); 613 | } 614 | if (object is List) { 615 | return encodeList(object); 616 | } 617 | if (object is Map) { 618 | return encodeMap(object); 619 | } 620 | if (object is _LCQueryCondition) { 621 | return object.encode(); 622 | } 623 | return object; 624 | } 625 | 626 | static dynamic encodeDateTime( 627 | DateTime dateTime, 628 | ) { 629 | return { 630 | '__type': 'Date', 631 | 'iso': _Utilities.isoDateFormat.format(dateTime), 632 | }; 633 | } 634 | 635 | static dynamic encodeBytes( 636 | Uint8List bytes, 637 | ) { 638 | return { 639 | '__type': 'Bytes', 640 | 'base64': base64Encode(bytes), 641 | }; 642 | } 643 | 644 | static dynamic encodeList(List list) { 645 | List l = []; 646 | for (var item in list) { 647 | l.add(encode(item)); 648 | } 649 | return l; 650 | } 651 | 652 | static dynamic encodeMap(Map map) { 653 | Map m = {}; 654 | map.forEach((key, value) { 655 | m[key] = encode(value); 656 | }); 657 | return m; 658 | } 659 | } 660 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.9.0" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.2.1" 25 | clock: 26 | dependency: transitive 27 | description: 28 | name: clock 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.1.1" 32 | collection: 33 | dependency: transitive 34 | description: 35 | name: collection 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.16.0" 39 | fake_async: 40 | dependency: transitive 41 | description: 42 | name: fake_async 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.3.1" 46 | flutter: 47 | dependency: "direct main" 48 | description: flutter 49 | source: sdk 50 | version: "0.0.0" 51 | flutter_test: 52 | dependency: "direct dev" 53 | description: flutter 54 | source: sdk 55 | version: "0.0.0" 56 | intl: 57 | dependency: "direct main" 58 | description: 59 | name: intl 60 | url: "https://pub.dartlang.org" 61 | source: hosted 62 | version: "0.17.0" 63 | matcher: 64 | dependency: transitive 65 | description: 66 | name: matcher 67 | url: "https://pub.dartlang.org" 68 | source: hosted 69 | version: "0.12.12" 70 | material_color_utilities: 71 | dependency: transitive 72 | description: 73 | name: material_color_utilities 74 | url: "https://pub.dartlang.org" 75 | source: hosted 76 | version: "0.1.5" 77 | meta: 78 | dependency: transitive 79 | description: 80 | name: meta 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "1.8.0" 84 | path: 85 | dependency: transitive 86 | description: 87 | name: path 88 | url: "https://pub.dartlang.org" 89 | source: hosted 90 | version: "1.8.2" 91 | sky_engine: 92 | dependency: transitive 93 | description: flutter 94 | source: sdk 95 | version: "0.0.99" 96 | source_span: 97 | dependency: transitive 98 | description: 99 | name: source_span 100 | url: "https://pub.dartlang.org" 101 | source: hosted 102 | version: "1.9.0" 103 | stack_trace: 104 | dependency: transitive 105 | description: 106 | name: stack_trace 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "1.10.0" 110 | stream_channel: 111 | dependency: transitive 112 | description: 113 | name: stream_channel 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "2.1.0" 117 | string_scanner: 118 | dependency: transitive 119 | description: 120 | name: string_scanner 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "1.1.1" 124 | term_glyph: 125 | dependency: transitive 126 | description: 127 | name: term_glyph 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "1.2.1" 131 | test_api: 132 | dependency: transitive 133 | description: 134 | name: test_api 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "0.4.12" 138 | vector_math: 139 | dependency: transitive 140 | description: 141 | name: vector_math 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "2.1.2" 145 | sdks: 146 | dart: ">=2.17.0-0 <3.0.0" 147 | flutter: ">=2.0.0" 148 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: leancloud_official_plugin 2 | description: An official flutter plugin for LeanCloud real-time message service based on LeanCloud-Swift-SDK and LeanCloud-Java-SDK. 3 | version: 1.0.1 4 | homepage: https://leancloud.cn 5 | 6 | environment: 7 | sdk: ">=2.12.0 <3.0.0" 8 | flutter: ">=2.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | intl: ^0.17.0 14 | 15 | dev_dependencies: 16 | flutter_test: 17 | sdk: flutter 18 | 19 | # For information on the generic Dart part of this file, see the 20 | # following page: https://dart.dev/tools/pub/pubspec 21 | 22 | # The following section is specific to Flutter. 23 | flutter: 24 | # This section identifies this Flutter project as a plugin project. 25 | # The androidPackage and pluginClass identifiers should not ordinarily 26 | # be modified. They are used by the tooling to maintain consistency when 27 | # adding or updating assets for this project. 28 | plugin: 29 | platforms: 30 | android: 31 | package: cn.leancloud.plugin 32 | pluginClass: LeancloudPlugin 33 | ios: 34 | pluginClass: LeancloudPlugin 35 | 36 | # To add assets to your plugin package, add an assets section, like this: 37 | # assets: 38 | # - images/a_dot_burr.jpeg 39 | # - images/a_dot_ham.jpeg 40 | # 41 | # For details regarding assets in packages, see 42 | # https://flutter.dev/assets-and-images/#from-packages 43 | # 44 | # An image asset can refer to one or more resolution-specific "variants", see 45 | # https://flutter.dev/assets-and-images/#resolution-aware. 46 | 47 | # To add custom fonts to your plugin package, add a fonts section here, 48 | # in this "flutter" section. Each entry in this list should have a 49 | # "family" key with the font family name, and a "fonts" key with a 50 | # list giving the asset and other descriptors for the font. For 51 | # example: 52 | # fonts: 53 | # - family: Schyler 54 | # fonts: 55 | # - asset: fonts/Schyler-Regular.ttf 56 | # - asset: fonts/Schyler-Italic.ttf 57 | # style: italic 58 | # - family: Trajan Pro 59 | # fonts: 60 | # - asset: fonts/TrajanPro.ttf 61 | # - asset: fonts/TrajanPro_Bold.ttf 62 | # weight: 700 63 | # 64 | # For details regarding fonts in packages, see 65 | # https://flutter.dev/custom-fonts/#from-packages 66 | -------------------------------------------------------------------------------- /test/leancloud_plugin_test.dart: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/Realtime-SDK-Flutter/ca990e3cbf558610e7ed58237c2bb2c4c531eb9b/test/leancloud_plugin_test.dart --------------------------------------------------------------------------------