├── .DS_Store ├── .github └── ISSUE_TEMPLATE │ └── ------.md ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── android ├── .classpath ├── .gitignore ├── .project ├── build.gradle ├── gradle.properties ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── io │ │ └── rong │ │ └── flutter │ │ └── imlib │ │ ├── MessageFactory.java │ │ ├── RCFlutterConfig.java │ │ ├── RCIMFlutterWrapper.java │ │ ├── RCListenerImpl.java │ │ ├── RCLog.java │ │ ├── RCMessageHandler.java │ │ ├── RCMethodList.java │ │ ├── ResultRecord.java │ │ ├── RongcloudImPlugin.java │ │ ├── Version.java │ │ └── forward │ │ └── CombineMessage.java │ └── res │ ├── layout │ ├── conversation.xml │ ├── conversationlist.xml │ └── main.xml │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .project │ ├── .settings │ │ └── org.eclipse.buildship.core.prefs │ ├── app │ │ ├── .classpath │ │ ├── .project │ │ ├── .settings │ │ │ └── org.eclipse.buildship.core.prefs │ │ ├── agconnect-services.json │ │ ├── build.gradle │ │ ├── google-services.json │ │ ├── lib │ │ │ ├── MiPush_SDK_Client_3_8_2.jar │ │ │ ├── com.heytap.msp-push-2.1.0.aar │ │ │ ├── meizu-push-4.0.7.aar │ │ │ └── vivo_pushsdk-v2.9.0.0.aar │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── rongcloud_im_plugin_example │ │ │ │ │ ├── MainActivity.java │ │ │ │ │ ├── TestMessage.java │ │ │ │ │ ├── message │ │ │ │ │ ├── LocationMessage.java │ │ │ │ │ └── LocationMessageHandler.java │ │ │ │ │ └── push │ │ │ │ │ └── SealNotificationReceiver.java │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ ├── app_icon.png │ │ │ │ └── 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 │ ├── rong_flutter.key │ └── settings.gradle ├── assets │ ├── RCFlutterConf.json │ ├── combine.json │ └── images │ │ ├── burnPicture.png │ │ ├── burnPictureForm.png │ │ ├── default_portrait.png │ │ ├── file_message_icon_apk.png │ │ ├── file_message_icon_audio.png │ │ ├── file_message_icon_else.png │ │ ├── file_message_icon_excel.png │ │ ├── file_message_icon_file.png │ │ ├── file_message_icon_key.png │ │ ├── file_message_icon_numbers.png │ │ ├── file_message_icon_pages.png │ │ ├── file_message_icon_pdf.png │ │ ├── file_message_icon_picture.png │ │ ├── file_message_icon_ppt.png │ │ ├── file_message_icon_video.png │ │ ├── file_message_icon_word.png │ │ ├── logo.png │ │ ├── rc_ic_warning.png │ │ ├── rich_content_msg_default.png │ │ ├── sight_camera_switch.png │ │ ├── sight_message_icon.png │ │ ├── sight_preview_cancel.png │ │ ├── sight_preview_done.png │ │ ├── sight_top_toolbar_close.png │ │ ├── voice_icon.png │ │ ├── voice_icon_reverse.png │ │ └── voice_recoder.gif ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── .last_build_id │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ ├── Flutter.podspec │ │ ├── Release.xcconfig │ │ └── flutter_export_environment.sh │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ └── Runner │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ ├── RCDLocationMessage.h │ │ ├── RCDLocationMessage.m │ │ ├── RCDTestMessage.h │ │ ├── RCDTestMessage.m │ │ ├── Runner.entitlements │ │ └── main.m ├── lib │ ├── im │ │ ├── pages │ │ │ ├── conversation_list_page.dart │ │ │ ├── conversation_page.dart │ │ │ ├── file_preview_page.dart │ │ │ ├── image_preview_page.dart │ │ │ ├── item │ │ │ │ ├── bottom_input_bar.dart │ │ │ │ ├── bottom_tool_bar.dart │ │ │ │ ├── conversation_item.dart │ │ │ │ ├── conversation_list_item.dart │ │ │ │ ├── message_content_list.dart │ │ │ │ ├── message_item_factory.dart │ │ │ │ └── widget_util.dart │ │ │ ├── sight │ │ │ │ ├── record_bottom_item.dart │ │ │ │ ├── record_top_item.dart │ │ │ │ ├── video_play_page.dart │ │ │ │ └── video_record_page.dart │ │ │ └── webview_page.dart │ │ ├── util │ │ │ ├── bloc │ │ │ │ ├── bloc_provider.dart │ │ │ │ └── message_bloc.dart │ │ │ ├── code_util.dart │ │ │ ├── combine_message_util.dart │ │ │ ├── db_manager.dart │ │ │ ├── dialog_util.dart │ │ │ ├── event_bus.dart │ │ │ ├── file.dart │ │ │ ├── file_suffix.dart │ │ │ ├── http_util.dart │ │ │ ├── media_util.dart │ │ │ ├── style.dart │ │ │ ├── time.dart │ │ │ └── user_info_datesource.dart │ │ └── widget │ │ │ └── cachImage │ │ │ ├── cached_image_widget.dart │ │ │ └── cached_network_image_provider.dart │ ├── location_message.dart │ ├── main.dart │ ├── other │ │ ├── chat_debug_page.dart │ │ ├── chatroom_debug_page.dart │ │ ├── contacts_page.dart │ │ ├── debug_page.dart │ │ ├── home_page.dart │ │ ├── login_page.dart │ │ ├── message_read_page.dart │ │ ├── search_message_page.dart │ │ └── select_conversation_page.dart │ ├── router.dart │ ├── test_message.dart │ └── user_data.dart ├── pubspec.lock ├── pubspec.yaml └── test │ └── widget_test.dart ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── RCFlutterConfig.h │ ├── RCFlutterConfig.m │ ├── RCFlutterMessageFactory.h │ ├── RCFlutterMessageFactory.m │ ├── RCFlutterUtil.h │ ├── RCFlutterUtil.m │ ├── RCIMFlutterDefine.h │ ├── RCIMFlutterLog.h │ ├── RCIMFlutterLog.m │ ├── RCIMFlutterWrapper.h │ ├── RCIMFlutterWrapper.m │ ├── RongcloudImPlugin.h │ └── RongcloudImPlugin.m └── rongcloud_im_plugin.podspec ├── lib ├── rongcloud_im_plugin.dart └── src │ ├── common_define.dart │ ├── info │ ├── blocked_message_info.dart │ ├── chatroom_info.dart │ ├── connection_status_convert.dart │ ├── conversation.dart │ ├── conversation_identifier.dart │ ├── conversation_tag_info.dart │ ├── history_message_option.dart │ ├── message.dart │ ├── push_config.dart │ ├── search_conversation_result.dart │ ├── send_message_option.dart │ ├── tag_info.dart │ └── typing_status.dart │ ├── message │ ├── chatroom_kv_notification_message.dart │ ├── combine_message.dart │ ├── file_message.dart │ ├── gif_message.dart │ ├── group_notification_message.dart │ ├── image_message.dart │ ├── location_message.dart │ ├── message_content.dart │ ├── recall_notification_message.dart │ ├── reference_message.dart │ ├── rich_content_message.dart │ ├── sight_message.dart │ ├── text_message.dart │ └── voice_message.dart │ ├── method_key.dart │ ├── rong_im_client.dart │ └── util │ ├── message_factory.dart │ └── type_util.dart ├── pubspec.lock ├── pubspec.yaml ├── rongcloud_im_plugin.iml └── test └── rongcloud_im_plugin_test.dart /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/.DS_Store -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/------.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 问题反馈模板 3 | about: 欢迎体验使用融云 IM Flutter SDK 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 为了快速解决问题,在寻求帮助的时候,请提供以下信息,方便我们快速定位问题 11 | * 本地 Flutter 版本信息 12 | * 异常设备是 iOS 还是 Android,列出具体机型和系统 13 | * 所调用的是什么 API 或者说是什么功能 14 | * 完整的报错信息或者问题描述 15 | * 提供问题发生时必要的日志,并以 txt 形式上传 16 | 17 | 文档链接 https://github.com/rongcloud/rongcloud-im-flutter-sdk/tree/dev/doc 18 | 文档持续更新中…… 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | .vscode/ 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | 9 | .idea/ 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 融云 RongCloud 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /android/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /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/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | rongcloud_im_plugin 4 | Project rongcloud_im_plugin created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | 25 | 1618472525820 26 | 27 | 30 28 | 29 | org.eclipse.core.resources.regexFilterMatcher 30 | node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'io.rong.flutter.imlib' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | repositories { 6 | google() 7 | jcenter() 8 | mavenCentral() 9 | maven { url 'https://developer.huawei.com/repo/' } 10 | } 11 | 12 | dependencies { 13 | classpath 'com.android.tools.build:gradle:3.5.4' 14 | } 15 | } 16 | 17 | rootProject.allprojects { 18 | repositories { 19 | google() 20 | jcenter() 21 | mavenCentral() 22 | maven { url 'https://developer.huawei.com/repo/' } 23 | } 24 | } 25 | 26 | apply plugin: 'com.android.library' 27 | 28 | android { 29 | compileSdkVersion 31 30 | 31 | defaultConfig { 32 | minSdkVersion 20 33 | } 34 | lintOptions { 35 | disable 'InvalidPackage' 36 | } 37 | } 38 | 39 | dependencies { 40 | api 'cn.rongcloud.sdk:im_libcore:5.2.3.2' 41 | api 'cn.rongcloud.sdk:im_chatroom:5.2.3.2' 42 | } 43 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'rongcloud_im_plugin' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /android/src/main/java/io/rong/flutter/imlib/RCFlutterConfig.java: -------------------------------------------------------------------------------- 1 | package io.rong.flutter.imlib; 2 | 3 | import java.util.Map; 4 | 5 | public class RCFlutterConfig { 6 | 7 | private boolean enablePersistentUserInfoCache; 8 | 9 | RCFlutterConfig(){ 10 | 11 | } 12 | 13 | public void updateConf(Map map) { 14 | Map imMap = (Map)map.get("im"); 15 | if(imMap != null) { 16 | setEnablePersistentUserInfoCache((boolean)imMap.get("enablePersistentUserInfoCache")); 17 | } 18 | } 19 | 20 | public boolean isEnablePersistentUserInfoCache() { 21 | return enablePersistentUserInfoCache; 22 | } 23 | 24 | public void setEnablePersistentUserInfoCache(boolean enablePersistentUserInfoCache) { 25 | this.enablePersistentUserInfoCache = enablePersistentUserInfoCache; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /android/src/main/java/io/rong/flutter/imlib/RCListenerImpl.java: -------------------------------------------------------------------------------- 1 | package io.rong.flutter.imlib; 2 | 3 | import android.os.Handler; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import io.flutter.plugin.common.MethodChannel; 9 | import io.rong.imlib.IRongCoreListener; 10 | import io.rong.imlib.chatroom.base.RongChatRoomClient; 11 | import io.rong.imlib.listener.OnReceiveMessageWrapperListener; 12 | import io.rong.imlib.model.Message; 13 | import io.rong.imlib.model.ReceivedProfile; 14 | 15 | /** 16 | * @author panmingda 17 | * @date 2022/1/17 18 | */ 19 | public class RCListenerImpl { 20 | 21 | public RCListenerImpl(MethodChannel channel, Handler handler) { 22 | this.channel = channel; 23 | this.handler = handler; 24 | 25 | receiveMessageWrapperListener = new ReceiveMessageWrapperListenerImpl(); 26 | kvStatusListener = new KVStatusListenerImpl(); 27 | connectionStatusListener = new ConnectionStatusListenerImpl(); 28 | } 29 | 30 | class ReceiveMessageWrapperListenerImpl extends OnReceiveMessageWrapperListener { 31 | @Override 32 | public void onReceivedMessage(final Message message, final ReceivedProfile profile) { 33 | handler.post(new Runnable() { 34 | @Override 35 | public void run() { 36 | String messageS = MessageFactory.getInstance().message2String(message); 37 | final Map map = new HashMap(); 38 | map.put("message", messageS); 39 | map.put("left", profile.getLeft()); 40 | map.put("offline", profile.isOffline()); 41 | map.put("hasPackage", profile.hasPackage()); 42 | 43 | channel.invokeMethod(RCMethodList.MethodCallBackKeyReceiveMessage, map); 44 | } 45 | }); 46 | } 47 | } 48 | 49 | class KVStatusListenerImpl implements RongChatRoomClient.KVStatusListener { 50 | 51 | @Override 52 | public void onChatRoomKVSync(final String roomId) { 53 | handler.post(new Runnable() { 54 | @Override 55 | public void run() { 56 | final Map resultMap = new HashMap<>(); 57 | resultMap.put("roomId", roomId); 58 | channel.invokeMethod(RCMethodList.MethodCallBackChatRoomKVDidSync, resultMap); 59 | } 60 | }); 61 | } 62 | 63 | @Override 64 | public void onChatRoomKVUpdate(final String roomId, final Map chatRoomKvMap) { 65 | handler.post(new Runnable() { 66 | @Override 67 | public void run() { 68 | final Map resultMap = new HashMap<>(); 69 | resultMap.put("roomId", roomId); 70 | resultMap.put("entry", chatRoomKvMap); 71 | channel.invokeMethod(RCMethodList.MethodCallBackChatRoomKVDidUpdate, resultMap); 72 | } 73 | }); 74 | } 75 | 76 | @Override 77 | public void onChatRoomKVRemove(final String roomId, final Map chatRoomKvMap) { 78 | handler.post(new Runnable() { 79 | @Override 80 | public void run() { 81 | final Map resultMap = new HashMap<>(); 82 | resultMap.put("roomId", roomId); 83 | resultMap.put("entry", chatRoomKvMap); 84 | channel.invokeMethod(RCMethodList.MethodCallBackChatRoomKVDidRemove, resultMap); 85 | } 86 | }); 87 | } 88 | } 89 | 90 | class ConnectionStatusListenerImpl implements IRongCoreListener.ConnectionStatusListener { 91 | 92 | @Override 93 | public void onChanged(ConnectionStatus status) { 94 | Map map = new HashMap<>(); 95 | map.put("status", status.getValue()); 96 | channel.invokeMethod(RCMethodList.MethodCallBackKeyConnectionStatusChange, map); 97 | } 98 | } 99 | 100 | private final MethodChannel channel; 101 | private final Handler handler; 102 | 103 | public final ReceiveMessageWrapperListenerImpl receiveMessageWrapperListener; 104 | public final KVStatusListenerImpl kvStatusListener; 105 | public final ConnectionStatusListenerImpl connectionStatusListener; 106 | } 107 | -------------------------------------------------------------------------------- /android/src/main/java/io/rong/flutter/imlib/RCLog.java: -------------------------------------------------------------------------------- 1 | package io.rong.flutter.imlib; 2 | 3 | import android.util.Log; 4 | 5 | public class RCLog { 6 | private static String TAG = "[RC-Flutter-IM] Android "; 7 | public static void i(String msg) { 8 | Log.i(TAG,msg); 9 | } 10 | 11 | public static void e(String msg) { 12 | Log.e(TAG+"error ",msg); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /android/src/main/java/io/rong/flutter/imlib/ResultRecord.java: -------------------------------------------------------------------------------- 1 | package io.rong.flutter.imlib; 2 | 3 | public class ResultRecord { 4 | public boolean isResultReturned; 5 | } 6 | -------------------------------------------------------------------------------- /android/src/main/java/io/rong/flutter/imlib/RongcloudImPlugin.java: -------------------------------------------------------------------------------- 1 | package io.rong.flutter.imlib; 2 | 3 | import android.util.Log; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import io.flutter.embedding.engine.plugins.FlutterPlugin; 8 | import io.flutter.plugin.common.MethodChannel; 9 | import io.flutter.plugin.common.PluginRegistry; 10 | 11 | public class RongcloudImPlugin implements FlutterPlugin { 12 | 13 | public static void registerWith(PluginRegistry.Registrar registrar) { 14 | channel = new MethodChannel(registrar.messenger(), "rongcloud_im_plugin"); 15 | channel.setMethodCallHandler(RCIMFlutterWrapper.getInstance()); 16 | RCIMFlutterWrapper.getInstance().saveContext(registrar.activity().getApplicationContext()); 17 | RCIMFlutterWrapper.getInstance().saveChannel(channel); 18 | RCIMFlutterWrapper.getInstance().initListener(); 19 | } 20 | 21 | @Override 22 | public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { 23 | channel = new MethodChannel(binding.getBinaryMessenger(), "rongcloud_im_plugin"); 24 | channel.setMethodCallHandler(RCIMFlutterWrapper.getInstance()); 25 | RCIMFlutterWrapper.getInstance().saveContext(binding.getApplicationContext()); 26 | RCIMFlutterWrapper.getInstance().saveChannel(channel); 27 | RCIMFlutterWrapper.getInstance().initListener(); 28 | } 29 | 30 | @Override 31 | public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { 32 | Log.e("onDetachedFromEngine", "onDetachedFromEngine"); 33 | RCIMFlutterWrapper.getInstance().releaseListener(); 34 | channel = null; 35 | } 36 | 37 | private static MethodChannel channel; 38 | } -------------------------------------------------------------------------------- /android/src/main/java/io/rong/flutter/imlib/Version.java: -------------------------------------------------------------------------------- 1 | package io.rong.flutter.imlib; 2 | 3 | /** 4 | * @author panmingda 5 | * @date 2021/9/7 6 | */ 7 | public class Version { 8 | public static final String SDK_VERSION = "5.1.8"; 9 | } 10 | -------------------------------------------------------------------------------- /android/src/main/res/layout/conversation.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /android/src/main/res/layout/conversationlist.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | //静态方式集成 ConversationListFragment 8 | 13 | -------------------------------------------------------------------------------- /android/src/main/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /android/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /android/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | IMDemo 3 | 4 | true 5 | 6 | 7 | [图片] 8 | [语音] 9 | [图文] 10 | [位置] 11 | [草稿] 12 | [有人@我] 13 | [文件] 14 | [小视频] 15 | [阅后即焚] 16 | [聊天记录] 17 | [个人名片] 18 | [动态表情] 19 | [红包] 20 | [音视频通话] 21 | 聊天记录 22 | 23 | -------------------------------------------------------------------------------- /android/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /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 | pubspec.lock 33 | 34 | # Android related 35 | **/android/**/gradle-wrapper.jar 36 | **/android/.gradle 37 | **/android/captures/ 38 | **/android/gradlew 39 | **/android/gradlew.bat 40 | **/android/local.properties 41 | **/android/**/GeneratedPluginRegistrant.java 42 | 43 | # iOS/XCode related 44 | **/ios/**/*.mode1v3 45 | **/ios/**/*.mode2v3 46 | **/ios/**/*.moved-aside 47 | **/ios/**/*.pbxuser 48 | **/ios/**/*.perspectivev3 49 | **/ios/**/*sync/ 50 | **/ios/**/.sconsign.dblite 51 | **/ios/**/.tags* 52 | **/ios/**/.vagrant/ 53 | **/ios/**/DerivedData/ 54 | **/ios/**/Icon? 55 | **/ios/**/Pods/ 56 | **/ios/**/.symlinks/ 57 | **/ios/**/profile 58 | **/ios/**/xcuserdata 59 | **/ios/.generated/ 60 | **/ios/Flutter/App.framework 61 | **/ios/Flutter/Flutter.framework 62 | **/ios/Flutter/Flutter.podspec 63 | **/ios/Flutter/Generated.xcconfig 64 | **/ios/Flutter/app.flx 65 | **/ios/Flutter/app.zip 66 | **/ios/Flutter/flutter_assets/ 67 | **/ios/Flutter/flutter_export_environment.sh 68 | **/ios/ServiceDefinitions.json 69 | **/ios/Runner/GeneratedPluginRegistrant.* 70 | 71 | ios/Podfile.lock 72 | 73 | # Exceptions to above rules. 74 | !**/ios/**/default.mode1v3 75 | !**/ios/**/default.mode2v3 76 | !**/ios/**/default.pbxuser 77 | !**/ios/**/default.perspectivev3 78 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 79 | -------------------------------------------------------------------------------- /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: 323373d3333e2cb9c2950e15e8c1cb9c73214ba9 8 | channel: master 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # 融云 IM Flutter Plugin Demo 2 | 3 | ![](https://raw.githubusercontent.com/rongcloud/imkit-flutter-quickstart/master/screenshot1.png) 4 | ![](https://raw.githubusercontent.com/rongcloud/imkit-flutter-quickstart/master/screenshot2.png) 5 | 6 | ##登陆账号 7 | 需要您在 [这里注册登陆账号] (https://sealtalk.rongcloud.cn/#/account/signup),然后用此账号登陆本 Demo。 8 | 9 | ## iOS 初次运行 10 | 11 | 初次安装需要从 pod 下载 IMLib 的 iOS SDK,要花费较长的时间,建议手动更新一下 12 | 13 | 1. Podfile 目录执行 `pod repo update` 命令 14 | 2. Podfile 目录执行 `pod update` 命令 15 | 16 | ### 常见问题 17 | 18 | 1. `audio_recorder` does not specify a Swift version and none of the targets (`Runner`) integrating it have the `SWIFT_VERSION` attribute set. Please contact the author or set the `SWIFT_VERSION` attribute in at least one of the targets that integrate this pod. 19 | 20 | Podfile 上添加 `use_frameworks!` 参数 21 | 22 | Xcode 打开,选择 TARGETS -> Runnder -> Build Settings -> Levels 右边加号 -> Add User-Defined Setting -> 添加字段 `SWIFT_VERSION`,写上 Swift 版本,如 4.0 23 | 24 | ## Android 初次运行 25 | 26 | Android 依赖不同的 Flutter Plugin 可能会出现不同版本的 Gradle 导致编译不通过,可以按需做如下处理 27 | 28 | 1. `将所有的 build.gradle 的 gradle 版本统一`,例如统一改为 3.4.1 29 | 2. `将 gradle-wrapper.properties 的 distributionUrl `版本改为和上一步统一,如上一步是 3.4.1 ,那么此处应该改为 5.1.1 30 | 3. `编译后查看是否存在 AndroidX 的冲突`,如果存在 AndroidX 冲突,那么在 `gradle.properties` 增加如下配置 31 | 32 | ``` 33 | android.useAndroidX=true 34 | android.enableJetifier=true 35 | ``` 36 | 37 | > demo 为了考虑 CPU 架构兼容性问题,在 demo 的 `build.gradle` 中配置的 ndk 仅支持 `armeabi-v7a` 38 | 39 | ``` 40 | ndk { 41 | abiFilters "armeabi-v7a" 42 | } 43 | ``` -------------------------------------------------------------------------------- /example/android/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | android 4 | Project android created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.buildship.core.gradleprojectbuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.buildship.core.gradleprojectnature 16 | 17 | 18 | 19 | 1618472525788 20 | 21 | 30 22 | 23 | org.eclipse.core.resources.regexFilterMatcher 24 | node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /example/android/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | arguments= 2 | auto.sync=false 3 | build.scans.enabled=false 4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) 5 | connection.project.dir= 6 | eclipse.preferences.version=1 7 | gradle.user.home= 8 | java.home=/Library/Java/JavaVirtualMachines/jdk-12.0.1.jdk/Contents/Home 9 | jvm.arguments= 10 | offline.mode=false 11 | override.workspace.settings=true 12 | show.console.view=true 13 | show.executions.view=true 14 | -------------------------------------------------------------------------------- /example/android/app/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /example/android/app/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | app 4 | Project app created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | 25 | 1618472525804 26 | 27 | 30 28 | 29 | org.eclipse.core.resources.regexFilterMatcher 30 | node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /example/android/app/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | connection.project.dir=.. 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /example/android/app/agconnect-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "client": { 3 | "cp_id": "填写您自己的申请的账号信息", 4 | "product_id": "填写您自己的申请的账号信息", 5 | "client_id": "填写您自己的申请的账号信息", 6 | "client_secret": "填写您自己的申请的账号信息", 7 | "app_id": "填写您自己的申请的账号信息", 8 | "package_name": "com.example.rongcloud_im_plugin_example" 9 | }, 10 | "configuration_version": "1.0" 11 | } -------------------------------------------------------------------------------- /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 31 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "com.example.rongcloud_im_plugin_example" 37 | minSdkVersion 23 38 | targetSdkVersion 31 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 42 | 43 | ndk { 44 | abiFilters "armeabi-v7a" , 'arm64-v8a', 'x86' , 'x86_64' 45 | } 46 | //,"x86" 47 | } 48 | 49 | signingConfigs { 50 | config { 51 | storeFile file("rong_flutter.key") 52 | storePassword "rongcloud" 53 | keyAlias "rongcloud" 54 | keyPassword "rongcloud" 55 | } 56 | } 57 | 58 | buildTypes { 59 | release { 60 | // TODO: Add your own signing config for the release build. 61 | // Signing with the debug keys for now, so `flutter run --release` works. 62 | signingConfig signingConfigs.debug 63 | shrinkResources false 64 | minifyEnabled false 65 | } 66 | } 67 | 68 | configurations.all { 69 | resolutionStrategy.eachDependency { details -> 70 | def requested = details.requested 71 | if (requested.group == 'com.android.support') { 72 | if (!requested.name.startsWith("multidex")) { 73 | details.useVersion '26.1.0' 74 | } 75 | } 76 | } 77 | } 78 | } 79 | 80 | flutter { 81 | source '../..' 82 | } 83 | 84 | dependencies { 85 | compile fileTree(include: ['*.jar', '*.aar'], dir: 'lib') 86 | testImplementation 'junit:junit:4.12' 87 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 88 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 89 | 90 | implementation 'com.android.support:appcompat-v7:26.0.0' 91 | } 92 | 93 | -------------------------------------------------------------------------------- /example/android/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "396364826160", 4 | "firebase_url": "https://sealtalk-4be99.firebaseio.com", 5 | "project_id": "sealtalk-4be99", 6 | "storage_bucket": "sealtalk-4be99.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:396364826160:android:a10a4a74ea61b328", 12 | "android_client_info": { 13 | "package_name": "com.example.rongcloud_im_plugin_example" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "", 19 | "client_type": 3 20 | }, 21 | { 22 | "client_id": "", 23 | "client_type": 3 24 | } 25 | ], 26 | "api_key": [ 27 | { 28 | "current_key": "" 29 | } 30 | ], 31 | "services": { 32 | "analytics_service": { 33 | "status": 1 34 | }, 35 | "appinvite_service": { 36 | "status": 1, 37 | "other_platform_oauth_client": [] 38 | }, 39 | "ads_service": { 40 | "status": 2 41 | } 42 | } 43 | } 44 | ], 45 | "configuration_version": "1" 46 | } -------------------------------------------------------------------------------- /example/android/app/lib/MiPush_SDK_Client_3_8_2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/android/app/lib/MiPush_SDK_Client_3_8_2.jar -------------------------------------------------------------------------------- /example/android/app/lib/com.heytap.msp-push-2.1.0.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/android/app/lib/com.heytap.msp-push-2.1.0.aar -------------------------------------------------------------------------------- /example/android/app/lib/meizu-push-4.0.7.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/android/app/lib/meizu-push-4.0.7.aar -------------------------------------------------------------------------------- /example/android/app/lib/vivo_pushsdk-v2.9.0.0.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/android/app/lib/vivo_pushsdk-v2.9.0.0.aar -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 39 | 43 | 44 | 45 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/example/rongcloud_im_plugin_example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.rongcloud_im_plugin_example; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.annotation.Nullable; 6 | 7 | import com.example.rongcloud_im_plugin_example.message.LocationMessage; 8 | 9 | import io.flutter.embedding.android.FlutterActivity; 10 | import io.rong.flutter.imlib.RCIMFlutterWrapper; 11 | 12 | public class MainActivity extends FlutterActivity { 13 | @Override 14 | protected void onCreate(@Nullable Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | RCIMFlutterWrapper.getInstance().registerMessage(LocationMessage.class); 17 | } 18 | 19 | // @Override 20 | // protected void onCreate(Bundle savedInstanceState) { 21 | // super.onCreate(savedInstanceState); 22 | // GeneratedPluginRegistrant.registerWith(this); 23 | // Context con = getApplicationContext(); 24 | // RCIMFlutterWrapper.getInstance().registerMessage(LocationMessage.class); 25 | // //Android 注册自定义消息必须在 init 之后 26 | // //如果注册了自定义消息,但是没有注册消息模板,无法进入 SDK 的聊天页面 27 | // //https://www.rongcloud.cn/docs/android.html 参见文档的"消息自定义" 28 | //// RongIMClient.init(con,"pvxdm17jxjaor"); 29 | //// RCIMFlutterWrapper.getInstance().registerMessage(TestMessage.class); 30 | // 31 | // // 测试Android往Flutter传递数据 32 | //// Timer timer = new Timer(); 33 | //// timer.schedule(new TimerTask() { 34 | //// public void run() { 35 | //// Map map = new HashMap(); 36 | //// map.put("key","android"); 37 | //// RCIMFlutterWrapper.getInstance().sendDataToFlutter(map); 38 | //// } 39 | //// },500); 40 | // } 41 | // 42 | // @Override 43 | // public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { 44 | // GeneratedPluginRegistrant.registerWith(flutterEngine); 45 | // } 46 | } -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/example/rongcloud_im_plugin_example/push/SealNotificationReceiver.java: -------------------------------------------------------------------------------- 1 | package com.example.rongcloud_im_plugin_example.push; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | 6 | import com.example.rongcloud_im_plugin_example.MainActivity; 7 | 8 | import io.rong.push.PushType; 9 | import io.rong.push.notification.PushMessageReceiver; 10 | import io.rong.push.notification.PushNotificationMessage; 11 | 12 | 13 | /** 14 | * 通知广播, 可在此让法中进行通知消息处理和点击自定义跳转 15 | */ 16 | public class SealNotificationReceiver extends PushMessageReceiver { 17 | 18 | @Override 19 | public boolean onNotificationMessageArrived(Context context, PushType pushType, PushNotificationMessage message) { 20 | return false; 21 | } 22 | 23 | @Override 24 | public boolean onNotificationMessageClicked(Context context, PushType pushType, PushNotificationMessage message) { 25 | if (!message.getSourceType().equals(PushNotificationMessage.PushSourceType.FROM_ADMIN)) { 26 | String targetId = message.getTargetId(); 27 | //根据自己需求填写点击跳转逻辑 28 | Intent intentMain = new Intent(context, MainActivity.class); 29 | intentMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 30 | context.startActivity(intentMain); 31 | } 32 | return false; 33 | } 34 | 35 | @Override 36 | public void onThirdPartyPushState(PushType pushType, String action, long resultCode) { 37 | super.onThirdPartyPushState(pushType, action, resultCode); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/android/app/src/main/res/drawable/app_icon.png -------------------------------------------------------------------------------- /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/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/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/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/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/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/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/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/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/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/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 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.4' 10 | } 11 | } 12 | 13 | allprojects { 14 | repositories { 15 | google() 16 | jcenter() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.enableR8=true 5 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /example/android/rong_flutter.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/android/rong_flutter.key -------------------------------------------------------------------------------- /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/RCFlutterConf.json: -------------------------------------------------------------------------------- 1 | { 2 | "im":{ 3 | "enablePersistentUserInfoCache":true 4 | } 5 | } -------------------------------------------------------------------------------- /example/assets/images/burnPicture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/burnPicture.png -------------------------------------------------------------------------------- /example/assets/images/burnPictureForm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/burnPictureForm.png -------------------------------------------------------------------------------- /example/assets/images/default_portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/default_portrait.png -------------------------------------------------------------------------------- /example/assets/images/file_message_icon_apk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/file_message_icon_apk.png -------------------------------------------------------------------------------- /example/assets/images/file_message_icon_audio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/file_message_icon_audio.png -------------------------------------------------------------------------------- /example/assets/images/file_message_icon_else.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/file_message_icon_else.png -------------------------------------------------------------------------------- /example/assets/images/file_message_icon_excel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/file_message_icon_excel.png -------------------------------------------------------------------------------- /example/assets/images/file_message_icon_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/file_message_icon_file.png -------------------------------------------------------------------------------- /example/assets/images/file_message_icon_key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/file_message_icon_key.png -------------------------------------------------------------------------------- /example/assets/images/file_message_icon_numbers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/file_message_icon_numbers.png -------------------------------------------------------------------------------- /example/assets/images/file_message_icon_pages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/file_message_icon_pages.png -------------------------------------------------------------------------------- /example/assets/images/file_message_icon_pdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/file_message_icon_pdf.png -------------------------------------------------------------------------------- /example/assets/images/file_message_icon_picture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/file_message_icon_picture.png -------------------------------------------------------------------------------- /example/assets/images/file_message_icon_ppt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/file_message_icon_ppt.png -------------------------------------------------------------------------------- /example/assets/images/file_message_icon_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/file_message_icon_video.png -------------------------------------------------------------------------------- /example/assets/images/file_message_icon_word.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/file_message_icon_word.png -------------------------------------------------------------------------------- /example/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/logo.png -------------------------------------------------------------------------------- /example/assets/images/rc_ic_warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/rc_ic_warning.png -------------------------------------------------------------------------------- /example/assets/images/rich_content_msg_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/rich_content_msg_default.png -------------------------------------------------------------------------------- /example/assets/images/sight_camera_switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/sight_camera_switch.png -------------------------------------------------------------------------------- /example/assets/images/sight_message_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/sight_message_icon.png -------------------------------------------------------------------------------- /example/assets/images/sight_preview_cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/sight_preview_cancel.png -------------------------------------------------------------------------------- /example/assets/images/sight_preview_done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/sight_preview_done.png -------------------------------------------------------------------------------- /example/assets/images/sight_top_toolbar_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/sight_top_toolbar_close.png -------------------------------------------------------------------------------- /example/assets/images/voice_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/voice_icon.png -------------------------------------------------------------------------------- /example/assets/images/voice_icon_reverse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/voice_icon_reverse.png -------------------------------------------------------------------------------- /example/assets/images/voice_recoder.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/assets/images/voice_recoder.gif -------------------------------------------------------------------------------- /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 | 34 | -------------------------------------------------------------------------------- /example/ios/Flutter/.last_build_id: -------------------------------------------------------------------------------- 1 | 35e2f1b5ed8cfb67df85c44bff45930f -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 9.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/Flutter.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # NOTE: This podspec is NOT to be published. It is only used as a local source! 3 | # This is a generated file; do not edit or check into version control. 4 | # 5 | 6 | Pod::Spec.new do |s| 7 | s.name = 'Flutter' 8 | s.version = '1.0.0' 9 | s.summary = 'High-performance, high-fidelity mobile apps.' 10 | s.homepage = 'https://flutter.io' 11 | s.license = { :type => 'MIT' } 12 | s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } 13 | s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } 14 | s.ios.deployment_target = '9.0' 15 | # Framework linking is handled by Flutter tooling, not CocoaPods. 16 | # Add a placeholder to satisfy `s.dependency 'Flutter'` plugin podspecs. 17 | s.vendored_frameworks = 'path/to/nothing' 18 | end 19 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/flutter_export_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is a generated file; do not edit or check into version control. 3 | export "FLUTTER_ROOT=/Users/panmingda/Documents/Software/flutter" 4 | export "FLUTTER_APPLICATION_PATH=/Users/panmingda/Documents/Publish/rongcloud-im-flutter-sdk/example" 5 | export "COCOAPODS_PARALLEL_CODE_SIGN=true" 6 | export "FLUTTER_TARGET=/Users/panmingda/Documents/Publish/rongcloud-im-flutter-sdk/example/lib/main.dart" 7 | export "FLUTTER_BUILD_DIR=build" 8 | export "FLUTTER_BUILD_NAME=1.0.0" 9 | export "FLUTTER_BUILD_NUMBER=1" 10 | export "DART_DEFINES=Zmx1dHRlci5pbnNwZWN0b3Iuc3RydWN0dXJlZEVycm9ycz10cnVl,RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==" 11 | export "DART_OBFUSCATION=false" 12 | export "TRACK_WIDGET_CREATION=true" 13 | export "TREE_SHAKE_ICONS=false" 14 | export "PACKAGE_CONFIG=/Users/panmingda/Documents/Publish/rongcloud-im-flutter-sdk/example/.dart_tool/package_config.json" 15 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | source 'https://github.com/CocoaPods/Specs.git' 6 | 7 | 8 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 9 | 10 | project 'Runner', { 11 | 'Debug' => :debug, 12 | 'Profile' => :release, 13 | 'Release' => :release, 14 | } 15 | 16 | def flutter_root 17 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 18 | unless File.exist?(generated_xcode_build_settings_path) 19 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 20 | end 21 | 22 | File.foreach(generated_xcode_build_settings_path) do |line| 23 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 24 | return matches[1].strip if matches 25 | end 26 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 27 | end 28 | 29 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 30 | 31 | flutter_ios_podfile_setup 32 | 33 | target 'Runner' do 34 | use_frameworks! 35 | use_modular_headers! 36 | 37 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 38 | end 39 | 40 | post_install do |installer| 41 | installer.pods_project.targets.each do |target| 42 | flutter_additional_ios_build_settings(target) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /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/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | #import 4 | #import "RCDTestMessage.h" 5 | #import "RCDLocationMessage.h" 6 | #import 7 | 8 | @implementation AppDelegate 9 | 10 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 11 | [GeneratedPluginRegistrant registerWithRegistry:self]; 12 | /** 13 | //注册自定义消息流程 14 | 用户只需要调用以下方法注册自定义消息。SDK 内部会在 connectWithToken: 方法里,链接 IM 之前 把所有自定义消息注册到底层 SDK。 15 | */ 16 | [[RCIMFlutterWrapper sharedWrapper] registerMessageType:[RCDTestMessage class]]; 17 | [[RCIMFlutterWrapper sharedWrapper] registerMessageType:[RCDLocationMessage class]]; 18 | 19 | /** 20 | * 推送处理1 (申请推送权限) 21 | */ 22 | if ([application 23 | respondsToSelector:@selector(registerUserNotificationSettings:)]) { 24 | //注册推送, 用于iOS8以及iOS8之后的系统 25 | UIUserNotificationSettings *settings = [UIUserNotificationSettings 26 | settingsForTypes:(UIUserNotificationTypeBadge | 27 | UIUserNotificationTypeSound | 28 | UIUserNotificationTypeAlert) 29 | categories:nil]; 30 | [application registerUserNotificationSettings:settings]; 31 | } 32 | 33 | /** 34 | // 远程推送的内容 35 | NSDictionary *remoteNotificationUserInfo = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]; 36 | // 传递远程推送数据 37 | if (remoteNotificationUserInfo != nil) { 38 | //远程推送的数据,延时之后再调用该接口,防止Flutter尚未初始化就调用,导致Flutter无法接受数据 39 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 40 | [[RCIMFlutterWrapper sharedWrapper] sendDataToFlutter:remoteNotificationUserInfo]; 41 | }); 42 | } 43 | */ 44 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 45 | } 46 | 47 | /** 48 | * 推送处理2 49 | */ 50 | - (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings { 51 | // register to receive notifications 52 | [application registerForRemoteNotifications]; 53 | } 54 | 55 | - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{ 56 | 57 | // [[RCIMFlutterWrapper sharedWrapper] sendDataToFlutter:userInfo]; 58 | } 59 | 60 | 61 | 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /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/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/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/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/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/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/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/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/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/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/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/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/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/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/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/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/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/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/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/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/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/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/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/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/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/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/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/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/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/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/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/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | RC_Flutter_Demo 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0.0 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | NSAllowsArbitraryLoadsInWebContent 30 | 31 | NSAllowsArbitraryLoadsForMedia 32 | 33 | 34 | io.flutter.embedded_views_preview 35 | 36 | NSCameraUsageDescription 37 | 访问相机 38 | NSMicrophoneUsageDescription 39 | 使用麦克风 40 | NSPhotoLibraryUsageDescription 41 | 访问相册 42 | NSPhotoLibraryAddUsageDescription 43 | 访问相册 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIMainStoryboardFile 47 | Main 48 | UISupportedInterfaceOrientations 49 | 50 | UIInterfaceOrientationPortrait 51 | UIInterfaceOrientationLandscapeLeft 52 | UIInterfaceOrientationLandscapeRight 53 | 54 | UISupportedInterfaceOrientations~ipad 55 | 56 | UIInterfaceOrientationPortrait 57 | UIInterfaceOrientationPortraitUpsideDown 58 | UIInterfaceOrientationLandscapeLeft 59 | UIInterfaceOrientationLandscapeRight 60 | 61 | UIViewControllerBasedStatusBarAppearance 62 | 63 | io.flutter.embedded_views_preview 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /example/ios/Runner/RCDLocationMessage.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCDLocationMessage.h 3 | // Runner 4 | // 5 | // Created by 孙浩 on 2021/9/6. 6 | // Copyright © 2021 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | #import 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | /*! 17 | 地理位置消息的类型名 18 | */ 19 | #define RCDLocationMessageTypeIdentifier @"RCD:LBSMsg" 20 | 21 | /*! 22 | 地理位置消息类 23 | 24 | @discussion 地理位置消息类,此消息会进行存储并计入未读消息数。 25 | 26 | @remarks 内容类消息 27 | */ 28 | @interface RCDLocationMessage : RCMessageContent 29 | 30 | /*! 31 | 地理位置的二维坐标 32 | */ 33 | @property (nonatomic, assign) CLLocationCoordinate2D location; 34 | 35 | /*! 36 | 地理位置的名称 37 | */ 38 | @property (nonatomic, copy) NSString *locationName; 39 | 40 | /*! 41 | 地理位置的缩略图 42 | */ 43 | @property (nonatomic, strong) UIImage *thumbnailImage; 44 | 45 | /*! 46 | 初始化地理位置消息 47 | 48 | @param image 地理位置的缩略图 49 | @param location 地理位置的二维坐标 50 | @param locationName 地理位置的名称 51 | @return 地理位置消息的对象 52 | */ 53 | + (instancetype)messageWithLocationImage:(UIImage *)image 54 | location:(CLLocationCoordinate2D)location 55 | locationName:(NSString *)locationName; 56 | 57 | @end 58 | 59 | NS_ASSUME_NONNULL_END 60 | -------------------------------------------------------------------------------- /example/ios/Runner/RCDTestMessage.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCDTestMessage.h 3 | // RCloudMessage 4 | // 5 | // Created by 岑裕 on 15/12/17. 6 | // Copyright © 2015年 RongCloud. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /*! 12 | 测试消息的类型名 13 | */ 14 | #define RCDTestMessageTypeIdentifier @"RCD:TstMsg" 15 | 16 | /*! 17 | Demo测试用的自定义消息类 18 | 19 | @discussion Demo测试用的自定义消息类,此消息会进行存储并计入未读消息数。 20 | */ 21 | @interface RCDTestMessage : RCMessageContent 22 | 23 | /*! 24 | 测试消息的内容 25 | */ 26 | @property(nonatomic, strong) NSString *content; 27 | 28 | /*! 29 | 测试消息的附加信息 30 | */ 31 | @property(nonatomic, strong) NSString *extra; 32 | 33 | /*! 34 | 初始化测试消息 35 | 36 | @param content 文本内容 37 | @return 测试消息对象 38 | */ 39 | + (instancetype)messageWithContent:(NSString *)content; 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /example/ios/Runner/RCDTestMessage.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCDTestMessage.m 3 | // RCloudMessage 4 | // 5 | // Created by 岑裕 on 15/12/17. 6 | // Copyright © 2015年 RongCloud. All rights reserved. 7 | // 8 | 9 | #import "RCDTestMessage.h" 10 | 11 | @implementation RCDTestMessage 12 | 13 | ///初始化 14 | + (instancetype)messageWithContent:(NSString *)content { 15 | RCDTestMessage *text = [[RCDTestMessage alloc] init]; 16 | if (text) { 17 | text.content = content; 18 | } 19 | return text; 20 | } 21 | 22 | ///消息是否存储,是否计入未读数 23 | + (RCMessagePersistent)persistentFlag { 24 | return (MessagePersistent_ISPERSISTED | MessagePersistent_ISCOUNTED); 25 | } 26 | 27 | /// NSCoding 28 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 29 | self = [super init]; 30 | if (self) { 31 | self.content = [aDecoder decodeObjectForKey:@"content"]; 32 | self.extra = [aDecoder decodeObjectForKey:@"extra"]; 33 | } 34 | return self; 35 | } 36 | 37 | /// NSCoding 38 | - (void)encodeWithCoder:(NSCoder *)aCoder { 39 | [aCoder encodeObject:self.content forKey:@"content"]; 40 | [aCoder encodeObject:self.extra forKey:@"extra"]; 41 | } 42 | 43 | ///将消息内容编码成json 44 | - (NSData *)encode { 45 | NSMutableDictionary *dataDict = [NSMutableDictionary dictionary]; 46 | [dataDict setObject:self.content forKey:@"content"]; 47 | if (self.extra) { 48 | [dataDict setObject:self.extra forKey:@"extra"]; 49 | } 50 | 51 | if (self.senderUserInfo) { 52 | [dataDict setObject:[self encodeUserInfo:self.senderUserInfo] forKey:@"user"]; 53 | } 54 | 55 | NSData *data = [NSJSONSerialization dataWithJSONObject:dataDict options:kNilOptions error:nil]; 56 | return data; 57 | } 58 | 59 | ///将json解码生成消息内容 60 | - (void)decodeWithData:(NSData *)data { 61 | if (data) { 62 | __autoreleasing NSError *error = nil; 63 | 64 | NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error]; 65 | 66 | if (dictionary) { 67 | self.content = dictionary[@"content"]; 68 | self.extra = dictionary[@"extra"]; 69 | 70 | NSDictionary *userinfoDic = dictionary[@"user"]; 71 | [self decodeUserInfo:userinfoDic]; 72 | } 73 | } 74 | } 75 | 76 | /// 会话列表中显示的摘要 77 | - (NSString *)conversationDigest { 78 | return self.content; 79 | } 80 | 81 | ///消息的类型名 82 | + (NSString *)getObjectName { 83 | return RCDTestMessageTypeIdentifier; 84 | } 85 | 86 | @end 87 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/lib/im/pages/image_preview_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:rongcloud_im_plugin/rongcloud_im_plugin.dart'; 5 | 6 | import '../util/media_util.dart'; 7 | 8 | class ImagePreviewPage extends StatefulWidget { 9 | final Message? message; 10 | 11 | const ImagePreviewPage({Key? key, this.message}) : super(key: key); 12 | 13 | @override 14 | State createState() { 15 | return _ImagePreviewPageState(message); 16 | } 17 | } 18 | 19 | class _ImagePreviewPageState extends State { 20 | final Message? message; 21 | 22 | _ImagePreviewPageState(this.message); 23 | 24 | //优先加载本地路径图片,否则加载网络图片 25 | Widget getImageWidget() { 26 | String? localPath; 27 | String? remoteUrl; 28 | if (message!.content is GifMessage) { 29 | GifMessage msg = message!.content as GifMessage; 30 | localPath = msg.localPath; 31 | remoteUrl = msg.remoteUrl; 32 | } else { 33 | ImageMessage msg = message!.content as ImageMessage; 34 | localPath = msg.localPath; 35 | remoteUrl = msg.imageUri; 36 | } 37 | Widget widget; 38 | if (localPath != null) { 39 | String path = MediaUtil.instance!.getCorrectedLocalPath(localPath)!; 40 | File file = File(path); 41 | if (file != null && file.existsSync()) { 42 | widget = Image.file(file); 43 | } else { 44 | widget = Image.network( 45 | remoteUrl!, 46 | fit: BoxFit.cover, 47 | loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) { 48 | if (loadingProgress == null) return child; 49 | return Center( 50 | child: CircularProgressIndicator( 51 | value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null, 52 | ), 53 | ); 54 | }, 55 | ); 56 | } 57 | } else { 58 | widget = Image.network( 59 | remoteUrl!, 60 | fit: BoxFit.cover, 61 | loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) { 62 | if (loadingProgress == null) return child; 63 | return Center( 64 | child: CircularProgressIndicator( 65 | value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null, 66 | ), 67 | ); 68 | }, 69 | ); 70 | } 71 | // Container container = Container( 72 | // margin: EdgeInsets.all(2), 73 | // child: widget, 74 | // alignment: Alignment.center, 75 | // ); 76 | // return container; 77 | return widget; 78 | // return container; 79 | } 80 | 81 | @override 82 | Widget build(BuildContext context) { 83 | return Scaffold( 84 | appBar: AppBar( 85 | title: Text("图片预览"), 86 | ), 87 | body: SingleChildScrollView( 88 | child: getImageWidget(), 89 | )); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /example/lib/im/pages/item/bottom_tool_bar.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer' as developer; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | import '../../util/style.dart'; 6 | 7 | class BottomToolBar extends StatefulWidget { 8 | BottomToolBarDelegate? delegate; 9 | 10 | BottomToolBar(BottomToolBarDelegate delegate) { 11 | this.delegate = delegate; 12 | } 13 | 14 | @override 15 | State createState() { 16 | return _BottomToolBarState(delegate); 17 | } 18 | } 19 | 20 | class _BottomToolBarState extends State { 21 | String pageName = "example.BottomToolBar"; 22 | BottomToolBarDelegate? delegate; 23 | 24 | _BottomToolBarState(BottomToolBarDelegate? delegate) { 25 | this.delegate = delegate; 26 | } 27 | 28 | @override 29 | void initState() { 30 | super.initState(); 31 | } 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return Container( 36 | color: Colors.white, 37 | child: Row( 38 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 39 | children: [ 40 | IconButton( 41 | icon: Icon(Icons.delete), 42 | iconSize: RCLayout.BottomIconLayoutSize, 43 | onPressed: () { 44 | tapDelete(); 45 | }, 46 | ), 47 | IconButton( 48 | icon: Icon(Icons.forward), 49 | iconSize: RCLayout.BottomIconLayoutSize, 50 | onPressed: () { 51 | tapForward(); 52 | }, 53 | ), 54 | ], 55 | ), 56 | ); 57 | } 58 | 59 | void tapDelete() { 60 | if (this.delegate != null) { 61 | this.delegate!.didTapDelete(); 62 | } else { 63 | developer.log("没有实现 BottomToolBarDelegate", name: pageName); 64 | } 65 | } 66 | 67 | void tapForward() { 68 | if (this.delegate != null) { 69 | this.delegate!.didTapForward(); 70 | } else { 71 | developer.log("没有实现 BottomToolBarDelegate", name: pageName); 72 | } 73 | } 74 | } 75 | 76 | abstract class BottomToolBarDelegate { 77 | void didTapDelete(); 78 | 79 | void didTapForward(); 80 | } 81 | -------------------------------------------------------------------------------- /example/lib/im/pages/sight/record_top_item.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer' as developer; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:percent_indicator/linear_percent_indicator.dart'; 5 | 6 | enum RecordState { 7 | //正常 [返回,切换摄像头] 8 | Normal, 9 | // 录制前加载 10 | RecordLoading, 11 | //录制 [返回,进度条] 12 | Recording, 13 | //预览 [返回] 14 | Preview 15 | } 16 | 17 | class TopRecordItem extends StatefulWidget { 18 | TopRecordItemDelegate? delegate; 19 | late _TopRecordItemState state; 20 | 21 | TopRecordItem(TopRecordItemDelegate delegate) { 22 | this.delegate = delegate; 23 | this.state = _TopRecordItemState(delegate); 24 | } 25 | 26 | void updateRecordState(RecordState s) { 27 | state.updateRecordState(s); 28 | } 29 | 30 | @override 31 | _TopRecordItemState createState() => state; 32 | } 33 | 34 | class _TopRecordItemState extends State { 35 | String pageName = "example.TopRecordItem"; 36 | TopRecordItemDelegate? delegate; 37 | 38 | RecordState currentRecordState = RecordState.Normal; 39 | 40 | _TopRecordItemState(TopRecordItemDelegate delegate) { 41 | this.delegate = delegate; 42 | } 43 | 44 | void updateRecordState(RecordState s) { 45 | setState(() { 46 | currentRecordState = s; 47 | }); 48 | } 49 | 50 | Widget recordLine() { 51 | return LinearPercentIndicator( 52 | width: MediaQuery.of(context).size.width - 40 - 25 - 40 - 35 - 30, 53 | animation: true, 54 | animationDuration: 10000, 55 | percent: 1, 56 | progressColor: Colors.white, 57 | ); 58 | } 59 | 60 | @override 61 | Widget build(BuildContext context) { 62 | return Container( 63 | height: 100, 64 | width: MediaQuery.of(context).size.width, 65 | child: Row( 66 | children: [ 67 | SizedBox( 68 | width: 40, 69 | ), 70 | GestureDetector( 71 | onTap: () { 72 | onPop(); 73 | }, 74 | child: Container( 75 | width: 25, 76 | height: 25, 77 | child: currentRecordState != RecordState.Recording && currentRecordState != RecordState.RecordLoading ? Image.asset("assets/images/sight_top_toolbar_close.png") : Container(), 78 | ), 79 | ), 80 | SizedBox( 81 | width: 15, 82 | ), 83 | Expanded( 84 | child: Container( 85 | child: currentRecordState == RecordState.Recording ? recordLine() : Container(), 86 | ), 87 | ), 88 | GestureDetector( 89 | onTap: () { 90 | currentRecordState == RecordState.Normal ? onSwitchCamera() : Container(); 91 | }, 92 | child: Container( 93 | width: 35, 94 | height: 35, 95 | child: currentRecordState == RecordState.Normal ? Image.asset("assets/images/sight_camera_switch.png") : Container(), 96 | ), 97 | ), 98 | SizedBox( 99 | width: 40, 100 | ), 101 | ], 102 | ), 103 | ); 104 | } 105 | 106 | void onPop() { 107 | if (delegate != null) { 108 | delegate!.didPop(); 109 | } else { 110 | developer.log("没有实现 didPop", name: pageName); 111 | } 112 | } 113 | 114 | void onSwitchCamera() { 115 | if (delegate != null) { 116 | delegate!.didSwitchCamera(); 117 | } else { 118 | developer.log("没有实现 didSwitchCamera", name: pageName); 119 | } 120 | } 121 | 122 | @override 123 | void dispose() { 124 | super.dispose(); 125 | } 126 | } 127 | 128 | abstract class TopRecordItemDelegate { 129 | //点击叉号按钮 130 | void didPop(); 131 | 132 | //切换摄像头 133 | void didSwitchCamera(); 134 | } 135 | -------------------------------------------------------------------------------- /example/lib/im/pages/sight/video_play_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:rongcloud_im_plugin/rongcloud_im_plugin.dart'; 5 | import 'package:video_player/video_player.dart'; 6 | 7 | class VideoPlayPage extends StatefulWidget { 8 | final Message? message; 9 | 10 | const VideoPlayPage({Key? key, this.message}) : super(key: key); 11 | 12 | @override 13 | State createState() { 14 | return _VideoPlayPageState(message); 15 | } 16 | } 17 | 18 | class _VideoPlayPageState extends State { 19 | final Message? message; 20 | 21 | VideoPlayerController? videoPlayerController; 22 | SightMessage? sightMessage; 23 | 24 | _VideoPlayPageState(this.message); 25 | 26 | @override 27 | void initState() { 28 | super.initState(); 29 | initVideoController(); 30 | } 31 | 32 | @override 33 | void dispose() { 34 | videoPlayerController?.dispose(); 35 | super.dispose(); 36 | } 37 | 38 | void initVideoController() async { 39 | sightMessage = message?.content as SightMessage?; 40 | if (sightMessage?.localPath != null && sightMessage?.localPath != "") { 41 | videoPlayerController = VideoPlayerController.file(File(sightMessage!.localPath!)); 42 | } else { 43 | //TODO 是否需要做缓存? VideoPlayerController.network 每次都会下载一遍视频 44 | videoPlayerController = VideoPlayerController.network(sightMessage!.remoteUrl!); 45 | } 46 | await videoPlayerController?.initialize(); 47 | await videoPlayerController?.play(); 48 | setState(() {}); 49 | } 50 | 51 | void onPop() { 52 | Navigator.pop(context); 53 | } 54 | 55 | @override 56 | Widget build(BuildContext context) { 57 | return Stack( 58 | children: [ 59 | VideoPlayer(videoPlayerController!), 60 | Container( 61 | height: 100, 62 | width: MediaQuery.of(context).size.width, 63 | child: Row( 64 | children: [ 65 | SizedBox( 66 | width: 40, 67 | ), 68 | GestureDetector( 69 | onTap: () { 70 | onPop(); 71 | }, 72 | child: Container(width: 25, height: 25, child: Image.asset("assets/images/sight_top_toolbar_close.png")), 73 | ) 74 | ], 75 | ), 76 | ) 77 | ], 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /example/lib/im/util/bloc/bloc_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | abstract class BlocBase { 4 | void dispose(); 5 | } 6 | 7 | class BlocProvider extends StatefulWidget { 8 | BlocProvider({ 9 | Key? key, 10 | required this.child, 11 | required this.bloc, 12 | }) : super(key: key); 13 | 14 | final T bloc; 15 | final Widget child; 16 | 17 | @override 18 | _BlocProviderState createState() => _BlocProviderState(); 19 | 20 | static T of(BuildContext context) { 21 | final type = _typeOf>(); 22 | BlocProvider provider = context.findAncestorWidgetOfExactType() as BlocProvider; 23 | return provider.bloc; 24 | } 25 | 26 | static Type _typeOf() => T; 27 | } 28 | 29 | class _BlocProviderState extends State> { 30 | @override 31 | void dispose() { 32 | widget.bloc.dispose(); 33 | super.dispose(); 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | return widget.child; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example/lib/im/util/bloc/message_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:rxdart/subjects.dart'; 2 | 3 | import 'bloc_provider.dart'; 4 | 5 | class MessageBloc extends BlocBase { 6 | MessageInfoWrapState? warpInfo; 7 | 8 | // 列表数据 9 | BehaviorSubject _listDataController = BehaviorSubject(sync: true); 10 | 11 | Sink get inListData => _listDataController.sink; 12 | 13 | Stream get outListData => _listDataController.stream; 14 | 15 | @override 16 | void dispose() { 17 | _listDataController.close(); 18 | } 19 | 20 | void updateMessageList(List messageList) { 21 | warpInfo = MessageInfoWrapState(messageList: messageList); 22 | inListData.add(warpInfo); 23 | } 24 | } 25 | 26 | class MessageInfoWrapState { 27 | MessageInfoWrapState({this.messageList}); 28 | 29 | List? messageList; 30 | } 31 | -------------------------------------------------------------------------------- /example/lib/im/util/code_util.dart: -------------------------------------------------------------------------------- 1 | class CodeUtil { 2 | /// 具体业务错误码 3 | static String? codeString(int? code) { 4 | String key = code.toString() != null ? code.toString() : ""; 5 | return codeMap[key]; 6 | } 7 | 8 | static Map codeMap = { 9 | "": "未找到对应错误", 10 | "-1": "未知错误(预留)", 11 | "0": "成功", 12 | "405": "已被对方加入黑名单,消息发送失败。", 13 | "5004": "超时", 14 | "20604": "发送消息频率过高,1秒钟最多只允许发送5条消息", 15 | "22406": "当前用户不在该群组中", 16 | "22408": "当前用户在群组中已被禁言", 17 | "23406": "当前用户不在该聊天室中", 18 | "23408": "当前用户在该聊天室中已被禁言", 19 | "23409": "当前用户已被踢出并禁止加入聊天室。被禁止的时间取决于服务端调用踢出接口时传入的时间。", 20 | "23410": "聊天室不存在", 21 | "23411": "聊天室成员超限", 22 | "23412": "聊天室接口参数无效。请确认参数是否为空或者有效。", 23 | "23414": "聊天室云存储业务未开通", 24 | "23423": "超过聊天室的最大状态设置数,1 个聊天室默认最多设置 100 个", 25 | "23424": "聊天室中非法覆盖状态值,状态已存在,没有权限覆盖", 26 | "23425": "超过聊天室中状态设置频率,1 个聊天室 1 秒钟最多设置和删除状态 100 次", 27 | "23426": "聊天室状态存储功能没有开通,请联系商务开通", 28 | "23427": "聊天室状态值不存在", 29 | "23428": "KV列表并未完全设置成功", 30 | "23429": "KV数量超过最大数量限制", 31 | "34004": "聊天室状态未同步完成", 32 | "30001": "当前连接不可用(连接已经被释放)", 33 | "30002": "当前连接不可用", 34 | "30003": "客户端发送消息请求,融云服务端响应超时。", 35 | "33001": "SDK没有初始化", 36 | "33002": "数据库错误", 37 | "33003": "开发者接口调用时传入的参数错误", 38 | "33007": "历史消息云存储业务未开通", 39 | "30016": "消息大小超限", 40 | "25101": "撤回消息参数无效", 41 | "26001": "push 设置参数无效", 42 | "20605": "操作被禁止", 43 | "20606": "操作不支持。仅私有云有效,服务端禁用了该操作。", 44 | "21501": "发送的消息中包含敏感词", 45 | "21502": "消息中敏感词已经被替换", 46 | "34002": "小视频时间长度超出限制", 47 | "34003": "GIF 消息文件大小超出限制", 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /example/lib/im/util/db_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:path/path.dart'; 4 | import 'package:sqflite/sqflite.dart'; 5 | 6 | import 'user_info_datesource.dart'; 7 | 8 | class DbManager { 9 | factory DbManager() => _getInstance()!; 10 | 11 | static DbManager? get instance => _getInstance(); 12 | static DbManager? _instance; 13 | static Database? database; 14 | static String dbName = 'UserInfoCache.db'; 15 | static String userTableName = 'users'; 16 | static String groupTableName = 'groups'; 17 | 18 | DbManager._internal() { 19 | // 初始化 20 | } 21 | 22 | static DbManager? _getInstance() { 23 | if (_instance == null) { 24 | _instance = new DbManager._internal(); 25 | } 26 | return _instance; 27 | } 28 | 29 | Future openDb() async { 30 | database = await openDatabase(join(await getDatabasesPath(), dbName), onCreate: (db, version) { 31 | db.execute("CREATE TABLE $userTableName(userId TEXT PRIMARY KEY, name TEXT, portraitUrl TEXT) "); 32 | db.execute("CREATE TABLE $groupTableName(groupId TEXT PRIMARY KEY, name TEXT, portraitUrl TEXT)"); 33 | }, version: 1); 34 | } 35 | 36 | Future setUserInfo(UserInfo info) async { 37 | if (database == null) { 38 | await openDb(); 39 | } 40 | await database?.insert(userTableName, info.toMap(), conflictAlgorithm: ConflictAlgorithm.replace); 41 | } 42 | 43 | Future> getUserInfo({String? userId}) async { 44 | List> maps = []; 45 | if (database == null) { 46 | await openDb(); 47 | } 48 | if (userId == null || userId.isEmpty) { 49 | maps = (await database?.query(userTableName))!; 50 | } else { 51 | maps = (await database?.query(userTableName, where: 'userId = ?', whereArgs: [userId]))!; 52 | } 53 | List infoList = []; 54 | if (maps.length > 0) { 55 | infoList = List.generate(maps.length, (i) { 56 | UserInfo info = UserInfo(); 57 | info.id = maps[i]['userId']; 58 | info.name = maps[i]['name']; 59 | info.portraitUrl = maps[i]['portraitUrl']; 60 | return info; 61 | }); 62 | } 63 | return infoList; 64 | } 65 | 66 | Future setGroupInfo(GroupInfo info) async { 67 | if (database == null) { 68 | await openDb(); 69 | } 70 | await database?.insert(groupTableName, info.toMap(), conflictAlgorithm: ConflictAlgorithm.replace); 71 | } 72 | 73 | Future> getGroupInfo({String? groupId}) async { 74 | List> maps = []; 75 | if (database == null) { 76 | await openDb(); 77 | } 78 | if (groupId == null || groupId.isEmpty) { 79 | maps = (await database?.query(groupTableName))!; 80 | } else { 81 | maps = (await database?.query(groupTableName, where: 'groupId = ?', whereArgs: [groupId]))!; 82 | } 83 | List infoList = []; 84 | if (maps.length > 0) { 85 | infoList = List.generate(maps.length, (i) { 86 | GroupInfo info = GroupInfo(); 87 | info.id = maps[i]['groupId']; 88 | info.name = maps[i]['name']; 89 | info.portraitUrl = maps[i]['portraitUrl']; 90 | return info; 91 | }); 92 | } 93 | return infoList; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /example/lib/im/util/dialog_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class DialogUtil { 4 | static void showAlertDiaLog(BuildContext context, String content, {String title = '', TextButton? confirmButton}) { 5 | showDialog( 6 | barrierDismissible: false, // 设置点击 dialog 外部不取消 dialog,默认能够取消 7 | context: context, 8 | builder: (context) => AlertDialog( 9 | title: Text(title), 10 | titleTextStyle: TextStyle(color: Colors.black), 11 | content: Text(content), 12 | contentTextStyle: TextStyle(color: Colors.black), 13 | backgroundColor: Colors.white, 14 | elevation: 8.0, 15 | // 投影的阴影高度 16 | semanticLabel: 'Label', 17 | // 这个用于无障碍下弹出 dialog 的提示 18 | shape: Border.all(), 19 | actions: [ 20 | confirmButton != null ? confirmButton : TextButton(onPressed: () => Navigator.pop(context), child: Text("确定")), 21 | ], 22 | )); 23 | } 24 | 25 | static void showBottomSheetDialog(BuildContext mContext, Map tips) { 26 | if (tips == null || tips.length <= 0) { 27 | return; 28 | } 29 | showModalBottomSheet( 30 | context: mContext, 31 | builder: (context) => Container( 32 | child: ListView.separated( 33 | key: UniqueKey(), 34 | controller: ScrollController(), 35 | itemCount: tips.length, 36 | itemBuilder: (BuildContext context, int index) { 37 | return InkWell( 38 | child: Container(alignment: Alignment.center, height: 60, child: Text(tips.keys.toList()[index])), 39 | onTap: () { 40 | Function() clickEvent = tips.values.toList()[index]; 41 | if (clickEvent != null) { 42 | Navigator.pop(mContext); 43 | clickEvent(); 44 | } 45 | }); 46 | }, 47 | separatorBuilder: (BuildContext context, int index) { 48 | return Container( 49 | color: Color(0xffC8C8C8), 50 | height: 0.5, 51 | ); 52 | }), 53 | height: (60 * tips.length * 1.0), 54 | ), 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /example/lib/im/util/event_bus.dart: -------------------------------------------------------------------------------- 1 | typedef void EventCallback(arg); 2 | 3 | //事件总线 4 | class EventBus { 5 | factory EventBus() => _getInstance()!; 6 | 7 | static EventBus? get instance => _getInstance(); 8 | static EventBus? _instance; 9 | 10 | EventBus._internal() { 11 | // 初始化 12 | } 13 | 14 | static EventBus? _getInstance() { 15 | if (_instance == null) { 16 | _instance = new EventBus._internal(); 17 | } 18 | return _instance; 19 | } 20 | 21 | Map _events = new Map(); 22 | 23 | //设置事件监听,当有人调用 commit ,并且 eventKey 一样的时候会触发此方法 24 | void addListener(String eventKey, EventCallback callback) { 25 | if (eventKey == null || callback == null) return; 26 | _events[eventKey] = callback; 27 | } 28 | 29 | //移除监听 30 | void removeListener(String eventKey) { 31 | if (eventKey == null) return; 32 | _events.remove(eventKey); 33 | } 34 | 35 | //提交事件 36 | void commit(String eventKey, Object? arg) { 37 | if (eventKey == null) return; 38 | EventCallback? callback = _events[eventKey]; 39 | if (callback != null) { 40 | callback(arg); 41 | } 42 | } 43 | } 44 | 45 | class EventKeys { 46 | static const String ConversationPageDispose = "ConversationPageDispose"; 47 | static const String ReceiveMessage = "ReceiveMessage"; 48 | static const String ReceiveReadReceipt = "ReceiveReadReceipt"; 49 | static const String ReceiveReceiptRequest = "ReceiveReceiptRequest"; 50 | static const String ReceiveReceiptResponse = "ReceiveReceiptResponse"; 51 | static const String LongPressUserPortrait = "LongPressUserPortrait"; 52 | static const String UpdateNotificationQuietStatus = "UpdateNotificationQuietStatus"; 53 | static const String ForwardMessageEnd = "ForwardMessageEnd"; 54 | static const String BurnMessage = "BurnMessage"; 55 | static const String BlockMessage = "BlockMessage"; 56 | } 57 | -------------------------------------------------------------------------------- /example/lib/im/util/file.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import '../util/file_suffix.dart'; 4 | 5 | class FileUtil { 6 | static int kilobyte = 1024; 7 | static int megabyte = 1024 * 1024; 8 | static int gigabyte = 1024 * 1024 * 1024; 9 | 10 | static String formatFileSize(int size) { 11 | if (size < kilobyte) { 12 | return "$size B"; 13 | } else if (size < megabyte) { 14 | String sizeStr = (size / kilobyte).toStringAsFixed(2); 15 | return "$sizeStr KB"; 16 | } else if (size < gigabyte) { 17 | String sizeStr = (size / megabyte).toStringAsFixed(2); 18 | return "$sizeStr MB"; 19 | } else { 20 | String sizeStr = (size / gigabyte).toStringAsFixed(2); 21 | return "$sizeStr G"; 22 | } 23 | } 24 | 25 | // 根据文件类型选择对应图片 26 | static String fileTypeImagePath(String? fileName) { 27 | String imagePath; 28 | if (checkSuffix(fileName, FileSuffix.ImageFileSuffix)) 29 | imagePath = "assets/images/file_message_icon_picture.png"; 30 | else if (checkSuffix(fileName, FileSuffix.FileFileSuffix)) 31 | imagePath = "assets/images/file_message_icon_file.png"; 32 | else if (checkSuffix(fileName, FileSuffix.VideoFileSuffix)) 33 | imagePath = "assets/images/file_message_icon_video.png"; 34 | else if (checkSuffix(fileName, FileSuffix.AudioFileSuffix)) 35 | imagePath = "assets/images/file_message_icon_audio.png"; 36 | else if (checkSuffix(fileName, FileSuffix.WordFileSuffix)) 37 | imagePath = "assets/images/file_message_icon_word.png"; 38 | else if (checkSuffix(fileName, FileSuffix.ExcelFileSuffix)) 39 | imagePath = "assets/images/file_message_icon_excel.png"; 40 | else if (checkSuffix(fileName, FileSuffix.PptFileSuffix)) 41 | imagePath = "assets/images/file_message_icon_ppt.png"; 42 | else if (checkSuffix(fileName, FileSuffix.PdfFileSuffix)) 43 | imagePath = "assets/images/file_message_icon_pdf.png"; 44 | else if (checkSuffix(fileName, FileSuffix.ApkFileSuffix)) 45 | imagePath = "assets/images/file_message_icon_apk.png"; 46 | else if (checkSuffix(fileName, FileSuffix.KeyFileSuffix)) 47 | imagePath = "assets/images/file_message_icon_key.png"; 48 | else if (checkSuffix(fileName, FileSuffix.NumbersFileSuffix)) 49 | imagePath = "assets/images/file_message_icon_numbers.png"; 50 | else if (checkSuffix(fileName, FileSuffix.PagesFileSuffix)) 51 | imagePath = "assets/images/file_message_icon_pages.png"; 52 | else 53 | imagePath = "assets/images/file_message_icon_else.png"; 54 | return imagePath; 55 | } 56 | 57 | static bool checkSuffix(String? fileName, List fileSuffix) { 58 | for (String suffix in fileSuffix) { 59 | if (fileName != null) { 60 | if (fileName.toLowerCase().endsWith(suffix)) { 61 | return true; 62 | } 63 | } 64 | } 65 | return false; 66 | } 67 | 68 | static Future writeStringToFile(String filePath, String fileName, String content) async { 69 | Directory file = Directory(filePath); 70 | if (!file.existsSync()) { 71 | file.create(); 72 | } 73 | return File("$filePath/$fileName").writeAsString(content); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /example/lib/im/util/file_suffix.dart: -------------------------------------------------------------------------------- 1 | class FileSuffix { 2 | static const List ImageFileSuffix = ['.bmp', '.cod', '.gif', '.ief', '.jpe', '.jpeg', '.jpg', '.jfif', '.svg', '.tif', '.tiff', '.ras', '.ico', '.pbm', '.pgm', '.png', '.pnm', '.ppm', '.xbm', '.xpm', '.xwd', '.rgb']; 3 | 4 | static const List FileFileSuffix = [ 5 | '.txt', 6 | '.log', 7 | '.html', 8 | '.stm', 9 | '.uls', 10 | '.bas', 11 | '.c', 12 | '.h', 13 | '.rtx', 14 | '.sct', 15 | '.tsv', 16 | '.htt', 17 | '.htc', 18 | '.etx', 19 | '.vcf', 20 | ]; 21 | 22 | static const List VideoFileSuffix = [ 23 | '.rmvb', 24 | '.avi', 25 | '.mp4', 26 | '.mp2', 27 | '.mpa', 28 | '.mpe', 29 | '.mpeg', 30 | '.mpg', 31 | '.mpv2', 32 | '.mov', 33 | '.qt', 34 | '.lsf', 35 | '.lsx', 36 | '.asf', 37 | '.asr', 38 | '.asx', 39 | '.avi', 40 | '.movie', 41 | '.wmv', 42 | ]; 43 | 44 | static const List AudioFileSuffix = [ 45 | '.mp3', 46 | '.au', 47 | '.snd', 48 | '.mid', 49 | '.rmi', 50 | '.aif', 51 | '.aifc', 52 | '.aiff', 53 | '.m3u', 54 | '.ra', 55 | '.ram', 56 | '.wav', 57 | '.wma', 58 | ]; 59 | 60 | static const List WordFileSuffix = [ 61 | '.doc', 62 | '.dot', 63 | '.docx', 64 | ]; 65 | 66 | static const List ExcelFileSuffix = [ 67 | '.xla', 68 | '.xlc', 69 | '.xlm', 70 | '.xls', 71 | '.xlt', 72 | '.xlw', 73 | '.xlsx', 74 | ]; 75 | 76 | static const List PptFileSuffix = [ 77 | '.ppt', 78 | '.pptx', 79 | ]; 80 | 81 | static const List PdfFileSuffix = [ 82 | '.pdf', 83 | ]; 84 | 85 | static const List ApkFileSuffix = [ 86 | '.apk', 87 | ]; 88 | 89 | static const List OtherFileSuffix = [ 90 | '.doc', 91 | '.dot', 92 | '.xla', 93 | '.xlc', 94 | '.xlm', 95 | '.xls', 96 | '.xlt', 97 | '.xlw', 98 | '.gif', 99 | '.jpe', 100 | '.jpeg', 101 | '.jpg', 102 | ]; 103 | 104 | static const List KeyFileSuffix = [ 105 | '.key', 106 | ]; 107 | 108 | static const List PagesFileSuffix = [ 109 | '.pages', 110 | ]; 111 | 112 | static const List NumbersFileSuffix = [ 113 | '.numbers', 114 | ]; 115 | } 116 | -------------------------------------------------------------------------------- /example/lib/im/util/http_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:connectivity/connectivity.dart'; 2 | import 'package:dio/dio.dart'; 3 | 4 | class HttpUtil { 5 | static Dio dio = Dio(); 6 | 7 | static void get(String url, Function callback, {Map? params, Function? errorCallback}) async { 8 | if (params != null && params.isNotEmpty) { 9 | StringBuffer buffer = new StringBuffer("?"); 10 | params.forEach((key, value) { 11 | buffer.write("$key" + "=" + "$value" + "&"); 12 | }); 13 | String paramStr = buffer.toString(); 14 | paramStr = paramStr.substring(0, paramStr.length - 1); 15 | url += paramStr; 16 | } 17 | Response response; 18 | try { 19 | response = await dio.get(url); 20 | print(response); 21 | if (callback != null) { 22 | callback(response.data); 23 | } 24 | } catch (e) { 25 | print(e.toString()); 26 | if (errorCallback != null) { 27 | errorCallback(e); 28 | } 29 | } 30 | } 31 | 32 | static void post(String url, Map params, {Function? callback}) async { 33 | var connectivityResult = await (Connectivity().checkConnectivity()); 34 | if (connectivityResult == ConnectivityResult.none) { 35 | Map body = {"code": -1}; 36 | if (callback != null) { 37 | callback(params, body); 38 | } 39 | } else { 40 | Response response; 41 | response = await Dio().post(url, data: params); 42 | print(response); 43 | if (callback != null) { 44 | callback(params,response.data); 45 | } 46 | } 47 | } 48 | 49 | // 下载 50 | static Future download(String url, String savePath, Function(int count, int total) progressCallback) async { 51 | return Dio().download(url, savePath, onReceiveProgress: progressCallback); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /example/lib/im/util/time.dart: -------------------------------------------------------------------------------- 1 | import 'dart:core'; 2 | 3 | class TimeUtil { 4 | //将 unix 时间戳转换为特定时间文本,如年月日 5 | static String convertTime(int timestamp) { 6 | DateTime msgTime = DateTime.fromMillisecondsSinceEpoch(timestamp); 7 | DateTime nowTime = DateTime.now(); 8 | 9 | if (nowTime.year == msgTime.year) { 10 | //同一年 11 | if (nowTime.month == msgTime.month) { 12 | //同一月 13 | if (nowTime.day == msgTime.day) { 14 | //同一天 时:分 15 | return msgTime.hour.toString() + ":" + msgTime.minute.toString(); 16 | } else { 17 | if (nowTime.day - msgTime.day == 1) { 18 | //昨天 19 | return "昨天"; 20 | } else if (nowTime.day - msgTime.day < 7) { 21 | return _getWeekday(msgTime.weekday); 22 | } 23 | } 24 | } 25 | } 26 | return msgTime.year.toString() + "/" + msgTime.month.toString() + "/" + msgTime.day.toString(); 27 | } 28 | 29 | ///是否需要显示时间,相差 5 分钟 30 | static bool needShowTime(int? sentTime1, int? sentTime2) { 31 | if (sentTime1 == null || sentTime2 == null) { 32 | return false; 33 | } 34 | return (sentTime1 - sentTime2).abs() > 5 * 60 * 1000; 35 | } 36 | 37 | static String _getWeekday(int weekday) { 38 | switch (weekday) { 39 | case 1: 40 | return "星期一"; 41 | case 2: 42 | return "星期二"; 43 | case 3: 44 | return "星期三"; 45 | case 4: 46 | return "星期四"; 47 | case 5: 48 | return "星期五"; 49 | case 6: 50 | return "星期六"; 51 | default: 52 | return "星期日"; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /example/lib/im/widget/cachImage/cached_network_image_provider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async' show Future; 2 | import 'dart:io' show File; 3 | import 'dart:ui' as ui show instantiateImageCodec, Codec; 4 | 5 | import 'package:flutter/foundation.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_cache_manager/flutter_cache_manager.dart'; 8 | 9 | typedef void ErrorListener(); 10 | 11 | class CachedNetworkImageProvider extends ImageProvider { 12 | /// Creates an ImageProvider which loads an image from the [url], using the [scale]. 13 | /// When the image fails to load [errorListener] is called. 14 | const CachedNetworkImageProvider(this.url, {this.scale = 1.0, this.errorListener, this.headers, this.cacheManager}) 15 | : assert(url != null), 16 | assert(scale != null); 17 | 18 | final BaseCacheManager? cacheManager; 19 | 20 | /// Web url of the image to load 21 | final String url; 22 | 23 | /// Scale of the image 24 | final double scale; 25 | 26 | /// Listener to be called when images fails to load. 27 | final ErrorListener? errorListener; 28 | 29 | // Set headers for the image provider, for example for authentication 30 | final Map? headers; 31 | 32 | @override 33 | Future obtainKey(ImageConfiguration configuration) { 34 | return SynchronousFuture(this); 35 | } 36 | 37 | @override 38 | ImageStreamCompleter load(CachedNetworkImageProvider key, DecoderCallback decode) { 39 | return MultiFrameImageStreamCompleter( 40 | codec: _loadAsync(key), 41 | scale: key.scale, 42 | informationCollector: () sync* { 43 | yield DiagnosticsProperty( 44 | 'Image provider: $this \n Image key: $key', 45 | this, 46 | style: DiagnosticsTreeStyle.errorProperty, 47 | ); 48 | }, 49 | ); 50 | } 51 | 52 | Future _loadAsync(CachedNetworkImageProvider key) async { 53 | var mngr = cacheManager ?? DefaultCacheManager(); 54 | var file = await mngr.getSingleFile(url, headers: headers!); 55 | if (file == null) { 56 | if (errorListener != null) errorListener!(); 57 | throw Exception('Couldn\'t download or retrieve file: $url'); 58 | } 59 | return _loadAsyncFromFile(key, file); 60 | } 61 | 62 | Future _loadAsyncFromFile(CachedNetworkImageProvider key, File file) async { 63 | assert(key == this); 64 | 65 | final bytes = await file.readAsBytes(); 66 | 67 | if (bytes.lengthInBytes == 0) { 68 | if (errorListener != null) errorListener!(); 69 | throw Exception('File was empty'); 70 | } 71 | 72 | return ui.instantiateImageCodec(bytes); 73 | } 74 | 75 | @override 76 | bool operator ==(dynamic other) { 77 | if (other is CachedNetworkImageProvider) { 78 | return url == other.url && scale == other.scale; 79 | } 80 | return false; 81 | } 82 | 83 | @override 84 | int get hashCode => hashValues(url, scale); 85 | 86 | @override 87 | String toString() => '$runtimeType("$url", scale: $scale)'; 88 | } 89 | -------------------------------------------------------------------------------- /example/lib/location_message.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' show json; 2 | 3 | import 'package:rongcloud_im_plugin/rongcloud_im_plugin.dart'; 4 | 5 | //app 层的位置消息 6 | class LocationMessage extends MessageContent { 7 | static const String objectName = "RCD:LBSMsg"; 8 | 9 | double? latitude; // 地理位置的纬度 10 | double? longitude; // 地理位置的经度 11 | String? imageUri; // 地理位置的缩略图地址 12 | String? poi; // 地理位置的名称 13 | String? extra; 14 | 15 | @override 16 | void decode(String? jsonStr) { 17 | Map map = json.decode(jsonStr.toString()); 18 | this.latitude = map["latitude"]; 19 | this.longitude = map["longitude"]; 20 | this.imageUri = map["mImgUri"]; 21 | this.poi = map["poi"]; 22 | this.extra = map["extra"]; 23 | 24 | // decode 消息内容中携带的发送者的用户信息 25 | Map? userMap = map["user"]; 26 | super.decodeUserInfo(userMap); 27 | 28 | // decode 消息中的 @ 提醒信息;消息需要携带 @ 信息时添加此方法 29 | Map? menthionedMap = map["mentionedInfo"]; 30 | super.decodeMentionedInfo(menthionedMap); 31 | } 32 | 33 | @override 34 | String encode() { 35 | Map map = {"extra": this.extra}; 36 | 37 | if (this.latitude != null) { 38 | map["latitude"] = this.latitude; 39 | } 40 | 41 | if (this.longitude != null) { 42 | map["longitude"] = this.longitude; 43 | } 44 | 45 | if (this.imageUri != null) { 46 | map["mImgUri"] = this.imageUri; 47 | } 48 | 49 | if (this.poi != null) { 50 | map["poi"] = this.poi; 51 | } 52 | 53 | // encode 消息内容中携带的发送者的用户信息 54 | if (this.sendUserInfo != null) { 55 | Map userMap = super.encodeUserInfo(this.sendUserInfo); 56 | map["user"] = userMap; 57 | } 58 | 59 | // encode 消息中的 @ 提醒信息;消息需要携带 @ 信息时添加此方法 60 | if (this.mentionedInfo != null) { 61 | Map mentionedMap = super.encodeMentionedInfo(this.mentionedInfo); 62 | map["mentionedInfo"] = mentionedMap; 63 | } 64 | return json.encode(map); 65 | } 66 | 67 | @override 68 | String? conversationDigest() { 69 | return poi; 70 | } 71 | 72 | @override 73 | String getObjectName() { 74 | return objectName; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /example/lib/other/contacts_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:rongcloud_im_plugin/rongcloud_im_plugin.dart' as prefix; 5 | import 'package:shared_preferences/shared_preferences.dart'; 6 | 7 | import '../im/util/user_info_datesource.dart' as example; 8 | import 'login_page.dart'; 9 | 10 | class ContactsPage extends StatefulWidget { 11 | @override 12 | State createState() { 13 | return new _ContactsPageState(); 14 | } 15 | } 16 | 17 | class _ContactsPageState extends State { 18 | List widgetList = []; 19 | List userList = []; 20 | 21 | @override 22 | void initState() { 23 | super.initState(); 24 | _addFriends(); 25 | } 26 | 27 | void _addFriends() { 28 | // List users = await _getRandomUserInfos(); 29 | _getRandomUserInfos().then((users) { 30 | for (example.UserInfo u in users) { 31 | this.widgetList.add(getWidget(u)); 32 | _refreshUI(); 33 | } 34 | }); 35 | } 36 | 37 | void _refreshUI() { 38 | if (mounted) setState(() {}); 39 | } 40 | 41 | Future> _getRandomUserInfos() async { 42 | this.userList.add(await example.UserInfoDataSource.getUserInfo("SealTalk")); 43 | this.userList.add(await example.UserInfoDataSource.getUserInfo("RongRTC")); 44 | this.userList.add(await example.UserInfoDataSource.getUserInfo("RongIM")); 45 | return this.userList; 46 | } 47 | 48 | void _onTapUser(example.UserInfo user) { 49 | Map arg = {"coversationType": prefix.RCConversationType.Private, "targetId": user.id}; 50 | Navigator.pushNamed(context, "/conversation", arguments: arg); 51 | } 52 | 53 | void _pushToDebug() { 54 | Navigator.pushNamed(context, "/debug"); 55 | } 56 | 57 | void _logout() async { 58 | prefix.RongIMClient.disconnect(false); 59 | SharedPreferences prefs = await SharedPreferences.getInstance(); 60 | prefs.remove("token"); 61 | Navigator.of(context).pushAndRemoveUntil(new MaterialPageRoute(builder: (context) => new LoginPage()), (route) => route == null); 62 | } 63 | 64 | Widget getWidget(example.UserInfo user) { 65 | return Container( 66 | height: 50.0, 67 | color: Colors.white, 68 | child: InkWell( 69 | onTap: () { 70 | _onTapUser(user); 71 | }, 72 | child: new ListTile( 73 | title: new Text(user.id!), 74 | leading: Container( 75 | width: 36, 76 | height: 36, 77 | child: ClipRRect( 78 | borderRadius: BorderRadius.circular(5), 79 | child: CachedNetworkImage( 80 | fit: BoxFit.fill, 81 | imageUrl: user.portraitUrl!, 82 | ), 83 | ), 84 | ), 85 | ), 86 | ), 87 | ); 88 | } 89 | 90 | @override 91 | Widget build(BuildContext context) { 92 | return Scaffold( 93 | appBar: AppBar( 94 | centerTitle: true, 95 | title: Text("RongCloud IM"), 96 | actions: [ 97 | IconButton( 98 | icon: Icon(Icons.more), 99 | onPressed: () { 100 | _pushToDebug(); 101 | }, 102 | ), 103 | IconButton( 104 | icon: Icon(Icons.power_settings_new), 105 | onPressed: () { 106 | _logout(); 107 | }, 108 | ), 109 | ], 110 | ), 111 | body: ListView.builder( 112 | itemCount: widgetList.length, 113 | itemBuilder: (BuildContext context, int index) { 114 | return widgetList[index]; 115 | }, 116 | ), 117 | ); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /example/lib/other/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer' as developer; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:rongcloud_im_plugin/rongcloud_im_plugin.dart'; 5 | import 'package:shared_preferences/shared_preferences.dart'; 6 | 7 | import '../im/pages/conversation_list_page.dart'; 8 | import '../im/util/db_manager.dart'; 9 | import '../im/util/event_bus.dart'; 10 | import '../im/util/user_info_datesource.dart'; 11 | import 'contacts_page.dart'; 12 | import 'login_page.dart'; 13 | 14 | class HomePage extends StatefulWidget { 15 | @override 16 | State createState() { 17 | return new _HomePageState(); 18 | } 19 | } 20 | 21 | class _HomePageState extends State { 22 | String pageName = "example.HomePage"; 23 | final List tabbarList = [ 24 | new BottomNavigationBarItem( 25 | icon: new Icon(Icons.chat, color: Colors.grey), 26 | label: "会话", 27 | ), 28 | new BottomNavigationBarItem( 29 | icon: new Icon( 30 | Icons.perm_contact_calendar, 31 | color: Colors.grey, 32 | ), 33 | label: "通讯录", 34 | ), 35 | ]; 36 | final List vcList = [new ConversationListPage(), new ContactsPage()]; 37 | 38 | int curIndex = 0; 39 | 40 | @override 41 | void initState() { 42 | super.initState(); 43 | _initUserInfoCache(); 44 | initPlatformState(); 45 | } 46 | 47 | initPlatformState() async { 48 | //1.初始化 im SDK 49 | // RongIMClient.init(RongAppKey); 50 | 51 | //2.连接 im SDK 52 | SharedPreferences prefs = await SharedPreferences.getInstance(); 53 | String? token = prefs.get("token") as String?; 54 | if (token != null && token.length > 0) { 55 | // int rc = await RongIMClient.connect(token); 56 | RongIMClient.connect(token, (int? code, String? userId) { 57 | developer.log("connect result " + code.toString(), name: pageName); 58 | EventBus.instance!.commit(EventKeys.UpdateNotificationQuietStatus, {}); 59 | if (code == 31004 || code == 12) { 60 | developer.log("connect result " + code.toString(), name: pageName); 61 | Navigator.of(context).pushAndRemoveUntil(new MaterialPageRoute(builder: (context) => new LoginPage()), (route) => route == null); 62 | } else if (code == 0) { 63 | developer.log("connect userId" + userId!, name: pageName); 64 | // 连接成功后打开数据库 65 | // _initUserInfoCache(); 66 | } 67 | }); 68 | } else { 69 | Navigator.of(context).pushAndRemoveUntil(new MaterialPageRoute(builder: (context) => new LoginPage()), (route) => route == null); 70 | } 71 | } 72 | 73 | // 初始化用户信息缓存 74 | void _initUserInfoCache() { 75 | DbManager.instance!.openDb(); 76 | UserInfoCacheListener cacheListener = UserInfoCacheListener(); 77 | cacheListener.getUserInfo = (String? userId) { 78 | return UserInfoDataSource.generateUserInfo(userId); 79 | }; 80 | cacheListener.getGroupInfo = (String? groupId) { 81 | return UserInfoDataSource.generateGroupInfo(groupId); 82 | }; 83 | UserInfoDataSource.setCacheListener(cacheListener); 84 | } 85 | 86 | @override 87 | Widget build(BuildContext context) { 88 | return new Scaffold( 89 | bottomNavigationBar: new BottomNavigationBar( 90 | items: tabbarList, 91 | type: BottomNavigationBarType.fixed, 92 | onTap: (int index) { 93 | setState(() { 94 | curIndex = index; 95 | }); 96 | }, 97 | currentIndex: curIndex, 98 | ), 99 | body: IndexedStack( 100 | index: curIndex, 101 | children: [new ConversationListPage(), new ContactsPage()], 102 | ), 103 | ); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /example/lib/other/login_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shared_preferences/shared_preferences.dart'; 3 | 4 | import '../user_data.dart'; 5 | import 'home_page.dart'; 6 | 7 | class LoginPage extends StatefulWidget { 8 | @override 9 | State createState() { 10 | return new _LoginPageState(); 11 | } 12 | } 13 | 14 | class _LoginPageState extends State { 15 | String pageName = "example.LoginPage"; 16 | TextEditingController _id = TextEditingController(); 17 | TextEditingController _token = TextEditingController(); 18 | 19 | @override 20 | void initState() { 21 | super.initState(); 22 | initPlatformState(); 23 | } 24 | 25 | initPlatformState() async { 26 | SharedPreferences prefs = await SharedPreferences.getInstance(); 27 | String id = prefs.getString("id") ?? CurrentUserId; 28 | String token = prefs.getString("token") ?? RongIMToken; 29 | 30 | _id.text = id; 31 | _token.text = token; 32 | } 33 | 34 | void _loginAction() async { 35 | String id = _id.text; 36 | String token = _token.text; 37 | await _saveUserInfo(id, token); 38 | Navigator.of(context).pushAndRemoveUntil(new MaterialPageRoute(builder: (context) => new HomePage()), (route) => false); 39 | } 40 | 41 | Future _saveUserInfo(String id, String token) async { 42 | if (id.isNotEmpty && token.isNotEmpty) { 43 | SharedPreferences prefs = await SharedPreferences.getInstance(); 44 | prefs.setString("id", id); 45 | prefs.setString("token", token); 46 | } 47 | } 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | final logo = new Hero( 52 | tag: 'hero', 53 | child: Container( 54 | width: 100, 55 | height: 100, 56 | child: Image.asset('assets/images/logo.png'), 57 | ), 58 | ); 59 | 60 | final id = TextFormField( 61 | keyboardType: TextInputType.text, 62 | autofocus: false, 63 | controller: _id, 64 | decoration: InputDecoration( 65 | hintText: 'User Id', 66 | contentPadding: new EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0), 67 | border: OutlineInputBorder( 68 | borderRadius: BorderRadius.circular(32.0), 69 | ), 70 | ), 71 | ); 72 | 73 | final token = TextFormField( 74 | keyboardType: TextInputType.text, 75 | autofocus: false, 76 | controller: _token, 77 | decoration: InputDecoration( 78 | hintText: 'User Token', 79 | contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0), 80 | border: OutlineInputBorder( 81 | borderRadius: BorderRadius.circular(32.0), 82 | ), 83 | ), 84 | ); 85 | 86 | final loginButton = Padding( 87 | padding: EdgeInsets.symmetric(vertical: 16.0), 88 | child: MaterialButton( 89 | minWidth: 200.0, 90 | height: 42.0, 91 | onPressed: () { 92 | _loginAction(); 93 | }, 94 | color: Colors.lightBlueAccent, 95 | child: Text( 96 | '登 录', 97 | style: TextStyle(color: Colors.white), 98 | ), 99 | ), 100 | ); 101 | 102 | return Scaffold( 103 | appBar: AppBar( 104 | title: Text("Login"), 105 | ), 106 | body: Center( 107 | child: ListView( 108 | shrinkWrap: true, 109 | padding: EdgeInsets.only(left: 24.0, right: 24.0), 110 | children: [ 111 | logo, 112 | SizedBox(height: 48.0), 113 | id, 114 | SizedBox( 115 | height: 8.0, 116 | ), 117 | token, 118 | SizedBox( 119 | height: 24.0, 120 | ), 121 | loginButton 122 | ], 123 | )), 124 | ); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /example/lib/other/message_read_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:rongcloud_im_plugin/rongcloud_im_plugin.dart' as prefix; 5 | 6 | import '../im/util/user_info_datesource.dart' as example; 7 | 8 | class MessageReadPage extends StatefulWidget { 9 | final prefix.Message? message; 10 | 11 | const MessageReadPage({Key? key, this.message}) : super(key: key); 12 | 13 | @override 14 | State createState() => _MessageReadPageState(message); 15 | } 16 | 17 | class _MessageReadPageState extends State { 18 | final prefix.Message? message; 19 | 20 | _MessageReadPageState(this.message); 21 | 22 | List widgetList = []; 23 | List userList = []; 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | _addFriends(); 29 | } 30 | 31 | _addFriends() async { 32 | await _getRandomUserInfos(); 33 | for (example.UserInfo u in this.userList) { 34 | this.widgetList.add(getWidget(u)); 35 | } 36 | setState(() {}); 37 | } 38 | 39 | Future _getRandomUserInfos() async { 40 | Map? userIdList = message!.readReceiptInfo!.userIdList; 41 | if (userIdList != null) { 42 | for (String? key in userIdList.keys) { 43 | example.UserInfo? userInfo = example.UserInfoDataSource.cachedUserMap[key]; 44 | if (userInfo == null) { 45 | userInfo = await example.UserInfoDataSource.getUserInfo(key); 46 | } 47 | this.userList.add(userInfo); 48 | } 49 | // return this.userList; 50 | } 51 | } 52 | 53 | Widget getWidget(example.UserInfo user) { 54 | return Container( 55 | height: 50.0, 56 | color: Colors.white, 57 | child: InkWell( 58 | child: new ListTile( 59 | title: new Text(user.id!), 60 | leading: Container( 61 | width: 36, 62 | height: 36, 63 | child: ClipRRect( 64 | borderRadius: BorderRadius.circular(5), 65 | child: CachedNetworkImage( 66 | fit: BoxFit.fill, 67 | imageUrl: user.portraitUrl!, 68 | ), 69 | ), 70 | ), 71 | ), 72 | ), 73 | ); 74 | } 75 | 76 | @override 77 | Widget build(BuildContext context) { 78 | return Scaffold( 79 | appBar: AppBar( 80 | title: Text("已读成员列表"), 81 | ), 82 | body: ListView.builder( 83 | scrollDirection: Axis.vertical, 84 | itemCount: this.widgetList.length, 85 | itemBuilder: (BuildContext context, int index) { 86 | return this.widgetList[index]; 87 | }, 88 | ), 89 | ); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /example/lib/other/search_message_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:rongcloud_im_plugin/rongcloud_im_plugin.dart'; 3 | 4 | import '../im/pages/item/widget_util.dart'; 5 | 6 | class SearchMessagePage extends StatefulWidget { 7 | final Map? arguments; 8 | 9 | SearchMessagePage({Key? key, this.arguments}) : super(key: key); 10 | 11 | @override 12 | State createState() { 13 | return _SearchMessagePageState(arguments: arguments); 14 | } 15 | } 16 | 17 | class _SearchMessagePageState extends State { 18 | Map? arguments; 19 | int? conversationType; 20 | String? targetId; 21 | late List messageList; 22 | 23 | _SearchMessagePageState({this.arguments}); 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | conversationType = arguments!["coversationType"]; 29 | targetId = arguments!["targetId"]; 30 | messageList = []; 31 | } 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return Scaffold( 36 | appBar: AppBar( 37 | title: Text("搜索会话历史消息"), 38 | ), 39 | body: Container( 40 | child: Column( 41 | mainAxisSize: MainAxisSize.max, 42 | crossAxisAlignment: CrossAxisAlignment.center, 43 | children: [ 44 | Container( 45 | margin: EdgeInsets.only(left: 12, right: 12, top: 20), 46 | height: 45, 47 | decoration: BoxDecoration(border: new Border.all(color: Colors.black54, width: 0.5), borderRadius: BorderRadius.circular(8)), 48 | child: TextField(textAlign: TextAlign.center, onSubmitted: _searchMessage, decoration: InputDecoration(border: InputBorder.none, hintText: '请输入关键词'), autofocus: true), 49 | ), 50 | messageList.length > 0 51 | ? Expanded( 52 | flex: 1, 53 | child: Container( 54 | margin: EdgeInsets.only(top: 14), 55 | child: ListView.separated( 56 | controller: ScrollController(), 57 | itemCount: messageList.length, 58 | itemBuilder: (BuildContext context, int index) { 59 | if (messageList.length != null && messageList.length > 0) { 60 | Message? message = messageList[index]; 61 | return GestureDetector( 62 | child: Container( 63 | alignment: Alignment.center, 64 | child: Container( 65 | padding: EdgeInsets.all(6), 66 | child: Text(message.toString(), 67 | style: new TextStyle( 68 | fontSize: 15, //字体大��� 69 | ))), 70 | )); 71 | } else { 72 | return WidgetUtil.buildEmptyWidget(); 73 | } 74 | }, 75 | separatorBuilder: (BuildContext context, int index) { 76 | return Container( 77 | color: Color(0xffC8C8C8), 78 | height: 0.5, 79 | ); 80 | }))) 81 | : Text( 82 | "无记录", 83 | style: new TextStyle(fontSize: 14, color: const Color(0xffff0000)), 84 | textAlign: TextAlign.center, 85 | ) 86 | ], 87 | ), 88 | ), 89 | ); 90 | } 91 | 92 | void _searchMessage(String keyWord) { 93 | if (keyWord == null || keyWord.isEmpty) { 94 | messageList.clear(); 95 | _refreshUI(); 96 | } 97 | RongIMClient.searchMessages(conversationType!, targetId!, keyWord, 50, 0, (List? /**/ msgList, int? code) { 98 | if (code == 0 && msgList != null) { 99 | messageList = msgList; 100 | _refreshUI(); 101 | } 102 | }); 103 | } 104 | 105 | void _refreshUI() { 106 | setState(() {}); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /example/lib/router.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'im/pages/conversation_page.dart'; 4 | import 'im/pages/file_preview_page.dart'; 5 | import 'im/pages/image_preview_page.dart'; 6 | import 'im/pages/sight/video_play_page.dart'; 7 | import 'im/pages/sight/video_record_page.dart'; 8 | import 'im/pages/webview_page.dart'; 9 | import 'other/chat_debug_page.dart'; 10 | import 'other/chatroom_debug_page.dart'; 11 | import 'other/debug_page.dart'; 12 | import 'other/home_page.dart'; 13 | import 'other/message_read_page.dart'; 14 | import 'other/search_message_page.dart'; 15 | import 'other/select_conversation_page.dart'; 16 | 17 | final routes = { 18 | '/': (context) => HomePage(), 19 | '/conversation': (context, {arguments}) => ConversationPage(arguments: arguments), 20 | '/image_preview': (context, {arguments}) => ImagePreviewPage(message: arguments), 21 | '/debug': (context) => DebugPage(), 22 | '/video_record': (context, {arguments}) => VideoRecordPage(arguments: arguments), 23 | '/video_play': (context, {arguments}) => VideoPlayPage(message: arguments), 24 | '/message_read_page': (context, {arguments}) => MessageReadPage(message: arguments), 25 | '/file_preview': (context, {arguments}) => FilePreviewPage(message: arguments), 26 | '/webview': (context, {arguments}) => WebViewPage(arguments: arguments), 27 | '/chat_debug': (context, {arguments}) => ChatDebugPage(arguments: arguments), 28 | '/chatroom_debug': (context, {arguments}) => ChatRoomDebugPage(), 29 | '/search_message': (context, {arguments}) => SearchMessagePage(arguments: arguments), 30 | '/select_conversation_page': (context, {arguments}) => SelectConversationPage(arguments: arguments), 31 | }; 32 | 33 | var onGenerateRoute = (RouteSettings settings) { 34 | // 统一处理 35 | final String? name = settings.name; 36 | final Function? pageContentBuilder = routes[name!]; 37 | if (pageContentBuilder != null) { 38 | if (settings.arguments != null) { 39 | final Route route = MaterialPageRoute(builder: (context) => pageContentBuilder(context, arguments: settings.arguments)); 40 | return route; 41 | } else { 42 | final Route route = MaterialPageRoute(builder: (context) => pageContentBuilder(context)); 43 | return route; 44 | } 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /example/lib/test_message.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' show json; 2 | 3 | import 'package:rongcloud_im_plugin/rongcloud_im_plugin.dart'; 4 | 5 | //app 层的测试消息 6 | class TestMessage extends MessageContent { 7 | static const String objectName = "RCD:TstMsg"; 8 | 9 | String? content; 10 | String? extra; 11 | 12 | @override 13 | void decode(String? jsonStr) { 14 | Map map = json.decode(jsonStr.toString()); 15 | this.content = map["content"]; 16 | this.extra = map["extra"]; 17 | 18 | // decode 消息内容中携带的发送者的用户信息 19 | Map? userMap = map["user"]; 20 | super.decodeUserInfo(userMap); 21 | 22 | // decode 消息中的 @ 提醒信息;消息需要携带 @ 信息时添加此方法 23 | Map? menthionedMap = map["mentionedInfo"]; 24 | super.decodeMentionedInfo(menthionedMap); 25 | } 26 | 27 | @override 28 | String encode() { 29 | Map map = {"content": this.content, "extra": this.extra}; 30 | 31 | // encode 消息内容中携带的发送者的用户信息 32 | if (this.sendUserInfo != null) { 33 | Map userMap = super.encodeUserInfo(this.sendUserInfo); 34 | map["user"] = userMap; 35 | } 36 | 37 | // encode 消息中的 @ 提醒信息;消息需要携带 @ 信息时添加此方法 38 | if (this.mentionedInfo != null) { 39 | Map mentionedMap = super.encodeMentionedInfo(this.mentionedInfo); 40 | map["mentionedInfo"] = mentionedMap; 41 | } 42 | return json.encode(map); 43 | } 44 | 45 | @override 46 | String? conversationDigest() { 47 | return content; 48 | } 49 | 50 | @override 51 | String getObjectName() { 52 | return objectName; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /example/lib/user_data.dart: -------------------------------------------------------------------------------- 1 | //融云 appkey 2 | String RongAppKey = ''; 3 | 4 | //用户 id 5 | String CurrentUserId = ''; 6 | 7 | //通过用户 id 生成的对应融云 token 8 | String RongIMToken = ''; 9 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: rongcloud_im_plugin_example 2 | description: Demonstrates how to use the rongcloud_im_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 | sqflite: 2.0.0+4 12 | path: 1.8.0 13 | 14 | # The following adds the Cupertino Icons font to your application. 15 | # Use with the CupertinoIcons class for iOS style icons. 16 | cupertino_icons: 1.0.3 17 | cached_network_image: 3.1.0 18 | image_picker: 0.8.4+3 19 | file_picker: 4.1.6 20 | open_file: 3.2.1 21 | record: 3.0.0 22 | just_audio: 0.9.11 23 | path_provider: 2.0.3 24 | permission_handler: 8.1.4+2 25 | flutter_sound: 9.1.7 26 | flutter_local_notifications: 8.1.1+2 27 | camera: 0.9.2+2 28 | video_player: 2.1.15 29 | percent_indicator: 3.0.1 30 | fluttertoast: 8.0.8 31 | dio: 4.0.0 32 | shared_preferences: 2.0.7 33 | webview_flutter_plus: 0.2.3 34 | image_gallery_saver: 1.7.1 35 | url_launcher: 6.0.10 36 | rxdart: 0.27.2 37 | flutter_cache_manager: 3.1.2 38 | connectivity: 3.0.6 39 | ffi: 1.1.2 40 | # flutter_background_service: 0.1.5 41 | 42 | 43 | dev_dependencies: 44 | flutter_test: 45 | sdk: flutter 46 | 47 | rongcloud_im_plugin: 48 | path: ../ 49 | 50 | # For information on the generic Dart part of this file, see the 51 | # following page: https://dart.dev/tools/pub/pubspec 52 | 53 | # The following section is specific to Flutter. 54 | flutter: 55 | 56 | # The following line ensures that the Material Icons font is 57 | # included with your application, so that you can use the icons in 58 | # the material Icons class. 59 | uses-material-design: true 60 | assets: 61 | - assets/RCFlutterConf.json 62 | - assets/images/ 63 | - assets/combine.json 64 | 65 | # To add assets to your application, add an assets section, like this: 66 | # assets: 67 | # - images/a_dot_burr.jpeg 68 | # - images/a_dot_ham.jpeg 69 | 70 | # An image asset can refer to one or more resolution-specific "variants", see 71 | # https://flutter.dev/assets-and-images/#resolution-aware. 72 | 73 | # For details regarding adding assets from package dependencies, see 74 | # https://flutter.dev/assets-and-images/#from-packages 75 | 76 | # To add custom fonts to your application, add a fonts section here, 77 | # in this "flutter" section. Each entry in this list should have a 78 | # "family" key with the font family name, and a "fonts" key with a 79 | # list giving the asset and other descriptors for the font. For 80 | # example: 81 | # fonts: 82 | # - family: Schyler 83 | # fonts: 84 | # - asset: fonts/Schyler-Regular.ttf 85 | # - asset: fonts/Schyler-Italic.ttf 86 | # style: italic 87 | # - family: Trajan Pro 88 | # fonts: 89 | # - asset: fonts/TrajanPro.ttf 90 | # - asset: fonts/TrajanPro_Bold.ttf 91 | # weight: 700 92 | # 93 | # For details regarding fonts from package dependencies, 94 | # see https://flutter.dev/custom-fonts/#from-packages -------------------------------------------------------------------------------- /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 | void main() { 12 | testWidgets('Verify Platform version', (WidgetTester tester) async { 13 | // Build our app and trigger a frame. 14 | // Verify that platform version is retrieved. 15 | expect( 16 | find.byWidgetPredicate( 17 | (Widget widget) => widget is Text && (widget.data?.startsWith('Running on:') ?? false), 18 | ), 19 | findsOneWidget, 20 | ); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /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/ephemeral/ 38 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongcloud/rongcloud-im-flutter-sdk/116a2d9a1fb53f7b86d8d8321079fdffafad72e4/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/RCFlutterConfig.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCFlutterConfig.h 3 | // Pods-Runner 4 | // 5 | // Created by Sin on 2019/6/6. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface RCFlutterConfig : NSObject 13 | @property (nonatomic, readonly, assign) BOOL enablePersistentUserInfoCache; 14 | 15 | - (void)updateConf:(NSDictionary *)dic; 16 | @end 17 | 18 | NS_ASSUME_NONNULL_END 19 | -------------------------------------------------------------------------------- /ios/Classes/RCFlutterConfig.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCFlutterConfig.m 3 | // Pods-Runner 4 | // 5 | // Created by Sin on 2019/6/6. 6 | // 7 | 8 | #import "RCFlutterConfig.h" 9 | 10 | @interface RCFlutterConfig () 11 | @property (nonatomic, assign) BOOL enablePersistentUserInfoCache; 12 | 13 | @property (nonatomic, strong) NSDictionary *originDic; 14 | @end 15 | @implementation RCFlutterConfig 16 | - (instancetype)init 17 | { 18 | self = [super init]; 19 | if (self) { 20 | self.enablePersistentUserInfoCache = YES; 21 | } 22 | return self; 23 | } 24 | - (void)updateConf:(NSDictionary *)dic { 25 | self.originDic = dic; 26 | NSDictionary *imDic = dic[@"im"]; 27 | if(imDic) { 28 | self.enablePersistentUserInfoCache = [imDic[@"enablePersistentUserInfoCache"] boolValue]; 29 | } 30 | } 31 | 32 | - (NSString *)description { 33 | return [NSString stringWithFormat:@"[%@]:%@",NSStringFromClass(self.class),self.originDic]; 34 | } 35 | @end 36 | -------------------------------------------------------------------------------- /ios/Classes/RCFlutterMessageFactory.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCFlutterMessageFactory.h 3 | // Pods-Runner 4 | // 5 | // Created by Sin on 2019/6/13. 6 | // 7 | 8 | #import 9 | #import 10 | #import 11 | #import 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface RCFlutterMessageFactory : NSObject 16 | + (NSString *)message2String:(RCMessage *)message; 17 | + (NSString *)conversation2String:(RCConversation *)conversation; 18 | + (NSDictionary *)chatRoomInfo2Dictionary:(RCChatRoomInfo *)chatRoomInfo; 19 | + (RCMessage *)dic2Message:(NSDictionary *)msgDic; 20 | + (NSString *)messageContent2String:(RCMessageContent *)content; 21 | + (NSString *)typingStatus2String:(RCUserTypingStatus *)status; 22 | + (NSString *)searchConversationResult2String:(RCSearchConversationResult *)result; 23 | + (NSString *)tagInfo2String:(RCTagInfo *)tagInfo; 24 | + (NSString *)conversationTagInfo2String:(RCConversationTagInfo *)tagInfo; 25 | + (RCConversationIdentifier *)dict2ConversationIdentifier:(NSDictionary *)dict; 26 | @end 27 | 28 | NS_ASSUME_NONNULL_END 29 | -------------------------------------------------------------------------------- /ios/Classes/RCFlutterUtil.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCFlutterUtil.h 3 | // rongcloud_im_plugin 4 | // 5 | // Created by Sin on 2019/10/15. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface RCFlutterUtil : NSObject 13 | + (UIImage*) getVideoPreViewImage:(NSString *)path; 14 | + (UIImage *)getThumbnailImage:(NSString *)thumbnailBase64String; 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /ios/Classes/RCFlutterUtil.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCFlutterUtil.m 3 | // rongcloud_im_plugin 4 | // 5 | // Created by Sin on 2019/10/15. 6 | // 7 | 8 | #import "RCFlutterUtil.h" 9 | #import 10 | #import 11 | #import 12 | #import 13 | #import 14 | 15 | @implementation RCFlutterUtil 16 | 17 | + (UIImage *)getVideoPreViewImage:(NSString *)path { 18 | if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { 19 | return nil; 20 | } 21 | 22 | if(![path hasPrefix:@"file://"]) {// 必须加 file:// 前缀才能被 AVFoundation 正常识别 23 | path = [NSString stringWithFormat:@"file://%@",path]; 24 | } 25 | AVURLAsset *asset = [AVURLAsset assetWithURL:[NSURL URLWithString:path]]; 26 | AVAssetImageGenerator *generator = [[AVAssetImageGenerator alloc] initWithAsset:asset]; 27 | generator.appliesPreferredTrackTransform = YES; 28 | CMTime time = CMTimeMakeWithSeconds(0.0, 600); 29 | NSError *error = nil; 30 | CMTime actualTime; 31 | CGImageRef image = [generator copyCGImageAtTime:time actualTime:&actualTime error:&error]; 32 | UIImage *shotImage = [[UIImage alloc] initWithCGImage:image]; 33 | CGImageRelease(image); 34 | return shotImage; 35 | } 36 | 37 | + (UIImage *)getThumbnailImage:(NSString *)thumbnailBase64String { 38 | if (!thumbnailBase64String) { 39 | NSLog(@"getThumbnailImage thumbnailBase64String is nil"); 40 | return nil;; 41 | } 42 | NSData *imageData = nil; 43 | if (class_getInstanceMethod([NSData class], @selector(initWithBase64EncodedString:options:))) { 44 | imageData = [[NSData alloc] initWithBase64EncodedString:thumbnailBase64String options:NSDataBase64DecodingIgnoreUnknownCharacters]; 45 | } else { 46 | imageData = [RCUtilities dataWithBase64EncodedString:thumbnailBase64String]; 47 | } 48 | UIImage *thumbnailImage = [UIImage imageWithData:imageData]; 49 | return thumbnailImage; 50 | } 51 | @end 52 | -------------------------------------------------------------------------------- /ios/Classes/RCIMFlutterLog.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCIMFlutterLog.h 3 | // Pods-Runner 4 | // 5 | // Created by Sin on 2019/7/12. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | #define RCLog RCIMFlutterLog 13 | 14 | @interface RCIMFlutterLog : NSObject 15 | + (void)i:(NSString *)content; 16 | + (void)e:(NSString *)content; 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /ios/Classes/RCIMFlutterLog.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCIMFlutterLog.m 3 | // Pods-Runner 4 | // 5 | // Created by Sin on 2019/7/12. 6 | // 7 | 8 | #import "RCIMFlutterLog.h" 9 | 10 | @implementation RCIMFlutterLog 11 | + (void)i:(NSString *)content { 12 | NSLog(@"[RC-Flutter-IM] iOS %@",content); 13 | } 14 | + (void)e:(NSString *)content { 15 | NSLog(@"[RC-Flutter-IM] iOS error %@",content); 16 | } 17 | @end 18 | -------------------------------------------------------------------------------- /ios/Classes/RCIMFlutterWrapper.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCIMFlutterWrapper.h 3 | // Pods-Runner 4 | // 5 | // Created by Sin on 2019/6/5. 6 | // 7 | 8 | #import 9 | #import 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface RCIMFlutterWrapper : NSObject 13 | 14 | + (instancetype)sharedWrapper; 15 | /* 16 | iOS可通过该接口向 Flutter 传递数据 17 | @discussion 如果是远程推送的数据,延时之后再调用该接口,防止 Flutter 尚未初始化就调用,导致 Flutter 无法接受数据 18 | */ 19 | - (void)sendDataToFlutter:(NSDictionary *)userInfo; 20 | 21 | /* 22 | 注册自定义消息 23 | */ 24 | - (void)registerMessageType:(Class)messageClass; 25 | 26 | + (NSString *)getVersion; 27 | 28 | @end 29 | 30 | NS_ASSUME_NONNULL_END 31 | 32 | /* 33 | 内部接口,开发者请勿调用 34 | */ 35 | @interface RCIMFlutterWrapper (Internal) 36 | 37 | - (void)setFlutterChannel:(FlutterMethodChannel *_Nullable)channel; 38 | - (void)handleMethodCall:(FlutterMethodCall*_Nonnull)call result:(FlutterResult _Nonnull )result; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /ios/Classes/RongcloudImPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RongcloudImPlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /ios/Classes/RongcloudImPlugin.m: -------------------------------------------------------------------------------- 1 | #import "RongcloudImPlugin.h" 2 | #import "RCIMFlutterWrapper.h" 3 | #import 4 | 5 | @implementation RongcloudImPlugin 6 | 7 | + (void)registerWithRegistrar:(NSObject*)registrar { 8 | dispatch_async(dispatch_get_main_queue(), ^{ 9 | FlutterMethodChannel* channel = [FlutterMethodChannel methodChannelWithName:@"rongcloud_im_plugin" 10 | binaryMessenger:[registrar messenger]]; 11 | RongcloudImPlugin* instance = [[RongcloudImPlugin alloc] init]; 12 | [registrar addApplicationDelegate:instance]; 13 | [registrar addMethodCallDelegate:instance channel:channel]; 14 | [[RCIMFlutterWrapper sharedWrapper] setFlutterChannel:channel]; 15 | }); 16 | } 17 | 18 | - (void)detachFromEngineForRegistrar:(NSObject *)registrar { 19 | dispatch_async(dispatch_get_main_queue(), ^{ 20 | [[RCIMFlutterWrapper sharedWrapper] setFlutterChannel:nil]; 21 | }); 22 | 23 | } 24 | 25 | - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { 26 | [[RCIMFlutterWrapper sharedWrapper] handleMethodCall:call result:result]; 27 | } 28 | 29 | - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { 30 | [[RCCoreClient sharedCoreClient] setDeviceTokenData:deviceToken]; 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /ios/rongcloud_im_plugin.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 3 | # 4 | Pod::Spec.new do |s| 5 | s.name = 'rongcloud_im_plugin' 6 | s.version = '0.0.1' 7 | s.summary = 'A new flutter plugin project.' 8 | s.description = <<-DESC 9 | A new flutter plugin project. 10 | DESC 11 | s.homepage = 'http://example.com' 12 | s.license = { :file => '../LICENSE' } 13 | s.author = { 'Your Company' => 'email@example.com' } 14 | s.source = { :path => '.' } 15 | s.source_files = 'Classes/**/*' 16 | s.public_header_files = 'Classes/**/*.h' 17 | s.static_framework = true 18 | s.dependency 'Flutter' 19 | 20 | s.dependency 'RongCloudIM/IMLibCore', '5.2.3' 21 | s.dependency 'RongCloudIM/ChatRoom', '5.2.3' 22 | s.dependency 'RongCloudIM/PublicService', '5.2.3' 23 | 24 | s.ios.deployment_target = '8.0' 25 | end 26 | 27 | -------------------------------------------------------------------------------- /lib/rongcloud_im_plugin.dart: -------------------------------------------------------------------------------- 1 | library rongcloud_im_plugin; 2 | 3 | //宏定义 4 | export 'src/common_define.dart'; 5 | //实体类 6 | export 'src/info/chatroom_info.dart'; 7 | export 'src/info/conversation.dart'; 8 | export 'src/info/conversation_identifier.dart'; 9 | export 'src/info/conversation_tag_info.dart'; 10 | export 'src/info/history_message_option.dart'; 11 | export 'src/info/message.dart'; 12 | export 'src/info/push_config.dart'; 13 | export 'src/info/search_conversation_result.dart'; 14 | export 'src/info/send_message_option.dart'; 15 | export 'src/info/tag_info.dart'; 16 | export 'src/info/typing_status.dart'; 17 | export 'src/info/blocked_message_info.dart'; 18 | 19 | export 'src/message/chatroom_kv_notification_message.dart'; 20 | export 'src/message/combine_message.dart'; 21 | export 'src/message/file_message.dart'; 22 | export 'src/message/gif_message.dart'; 23 | export 'src/message/group_notification_message.dart'; 24 | export 'src/message/image_message.dart'; 25 | export 'src/message/location_message.dart'; 26 | 27 | //消息体 28 | export 'src/message/message_content.dart'; 29 | export 'src/message/recall_notification_message.dart'; 30 | export 'src/message/reference_message.dart'; 31 | export 'src/message/rich_content_message.dart'; 32 | export 'src/message/sight_message.dart'; 33 | export 'src/message/text_message.dart'; 34 | export 'src/message/voice_message.dart'; 35 | //核心类 36 | export 'src/rong_im_client.dart'; 37 | -------------------------------------------------------------------------------- /lib/src/info/blocked_message_info.dart: -------------------------------------------------------------------------------- 1 | /// @author Pan ming da 2 | /// @time 2021/9/8 16:28 3 | /// @version 1.0 4 | 5 | class BlockedMessageInfo { 6 | final int conversationType; 7 | final String targetId; 8 | final String blockMsgUId; 9 | final int blockType; 10 | final String? extra; 11 | 12 | BlockedMessageInfo.fromMap(Map map) 13 | : conversationType = map['conversationType'], 14 | targetId = map['targetId'], 15 | blockMsgUId = map['blockMsgUId'], 16 | blockType = map['blockType'], 17 | extra = map['extra']; 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/info/chatroom_info.dart: -------------------------------------------------------------------------------- 1 | class ChatRoomInfo { 2 | String? targetId; 3 | int? memberOrder; //参考 RCChatRoomMemberOrder 4 | List? memberInfoList; 5 | int? totalMemeberCount; 6 | } 7 | 8 | class ChatRoomMemberInfo { 9 | String? userId; 10 | int? joinTime; 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/info/connection_status_convert.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | import '../common_define.dart'; 4 | 5 | ///网络状态装换 6 | /// 7 | ///由于 iOS 与 Android 的网络状态码并不完全匹配,所以要在此进行转换 8 | /// 9 | ///具体可以参见对应平台的枚举: iOS 的 [RCConnectionStatus] 和 Android 的 [ConnectionStatus] 10 | class ConnectionStatusConvert { 11 | static int? convert(int? originCode) { 12 | if (TargetPlatform.android == defaultTargetPlatform) { 13 | return _convertAndroid(originCode); 14 | } else if (TargetPlatform.iOS == defaultTargetPlatform) { 15 | return _convertIOS(originCode); 16 | } 17 | return originCode; 18 | } 19 | 20 | static int? _convertIOS(int? originCode) { 21 | if (originCode == 0) { 22 | return RCConnectionStatus.Connected; 23 | } else if (originCode == 10) { 24 | return RCConnectionStatus.Connecting; 25 | } else if (originCode == 6) { 26 | return RCConnectionStatus.KickedByOtherClient; 27 | } else if (originCode == 1) { 28 | return RCConnectionStatus.NetworkUnavailable; 29 | } else if (originCode == 15) { 30 | return RCConnectionStatus.TokenIncorrect; 31 | } else if (originCode == 16) { 32 | return RCConnectionStatus.UserBlocked; 33 | } else if (originCode == 12) { 34 | return RCConnectionStatus.DisConnected; 35 | } else if (originCode == 13) { 36 | return RCConnectionStatus.Suspend; 37 | } else if (originCode == 14) { 38 | return RCConnectionStatus.Timeout; 39 | } 40 | return originCode; 41 | } 42 | 43 | static int? _convertAndroid(int? originCode) { 44 | if (originCode == 0) { 45 | return RCConnectionStatus.Connected; 46 | } else if (originCode == 1) { 47 | return RCConnectionStatus.Connecting; 48 | } else if (originCode == 12) { 49 | return RCConnectionStatus.DisConnected; 50 | } else if (originCode == 3) { 51 | return RCConnectionStatus.KickedByOtherClient; 52 | } else if (originCode == -1) { 53 | return RCConnectionStatus.NetworkUnavailable; 54 | } else if (originCode == 4) { 55 | return RCConnectionStatus.TokenIncorrect; 56 | } else if (originCode == 6) { 57 | return RCConnectionStatus.UserBlocked; 58 | } else if (originCode == 13) { 59 | return RCConnectionStatus.Suspend; 60 | } else if (originCode == 14) { 61 | return RCConnectionStatus.Timeout; 62 | } 63 | return originCode; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/src/info/conversation.dart: -------------------------------------------------------------------------------- 1 | import '../message/message_content.dart'; 2 | 3 | class Conversation { 4 | int? conversationType; 5 | String? targetId; 6 | int? unreadMessageCount; 7 | int? receivedStatus; 8 | int? sentStatus; 9 | int? sentTime; 10 | bool? isTop; 11 | String? objectName; 12 | String? senderUserId; 13 | int? latestMessageId; 14 | MessageContent? latestMessageContent; 15 | int? mentionedCount; // 会话中@消息的个数 16 | String? draft; //会话草稿内容 17 | int? blockStatus; // 会话是否是免打扰状态 18 | int? receivedTime; 19 | 20 | //如果 content 为 null ,说明消息内容本身未被 flutter 层正确解析,则消息内容会保存到该 map 中 21 | Map? originContentMap; 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/info/conversation_identifier.dart: -------------------------------------------------------------------------------- 1 | class ConversationIdentifier { 2 | int? conversationType; 3 | String? targetId; 4 | } 5 | -------------------------------------------------------------------------------- /lib/src/info/conversation_tag_info.dart: -------------------------------------------------------------------------------- 1 | import 'package:rongcloud_im_plugin/src/info/tag_info.dart'; 2 | 3 | class ConversationTagInfo { 4 | TagInfo? tagInfo; 5 | bool? isTop; 6 | } 7 | -------------------------------------------------------------------------------- /lib/src/info/history_message_option.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | class HistoryMessageOption { 4 | int count; 5 | int recordTime; 6 | int order; //0 降序, 1 升序 7 | HistoryMessageOption(this.count, this.recordTime, this.order); 8 | } 9 | -------------------------------------------------------------------------------- /lib/src/info/message.dart: -------------------------------------------------------------------------------- 1 | import '../message/message_content.dart'; 2 | 3 | class Message extends Object { 4 | int? conversationType; //会话类型 参见 RCConversationType 5 | String? targetId; //会话 id 6 | int? messageId; //messageId ,本地数据库的自增 id 7 | int? messageDirection; //消息方向 参见 RCMessageDirection 8 | String? senderUserId; //发送者 id 9 | int? receivedStatus; //消息接收状态 参见 RCReceivedStatus 10 | int? sentStatus; //消息发送状态 参见 RCSentStatus 11 | int? sentTime; //发送时间,unix 时间戳,单位毫秒 12 | String? objectName; //消息 objName 13 | MessageContent? content; //消息内容 14 | String? messageUId; //消息 UID,全网唯一 Id 15 | String? extra; // 扩展信息 16 | bool? canIncludeExpansion; // 消息是否可以包含扩展信息 17 | Map? expansionDic; // 消息扩展信息列表 18 | 19 | ReadReceiptInfo? readReceiptInfo; //阅读回执状态 20 | MessageConfig? messageConfig; // 消息配置 21 | MessagePushConfig? messagePushConfig; // 推送配置 22 | 23 | //如果 content 为 null ,说明消息内容本身未被 flutter 层正确解析,则消息内容会保存到该 map 中 24 | Map? originContentMap; 25 | 26 | String toString() { 27 | return "messageId:$messageId messageUId:$messageUId objectName:$objectName conversationType:$conversationType targetId:$targetId conversationType:$conversationType messageDirection:$messageDirection senderUserId:$senderUserId receivedStatus:$receivedStatus sentStatus:$sentStatus sentTime:$sentTime content:${content!.encode()}"; 28 | } 29 | } 30 | 31 | class ReadReceiptInfo extends Object { 32 | bool? isReceiptRequestMessage; //是否需要回执消息 33 | bool? hasRespond; //是否已经发送回执 34 | Map? userIdList; //发送回执的用户ID列表 35 | } 36 | 37 | class MessageConfig extends Object { 38 | bool? disableNotification; //是否关闭通知,true 为关闭通知,false 为打开通知,默认为 false 39 | } 40 | 41 | class MessagePushConfig extends Object { 42 | String? pushTitle; //推送标题 43 | String? pushContent; //推送内容 44 | String? pushData; //远程推送附加信息 45 | bool? forceShowDetailContent; //是否强制显示通知详情 46 | IOSConfig? iOSConfig; //iOS 平台相关配置 47 | AndroidConfig? androidConfig; //Android 平台相关配置 48 | bool? disablePushTitle; //通知栏是否屏蔽通知标题 49 | String? templateId; //推送模板 ID 50 | } 51 | 52 | class IOSConfig extends Object { 53 | String? thread_id; //iOS 平台通知栏分组 ID 54 | String? apns_collapse_id; //iOS 平台通知覆盖 ID 55 | } 56 | 57 | class AndroidConfig extends Object { 58 | String? notificationId; // Android 平台 Push 唯一标识 59 | String? channelIdMi; // 小米推送平台渠道 ID 60 | String? channelIdHW; // 华为推送平台渠道 ID 61 | String? channelIdOPPO; // OPPO 推送平台渠道 ID 62 | String? typeVivo; // VIVO 推送平台推送类型 ,目前可选值"0"(运营消息); "1"(系统消息) 63 | } 64 | -------------------------------------------------------------------------------- /lib/src/info/push_config.dart: -------------------------------------------------------------------------------- 1 | class PushConfig { 2 | bool? enableHWPush; 3 | bool? enableFCM; 4 | bool? enableVivoPush; 5 | String? miAppId; 6 | String? miAppKey; 7 | String? mzAppId; 8 | String? mzAppKey; 9 | String? oppoAppKey; 10 | String? oppoAppSecret; 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/info/search_conversation_result.dart: -------------------------------------------------------------------------------- 1 | import 'conversation.dart'; 2 | 3 | class SearchConversationResult { 4 | Conversation? mConversation; 5 | int? mMatchCount; 6 | } 7 | -------------------------------------------------------------------------------- /lib/src/info/send_message_option.dart: -------------------------------------------------------------------------------- 1 | class SendMessageOption { 2 | bool isVoIPPush; // 发送的消息,是否走 VOIP 推送 3 | SendMessageOption(this.isVoIPPush); 4 | } 5 | -------------------------------------------------------------------------------- /lib/src/info/tag_info.dart: -------------------------------------------------------------------------------- 1 | class TagInfo { 2 | String? tagId; 3 | String? tagName; 4 | int? count; 5 | int? timestamp; 6 | } 7 | -------------------------------------------------------------------------------- /lib/src/info/typing_status.dart: -------------------------------------------------------------------------------- 1 | class TypingStatus { 2 | String? userId; 3 | String? typingContentType; 4 | int? sentTime; 5 | } 6 | -------------------------------------------------------------------------------- /lib/src/message/chatroom_kv_notification_message.dart: -------------------------------------------------------------------------------- 1 | import 'message_content.dart'; 2 | import 'dart:convert' show json; 3 | import 'dart:developer' as developer; 4 | 5 | // 聊天室自定义属性通知消息 6 | // 不要随意构造此类消息发送,调用设置或者删除接口时会自动构建。 7 | class ChatroomKVNotificationMessage extends MessageContent { 8 | static const String objectName = "RC:chrmKVNotiMsg"; 9 | 10 | int? type; //聊天室操作的类型 11 | String? key; //聊天室属性名称 12 | String? value; //聊天室属性对应的值 13 | String? extra; //通知消息的自定义字段,最大长度 2 kb 14 | 15 | @override 16 | void decode(String? jsonStr) { 17 | if (jsonStr == null && jsonStr!.isEmpty) { 18 | developer.log("Flutter ChatroomKVNotificationMessage deocde error: no content", name: "RongIMClient.ChatroomKVNotificationMessage"); 19 | return; 20 | } 21 | Map map = json.decode(jsonStr.toString()); 22 | this.type = map["type"]; 23 | this.key = map["key"]; 24 | this.value = map["value"]; 25 | this.extra = map["extra"]; 26 | Map? userMap = map["user"]; 27 | super.decodeUserInfo(userMap); 28 | Map? menthionedMap = map["mentionedInfo"]; 29 | super.decodeMentionedInfo(menthionedMap); 30 | } 31 | 32 | @override 33 | String encode() { 34 | Map map = {"type": this.type, "key": this.key, "value": this.value, "extra": this.extra}; 35 | if (this.sendUserInfo != null) { 36 | Map userMap = super.encodeUserInfo(this.sendUserInfo); 37 | map["user"] = userMap; 38 | } else { 39 | map["user"] = {}; 40 | } 41 | if (this.mentionedInfo != null) { 42 | Map mentionedMap = super.encodeMentionedInfo(this.mentionedInfo); 43 | map["mentionedInfo"] = mentionedMap; 44 | } 45 | return json.encode(map); 46 | } 47 | 48 | @override 49 | String getObjectName() { 50 | return objectName; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/src/message/combine_message.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:developer' as developer; 3 | 4 | import 'message_content.dart'; 5 | 6 | class CombineMessage extends MessageContent { 7 | static const String objectName = "RC:CombineMsg"; 8 | String? title = ""; 9 | 10 | // 这两个参数用来拼装默认消息的标题 11 | // 区分合并消息是在群聊里还是单聊里 12 | int? conversationType; 13 | 14 | // 单聊里最多有两个,群聊不记录 15 | List? nameList; 16 | 17 | // 默认消息的内容 18 | List? summaryList; 19 | String? localPath; 20 | String? mMediaUrl; 21 | String? extra = ""; 22 | String? mName = ""; 23 | 24 | static CombineMessage obtain(String localPath) { 25 | CombineMessage msg = new CombineMessage(); 26 | msg.localPath = localPath; 27 | // 会话类型默认是私聊 28 | msg.conversationType = 1; 29 | return msg; 30 | } 31 | 32 | @override 33 | String encode() { 34 | Map map = {"title": title, "name": mName, "localPath": localPath, "remoteUrl": mMediaUrl, "extra": extra, "conversationType": conversationType, "nameList": nameList, "summaryList": summaryList}; 35 | if (this.sendUserInfo != null) { 36 | Map userMap = super.encodeUserInfo(this.sendUserInfo); 37 | map["user"] = userMap; 38 | } 39 | if (this.mentionedInfo != null) { 40 | Map mentionedMap = super.encodeMentionedInfo(this.mentionedInfo); 41 | map["mentionedInfo"] = mentionedMap; 42 | } 43 | if (this.destructDuration != null && this.destructDuration! > 0) { 44 | map["burnDuration"] = this.destructDuration; 45 | } 46 | return json.encode(map); 47 | } 48 | 49 | @override 50 | void decode(String? jsonStr) { 51 | if (jsonStr == null || jsonStr == "") { 52 | developer.log("Flutter CombineMessage deocde error: no content", name: "RongIMClient.CombineMessage"); 53 | return; 54 | } 55 | Map map = json.decode(jsonStr); 56 | this.title = map["title"]; 57 | this.mName = map["name"]; 58 | this.mMediaUrl = map["remoteUrl"]; 59 | this.localPath = map["localPath"]; 60 | this.conversationType = map["conversationType"]; 61 | if (map["nameList"] != null) { 62 | this.nameList = List.from(map["nameList"]); 63 | } else { 64 | this.nameList = []; 65 | } 66 | 67 | this.summaryList = List.from(map["summaryList"]); 68 | this.extra = map["extra"]; 69 | Map? userMap = map["user"]; 70 | super.decodeUserInfo(userMap); 71 | Map? menthionedMap = map["mentionedInfo"]; 72 | super.decodeMentionedInfo(menthionedMap); 73 | this.destructDuration = map["burnDuration"]; 74 | } 75 | 76 | @override 77 | String conversationDigest() { 78 | return "[聊天记录]"; 79 | } 80 | 81 | @override 82 | String getObjectName() { 83 | return objectName; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/src/message/file_message.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:developer' as developer; 3 | 4 | import 'message_content.dart'; 5 | 6 | class FileMessage extends MessageContent { 7 | static const String objectName = "RC:FileMsg"; 8 | int? mSize; 9 | String? mType; //后缀名,默认是 bin 10 | int? progress; 11 | String? localPath; 12 | String? mMediaUrl; 13 | String? extra = ""; 14 | String? mName = ""; 15 | 16 | /// [localPath] 本地路径,Android 必须以 file:// 开头 17 | static FileMessage obtain(String localPath) { 18 | FileMessage msg = new FileMessage(); 19 | msg.localPath = localPath; 20 | return msg; 21 | } 22 | 23 | @override 24 | void decode(String? jsonStr) { 25 | if (jsonStr == null || jsonStr == "") { 26 | developer.log("Flutter FileMessage deocde error: no content", name: "RongIMClient.FileMessage"); 27 | return; 28 | } 29 | Map map = json.decode(jsonStr); 30 | this.mName = map["name"]; 31 | this.mType = map["type"]; 32 | this.mSize = map["size"]; 33 | this.localPath = map["localPath"]; 34 | this.extra = map["extra"]; 35 | this.mMediaUrl = map["fileUrl"]; 36 | Map? userMap = map["user"]; 37 | super.decodeUserInfo(userMap); 38 | Map? menthionedMap = map["mentionedInfo"]; 39 | super.decodeMentionedInfo(menthionedMap); 40 | // this.destructDuration = map["burnDuration"]; 41 | } 42 | 43 | @override 44 | String encode() { 45 | Map map = Map(); 46 | if (this.extra != null) { 47 | map["extra"] = this.extra; 48 | } 49 | if (this.mType != null) { 50 | map["type"] = this.mType; 51 | } else { 52 | map["type"] = ""; 53 | } 54 | if (this.mName != null) { 55 | map["name"] = this.mName; 56 | } else { 57 | map["name"] = ""; 58 | } 59 | if (this.mSize != null) { 60 | map["size"] = this.mSize; 61 | } else { 62 | map["size"] = 0; 63 | } 64 | if (this.localPath != null) { 65 | map["localPath"] = this.localPath; 66 | } else { 67 | map["localPath"] = ""; 68 | } 69 | if (mMediaUrl != null && mMediaUrl!.length > 0) { 70 | map['fileUrl'] = mMediaUrl; 71 | } 72 | if (this.sendUserInfo != null) { 73 | Map userMap = super.encodeUserInfo(this.sendUserInfo); 74 | map["user"] = userMap; 75 | } 76 | if (this.mentionedInfo != null) { 77 | Map mentionedMap = super.encodeMentionedInfo(this.mentionedInfo); 78 | map["mentionedInfo"] = mentionedMap; 79 | } 80 | // if (this.destructDuration != null && this.destructDuration > 0) { 81 | // map["burnDuration"] = this.destructDuration; 82 | // } 83 | return json.encode(map); 84 | } 85 | 86 | @override 87 | String conversationDigest() { 88 | return "文件"; 89 | } 90 | 91 | @override 92 | String getObjectName() { 93 | return objectName; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/src/message/gif_message.dart: -------------------------------------------------------------------------------- 1 | import 'message_content.dart'; 2 | import 'dart:convert' show json; 3 | import 'dart:developer' as developer; 4 | 5 | //Gif消息 6 | class GifMessage extends MessageContent { 7 | static const String objectName = "RC:GIFMsg"; 8 | 9 | String? localPath; //本地路径 10 | String? remoteUrl; //远端路径 11 | int? width; //GIF 图的宽 12 | int? height; //GIF 图的高 13 | int? gifDataSize; //GIF 图的大小 14 | String? name; //名字 15 | String? extra; //GIF 消息的附加信息 16 | 17 | /// 根据 localPath 构建 GifMessage 18 | /// [localPath] 本地路径,Android 必须以 file:// 开头 19 | static GifMessage obtain(String localPath, {int width = 0, int height = 0}) { 20 | GifMessage msg = new GifMessage(); 21 | msg.localPath = localPath; 22 | msg.width = width; 23 | msg.height = height; 24 | return msg; 25 | } 26 | 27 | @override 28 | void decode(String? jsonStr) { 29 | if (jsonStr == null || jsonStr == "") { 30 | developer.log("Flutter GifMessage deocde error: no content", name: "RongIMClient.GifMessage"); 31 | return; 32 | } 33 | Map map = json.decode(jsonStr.toString()); 34 | this.localPath = map["localPath"]; 35 | this.remoteUrl = map["remoteUrl"]; 36 | // this.gifImageData = map["gifImageData"]; 37 | this.width = map["width"]; 38 | this.height = map["height"]; 39 | this.name = map["name"]; 40 | this.extra = map["extra"]; 41 | this.gifDataSize = map["gifDataSize"]; 42 | Map? userMap = map["user"]; 43 | super.decodeUserInfo(userMap); 44 | Map? menthionedMap = map["mentionedInfo"]; 45 | super.decodeMentionedInfo(menthionedMap); 46 | this.destructDuration = map["burnDuration"]; 47 | } 48 | 49 | @override 50 | String encode() { 51 | Map map = {"localPath": this.localPath, "extra": this.extra}; 52 | if (this.width != null) { 53 | map["width"] = this.width; 54 | } 55 | if (this.height != null) { 56 | map["height"] = this.height; 57 | } 58 | if (this.name != null) { 59 | map["name"] = this.name; 60 | } 61 | if (this.gifDataSize != null) { 62 | map["gifDataSize"] = this.gifDataSize; 63 | } 64 | if (this.sendUserInfo != null) { 65 | Map userMap = super.encodeUserInfo(this.sendUserInfo); 66 | map["user"] = userMap; 67 | } 68 | if (this.mentionedInfo != null) { 69 | Map mentionedMap = super.encodeMentionedInfo(this.mentionedInfo); 70 | map["mentionedInfo"] = mentionedMap; 71 | } 72 | if (this.remoteUrl != null && this.remoteUrl!.isNotEmpty) { 73 | map["remoteUrl"] = this.remoteUrl; 74 | } 75 | if (this.destructDuration != null && this.destructDuration! > 0) { 76 | map["burnDuration"] = this.destructDuration; 77 | } 78 | return json.encode(map); 79 | } 80 | 81 | @override 82 | String conversationDigest() { 83 | return "动图"; 84 | } 85 | 86 | @override 87 | String getObjectName() { 88 | return objectName; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/src/message/group_notification_message.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' show json; 2 | import 'dart:developer' as developer; 3 | 4 | import 'message_content.dart'; 5 | 6 | class GroupNotificationMessage extends MessageContent { 7 | static const String objectName = "RC:GrpNtf"; 8 | 9 | /// 创建群 10 | static const String GROUP_OPERATION_CREATE = 'Create'; 11 | 12 | /// 新成员加入群 13 | static const String GROUP_OPERATION_ADD = "Add"; 14 | 15 | /// 解散群。 16 | static const String GROUP_OPERATION_DISMISS = "Dismiss"; 17 | 18 | /// 成员退出群。 19 | static const String GROUP_OPERATION_QUIT = "Quit"; 20 | 21 | /// 成员被管理员踢出。 22 | static const String GROUP_OPERATION_KICKED = "Kicked"; 23 | 24 | /// 群组重命名。 25 | static const String GROUP_OPERATION_RENAME = "Rename"; 26 | 27 | /// 群组公告变更。 28 | static const String GROUP_OPERATION_BULLETIN = "Bulletin"; 29 | 30 | /// 操作人 UserId,可以为空; 31 | String? operatorUserId; 32 | 33 | /// 操作名,对应 GroupOperationXxxx,或任意字符串。 34 | String? operation; 35 | 36 | /// 被操做人 UserId 或者操作数据(如改名后的名称)。 37 | String? data; 38 | 39 | ///操作信息,可以为空,如:你被 xxx 踢出了群。 40 | String? message; 41 | 42 | /// 附加信息。 43 | String? extra; 44 | 45 | @override 46 | String encode() { 47 | Map map = { 48 | "operatorUserId": this.operation, 49 | "operation": this.operation, 50 | }; 51 | if (data != null && data!.isNotEmpty) { 52 | map["data"] = data; 53 | } 54 | if (message != null && message!.isNotEmpty) { 55 | map["message"] = message; 56 | } 57 | if (extra != null && extra!.isNotEmpty) { 58 | map["extra"] = extra; 59 | } 60 | if (this.sendUserInfo != null) { 61 | Map userMap = super.encodeUserInfo(this.sendUserInfo); 62 | map["user"] = userMap; 63 | } else { 64 | map["user"] = {}; 65 | } 66 | if (this.mentionedInfo != null) { 67 | Map mentionedMap = super.encodeMentionedInfo(this.mentionedInfo); 68 | map["mentionedInfo"] = mentionedMap; 69 | } 70 | return json.encode(map); 71 | } 72 | 73 | @override 74 | String conversationDigest() { 75 | return '[群组通知]'; 76 | } 77 | 78 | @override 79 | void decode(String? jsonStr) { 80 | if (jsonStr == null || jsonStr.isEmpty) { 81 | developer.log("Flutter GroupNotificationMessage deocde error: no content", name: "RongIMClient.GroupNotificationMessage"); 82 | return; 83 | } 84 | Map map = json.decode(jsonStr.toString()); 85 | this.operatorUserId = map["operatorUserId"]; 86 | this.operation = map["operation"]; 87 | this.data = map["data"]; 88 | this.message = map["message"]; 89 | this.extra = map["extra"]; 90 | Map? userMap = map["user"]; 91 | super.decodeUserInfo(userMap); 92 | Map? menthionedMap = map["mentionedInfo"]; 93 | super.decodeMentionedInfo(menthionedMap); 94 | } 95 | 96 | @override 97 | String getObjectName() { 98 | return objectName; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/src/message/image_message.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' show json; 2 | import 'dart:developer' as developer; 3 | 4 | import 'message_content.dart'; 5 | 6 | class ImageMessage extends MessageContent { 7 | static const String objectName = "RC:ImgMsg"; 8 | 9 | String? localPath; 10 | String? extra; 11 | String? content; 12 | String? imageUri; 13 | String? mThumbUri; //缩略图地址 14 | 15 | /// [localPath] 本地路径,Android 必须以 file:// 开头 16 | static ImageMessage obtain(String localPath) { 17 | ImageMessage msg = new ImageMessage(); 18 | msg.localPath = localPath; 19 | return msg; 20 | } 21 | 22 | @override 23 | void decode(String? jsonStr) { 24 | if (jsonStr == null || jsonStr.isEmpty) { 25 | developer.log("Flutter ImageMessage deocde error: no content", name: "RongIMClient.ImageMessage"); 26 | return; 27 | } 28 | Map map = json.decode(jsonStr.toString()); 29 | this.localPath = map["localPath"]; 30 | this.content = map["content"]; 31 | this.imageUri = map["imageUri"]; 32 | this.mThumbUri = map["thumbUri"]; 33 | this.extra = map["extra"]; 34 | Map? userMap = map["user"]; 35 | super.decodeUserInfo(userMap); 36 | Map? menthionedMap = map["mentionedInfo"]; 37 | super.decodeMentionedInfo(menthionedMap); 38 | this.destructDuration = map["burnDuration"]; 39 | } 40 | 41 | @override 42 | String encode() { 43 | Map map = {"extra": this.extra}; 44 | if (this.content != null) { 45 | map["content"] = this.content; 46 | } 47 | if (this.localPath != null) { 48 | map["localPath"] = this.localPath; 49 | } else { 50 | map["localPath"] = ""; 51 | } 52 | if (this.imageUri != null) { 53 | map["imageUri"] = this.imageUri; 54 | } 55 | if (this.mThumbUri != null) { 56 | map["thumbUri"] = this.mThumbUri; 57 | } 58 | if (this.sendUserInfo != null) { 59 | Map userMap = super.encodeUserInfo(this.sendUserInfo); 60 | map["user"] = userMap; 61 | } 62 | if (this.mentionedInfo != null) { 63 | Map mentionedMap = super.encodeMentionedInfo(this.mentionedInfo); 64 | map["mentionedInfo"] = mentionedMap; 65 | } 66 | if (this.destructDuration != null && this.destructDuration! > 0) { 67 | map["burnDuration"] = this.destructDuration; 68 | } 69 | return json.encode(map); 70 | } 71 | 72 | @override 73 | String conversationDigest() { 74 | return "图片"; 75 | } 76 | 77 | @override 78 | String getObjectName() { 79 | return objectName; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/src/message/location_message.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:developer' as developer; 3 | 4 | import 'message_content.dart'; 5 | 6 | class LocationMessage extends MessageContent { 7 | static const String objectName = "RC:LBSMsg"; 8 | double? mLat; 9 | double? mLng; 10 | String? mPoi; 11 | String? mBase64; 12 | String? mImgUri; 13 | 14 | static LocationMessage obtain(double lat, double lng, String poi, String imgUri) { 15 | LocationMessage msg = LocationMessage(); 16 | msg.mLat = lat; 17 | msg.mLng = lng; 18 | msg.mPoi = poi; 19 | msg.mImgUri = imgUri; 20 | return msg; 21 | } 22 | 23 | @override 24 | void decode(String? jsonStr) { 25 | if (jsonStr == null || jsonStr.isEmpty) { 26 | developer.log("Flutter LocationMessage deocde error: no content", name: "RongIMClient.LocationMessage"); 27 | return; 28 | } 29 | Map map = json.decode(jsonStr); 30 | this.mLat = map["latitude"]; 31 | this.mLng = map["longitude"]; 32 | this.mPoi = map["poi"]; 33 | this.mBase64 = map["content"]; 34 | this.mImgUri = map["content"]; 35 | Map? userMap = map["user"]; 36 | super.decodeUserInfo(userMap); 37 | } 38 | 39 | @override 40 | String encode() { 41 | Map map = {"latitude": this.mLat, "longitude": this.mLng, "poi": mPoi, "mBase64": mBase64, "mImgUri": mImgUri, "content": mBase64}; 42 | if (this.sendUserInfo != null) { 43 | Map userMap = super.encodeUserInfo(this.sendUserInfo); 44 | map["user"] = userMap; 45 | } 46 | return json.encode(map); 47 | } 48 | 49 | @override 50 | String conversationDigest() { 51 | return "位置"; 52 | } 53 | 54 | @override 55 | String getObjectName() { 56 | return objectName; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/src/message/message_content.dart: -------------------------------------------------------------------------------- 1 | import '../common_define.dart'; 2 | 3 | class MessageContent implements MessageCoding, MessageContentView { 4 | // 消息内容中携带的发送者的用户信息 5 | UserInfo? sendUserInfo; 6 | 7 | // 消息中的 @ 提醒信息 8 | MentionedInfo? mentionedInfo; 9 | 10 | // 焚烧时间,默认是 0,0 代表该消息非阅后即焚消息。 11 | int? destructDuration = 0; 12 | 13 | @override 14 | void decode(String jsonStr) { 15 | // TODO: implement decode 16 | } 17 | 18 | @override 19 | String? encode() { 20 | // TODO: implement encode 21 | return null; 22 | } 23 | 24 | @override 25 | String? conversationDigest() { 26 | // TODO: implement conversationDigest 27 | return null; 28 | } 29 | 30 | @override 31 | String? getObjectName() { 32 | return null; 33 | } 34 | 35 | void decodeUserInfo(Map? userMap) { 36 | if (userMap == null) { 37 | return; 38 | } 39 | UserInfo userInfo = new UserInfo(); 40 | userInfo.userId = userMap["id"]; 41 | userInfo.name = userMap["name"]; 42 | userInfo.portraitUri = userMap["portrait"]; 43 | userInfo.extra = userMap["extra"]; 44 | this.sendUserInfo = userInfo; 45 | } 46 | 47 | Map encodeUserInfo(UserInfo? userInfo) { 48 | Map userMap = new Map(); 49 | if (userInfo != null) { 50 | if (userInfo.userId != null) { 51 | userMap["id"] = userInfo.userId; 52 | } 53 | if (userInfo.name != null) { 54 | userMap["name"] = userInfo.name; 55 | } 56 | if (userInfo.portraitUri != null) { 57 | userMap["portrait"] = userInfo.portraitUri; 58 | } 59 | if (userInfo.extra != null) { 60 | userMap["extra"] = userInfo.extra; 61 | } 62 | } 63 | return userMap; 64 | } 65 | 66 | void decodeMentionedInfo(Map? mentionedMap) { 67 | if (mentionedMap == null) { 68 | return; 69 | } 70 | MentionedInfo mentionedInfo = new MentionedInfo(); 71 | mentionedInfo.type = mentionedMap["type"]; 72 | if (mentionedInfo.type == RCMentionedType.Users) { 73 | if (mentionedMap["userIdList"] == null) { 74 | return; 75 | } 76 | List userIdList = new List.from(mentionedMap["userIdList"]); 77 | mentionedInfo.userIdList = userIdList; 78 | } 79 | mentionedInfo.mentionedContent = mentionedMap["mentionedContent"]; 80 | this.mentionedInfo = mentionedInfo; 81 | } 82 | 83 | Map encodeMentionedInfo(MentionedInfo? mentionedInfo) { 84 | Map mentionedMap = new Map(); 85 | if (mentionedInfo != null) { 86 | if (mentionedInfo.type != null) { 87 | mentionedMap["type"] = mentionedInfo.type; 88 | } 89 | if (mentionedInfo.type == RCMentionedType.Users && mentionedInfo.userIdList != null) { 90 | mentionedMap["userIdList"] = mentionedInfo.userIdList; 91 | } 92 | if (mentionedInfo.mentionedContent != null) { 93 | mentionedMap["mentionedContent"] = mentionedInfo.mentionedContent; 94 | } 95 | } 96 | return mentionedMap; 97 | } 98 | } 99 | 100 | class MessageCoding { 101 | String? encode() { 102 | return null; 103 | } 104 | 105 | void decode(String jsonStr) {} 106 | 107 | String? getObjectName() { 108 | return null; 109 | } 110 | } 111 | 112 | class MessageContentView { 113 | String? conversationDigest() { 114 | return null; 115 | } 116 | } 117 | 118 | class UserInfo { 119 | String? userId; 120 | String? name; 121 | String? portraitUri; 122 | String? extra; 123 | } 124 | 125 | // 消息中的 @ 提醒信息 126 | class MentionedInfo { 127 | // @ 提醒的类型,参见枚举 [RCMentionedType] 128 | int? /*RCMentionedType*/ type; 129 | 130 | // @ 的用户 ID 列表,如果 type 是 @ 所有人,则可以传 nil 131 | List? userIdList; 132 | 133 | // 包含 @ 提醒的消息,建议用做本地通知和远程推送显示的内容 134 | String? mentionedContent; 135 | } 136 | -------------------------------------------------------------------------------- /lib/src/message/recall_notification_message.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' show json; 2 | import 'dart:developer' as developer; 3 | 4 | import 'message_content.dart'; 5 | 6 | class RecallNotificationMessage extends MessageContent { 7 | static const String objectName = "RC:RcNtf"; 8 | 9 | String? mOperatorId; //发起撤回消息的用户id 10 | int? mRecallTime; //撤回的时间(毫秒) 11 | String? mOriginalObjectName; //原消息的消息类型名 12 | bool? mAdmin; //是否是管理员操作 13 | bool? mDelete; //是否删除 14 | String? recallContent; //撤回的文本消息的内容 15 | int? recallActionTime; //撤回消息的发送时间 16 | 17 | @override 18 | void decode(String? jsonStr) { 19 | if (jsonStr == null || jsonStr.isEmpty) { 20 | developer.log("Flutter RecallNotificationMessage deocde error: no content", name: "RongIMClient.RecallNotificationMessage"); 21 | return; 22 | } 23 | Map map = json.decode(jsonStr.toString()); 24 | this.mOperatorId = map["operatorId"]; 25 | this.mRecallTime = map["recallTime"]; 26 | this.mOriginalObjectName = map["originalObjectName"]; 27 | this.mAdmin = map["admin"]; 28 | this.mDelete = map["delete"]; 29 | this.recallContent = map["recallContent"]; 30 | this.recallActionTime = map["recallActionTime"] != null ? map["recallActionTime"] : 0; 31 | Map? userMap = map["user"]; 32 | super.decodeUserInfo(userMap); 33 | Map? menthionedMap = map["mentionedInfo"]; 34 | super.decodeMentionedInfo(menthionedMap); 35 | } 36 | 37 | @override 38 | String encode() { 39 | Map map = { 40 | "mOperatorId": this.mOperatorId, 41 | "mRecallTime": this.mRecallTime, 42 | "mOriginalObjectName": this.mOriginalObjectName, 43 | "mAdmin": this.mAdmin, 44 | "mDelete": this.mDelete, 45 | "recallContent": this.recallContent, 46 | }; 47 | 48 | if (this.recallActionTime != null) { 49 | map["recallActionTime"] = this.recallActionTime; 50 | } 51 | if (this.sendUserInfo != null) { 52 | Map userMap = super.encodeUserInfo(this.sendUserInfo); 53 | map["user"] = userMap; 54 | } else { 55 | map["user"] = []; 56 | } 57 | if (this.mentionedInfo != null) { 58 | Map mentionedMap = super.encodeMentionedInfo(this.mentionedInfo); 59 | map["mentionedInfo"] = mentionedMap; 60 | } 61 | return json.encode(map); 62 | } 63 | 64 | @override 65 | String conversationDigest() { 66 | return "撤回了一条消息"; 67 | } 68 | 69 | @override 70 | String getObjectName() { 71 | return objectName; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/src/message/reference_message.dart: -------------------------------------------------------------------------------- 1 | import 'package:rongcloud_im_plugin/rongcloud_im_plugin.dart'; 2 | 3 | import 'message_content.dart'; 4 | import 'dart:convert' show json; 5 | import '../util/message_factory.dart'; 6 | import 'dart:developer' as developer; 7 | 8 | //Gif消息 9 | class ReferenceMessage extends MessageContent { 10 | static const String objectName = "RC:ReferenceMsg"; 11 | 12 | String? content; //引用文本 13 | String? referMsgUserId; //被引用消息的发送者 ID 14 | MessageContent? referMsg; //被引用消息体 15 | String? extra; //引用消息的附加信息 16 | 17 | @override 18 | void decode(String? jsonStr) { 19 | if (jsonStr == null || jsonStr == "") { 20 | developer.log("Flutter ReferenceMessage deocde error: no content", name: "RongIMClient.ReferenceMessage"); 21 | return; 22 | } 23 | Map? map = json.decode(jsonStr); 24 | if (map == null) { 25 | developer.log("Flutter ReferenceMessage deocde error: no right content", name: "RongIMClient.ReferenceMessage"); 26 | return; 27 | } 28 | this.content = map["content"]; 29 | this.referMsgUserId = map["referMsgUserId"]; 30 | Map? messageMap = map["referMsg"]; 31 | String messageStr = json.encode(messageMap); 32 | String? objectName = map["objName"]; 33 | this.referMsg = MessageFactory.instance!.string2MessageContent(messageStr, objectName); 34 | this.extra = map["extra"]; 35 | Map? userMap = map["user"]; 36 | super.decodeUserInfo(userMap); 37 | Map? menthionedMap = map["mentionedInfo"]; 38 | super.decodeMentionedInfo(menthionedMap); 39 | this.destructDuration = map["burnDuration"]; 40 | } 41 | 42 | @override 43 | String encode() { 44 | Map map = Map(); 45 | if (this.content != null) { 46 | map["content"] = this.content; 47 | } 48 | if (this.referMsgUserId != null) { 49 | map["referMsgUserId"] = this.referMsgUserId; 50 | } 51 | if (messageInfoWithContent() != null) { 52 | Map? messageMap = messageInfoWithContent(); 53 | map["referMsg"] = messageMap; 54 | } 55 | if (this.referMsg?.getObjectName() != null) { 56 | map["objName"] = this.referMsg!.getObjectName(); 57 | } 58 | if (this.extra != null) { 59 | map["extra"] = this.extra; 60 | } 61 | if (this.sendUserInfo != null) { 62 | Map userMap = super.encodeUserInfo(this.sendUserInfo); 63 | map["user"] = userMap; 64 | } 65 | if (this.mentionedInfo != null) { 66 | Map mentionedMap = super.encodeMentionedInfo(this.mentionedInfo); 67 | map["mentionedInfo"] = mentionedMap; 68 | } 69 | if (this.destructDuration != null && this.destructDuration! > 0) { 70 | map["burnDuration"] = this.destructDuration; 71 | } 72 | return json.encode(map); 73 | } 74 | 75 | @override 76 | String? conversationDigest() { 77 | return this.content; 78 | } 79 | 80 | @override 81 | String getObjectName() { 82 | return objectName; 83 | } 84 | 85 | Map? messageInfoWithContent() { 86 | MessageContent? content = this.referMsg; 87 | Map? map = Map(); 88 | if (content != null) { 89 | switch (content.getObjectName()) { 90 | case "RC:TxtMsg": 91 | TextMessage textMsg = content as TextMessage; 92 | map = json.decode(textMsg.encode()); 93 | break; 94 | case "RC:ImgMsg": 95 | ImageMessage imageMsg = content as ImageMessage; 96 | map = json.decode(imageMsg.encode()); 97 | break; 98 | case "RC:FileMsg": 99 | FileMessage fileMsg = content as FileMessage; 100 | map = json.decode(fileMsg.encode()); 101 | break; 102 | case "RC:ImgTextMsg": 103 | RichContentMessage richContentMsg = content as RichContentMessage; 104 | map = json.decode(richContentMsg.encode()); 105 | break; 106 | default: 107 | return null; 108 | } 109 | return map; 110 | } else { 111 | return null; 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /lib/src/message/rich_content_message.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' show json; 2 | import 'dart:developer' as developer; 3 | 4 | import 'message_content.dart'; 5 | 6 | /// 图文消息 7 | class RichContentMessage extends MessageContent { 8 | static const String objectName = "RC:ImgTextMsg"; 9 | 10 | String? title; 11 | String? digest; 12 | String? imageURL; 13 | String? url; 14 | String? extra; 15 | 16 | /// [content] 文本内容 17 | static RichContentMessage obtain(String title, String digest, String imageURL, {String url = '', String extra = ''}) { 18 | RichContentMessage msg = new RichContentMessage(); 19 | msg.title = title; 20 | msg.digest = digest; 21 | msg.imageURL = imageURL; 22 | msg.url = url; 23 | msg.extra = extra; 24 | return msg; 25 | } 26 | 27 | @override 28 | void decode(String? jsonStr) { 29 | if (jsonStr == null) { 30 | developer.log("Flutter TextMessage deocde error: no content", name: "RongIMClient.RichContentMessage"); 31 | return; 32 | } 33 | Map map = json.decode(jsonStr.toString()); 34 | this.imageURL = map["imageUri"]; 35 | this.extra = map["extra"]; 36 | this.digest = map["content"]; 37 | this.title = map["title"]; 38 | this.url = map["url"]; 39 | Map? userMap = map["user"]; 40 | super.decodeUserInfo(userMap); 41 | Map? menthionedMap = map["mentionedInfo"]; 42 | super.decodeMentionedInfo(menthionedMap); 43 | // this.destructDuration = map["burnDuration"]; 44 | } 45 | 46 | @override 47 | String encode() { 48 | Map map = {"imageUri": this.imageURL, "extra": this.extra, "content": this.digest, "title": this.title, "url": this.url}; 49 | if (this.sendUserInfo != null) { 50 | Map userMap = super.encodeUserInfo(this.sendUserInfo); 51 | map["user"] = userMap; 52 | } 53 | if (this.mentionedInfo != null) { 54 | Map mentionedMap = super.encodeMentionedInfo(this.mentionedInfo); 55 | map["mentionedInfo"] = mentionedMap; 56 | } 57 | // if (this.destructDuration != null && this.destructDuration > 0) { 58 | // map["burnDuration"] = this.destructDuration; 59 | // } 60 | return json.encode(map); 61 | } 62 | 63 | @override 64 | String conversationDigest() { 65 | return "图文"; 66 | } 67 | 68 | @override 69 | String getObjectName() { 70 | return objectName; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/src/message/sight_message.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' show json; 2 | import 'dart:developer' as developer; 3 | 4 | import 'message_content.dart'; 5 | 6 | //小视频消息 7 | //小视频消息使用必须在融云开发者后台进行开通 8 | class SightMessage extends MessageContent { 9 | static const String objectName = "RC:SightMsg"; 10 | 11 | String? localPath; //本地路径 12 | String? remoteUrl; //远端路径 13 | String? content; //缩略图内容 14 | int? duration; //时长 15 | String? extra; //额外数据 16 | int size = 0; 17 | String? mThumbUri; //缩略图地址 18 | String? mName = ""; 19 | int? mSize; 20 | 21 | /// [localPath] 本地路径,Android 必须以 file:// 开头 22 | /// 23 | /// [duration] 视频时长,单位 秒 24 | static SightMessage obtain(String localPath, int duration) { 25 | SightMessage msg = new SightMessage(); 26 | msg.localPath = localPath; 27 | msg.duration = duration; 28 | return msg; 29 | } 30 | 31 | @override 32 | void decode(String? jsonStr) { 33 | if (jsonStr == null || jsonStr == "") { 34 | developer.log("Flutter SightMessage deocde error: no content", name: "RongIMClient.SightMessage"); 35 | return; 36 | } 37 | Map map = json.decode(jsonStr.toString()); 38 | this.mName = map["name"]; 39 | var size = map["size"] != null ? map["size"] : 0; 40 | if (size is String) { 41 | this.mSize = int.parse(size); 42 | } else { 43 | this.mSize = size; 44 | } 45 | this.localPath = map["localPath"]; 46 | this.remoteUrl = map["sightUrl"]; 47 | this.content = map["content"]; 48 | this.mThumbUri = map["thumbUri"]; 49 | var d = map["duration"]; 50 | if (d is String) { 51 | this.duration = int.parse(d); 52 | } else { 53 | this.duration = d; 54 | } 55 | this.extra = map["extra"]; 56 | Map? userMap = map["user"]; 57 | super.decodeUserInfo(userMap); 58 | Map? menthionedMap = map["mentionedInfo"]; 59 | super.decodeMentionedInfo(menthionedMap); 60 | this.destructDuration = map["burnDuration"]; 61 | } 62 | 63 | @override 64 | String encode() { 65 | Map map = {"duration": this.duration, "extra": this.extra}; 66 | if (this.size > 0) { 67 | map["size"] = this.size; 68 | } 69 | if (this.content != null) { 70 | map["content"] = this.content; 71 | } 72 | if (this.localPath != null) { 73 | map["localPath"] = this.localPath; 74 | } else { 75 | map["localPath"] = ""; 76 | } 77 | if (this.remoteUrl != null) { 78 | map["sightUrl"] = this.remoteUrl; 79 | } 80 | if (this.mThumbUri != null) { 81 | map["thumbUri"] = this.mThumbUri; 82 | } 83 | if (this.sendUserInfo != null) { 84 | Map userMap = super.encodeUserInfo(this.sendUserInfo); 85 | map["user"] = userMap; 86 | } 87 | if (this.mentionedInfo != null) { 88 | Map mentionedMap = super.encodeMentionedInfo(this.mentionedInfo); 89 | map["mentionedInfo"] = mentionedMap; 90 | } 91 | if (this.destructDuration != null && this.destructDuration! > 0) { 92 | map["burnDuration"] = this.destructDuration; 93 | } 94 | return json.encode(map); 95 | } 96 | 97 | @override 98 | String conversationDigest() { 99 | return "小视频"; 100 | } 101 | 102 | @override 103 | String getObjectName() { 104 | return objectName; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /lib/src/message/text_message.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' show json; 2 | import 'dart:developer' as developer; 3 | 4 | import 'message_content.dart'; 5 | 6 | class TextMessage extends MessageContent { 7 | static const String objectName = "RC:TxtMsg"; 8 | 9 | String? content; 10 | String? extra; 11 | 12 | /// [content] 文本内容 13 | static TextMessage obtain(String content) { 14 | TextMessage msg = new TextMessage(); 15 | msg.content = content; 16 | return msg; 17 | } 18 | 19 | @override 20 | void decode(String? jsonStr) { 21 | if (jsonStr == null) { 22 | developer.log("Flutter TextMessage deocde error: no content", name: "RongIMClient.TextMessage"); 23 | return; 24 | } 25 | Map map = json.decode(jsonStr.toString()); 26 | this.content = map["content"]; 27 | this.extra = map["extra"]; 28 | Map? userMap = map["user"]; 29 | super.decodeUserInfo(userMap); 30 | Map? menthionedMap = map["mentionedInfo"]; 31 | super.decodeMentionedInfo(menthionedMap); 32 | this.destructDuration = map["burnDuration"]; 33 | } 34 | 35 | @override 36 | String encode() { 37 | Map map = {"content": this.content, "extra": this.extra}; 38 | if (this.sendUserInfo != null) { 39 | Map userMap = super.encodeUserInfo(this.sendUserInfo); 40 | map["user"] = userMap; 41 | } 42 | if (this.mentionedInfo != null) { 43 | Map mentionedMap = super.encodeMentionedInfo(this.mentionedInfo); 44 | map["mentionedInfo"] = mentionedMap; 45 | } 46 | if (this.destructDuration != null && this.destructDuration! > 0) { 47 | map["burnDuration"] = this.destructDuration; 48 | } 49 | return json.encode(map); 50 | } 51 | 52 | @override 53 | String? conversationDigest() { 54 | return content; 55 | } 56 | 57 | @override 58 | String getObjectName() { 59 | return objectName; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/src/message/voice_message.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' show json; 2 | import 'dart:developer' as developer; 3 | 4 | import 'message_content.dart'; 5 | 6 | class VoiceMessage extends MessageContent { 7 | static const String objectName = "RC:HQVCMsg"; 8 | 9 | String? localPath; 10 | String? remoteUrl; 11 | int? duration; 12 | String? extra; 13 | 14 | /// [localPath] 本地路径,Android 必须以 file:// 开头 15 | /// 16 | /// [duration] 语音时长,单位 秒 17 | static VoiceMessage obtain(String localPath, int duration) { 18 | VoiceMessage msg = new VoiceMessage(); 19 | msg.localPath = localPath; 20 | msg.duration = duration; 21 | return msg; 22 | } 23 | 24 | @override 25 | void decode(String? jsonStr) { 26 | if (jsonStr == null) { 27 | developer.log("Flutter VoiceMessage deocde error: no content", name: "RongIMClient.VoiceMessage"); 28 | return; 29 | } 30 | Map map = json.decode(jsonStr.toString()); 31 | this.localPath = map["localPath"]; 32 | this.remoteUrl = map["remoteUrl"]; 33 | this.duration = map["duration"]; 34 | this.extra = map["extra"]; 35 | Map? userMap = map["user"]; 36 | super.decodeUserInfo(userMap); 37 | Map? menthionedMap = map["mentionedInfo"]; 38 | super.decodeMentionedInfo(menthionedMap); 39 | this.destructDuration = map["burnDuration"]; 40 | } 41 | 42 | @override 43 | String encode() { 44 | Map map = {"duration": this.duration, "extra": this.extra}; 45 | if (this.localPath != null) { 46 | map["localPath"] = this.localPath; 47 | } else { 48 | map["localPath"] = ""; 49 | } 50 | if (this.remoteUrl != null) { 51 | map["remoteUrl"] = this.remoteUrl; 52 | } 53 | if (this.sendUserInfo != null) { 54 | Map userMap = super.encodeUserInfo(this.sendUserInfo); 55 | map["user"] = userMap; 56 | } 57 | if (this.mentionedInfo != null) { 58 | Map mentionedMap = super.encodeMentionedInfo(this.mentionedInfo); 59 | map["mentionedInfo"] = mentionedMap; 60 | } 61 | if (this.destructDuration != null && this.destructDuration! > 0) { 62 | map["burnDuration"] = this.destructDuration; 63 | } 64 | return json.encode(map); 65 | } 66 | 67 | @override 68 | String conversationDigest() { 69 | return "语音"; 70 | } 71 | 72 | @override 73 | String getObjectName() { 74 | return objectName; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/src/util/type_util.dart: -------------------------------------------------------------------------------- 1 | class TypeUtil { 2 | /// 检测是否是空字符串 3 | static bool isEmptyString(String? s) { 4 | if (s == null || s.length <= 0) { 5 | return true; 6 | } 7 | return false; 8 | } 9 | 10 | /// 返回非法的 int 值,将负数转为正数 11 | static int getProperInt(int value) { 12 | if (value <= 0) { 13 | value = 0; 14 | } 15 | return value; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /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.flutter-io.cn" 9 | source: hosted 10 | version: "2.8.2" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.2.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.3.1" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.15.0" 46 | fake_async: 47 | dependency: transitive 48 | description: 49 | name: fake_async 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "1.2.0" 53 | flutter: 54 | dependency: "direct main" 55 | description: flutter 56 | source: sdk 57 | version: "0.0.0" 58 | flutter_test: 59 | dependency: "direct dev" 60 | description: flutter 61 | source: sdk 62 | version: "0.0.0" 63 | matcher: 64 | dependency: transitive 65 | description: 66 | name: matcher 67 | url: "https://pub.flutter-io.cn" 68 | source: hosted 69 | version: "0.12.11" 70 | meta: 71 | dependency: transitive 72 | description: 73 | name: meta 74 | url: "https://pub.flutter-io.cn" 75 | source: hosted 76 | version: "1.7.0" 77 | path: 78 | dependency: transitive 79 | description: 80 | name: path 81 | url: "https://pub.flutter-io.cn" 82 | source: hosted 83 | version: "1.8.0" 84 | sky_engine: 85 | dependency: transitive 86 | description: flutter 87 | source: sdk 88 | version: "0.0.99" 89 | source_span: 90 | dependency: transitive 91 | description: 92 | name: source_span 93 | url: "https://pub.flutter-io.cn" 94 | source: hosted 95 | version: "1.8.1" 96 | stack_trace: 97 | dependency: transitive 98 | description: 99 | name: stack_trace 100 | url: "https://pub.flutter-io.cn" 101 | source: hosted 102 | version: "1.10.0" 103 | stream_channel: 104 | dependency: transitive 105 | description: 106 | name: stream_channel 107 | url: "https://pub.flutter-io.cn" 108 | source: hosted 109 | version: "2.1.0" 110 | string_scanner: 111 | dependency: transitive 112 | description: 113 | name: string_scanner 114 | url: "https://pub.flutter-io.cn" 115 | source: hosted 116 | version: "1.1.0" 117 | term_glyph: 118 | dependency: transitive 119 | description: 120 | name: term_glyph 121 | url: "https://pub.flutter-io.cn" 122 | source: hosted 123 | version: "1.2.0" 124 | test_api: 125 | dependency: transitive 126 | description: 127 | name: test_api 128 | url: "https://pub.flutter-io.cn" 129 | source: hosted 130 | version: "0.4.3" 131 | typed_data: 132 | dependency: transitive 133 | description: 134 | name: typed_data 135 | url: "https://pub.flutter-io.cn" 136 | source: hosted 137 | version: "1.3.0" 138 | vector_math: 139 | dependency: transitive 140 | description: 141 | name: vector_math 142 | url: "https://pub.flutter-io.cn" 143 | source: hosted 144 | version: "2.1.1" 145 | sdks: 146 | dart: ">=2.14.0 <3.0.0" 147 | flutter: ">=2.0.0" 148 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: rongcloud_im_plugin 2 | description: RongCloud IM Flutter Plugin. 3 | version: 5.1.8+7 4 | homepage: https://www.rongcloud.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 | 14 | dev_dependencies: 15 | flutter_test: 16 | sdk: flutter 17 | 18 | # For information on the generic Dart part of this file, see the 19 | # following page: https://dart.dev/tools/pub/pubspec 20 | 21 | # The following section is specific to Flutter. 22 | flutter: 23 | # This section identifies this Flutter project as a plugin project. 24 | # The androidPackage and pluginClass identifiers should not ordinarily 25 | # be modified. They are used by the tooling to maintain consistency when 26 | # adding or updating assets for this project. 27 | plugin: 28 | platforms: 29 | android: 30 | package: io.rong.flutter.imlib 31 | pluginClass: RongcloudImPlugin 32 | ios: 33 | pluginClass: RongcloudImPlugin 34 | 35 | # To add assets to your plugin package, add an assets section, like this: 36 | # assets: 37 | # - images/a_dot_burr.jpeg 38 | # - images/a_dot_ham.jpeg 39 | # 40 | # For details regarding assets in packages, see 41 | # https://flutter.dev/assets-and-images/#from-packages 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 | # To add custom fonts to your plugin package, add a fonts section here, 47 | # in this "flutter" section. Each entry in this list should have a 48 | # "family" key with the font family name, and a "fonts" key with a 49 | # list giving the asset and other descriptors for the font. For 50 | # example: 51 | # fonts: 52 | # - family: Schyler 53 | # fonts: 54 | # - asset: fonts/Schyler-Regular.ttf 55 | # - asset: fonts/Schyler-Italic.ttf 56 | # style: italic 57 | # - family: Trajan Pro 58 | # fonts: 59 | # - asset: fonts/TrajanPro.ttf 60 | # - asset: fonts/TrajanPro_Bold.ttf 61 | # weight: 700 62 | # 63 | # For details regarding fonts in packages, see 64 | # https://flutter.dev/custom-fonts/#from-packages 65 | -------------------------------------------------------------------------------- /rongcloud_im_plugin.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/rongcloud_im_plugin_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | void main() { 5 | const MethodChannel channel = MethodChannel('rongcloud_im_plugin'); 6 | 7 | setUp(() { 8 | channel.setMockMethodCallHandler((MethodCall methodCall) async { 9 | return '42'; 10 | }); 11 | }); 12 | 13 | tearDown(() { 14 | channel.setMockMethodCallHandler(null); 15 | }); 16 | 17 | 18 | } 19 | --------------------------------------------------------------------------------