├── .gitignore ├── README.md └── lc_realtime ├── .gitignore ├── .gradle ├── 5.2.1 │ ├── fileChanges │ │ └── last-build.bin │ ├── fileHashes │ │ └── fileHashes.lock │ └── gc.properties ├── buildOutputCleanup │ ├── buildOutputCleanup.lock │ └── cache.properties └── vcs-1 │ └── gc.properties ├── .metadata ├── README.md ├── android ├── .gitignore ├── app │ ├── agconnect-services.json │ ├── build.gradle │ ├── libs │ │ ├── MiPush_SDK_Client_3_7_5.jar │ │ ├── oppo-mcssdk-2.0.2.jar │ │ └── vivo_pushsdk-v2.9.0.0.aar │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── lcrealtime │ │ │ │ ├── MainActivity.java │ │ │ │ └── MyApplication.java │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── test.keystore ├── assets └── download_fail.png ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ ├── Runner-Bridging-Header.h │ ├── RunnerDebug.entitlements │ └── RunnerRelease.entitlements ├── lib ├── Common │ └── Global.dart ├── Models │ └── CurrentClient.dart ├── Routes │ ├── ContactsPage.dart │ ├── ConversationDetailPage.dart │ ├── ConversationListPage.dart │ ├── HomeBottomBar.dart │ ├── LoginPage.dart │ ├── SelectChatMembers.dart │ └── UserProtocol.dart ├── States │ ├── ChangeNotifier.dart │ ├── ChangeNotifierProvider.dart │ ├── Consumer.dart │ ├── ConversationModel.dart │ ├── GlobalEvent.dart │ └── InheritedWidget.dart ├── Widgets │ ├── InputMessageView.dart │ ├── MessageList.dart │ └── TextWidget.dart └── main.dart ├── pubspec.lock ├── pubspec.yaml └── test └── widget_test.dart /.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 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Flutter 新手上路,UI 还有一些问题正在解决中,欢迎小伙伴批评指正 :) 2 | * [apk 体验链接](https://flutter-rtm-files.lncld.net/fWtxzeC8GzvFGTB3r0G7gBl1rvnbjyI9/app-release.apk) 3 | ## 应用简介 4 | 5 | 本应用是一款社交聊天的应用,实现了基本的聊天需求。实现这款应用一方面是为了了解与学习 Flutter,另一方面趁此机会熟悉 LeanCloud 即时通信 Flutter SDK 的使用。 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 | 第一步:Flutter 安装和环境搭建直接查看:[ Flutter 文档](https://flutter.dev/docs/get-started/install)。 32 | 第二步:登录 [LeanCloud 控制台](https://leancloud.cn/dashboard/login.html#/signin),创建 LeanCloud 应用。 33 | 34 | - 在控制台 > 应用 > 设置 >域名绑定页面绑定 **API 访问域名**。暂时没有域名可以略过这一步,LeanCloud 也提供了短期有效的免费体验域名;或者注册[ LeanCloud 国际版](https://console.leancloud.app/login.html#/signin),国际版不要求绑定域名。 35 | - 在控制台 > 应用 > 设置 > 应用 Keys 页面记录 AppID、AppKey 与服务器地址备用,这里的服务器地址就是 REST API 服务器地址。如果未绑定域名,控制台相同的位置可以获取到免费的共享域名。 36 | 37 | ## APP 初始化设置 38 | 39 | 在 pubspec.yaml 中,将 LeanCloud Flutter SDK 添加到依赖项列表: 40 | 41 | ``` 42 | dependencies: 43 | leancloud_official_plugin: ^1.0.0 //即时通信插件 44 | leancloud_storage: ^0.7.7 //数据存储 SDK 45 | ``` 46 | 47 | 然后运行 flutter pub get 安装 SDK。 48 | 49 | 因为 leancloud_official_plugin 是基于 [Swift SDK](https://github.com/leancloud/swift-sdk) 以及 [Java Unified SDK](https://github.com/leancloud/java-unified-sdk) 开发,所以还要安装后面两个 SDK,这样应用才能分别在 iOS 和 Android 设备运行。 50 | 51 | 需要通过 CocoaPods 安装 Swift SDK,这一步和安装 iOS 其他第三方库是一样的,在应用的 ios 目录下执行 pod update 即可。 52 | 53 | ``` 54 | $ cd ios/ 55 | $ pod update # 或者 $ pod install --repo-update 56 | ``` 57 | 58 | 同样的,需要配置 Gradle 来安装 Java Unified SDK,打开工程目录 android/app/build.gradle,添加如下依赖,用 Android Studio 打开工程下的 android 目录,同步更新 Gradle 即可。 59 | 60 | ``` 61 | dependencies { 62 | implementation 'cn.leancloud:storage-android:8.1.4' 63 | implementation 'cn.leancloud:realtime-android:8.1.4' 64 | implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' 65 | } 66 | ``` 67 | 68 | > 小 tips: 安装 SDK 期间遇到任何困难都可在 [LeanCloud 社区](https://forum.leancloud.cn/latest) 发帖求助。 69 | 70 | SDK 安装成功以后,需要分别 [初始化 iOS 和 Android 平台](https://leancloud.cn/docs/sdk_setup-flutter.html#hash662673194)。 71 | 72 | ## 用户系统 73 | 74 | Demo 里面并没有内置用户系统,可以选择联系人列表中的用户名来登录聊天系统。LeanCloud 即时通信服务端中只需要传入一个唯一标识字符串既可以表示一个「用户」,对应唯一的 Client, 在应用内唯一标识自己的 ID(clientId)。 75 | 76 | 在自己的项目中,如果已经有独立的用户系统也很方便维护。 77 | 或者使用 LeanStorage 提供的[用户系统](https://leancloud.cn/docs/leanstorage_guide-java.html#hash954895)。 78 | 79 | ## 会话列表 80 | 81 | 会话列表要展示当前用户所参与的会话,会话名称、会话的成员,会话的最后一条消息。还需要展示未读消息数目。 82 | 83 | 会话列表对应 Conversation 表,查询当前用户的全部会话只需要下面两行代码: 84 | 85 | ``` 86 | ConversationQuery query = client.conversationQuery(); 87 | await query.find(); 88 | ``` 89 | 90 | 按照会话的更新时间排序: 91 | 92 | ``` 93 | query.orderByDescending('updatedAt'); 94 | ``` 95 | 96 | 为了展示会话的最新一条消息,查询的时候要额外加上这行代码: 97 | 98 | ``` 99 | //让查询结果附带一条最新消息 100 | query.includeLastMessage = true; 101 | ``` 102 | 103 | 这样会话页面的后端数据就搞定了。下面看一下如何显示数据。 104 | 105 | 会话查询成功返回的数据格式是 Conversation 类型的 List。 106 | 107 | - conversation.name 即会话的名称 108 | - conversation.members 即会话成员 109 | - conversation.lastMessage 就是当前会话的最新一条消息了。 110 | 111 | ### 未读消息数的处理 112 | 113 | 如果要在 Android 设备上运行,需要在初始化 Java SDK 的时候加上下面这行代码,表示开启未读消息数通知: 114 | 115 | ``` 116 | AVIMOptions.getGlobalOptions().setUnreadNotificationEnabled(true); 117 | ``` 118 | 119 | swift SDK 是默认支持,无需额外设置。 120 | 121 | 可以监听 onUnreadMessageCountUpdated 时间获取未读消息数通知: 122 | 123 | ``` 124 | client.onUnreadMessageCountUpdated = ({ 125 | Client client, 126 | Conversation conversation, 127 | }) { 128 | // conversation.unreadMessageCount 即该 conversation 的未读消息数量 129 | }; 130 | ``` 131 | 132 | 注意要在以下两处清除未读消息数: 133 | 134 | - 在对话列表点击某对话进入到对话页面时 135 | - 用户正在某个对话页面聊天,并在这个对话中收到了消息时 136 | 137 | ## 会话详情页面 138 | 139 | ### 上拉加载更多历史消息 140 | 141 | 查询聊天记录的时候,先查最近的 10 条消息,然后以第一页的最早的消息作为开始,继续向前拉取消息: 142 | 143 | ``` 144 | List messages; 145 | try { 146 | //第一次查询成功 147 | messages = await conversation.queryMessage( 148 | limit: 10, 149 | ); 150 | } catch (e) { 151 | print(e); 152 | } 153 | 154 | try { 155 | // 返回的消息一定是时间增序排列,也就是最早的消息一定是第一个 156 | Message oldMessage = messages.first; 157 | // 以第一页的最早的消息作为开始,继续向前拉取消息 158 | List messages2 = await conversation.queryMessage( 159 | startTimestamp: oldMessage.sentTimestamp, 160 | startMessageID: oldMessage.id, 161 | startClosed: true, 162 | limit: 10, 163 | ); 164 | } catch (e) { 165 | print(e); 166 | } 167 | ``` 168 | 169 | ### 修改会话名 170 | 171 | 对话的名称是会话表 Conversation 默认的属性,更新会话名称只需要执行: 172 | 173 | ``` 174 | await conversation.updateInfo(attributes: { 175 | 'name': 'New Name', 176 | }); 177 | ``` 178 | 179 | ### 图片、语音消息 180 | 181 | LeanCloud 即时通信 SDK 提供了下面几种默认的消息类型,Demo 中只用到了文本消息,图像消息和语音消息。 182 | 183 | - TextMessage 文本消息 184 | - ImageMessage 图像消息 185 | - AudioMessage 音频消息 186 | - VideoMessage 视频消息 187 | - FileMessage 普通文件消息(.txt/.doc/.md 等各种) 188 | - LocationMessage 地理位置消息 189 | 190 | **注意,图片与语音消息需要先保存成 LCFile,然后再调用发消息接口发消息。** 191 | 192 | 比如发送音频消息。第一步先保存音频文件为 LCFile: 193 | 194 | ``` 195 | LCFile file = await LCFile.fromPath('message.wav', path); 196 | await file.save(); 197 | ``` 198 | 199 | 第二步,再发消息: 200 | 201 | ``` 202 | //发送消息 203 | AudioMessage audioMessage = AudioMessage.from( 204 | binaryData: file.data, 205 | format: 'wav', 206 | ); 207 | await this.widget.conversation.send(message: audioMessage); 208 | ``` 209 | 210 | 还要注意 iOS 设备发送图片消息注意打开相册和相机权限,语音消息需要麦克风权限: 211 | 212 | ``` 213 | NSMicrophoneUsageDescription 214 | 录音功能需要访问麦克风,如果不允许,你将无法在聊天过程中发送语音消息。 215 | 216 | NSCameraUsageDescription 217 | 发送图片功能需要访问您的相机。如果不允许,你将无法在聊天过程中发送图片消息。 218 | 219 | NSPhotoLibraryUsageDescription 220 | 发送图片功能需要访问您的相册。如果不允许,你将无法在聊天过程中发送相册中的图片。 221 | ``` 222 | 223 | ## 离线推送通知 224 | 225 | 当用户下线以后,收到消息的时候,往往希望能有推送提醒。最简单的一种推送设置就是在 LeanCloud **控制台 > 消息 > 即时通讯 > 设置 > 离线推送设置** 页面,填入: 226 | 227 | ``` 228 | { "alert": "您有新的消息", "badge": "Increment" } 229 | ``` 230 | 231 | 这样 iOS 设备有离线消息的时候会收到提醒。这里 badge 参数为 iOS 设备专用,用于增加应用 badge 上的数字计数。 232 | 233 | > 如果想在 Android 设备上实现离线推送,要增加一步接入[ Android 混合推送](https://leancloud.cn/docs/android_mixpush_guide.html)。 234 | 235 | 当然在实际项目中,离线消息的提醒往往会要求更加具体,比如推送中要包括消息的内容或者消息类型等。LeanCloud 也提供了其他几种定制离线推送的方法,感兴趣可以自行查阅[文档](https://leancloud.cn/docs/realtime-guide-intermediate.html#hash-485620600)。 236 | 237 | 还要注意,iOS 推送一定要正确配置 [配置 APNs 推送证书](https://leancloud.cn/docs/ios_push_cert.html),并打开 Xcode 的推送开关: 238 | 239 | 240 | 241 | 242 | AppDelegate.swift 中开启推送,要这样设置: 243 | 244 | ``` 245 | import Flutter 246 | import LeanCloud 247 | import UserNotifications 248 | 249 | @UIApplicationMain 250 | @objc class AppDelegate: FlutterAppDelegate { 251 | override func application( 252 | _ application: UIApplication, 253 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 254 | ) -> Bool { 255 | do { 256 | LCApplication.logLevel = .all 257 | try LCApplication.default.set( 258 | id: "你的APPID", 259 | key: "你的 APPKey", 260 | serverURL: "服务器地址") 261 | GeneratedPluginRegistrant.register(with: self) 262 | /* 263 | register APNs to access token, like this: 264 | */ 265 | UNUserNotificationCenter.current().getNotificationSettings { (settings) in 266 | switch settings.authorizationStatus { 267 | case .authorized: 268 | DispatchQueue.main.async { 269 | UIApplication.shared.registerForRemoteNotifications() 270 | } 271 | case .notDetermined: 272 | UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound]) { (granted, error) in 273 | if granted { 274 | DispatchQueue.main.async { 275 | UIApplication.shared.registerForRemoteNotifications() 276 | } 277 | } 278 | } 279 | default: 280 | break 281 | } 282 | _ = LCApplication.default.currentInstallation 283 | } 284 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 285 | } catch { 286 | fatalError("\(error)") 287 | } 288 | } 289 | 290 | override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { 291 | /* 292 | set APNs deviceToken and Team ID. 293 | */ 294 | LCApplication.default.currentInstallation.set( 295 | deviceToken: deviceToken, 296 | apnsTeamId: "你的 TeamId") 297 | /* 298 | save to LeanCloud. 299 | */ 300 | LCApplication.default.currentInstallation.save { (result) in 301 | switch result { 302 | case .success: 303 | break 304 | case .failure(error: let error): 305 | print(error) 306 | } 307 | } 308 | } 309 | override func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { 310 | //如果注册推送失败,可以检查 error 信息 311 | print(error) 312 | } 313 | } 314 | ``` 315 | 316 | ## 投诉举报、黑名单 317 | 318 | 在 APP Store 审核过程中,因为用户可以随意发送消息,被要求对用户生成的内容要有适当的预防措施。 319 | 320 | 要求提供下面这两条内容: 321 | 322 | - A mechanism for users to flag objectionable content 323 | 324 | 用户标记不良信息的机制,就是举报功能。 325 | 326 | - A mechanism for users to block abusive users 327 | 328 | 用户阻止滥用用户的机制,实际就是黑名单。 329 | 330 | ### 实现举报功能 331 | 332 | 我的解决办法是在消息处长按弹出举报窗口。 333 | 334 | 335 | 336 | 使用 LeanCloud 存储服务,新建一张 Report 表用于记录举报信息: 337 | 338 | ``` 339 | //保存一条举报信息 340 | LCObject report = LCObject('Report'); 341 | report['clientID'] = Global.clientID; //举报人 342 | report['messageID'] = messageID; //消息 ID 343 | report['conversationID'] = this.widget.conversation.id; //会话 ID 344 | report['content'] = _selectedReportList.toString(); //举报内容 345 | await report.save(); //保存举报信息 346 | ``` 347 | 348 | 可以在控制台查看举报内容: 349 | 350 | ![img](https://flutter-rtm-files.lncld.net/mIipnS9TSLbljz6AWVlnwUmmjrU9fmkS/flutter-show-report.png) 351 | 352 | ### 实现黑名单功能 353 | 354 | 355 | 356 | 我的解决办法是,在联系人列表处单击联系人弹框提示是否加入黑名单。 357 | 358 | 黑名单实现思路是新建一张 BlackList 表,来保存每个用户的黑名单列表,使用 [LeanCloud 云函数](https://leancloud.cn/docs/realtime-guide-systemconv.html#hash1748033991) 实现屏蔽消息。在 [_messageReceived](https://leancloud.cn/docs/realtime-guide-systemconv.html#hash-1573260517) 这个 Hook 函数下(这个 hook 发生在消息到达 LeanCloud 云端之后),先查此条消息的发件人与收件人是否在黑名单列表,如果在黑名单列表就删除其中要求屏蔽的收件人,返回新的收件人列表。 359 | 360 | 实现起来也比较简单,把下面这个云函数粘贴在 LeanCloud 控制台 > 云引擎 >云函数在线编辑框中即可。 361 | 362 | ![img](https://flutter-rtm-files.lncld.net/3yuFiTVjXvY0HtIXe6TMVmOqRWtFfpM3/flutter-hook.png) 363 | 364 | > 步骤 365 | > 366 | > 先点击「创建函数」,然后选择 _messageReceived 类型,粘贴下面的代码,最后点击「部署」按钮。 367 | 368 | 等待部署完成,黑名单功能就已经实现成功,将不再收到加入黑名单用户的全部消息。 369 | 370 | ``` 371 | //下面这个函数粘贴在 LeanCloud 控制台 372 | 373 | AV.Cloud.define('_messageReceived', async function(request) { 374 | let fromPeer = request.params.fromPeer; 375 | let toPeersNew = request.params.toPeers; 376 | 377 | var query = new AV.Query('BlackList'); 378 | query.equalTo('blackedList', fromPeer); 379 | query.containedIn('clientID', toPeersNew); 380 | return query.find().then((results) => { 381 | if (results.length > 0) { 382 | var clientID = results[0].get('clientID'); 383 | var index = toPeersNew.indexOf(clientID); 384 | if (index > -1) { 385 | toPeersNew.splice(index, 1); 386 | } 387 | return { 388 | toPeers: toPeersNew 389 | } 390 | } 391 | return { 392 | } 393 | }); 394 | }) 395 | ``` 396 | 397 | ## APP 安装(因开发者账号调整,暂不可用) 398 | 399 | [APP Store 下载链接](https://apps.apple.com/cn/app/leanmessage/id1529417244) 400 | 401 | ## 文档 402 | 403 | - [LeanCloud 即时通信插件链接](https://pub.dev/packages/leancloud_official_plugin#leancloud_official_plugin) 404 | - [LeanCloud 即时通信开发文档](https://leancloud.cn/docs/#即时通讯) 405 | - [Flutter 文档](https://flutter.dev/docs) 406 | -------------------------------------------------------------------------------- /lc_realtime/.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 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /lc_realtime/.gradle/5.2.1/fileChanges/last-build.bin: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lc_realtime/.gradle/5.2.1/fileHashes/fileHashes.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/.gradle/5.2.1/fileHashes/fileHashes.lock -------------------------------------------------------------------------------- /lc_realtime/.gradle/5.2.1/gc.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/.gradle/5.2.1/gc.properties -------------------------------------------------------------------------------- /lc_realtime/.gradle/buildOutputCleanup/buildOutputCleanup.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/.gradle/buildOutputCleanup/buildOutputCleanup.lock -------------------------------------------------------------------------------- /lc_realtime/.gradle/buildOutputCleanup/cache.properties: -------------------------------------------------------------------------------- 1 | #Fri May 08 12:27:13 CST 2020 2 | gradle.version=5.2.1 3 | -------------------------------------------------------------------------------- /lc_realtime/.gradle/vcs-1/gc.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/.gradle/vcs-1/gc.properties -------------------------------------------------------------------------------- /lc_realtime/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 27321ebbad34b0a3fafe99fac037102196d655ff 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /lc_realtime/README.md: -------------------------------------------------------------------------------- 1 | # lcrealtime 2 | 3 | A new Flutter application. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /lc_realtime/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /lc_realtime/android/app/agconnect-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "agcgw":{ 3 | "backurl":"connect-drcn.dbankcloud.cn", 4 | "url":"connect-drcn.hispace.hicloud.com" 5 | }, 6 | "client":{ 7 | "cp_id":"890086000102298554", 8 | "product_id":"9105371292390931866", 9 | "client_id":"315681308267250688", 10 | "client_secret":"56E8F24C182F4C4920B300833238FA3D68E474A6FF288E4B016AA9D779EBEE7F", 11 | "app_id":"101912147", 12 | "package_name":"cn.leancloud.demo.mixpush", 13 | "api_key":"CV6QAq2ligdk9gSk//NnHzViDb8puYQpks7kGgX+7sOQC8P1J5tObmlzZaf72l0QXoS8BDSEL/5iolU645u/Qsr8SSpR" 14 | }, 15 | "service":{ 16 | "analytics":{ 17 | "collector_url":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn", 18 | "resource_id":"p1", 19 | "channel_id":"" 20 | }, 21 | "cloudstorage":{ 22 | "storage_url":"https://agc-storage-drcn.platform.dbankcloud.cn" 23 | }, 24 | "ml":{ 25 | "mlservice_url":"ml-api-drcn.ai.dbankcloud.com,ml-api-drcn.ai.dbankcloud.cn" 26 | } 27 | }, 28 | "region":"CN", 29 | "configuration_version":"1.0" 30 | } -------------------------------------------------------------------------------- /lc_realtime/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 | apply plugin: 'com.huawei.agconnect' 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | 28 | android { 29 | 30 | // signingConfigs { 31 | // config { 32 | // keyPassword '123456' 33 | // keyAlias 'testalias' 34 | // storeFile file('/Users/vivian/Documents/LeanCloud/Flutter_WorkPlace/FlutterRealtimeDemo/lc_realtime/android/test.keystore') 35 | // storePassword '123456' 36 | // } 37 | // } 38 | //配置签名 39 | compileSdkVersion 28 40 | 41 | lintOptions { 42 | disable 'InvalidPackage' 43 | } 44 | 45 | defaultConfig { 46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 47 | applicationId "com.example.lcrealtime" 48 | minSdkVersion 23 49 | targetSdkVersion 28 50 | multiDexEnabled true 51 | versionCode flutterVersionCode.toInteger() 52 | versionName flutterVersionName 53 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 54 | } 55 | 56 | buildTypes { 57 | // debug { 58 | // signingConfig signingConfigs.config 59 | // } 60 | // release { 61 | // signingConfig signingConfigs.config 62 | // minifyEnabled false 63 | // proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 64 | // } 65 | } 66 | } 67 | 68 | flutter { 69 | source '../..' 70 | } 71 | 72 | dependencies { 73 | implementation fileTree(dir: 'libs', include: ['*.jar']) 74 | // implementation files("libs/vivo_pushsdk-v2.9.0.0.aar") 75 | // implementation 'cn.leancloud:mixpush-oppo:6.5.12' 76 | 77 | testImplementation 'junit:junit:4.12' 78 | androidTestImplementation 'androidx.test:runner:1.1.1' 79 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 80 | 81 | //使用存储服务 82 | implementation 'cn.leancloud:storage-android:8.1.4' 83 | //使用即时通信 84 | implementation 'cn.leancloud:realtime-android:8.1.4' 85 | implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' 86 | //混合推送需要的包 87 | // implementation 'cn.leancloud:mixpush-android:6.5.12' 88 | //华为推送 89 | // implementation 'com.huawei.hms:push:4.0.2.300' 90 | // 魅族推送需要的包 91 | // implementation 'com.meizu.flyme.internet:push-internal:3.6.+@aar' 92 | // implementation files("libs/vivo_pushsdk-v2.9.0.0.aar") 93 | // implementation 'cn.leancloud:mixpush-oppo:6.5.12' 94 | } 95 | -------------------------------------------------------------------------------- /lc_realtime/android/app/libs/MiPush_SDK_Client_3_7_5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/android/app/libs/MiPush_SDK_Client_3_7_5.jar -------------------------------------------------------------------------------- /lc_realtime/android/app/libs/oppo-mcssdk-2.0.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/android/app/libs/oppo-mcssdk-2.0.2.jar -------------------------------------------------------------------------------- /lc_realtime/android/app/libs/vivo_pushsdk-v2.9.0.0.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/android/app/libs/vivo_pushsdk-v2.9.0.0.aar -------------------------------------------------------------------------------- /lc_realtime/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lc_realtime/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 20 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /lc_realtime/android/app/src/main/java/com/example/lcrealtime/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.lcrealtime; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import io.flutter.embedding.android.FlutterActivity; 6 | import io.flutter.embedding.engine.FlutterEngine; 7 | import io.flutter.plugins.GeneratedPluginRegistrant; 8 | 9 | public class MainActivity extends FlutterActivity { 10 | @Override 11 | public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { 12 | // AVMixPushManager.connectHMS(this); 13 | GeneratedPluginRegistrant.registerWith(flutterEngine); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lc_realtime/android/app/src/main/java/com/example/lcrealtime/MyApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.lcrealtime; 2 | import cn.leancloud.LCLogger; 3 | import cn.leancloud.LeanCloud; 4 | import cn.leancloud.im.LCIMOptions; 5 | import io.flutter.app.FlutterApplication; 6 | 7 | public class MyApplication extends FlutterApplication{ 8 | private static final String LC_App_Id = "1eUivazFXYwJvuGpPl2LE4uY-gzGzoHsz"; 9 | private static final String LC_App_Key = "nLMIaQSwIsHfF206PnOFoYYa"; 10 | private static final String LC_Server_Url = "https://1euivazf.lc-cn-n1-shared.com"; 11 | // private static final String MEIZU_APP = "119851"; 12 | // private static final String MEIZU_KEY = "73c48ba926fb40b898797b819030830d"; 13 | // 14 | // private static final String MI_APPID = "2882303761517988199"; 15 | // private static final String MI_APPKEY = "5571798886199"; 16 | @Override 17 | public void onCreate() { 18 | super.onCreate(); 19 | 20 | LCIMOptions.getGlobalOptions().setUnreadNotificationEnabled(true); 21 | 22 | LeanCloud.setLogLevel(LCLogger.Level.DEBUG); 23 | LeanCloud.initialize(this, LC_App_Id, LC_App_Key, LC_Server_Url); 24 | 25 | // //华为推送 26 | // LCMixPushManager.registerHMSPush(this); 27 | // //小米 28 | // LCMixPushManager.registerXiaomiPush(this , MI_APPID, MI_APPKEY); 29 | // //魅族 30 | // LCMixPushManager.registerFlymePush(this, MEIZU_APP, MEIZU_KEY); 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lc_realtime/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /lc_realtime/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /lc_realtime/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /lc_realtime/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /lc_realtime/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /lc_realtime/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /lc_realtime/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /lc_realtime/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lc_realtime/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | maven {url 'http://developer.huawei.com/repo/'} 6 | // mavenLocal() 7 | } 8 | 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.6.3' 11 | classpath 'com.huawei.agconnect:agcp:1.2.1.301' 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | maven {url 'http://developer.huawei.com/repo/'} 20 | // mavenLocal() 21 | } 22 | } 23 | 24 | rootProject.buildDir = '../build' 25 | subprojects { 26 | project.buildDir = "${rootProject.buildDir}/${project.name}" 27 | } 28 | subprojects { 29 | project.evaluationDependsOn(':app') 30 | } 31 | 32 | task clean(type: Delete) { 33 | delete rootProject.buildDir 34 | } 35 | -------------------------------------------------------------------------------- /lc_realtime/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | #android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /lc_realtime/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri May 08 12:43:48 CST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip 7 | -------------------------------------------------------------------------------- /lc_realtime/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 | -------------------------------------------------------------------------------- /lc_realtime/android/test.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/android/test.keystore -------------------------------------------------------------------------------- /lc_realtime/assets/download_fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/assets/download_fail.png -------------------------------------------------------------------------------- /lc_realtime/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 | -------------------------------------------------------------------------------- /lc_realtime/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lc_realtime/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /lc_realtime/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /lc_realtime/ios/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | 3 | # Uncomment this line to define a global platform for your project 4 | platform :ios, '11.0' 5 | 6 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 7 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 8 | 9 | project 'Runner', { 10 | 'Debug' => :debug, 11 | 'Profile' => :release, 12 | 'Release' => :release, 13 | } 14 | 15 | def flutter_root 16 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 17 | unless File.exist?(generated_xcode_build_settings_path) 18 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 19 | end 20 | 21 | File.foreach(generated_xcode_build_settings_path) do |line| 22 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 23 | return matches[1].strip if matches 24 | end 25 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 26 | end 27 | 28 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 29 | 30 | flutter_ios_podfile_setup 31 | 32 | target 'Runner' do 33 | use_frameworks! 34 | use_modular_headers! 35 | 36 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 37 | end 38 | 39 | post_install do |installer| 40 | installer.pods_project.targets.each do |target| 41 | flutter_additional_ios_build_settings(target) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lc_realtime/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (5.5.0) 3 | - audioplayers (0.0.1): 4 | - Flutter 5 | - Flutter (1.0.0) 6 | - flutter_plugin_record (0.0.1): 7 | - Flutter 8 | - fluttertoast (0.0.2): 9 | - Flutter 10 | - Toast 11 | - FMDB (2.7.5): 12 | - FMDB/standard (= 2.7.5) 13 | - FMDB/standard (2.7.5) 14 | - image_picker (0.0.1): 15 | - Flutter 16 | - LeanCloud/Foundation (17.10.1): 17 | - Alamofire (~> 5.4) 18 | - LeanCloud/RTM-no-local-storage (17.10.1): 19 | - LeanCloud/Foundation (= 17.10.1) 20 | - SwiftProtobuf (~> 1.18) 21 | - leancloud_official_plugin (0.0.1): 22 | - Flutter 23 | - LeanCloud/RTM-no-local-storage (~> 17.10) 24 | - path_provider_ios (0.0.1): 25 | - Flutter 26 | - shared_preferences_ios (0.0.1): 27 | - Flutter 28 | - sqflite (0.0.2): 29 | - Flutter 30 | - FMDB (>= 2.7.5) 31 | - SwiftProtobuf (1.18.0) 32 | - Toast (4.0.0) 33 | 34 | DEPENDENCIES: 35 | - audioplayers (from `.symlinks/plugins/audioplayers/ios`) 36 | - Flutter (from `Flutter`) 37 | - flutter_plugin_record (from `.symlinks/plugins/flutter_plugin_record/ios`) 38 | - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) 39 | - image_picker (from `.symlinks/plugins/image_picker/ios`) 40 | - leancloud_official_plugin (from `.symlinks/plugins/leancloud_official_plugin/ios`) 41 | - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) 42 | - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`) 43 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 44 | 45 | SPEC REPOS: 46 | https://github.com/CocoaPods/Specs.git: 47 | - Alamofire 48 | - FMDB 49 | - LeanCloud 50 | - SwiftProtobuf 51 | - Toast 52 | 53 | EXTERNAL SOURCES: 54 | audioplayers: 55 | :path: ".symlinks/plugins/audioplayers/ios" 56 | Flutter: 57 | :path: Flutter 58 | flutter_plugin_record: 59 | :path: ".symlinks/plugins/flutter_plugin_record/ios" 60 | fluttertoast: 61 | :path: ".symlinks/plugins/fluttertoast/ios" 62 | image_picker: 63 | :path: ".symlinks/plugins/image_picker/ios" 64 | leancloud_official_plugin: 65 | :path: ".symlinks/plugins/leancloud_official_plugin/ios" 66 | path_provider_ios: 67 | :path: ".symlinks/plugins/path_provider_ios/ios" 68 | shared_preferences_ios: 69 | :path: ".symlinks/plugins/shared_preferences_ios/ios" 70 | sqflite: 71 | :path: ".symlinks/plugins/sqflite/ios" 72 | 73 | SPEC CHECKSUMS: 74 | Alamofire: 1c4fb5369c3fe93d2857c780d8bbe09f06f97e7c 75 | audioplayers: 455322b54050b30ea4b1af7cd9e9d105f74efa8c 76 | Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a 77 | flutter_plugin_record: 562ded56f3a109d769e72c3ef52ef20d835493d4 78 | fluttertoast: 6122fa75143e992b1d3470f61000f591a798cc58 79 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 80 | image_picker: 9aa50e1d8cdacdbed739e925b7eea16d014367e6 81 | LeanCloud: e6de4b8129ab68be7f70a9b77c4304f71c5a1a2c 82 | leancloud_official_plugin: 6f30dc57a7f181193f511355c17519a61dce033d 83 | path_provider_ios: 7d7ce634493af4477d156294792024ec3485acd5 84 | shared_preferences_ios: aef470a42dc4675a1cdd50e3158b42e3d1232b32 85 | sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 86 | SwiftProtobuf: c3c12645230d9b09c72267e0de89468c5543bd86 87 | Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 88 | 89 | PODFILE CHECKSUM: 216a07bd17c29f43047bcc707524116833a4fb33 90 | 91 | COCOAPODS: 1.11.2 92 | -------------------------------------------------------------------------------- /lc_realtime/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 75D38B816F724647BCB5345C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6717F1ECB7B06F8CEF021B36 /* Pods_Runner.framework */; }; 14 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 15 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 16 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = ""; 24 | dstSubfolderSpec = 10; 25 | files = ( 26 | ); 27 | name = "Embed Frameworks"; 28 | runOnlyForDeploymentPostprocessing = 0; 29 | }; 30 | /* End PBXCopyFilesBuildPhase section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 34 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 35 | 27DD645B83EB9C5FC47E9415 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 36 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 37 | 5E77470B24F6709B006FA0C7 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = ""; }; 38 | 5E77470C24F670B6006FA0C7 /* RunnerRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerRelease.entitlements; sourceTree = ""; }; 39 | 6717F1ECB7B06F8CEF021B36 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 41 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 42 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 43 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 44 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 45 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 47 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 48 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 49 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50 | CD0B25761FAADF9218BF981B /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 51 | D14C5338F76FC1C167E1563A /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 52 | /* End PBXFileReference section */ 53 | 54 | /* Begin PBXFrameworksBuildPhase section */ 55 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 56 | isa = PBXFrameworksBuildPhase; 57 | buildActionMask = 2147483647; 58 | files = ( 59 | 75D38B816F724647BCB5345C /* Pods_Runner.framework in Frameworks */, 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | /* End PBXFrameworksBuildPhase section */ 64 | 65 | /* Begin PBXGroup section */ 66 | 3EEE743274DC7BDA887AFCD8 /* Frameworks */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 6717F1ECB7B06F8CEF021B36 /* Pods_Runner.framework */, 70 | ); 71 | name = Frameworks; 72 | sourceTree = ""; 73 | }; 74 | 9740EEB11CF90186004384FC /* Flutter */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 78 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 79 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 80 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 81 | ); 82 | name = Flutter; 83 | sourceTree = ""; 84 | }; 85 | 97C146E51CF9000F007C117D = { 86 | isa = PBXGroup; 87 | children = ( 88 | 9740EEB11CF90186004384FC /* Flutter */, 89 | 97C146F01CF9000F007C117D /* Runner */, 90 | 97C146EF1CF9000F007C117D /* Products */, 91 | C038014A027BE48EF318F892 /* Pods */, 92 | 3EEE743274DC7BDA887AFCD8 /* Frameworks */, 93 | ); 94 | sourceTree = ""; 95 | }; 96 | 97C146EF1CF9000F007C117D /* Products */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 97C146EE1CF9000F007C117D /* Runner.app */, 100 | ); 101 | name = Products; 102 | sourceTree = ""; 103 | }; 104 | 97C146F01CF9000F007C117D /* Runner */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 5E77470C24F670B6006FA0C7 /* RunnerRelease.entitlements */, 108 | 5E77470B24F6709B006FA0C7 /* RunnerDebug.entitlements */, 109 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 110 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 111 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 112 | 97C147021CF9000F007C117D /* Info.plist */, 113 | 97C146F11CF9000F007C117D /* Supporting Files */, 114 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 115 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 116 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 117 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 118 | ); 119 | path = Runner; 120 | sourceTree = ""; 121 | }; 122 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | ); 126 | name = "Supporting Files"; 127 | sourceTree = ""; 128 | }; 129 | C038014A027BE48EF318F892 /* Pods */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | CD0B25761FAADF9218BF981B /* Pods-Runner.debug.xcconfig */, 133 | 27DD645B83EB9C5FC47E9415 /* Pods-Runner.release.xcconfig */, 134 | D14C5338F76FC1C167E1563A /* Pods-Runner.profile.xcconfig */, 135 | ); 136 | path = Pods; 137 | sourceTree = ""; 138 | }; 139 | /* End PBXGroup section */ 140 | 141 | /* Begin PBXNativeTarget section */ 142 | 97C146ED1CF9000F007C117D /* Runner */ = { 143 | isa = PBXNativeTarget; 144 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 145 | buildPhases = ( 146 | 4FEDB0401F4C7531A6C93068 /* [CP] Check Pods Manifest.lock */, 147 | 9740EEB61CF901F6004384FC /* Run Script */, 148 | 97C146EA1CF9000F007C117D /* Sources */, 149 | 97C146EB1CF9000F007C117D /* Frameworks */, 150 | 97C146EC1CF9000F007C117D /* Resources */, 151 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 152 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 153 | E0B72C6678CA091ED1272C26 /* [CP] Embed Pods Frameworks */, 154 | ); 155 | buildRules = ( 156 | ); 157 | dependencies = ( 158 | ); 159 | name = Runner; 160 | productName = Runner; 161 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 162 | productType = "com.apple.product-type.application"; 163 | }; 164 | /* End PBXNativeTarget section */ 165 | 166 | /* Begin PBXProject section */ 167 | 97C146E61CF9000F007C117D /* Project object */ = { 168 | isa = PBXProject; 169 | attributes = { 170 | LastUpgradeCheck = 1020; 171 | ORGANIZATIONNAME = "The Chromium Authors"; 172 | TargetAttributes = { 173 | 97C146ED1CF9000F007C117D = { 174 | CreatedOnToolsVersion = 7.3.1; 175 | DevelopmentTeam = 7J5XFNL99Q; 176 | LastSwiftMigration = 1100; 177 | ProvisioningStyle = Automatic; 178 | }; 179 | }; 180 | }; 181 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 182 | compatibilityVersion = "Xcode 3.2"; 183 | developmentRegion = en; 184 | hasScannedForEncodings = 0; 185 | knownRegions = ( 186 | en, 187 | Base, 188 | ); 189 | mainGroup = 97C146E51CF9000F007C117D; 190 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 191 | projectDirPath = ""; 192 | projectRoot = ""; 193 | targets = ( 194 | 97C146ED1CF9000F007C117D /* Runner */, 195 | ); 196 | }; 197 | /* End PBXProject section */ 198 | 199 | /* Begin PBXResourcesBuildPhase section */ 200 | 97C146EC1CF9000F007C117D /* Resources */ = { 201 | isa = PBXResourcesBuildPhase; 202 | buildActionMask = 2147483647; 203 | files = ( 204 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 205 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 206 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 207 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 208 | ); 209 | runOnlyForDeploymentPostprocessing = 0; 210 | }; 211 | /* End PBXResourcesBuildPhase section */ 212 | 213 | /* Begin PBXShellScriptBuildPhase section */ 214 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 215 | isa = PBXShellScriptBuildPhase; 216 | buildActionMask = 2147483647; 217 | files = ( 218 | ); 219 | inputPaths = ( 220 | ); 221 | name = "Thin Binary"; 222 | outputPaths = ( 223 | ); 224 | runOnlyForDeploymentPostprocessing = 0; 225 | shellPath = /bin/sh; 226 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 227 | }; 228 | 4FEDB0401F4C7531A6C93068 /* [CP] Check Pods Manifest.lock */ = { 229 | isa = PBXShellScriptBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | ); 233 | inputFileListPaths = ( 234 | ); 235 | inputPaths = ( 236 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 237 | "${PODS_ROOT}/Manifest.lock", 238 | ); 239 | name = "[CP] Check Pods Manifest.lock"; 240 | outputFileListPaths = ( 241 | ); 242 | outputPaths = ( 243 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 244 | ); 245 | runOnlyForDeploymentPostprocessing = 0; 246 | shellPath = /bin/sh; 247 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 248 | showEnvVarsInLog = 0; 249 | }; 250 | 9740EEB61CF901F6004384FC /* Run Script */ = { 251 | isa = PBXShellScriptBuildPhase; 252 | buildActionMask = 2147483647; 253 | files = ( 254 | ); 255 | inputPaths = ( 256 | ); 257 | name = "Run Script"; 258 | outputPaths = ( 259 | ); 260 | runOnlyForDeploymentPostprocessing = 0; 261 | shellPath = /bin/sh; 262 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 263 | }; 264 | E0B72C6678CA091ED1272C26 /* [CP] Embed Pods Frameworks */ = { 265 | isa = PBXShellScriptBuildPhase; 266 | buildActionMask = 2147483647; 267 | files = ( 268 | ); 269 | inputPaths = ( 270 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", 271 | "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", 272 | "${BUILT_PRODUCTS_DIR}/FMDB/FMDB.framework", 273 | "${PODS_ROOT}/../Flutter/Flutter.framework", 274 | "${BUILT_PRODUCTS_DIR}/LeanCloud/LeanCloud.framework", 275 | "${BUILT_PRODUCTS_DIR}/SwiftProtobuf/SwiftProtobuf.framework", 276 | "${BUILT_PRODUCTS_DIR}/audioplayers/audioplayers.framework", 277 | "${BUILT_PRODUCTS_DIR}/flutter_plugin_record/flutter_plugin_record.framework", 278 | "${BUILT_PRODUCTS_DIR}/fluttertoast/fluttertoast.framework", 279 | "${BUILT_PRODUCTS_DIR}/image_picker/image_picker.framework", 280 | "${BUILT_PRODUCTS_DIR}/leancloud_official_plugin/leancloud_official_plugin.framework", 281 | "${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework", 282 | "${BUILT_PRODUCTS_DIR}/shared_preferences/shared_preferences.framework", 283 | "${BUILT_PRODUCTS_DIR}/sqflite/sqflite.framework", 284 | ); 285 | name = "[CP] Embed Pods Frameworks"; 286 | outputPaths = ( 287 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", 288 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FMDB.framework", 289 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", 290 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LeanCloud.framework", 291 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftProtobuf.framework", 292 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/audioplayers.framework", 293 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_plugin_record.framework", 294 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/fluttertoast.framework", 295 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker.framework", 296 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/leancloud_official_plugin.framework", 297 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework", 298 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences.framework", 299 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqflite.framework", 300 | ); 301 | runOnlyForDeploymentPostprocessing = 0; 302 | shellPath = /bin/sh; 303 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 304 | showEnvVarsInLog = 0; 305 | }; 306 | /* End PBXShellScriptBuildPhase section */ 307 | 308 | /* Begin PBXSourcesBuildPhase section */ 309 | 97C146EA1CF9000F007C117D /* Sources */ = { 310 | isa = PBXSourcesBuildPhase; 311 | buildActionMask = 2147483647; 312 | files = ( 313 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 314 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 315 | ); 316 | runOnlyForDeploymentPostprocessing = 0; 317 | }; 318 | /* End PBXSourcesBuildPhase section */ 319 | 320 | /* Begin PBXVariantGroup section */ 321 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 322 | isa = PBXVariantGroup; 323 | children = ( 324 | 97C146FB1CF9000F007C117D /* Base */, 325 | ); 326 | name = Main.storyboard; 327 | sourceTree = ""; 328 | }; 329 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 330 | isa = PBXVariantGroup; 331 | children = ( 332 | 97C147001CF9000F007C117D /* Base */, 333 | ); 334 | name = LaunchScreen.storyboard; 335 | sourceTree = ""; 336 | }; 337 | /* End PBXVariantGroup section */ 338 | 339 | /* Begin XCBuildConfiguration section */ 340 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 341 | isa = XCBuildConfiguration; 342 | buildSettings = { 343 | ALWAYS_SEARCH_USER_PATHS = NO; 344 | CLANG_ANALYZER_NONNULL = YES; 345 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 346 | CLANG_CXX_LIBRARY = "libc++"; 347 | CLANG_ENABLE_MODULES = YES; 348 | CLANG_ENABLE_OBJC_ARC = YES; 349 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 350 | CLANG_WARN_BOOL_CONVERSION = YES; 351 | CLANG_WARN_COMMA = YES; 352 | CLANG_WARN_CONSTANT_CONVERSION = YES; 353 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 354 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 355 | CLANG_WARN_EMPTY_BODY = YES; 356 | CLANG_WARN_ENUM_CONVERSION = YES; 357 | CLANG_WARN_INFINITE_RECURSION = YES; 358 | CLANG_WARN_INT_CONVERSION = YES; 359 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 360 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 361 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 362 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 363 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 364 | CLANG_WARN_STRICT_PROTOTYPES = YES; 365 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 366 | CLANG_WARN_UNREACHABLE_CODE = YES; 367 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 368 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 369 | COPY_PHASE_STRIP = NO; 370 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 371 | ENABLE_NS_ASSERTIONS = NO; 372 | ENABLE_STRICT_OBJC_MSGSEND = YES; 373 | GCC_C_LANGUAGE_STANDARD = gnu99; 374 | GCC_NO_COMMON_BLOCKS = YES; 375 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 376 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 377 | GCC_WARN_UNDECLARED_SELECTOR = YES; 378 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 379 | GCC_WARN_UNUSED_FUNCTION = YES; 380 | GCC_WARN_UNUSED_VARIABLE = YES; 381 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 382 | MTL_ENABLE_DEBUG_INFO = NO; 383 | SDKROOT = iphoneos; 384 | SUPPORTED_PLATFORMS = iphoneos; 385 | TARGETED_DEVICE_FAMILY = "1,2"; 386 | VALIDATE_PRODUCT = YES; 387 | }; 388 | name = Profile; 389 | }; 390 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 391 | isa = XCBuildConfiguration; 392 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 393 | buildSettings = { 394 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 395 | CLANG_ENABLE_MODULES = YES; 396 | CODE_SIGN_IDENTITY = "Apple Development"; 397 | CODE_SIGN_STYLE = Automatic; 398 | CURRENT_PROJECT_VERSION = 10; 399 | DEVELOPMENT_TEAM = 7J5XFNL99Q; 400 | ENABLE_BITCODE = NO; 401 | FRAMEWORK_SEARCH_PATHS = ( 402 | "$(inherited)", 403 | "$(PROJECT_DIR)/Flutter", 404 | ); 405 | INFOPLIST_FILE = Runner/Info.plist; 406 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 407 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 408 | LIBRARY_SEARCH_PATHS = ( 409 | "$(inherited)", 410 | "$(PROJECT_DIR)/Flutter", 411 | ); 412 | PRODUCT_BUNDLE_IDENTIFIER = leancloud.flutterRealtimeApp; 413 | PRODUCT_NAME = "$(TARGET_NAME)"; 414 | PROVISIONING_PROFILE_SPECIFIER = ""; 415 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 416 | SWIFT_VERSION = 5.0; 417 | VERSIONING_SYSTEM = "apple-generic"; 418 | }; 419 | name = Profile; 420 | }; 421 | 97C147031CF9000F007C117D /* Debug */ = { 422 | isa = XCBuildConfiguration; 423 | buildSettings = { 424 | ALWAYS_SEARCH_USER_PATHS = NO; 425 | CLANG_ANALYZER_NONNULL = YES; 426 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 427 | CLANG_CXX_LIBRARY = "libc++"; 428 | CLANG_ENABLE_MODULES = YES; 429 | CLANG_ENABLE_OBJC_ARC = YES; 430 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 431 | CLANG_WARN_BOOL_CONVERSION = YES; 432 | CLANG_WARN_COMMA = YES; 433 | CLANG_WARN_CONSTANT_CONVERSION = YES; 434 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 435 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 436 | CLANG_WARN_EMPTY_BODY = YES; 437 | CLANG_WARN_ENUM_CONVERSION = YES; 438 | CLANG_WARN_INFINITE_RECURSION = YES; 439 | CLANG_WARN_INT_CONVERSION = YES; 440 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 441 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 442 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 443 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 444 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 445 | CLANG_WARN_STRICT_PROTOTYPES = YES; 446 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 447 | CLANG_WARN_UNREACHABLE_CODE = YES; 448 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 449 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 450 | COPY_PHASE_STRIP = NO; 451 | DEBUG_INFORMATION_FORMAT = dwarf; 452 | ENABLE_STRICT_OBJC_MSGSEND = YES; 453 | ENABLE_TESTABILITY = YES; 454 | GCC_C_LANGUAGE_STANDARD = gnu99; 455 | GCC_DYNAMIC_NO_PIC = NO; 456 | GCC_NO_COMMON_BLOCKS = YES; 457 | GCC_OPTIMIZATION_LEVEL = 0; 458 | GCC_PREPROCESSOR_DEFINITIONS = ( 459 | "DEBUG=1", 460 | "$(inherited)", 461 | ); 462 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 463 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 464 | GCC_WARN_UNDECLARED_SELECTOR = YES; 465 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 466 | GCC_WARN_UNUSED_FUNCTION = YES; 467 | GCC_WARN_UNUSED_VARIABLE = YES; 468 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 469 | MTL_ENABLE_DEBUG_INFO = YES; 470 | ONLY_ACTIVE_ARCH = YES; 471 | SDKROOT = iphoneos; 472 | TARGETED_DEVICE_FAMILY = "1,2"; 473 | }; 474 | name = Debug; 475 | }; 476 | 97C147041CF9000F007C117D /* Release */ = { 477 | isa = XCBuildConfiguration; 478 | buildSettings = { 479 | ALWAYS_SEARCH_USER_PATHS = NO; 480 | CLANG_ANALYZER_NONNULL = YES; 481 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 482 | CLANG_CXX_LIBRARY = "libc++"; 483 | CLANG_ENABLE_MODULES = YES; 484 | CLANG_ENABLE_OBJC_ARC = YES; 485 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 486 | CLANG_WARN_BOOL_CONVERSION = YES; 487 | CLANG_WARN_COMMA = YES; 488 | CLANG_WARN_CONSTANT_CONVERSION = YES; 489 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 490 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 491 | CLANG_WARN_EMPTY_BODY = YES; 492 | CLANG_WARN_ENUM_CONVERSION = YES; 493 | CLANG_WARN_INFINITE_RECURSION = YES; 494 | CLANG_WARN_INT_CONVERSION = YES; 495 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 496 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 497 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 498 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 499 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 500 | CLANG_WARN_STRICT_PROTOTYPES = YES; 501 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 502 | CLANG_WARN_UNREACHABLE_CODE = YES; 503 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 504 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 505 | COPY_PHASE_STRIP = NO; 506 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 507 | ENABLE_NS_ASSERTIONS = NO; 508 | ENABLE_STRICT_OBJC_MSGSEND = YES; 509 | GCC_C_LANGUAGE_STANDARD = gnu99; 510 | GCC_NO_COMMON_BLOCKS = YES; 511 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 512 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 513 | GCC_WARN_UNDECLARED_SELECTOR = YES; 514 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 515 | GCC_WARN_UNUSED_FUNCTION = YES; 516 | GCC_WARN_UNUSED_VARIABLE = YES; 517 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 518 | MTL_ENABLE_DEBUG_INFO = NO; 519 | SDKROOT = iphoneos; 520 | SUPPORTED_PLATFORMS = iphoneos; 521 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 522 | TARGETED_DEVICE_FAMILY = "1,2"; 523 | VALIDATE_PRODUCT = YES; 524 | }; 525 | name = Release; 526 | }; 527 | 97C147061CF9000F007C117D /* Debug */ = { 528 | isa = XCBuildConfiguration; 529 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 530 | buildSettings = { 531 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 532 | CLANG_ENABLE_MODULES = YES; 533 | CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements; 534 | CODE_SIGN_IDENTITY = "Apple Development: Beijing sui (LGJ84F26F9)"; 535 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development"; 536 | CODE_SIGN_STYLE = Automatic; 537 | CURRENT_PROJECT_VERSION = 10; 538 | DEVELOPMENT_TEAM = 7J5XFNL99Q; 539 | ENABLE_BITCODE = NO; 540 | FRAMEWORK_SEARCH_PATHS = ( 541 | "$(inherited)", 542 | "$(PROJECT_DIR)/Flutter", 543 | ); 544 | INFOPLIST_FILE = Runner/Info.plist; 545 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 546 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 547 | LIBRARY_SEARCH_PATHS = ( 548 | "$(inherited)", 549 | "$(PROJECT_DIR)/Flutter", 550 | ); 551 | PRODUCT_BUNDLE_IDENTIFIER = leancloud.flutterRealtimeApp; 552 | PRODUCT_NAME = "$(TARGET_NAME)"; 553 | PROVISIONING_PROFILE_SPECIFIER = ""; 554 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 555 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 556 | SWIFT_VERSION = 5.0; 557 | VERSIONING_SYSTEM = "apple-generic"; 558 | }; 559 | name = Debug; 560 | }; 561 | 97C147071CF9000F007C117D /* Release */ = { 562 | isa = XCBuildConfiguration; 563 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 564 | buildSettings = { 565 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 566 | CLANG_ENABLE_MODULES = YES; 567 | CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements; 568 | CODE_SIGN_IDENTITY = "Apple Development"; 569 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development"; 570 | CODE_SIGN_STYLE = Automatic; 571 | CURRENT_PROJECT_VERSION = 10; 572 | DEVELOPMENT_TEAM = 7J5XFNL99Q; 573 | ENABLE_BITCODE = NO; 574 | FRAMEWORK_SEARCH_PATHS = ( 575 | "$(inherited)", 576 | "$(PROJECT_DIR)/Flutter", 577 | ); 578 | INFOPLIST_FILE = Runner/Info.plist; 579 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 580 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 581 | LIBRARY_SEARCH_PATHS = ( 582 | "$(inherited)", 583 | "$(PROJECT_DIR)/Flutter", 584 | ); 585 | PRODUCT_BUNDLE_IDENTIFIER = leancloud.flutterRealtimeApp; 586 | PRODUCT_NAME = "$(TARGET_NAME)"; 587 | PROVISIONING_PROFILE_SPECIFIER = ""; 588 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 589 | SWIFT_VERSION = 5.0; 590 | VERSIONING_SYSTEM = "apple-generic"; 591 | }; 592 | name = Release; 593 | }; 594 | /* End XCBuildConfiguration section */ 595 | 596 | /* Begin XCConfigurationList section */ 597 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 598 | isa = XCConfigurationList; 599 | buildConfigurations = ( 600 | 97C147031CF9000F007C117D /* Debug */, 601 | 97C147041CF9000F007C117D /* Release */, 602 | 249021D3217E4FDB00AE95B9 /* Profile */, 603 | ); 604 | defaultConfigurationIsVisible = 0; 605 | defaultConfigurationName = Release; 606 | }; 607 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 608 | isa = XCConfigurationList; 609 | buildConfigurations = ( 610 | 97C147061CF9000F007C117D /* Debug */, 611 | 97C147071CF9000F007C117D /* Release */, 612 | 249021D4217E4FDB00AE95B9 /* Profile */, 613 | ); 614 | defaultConfigurationIsVisible = 0; 615 | defaultConfigurationName = Release; 616 | }; 617 | /* End XCConfigurationList section */ 618 | }; 619 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 620 | } 621 | -------------------------------------------------------------------------------- /lc_realtime/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lc_realtime/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 | -------------------------------------------------------------------------------- /lc_realtime/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /lc_realtime/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lc_realtime/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lc_realtime/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import LeanCloud 3 | import UserNotifications 4 | 5 | @UIApplicationMain 6 | @objc class AppDelegate: FlutterAppDelegate { 7 | override func application( 8 | _ application: UIApplication, 9 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 10 | ) -> Bool { 11 | do { 12 | 13 | LCApplication.logLevel = .all 14 | try LCApplication.default.set( 15 | id: "1eUivazFXYwJvuGpPl2LE4uY-gzGzoHsz", 16 | key: "nLMIaQSwIsHfF206PnOFoYYa", 17 | serverURL: "https://1euivazf.lc-cn-n1-shared.com") 18 | GeneratedPluginRegistrant.register(with: self) 19 | /* 20 | register APNs to access token, like this: 21 | */ 22 | UNUserNotificationCenter.current().getNotificationSettings { (settings) in 23 | switch settings.authorizationStatus { 24 | case .authorized: 25 | DispatchQueue.main.async { 26 | UIApplication.shared.registerForRemoteNotifications() 27 | } 28 | case .notDetermined: 29 | UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound]) { (granted, error) in 30 | if granted { 31 | DispatchQueue.main.async { 32 | UIApplication.shared.registerForRemoteNotifications() 33 | } 34 | } 35 | } 36 | default: 37 | break 38 | } 39 | _ = LCApplication.default.currentInstallation 40 | } 41 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 42 | } catch { 43 | fatalError("\(error)") 44 | } 45 | } 46 | 47 | override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { 48 | print("测试") 49 | /* 50 | set APNs deviceToken and Team ID. 51 | */ 52 | LCApplication.default.currentInstallation.set( 53 | deviceToken: deviceToken, 54 | apnsTeamId: "7J5XFNL99Q") 55 | /* 56 | save to LeanCloud. 57 | */ 58 | LCApplication.default.currentInstallation.save { (result) in 59 | switch result { 60 | case .success: 61 | break 62 | case .failure(error: let error): 63 | print(error) 64 | } 65 | } 66 | } 67 | override func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { 68 | print(error) 69 | } 70 | 71 | override func applicationDidBecomeActive(_ application: UIApplication) { 72 | //本地清空角标 73 | application.applicationIconBadgeNumber = 0 74 | //currentInstallation 的角标清零 75 | LCApplication.default.currentInstallation.badge = 0 76 | LCApplication.default.currentInstallation.save { (result) in 77 | switch result { 78 | case .success: 79 | break 80 | case .failure(error: let error): 81 | print(error) 82 | } 83 | } 84 | 85 | } 86 | } 87 | 88 | 89 | -------------------------------------------------------------------------------- /lc_realtime/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 | -------------------------------------------------------------------------------- /lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /lc_realtime/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 | -------------------------------------------------------------------------------- /lc_realtime/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /lc_realtime/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /lc_realtime/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SXiaoXu/FlutterRealtimeDemo/d0e9998e1d168bb7d68cd9a06e7e66c182e036cd/lc_realtime/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /lc_realtime/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. -------------------------------------------------------------------------------- /lc_realtime/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 | -------------------------------------------------------------------------------- /lc_realtime/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 | -------------------------------------------------------------------------------- /lc_realtime/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | LeanMessage 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | NSCameraUsageDescription 31 | 发送图片功能需要访问您的相机。如果不允许,你将无法在聊天过程中发送图片消息。 32 | NSMicrophoneUsageDescription 33 | 录音功能需要访问麦克风,如果不允许,你将无法在聊天过程中发送语音消息。 34 | NSPhotoLibraryUsageDescription 35 | 发送图片功能需要访问您的相册。如果不允许,你将无法在聊天过程中发送相册中的图片。 36 | UIBackgroundModes 37 | 38 | remote-notification 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIMainStoryboardFile 43 | Main 44 | UISupportedInterfaceOrientations 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationLandscapeLeft 48 | UIInterfaceOrientationLandscapeRight 49 | 50 | UISupportedInterfaceOrientations~ipad 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationPortraitUpsideDown 54 | UIInterfaceOrientationLandscapeLeft 55 | UIInterfaceOrientationLandscapeRight 56 | 57 | UIViewControllerBasedStatusBarAppearance 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /lc_realtime/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /lc_realtime/ios/Runner/RunnerDebug.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | 8 | 9 | -------------------------------------------------------------------------------- /lc_realtime/ios/Runner/RunnerRelease.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | 8 | 9 | -------------------------------------------------------------------------------- /lc_realtime/lib/Common/Global.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fluttertoast/fluttertoast.dart'; 3 | import 'package:leancloud_official_plugin/leancloud_plugin.dart'; 4 | import 'package:leancloud_storage/leancloud.dart'; 5 | import 'package:shared_preferences/shared_preferences.dart'; 6 | 7 | enum MyEvent { 8 | NewMessage, 9 | ScrollviewDidScroll, 10 | ImageMessageHeight, 11 | PlayAudioMessage, 12 | ConversationRefresh 13 | } 14 | 15 | //TextMessage 文本消息 16 | //ImageMessage 图像消息 17 | //AudioMessage 音频消息 18 | //VideoMessage 视频消息 19 | //FileMessage 普通文件消息(.txt/.doc/.md 等各种) 20 | //LocationMessage 地理位置消息 21 | 22 | enum MyMessageType { 23 | TextMessage, 24 | ImageMessage, 25 | AudioMessage, 26 | VideoMessage, 27 | FileMessage, 28 | LocationMessage 29 | } 30 | 31 | void showToastRed(String msg) { 32 | Fluttertoast.showToast( 33 | msg: msg, 34 | toastLength: Toast.LENGTH_SHORT, 35 | gravity: ToastGravity.CENTER, 36 | timeInSecForIosWeb: 1, 37 | backgroundColor: Colors.red, 38 | textColor: Colors.white, 39 | fontSize: 20.0); 40 | } 41 | 42 | void showToastGreen(String msg) { 43 | Fluttertoast.showToast( 44 | msg: msg, 45 | toastLength: Toast.LENGTH_SHORT, 46 | gravity: ToastGravity.CENTER, 47 | timeInSecForIosWeb: 1, 48 | backgroundColor: Colors.green, 49 | textColor: Colors.white, 50 | fontSize: 20.0); 51 | } 52 | 53 | List allClients() { 54 | List list = [ 55 | 'Tom', 56 | 'Jerry', 57 | 'Bob', 58 | 'Mary', 59 | 'Linda', 60 | 'Bill', 61 | 'XiaoHong', 62 | 'Lisa', 63 | 'Object', 64 | 'LC', 65 | 'William', 66 | 'robot', 67 | ]; 68 | return list; 69 | } 70 | 71 | String getMessageString(Message message) { 72 | String messageString = ''; 73 | if (message.binaryContent != null) { 74 | print('收到二进制消息:${message.binaryContent.toString()}'); 75 | messageString = '收到二进制消息'; 76 | } else if (message is TextMessage) { 77 | print('收到文本类型消息:${message.text}'); 78 | messageString = message.text; 79 | } else if (message is LocationMessage) { 80 | print('收到地理位置消息,坐标:${message.latitude},${message.longitude}'); 81 | messageString = '地理位置消息'; 82 | } else if (message is FileMessage) { 83 | if (message is ImageMessage) { 84 | print('收到图像消息,图像 URL:${message.url}'); 85 | messageString = '收到图像消息'; 86 | } else if (message is AudioMessage) { 87 | print('收到音频消息,消息时长:${message.duration}'); 88 | messageString = '收到语音消息'; 89 | } else if (message is VideoMessage) { 90 | print('收到视频消息,消息时长:${message.duration}'); 91 | messageString = '收到视频消息'; 92 | } else { 93 | print('收到.txt/.doc/.md 等各种类型的普通文件消息,URL:${message.url}'); 94 | messageString = '收到文件消息'; 95 | } 96 | } 97 | // else if (message is CustomMessage) { 98 | // // CustomMessage 是自定义的消息类型 99 | // print('收到自定义类型消息'); 100 | // } 101 | else { 102 | // 这里可以继续添加自定义类型的判断条件 103 | print('收到未知消息类型'); 104 | messageString = '未知消息类型'; 105 | // if (message.stringContent != null) { 106 | // print('收到普通消息:${message.stringContent}'); 107 | // lastMessage = message.stringContent; 108 | // } 109 | } 110 | return messageString; 111 | } 112 | 113 | ///根据给定的日期得到format后的日期 114 | String getFormatDate(String dateOriginal) { 115 | if (dateOriginal == null) { 116 | return ''; 117 | } 118 | //现在的日期 119 | var today = DateTime.now(); 120 | //今天的23:59:59 121 | var standardDate = DateTime(today.year, today.month, today.day, 23, 59, 59); 122 | //传入的日期与今天的23:59:59秒进行比较 123 | Duration diff = standardDate.difference(DateTime.parse(dateOriginal)); 124 | if (diff < Duration(days: 1)) { 125 | //今天 126 | // 09:20 127 | return dateOriginal.substring(11, 16); 128 | } else if (diff >= Duration(days: 1) && diff < Duration(days: 2)) { 129 | //昨天 130 | //昨天 09:20 131 | return "昨天 " + dateOriginal.substring(11, 16); 132 | } else { 133 | //昨天之前 134 | // 2019-01-23 09:20 135 | return dateOriginal.substring(0, 16); 136 | } 137 | } 138 | 139 | class CommonUtil { 140 | static Future showLoadingDialog(BuildContext context) { 141 | return showDialog( 142 | context: context, 143 | builder: (BuildContext context) { 144 | return new Material( 145 | color: Colors.transparent, 146 | child: WillPopScope( 147 | onWillPop: () => new Future.value(false), 148 | child: Center( 149 | child: new CircularProgressIndicator(), 150 | ))); 151 | }); 152 | } 153 | } 154 | 155 | class Global { 156 | static SharedPreferences _prefs; 157 | static String clientID; 158 | 159 | //初始化全局信息,会在APP启动时执行 160 | static Future init() async { 161 | _prefs = await SharedPreferences.getInstance(); 162 | 163 | var _profile = _prefs.getString("clientID"); 164 | if (_profile != null) { 165 | try { 166 | clientID = _profile; 167 | } catch (e) { 168 | print(e); 169 | } 170 | } 171 | LeanCloud.initialize( 172 | '1eUivazFXYwJvuGpPl2LE4uY-gzGzoHsz', 'nLMIaQSwIsHfF206PnOFoYYa', 173 | server: 'https://1euivazf.lc-cn-n1-shared.com', 174 | queryCache: new LCQueryCache()); 175 | } 176 | 177 | static saveClientID(String id) { 178 | _prefs.setString("clienidtID", id); 179 | clientID = id; 180 | } 181 | 182 | static removeClientID() { 183 | _prefs.remove("clientID"); 184 | _prefs.clear(); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /lc_realtime/lib/Models/CurrentClient.dart: -------------------------------------------------------------------------------- 1 | import 'package:leancloud_official_plugin/leancloud_plugin.dart'; 2 | import 'package:flutter/material.dart'; 3 | import '../Common/Global.dart'; 4 | 5 | class CurrentClient { 6 | factory CurrentClient() => _sharedInstance(); 7 | static CurrentClient _instance = CurrentClient._(); 8 | Client client ; 9 | 10 | CurrentClient._() { 11 | print('初始化'); 12 | client = Client(id: Global.clientID); 13 | } 14 | 15 | static CurrentClient _sharedInstance() { 16 | return _instance; 17 | } 18 | 19 | void updateClient(){ 20 | client = Client(id: Global.clientID); 21 | } 22 | // 23 | // void open() async { 24 | // print('clint.open'); 25 | //// await client.open(); 26 | // } 27 | } 28 | -------------------------------------------------------------------------------- /lc_realtime/lib/Routes/ContactsPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:lcrealtime/Common/Global.dart'; 3 | import 'package:lcrealtime/Models/CurrentClient.dart'; 4 | import 'package:leancloud_official_plugin/leancloud_plugin.dart'; 5 | import 'package:leancloud_storage/leancloud.dart'; 6 | import 'ConversationDetailPage.dart'; 7 | 8 | class ContactsPage extends StatefulWidget { 9 | @override 10 | _ContactsPageState createState() => new _ContactsPageState(); 11 | } 12 | 13 | class _ContactsPageState extends State { 14 | List _list = allClients(); 15 | @override 16 | void initState() { 17 | super.initState(); 18 | removeCurrentClient(); 19 | } 20 | 21 | removeCurrentClient() { 22 | _list.remove(Global.clientID); 23 | setState(() {}); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Column(children: [ 29 | Expanded( 30 | child: ListView.separated( 31 | //添加分割线 32 | separatorBuilder: (BuildContext context, int index) { 33 | return new Divider( 34 | height: 0.8, 35 | color: Colors.grey, 36 | ); 37 | }, 38 | itemCount: _list.length, 39 | // itemExtent: 50.0, //强制高度为50.0 40 | itemBuilder: (BuildContext context, int index) { 41 | return GestureDetector( 42 | onTap: () { 43 | showConfirmDialog(_list[index]); 44 | }, 45 | 46 | child: ListTile(title: Text(_list[index])), 47 | ); 48 | }), 49 | ), 50 | ]); 51 | } 52 | 53 | void addBlackList(String clientID) async { 54 | if (Global.clientID != null) { 55 | 56 | LCObject blackList = LCObject('BlackList'); 57 | blackList['clientID'] = Global.clientID; 58 | blackList.addAllUnique('blackedList', [clientID]); 59 | await blackList.save(); 60 | showToastGreen('加入黑名单成功!'); 61 | _list.remove(clientID); 62 | setState(() { 63 | }); 64 | 65 | // try { 66 | // Conversation conversation = await currentClient.client.createConversation( 67 | // isUnique: true, 68 | // members: {clientID}, 69 | // name: Global.clientID + ' & ' + clientID); 70 | // 71 | // Navigator.push( 72 | // context, 73 | // new MaterialPageRoute( 74 | // builder: (context) => 75 | // new ConversationDetailPage(conversation: conversation), 76 | // ), 77 | // ); 78 | // } catch (e) { 79 | // showToastRed('创建会话失败:${e.message}'); 80 | // } 81 | } else { 82 | showToastRed('用户未登录'); 83 | return; 84 | } 85 | } 86 | Future showConfirmDialog(String name) async { 87 | return showDialog( 88 | context: context, 89 | builder: (context) { 90 | return AlertDialog( 91 | title: Text("加入黑名单"), 92 | content: Text("确认将 $name 加入黑名单,不再接收其任何消息吗?"), 93 | actions: [ 94 | FlatButton( 95 | child: Text("取消"), 96 | onPressed: () => Navigator.of(context).pop(), // 关闭对话框 97 | ), 98 | FlatButton( 99 | child: Text("确认"), 100 | onPressed: () { 101 | addBlackList(name); 102 | //关闭对话框并返回true 103 | Navigator.of(context).pop(); 104 | }, 105 | ), 106 | ], 107 | ); 108 | }, 109 | ); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /lc_realtime/lib/Routes/ConversationDetailPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:lcrealtime/Common/Global.dart'; 3 | import 'package:lcrealtime/Models/CurrentClient.dart'; 4 | import 'package:lcrealtime/States/GlobalEvent.dart'; 5 | import 'package:lcrealtime/Widgets/MessageList.dart'; 6 | import 'package:lcrealtime/Widgets/InputMessageView.dart'; 7 | import 'package:leancloud_official_plugin/leancloud_plugin.dart'; 8 | 9 | class ConversationDetailPage extends StatefulWidget { 10 | final Conversation conversation; 11 | 12 | ConversationDetailPage({Key key, @required this.conversation}) 13 | : super(key: key); 14 | @override 15 | _ConversationDetailPageState createState() => 16 | new _ConversationDetailPageState(); 17 | } 18 | 19 | class _ConversationDetailPageState extends State { 20 | // ScrollController _scrollController = ScrollController(keepScrollOffset: true); 21 | TextEditingController renameController = TextEditingController(); 22 | 23 | Message _firstMessage; 24 | CurrentClient currentClint; 25 | 26 | @override 27 | void initState() { 28 | super.initState(); 29 | currentClint = CurrentClient(); 30 | //进入会话详情页面,标记会话已读 31 | this.widget.conversation.read(); 32 | print(this.widget.conversation.id); 33 | 34 | 35 | 36 | 37 | 38 | } 39 | 40 | @override 41 | void deactivate() async { 42 | super.deactivate(); 43 | // //刷新列表 44 | // mess.emit(MyEvent.ConversationRefresh); 45 | } 46 | 47 | @override 48 | Widget build(BuildContext context) { 49 | return Scaffold( 50 | appBar: AppBar( 51 | centerTitle: true, 52 | title: Text(this.widget.conversation.name), 53 | actions: [ 54 | IconButton( 55 | icon: Icon(Icons.settings, color: Colors.white), 56 | onPressed: () { 57 | showConfirmDialog(); 58 | }, 59 | ) 60 | ], 61 | ), 62 | body: Container( 63 | padding: EdgeInsets.fromLTRB(10, 10, 10, 20), 64 | child: FutureBuilder>( 65 | future: queryMessages(), 66 | builder: (BuildContext context, AsyncSnapshot snapshot) { 67 | // 请求已结束 68 | if (snapshot.connectionState == ConnectionState.done) { 69 | if (snapshot.hasError) { 70 | return Container( 71 | height: 60.0, 72 | child: Center( 73 | child: Text("Error: ${snapshot.error}"), 74 | ), 75 | ); 76 | } else { 77 | return Column( 78 | children: [ 79 | MessageList( 80 | // scrollController: _scrollController, 81 | conversation: this.widget.conversation, 82 | firstPageMessages: snapshot.data, 83 | firstMessage: _firstMessage), 84 | InputMessageView( 85 | // scrollController: _scrollController, 86 | conversation: this.widget.conversation), 87 | ], 88 | ); 89 | } 90 | } else { 91 | // 请求未结束,显示loading 92 | return CircularProgressIndicator(); 93 | } 94 | }, 95 | ), 96 | )); 97 | } 98 | 99 | void updateConInfo() async { 100 | if (renameController.text != null && renameController.text != '') { 101 | await widget.conversation.updateInfo(attributes: { 102 | 'name': renameController.text, 103 | }); 104 | // setState(() {}); 105 | 106 | 107 | List conversations; 108 | ConversationQuery query = currentClint.client.conversationQuery(); 109 | query.whereEqualTo('objectId', widget.conversation.id); 110 | 111 | conversations = await query.find(); 112 | Conversation conversationFirst = conversations.first; 113 | print('name--->' + conversationFirst.name); 114 | 115 | 116 | } else { 117 | showToastRed('名称不能为空'); 118 | } 119 | } 120 | 121 | Future showConfirmDialog() async { 122 | return showDialog( 123 | context: context, 124 | builder: (context) { 125 | return AlertDialog( 126 | title: Text( 127 | "修改会话名称:", 128 | style: new TextStyle( 129 | fontWeight: FontWeight.normal, 130 | ), 131 | ), 132 | content: TextField( 133 | controller: renameController, 134 | ), 135 | actions: [ 136 | FlatButton( 137 | child: Text("取消"), 138 | onPressed: () => Navigator.of(context).pop(), // 关闭对话框 139 | ), 140 | FlatButton( 141 | child: Text("确认"), 142 | onPressed: () { 143 | updateConInfo(); 144 | //关闭对话框并返回true 145 | Navigator.of(context).pop(); 146 | }, 147 | ), 148 | ], 149 | ); 150 | }, 151 | ); 152 | } 153 | 154 | Future> queryMessages() async { 155 | List messages; 156 | try { 157 | messages = await this.widget.conversation.queryMessage( 158 | limit: 10, 159 | ); 160 | _firstMessage = messages.first; 161 | } catch (e) { 162 | print(e.message); 163 | } 164 | return messages; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /lc_realtime/lib/Routes/ConversationListPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:lcrealtime/Models/CurrentClient.dart'; 3 | import 'package:lcrealtime/Routes/ConversationDetailPage.dart'; 4 | import 'package:lcrealtime/Widgets/TextWidget.dart'; 5 | import 'package:shared_preferences/shared_preferences.dart'; 6 | import '../Common/Global.dart'; 7 | import 'package:leancloud_official_plugin/leancloud_plugin.dart'; 8 | import 'package:lcrealtime/States/GlobalEvent.dart'; 9 | 10 | class ConversationListPage extends StatefulWidget { 11 | @override 12 | _ConversationListPageState createState() => new _ConversationListPageState(); 13 | } 14 | 15 | class _ConversationListPageState extends State { 16 | CurrentClient currentClint; 17 | 18 | Map unreadCountMap = Map(); 19 | Map conversationIDToIndexMap = Map(); 20 | 21 | @override 22 | void initState() { 23 | super.initState(); 24 | currentClint = CurrentClient(); 25 | 26 | //收到新消息 27 | currentClint.client.onMessage = ({ 28 | Client client, 29 | Conversation conversation, 30 | Message message, 31 | }) { 32 | if (message != null) { 33 | receiveNewMessage(message); 34 | print('收到信息---'); 35 | } 36 | }; 37 | // 38 | mess.on(MyEvent.ConversationRefresh, (arg) { 39 | setState(() {}); 40 | }); 41 | //未读数更新通知 42 | currentClint.client.onUnreadMessageCountUpdated = ({ 43 | Client client, 44 | Conversation conversation, 45 | }) { 46 | print('onUnreadMessageCountUpdated-----:' + 47 | conversation.unreadMessageCount.toString()); 48 | // final prefs = await SharedPreferences.getInstance(); 49 | if (conversation.unreadMessageCount != null) { 50 | // prefs.setInt(conversation.id, conversation.unreadMessageCount); 51 | unreadCountMap[conversation.id] = conversation.unreadMessageCount; 52 | } else { 53 | // prefs.setInt(conversation.id, 0); 54 | unreadCountMap[conversation.id] = 0; 55 | } 56 | setState(() {}); 57 | //TODO 局部刷新 58 | // int count = unreadCountMap[conversation.id]; 59 | // int index = conversationIDToIndexMap[conversation.id]; 60 | // print(index.toString()+'index--'); 61 | // if (count != null && index != null) { 62 | // _keyList[index].currentState.onPressed(count); 63 | // } 64 | }; 65 | } 66 | 67 | void receiveNewMessage(Message message) { 68 | //收到新消息刷新页面 69 | // setState(() {}); 70 | } 71 | // Future getUnReadMessageCount(String conversationId) async { 72 | // final prefs = await SharedPreferences.getInstance(); 73 | // int counter = prefs.getInt(conversationId) ?? 0; 74 | // print('counter:-----:' + counter.toString()); 75 | // return counter; 76 | // } 77 | //根据ID获取index 78 | @override 79 | void dispose() { 80 | super.dispose(); 81 | //取消订阅 82 | mess.off(MyEvent.ConversationRefresh); 83 | } 84 | 85 | @override 86 | Widget build(BuildContext context) { 87 | return Scaffold( 88 | body: Center( 89 | // padding: EdgeInsets.all(2.0), 90 | child: FutureBuilder>( 91 | future: retrieveData(), 92 | builder: (BuildContext context, AsyncSnapshot snapshot) { 93 | // 请求已结束 94 | if (snapshot.connectionState == ConnectionState.done) { 95 | if (snapshot.hasError) { 96 | return Text("Error: ${snapshot.error}"); 97 | } else { 98 | return ListView.separated( 99 | //添加分割线 100 | separatorBuilder: (BuildContext context, int index) { 101 | return new Divider( 102 | height: 0.8, 103 | color: Colors.grey, 104 | ); 105 | }, 106 | physics: const AlwaysScrollableScrollPhysics(), 107 | itemCount: snapshot.data.length, 108 | itemBuilder: (context, index) { 109 | Conversation con = snapshot.data[index]; 110 | String name = con.name; 111 | List members = con.members; 112 | String time; 113 | String lastMessageString = '暂无新消息'; 114 | if (con.lastMessage == null) { 115 | time = getFormatDate(con.updatedAt.toString()); 116 | } else { 117 | time = getFormatDate(con.lastMessageDate.toString()); 118 | lastMessageString = con.lastMessage.fromClientID + 119 | ':' + 120 | getMessageString(con.lastMessage); 121 | } 122 | int unreadCount = 0; 123 | if (unreadCountMap[con.id] != null) { 124 | unreadCount = unreadCountMap[con.id]; 125 | } 126 | // conversationIDToIndexMap[con.id] = index; 127 | print('unreadCount:-----:' + unreadCount.toString()); 128 | 129 | return GestureDetector( 130 | onTap: () { 131 | Conversation con = snapshot.data[index]; 132 | onTapEvent(con); 133 | }, //点击 134 | child: Container( 135 | padding: const EdgeInsets.all(10), 136 | color: Colors.white, 137 | child: Row( 138 | children: [ 139 | new Expanded( 140 | flex: 2, 141 | child: new Column( 142 | crossAxisAlignment: CrossAxisAlignment.start, 143 | children: [ 144 | new Container( 145 | padding: const EdgeInsets.only( 146 | bottom: 8.0, right: 8, left: 10), 147 | child: Row( 148 | mainAxisAlignment: 149 | MainAxisAlignment.start, 150 | children: [ 151 | Container( 152 | padding: const EdgeInsets.only( 153 | right: 4, 154 | ), 155 | child: Text( 156 | name, 157 | style: new TextStyle( 158 | fontWeight: FontWeight.bold, 159 | ), 160 | ), 161 | ), 162 | buildUnReadCountView(unreadCount), 163 | ], 164 | ), 165 | ), 166 | new Container( 167 | padding: const EdgeInsets.only( 168 | bottom: 8.0, right: 8, left: 10), 169 | child: new Text( 170 | members.toString(), 171 | style: new TextStyle( 172 | color: Colors.grey[600], 173 | ), 174 | ), 175 | ), 176 | new Container( 177 | padding: const EdgeInsets.only( 178 | right: 8, left: 10), 179 | child: new Text( 180 | lastMessageString, 181 | style: new TextStyle( 182 | color: Colors.black87, 183 | ), 184 | ), 185 | ), 186 | ], 187 | ), 188 | ), 189 | new Expanded( 190 | flex: 1, 191 | child: new Column( 192 | crossAxisAlignment: CrossAxisAlignment.end, 193 | children: [ 194 | new Container( 195 | padding: const EdgeInsets.only( 196 | bottom: 0, right: 0), 197 | child: new Text( 198 | time, 199 | style: new TextStyle( 200 | color: Colors.black54, 201 | ), 202 | ), 203 | ), 204 | ], 205 | ), 206 | ), 207 | ], 208 | ), 209 | )); 210 | }, 211 | ); 212 | } 213 | } else { 214 | // 请求未结束,显示loading 215 | return CircularProgressIndicator(); 216 | } 217 | }, 218 | ), 219 | ), 220 | ); 221 | } 222 | 223 | Widget buildUnReadCountView(int count) { 224 | if (count > 0) { 225 | String showNum = ''; 226 | if (count < 10) { 227 | showNum = ''' ''' + count.toString() + ''' '''; 228 | } else { 229 | showNum = count.toString(); 230 | } 231 | return DecoratedBox( 232 | decoration: BoxDecoration( 233 | gradient: LinearGradient(colors: [Colors.red, Colors.red]), 234 | borderRadius: BorderRadius.circular(16.0), //圆角 235 | ), 236 | child: Padding( 237 | padding: EdgeInsets.symmetric(horizontal: 3.0, vertical: 3.0), 238 | // child: TextWidget(_keyList[index]))); 239 | child: Text( 240 | showNum, 241 | style: TextStyle( 242 | color: Colors.white, 243 | fontSize: 12.0, 244 | ), 245 | ))); 246 | } else { 247 | return Container( 248 | height: 0, 249 | ); 250 | } 251 | } 252 | 253 | void onTapEvent(Conversation con) { 254 | Navigator.push( 255 | context, 256 | new MaterialPageRoute( 257 | builder: (context) => new ConversationDetailPage(conversation: con), 258 | ), 259 | ); 260 | } 261 | 262 | Future> retrieveData() async { 263 | CurrentClient currentClient = CurrentClient(); 264 | List conversations; 265 | try { 266 | ConversationQuery query = currentClient.client.conversationQuery(); 267 | //TODO:上拉加载更多 268 | query.limit = 20; 269 | query.orderByDescending('updatedAt'); 270 | //让查询结果附带一条最新消息 271 | query.includeLastMessage = true; 272 | conversations = await query.find(); 273 | 274 | //记录未读消息数 275 | final prefs = await SharedPreferences.getInstance(); 276 | conversations.forEach((item) { 277 | if (item.unreadMessageCount != null) { 278 | // prefs.setInt(conversation.id, conversation.unreadMessageCount); 279 | unreadCountMap[item.id] = item.unreadMessageCount; 280 | } else { 281 | // prefs.setInt(conversation.id, 0); 282 | unreadCountMap[item.id] = 0; 283 | } 284 | 285 | // //之前没有值,存储一份 286 | // if (prefs.getInt(item.id) == null) { 287 | // if (item.unreadMessageCount != null) { 288 | // prefs.setInt(item.id, item.unreadMessageCount); 289 | // unreadCountMap[item.id] = item.unreadMessageCount; 290 | // } else { 291 | // prefs.setInt(item.id, 0); 292 | // unreadCountMap[item.id] = 0; 293 | // } 294 | // } else { 295 | // unreadCountMap[item.id] = prefs.getInt(item.id); 296 | // } 297 | }); 298 | } catch (e) { 299 | print(e); 300 | showToastRed(e.message); 301 | } 302 | return conversations; 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /lc_realtime/lib/Routes/HomeBottomBar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:lcrealtime/routes/ConversationListPage.dart'; 3 | import 'package:lcrealtime/routes/LoginPage.dart'; 4 | import 'ContactsPage.dart'; 5 | import 'package:leancloud_official_plugin/leancloud_plugin.dart'; 6 | import '../Common/Global.dart'; 7 | import 'SelectChatMembers.dart'; 8 | import 'package:lcrealtime/Models/CurrentClient.dart'; 9 | 10 | class HomeBottomBarPage extends StatefulWidget { 11 | @override 12 | _HomeBottomBarPageState createState() => _HomeBottomBarPageState(); 13 | } 14 | 15 | class _HomeBottomBarPageState extends State { 16 | int _currentIndex = 0; //记录当前选中的页面 17 | 18 | List _pages = [ 19 | ConversationListPage(), 20 | ContactsPage(), 21 | ]; 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | } 27 | 28 | //退出 29 | Future clientClose() async { 30 | CommonUtil.showLoadingDialog(context); //发起请求前弹出loading 31 | 32 | close().then((value) { 33 | Navigator.pop(context); //销毁 loading 34 | Navigator.pushAndRemoveUntil( 35 | context, 36 | new MaterialPageRoute(builder: (context) => LoginPage()), 37 | (_) => false); 38 | }).catchError((error) { 39 | showToastRed(error.message); 40 | Navigator.pop(context); //销毁 loading 41 | }); 42 | } 43 | 44 | Future showConfirmDialog() async { 45 | return showDialog( 46 | context: context, 47 | builder: (context) { 48 | return AlertDialog( 49 | title: Text("提示"), 50 | content: Text("确认退出登录"), 51 | actions: [ 52 | FlatButton( 53 | child: Text("取消"), 54 | onPressed: () => Navigator.of(context).pop(), // 关闭对话框 55 | ), 56 | FlatButton( 57 | child: Text("确认"), 58 | onPressed: () { 59 | //Client close; 60 | //关闭对话框并返回true 61 | clientClose(); 62 | Navigator.of(context).pop(); 63 | }, 64 | ), 65 | ], 66 | ); 67 | }, 68 | ); 69 | } 70 | 71 | Align navRightButton(BuildContext context) { 72 | Align content; 73 | content = Align( 74 | alignment: Alignment.center, 75 | child: Padding( 76 | padding: EdgeInsets.only(top: 0.0), 77 | child: IconButton( 78 | icon: Icon(Icons.directions_run), 79 | onPressed: () { 80 | showConfirmDialog(); 81 | }), 82 | ), 83 | ); 84 | return content; 85 | } 86 | 87 | Align navLeftButton(BuildContext context) { 88 | Align content; 89 | content = Align( 90 | alignment: Alignment.center, 91 | child: Padding( 92 | padding: EdgeInsets.only(top: 0.0), 93 | child: IconButton( 94 | icon: Icon(Icons.add), 95 | onPressed: () { 96 | Navigator.push( 97 | context, 98 | new MaterialPageRoute( 99 | builder: (context) => new SelectChatMembers(), 100 | ), 101 | ); 102 | }), 103 | ), 104 | ); 105 | return content; 106 | } 107 | 108 | @override 109 | Widget build(BuildContext context) { 110 | return Scaffold( 111 | appBar: AppBar( 112 | //导航栏 113 | title: Text("当前用户:${Global.clientID}"), 114 | centerTitle: true, 115 | //导航栏右侧菜单 116 | actions: [ 117 | navRightButton(context), 118 | ], 119 | leading: navLeftButton(context), //导航栏左侧菜单 120 | ), 121 | body: this._pages[this._currentIndex], 122 | bottomNavigationBar: BottomNavigationBar( 123 | // 底部导航 124 | items: [ 125 | BottomNavigationBarItem(icon: Icon(Icons.message), title: Text('会话')), 126 | BottomNavigationBarItem( 127 | icon: Icon(Icons.perm_contact_calendar), title: Text('联系人')), 128 | ], 129 | currentIndex: this._currentIndex, 130 | fixedColor: Colors.blue, 131 | onTap: (index) { 132 | setState(() { 133 | //设置点击底部Tab的时候的页面跳转 134 | this._currentIndex = index; 135 | }); 136 | }, 137 | type: BottomNavigationBarType.fixed, 138 | ), 139 | ); 140 | } 141 | 142 | Future close() async { 143 | if (Global.clientID != null) { 144 | CurrentClient currentClint = CurrentClient(); 145 | await currentClint.client.close(); 146 | Global.removeClientID(); 147 | } else { 148 | showToastRed('有 BUG,重启一下试试。。。'); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /lc_realtime/lib/Routes/LoginPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:lcrealtime/Models/CurrentClient.dart'; 3 | import 'package:lcrealtime/Routes/UserProtocol.dart'; 4 | import '../Common/Global.dart'; 5 | import 'HomeBottomBar.dart'; 6 | 7 | class LoginPage extends StatefulWidget { 8 | @override 9 | _LoginPageState createState() => _LoginPageState(); 10 | } 11 | 12 | class _LoginPageState extends State { 13 | String _clientID; 14 | bool _checkboxSelected = true; 15 | 16 | @override 17 | void initState() { 18 | super.initState(); 19 | 20 | if (Global.clientID != null) { 21 | _clientID = Global.clientID; 22 | setState(() {}); 23 | } 24 | } 25 | 26 | Future userLogin(String clientID) async { 27 | CommonUtil.showLoadingDialog(context); //发起请求前弹出loading 28 | Global.saveClientID(clientID); 29 | 30 | login(clientID).then((value) { 31 | Navigator.pop(context); //销毁 loading 32 | Navigator.pushAndRemoveUntil( 33 | context, 34 | new MaterialPageRoute(builder: (context) => HomeBottomBarPage()), 35 | (_) => false); 36 | }).catchError((error) { 37 | showToastRed(error.message); 38 | Navigator.pop(context); //销毁 loading 39 | }); 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return Scaffold( 45 | body: Form( 46 | child: ListView( 47 | padding: EdgeInsets.symmetric(horizontal: 22.0), 48 | children: [ 49 | SizedBox( 50 | height: kToolbarHeight, 51 | ), 52 | SizedBox(height: 80.0), 53 | buildTitle(), 54 | SizedBox(height: 30.0), 55 | buildChooseUserDropdownButton(context), 56 | SizedBox(height: 30.0), 57 | buildCheckBox(context), 58 | buildClientOpenButton(context), 59 | ], 60 | ))); 61 | } 62 | 63 | Padding buildChooseUserDropdownButton(BuildContext context) { 64 | return Padding( 65 | padding: const EdgeInsets.only(top: 8.0), 66 | child: Row( 67 | mainAxisAlignment: MainAxisAlignment.center, 68 | children: [ 69 | Text('ID: '), 70 | InkWell( 71 | child: Row( 72 | mainAxisAlignment: MainAxisAlignment.start, 73 | children: [ 74 | DropdownButton( 75 | value: this._clientID, 76 | onChanged: (String newValue) { 77 | setState(() { 78 | this._clientID = newValue; 79 | }); 80 | }, 81 | items: allClients() 82 | .map>((String value) { 83 | return DropdownMenuItem( 84 | value: value, 85 | child: Text(value), 86 | ); 87 | }).toList(), 88 | ), 89 | ], 90 | ), 91 | ), 92 | ], 93 | ), 94 | ); 95 | } 96 | 97 | Padding buildCheckBox(BuildContext context) { 98 | return Padding( 99 | padding: const EdgeInsets.only(top: 10), 100 | child: Row( 101 | mainAxisAlignment: MainAxisAlignment.center, 102 | children: [ 103 | Checkbox( 104 | value: _checkboxSelected, 105 | activeColor: Colors.blue, //选中时的颜色 106 | onChanged: (value) { 107 | setState(() { 108 | _checkboxSelected = value; 109 | }); 110 | }, 111 | ), 112 | GestureDetector( 113 | child: Text( 114 | '我已阅读并同意使用协议', 115 | style: TextStyle( 116 | color: Colors.blue, 117 | decoration: TextDecoration.underline, 118 | fontSize: 15.0, 119 | ), 120 | ), 121 | onTap: () => showUserProtocolPage(), //点击 122 | ) 123 | ], 124 | ), 125 | ); 126 | } 127 | 128 | Align buildClientOpenButton(BuildContext context) { 129 | return Align( 130 | child: SizedBox( 131 | height: 45.0, 132 | width: 270.0, 133 | child: RaisedButton( 134 | child: Text( 135 | '开始聊天', 136 | style: TextStyle(fontSize: 18.0, color: Colors.white), 137 | ), 138 | color: Colors.blue, 139 | onPressed: () { 140 | if (!_checkboxSelected) { 141 | showToastRed('未同意用户使用协议'); 142 | } else { 143 | userLogin(_clientID); 144 | } 145 | }, 146 | ), 147 | ), 148 | ); 149 | } 150 | 151 | Padding buildTitle() { 152 | return Padding( 153 | padding: EdgeInsets.all(8.0), 154 | child: Align( 155 | alignment: Alignment.center, 156 | child: Text( 157 | 'LeanMessage', 158 | style: TextStyle(fontSize: 26.0, color: Colors.blue), 159 | ), 160 | )); 161 | } 162 | 163 | showUserProtocolPage() { 164 | Navigator.push( 165 | context, 166 | new MaterialPageRoute( 167 | builder: (context) => new UserProtocolPage(), 168 | ), 169 | ); 170 | } 171 | 172 | Future login(String clintID) async { 173 | CurrentClient currentClint = CurrentClient(); 174 | if (clintID != currentClint.client.id) { 175 | currentClint.updateClient(); 176 | } 177 | await currentClint.client.open(); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /lc_realtime/lib/Routes/SelectChatMembers.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:lcrealtime/Common/Global.dart'; 3 | import 'package:leancloud_official_plugin/leancloud_plugin.dart'; 4 | import 'ConversationDetailPage.dart'; 5 | 6 | class SelectChatMembers extends StatefulWidget { 7 | @override 8 | _SelectChatMembersState createState() => new _SelectChatMembersState(); 9 | } 10 | 11 | class _SelectChatMembersState extends State { 12 | List _list = allClients(); 13 | Map _checkboxSelectedList = new Map(); 14 | Set _selectedClientList = new Set(); 15 | 16 | @override 17 | void initState() { 18 | super.initState(); 19 | removeCurrentClient(); 20 | } 21 | removeCurrentClient() { 22 | _list.remove(Global.clientID); 23 | _list.forEach((item) { 24 | //index:_list.indexOf(item) 25 | _checkboxSelectedList[item] = false; 26 | }); 27 | // setState(() {}); 28 | } 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return Scaffold( 33 | appBar: AppBar( 34 | //导航栏 35 | title: Text("选择联系人"), 36 | centerTitle: true, 37 | //导航栏右侧菜单 38 | actions: [ 39 | navRightButton(context), 40 | ]), 41 | body: Container( 42 | child: Column(children: [ 43 | Expanded( 44 | child: ListView.separated( 45 | //添加分割线 46 | separatorBuilder: (BuildContext context, int index) { 47 | return new Divider( 48 | height: 0.8, 49 | color: Colors.grey, 50 | ); 51 | }, 52 | itemCount: _list.length, 53 | // itemExtent: 50.0, //强制高度为50.0 54 | itemBuilder: (BuildContext context, int index) { 55 | return CheckboxListTile( 56 | onChanged: (isCheck) { 57 | setState(() { 58 | _checkboxSelectedList[_list[index]] = isCheck; 59 | }); 60 | }, 61 | selected: false, 62 | value: _checkboxSelectedList[_list[index]], 63 | title: Text(_list[index]), 64 | controlAffinity: ListTileControlAffinity.leading, 65 | ); 66 | }), 67 | ), 68 | ]))); 69 | } 70 | 71 | Align navRightButton(BuildContext context) { 72 | Align content; 73 | content = Align( 74 | alignment: Alignment.center, 75 | child: Padding( 76 | padding: EdgeInsets.only(top: 0.0), 77 | child: RaisedButton( 78 | textColor: Colors.white, 79 | child: Text("完成", style: TextStyle(fontSize: 16.0)), 80 | shape: RoundedRectangleBorder( 81 | borderRadius: BorderRadius.circular(15.0)), 82 | color: Colors.blue, 83 | highlightColor: Colors.blue[900], 84 | colorBrightness: Brightness.dark, 85 | splashColor: Colors.grey, 86 | onPressed: () { 87 | createConversation(); 88 | }), 89 | ), 90 | ); 91 | return content; 92 | } 93 | 94 | void createConversation() async { 95 | _checkboxSelectedList.forEach((key, value) { 96 | if (value == true) { 97 | _selectedClientList.add(key); 98 | } 99 | }); 100 | if (_selectedClientList.length == 0) { 101 | showToastRed('请选择成员!'); 102 | return; 103 | } 104 | 105 | if (Global.clientID != null) { 106 | Client currentClient = Client(id: Global.clientID); 107 | try { 108 | Conversation conversation = await currentClient.createConversation( 109 | isUnique: true, 110 | members: _selectedClientList, 111 | name: Global.clientID + '发起群聊'); 112 | 113 | Navigator.pop(context); 114 | Navigator.push( 115 | context, 116 | new MaterialPageRoute( 117 | builder: (context) => 118 | new ConversationDetailPage(conversation: conversation), 119 | ), 120 | ); 121 | } catch (e) { 122 | showToastRed('创建会话失败:${e.message}'); 123 | } 124 | } else { 125 | showToastRed('用户未登录'); 126 | return; 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /lc_realtime/lib/Routes/UserProtocol.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class UserProtocolPage extends StatefulWidget { 4 | @override 5 | _UserProtocolPageState createState() => new _UserProtocolPageState(); 6 | } 7 | 8 | class _UserProtocolPageState extends State { 9 | @override 10 | void initState() { 11 | super.initState(); 12 | } 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | String str = ''' 17 | 18 | 本软件是一款为移动通信设备提供即时通信的应用软件。用户使用本应用需接受本协议的全部条款。 19 | 20 | 1.您使用本应用的行为必须合法。 21 | 22 | 2.我们将保留基于我们的判断检查用户内容的权利。 23 | 24 | 3.您在应用内上传与发布的内容必须合法,否则您将承担全部法律责任。 25 | 26 | 4.一旦发现您上传的内容非法,我们会删除您账号下的全部数据,并禁止继续访问。 27 | 28 | 5. 禁止发布危害国家安全、泄露国家机密、破坏国家统一的内容。 29 | 30 | 6. 禁止发布煽动民族仇恨、民族歧视,破坏民族团结的内容。 31 | 32 | 7. 禁止发布破坏国家宗教政策,宣扬邪教和封建迷信的内容。 33 | 34 | 8. 禁止发布扰乱社会秩序,破坏社会稳定的谣言。 35 | 36 | 9. 禁止散步淫秽、色情、赌博、暴力、凶杀、恐怖或者教唆犯罪的内容 37 | 38 | 10. 禁止发布侮辱或者诽谤他人,侵害他人合法权益的内容 39 | 40 | 11. 禁止发布含有法律、行政法规禁止的其他内容的。 41 | '''; 42 | return Scaffold( 43 | appBar: AppBar( 44 | //导航栏 45 | title: Text("用户协议"), 46 | centerTitle: true, 47 | ), 48 | body: Scrollbar( 49 | // 显示进度条 50 | child: SingleChildScrollView( 51 | padding: EdgeInsets.all(16.0), 52 | child: Column( 53 | crossAxisAlignment: CrossAxisAlignment.start, 54 | children: [ 55 | Text(str), 56 | ], 57 | 58 | ), 59 | )) 60 | ); 61 | } 62 | } -------------------------------------------------------------------------------- /lc_realtime/lib/States/ChangeNotifier.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ChangeNotifier implements Listenable { 4 | List listeners = []; 5 | @override 6 | void addListener(VoidCallback listener) { 7 | //添加监听器 8 | listeners.add(listener); 9 | } 10 | 11 | @override 12 | void removeListener(VoidCallback listener) { 13 | //移除监听器 14 | listeners.remove(listener); 15 | } 16 | 17 | void notifyListeners() { 18 | //通知所有监听器,触发监听器回调 19 | listeners.forEach((item) => item()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lc_realtime/lib/States/ChangeNotifierProvider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'InheritedWidget.dart'; 3 | 4 | class ChangeNotifierProvider extends StatefulWidget { 5 | ChangeNotifierProvider({ 6 | Key key, 7 | this.data, 8 | this.child, 9 | }); 10 | 11 | final Widget child; 12 | final T data; 13 | 14 | //添加一个listen参数,表示是否建立依赖关系 15 | static T of(BuildContext context, {bool listen = true}) { 16 | // final type = _typeOf>(); 17 | final provider = listen 18 | ? context.dependOnInheritedWidgetOfExactType>() 19 | : context 20 | .getElementForInheritedWidgetOfExactType>() 21 | ?.widget as InheritedProvider; 22 | return provider.data; 23 | } 24 | 25 | @override 26 | _ChangeNotifierProviderState createState() => 27 | _ChangeNotifierProviderState(); 28 | } 29 | 30 | class _ChangeNotifierProviderState 31 | extends State> { 32 | void update() { 33 | //如果数据发生变化(model类调用了notifyListeners),重新构建InheritedProvider 34 | setState(() => {}); 35 | } 36 | 37 | @override 38 | void didUpdateWidget(ChangeNotifierProvider oldWidget) { 39 | //当Provider更新时,如果新旧数据不"==",则解绑旧数据监听,同时添加新数据监听 40 | if (widget.data != oldWidget.data) { 41 | oldWidget.data.removeListener(update); 42 | widget.data.addListener(update); 43 | } 44 | super.didUpdateWidget(oldWidget); 45 | } 46 | 47 | @override 48 | void initState() { 49 | // 给model添加监听器 50 | widget.data.addListener(update); 51 | super.initState(); 52 | } 53 | 54 | @override 55 | void dispose() { 56 | // 移除model的监听器 57 | widget.data.removeListener(update); 58 | super.dispose(); 59 | } 60 | 61 | @override 62 | Widget build(BuildContext context) { 63 | return InheritedProvider( 64 | data: widget.data, 65 | child: widget.child, 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lc_realtime/lib/States/Consumer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'ChangeNotifierProvider.dart'; 3 | 4 | // 这是一个便捷类,会获得当前context和指定数据类型的Provider 5 | class Consumer extends StatelessWidget { 6 | Consumer({ 7 | Key key, 8 | @required this.builder, 9 | this.child, 10 | }) : assert(builder != null), 11 | super(key: key); 12 | 13 | final Widget child; 14 | 15 | final Widget Function(BuildContext context, T value) builder; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return builder( 20 | context, 21 | ChangeNotifierProvider.of(context), //自动获取Model 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lc_realtime/lib/States/ConversationModel.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leancloud_official_plugin/leancloud_plugin.dart'; 3 | 4 | class ConversationModel extends ChangeNotifier { 5 | 6 | //更新消息列表 7 | Future> updateMessageListView(Conversation conversation) async { 8 | List messages; 9 | try { 10 | messages = await conversation.queryMessage( 11 | limit: 100, 12 | ); 13 | print(messages.length); 14 | } catch (e) { 15 | print(e.message); 16 | } 17 | return messages; 18 | } 19 | 20 | void sendNewMessage() { 21 | // 通知监听器(订阅者),重新构建InheritedProvider, 更新状态。 22 | notifyListeners(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lc_realtime/lib/States/GlobalEvent.dart: -------------------------------------------------------------------------------- 1 | //订阅者回调签名 2 | typedef void EventCallback(arg); 3 | 4 | class EventMessage { 5 | //私有构造函数 6 | EventMessage._internal(); 7 | 8 | //保存单例 9 | static EventMessage _singleton = new EventMessage._internal(); 10 | 11 | //工厂构造函数 12 | factory EventMessage()=> _singleton; 13 | 14 | //保存事件订阅者队列,key:事件名(id),value: 对应事件的订阅者队列 15 | var _emap = new Map>(); 16 | 17 | //添加订阅者 18 | void on(eventName, EventCallback f) { 19 | if (eventName == null || f == null) return; 20 | _emap[eventName] ??= new List(); 21 | _emap[eventName].add(f); 22 | } 23 | 24 | //移除订阅者 25 | void off(eventName, [EventCallback f]) { 26 | var list = _emap[eventName]; 27 | if (eventName == null || list == null) return; 28 | if (f == null) { 29 | _emap[eventName] = null; 30 | } else { 31 | list.remove(f); 32 | } 33 | } 34 | 35 | //触发事件,事件触发后该事件所有订阅者会被调用 36 | void emit(eventName, [arg]) { 37 | var list = _emap[eventName]; 38 | if (list == null) return; 39 | int len = list.length - 1; 40 | //反向遍历,防止订阅者在回调中移除自身带来的下标错位 41 | for (var i = len; i > -1; --i) { 42 | list[i](arg); 43 | } 44 | } 45 | } 46 | 47 | //定义一个top-level(全局)变量,页面引入该文件后可以直接使用mess 48 | var mess = new EventMessage(); -------------------------------------------------------------------------------- /lc_realtime/lib/States/InheritedWidget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // 一个通用的InheritedWidget,保存任需要跨组件共享的状态 4 | class InheritedProvider extends InheritedWidget { 5 | InheritedProvider({@required this.data, Widget child}) : super(child: child); 6 | 7 | //共享状态使用泛型 8 | final T data; 9 | 10 | @override 11 | bool updateShouldNotify(InheritedProvider old) { 12 | //在此简单返回true,则每次更新都会调用依赖其的子孙节点的`didChangeDependencies`。 13 | return true; 14 | } 15 | } -------------------------------------------------------------------------------- /lc_realtime/lib/Widgets/InputMessageView.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:typed_data'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:image_picker/image_picker.dart'; 5 | import 'package:lcrealtime/Common/Global.dart'; 6 | import 'package:lcrealtime/States/GlobalEvent.dart'; 7 | import 'package:leancloud_official_plugin/leancloud_plugin.dart'; 8 | import 'package:leancloud_storage/leancloud.dart'; 9 | import 'package:flutter_plugin_record/flutter_plugin_record.dart'; 10 | import 'package:flutter_plugin_record/index.dart'; 11 | 12 | class InputMessageView extends StatefulWidget { 13 | final Conversation conversation; 14 | InputMessageView({Key key, @required this.conversation}) : super(key: key); 15 | @override 16 | _InputMessageViewState createState() => new _InputMessageViewState(); 17 | } 18 | 19 | class _InputMessageViewState extends State { 20 | TextEditingController _messController = new TextEditingController(); 21 | final ImagePicker _imagePicker = ImagePicker(); 22 | 23 | FocusNode myFocusNode; 24 | FlutterPluginRecord recordPlugin; 25 | bool _isShowImageGridView = false; 26 | bool _isShowVoiceIcon = true; 27 | IconData _voiceOrTextIcon; 28 | List _icons = [ 29 | {'name': '照片', 'icon': Icons.photo_library}, 30 | {'name': '拍摄', 'icon': Icons.photo_camera}, 31 | //可以继续添加更多 icons 32 | ]; 33 | 34 | @override 35 | void initState() { 36 | super.initState(); 37 | 38 | myFocusNode = FocusNode(); 39 | mess.on(MyEvent.ScrollviewDidScroll, (arg) { 40 | myFocusNode.unfocus(); // 失去焦点 41 | }); 42 | myFocusNode.addListener(() { 43 | if (myFocusNode.hasFocus) { 44 | setState(() { 45 | _isShowImageGridView = false; 46 | }); 47 | } 48 | }); 49 | _voiceOrTextIcon = Icons.keyboard_voice; 50 | //录音组件 51 | recordPlugin = new FlutterPluginRecord(); 52 | recordPlugin.init(); 53 | // 54 | // /// 开始录制或结束录制的监听 55 | // recordPlugin.response.listen((data) { 56 | // if (data.msg == "onStop") { 57 | // ///结束录制时会返回录制文件的地址方便上传服务器 58 | // print("onStop " + data.path); 59 | // } else if (data.msg == "onStart") { 60 | // print("onStart --"); 61 | // } 62 | // }); 63 | 64 | // mess.on(MyEvent.PlayAudioMessage, (path) { 65 | // showToastGreen('消息正在播放'); 66 | // recordPlugin.playByPath(path,'url'); 67 | // }); 68 | } 69 | 70 | @override 71 | void dispose() { 72 | myFocusNode.dispose(); 73 | super.dispose(); 74 | //取消订阅 75 | mess.off( 76 | MyEvent.ScrollviewDidScroll, 77 | ); 78 | mess.off( 79 | MyEvent.PlayAudioMessage, 80 | ); 81 | recordPlugin.dispose(); 82 | } 83 | 84 | @override 85 | Widget build(BuildContext context) { 86 | return Container( 87 | margin: const EdgeInsets.only(top: 15), 88 | child: Column(children: [ 89 | Container( 90 | child: buildTextField(), 91 | decoration: BoxDecoration(color: Color.fromRGBO(241, 243, 244, 0.9)), 92 | ), 93 | buildImageGridView() 94 | ]), 95 | ); 96 | } 97 | 98 | Widget buildImageGridView() { 99 | if (_isShowImageGridView) { 100 | return Container( 101 | decoration: BoxDecoration(color: Color.fromRGBO(241, 243, 244, 0.9)), 102 | child: GridView.builder( 103 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 104 | crossAxisCount: 4, 105 | crossAxisSpacing: 10.0, 106 | mainAxisSpacing: 10.0, 107 | childAspectRatio: 0.8), 108 | padding: 109 | const EdgeInsets.symmetric(horizontal: 20.0, vertical: 20.0), 110 | scrollDirection: Axis.vertical, 111 | itemCount: _icons.length, 112 | itemBuilder: (BuildContext context, int index) { 113 | return _buildIconButton( 114 | _icons[index]['name'], _icons[index]['icon']); 115 | }), 116 | height: 200, 117 | ); 118 | } else { 119 | return Container( 120 | height: 0, 121 | ); 122 | } 123 | } 124 | 125 | Widget _buildIconButton(String name, IconData icon) { 126 | return Column( 127 | children: [ 128 | GestureDetector( 129 | excludeFromSemantics: true, 130 | onTap: () { 131 | if (name == '照片') { 132 | _onImageButtonPressed(ImageSource.gallery, context: context); 133 | } else if (name == '拍摄') { 134 | _onImageButtonPressed(ImageSource.camera, context: context); 135 | } 136 | }, 137 | child: Container( 138 | width: 60.0, 139 | height: 60.0, 140 | alignment: Alignment.center, 141 | decoration: BoxDecoration( 142 | color: Colors.white, borderRadius: BorderRadius.circular(10.0)), 143 | child: Icon( 144 | icon, 145 | size: 28.0, 146 | ), 147 | ), 148 | ), 149 | Container( 150 | margin: EdgeInsets.only(top: 3.0), 151 | child: Text(name, 152 | style: TextStyle(fontSize: 12.0, color: Colors.grey[600]))) 153 | ], 154 | ); 155 | } 156 | 157 | Widget buildTextField() { 158 | return Container( 159 | alignment: Alignment.center, 160 | child: Row( 161 | children: [ 162 | Container( 163 | child: IconButton( 164 | onPressed: _isShowVoiceIcon 165 | ? voiceButtonPressed 166 | : keyboardButtonPressed, 167 | iconSize: 22.0, 168 | highlightColor: Color(00000000), 169 | focusColor: Color(00000000), 170 | hoverColor: Color(00000000), 171 | icon: Icon( 172 | _voiceOrTextIcon, 173 | // color: Colors.grey, 174 | )), 175 | ), 176 | Container(child: voiceOrTextView()), 177 | Container( 178 | child: IconButton( 179 | onPressed: showImageGirdView, 180 | iconSize: 22.0, 181 | highlightColor: Color(00000000), 182 | focusColor: Color(00000000), 183 | hoverColor: Color(00000000), 184 | icon: Icon( 185 | Icons.add, 186 | // color: Colors.grey, 187 | )), 188 | ), 189 | ], 190 | ), 191 | ); 192 | } 193 | 194 | Widget voiceOrTextView() { 195 | if (_isShowVoiceIcon) { 196 | return Flexible( 197 | child: Container( 198 | margin: const EdgeInsets.only(top: 2, bottom: 2), 199 | child: TextField( 200 | textInputAction: TextInputAction.send, 201 | controller: _messController, 202 | focusNode: myFocusNode, 203 | onEditingComplete: () { 204 | sendTextMessage(); 205 | }, 206 | ), 207 | ), 208 | ); 209 | } else { 210 | return Flexible( 211 | child: Container( 212 | // margin: EdgeInsets.fromLTRB(50, 0, 50, 0), 213 | child: 214 | VoiceWidget(startRecord: startRecord, stopRecord: stopRecord)), 215 | ); 216 | } 217 | } 218 | 219 | void sendVoiceMessage() async {} 220 | 221 | void voiceButtonPressed() { 222 | //TODO 显示语音条 223 | 224 | setState(() { 225 | _isShowImageGridView = false; 226 | _isShowVoiceIcon = false; 227 | _voiceOrTextIcon = Icons.keyboard; 228 | }); 229 | if (myFocusNode.hasFocus) { 230 | myFocusNode.unfocus(); 231 | } 232 | } 233 | 234 | void keyboardButtonPressed() { 235 | setState(() { 236 | _isShowImageGridView = false; 237 | _isShowVoiceIcon = true; 238 | _voiceOrTextIcon = Icons.keyboard_voice; 239 | }); 240 | if (!myFocusNode.hasFocus) { 241 | myFocusNode.requestFocus(); 242 | } 243 | } 244 | 245 | // 点击加号 246 | void showImageGirdView() { 247 | // 监听焦点变化,获得焦点时focusNode.hasFocus 值为true,失去焦点时为false。 248 | if (myFocusNode.hasFocus) { 249 | myFocusNode.unfocus(); 250 | } 251 | setState(() { 252 | _isShowImageGridView = !_isShowImageGridView; 253 | // _isShowVoice = false; 254 | }); 255 | } 256 | 257 | //发消息 258 | // void sendMessage(MyMessageType type) { 259 | // switch (type.index) { 260 | // case 0: 261 | // //TextMessage 文本消息 262 | // 263 | // break; 264 | // case 1: 265 | // //ImageMessage 图像消息 266 | // sendImageMessage(); 267 | // break; 268 | // case 2: 269 | // //AudioMessage 音频消息 270 | // break; 271 | // case 3: 272 | // //VideoMessage 视频消息 273 | // break; 274 | // case 4: 275 | // //FileMessage 普通文件消息(.txt/.doc/.md 等各种) 276 | // break; 277 | // case 5: 278 | // //LocationMessage 地理位置消息 279 | // break; 280 | // default: 281 | // { 282 | // showToastRed('消息类型错误!'); 283 | // } 284 | // break; 285 | // } 286 | // } 287 | void sendTextMessage() async { 288 | if (_messController.text != null && _messController.text != '') { 289 | try { 290 | TextMessage textMessage = TextMessage(); 291 | textMessage.text = _messController.text; 292 | await this.widget.conversation.send(message: textMessage); 293 | // showToastGreen('发送成功'); 294 | mess.emit(MyEvent.NewMessage, textMessage); 295 | _messController.clear(); 296 | myFocusNode.unfocus(); 297 | } catch (e) { 298 | showToastRed(e.toString()); 299 | print(e.toString()); 300 | } 301 | } else { 302 | showToastRed('未输入消息内容'); 303 | return; 304 | } 305 | } 306 | 307 | //发送图片消息 308 | void _onImageButtonPressed(ImageSource source, {BuildContext context}) async { 309 | final _imageFile = await _imagePicker.getImage( 310 | source: source, 311 | maxWidth: MediaQuery.of(context).size.width * 0.4, 312 | maxHeight: MediaQuery.of(context).size.width * 0.6, 313 | imageQuality: 70, 314 | ); 315 | Image image = Image.file(File(_imageFile.path)); 316 | // 预先获取图片信息 317 | double imageHeight = 250; 318 | image.image 319 | .resolve(new ImageConfiguration()) 320 | .addListener(new ImageStreamListener((ImageInfo info, bool _) { 321 | //图片的宽高 info.image.height 322 | print('image.height:---->' + info.image.height.toString()); 323 | print('image.width:---->' + info.image.width.toString()); 324 | imageHeight = info.image.height.toDouble(); 325 | })); 326 | Uint8List bytes = await _imageFile.readAsBytes(); 327 | LCFile file = LCFile.fromBytes('imageMessage.png', bytes); 328 | 329 | await file.save(onProgress: (int count, int total) { 330 | print('$count/$total'); 331 | if (count == total) { 332 | //发消息 333 | sendImageMessage(file.data, imageHeight); 334 | } 335 | }); 336 | } 337 | 338 | void sendImageMessage(Uint8List binaryData, double imageHeight) async { 339 | //上传完成 340 | try { 341 | ImageMessage imageMessage = ImageMessage.from(binaryData: binaryData); 342 | await this.widget.conversation.send(message: imageMessage); 343 | // showToastGreen('发送成功 url:' + imageMessage.url); 344 | //预先显示图片要知道高度 345 | mess.emit(MyEvent.ImageMessageHeight, imageHeight); 346 | 347 | // print('发送成功 url:' + imageMessage.url); 348 | mess.emit(MyEvent.NewMessage, imageMessage); 349 | setState(() { 350 | _messController.clear(); 351 | myFocusNode.unfocus(); 352 | _isShowImageGridView = false; 353 | }); 354 | } catch (e) { 355 | showToastRed(e.toString()); 356 | print(e.toString()); 357 | } 358 | } 359 | 360 | startRecord() { 361 | print("开始录制"); 362 | } 363 | 364 | stopRecord(String path, double audioTimeLength) async { 365 | print("结束束录制"); 366 | print("音频文件位置" + path); 367 | print("音频录制时长" + audioTimeLength.toString()); 368 | LCFile file = await LCFile.fromPath('message.wav', path); 369 | file.addMetaData('duration', audioTimeLength.toInt()); 370 | try { 371 | await file.save(); 372 | print(file.objectId); 373 | sendAudioMessage(file.data); 374 | } catch (e) { 375 | showToastRed(e.toString()); 376 | print(e.toString()); 377 | } 378 | } 379 | 380 | void sendAudioMessage(Uint8List binaryData) async { 381 | try { 382 | //发送消息 383 | AudioMessage audioMessage = AudioMessage.from( 384 | binaryData: binaryData, 385 | format: 'wav', 386 | ); 387 | audioMessage.text = '语音消息'; 388 | await this.widget.conversation.send(message: audioMessage); 389 | mess.emit(MyEvent.NewMessage, audioMessage); 390 | // print('语音消息发送成功'); 391 | setState(() { 392 | _messController.clear(); 393 | myFocusNode.unfocus(); 394 | _isShowImageGridView = false; 395 | }); 396 | } catch (e) { 397 | showToastRed(e.toString()); 398 | print(e.toString()); 399 | } 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /lc_realtime/lib/Widgets/MessageList.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_plugin_record/flutter_plugin_record.dart'; 5 | import 'package:lcrealtime/Common/Global.dart'; 6 | import 'package:lcrealtime/States/GlobalEvent.dart'; 7 | import 'package:leancloud_official_plugin/leancloud_plugin.dart'; 8 | import 'package:leancloud_storage/leancloud.dart'; 9 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 10 | import 'package:lcrealtime/Models/CurrentClient.dart'; 11 | import 'package:scroll_to_index/scroll_to_index.dart'; 12 | import 'package:cached_network_image/cached_network_image.dart'; 13 | 14 | class MessageList extends StatefulWidget { 15 | final Conversation conversation; 16 | final List firstPageMessages; 17 | final Message firstMessage; 18 | 19 | MessageList( 20 | {Key key, 21 | @required this.conversation, 22 | this.firstPageMessages, 23 | this.firstMessage}) 24 | : super(key: key); 25 | @override 26 | _MessageListState createState() => new _MessageListState(); 27 | } 28 | 29 | class _MessageListState extends State { 30 | double _textMessageMaxWidth = 200; 31 | double _imageMessageHeight = 250; 32 | RefreshController _refreshController = 33 | RefreshController(initialRefresh: false); 34 | AutoScrollController _autoScrollController; 35 | 36 | List _showMessageList = List(); 37 | bool _isMessagePositionLeft = false; 38 | CurrentClient currentClint; 39 | bool isImageMessageSendBySelf = false; 40 | 41 | //翻页位置的第一条消息 42 | Message _oldMessage = Message(); 43 | //翻页最后一页长度小于 10 特殊处理 44 | int _lastPageLength = 0; 45 | bool _isNeedScrollToNewPage = false; 46 | 47 | FlutterPluginRecord recordPlugin; 48 | Map _checkboxSelectedList = new Map(); 49 | List _selectList; 50 | Set _selectedReportList = new Set(); 51 | @override 52 | void initState() { 53 | super.initState(); 54 | _selectList = [ 55 | '含有辱骂、人生攻击内容', 56 | '不友善内容' 57 | '垃圾广告内容', 58 | '有害内容', 59 | '违法内容', 60 | '不实内容', 61 | '其他', 62 | ]; 63 | _selectList.forEach((item) { 64 | //index:_list.indexOf(item) 65 | _checkboxSelectedList[item] = false; 66 | }); 67 | _oldMessage = widget.firstMessage; 68 | if (widget.firstPageMessages != null) { 69 | _showMessageList = widget.firstPageMessages; 70 | } 71 | 72 | _autoScrollController = AutoScrollController( 73 | viewportBoundaryGetter: () => 74 | Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom), 75 | axis: Axis.vertical); 76 | 77 | //第一次进来滚到底 78 | if (_showMessageList.length >= 10) { 79 | _scrollToIndex(10); 80 | } else { 81 | _scrollToIndex(_showMessageList.length); 82 | } 83 | 84 | //监听滚动 85 | _autoScrollController.addListener(() { 86 | //通知收起键盘 87 | mess.emit(MyEvent.ScrollviewDidScroll); 88 | }); 89 | 90 | //监听自己发送了新消息 91 | mess.on(MyEvent.NewMessage, (message) { 92 | if (message != null) { 93 | receiveNewMessage(message); 94 | } 95 | }); 96 | //收到新消息 97 | currentClint = CurrentClient(); 98 | currentClint.client.onMessage = ({ 99 | Client client, 100 | Conversation conversation, 101 | Message message, 102 | }) { 103 | if (message != null) { 104 | //用户正在某个对话页面聊天,并在这个对话中收到了消息时,需要将会话标记为已读 105 | if (conversation.id == widget.conversation.id) { 106 | conversation.read(); 107 | receiveNewMessage(message); 108 | } 109 | } 110 | }; 111 | mess.on(MyEvent.ImageMessageHeight, (height) { 112 | _imageMessageHeight = height; 113 | }); 114 | recordPlugin = new FlutterPluginRecord(); 115 | // 初始化 116 | recordPlugin.init(); 117 | recordPlugin.responsePlayStateController.listen((data) { 118 | print("播放路径 " + data.playPath); 119 | print("播放状态 " + data.playState); 120 | }); 121 | } 122 | 123 | void playAudio(String url) { 124 | print('play...'); 125 | recordPlugin.playByPath(url, 'url'); 126 | } 127 | 128 | void receiveNewMessage(Message message) { 129 | if (message is TextMessage) { 130 | double height = calculateTextHeight(getMessageString(message), 14.0, 131 | FontWeight.bold, _textMessageMaxWidth - 16, 100) + 132 | 16 + 133 | 30; 134 | setState(() { 135 | _showMessageList.add(message); 136 | _autoScrollController 137 | .jumpTo(_autoScrollController.position.maxScrollExtent + height); 138 | }); 139 | } 140 | if (message is ImageMessage) { 141 | setState(() { 142 | _showMessageList.add(message); 143 | _autoScrollController.animateTo( 144 | _autoScrollController.position.maxScrollExtent + 145 | _imageMessageHeight, 146 | duration: Duration(milliseconds: 500), 147 | curve: Curves.ease); 148 | }); 149 | } 150 | if (message is AudioMessage) { 151 | double height = 40.0 + 16 + 30; 152 | setState(() { 153 | _showMessageList.add(message); 154 | _autoScrollController 155 | .jumpTo(_autoScrollController.position.maxScrollExtent + height); 156 | }); 157 | } 158 | //收到新消息以后再刷新列表 159 | mess.emit(MyEvent.ConversationRefresh); 160 | } 161 | 162 | Future _scrollToIndex(int index) async { 163 | await _autoScrollController.scrollToIndex(index, 164 | duration: Duration(milliseconds: 100), 165 | preferPosition: AutoScrollPosition.end); 166 | } 167 | 168 | void _onRefresh() async { 169 | _isNeedScrollToNewPage = true; 170 | 171 | if (_showMessageList.length == 0) { 172 | _refreshController.refreshCompleted(); 173 | _isNeedScrollToNewPage = false; 174 | return; 175 | } 176 | //每次查询 10 条消息 177 | try { 178 | // 以上一页的最早的消息作为开始,继续向前拉取消息 179 | List messages2 = await this.widget.conversation.queryMessage( 180 | startTimestamp: _oldMessage.sentTimestamp, 181 | startMessageID: _oldMessage.id, 182 | startClosed: false, 183 | limit: 10, 184 | ); 185 | if (messages2.length == 0) { 186 | _refreshController.refreshCompleted(); 187 | _isNeedScrollToNewPage = false; 188 | return; 189 | } else if (messages2.length < 10) { 190 | _lastPageLength = messages2.length; 191 | } 192 | _oldMessage = messages2.first; 193 | _showMessageList.insertAll(0, messages2); 194 | } catch (e) { 195 | print(e); 196 | } 197 | if (mounted) setState(() {}); 198 | _refreshController.refreshCompleted(); 199 | } 200 | 201 | @override 202 | Widget build(BuildContext context) { 203 | return Expanded( 204 | child: GestureDetector( 205 | //点击页面通知收起键盘 206 | onTap: () => mess.emit(MyEvent.ScrollviewDidScroll), 207 | onDoubleTap: () => mess.emit(MyEvent.ScrollviewDidScroll), 208 | child: Container( 209 | child: SmartRefresher( 210 | enablePullDown: true, 211 | header: CustomHeader( 212 | // completeDuration: Duration(milliseconds: 200), 213 | builder: (context, mode) { 214 | Widget body; 215 | if (mode == RefreshStatus.idle) { 216 | body = Text("pull down refresh"); 217 | } else if (mode == RefreshStatus.refreshing) { 218 | body = CupertinoActivityIndicator(); 219 | } else if (mode == RefreshStatus.canRefresh) { 220 | // body = Text("release to refresh"); 221 | body = CupertinoActivityIndicator(); 222 | } else if (mode == RefreshStatus.completed) { 223 | // body = Text("refreshCompleted!"); 224 | if (_isNeedScrollToNewPage) { 225 | _lastPageLength != 0 226 | ? _scrollToIndex(_lastPageLength) 227 | : _scrollToIndex(10); 228 | } 229 | } 230 | return Container( 231 | height: 60.0, 232 | child: Center( 233 | child: body, 234 | ), 235 | ); 236 | }, 237 | ), 238 | onRefresh: _onRefresh, 239 | controller: _refreshController, 240 | child: ListView.builder( 241 | //根据子组件的总长度来设置ListView的长度 242 | shrinkWrap: true, 243 | physics: const AlwaysScrollableScrollPhysics(), 244 | controller: _autoScrollController, 245 | itemCount: _showMessageList.length, 246 | itemBuilder: (context, index) { 247 | _textMessageMaxWidth = 248 | MediaQuery.of(context).size.width * 0.7; 249 | Message message = _showMessageList[index]; 250 | String fromClientID = message.fromClientID; 251 | // string time = message.sentDate;// 252 | _isMessagePositionLeft = false; 253 | if (fromClientID != currentClint.client.id) { 254 | _isMessagePositionLeft = true; 255 | } 256 | return AutoScrollTag( 257 | key: ValueKey(index), 258 | controller: _autoScrollController, 259 | index: index, 260 | child: Container( 261 | // color: Color(0xfff5f5f5), 262 | padding: const EdgeInsets.all(5), 263 | child: Row( 264 | children: [ 265 | new Expanded( 266 | child: new Column( 267 | crossAxisAlignment: _isMessagePositionLeft 268 | ? CrossAxisAlignment.start 269 | : CrossAxisAlignment.end, 270 | children: [ 271 | GestureDetector( 272 | onLongPress: () { 273 | //TODO:禁言 274 | }, 275 | child: Container( 276 | padding: const EdgeInsets.only( 277 | right: 8, left: 8), 278 | child: new Text( 279 | fromClientID, 280 | style: new TextStyle( 281 | fontWeight: FontWeight.normal, 282 | ), 283 | ), 284 | )), 285 | Padding( 286 | padding: const EdgeInsets.all(2.0), 287 | child: Flex( 288 | direction: Axis.horizontal, 289 | mainAxisAlignment: 290 | _isMessagePositionLeft 291 | ? MainAxisAlignment.start 292 | : MainAxisAlignment.end, 293 | children: [ 294 | typeMessageView(message), 295 | ], 296 | ), 297 | ) 298 | ], 299 | ), 300 | ), 301 | ], 302 | ), 303 | )); 304 | }, 305 | ))))); 306 | } 307 | 308 | //展示不同的消息类型 309 | Widget typeMessageView(Message message) { 310 | if (message is TextMessage) { 311 | return GestureDetector( 312 | onLongPress: () { 313 | showReportDialog(message.id); 314 | }, 315 | child: Container( 316 | padding: const EdgeInsets.all(8.0), 317 | constraints: BoxConstraints( 318 | maxWidth: _textMessageMaxWidth, 319 | ), 320 | decoration: _isMessagePositionLeft 321 | ? BoxDecoration( 322 | color: Colors.blue, 323 | borderRadius: BorderRadius.all( 324 | Radius.circular(12.0), 325 | ), 326 | ) 327 | : BoxDecoration( 328 | color: Colors.grey[300], 329 | borderRadius: BorderRadius.all( 330 | Radius.circular(12.0), 331 | ), 332 | ), 333 | child: new Text( 334 | getMessageString(message), 335 | style: new TextStyle( 336 | fontWeight: FontWeight.bold, 337 | color: _isMessagePositionLeft ? Colors.white : Colors.blue), 338 | ))); 339 | } else if (message is FileMessage) { 340 | if (message is ImageMessage) { 341 | return GestureDetector( 342 | onLongPress: () { 343 | showReportDialog(message.id); 344 | }, 345 | child: Container( 346 | constraints: BoxConstraints( 347 | maxWidth: MediaQuery.of(context).size.width * 0.4, 348 | ), 349 | child: CachedNetworkImage( 350 | imageUrl: message.url, 351 | progressIndicatorBuilder: (context, url, downloadProgress) => 352 | CircularProgressIndicator(value: downloadProgress.progress), 353 | errorWidget: (context, url, error) => Icon(Icons.error), 354 | ), 355 | )); 356 | } else if (message is AudioMessage) { 357 | int duration = message.duration.toInt(); 358 | double width = _textMessageMaxWidth * (duration / 20); 359 | if (duration >= 20) { 360 | width = _textMessageMaxWidth; 361 | } 362 | if (duration <= 3) { 363 | width = _textMessageMaxWidth * (3 / 20); 364 | } 365 | return GestureDetector( 366 | onLongPress: () { 367 | showReportDialog(message.id); 368 | }, 369 | onTap: () { 370 | if (message.url != null) { 371 | // mess.emit(MyEvent.PlayAudioMessage, message.url); 372 | // showToastGreen('消息正在播放'); 373 | // recordPlugin.playByPath(message.url,'url'); 374 | 375 | playAudio(message.url); 376 | } else { 377 | showToastGreen('消息无法播放'); 378 | } 379 | }, 380 | child: Container( 381 | padding: const EdgeInsets.all(8.0), 382 | width: width, 383 | constraints: BoxConstraints( 384 | // maxWidth: width, 385 | ), 386 | decoration: _isMessagePositionLeft 387 | ? BoxDecoration( 388 | color: Colors.blue, 389 | borderRadius: BorderRadius.all( 390 | Radius.circular(12.0), 391 | ), 392 | ) 393 | : BoxDecoration( 394 | color: Colors.grey[300], 395 | borderRadius: BorderRadius.all( 396 | Radius.circular(12.0), 397 | ), 398 | ), 399 | child: new Text( 400 | '${duration.toString()}"', 401 | style: new TextStyle( 402 | fontWeight: FontWeight.bold, 403 | color: 404 | _isMessagePositionLeft ? Colors.white : Colors.blue), 405 | ))); 406 | } 407 | } else { 408 | return Text('暂未支持的消息类型。。。'); 409 | } 410 | } 411 | Future showlacklistDialog(String name) async { 412 | return showDialog( 413 | context: context, 414 | builder: (context) { 415 | return AlertDialog( 416 | title: Text("加入黑名单"), 417 | content: Text("确认拉黑 $name,不再接受来自 $name 的消息吗"), 418 | actions: [ 419 | FlatButton( 420 | child: Text("取消"), 421 | onPressed: () => Navigator.of(context).pop(), // 关闭对话框 422 | ), 423 | FlatButton( 424 | child: Text("确认"), 425 | onPressed: () { 426 | // 427 | 428 | //关闭对话框并返回true 429 | Navigator.of(context).pop(); 430 | }, 431 | ), 432 | ], 433 | ); 434 | }, 435 | ); 436 | } 437 | Future showReportDialog(String messageID) async { 438 | return showDialog( 439 | context: context, 440 | builder: (context) { 441 | return AlertDialog( 442 | title: Text( 443 | "举报", 444 | textAlign: TextAlign.center, 445 | ), 446 | content: 447 | new StatefulBuilder(builder: (context, StateSetter setState) { 448 | return Container( 449 | // padding: const EdgeInsets.only(bottom: 8.0, right: 8, left: 10), 450 | height: MediaQuery.of(context).size.height * 0.6, 451 | width: MediaQuery.of(context).size.width * 0.7, 452 | child: Column( 453 | mainAxisAlignment: MainAxisAlignment.start, 454 | children: [ 455 | Container( 456 | padding: const EdgeInsets.only( 457 | bottom: 15, 458 | ), 459 | child: Text( 460 | '请选择举报理由', 461 | textAlign: TextAlign.center, 462 | style: new TextStyle( 463 | fontWeight: FontWeight.bold, 464 | ), 465 | )), 466 | Expanded( 467 | child: ListView.separated( 468 | //添加分割线 469 | separatorBuilder: (BuildContext context, int index) { 470 | return new Divider( 471 | height: 0.5, 472 | color: Colors.grey, 473 | ); 474 | }, 475 | itemCount: _selectList.length, 476 | // itemExtent: 50.0, //强制高度为50.0 477 | itemBuilder: (BuildContext context, int index) { 478 | return CheckboxListTile( 479 | onChanged: (isCheck) { 480 | setState(() { 481 | _checkboxSelectedList[_selectList[index]] = 482 | isCheck; 483 | }); 484 | }, 485 | selected: false, 486 | value: _checkboxSelectedList[_selectList[index]], 487 | title: Text(_selectList[index], 488 | style: new TextStyle( 489 | fontSize: 12, 490 | )), 491 | controlAffinity: ListTileControlAffinity.leading, 492 | ); 493 | })) 494 | ], 495 | ), 496 | ); 497 | }), 498 | actions: [ 499 | FlatButton( 500 | child: Text("取消"), 501 | onPressed: () => Navigator.of(context).pop(), // 关闭对话框 502 | ), 503 | FlatButton( 504 | child: Text("确认"), 505 | onPressed: () { 506 | saveReports(messageID); 507 | //关闭对话框并返回true 508 | Navigator.of(context).pop(); 509 | }, 510 | ), 511 | ], 512 | ); 513 | }, 514 | ); 515 | } 516 | //TODO:禁言 517 | Future addBlackList(String name)async{ 518 | 519 | } 520 | Future saveReports(String messageID) async { 521 | _checkboxSelectedList.forEach((key, value) { 522 | if (value == true) { 523 | _selectedReportList.add(key); 524 | } 525 | }); 526 | if (_selectedReportList.length == 0) { 527 | showToastRed('请选择举报理由!'); 528 | return; 529 | } 530 | LCObject report = LCObject('Report'); 531 | report['clientID'] = Global.clientID; 532 | report['messageID'] = messageID; 533 | report['conversationID'] = this.widget.conversation.id; 534 | report['content'] = _selectedReportList.toString(); 535 | await report.save(); 536 | showToastGreen('提交成功!'); 537 | } 538 | // 时间显示规则: 539 | // 当天的消息,以每 5 分钟为一个跨度显示时间 540 | // 消息超过 1 天、小于 1 周,显示为「星期 消息发送时间」 541 | // 消息大于 1 周,显示「日期 消息发送时间」 542 | 543 | // calculateTimeVisibility(List list) { 544 | // if (list.isEmpty) { 545 | // return; 546 | // } 547 | // DateTime lastVisiableTime = list.last.sendTime; 548 | // list.last.timeVisility = true; 549 | // 550 | // //倒序遍历 551 | // for (int i = list.length - 1; i >= 0; i--) { 552 | // Message message = list[i]; 553 | //// print( 554 | //// "message.sendTime.difference(DateTime.now()).inDays:${message.sendTime.difference(DateTime.now()).inDays}"); 555 | // 556 | // int diffDays = lastVisiableTime.difference(message.sendTime).inDays; 557 | // if (diffDays == 0) { 558 | // //同一天 559 | // if (lastVisiableTime.difference(message.sendTime).inMinutes < 5) { 560 | // //间隔小于上一次5分钟 561 | // message.timeVisility = false; 562 | // } else { 563 | // //间隔大于上一次5分钟 564 | // lastVisiableTime = message.sendTime; 565 | // message.timeVisility = true; 566 | // } 567 | // } else if (diffDays < 7) { 568 | // //超过1天、小于1周 569 | // message.timeVisility = true; 570 | // lastVisiableTime = message.sendTime; 571 | // } else { 572 | // //消息大于1周 573 | // message.timeVisility = true; 574 | // lastVisiableTime = message.sendTime; 575 | // } 576 | // } 577 | // } 578 | 579 | ///value: 文本内容;fontSize : 文字的大小;fontWeight:文字权重;maxWidth:文本框的最大宽度;maxLines:文本支持最大多少行 580 | static double calculateTextHeight(String value, fontSize, 581 | FontWeight fontWeight, double maxWidth, int maxLines) { 582 | TextPainter painter = TextPainter( 583 | maxLines: maxLines, 584 | textDirection: TextDirection.ltr, 585 | text: TextSpan( 586 | text: value, 587 | style: TextStyle( 588 | fontWeight: fontWeight, 589 | fontSize: fontSize, 590 | ))); 591 | painter.layout(maxWidth: maxWidth); 592 | 593 | ///文字的宽度:painter.width 594 | return painter.height; 595 | } 596 | 597 | @override 598 | void dispose() { 599 | super.dispose(); 600 | recordPlugin.dispose(); 601 | //取消订阅 602 | mess.off(MyEvent.NewMessage); 603 | mess.off(MyEvent.ImageMessageHeight); 604 | // mess.off(MyEvent.EditingMessage); 605 | } 606 | 607 | @override 608 | void deactivate() async { 609 | print('结束'); 610 | // int result = await audioPlayer.release(); 611 | // if (result == 1) { 612 | // print('release success'); 613 | // } else { 614 | // print('release failed'); 615 | // } 616 | super.deactivate(); 617 | recordPlugin.stopPlay(); 618 | recordPlugin.dispose(); 619 | } 620 | } 621 | -------------------------------------------------------------------------------- /lc_realtime/lib/Widgets/TextWidget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // 封装的文本组件Widget 4 | class TextWidget extends StatefulWidget { 5 | final Key key; 6 | // final int count; 7 | 8 | // 接收一个Key 9 | TextWidget(this.key); 10 | @override 11 | State createState() => TextWidgetState(); 12 | } 13 | 14 | class TextWidgetState extends State { 15 | static int _count; 16 | 17 | @override 18 | void initState() { 19 | super.initState(); 20 | _count = 0; 21 | } 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | if (_count > 0) { 26 | String showNum = ''; 27 | if (_count < 10) { 28 | showNum = ''' ''' + _count.toString() + ''' '''; 29 | } else { 30 | showNum = _count.toString(); 31 | } 32 | return Text( 33 | showNum, 34 | style: TextStyle( 35 | color: Colors.white, 36 | fontSize: 12.0, 37 | ), 38 | ); 39 | } else { 40 | return Container( 41 | height: 0, 42 | ); 43 | } 44 | } 45 | 46 | void onPressed(int unreadCount) { 47 | setState(() => _count = unreadCount); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lc_realtime/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'Common/Global.dart'; 3 | import 'package:lcrealtime/routes/LoginPage.dart'; 4 | import 'package:flutter_localizations/flutter_localizations.dart'; 5 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 6 | 7 | void main() { 8 | WidgetsFlutterBinding.ensureInitialized(); 9 | 10 | Global.init().then((e) => runApp(MyApp())); 11 | } 12 | 13 | class MyApp extends StatelessWidget { 14 | MyApp({Key key}) : super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return MaterialApp( 19 | localizationsDelegates: [ 20 | RefreshLocalizations.delegate, 21 | GlobalMaterialLocalizations.delegate, 22 | GlobalWidgetsLocalizations.delegate, 23 | ], 24 | supportedLocales: [ 25 | const Locale.fromSubtags(languageCode: 'zh'), 26 | const Locale.fromSubtags(languageCode: 'en'), 27 | ], 28 | home:LoginPage(), 29 | locale: Locale('zh'), 30 | ); 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /lc_realtime/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "22.0.0" 11 | analyzer: 12 | dependency: transitive 13 | description: 14 | name: analyzer 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.7.2" 18 | args: 19 | dependency: transitive 20 | description: 21 | name: args 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.3.0" 25 | async: 26 | dependency: transitive 27 | description: 28 | name: async 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.8.2" 32 | audioplayers: 33 | dependency: "direct main" 34 | description: 35 | name: audioplayers 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "0.20.1" 39 | boolean_selector: 40 | dependency: transitive 41 | description: 42 | name: boolean_selector 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "2.1.0" 46 | build: 47 | dependency: transitive 48 | description: 49 | name: build 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.1.1" 53 | build_config: 54 | dependency: transitive 55 | description: 56 | name: build_config 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.0.0" 60 | cached_network_image: 61 | dependency: "direct main" 62 | description: 63 | name: cached_network_image 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "3.2.0" 67 | cached_network_image_platform_interface: 68 | dependency: transitive 69 | description: 70 | name: cached_network_image_platform_interface 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "1.0.0" 74 | cached_network_image_web: 75 | dependency: transitive 76 | description: 77 | name: cached_network_image_web 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "1.0.1" 81 | characters: 82 | dependency: transitive 83 | description: 84 | name: characters 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "1.2.0" 88 | charcode: 89 | dependency: transitive 90 | description: 91 | name: charcode 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "1.3.1" 95 | checked_yaml: 96 | dependency: transitive 97 | description: 98 | name: checked_yaml 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "2.0.1" 102 | cli_util: 103 | dependency: transitive 104 | description: 105 | name: cli_util 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "0.3.5" 109 | clock: 110 | dependency: transitive 111 | description: 112 | name: clock 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "1.1.0" 116 | collection: 117 | dependency: transitive 118 | description: 119 | name: collection 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "1.15.0" 123 | convert: 124 | dependency: transitive 125 | description: 126 | name: convert 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "3.0.1" 130 | cross_file: 131 | dependency: transitive 132 | description: 133 | name: cross_file 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "0.3.2" 137 | crypto: 138 | dependency: transitive 139 | description: 140 | name: crypto 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "3.0.1" 144 | cupertino_icons: 145 | dependency: "direct main" 146 | description: 147 | name: cupertino_icons 148 | url: "https://pub.dartlang.org" 149 | source: hosted 150 | version: "0.1.3" 151 | dart_style: 152 | dependency: transitive 153 | description: 154 | name: dart_style 155 | url: "https://pub.dartlang.org" 156 | source: hosted 157 | version: "2.1.1" 158 | dio: 159 | dependency: transitive 160 | description: 161 | name: dio 162 | url: "https://pub.dartlang.org" 163 | source: hosted 164 | version: "4.0.4" 165 | dio_http_cache: 166 | dependency: transitive 167 | description: 168 | name: dio_http_cache 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "0.3.0" 172 | fake_async: 173 | dependency: transitive 174 | description: 175 | name: fake_async 176 | url: "https://pub.dartlang.org" 177 | source: hosted 178 | version: "1.2.0" 179 | ffi: 180 | dependency: transitive 181 | description: 182 | name: ffi 183 | url: "https://pub.dartlang.org" 184 | source: hosted 185 | version: "1.1.2" 186 | file: 187 | dependency: transitive 188 | description: 189 | name: file 190 | url: "https://pub.dartlang.org" 191 | source: hosted 192 | version: "6.1.2" 193 | flutter: 194 | dependency: "direct main" 195 | description: flutter 196 | source: sdk 197 | version: "0.0.0" 198 | flutter_blurhash: 199 | dependency: transitive 200 | description: 201 | name: flutter_blurhash 202 | url: "https://pub.dartlang.org" 203 | source: hosted 204 | version: "0.6.0" 205 | flutter_cache_manager: 206 | dependency: transitive 207 | description: 208 | name: flutter_cache_manager 209 | url: "https://pub.dartlang.org" 210 | source: hosted 211 | version: "3.3.0" 212 | flutter_localizations: 213 | dependency: "direct main" 214 | description: flutter 215 | source: sdk 216 | version: "0.0.0" 217 | flutter_plugin_android_lifecycle: 218 | dependency: transitive 219 | description: 220 | name: flutter_plugin_android_lifecycle 221 | url: "https://pub.dartlang.org" 222 | source: hosted 223 | version: "2.0.5" 224 | flutter_plugin_record: 225 | dependency: "direct main" 226 | description: 227 | name: flutter_plugin_record 228 | url: "https://pub.dartlang.org" 229 | source: hosted 230 | version: "1.0.1" 231 | flutter_test: 232 | dependency: "direct dev" 233 | description: flutter 234 | source: sdk 235 | version: "0.0.0" 236 | flutter_web_plugins: 237 | dependency: transitive 238 | description: flutter 239 | source: sdk 240 | version: "0.0.0" 241 | fluttertoast: 242 | dependency: "direct main" 243 | description: 244 | name: fluttertoast 245 | url: "https://pub.dartlang.org" 246 | source: hosted 247 | version: "8.0.8" 248 | glob: 249 | dependency: transitive 250 | description: 251 | name: glob 252 | url: "https://pub.dartlang.org" 253 | source: hosted 254 | version: "2.0.2" 255 | http: 256 | dependency: transitive 257 | description: 258 | name: http 259 | url: "https://pub.dartlang.org" 260 | source: hosted 261 | version: "0.13.4" 262 | http_parser: 263 | dependency: transitive 264 | description: 265 | name: http_parser 266 | url: "https://pub.dartlang.org" 267 | source: hosted 268 | version: "4.0.0" 269 | image_picker: 270 | dependency: "direct main" 271 | description: 272 | name: image_picker 273 | url: "https://pub.dartlang.org" 274 | source: hosted 275 | version: "0.8.4+4" 276 | image_picker_for_web: 277 | dependency: transitive 278 | description: 279 | name: image_picker_for_web 280 | url: "https://pub.dartlang.org" 281 | source: hosted 282 | version: "2.1.4" 283 | image_picker_platform_interface: 284 | dependency: transitive 285 | description: 286 | name: image_picker_platform_interface 287 | url: "https://pub.dartlang.org" 288 | source: hosted 289 | version: "2.4.1" 290 | intl: 291 | dependency: transitive 292 | description: 293 | name: intl 294 | url: "https://pub.dartlang.org" 295 | source: hosted 296 | version: "0.17.0" 297 | js: 298 | dependency: transitive 299 | description: 300 | name: js 301 | url: "https://pub.dartlang.org" 302 | source: hosted 303 | version: "0.6.3" 304 | json_annotation: 305 | dependency: transitive 306 | description: 307 | name: json_annotation 308 | url: "https://pub.dartlang.org" 309 | source: hosted 310 | version: "4.1.0" 311 | json_serializable: 312 | dependency: transitive 313 | description: 314 | name: json_serializable 315 | url: "https://pub.dartlang.org" 316 | source: hosted 317 | version: "4.1.4" 318 | leancloud_official_plugin: 319 | dependency: "direct main" 320 | description: 321 | name: leancloud_official_plugin 322 | url: "https://pub.dartlang.org" 323 | source: hosted 324 | version: "1.0.0" 325 | leancloud_storage: 326 | dependency: "direct main" 327 | description: 328 | name: leancloud_storage 329 | url: "https://pub.dartlang.org" 330 | source: hosted 331 | version: "0.7.7" 332 | logging: 333 | dependency: transitive 334 | description: 335 | name: logging 336 | url: "https://pub.dartlang.org" 337 | source: hosted 338 | version: "1.0.2" 339 | matcher: 340 | dependency: transitive 341 | description: 342 | name: matcher 343 | url: "https://pub.dartlang.org" 344 | source: hosted 345 | version: "0.12.11" 346 | meta: 347 | dependency: transitive 348 | description: 349 | name: meta 350 | url: "https://pub.dartlang.org" 351 | source: hosted 352 | version: "1.7.0" 353 | octo_image: 354 | dependency: transitive 355 | description: 356 | name: octo_image 357 | url: "https://pub.dartlang.org" 358 | source: hosted 359 | version: "1.0.1" 360 | package_config: 361 | dependency: transitive 362 | description: 363 | name: package_config 364 | url: "https://pub.dartlang.org" 365 | source: hosted 366 | version: "2.0.2" 367 | path: 368 | dependency: transitive 369 | description: 370 | name: path 371 | url: "https://pub.dartlang.org" 372 | source: hosted 373 | version: "1.8.0" 374 | path_provider: 375 | dependency: transitive 376 | description: 377 | name: path_provider 378 | url: "https://pub.dartlang.org" 379 | source: hosted 380 | version: "2.0.8" 381 | path_provider_android: 382 | dependency: transitive 383 | description: 384 | name: path_provider_android 385 | url: "https://pub.dartlang.org" 386 | source: hosted 387 | version: "2.0.9" 388 | path_provider_ios: 389 | dependency: transitive 390 | description: 391 | name: path_provider_ios 392 | url: "https://pub.dartlang.org" 393 | source: hosted 394 | version: "2.0.7" 395 | path_provider_linux: 396 | dependency: transitive 397 | description: 398 | name: path_provider_linux 399 | url: "https://pub.dartlang.org" 400 | source: hosted 401 | version: "2.1.4" 402 | path_provider_macos: 403 | dependency: transitive 404 | description: 405 | name: path_provider_macos 406 | url: "https://pub.dartlang.org" 407 | source: hosted 408 | version: "2.0.4" 409 | path_provider_platform_interface: 410 | dependency: transitive 411 | description: 412 | name: path_provider_platform_interface 413 | url: "https://pub.dartlang.org" 414 | source: hosted 415 | version: "2.0.1" 416 | path_provider_windows: 417 | dependency: transitive 418 | description: 419 | name: path_provider_windows 420 | url: "https://pub.dartlang.org" 421 | source: hosted 422 | version: "2.0.4" 423 | pedantic: 424 | dependency: transitive 425 | description: 426 | name: pedantic 427 | url: "https://pub.dartlang.org" 428 | source: hosted 429 | version: "1.11.1" 430 | platform: 431 | dependency: transitive 432 | description: 433 | name: platform 434 | url: "https://pub.dartlang.org" 435 | source: hosted 436 | version: "3.1.0" 437 | plugin_platform_interface: 438 | dependency: transitive 439 | description: 440 | name: plugin_platform_interface 441 | url: "https://pub.dartlang.org" 442 | source: hosted 443 | version: "2.0.2" 444 | process: 445 | dependency: transitive 446 | description: 447 | name: process 448 | url: "https://pub.dartlang.org" 449 | source: hosted 450 | version: "4.2.4" 451 | pub_semver: 452 | dependency: transitive 453 | description: 454 | name: pub_semver 455 | url: "https://pub.dartlang.org" 456 | source: hosted 457 | version: "2.1.0" 458 | pubspec_parse: 459 | dependency: transitive 460 | description: 461 | name: pubspec_parse 462 | url: "https://pub.dartlang.org" 463 | source: hosted 464 | version: "1.1.0" 465 | pull_to_refresh: 466 | dependency: "direct main" 467 | description: 468 | name: pull_to_refresh 469 | url: "https://pub.dartlang.org" 470 | source: hosted 471 | version: "2.0.0" 472 | quiver: 473 | dependency: transitive 474 | description: 475 | name: quiver 476 | url: "https://pub.dartlang.org" 477 | source: hosted 478 | version: "3.0.1+1" 479 | rxdart: 480 | dependency: transitive 481 | description: 482 | name: rxdart 483 | url: "https://pub.dartlang.org" 484 | source: hosted 485 | version: "0.27.3" 486 | scroll_to_index: 487 | dependency: "direct main" 488 | description: 489 | name: scroll_to_index 490 | url: "https://pub.dartlang.org" 491 | source: hosted 492 | version: "2.1.1" 493 | shared_preferences: 494 | dependency: "direct main" 495 | description: 496 | name: shared_preferences 497 | url: "https://pub.dartlang.org" 498 | source: hosted 499 | version: "2.0.11" 500 | shared_preferences_android: 501 | dependency: transitive 502 | description: 503 | name: shared_preferences_android 504 | url: "https://pub.dartlang.org" 505 | source: hosted 506 | version: "2.0.9" 507 | shared_preferences_ios: 508 | dependency: transitive 509 | description: 510 | name: shared_preferences_ios 511 | url: "https://pub.dartlang.org" 512 | source: hosted 513 | version: "2.0.8" 514 | shared_preferences_linux: 515 | dependency: transitive 516 | description: 517 | name: shared_preferences_linux 518 | url: "https://pub.dartlang.org" 519 | source: hosted 520 | version: "2.0.3" 521 | shared_preferences_macos: 522 | dependency: transitive 523 | description: 524 | name: shared_preferences_macos 525 | url: "https://pub.dartlang.org" 526 | source: hosted 527 | version: "2.0.2" 528 | shared_preferences_platform_interface: 529 | dependency: transitive 530 | description: 531 | name: shared_preferences_platform_interface 532 | url: "https://pub.dartlang.org" 533 | source: hosted 534 | version: "2.0.0" 535 | shared_preferences_web: 536 | dependency: transitive 537 | description: 538 | name: shared_preferences_web 539 | url: "https://pub.dartlang.org" 540 | source: hosted 541 | version: "2.0.2" 542 | shared_preferences_windows: 543 | dependency: transitive 544 | description: 545 | name: shared_preferences_windows 546 | url: "https://pub.dartlang.org" 547 | source: hosted 548 | version: "2.0.3" 549 | sky_engine: 550 | dependency: transitive 551 | description: flutter 552 | source: sdk 553 | version: "0.0.99" 554 | source_gen: 555 | dependency: transitive 556 | description: 557 | name: source_gen 558 | url: "https://pub.dartlang.org" 559 | source: hosted 560 | version: "1.0.3" 561 | source_span: 562 | dependency: transitive 563 | description: 564 | name: source_span 565 | url: "https://pub.dartlang.org" 566 | source: hosted 567 | version: "1.8.1" 568 | sqflite: 569 | dependency: transitive 570 | description: 571 | name: sqflite 572 | url: "https://pub.dartlang.org" 573 | source: hosted 574 | version: "2.0.1" 575 | sqflite_common: 576 | dependency: transitive 577 | description: 578 | name: sqflite_common 579 | url: "https://pub.dartlang.org" 580 | source: hosted 581 | version: "2.1.0" 582 | stack_trace: 583 | dependency: transitive 584 | description: 585 | name: stack_trace 586 | url: "https://pub.dartlang.org" 587 | source: hosted 588 | version: "1.10.0" 589 | stream_channel: 590 | dependency: transitive 591 | description: 592 | name: stream_channel 593 | url: "https://pub.dartlang.org" 594 | source: hosted 595 | version: "2.1.0" 596 | string_scanner: 597 | dependency: transitive 598 | description: 599 | name: string_scanner 600 | url: "https://pub.dartlang.org" 601 | source: hosted 602 | version: "1.1.0" 603 | synchronized: 604 | dependency: transitive 605 | description: 606 | name: synchronized 607 | url: "https://pub.dartlang.org" 608 | source: hosted 609 | version: "3.0.0" 610 | term_glyph: 611 | dependency: transitive 612 | description: 613 | name: term_glyph 614 | url: "https://pub.dartlang.org" 615 | source: hosted 616 | version: "1.2.0" 617 | test_api: 618 | dependency: transitive 619 | description: 620 | name: test_api 621 | url: "https://pub.dartlang.org" 622 | source: hosted 623 | version: "0.4.3" 624 | typed_data: 625 | dependency: transitive 626 | description: 627 | name: typed_data 628 | url: "https://pub.dartlang.org" 629 | source: hosted 630 | version: "1.3.0" 631 | uuid: 632 | dependency: transitive 633 | description: 634 | name: uuid 635 | url: "https://pub.dartlang.org" 636 | source: hosted 637 | version: "3.0.5" 638 | vector_math: 639 | dependency: transitive 640 | description: 641 | name: vector_math 642 | url: "https://pub.dartlang.org" 643 | source: hosted 644 | version: "2.1.1" 645 | watcher: 646 | dependency: transitive 647 | description: 648 | name: watcher 649 | url: "https://pub.dartlang.org" 650 | source: hosted 651 | version: "1.0.1" 652 | win32: 653 | dependency: transitive 654 | description: 655 | name: win32 656 | url: "https://pub.dartlang.org" 657 | source: hosted 658 | version: "2.3.1" 659 | xdg_directories: 660 | dependency: transitive 661 | description: 662 | name: xdg_directories 663 | url: "https://pub.dartlang.org" 664 | source: hosted 665 | version: "0.2.0" 666 | yaml: 667 | dependency: transitive 668 | description: 669 | name: yaml 670 | url: "https://pub.dartlang.org" 671 | source: hosted 672 | version: "3.1.0" 673 | sdks: 674 | dart: ">=2.15.0 <3.0.0" 675 | flutter: ">=2.5.0" 676 | -------------------------------------------------------------------------------- /lc_realtime/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: lcrealtime 2 | description: A new Flutter application. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.0.0+1 15 | 16 | environment: 17 | sdk: ">=2.2.0 <3.0.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | 23 | # The following adds the Cupertino Icons font to your application. 24 | # Use with the CupertinoIcons class for iOS style icons. 25 | cupertino_icons: ^0.1.2 26 | leancloud_official_plugin: ^1.0.0 27 | leancloud_storage: ^0.7.7 28 | fluttertoast: ^8.0.0 29 | shared_preferences: ^2.0.11 30 | pull_to_refresh: ^2.0.0 31 | scroll_to_index: ^2.1.1 32 | flutter_plugin_record: ^1.0.1 33 | image_picker: ^0.8.4+4 34 | cached_network_image: ^3.2.0 35 | audioplayers: ^0.20.1 36 | flutter_localizations: 37 | sdk: flutter 38 | 39 | dev_dependencies: 40 | flutter_test: 41 | sdk: flutter 42 | 43 | # For information on the generic Dart part of this file, see the 44 | # following page: https://dart.dev/tools/pub/pubspec 45 | 46 | # The following section is specific to Flutter. 47 | flutter: 48 | 49 | # The following line ensures that the Material Icons font is 50 | # included with your application, so that you can use the icons in 51 | # the material Icons class. 52 | uses-material-design: true 53 | 54 | # To add assets to your application, add an assets section, like this: 55 | assets: 56 | - assets/download_fail.png 57 | 58 | # An image asset can refer to one or more resolution-specific "variants", see 59 | # https://flutter.dev/assets-and-images/#resolution-aware. 60 | 61 | # For details regarding adding assets from package dependencies, see 62 | # https://flutter.dev/assets-and-images/#from-packages 63 | 64 | # To add custom fonts to your application, add a fonts section here, 65 | # in this "flutter" section. Each entry in this list should have a 66 | # "family" key with the font family name, and a "fonts" key with a 67 | # list giving the asset and other descriptors for the font. For 68 | # example: 69 | # fonts: 70 | # - family: Schyler 71 | # fonts: 72 | # - asset: fonts/Schyler-Regular.ttf 73 | # - asset: fonts/Schyler-Italic.ttf 74 | # style: italic 75 | # - family: Trajan Pro 76 | # fonts: 77 | # - asset: fonts/TrajanPro.ttf 78 | # - asset: fonts/TrajanPro_Bold.ttf 79 | # weight: 700 80 | # 81 | # For details regarding fonts from package dependencies, 82 | # see https://flutter.dev/custom-fonts/#from-packages 83 | -------------------------------------------------------------------------------- /lc_realtime/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:lcrealtime/main.dart'; 12 | 13 | void main() { 14 | WidgetsFlutterBinding.ensureInitialized(); 15 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 16 | // Build our app and trigger a frame. 17 | await tester.pumpWidget(MyApp()); 18 | 19 | // Verify that our counter starts at 0. 20 | expect(find.text('0'), findsOneWidget); 21 | expect(find.text('1'), findsNothing); 22 | 23 | // Tap the '+' icon and trigger a frame. 24 | await tester.tap(find.byIcon(Icons.add)); 25 | await tester.pump(); 26 | 27 | // Verify that our counter has incremented. 28 | expect(find.text('0'), findsNothing); 29 | expect(find.text('1'), findsOneWidget); 30 | }); 31 | } 32 | --------------------------------------------------------------------------------