├── .DS_Store ├── .idea ├── .name ├── codeStyles │ └── Project.xml └── libraries │ ├── Dart_Packages.xml │ ├── Dart_SDK.xml │ └── Flutter_Plugins.xml ├── README.md ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── imclient │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets └── images │ ├── icon_female.png │ └── icon_male.png ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── 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 ├── lib ├── config.dart ├── core │ ├── Account.dart │ ├── Codes.dart │ └── IMClient.dart ├── env │ └── ServerConfig.dart ├── im │ ├── Action.dart │ ├── AutoLogin.dart │ ├── FriendDataCache.dart │ ├── GetFriendListAction.dart │ ├── ImMessage.dart │ ├── LoginAction.dart │ └── LoginOutAction.dart ├── main.dart ├── model │ ├── AuthBaseBean.dart │ ├── Codec.dart │ ├── Friend.dart │ ├── IMMessage.dart │ ├── Login.dart │ ├── Msg.dart │ ├── Person.dart │ ├── RecipeAck.dart │ ├── RecipeMsg.dart │ └── bytebuf.dart ├── page │ ├── BaseState.dart │ ├── ContactsPage.dart │ ├── FriendCardPage.dart │ ├── LoginPage.dart │ ├── MainPage.dart │ ├── MessagePage.dart │ ├── SessionPage.dart │ ├── SettingPage.dart │ └── pages.dart └── util │ ├── GenUtil.dart │ ├── LruCache.dart │ ├── TextUtil.dart │ └── TimeUtil.dart ├── pubspec.lock ├── pubspec.yaml └── test └── widget_test.dart /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siwangqishiq/FlutterIM/89bcbacd6123bbcad608f0d9b171b56ca7509c80/.DS_Store -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | imserver -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /.idea/libraries/Dart_Packages.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /.idea/libraries/Dart_SDK.xml: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /.idea/libraries/Flutter_Plugins.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # imclient 2 | 3 | A new Flutter project. 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 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "panyi.xyz.flutterim" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | } 47 | 48 | buildTypes { 49 | release { 50 | // TODO: Add your own signing config for the release build. 51 | // Signing with the debug keys for now, so `flutter run --release` works. 52 | signingConfig signingConfigs.debug 53 | } 54 | } 55 | } 56 | 57 | flutter { 58 | source '../..' 59 | } 60 | 61 | dependencies { 62 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 63 | } 64 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 11 | 18 | 22 | 26 | 31 | 35 | 36 | 37 | 38 | 39 | 40 | 42 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/imclient/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.imclient 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siwangqishiq/FlutterIM/89bcbacd6123bbcad608f0d9b171b56ca7509c80/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siwangqishiq/FlutterIM/89bcbacd6123bbcad608f0d9b171b56ca7509c80/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siwangqishiq/FlutterIM/89bcbacd6123bbcad608f0d9b171b56ca7509c80/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | include ':app' 6 | 7 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 8 | def properties = new Properties() 9 | 10 | assert localPropertiesFile.exists() 11 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 12 | 13 | def flutterSdkPath = properties.getProperty("flutter.sdk") 14 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 15 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 16 | -------------------------------------------------------------------------------- /assets/images/icon_female.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siwangqishiq/FlutterIM/89bcbacd6123bbcad608f0d9b171b56ca7509c80/assets/images/icon_female.png -------------------------------------------------------------------------------- /assets/images/icon_male.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siwangqishiq/FlutterIM/89bcbacd6123bbcad608f0d9b171b56ca7509c80/assets/images/icon_male.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /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 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXCopyFilesBuildPhase section */ 19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 20 | isa = PBXCopyFilesBuildPhase; 21 | buildActionMask = 2147483647; 22 | dstPath = ""; 23 | dstSubfolderSpec = 10; 24 | files = ( 25 | ); 26 | name = "Embed Frameworks"; 27 | runOnlyForDeploymentPostprocessing = 0; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 9740EEB11CF90186004384FC /* Flutter */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 65 | ); 66 | name = Flutter; 67 | sourceTree = ""; 68 | }; 69 | 97C146E51CF9000F007C117D = { 70 | isa = PBXGroup; 71 | children = ( 72 | 9740EEB11CF90186004384FC /* Flutter */, 73 | 97C146F01CF9000F007C117D /* Runner */, 74 | 97C146EF1CF9000F007C117D /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | 97C146EF1CF9000F007C117D /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 97C146EE1CF9000F007C117D /* Runner.app */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | 97C146F01CF9000F007C117D /* Runner */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 92 | 97C147021CF9000F007C117D /* Info.plist */, 93 | 97C146F11CF9000F007C117D /* Supporting Files */, 94 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 95 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 96 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 97 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 98 | ); 99 | path = Runner; 100 | sourceTree = ""; 101 | }; 102 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | ); 106 | name = "Supporting Files"; 107 | sourceTree = ""; 108 | }; 109 | /* End PBXGroup section */ 110 | 111 | /* Begin PBXNativeTarget section */ 112 | 97C146ED1CF9000F007C117D /* Runner */ = { 113 | isa = PBXNativeTarget; 114 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 115 | buildPhases = ( 116 | 9740EEB61CF901F6004384FC /* Run Script */, 117 | 97C146EA1CF9000F007C117D /* Sources */, 118 | 97C146EB1CF9000F007C117D /* Frameworks */, 119 | 97C146EC1CF9000F007C117D /* Resources */, 120 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 121 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 122 | ); 123 | buildRules = ( 124 | ); 125 | dependencies = ( 126 | ); 127 | name = Runner; 128 | productName = Runner; 129 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 130 | productType = "com.apple.product-type.application"; 131 | }; 132 | /* End PBXNativeTarget section */ 133 | 134 | /* Begin PBXProject section */ 135 | 97C146E61CF9000F007C117D /* Project object */ = { 136 | isa = PBXProject; 137 | attributes = { 138 | LastUpgradeCheck = 1020; 139 | ORGANIZATIONNAME = ""; 140 | TargetAttributes = { 141 | 97C146ED1CF9000F007C117D = { 142 | CreatedOnToolsVersion = 7.3.1; 143 | LastSwiftMigration = 1100; 144 | }; 145 | }; 146 | }; 147 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 148 | compatibilityVersion = "Xcode 9.3"; 149 | developmentRegion = en; 150 | hasScannedForEncodings = 0; 151 | knownRegions = ( 152 | en, 153 | Base, 154 | ); 155 | mainGroup = 97C146E51CF9000F007C117D; 156 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 157 | projectDirPath = ""; 158 | projectRoot = ""; 159 | targets = ( 160 | 97C146ED1CF9000F007C117D /* Runner */, 161 | ); 162 | }; 163 | /* End PBXProject section */ 164 | 165 | /* Begin PBXResourcesBuildPhase section */ 166 | 97C146EC1CF9000F007C117D /* Resources */ = { 167 | isa = PBXResourcesBuildPhase; 168 | buildActionMask = 2147483647; 169 | files = ( 170 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 171 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 172 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 173 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 174 | ); 175 | runOnlyForDeploymentPostprocessing = 0; 176 | }; 177 | /* End PBXResourcesBuildPhase section */ 178 | 179 | /* Begin PBXShellScriptBuildPhase section */ 180 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 181 | isa = PBXShellScriptBuildPhase; 182 | buildActionMask = 2147483647; 183 | files = ( 184 | ); 185 | inputPaths = ( 186 | ); 187 | name = "Thin Binary"; 188 | outputPaths = ( 189 | ); 190 | runOnlyForDeploymentPostprocessing = 0; 191 | shellPath = /bin/sh; 192 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 193 | }; 194 | 9740EEB61CF901F6004384FC /* Run Script */ = { 195 | isa = PBXShellScriptBuildPhase; 196 | buildActionMask = 2147483647; 197 | files = ( 198 | ); 199 | inputPaths = ( 200 | ); 201 | name = "Run Script"; 202 | outputPaths = ( 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | shellPath = /bin/sh; 206 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 207 | }; 208 | /* End PBXShellScriptBuildPhase section */ 209 | 210 | /* Begin PBXSourcesBuildPhase section */ 211 | 97C146EA1CF9000F007C117D /* Sources */ = { 212 | isa = PBXSourcesBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 216 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 217 | ); 218 | runOnlyForDeploymentPostprocessing = 0; 219 | }; 220 | /* End PBXSourcesBuildPhase section */ 221 | 222 | /* Begin PBXVariantGroup section */ 223 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 224 | isa = PBXVariantGroup; 225 | children = ( 226 | 97C146FB1CF9000F007C117D /* Base */, 227 | ); 228 | name = Main.storyboard; 229 | sourceTree = ""; 230 | }; 231 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 232 | isa = PBXVariantGroup; 233 | children = ( 234 | 97C147001CF9000F007C117D /* Base */, 235 | ); 236 | name = LaunchScreen.storyboard; 237 | sourceTree = ""; 238 | }; 239 | /* End PBXVariantGroup section */ 240 | 241 | /* Begin XCBuildConfiguration section */ 242 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 243 | isa = XCBuildConfiguration; 244 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 245 | buildSettings = { 246 | ALWAYS_SEARCH_USER_PATHS = NO; 247 | CLANG_ANALYZER_NONNULL = YES; 248 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 249 | CLANG_CXX_LIBRARY = "libc++"; 250 | CLANG_ENABLE_MODULES = YES; 251 | CLANG_ENABLE_OBJC_ARC = YES; 252 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 253 | CLANG_WARN_BOOL_CONVERSION = YES; 254 | CLANG_WARN_COMMA = YES; 255 | CLANG_WARN_CONSTANT_CONVERSION = YES; 256 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 257 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 258 | CLANG_WARN_EMPTY_BODY = YES; 259 | CLANG_WARN_ENUM_CONVERSION = YES; 260 | CLANG_WARN_INFINITE_RECURSION = YES; 261 | CLANG_WARN_INT_CONVERSION = YES; 262 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 263 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 264 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 265 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 266 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 267 | CLANG_WARN_STRICT_PROTOTYPES = YES; 268 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 269 | CLANG_WARN_UNREACHABLE_CODE = YES; 270 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 271 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 272 | COPY_PHASE_STRIP = NO; 273 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 274 | ENABLE_NS_ASSERTIONS = NO; 275 | ENABLE_STRICT_OBJC_MSGSEND = YES; 276 | GCC_C_LANGUAGE_STANDARD = gnu99; 277 | GCC_NO_COMMON_BLOCKS = YES; 278 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 279 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 280 | GCC_WARN_UNDECLARED_SELECTOR = YES; 281 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 282 | GCC_WARN_UNUSED_FUNCTION = YES; 283 | GCC_WARN_UNUSED_VARIABLE = YES; 284 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 285 | MTL_ENABLE_DEBUG_INFO = NO; 286 | SDKROOT = iphoneos; 287 | SUPPORTED_PLATFORMS = iphoneos; 288 | TARGETED_DEVICE_FAMILY = "1,2"; 289 | VALIDATE_PRODUCT = YES; 290 | }; 291 | name = Profile; 292 | }; 293 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 294 | isa = XCBuildConfiguration; 295 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 296 | buildSettings = { 297 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 298 | CLANG_ENABLE_MODULES = YES; 299 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 300 | ENABLE_BITCODE = NO; 301 | FRAMEWORK_SEARCH_PATHS = ( 302 | "$(inherited)", 303 | "$(PROJECT_DIR)/Flutter", 304 | ); 305 | INFOPLIST_FILE = Runner/Info.plist; 306 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 307 | LIBRARY_SEARCH_PATHS = ( 308 | "$(inherited)", 309 | "$(PROJECT_DIR)/Flutter", 310 | ); 311 | PRODUCT_BUNDLE_IDENTIFIER = com.example.imclient; 312 | PRODUCT_NAME = "$(TARGET_NAME)"; 313 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 314 | SWIFT_VERSION = 5.0; 315 | VERSIONING_SYSTEM = "apple-generic"; 316 | }; 317 | name = Profile; 318 | }; 319 | 97C147031CF9000F007C117D /* Debug */ = { 320 | isa = XCBuildConfiguration; 321 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 322 | buildSettings = { 323 | ALWAYS_SEARCH_USER_PATHS = NO; 324 | CLANG_ANALYZER_NONNULL = YES; 325 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 326 | CLANG_CXX_LIBRARY = "libc++"; 327 | CLANG_ENABLE_MODULES = YES; 328 | CLANG_ENABLE_OBJC_ARC = YES; 329 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 330 | CLANG_WARN_BOOL_CONVERSION = YES; 331 | CLANG_WARN_COMMA = YES; 332 | CLANG_WARN_CONSTANT_CONVERSION = YES; 333 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 334 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 335 | CLANG_WARN_EMPTY_BODY = YES; 336 | CLANG_WARN_ENUM_CONVERSION = YES; 337 | CLANG_WARN_INFINITE_RECURSION = YES; 338 | CLANG_WARN_INT_CONVERSION = YES; 339 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 340 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 341 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 342 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 343 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 344 | CLANG_WARN_STRICT_PROTOTYPES = YES; 345 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 346 | CLANG_WARN_UNREACHABLE_CODE = YES; 347 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 348 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 349 | COPY_PHASE_STRIP = NO; 350 | DEBUG_INFORMATION_FORMAT = dwarf; 351 | ENABLE_STRICT_OBJC_MSGSEND = YES; 352 | ENABLE_TESTABILITY = YES; 353 | GCC_C_LANGUAGE_STANDARD = gnu99; 354 | GCC_DYNAMIC_NO_PIC = NO; 355 | GCC_NO_COMMON_BLOCKS = YES; 356 | GCC_OPTIMIZATION_LEVEL = 0; 357 | GCC_PREPROCESSOR_DEFINITIONS = ( 358 | "DEBUG=1", 359 | "$(inherited)", 360 | ); 361 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 362 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 363 | GCC_WARN_UNDECLARED_SELECTOR = YES; 364 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 365 | GCC_WARN_UNUSED_FUNCTION = YES; 366 | GCC_WARN_UNUSED_VARIABLE = YES; 367 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 368 | MTL_ENABLE_DEBUG_INFO = YES; 369 | ONLY_ACTIVE_ARCH = YES; 370 | SDKROOT = iphoneos; 371 | TARGETED_DEVICE_FAMILY = "1,2"; 372 | }; 373 | name = Debug; 374 | }; 375 | 97C147041CF9000F007C117D /* Release */ = { 376 | isa = XCBuildConfiguration; 377 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 378 | buildSettings = { 379 | ALWAYS_SEARCH_USER_PATHS = NO; 380 | CLANG_ANALYZER_NONNULL = YES; 381 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 382 | CLANG_CXX_LIBRARY = "libc++"; 383 | CLANG_ENABLE_MODULES = YES; 384 | CLANG_ENABLE_OBJC_ARC = YES; 385 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 386 | CLANG_WARN_BOOL_CONVERSION = YES; 387 | CLANG_WARN_COMMA = YES; 388 | CLANG_WARN_CONSTANT_CONVERSION = YES; 389 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 390 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 391 | CLANG_WARN_EMPTY_BODY = YES; 392 | CLANG_WARN_ENUM_CONVERSION = YES; 393 | CLANG_WARN_INFINITE_RECURSION = YES; 394 | CLANG_WARN_INT_CONVERSION = YES; 395 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 396 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 397 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 398 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 399 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 400 | CLANG_WARN_STRICT_PROTOTYPES = YES; 401 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 402 | CLANG_WARN_UNREACHABLE_CODE = YES; 403 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 404 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 405 | COPY_PHASE_STRIP = NO; 406 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 407 | ENABLE_NS_ASSERTIONS = NO; 408 | ENABLE_STRICT_OBJC_MSGSEND = YES; 409 | GCC_C_LANGUAGE_STANDARD = gnu99; 410 | GCC_NO_COMMON_BLOCKS = YES; 411 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 412 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 413 | GCC_WARN_UNDECLARED_SELECTOR = YES; 414 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 415 | GCC_WARN_UNUSED_FUNCTION = YES; 416 | GCC_WARN_UNUSED_VARIABLE = YES; 417 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 418 | MTL_ENABLE_DEBUG_INFO = NO; 419 | SDKROOT = iphoneos; 420 | SUPPORTED_PLATFORMS = iphoneos; 421 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 422 | TARGETED_DEVICE_FAMILY = "1,2"; 423 | VALIDATE_PRODUCT = YES; 424 | }; 425 | name = Release; 426 | }; 427 | 97C147061CF9000F007C117D /* Debug */ = { 428 | isa = XCBuildConfiguration; 429 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 430 | buildSettings = { 431 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 432 | CLANG_ENABLE_MODULES = YES; 433 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 434 | ENABLE_BITCODE = NO; 435 | FRAMEWORK_SEARCH_PATHS = ( 436 | "$(inherited)", 437 | "$(PROJECT_DIR)/Flutter", 438 | ); 439 | INFOPLIST_FILE = Runner/Info.plist; 440 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 441 | LIBRARY_SEARCH_PATHS = ( 442 | "$(inherited)", 443 | "$(PROJECT_DIR)/Flutter", 444 | ); 445 | PRODUCT_BUNDLE_IDENTIFIER = com.example.imclient; 446 | PRODUCT_NAME = "$(TARGET_NAME)"; 447 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 448 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 449 | SWIFT_VERSION = 5.0; 450 | VERSIONING_SYSTEM = "apple-generic"; 451 | }; 452 | name = Debug; 453 | }; 454 | 97C147071CF9000F007C117D /* Release */ = { 455 | isa = XCBuildConfiguration; 456 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 457 | buildSettings = { 458 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 459 | CLANG_ENABLE_MODULES = YES; 460 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 461 | ENABLE_BITCODE = NO; 462 | FRAMEWORK_SEARCH_PATHS = ( 463 | "$(inherited)", 464 | "$(PROJECT_DIR)/Flutter", 465 | ); 466 | INFOPLIST_FILE = Runner/Info.plist; 467 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 468 | LIBRARY_SEARCH_PATHS = ( 469 | "$(inherited)", 470 | "$(PROJECT_DIR)/Flutter", 471 | ); 472 | PRODUCT_BUNDLE_IDENTIFIER = com.example.imclient; 473 | PRODUCT_NAME = "$(TARGET_NAME)"; 474 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 475 | SWIFT_VERSION = 5.0; 476 | VERSIONING_SYSTEM = "apple-generic"; 477 | }; 478 | name = Release; 479 | }; 480 | /* End XCBuildConfiguration section */ 481 | 482 | /* Begin XCConfigurationList section */ 483 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 484 | isa = XCConfigurationList; 485 | buildConfigurations = ( 486 | 97C147031CF9000F007C117D /* Debug */, 487 | 97C147041CF9000F007C117D /* Release */, 488 | 249021D3217E4FDB00AE95B9 /* Profile */, 489 | ); 490 | defaultConfigurationIsVisible = 0; 491 | defaultConfigurationName = Release; 492 | }; 493 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 494 | isa = XCConfigurationList; 495 | buildConfigurations = ( 496 | 97C147061CF9000F007C117D /* Debug */, 497 | 97C147071CF9000F007C117D /* Release */, 498 | 249021D4217E4FDB00AE95B9 /* Profile */, 499 | ); 500 | defaultConfigurationIsVisible = 0; 501 | defaultConfigurationName = Release; 502 | }; 503 | /* End XCConfigurationList section */ 504 | }; 505 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 506 | } 507 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siwangqishiq/FlutterIM/89bcbacd6123bbcad608f0d9b171b56ca7509c80/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siwangqishiq/FlutterIM/89bcbacd6123bbcad608f0d9b171b56ca7509c80/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siwangqishiq/FlutterIM/89bcbacd6123bbcad608f0d9b171b56ca7509c80/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siwangqishiq/FlutterIM/89bcbacd6123bbcad608f0d9b171b56ca7509c80/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siwangqishiq/FlutterIM/89bcbacd6123bbcad608f0d9b171b56ca7509c80/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siwangqishiq/FlutterIM/89bcbacd6123bbcad608f0d9b171b56ca7509c80/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siwangqishiq/FlutterIM/89bcbacd6123bbcad608f0d9b171b56ca7509c80/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siwangqishiq/FlutterIM/89bcbacd6123bbcad608f0d9b171b56ca7509c80/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siwangqishiq/FlutterIM/89bcbacd6123bbcad608f0d9b171b56ca7509c80/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siwangqishiq/FlutterIM/89bcbacd6123bbcad608f0d9b171b56ca7509c80/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siwangqishiq/FlutterIM/89bcbacd6123bbcad608f0d9b171b56ca7509c80/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siwangqishiq/FlutterIM/89bcbacd6123bbcad608f0d9b171b56ca7509c80/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siwangqishiq/FlutterIM/89bcbacd6123bbcad608f0d9b171b56ca7509c80/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siwangqishiq/FlutterIM/89bcbacd6123bbcad608f0d9b171b56ca7509c80/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siwangqishiq/FlutterIM/89bcbacd6123bbcad608f0d9b171b56ca7509c80/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siwangqishiq/FlutterIM/89bcbacd6123bbcad608f0d9b171b56ca7509c80/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siwangqishiq/FlutterIM/89bcbacd6123bbcad608f0d9b171b56ca7509c80/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siwangqishiq/FlutterIM/89bcbacd6123bbcad608f0d9b171b56ca7509c80/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | imclient 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/config.dart: -------------------------------------------------------------------------------- 1 | const String IM_SERVER_HOST = "10.219.32.66"; 2 | // const String IM_SERVER_HOST = "192.168.31.134"; //"10.219.32.66"; 3 | // const String IM_SERVER_HOST = "panyi.xyz"; 4 | const int IM_SERVER_PORT = 1998; 5 | -------------------------------------------------------------------------------- /lib/core/Account.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | import 'package:imclient/util/TextUtil.dart'; 3 | 4 | //当前账户 5 | class Account { 6 | static const String _KEY_TOKEN = "token"; 7 | static const String _KEY_ACCOUNT = "account"; 8 | static const String _KEY_UID = "_uid"; 9 | static const String _KEY_AVATOR = "_avator"; 10 | static const String _KEY_NAME = "_name"; 11 | static const String _KEY_DESC = "_desc"; 12 | static const String _KEY_SEX = "_sex"; 13 | static const String _KEY_AGE = "_age"; 14 | 15 | static String _token; 16 | static String _account; 17 | static int _uid; 18 | static String _avator; 19 | static String name; 20 | static int sex; 21 | static String desc; 22 | static int age; 23 | 24 | // 25 | static Future loadAccount() async{ 26 | SharedPreferences prefs = await SharedPreferences.getInstance(); 27 | _token = prefs.getString(_KEY_TOKEN); 28 | _account = prefs.getString(_KEY_ACCOUNT); 29 | _uid = prefs.getInt(_KEY_UID); 30 | _avator = prefs.getString(_KEY_AVATOR); 31 | name = prefs.getString(_KEY_NAME); 32 | sex = prefs.getInt(_KEY_SEX); 33 | desc = prefs.getString(_KEY_DESC); 34 | age = prefs.getInt(_KEY_AGE); 35 | } 36 | 37 | static void setUserInfo(String token , String account , int uid , String avator , String displayName , 38 | int male , String description , int _age) async { 39 | _token = token; 40 | _account = account; 41 | _uid = uid; 42 | _avator = avator; 43 | name = displayName; 44 | sex = male; 45 | desc = description; 46 | age = _age; 47 | _infoDao(); 48 | } 49 | 50 | static void clearUserInfo() async{ 51 | _token = null; 52 | _account = null; 53 | _uid = 0; 54 | _avator = null; 55 | name = null; 56 | sex = 1; 57 | desc = null; 58 | age = 0; 59 | 60 | _infoDao(); 61 | } 62 | 63 | static void _infoDao() async{ 64 | SharedPreferences prefs = await SharedPreferences.getInstance(); 65 | prefs.setString(_KEY_TOKEN, _token); 66 | prefs.setString(_KEY_ACCOUNT, _account); 67 | prefs.setInt(_KEY_UID, _uid); 68 | prefs.setString(_KEY_AVATOR, _avator); 69 | prefs.setString(_KEY_NAME, name); 70 | prefs.setInt(_KEY_SEX, sex); 71 | prefs.setString(_KEY_DESC, desc); 72 | prefs.setInt(_KEY_AGE, age); 73 | } 74 | 75 | static String getToken(){ 76 | return _token; 77 | } 78 | 79 | static int getUid(){ 80 | return _uid; 81 | } 82 | 83 | static String getAvator(){ 84 | return _avator; 85 | } 86 | 87 | //是否登录 88 | static bool isLogin(){ 89 | return !TextUtil.isEmpty(_account) && !TextUtil.isEmpty(_token); 90 | } 91 | 92 | }//end class 93 | -------------------------------------------------------------------------------- /lib/core/Codes.dart: -------------------------------------------------------------------------------- 1 | class Codes { 2 | static const int CODE_PRESON = 10001; 3 | static const int CODE_PERSON_RESP = 10002; //测试响应 4 | 5 | static const int CODE_LOGIN_REQ = 1003; //登录请求 6 | static const int CODE_LOGIN_RESP = 1004; //登录响应 7 | 8 | static const int CODE_LOGIN_OUT_REQ = 1005; //注销登录req 9 | static const int CODE_LOGIN_OUT_RESP = 1006; //注销登录 resp 10 | 11 | static const int CODE_AUTO_LOGIN_REQ = 1007; //自动登录req 12 | static const int CODE_AUTO_LOGIN_RESP = 1008; //自动登录resp 13 | 14 | static const int CODE_IMMESSAGE_SEND = 1009; //发送IM消息 15 | static const int CODE_IMMESSAGE_RECEIVED = 1010; //接收IM消息 16 | 17 | static const int CODE_FRIEND_LIST_RESP = 3002; //好友列表resp 18 | 19 | static const int CODE_RECIPE_HELLO = 4003; //测试消息 20 | 21 | static const int CODE_RECIPE_ACK = 9001; //反馈已接收消息 22 | } 23 | -------------------------------------------------------------------------------- /lib/core/IMClient.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:typed_data'; 3 | import 'package:fluttertoast/fluttertoast.dart'; 4 | import 'package:imclient/env/ServerConfig.dart'; 5 | import 'package:imclient/im/Action.dart'; 6 | import 'package:imclient/im/AutoLogin.dart'; 7 | import 'package:imclient/im/GetFriendListAction.dart'; 8 | import 'package:imclient/im/LoginAction.dart'; 9 | import 'package:imclient/im/LoginOutAction.dart'; 10 | import 'package:imclient/model/Codec.dart'; 11 | import 'package:imclient/model/Friend.dart'; 12 | import 'package:imclient/model/IMMessage.dart'; 13 | import 'package:imclient/model/RecipeAck.dart'; 14 | import 'package:imclient/model/bytebuf.dart'; 15 | import 'package:imclient/model/Msg.dart'; 16 | import 'package:imclient/model/Login.dart'; 17 | import 'package:imclient/core/Codes.dart'; 18 | import 'package:imclient/core/Account.dart'; 19 | import 'package:imclient/util/LruCache.dart'; 20 | import 'package:imclient/util/TimeUtil.dart'; 21 | 22 | //typedef OnMsgCallback = bool Function(Msg msg); 23 | 24 | abstract class ClientCallback { 25 | //受到消息 26 | void onReceivedMsg(Msg msg); 27 | 28 | //网络状态改变 29 | void onNetStatusChange(NetStatus oldStatus, NetStatus newStatus); 30 | } 31 | 32 | abstract class AuthCallback { 33 | //验证登录成功 34 | void onAuthSuccess(String token, String account, int uid); 35 | 36 | //登录失败 37 | void onAuthError(int errorCode); 38 | } 39 | 40 | //注销登录回调 41 | abstract class LoginOutCallback { 42 | void loginOutSuccess(); 43 | 44 | void loginOutError(); 45 | } 46 | 47 | enum NetStatus { 48 | offline, //初始 离线状态 49 | connecting, //连接中 50 | online //在线 51 | } 52 | 53 | class IMClient { 54 | static IMClient instance; 55 | 56 | static const int RESEND_TIME_OUT = 10; //重发超时时间 57 | static const int MAX_RETRY_TIMES = 4; //最大重试次数 58 | 59 | Socket _socket; 60 | NetStatus _netStatus; 61 | 62 | List callbackList = []; 63 | AuthCallback _authCallback; 64 | LoginOutCallback _loginOutCallback; 65 | 66 | List _waitSendMsgs = []; //缓存未来得及发送的数据包 67 | List preRawData = []; //上一次未读取完的byte数据 68 | 69 | LruCache _receivedMsgCache = new LruCache(cacheSize: 128); 70 | 71 | int _lastSockReadTime = 0; 72 | int _lastSockWriteTime = 0; //记录上一次Socket读写时间 73 | 74 | Map _shouldResendMsgs = {}; //缓存需要超时重发的消息 75 | 76 | static IMClient getInstance() { 77 | if (instance == null) { 78 | instance = new IMClient(); 79 | } 80 | 81 | return instance; 82 | } 83 | 84 | IMClient() { 85 | _netStatus = NetStatus.offline; 86 | preRawData.clear(); 87 | } 88 | 89 | void init() { 90 | connectServer(); 91 | _schduleTask(); 92 | } 93 | 94 | //启动一个定时任务 空闲时完成心跳包的发送 95 | void _schduleTask() {} 96 | 97 | void addAuthCallback(AuthCallback authCb) { 98 | if (_authCallback != authCb) { 99 | _authCallback = authCb; 100 | } 101 | } 102 | 103 | void addLoginOutCallback(LoginOutCallback cb) { 104 | if (_loginOutCallback != cb) { 105 | _loginOutCallback = cb; 106 | } 107 | } 108 | 109 | void clearLoginOutCallback() { 110 | _loginOutCallback = null; 111 | } 112 | 113 | //添加事件监听 114 | bool addListener(ClientCallback cb) { 115 | if (callbackList.contains(cb)) { 116 | return false; 117 | } 118 | 119 | callbackList.add(cb); 120 | return true; 121 | } 122 | 123 | //移除事件监听 124 | bool removeListener(ClientCallback cb) { 125 | return callbackList.remove(cb); 126 | } 127 | 128 | void clearAuthCallback() { 129 | _authCallback = null; 130 | } 131 | 132 | void connectServer() { 133 | if (_netStatus == NetStatus.online || _netStatus == NetStatus.connecting) { 134 | print("connect server cancel"); 135 | return; 136 | } 137 | 138 | //print("im netclient init"); 139 | _switchStatus(_netStatus, NetStatus.connecting); 140 | 141 | Socket.connect(ServerConfig.instance.imServer, ServerConfig.instance.imPort, 142 | timeout: Duration(milliseconds: 30 * 1000)) 143 | .then((socket) { 144 | print( 145 | "connect success! ${socket.address} : ${socket.port} => ${socket.remoteAddress} : ${socket.remotePort}"); 146 | _socket = socket; 147 | _switchStatus(_netStatus, NetStatus.online); 148 | 149 | addSocketListener(); 150 | 151 | // 152 | while (_waitSendMsgs.isNotEmpty) { 153 | Msg msg = _waitSendMsgs.removeAt(0); 154 | sendMsg(msg); 155 | } 156 | }).catchError((e) { 157 | _switchStatus(_netStatus, NetStatus.offline); 158 | 159 | if (_socket != null) { 160 | _socket.close(); 161 | } 162 | print("连接发生错误 connect error ${e.toString()}"); 163 | }); 164 | } 165 | 166 | void addSocketListener() { 167 | if (_socket == null) return; 168 | 169 | _socket.listen( 170 | (Uint8List data) { 171 | _lastSockReadTime = TimeUtil.getNowMilliseconds(); //更新读写时间值 172 | 173 | print("received (${data.lengthInBytes} | ${data.length})"); 174 | //print("content = ${ByteBufUtil.readString(data)}"); 175 | 176 | List msgList = _parseRawData(data); 177 | 178 | for (Msg msg in msgList) { 179 | if (msg == null) continue; 180 | 181 | print( 182 | "received msg : length = ${msg.length} code = ${msg.code} uuid = ${msg.uuid}"); 183 | 184 | if (_receivedMsgCache.get(msg.uuid) != null) { 185 | continue; 186 | } 187 | _receivedMsgCache.put(msg.uuid, msg); 188 | //print("content = ${msg.data}"); 189 | 190 | _handleMsg(msg); 191 | } //end for each 192 | }, 193 | onDone: () { 194 | print("on close socket"); 195 | onCloseSocket(); 196 | }, 197 | onError: (e) { 198 | print("onError : ${e.toString()}"); 199 | _switchStatus(_netStatus, NetStatus.offline); 200 | }, 201 | cancelOnError: false, 202 | ); 203 | } 204 | 205 | //解析接收到的原始数据 206 | List _parseRawData(Uint8List rawData) { 207 | List dataList = ByteBufUtil.convertUint8ListToMutable(rawData); 208 | 209 | if (preRawData.isNotEmpty) { 210 | //拼接上一次的数据 211 | dataList.insertAll(0, preRawData); 212 | preRawData.clear(); 213 | } 214 | 215 | List resultMsgList = []; 216 | 217 | while (dataList.isNotEmpty) { 218 | if (dataList.length < 4) { 219 | // 首字节不足4位 220 | preRawData.addAll(dataList); 221 | dataList.removeRange(0, dataList.length); 222 | break; 223 | } 224 | 225 | int len = ByteBufUtil.readInt32(dataList); 226 | if (len > dataList.length) { 227 | //数据不完整 228 | preRawData.addAll(dataList); 229 | dataList.removeRange(0, dataList.length); 230 | break; 231 | } else { 232 | // len <= data.length 233 | Msg msg = new Msg(); 234 | print("msg len = $len , dataLen = ${dataList.length}"); 235 | msg.decode(Uint8List.fromList(dataList.sublist(0, len))); 236 | 237 | resultMsgList.add(msg); 238 | 239 | dataList.removeRange(0, len); 240 | } 241 | } //end while 242 | return resultMsgList; 243 | } 244 | 245 | void onCloseSocket() { 246 | _netStatus = NetStatus.offline; 247 | } 248 | 249 | // 发送Msg消息 250 | Future sendMsg(Msg msg, {autoResend: false}) async { 251 | if (_netStatus == NetStatus.online) { 252 | // print("send msg : ${msg.code} ${msg.length} ${msg.data}"); 253 | var sendBytes = msg.encode(); 254 | //print("sendBytes : $sendBytes"); 255 | //_socket.write(sendBytes); 256 | 257 | _socket.add(sendBytes); 258 | await _socket.flush(); 259 | 260 | _lastSockWriteTime = TimeUtil.getNowMilliseconds(); 261 | 262 | if (msg.resend) { 263 | //此消息需要做超时重发 264 | //todo 启动定时任务 265 | if (!autoResend) { 266 | _shouldResendMsgs[msg.uuid] = msg; 267 | _resendMsgTask(msg.uuid); 268 | } 269 | } 270 | } else if (_netStatus == NetStatus.connecting) { 271 | _waitSendMsgs.add(msg); 272 | } else if (_netStatus == NetStatus.offline) { 273 | _waitSendMsgs.add(msg); 274 | connectServer(); 275 | } 276 | } 277 | 278 | void _resendMsgTask(int uuid) { 279 | final Msg msg = _shouldResendMsgs[uuid]; 280 | if (msg == null) { 281 | return; 282 | } 283 | 284 | //重新发送 285 | msg.sendTimes++; 286 | 287 | if (msg.sendTimes > MAX_RETRY_TIMES) { 288 | //超过了最大重试次数 289 | print("消息${msg.uuid} 超过最大重试次数了 不再重发!"); 290 | 291 | _shouldResendMsgs.remove(uuid); 292 | //错误回调 293 | if (msg.getCallback() != null) { 294 | msg.getCallback().onSendError(msg); 295 | } 296 | } else { 297 | //重新发送 298 | Future.delayed(Duration(seconds: RESEND_TIME_OUT)).then((v) { 299 | final Msg msg = _shouldResendMsgs[uuid]; 300 | if (msg == null) { 301 | return; 302 | } 303 | print("消息 $uuid 启动重发~"); 304 | sendMsg(msg, autoResend: true); 305 | }); 306 | } 307 | } 308 | 309 | void dispose() { 310 | print("im netclient dispose"); 311 | if (_socket != null) { 312 | _socket.close().then((r) { 313 | print("close socket success"); 314 | _socket = null; 315 | _switchStatus(_netStatus, NetStatus.offline); 316 | }).catchError((e) { 317 | print("close socket error ${e.toString()}"); 318 | }); 319 | } 320 | } 321 | 322 | void _switchStatus(NetStatus oldStatus, NetStatus newStatus) { 323 | print("oldStatus: $oldStatus newStatus: $newStatus"); 324 | if (_netStatus != newStatus) { 325 | _netStatus = newStatus; 326 | 327 | //callback 328 | for (ClientCallback cb in callbackList) { 329 | cb.onNetStatusChange(oldStatus, newStatus); 330 | } //end for each 331 | } 332 | } 333 | 334 | //将model编码成msg 335 | Future _sendModel(Codec model) async { 336 | Msg msg = Msg.buildMsg(model.getCode(), model); 337 | print("send msg = ${msg.length} " + 338 | "${msg.code} uuid = ${msg.uuid} resend = ${msg.needResend()}"); 339 | await sendMsg(msg); 340 | } 341 | 342 | //用户名密码手动登录 343 | void auth(String account, String password) { 344 | LoginReq loginReq = new LoginReq(); 345 | loginReq.account = account; 346 | loginReq.password = password; 347 | 348 | _sendModel(loginReq); 349 | } 350 | 351 | //注销登录 352 | void loginOut() { 353 | LoginOutReq loginOutReq = new LoginOutReq(Account.getUid()); 354 | _sendModel(loginOutReq); 355 | } 356 | 357 | //利用token 自动登录 358 | void autoLogin() { 359 | AutoLoginReq autoLoginReq = new AutoLoginReq(); 360 | _sendModel(autoLoginReq); 361 | } 362 | 363 | // 发送ack已接收报文 364 | void _sendRecipeAckMsg(int uuid) async { 365 | RecipeAck ack = new RecipeAck(); 366 | ack.uuid = uuid; 367 | print("send ack uuid = ${ack.uuid}"); 368 | await _sendModel(ack); 369 | 370 | RecipeHello testRecipe = new RecipeHello(); 371 | testRecipe.content = "你好啊 我是Andoird客户端 如果你不理我 我会多次重发的"; 372 | testRecipe.setCallback(new MsgSendCallbackDefault()); 373 | await _sendModel(testRecipe); 374 | } 375 | 376 | // 接收到ack的操作 377 | void _handleRecipeAck(int ackUuid) { 378 | print("接到$ackUuid 应答 可以不重发了"); 379 | 380 | if(_shouldResendMsgs[ackUuid] != null && _shouldResendMsgs[ackUuid].getCallback() != null){ 381 | _shouldResendMsgs[ackUuid].getCallback().onSendSuccess(_shouldResendMsgs[ackUuid]); 382 | } 383 | _shouldResendMsgs.remove(ackUuid); 384 | 385 | } 386 | 387 | void sendIMMessage(IMMessage msg){ 388 | SendIMMessagePacket packet = new SendIMMessagePacket(); 389 | packet.imMessage = msg; 390 | 391 | _sendModel(packet); 392 | } 393 | 394 | //处理消息 395 | void _handleMsg(Msg msg) { 396 | IMAction action; 397 | switch (msg.code) { 398 | case Codes.CODE_LOGIN_RESP: //登录响应 399 | action = new LoginAction(_authCallback); 400 | break; 401 | case Codes.CODE_LOGIN_OUT_RESP: //注销登录 响应 402 | action = new LoginOutAction(_loginOutCallback); 403 | break; 404 | case Codes.CODE_AUTO_LOGIN_RESP: //自动登录 405 | action = new AutoLoginAction(); 406 | break; 407 | case Codes.CODE_FRIEND_LIST_RESP: //好友列表 408 | action = new GetFriendListAction(); 409 | break; 410 | case Codes.CODE_RECIPE_HELLO: 411 | _sendRecipeAckMsg(msg.uuid); 412 | break; 413 | case Codes.CODE_RECIPE_ACK: //应答ack 414 | RecipeAck ack = new RecipeAck(); 415 | ack.decode(msg.data); 416 | _handleRecipeAck(ack.uuid); 417 | break; 418 | } //end switch 419 | 420 | //执行action 逻辑 421 | if (action == null) return; 422 | 423 | action.handleMsg(msg); 424 | 425 | if (action.needCallback()) { 426 | for (ClientCallback callback in callbackList) { 427 | //callback for others 428 | callback.onReceivedMsg(msg); 429 | } //end for each 430 | } 431 | } 432 | } //end class 433 | 434 | class MsgSendCallbackDefault extends SendCallback { 435 | 436 | @override 437 | void onSendError(Codec msg) { 438 | print("重发了很多次了 都没响应 算了 不发了"); 439 | } 440 | 441 | @override 442 | void onSendSuccess(Codec msg) { 443 | print("成功接收回馈 发送成功"); 444 | } 445 | } 446 | -------------------------------------------------------------------------------- /lib/env/ServerConfig.dart: -------------------------------------------------------------------------------- 1 | import 'package:imclient/config.dart'; 2 | 3 | enum Env{ 4 | REAL, 5 | DEV 6 | } 7 | 8 | class ServerConfig{ 9 | Env env; 10 | 11 | String imServer; 12 | int imPort; 13 | 14 | static ServerConfig instance = new ServerConfig(Env.DEV); 15 | 16 | ServerConfig(Env e){ 17 | this.env = e; 18 | 19 | if(env == Env.REAL){ //真实环境 20 | imServer = IM_SERVER_HOST; 21 | imPort = IM_SERVER_PORT; 22 | }else{ //测试环境 23 | imServer = IM_SERVER_HOST; 24 | imPort = IM_SERVER_PORT; 25 | } 26 | } 27 | }//end class -------------------------------------------------------------------------------- /lib/im/Action.dart: -------------------------------------------------------------------------------- 1 | import 'package:imclient/model/Msg.dart'; 2 | 3 | /// 4 | /// 接收msg相应操作 5 | abstract class IMAction{ 6 | //处理消息 7 | void handleMsg(Msg msg); 8 | 9 | //是否回调 默认需要 10 | bool needCallback(){ 11 | return true; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/im/AutoLogin.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluttertoast/fluttertoast.dart'; 2 | import 'package:imclient/im/Action.dart'; 3 | import 'package:imclient/model/Msg.dart'; 4 | 5 | /// 6 | ///自动登录 7 | /// 8 | 9 | class AutoLoginAction extends IMAction{ 10 | @override 11 | void handleMsg(Msg msg) { 12 | print("自动登录 success"); 13 | FlutterToast.showToast(msg: "自动登录成功"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/im/FriendDataCache.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:imclient/model/Friend.dart'; 3 | 4 | /// 5 | ///好友数据本地Cache 6 | /// 7 | class FriendDataCache{ 8 | static FriendDataCache _instance; 9 | 10 | static FriendDataCache instance(){ 11 | if(_instance == null){ 12 | _instance = new FriendDataCache(); 13 | } 14 | return _instance; 15 | } 16 | 17 | List _list = []; 18 | 19 | // 重新添加好友数据 20 | bool reAddAllFriend(List friendList){ 21 | _list.clear(); 22 | 23 | _list.addAll(friendList); 24 | return true; 25 | } 26 | 27 | List get() => _list; 28 | }//end class -------------------------------------------------------------------------------- /lib/im/GetFriendListAction.dart: -------------------------------------------------------------------------------- 1 | import 'package:imclient/im/Action.dart'; 2 | import 'package:imclient/im/FriendDataCache.dart'; 3 | import 'package:imclient/model/Codec.dart'; 4 | import 'package:imclient/model/Friend.dart'; 5 | import 'package:imclient/model/Msg.dart'; 6 | 7 | //获取好友数据 8 | class GetFriendListAction extends IMAction { 9 | @override 10 | void handleMsg(Msg msg) { 11 | print("获取好友数据"); 12 | FriendResp resp = new FriendResp(); 13 | resp.decode(msg.data); 14 | if(resp.result == Codec.RESULT_CODE_SUCCESS){ 15 | onReceivedFriendList(resp.friendList); 16 | } 17 | } 18 | 19 | void onReceivedFriendList(List friendList){ 20 | FriendDataCache.instance().reAddAllFriend(friendList); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/im/ImMessage.dart: -------------------------------------------------------------------------------- 1 | 2 | enum SessionType{ 3 | P2P, 4 | Team 5 | } 6 | 7 | enum MessageType{ 8 | text, 9 | image, 10 | } 11 | 12 | //IM 消息 13 | class ImMessage{ 14 | String from; 15 | String to; 16 | SessionType sessionType; 17 | MessageType messageType; 18 | 19 | factory ImMessage.createImMessage(){ 20 | return null; 21 | } 22 | } 23 | 24 | 25 | -------------------------------------------------------------------------------- /lib/im/LoginAction.dart: -------------------------------------------------------------------------------- 1 | import 'package:imclient/core/Account.dart'; 2 | import 'package:imclient/core/IMClient.dart'; 3 | import 'package:imclient/im/Action.dart'; 4 | import 'package:imclient/model/Login.dart'; 5 | import 'package:imclient/model/Msg.dart'; 6 | 7 | //登录消息处理 8 | class LoginAction extends IMAction{ 9 | AuthCallback authCallback; 10 | 11 | LoginAction(this.authCallback); 12 | 13 | @override 14 | void handleMsg(Msg msg) { 15 | LoginResp loginResp = new LoginResp(); 16 | loginResp.decode(msg.data); 17 | 18 | _handleLogin(loginResp); 19 | } 20 | 21 | @override 22 | bool needCallback(){ 23 | return false; 24 | } 25 | 26 | void _handleLogin(LoginResp loginResp){ 27 | if(loginResp == null) 28 | return; 29 | 30 | if(loginResp.resultCode == LoginResp.RESULT_CODE_SUCCESS){ 31 | Account.setUserInfo(loginResp.token, loginResp.account, loginResp.uid , loginResp.avator , 32 | loginResp.name,loginResp.sex , loginResp.desc , loginResp.age); 33 | 34 | if(authCallback != null){ 35 | authCallback.onAuthSuccess(loginResp.token, loginResp.account, loginResp.uid); 36 | } 37 | }else{//登录失败 38 | if(authCallback != null){ 39 | authCallback.onAuthError(loginResp.resultCode); 40 | } 41 | } 42 | } 43 | }//end class -------------------------------------------------------------------------------- /lib/im/LoginOutAction.dart: -------------------------------------------------------------------------------- 1 | import 'package:imclient/core/Account.dart'; 2 | import 'package:imclient/core/IMClient.dart'; 3 | import 'package:imclient/im/Action.dart'; 4 | import 'package:imclient/model/Codec.dart'; 5 | import 'package:imclient/model/Login.dart'; 6 | import 'package:imclient/model/Msg.dart'; 7 | 8 | /// 9 | ///退出登录 10 | /// 11 | class LoginOutAction extends IMAction{ 12 | LoginOutCallback _loginOutCallback; 13 | 14 | LoginOutAction(this._loginOutCallback); 15 | 16 | @override 17 | void handleMsg(Msg msg){ 18 | LoginOutResp resp = new LoginOutResp(); 19 | resp.decode(msg.data); 20 | 21 | _handleLoginOutResp(resp); 22 | } 23 | 24 | //注销登录响应 25 | void _handleLoginOutResp(LoginOutResp resp){ 26 | if(resp.resultCode == Codec.RESULT_CODE_SUCCESS){ 27 | Account.clearUserInfo(); 28 | if(_loginOutCallback != null){ 29 | _loginOutCallback.loginOutSuccess(); 30 | } 31 | }else{ 32 | if(_loginOutCallback != null){ 33 | _loginOutCallback.loginOutError(); 34 | } 35 | } 36 | } 37 | 38 | }//end class -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:imclient/core/IMClient.dart'; 5 | import 'package:imclient/page/LoginPage.dart'; 6 | import 'package:imclient/page/MainPage.dart'; 7 | import 'package:imclient/page/pages.dart'; 8 | import 'package:imclient/util/GenUtil.dart'; 9 | import 'package:imclient/util/LruCache.dart'; 10 | import 'dart:convert' show utf8; 11 | import 'core/Account.dart'; 12 | 13 | void main() async { 14 | WidgetsFlutterBinding.ensureInitialized(); //确保flutter环境已经完全启动 否则后面会报错 15 | unitTest(); 16 | appInit(); 17 | runApp(MyApp()); 18 | } 19 | 20 | void unitTest() { 21 | // for(int i=0 ;i < 100;i++){ 22 | // print(GenUtil.get16Uuid()); 23 | // } 24 | 25 | // LruCache cache = LruCache(); 26 | 27 | // LruCache cache = LruCache(cacheSize: 3); 28 | // cache.put(1, "111"); 29 | // cache.put(2, "222"); 30 | // cache.put(3, "333"); 31 | 32 | // cache.put(4, "4444"); 33 | 34 | // print("LruCache ---> ${cache.get(1)}"); 35 | 36 | // int u = GenUtil.genUuid(); 37 | // print("GenUtil ---> $u"); 38 | } 39 | 40 | void appInit() async { 41 | await Account.loadAccount(); 42 | 43 | print("Account isLogin = ${Account.isLogin()}"); 44 | if (Account.isLogin()) { 45 | //自动登录 46 | IMClient.getInstance().autoLogin(); 47 | } 48 | } 49 | 50 | class MyApp extends StatelessWidget { 51 | // This widget is the root of your application. 52 | @override 53 | Widget build(BuildContext context) { 54 | return MaterialApp( 55 | title: 'Flutter Demo', 56 | theme: ThemeData( 57 | primarySwatch: Colors.blue, 58 | visualDensity: VisualDensity.adaptivePlatformDensity, 59 | ), 60 | //home: MyHomePage(title: '客户端'), 61 | home: Account.isLogin() ? MainPage() : LoginPage(), 62 | ); 63 | } 64 | } 65 | 66 | // void testUnit(){ 67 | // print("init app"); 68 | 69 | // String str ="毛利兰"; 70 | // Uint8List strUintList = utf8.encode(str); 71 | // print("strUintList : $strUintList"); 72 | // print("strUintList decode : ${utf8.decode(strUintList)}"); 73 | // Person p = new Person(name:str); 74 | // Uint8List pData = p.encode(); 75 | // print("pData : $pData"); 76 | 77 | // Person decodePerson = Person.build(pData); 78 | // print("decodePerson ${decodePerson.name} ${decodePerson.age} ${decodePerson.desc}"); 79 | // } 80 | -------------------------------------------------------------------------------- /lib/model/AuthBaseBean.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:imclient/model/Codec.dart'; 4 | 5 | /** 6 | * 登录后 需要传token标示自身身份的bean 7 | */ 8 | class AuthBaseBean extends Codec{ 9 | String token; 10 | 11 | AuthBaseBean(this.token); 12 | 13 | int decodeModel(Uint8List rawData){ 14 | return 0; 15 | } 16 | 17 | Uint8List encodeModel(List result){ 18 | return null; 19 | } 20 | 21 | @override 22 | int decode(Uint8List rawData) { 23 | resetReadIndex(); 24 | token = readString(rawData); 25 | 26 | decodeModel(rawData); 27 | 28 | return getReadIndex(); 29 | } 30 | 31 | @override 32 | Uint8List encode() { 33 | List result = []; 34 | result.add(writeString(token)); 35 | 36 | encodeModel(result); 37 | return Uint8List.fromList(result.expand((x)=>x).toList()); 38 | } 39 | 40 | }//end class 41 | -------------------------------------------------------------------------------- /lib/model/Codec.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'dart:convert' show utf8; 3 | 4 | abstract class Codec { 5 | static const int RESULT_CODE_SUCCESS = 1; 6 | static const int RESULT_CODE_ERROR = -1; 7 | 8 | int sendTimes = 0; 9 | 10 | int _readIndex = 0; 11 | SendCallback _callback; 12 | 13 | //转为字节流 14 | Uint8List encode(); 15 | 16 | //字节流转为对象 返回读取的字节数 17 | int decode(Uint8List rawData); 18 | 19 | //设置事件回调 20 | void setCallback(SendCallback cb) { 21 | _callback = cb; 22 | } 23 | 24 | SendCallback getCallback() { 25 | return _callback; 26 | } 27 | 28 | int getCode() { 29 | return 0; 30 | } 31 | 32 | //是否有超时重发确认机制 默认没有 33 | bool needResend() { 34 | return false; 35 | } 36 | 37 | //重置已读索引 38 | void resetReadIndex() { 39 | _readIndex = 0; 40 | } 41 | 42 | int getReadIndex() => _readIndex; 43 | 44 | //向字节流中写入一个32位int 45 | Uint8List writeInt32(int value) { 46 | ByteData data = ByteData(4); 47 | data.setInt32(0, value, Endian.little); 48 | return data.buffer.asUint8List(); 49 | } 50 | 51 | Uint8List writeInt64(int value) { 52 | ByteData data = ByteData(8); 53 | data.setInt64(0, value, Endian.little); 54 | return data.buffer.asUint8List(); 55 | } 56 | 57 | String readString(Uint8List bytes) { 58 | //print("readString $bytes"); 59 | int len = bytes.sublist(_readIndex).buffer.asInt32List()[0]; 60 | //print("$_readIndex len = $len"); 61 | String readString = 62 | utf8.decode(bytes.sublist(_readIndex + 4, _readIndex + 4 + len)); 63 | _readIndex += (4 + len); //string 长度 + 数据长度 64 | 65 | return readString; 66 | } 67 | 68 | // 69 | Uint8List writeString(String str) { 70 | if (str == null || str.isEmpty) { 71 | return writeInt32(0); 72 | } 73 | 74 | List result = []; 75 | 76 | Uint8List strUint8List = utf8.encode(str); 77 | result.add(writeInt32(strUint8List.length)); 78 | result.add(strUint8List); 79 | 80 | return Uint8List.fromList(result.expand((x) => x).toList()); 81 | } 82 | 83 | int readInt8(Uint8List bytes) { 84 | int result = bytes.sublist(_readIndex).buffer.asInt8List()[0]; 85 | _readIndex += 1; 86 | return result; 87 | } 88 | 89 | Uint8List writeInt8(int value) { 90 | ByteData data = ByteData(1); 91 | data.setInt8(0, value); 92 | return data.buffer.asUint8List(); 93 | } 94 | 95 | int readInt32(Uint8List bytes) { 96 | int result = bytes.sublist(_readIndex).buffer.asInt32List()[0]; 97 | _readIndex += 4; 98 | return result; 99 | } 100 | 101 | //写入一个list 102 | Uint8List writeList(List list) { 103 | if (list == null) { 104 | return writeInt32(0); 105 | } else { 106 | List result = []; 107 | result.add(writeInt32(list.length)); 108 | for (int i = 0; i < list.length; i++) { 109 | Codec item = list[i]; 110 | result.add(item.encode()); 111 | } //end for i 112 | return Uint8List.fromList(result.expand((x) => x).toList()); 113 | } 114 | } 115 | 116 | //读取一个List 117 | List readList(Uint8List bytes, IGenListItem genCallback) { 118 | int listSize = readInt32(bytes); 119 | //print("listSize = $listSize"); 120 | 121 | List list = []; 122 | if (listSize > 0) { 123 | for (int i = 0; i < listSize; i++) { 124 | if (genCallback != null) { 125 | T item = genCallback.createListItem(); 126 | 127 | //print("read list readIndex = $_readIndex"); 128 | int readByteCount = item.decode(bytes.sublist(_readIndex)); 129 | _readIndex += readByteCount; 130 | 131 | list.add(item); 132 | } 133 | } //end for i 134 | } 135 | return list; 136 | } 137 | 138 | // static int readInt32NoMoveIndex(Uint8List bytes){ 139 | // int result = bytes.sublist(0).buffer.asInt32List()[0]; 140 | // return result; 141 | // } 142 | 143 | num readInt64(Uint8List bytes) { 144 | num result = bytes.sublist(_readIndex).buffer.asInt64List()[0]; 145 | _readIndex += 8; 146 | return result; 147 | } 148 | } //end class 149 | 150 | //生成列表中的实体对象 151 | abstract class IGenListItem { 152 | T createListItem(); 153 | } 154 | 155 | //发送消息时 产生的事件回调 156 | abstract class SendCallback { 157 | //远端成功接收 158 | void onSendSuccess(Codec msg); 159 | 160 | //重试超过指定次数仍然无回应 则报错 回调 161 | void onSendError(Codec msg); 162 | } 163 | -------------------------------------------------------------------------------- /lib/model/Friend.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:typed_data'; 3 | 4 | import 'package:imclient/model/Codec.dart'; 5 | import 'package:imclient/util/TextUtil.dart'; 6 | 7 | /// 8 | ///好友数据列表 9 | /// 10 | class FriendResp extends Codec implements IGenListItem{ 11 | int result; 12 | List friendList; 13 | 14 | @override 15 | int decode(Uint8List rawData) { 16 | resetReadIndex(); 17 | result = readInt32(rawData); 18 | //print("friendResp result = $result"); 19 | 20 | friendList = readList(rawData, this); 21 | return getReadIndex(); 22 | } 23 | 24 | @override 25 | Uint8List encode() { 26 | return null; 27 | } 28 | 29 | @override 30 | Friend createListItem() { 31 | return new Friend(); 32 | } 33 | }//end class 34 | 35 | class Friend extends Codec{ 36 | static const int SEX_MALE = 1; 37 | static const int SEX_FEMALE = 0; 38 | 39 | 40 | int uid; 41 | String account; 42 | String avator; 43 | int sex; 44 | String nick; 45 | int age; 46 | String desc; 47 | 48 | //显示名字 49 | String displayName(){ 50 | return TextUtil.isEmpty(nick)?account:nick; 51 | } 52 | 53 | @override 54 | int decode(Uint8List rawData) { 55 | resetReadIndex(); 56 | 57 | uid = readInt64(rawData); 58 | sex = readInt32(rawData); 59 | avator = readString(rawData); 60 | account = readString(rawData); 61 | nick = readString(rawData); 62 | age = readInt32(rawData); 63 | desc = readString(rawData); 64 | 65 | return getReadIndex(); 66 | } 67 | 68 | @override 69 | Uint8List encode() { 70 | List result = []; 71 | result.add(writeInt64(uid)); 72 | result.add(writeInt32(sex)); 73 | result.add(writeString(avator)); 74 | result.add(writeString(account)); 75 | result.add(writeString(nick)); 76 | result.add(writeInt32(age)); 77 | result.add(writeString(desc)); 78 | 79 | return Uint8List.fromList(result.expand((x)=>x).toList()); 80 | } 81 | 82 | @override 83 | String toString() { 84 | return "{ uid = $uid , sex = $sex , avator =$avator , account = $account , nick = $nick , age =$age}"; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/model/IMMessage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:imclient/core/Account.dart'; 4 | import 'package:imclient/core/Codes.dart'; 5 | import 'package:imclient/model/Codec.dart'; 6 | import 'package:imclient/util/GenUtil.dart'; 7 | import 'package:imclient/util/TimeUtil.dart'; 8 | import 'package:imclient/model/Login.dart'; 9 | import 'package:imclient/model/AuthBaseBean.dart'; 10 | 11 | //会话类型 12 | enum SessionType { 13 | P2P, //0 14 | TEAM, //1 15 | } 16 | 17 | //消息方法 发出 or 接收 18 | enum MessageDirection { IN, OUT } 19 | 20 | enum MessageType { 21 | text, //文本 0 22 | image, //图片 1 23 | audio, // 音频 2 24 | video, // 视频 3 25 | file, //文件 4 26 | stick, // 贴图表情5 27 | tip, //提示 6 28 | custom, //自定义 7 29 | } 30 | 31 | //消息状态 32 | enum MessageState { 33 | init, // 34 | sending, 35 | sendSuccess, 36 | sendFailed, 37 | readed, 38 | error 39 | } 40 | 41 | //IM消息结构体 42 | class IMMessage extends Codec { 43 | int msgUuid; //消息唯一标识 44 | int createTime; //创建时间 45 | int updateTime; //更新时间 46 | int from; //消息发出者 47 | int to; //消息接收者 48 | SessionType sessionType; // 会话类型 1.p2p 2.team 49 | MessageType messageType; // 消息类型 50 | MessageDirection direction; //消息方向 51 | MessageState state; //消息状态 52 | 53 | String content; //消息内容 54 | String config; //配置 55 | String attachJsonStr; 56 | String extra; // 57 | 58 | dynamic attachment; //附件 59 | 60 | static IMMessage createBaseIMMessage(SessionType sessionType, int toUid) { 61 | if (!Account.isLogin()) { 62 | return null; 63 | } 64 | 65 | IMMessage imMessage = new IMMessage(); 66 | imMessage.msgUuid = GenUtil.genUuid(); 67 | int time = TimeUtil.getNowMilliseconds(); 68 | imMessage.createTime = time; 69 | imMessage.updateTime = time; 70 | imMessage.from = Account.getUid(); 71 | imMessage.to = toUid; 72 | imMessage.sessionType = sessionType; 73 | imMessage.direction = MessageDirection.OUT; 74 | imMessage.state = MessageState.init; 75 | return imMessage; 76 | } 77 | 78 | //创建文本类型消息 79 | static IMMessage createTextIMMessage( 80 | SessionType sessionType, int toUid, String content) { 81 | IMMessage imMessage = createBaseIMMessage(sessionType, toUid); 82 | 83 | //set message type 84 | imMessage.messageType = MessageType.text; 85 | imMessage.content = content; 86 | 87 | return imMessage; 88 | } 89 | 90 | @override 91 | int decode(Uint8List rawData) { 92 | resetReadIndex(); 93 | 94 | msgUuid = readInt64(rawData); 95 | createTime = readInt64(rawData); 96 | updateTime = readInt64(rawData); 97 | from = readInt64(rawData); 98 | to = readInt64(rawData); 99 | 100 | int sessionTypeValue = readInt8(rawData); 101 | sessionType = SessionType.values[sessionTypeValue]; 102 | 103 | int messageTypeValue = readInt8(rawData); 104 | messageType = MessageType.values[messageTypeValue]; 105 | 106 | int messageDirectionValue = readInt8(rawData); 107 | direction = MessageDirection.values[messageDirectionValue]; 108 | 109 | int stateValue = readInt8(rawData); 110 | state = MessageState.values[stateValue]; 111 | 112 | content = readString(rawData); 113 | config = readString(rawData); 114 | attachJsonStr = readString(rawData); 115 | extra = readString(rawData); 116 | 117 | switch (messageType) { 118 | case MessageType.text: 119 | break; 120 | case MessageType.image: 121 | break; 122 | case MessageType.audio: 123 | break; 124 | case MessageType.video: 125 | break; 126 | case MessageType.file: 127 | break; 128 | case MessageType.stick: 129 | break; 130 | case MessageType.tip: 131 | break; 132 | case MessageType.custom: 133 | break; 134 | default: 135 | break; 136 | } //end switch 137 | 138 | return getReadIndex(); 139 | } 140 | 141 | @override 142 | Uint8List encode() { 143 | List result = []; 144 | 145 | //result.add(writeInt32(resultCode)); 146 | return Uint8List.fromList(result.expand((x) => x).toList()); 147 | } 148 | 149 | @override 150 | bool needResend() { 151 | return true; 152 | } 153 | 154 | @override 155 | int getCode() { 156 | return 0; 157 | } 158 | } //end class 159 | 160 | 161 | //发送IM消息packet 162 | class SendIMMessagePacket extends AuthBaseBean{ 163 | IMMessage imMessage; 164 | 165 | SendIMMessagePacket() : super(Account.getToken()); 166 | 167 | @override 168 | int decodeModel(Uint8List rawData) { 169 | return getReadIndex(); 170 | } 171 | 172 | //是否有超时重发确认机制 173 | bool needResend() { 174 | return true; 175 | } 176 | 177 | @override 178 | Uint8List encodeModel(List result) { 179 | Uint8List imMessageUint8List = imMessage.encode(); 180 | result.add(imMessageUint8List); 181 | return imMessageUint8List; 182 | } 183 | 184 | @override 185 | int getCode() { 186 | return Codes.CODE_IMMESSAGE_SEND; 187 | } 188 | }//end class 189 | 190 | 191 | -------------------------------------------------------------------------------- /lib/model/Login.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:imclient/core/Account.dart'; 4 | import 'package:imclient/model/AuthBaseBean.dart'; 5 | import 'package:imclient/model/Codec.dart'; 6 | import 'package:imclient/core/Codes.dart'; 7 | 8 | class LoginReq extends Codec { 9 | String account; 10 | String password; 11 | 12 | @override 13 | int decode(Uint8List rawData) { 14 | resetReadIndex(); 15 | 16 | account = readString(rawData); 17 | password = readString(rawData); 18 | 19 | return getReadIndex(); 20 | } 21 | 22 | @override 23 | Uint8List encode() { 24 | List result = []; 25 | result.add(writeString(account)); 26 | result.add(writeString(password)); 27 | return Uint8List.fromList(result.expand((x) => x).toList()); 28 | } 29 | 30 | @override 31 | int getCode() { 32 | return Codes.CODE_LOGIN_REQ; 33 | } 34 | } 35 | 36 | //登录结果报文 会带上登录者的个人信息 37 | class LoginResp extends Codec { 38 | static const int RESULT_CODE_SUCCESS = 1; 39 | static const int RESULT_CODE_ERROR = -1; 40 | 41 | int resultCode; 42 | String token; 43 | String account; 44 | int uid; 45 | String avator; 46 | String name; 47 | int sex; 48 | String desc; 49 | int age; 50 | 51 | @override 52 | int decode(Uint8List rawData) { 53 | resetReadIndex(); 54 | 55 | resultCode = readInt32(rawData); 56 | token = readString(rawData); 57 | account = readString(rawData); 58 | uid = readInt64(rawData); 59 | avator = readString(rawData); 60 | name = readString(rawData); 61 | 62 | sex = readInt32(rawData); 63 | desc = readString(rawData); 64 | age = readInt32(rawData); 65 | 66 | return getReadIndex(); 67 | } 68 | 69 | @override 70 | Uint8List encode() { 71 | List result = []; 72 | 73 | result.add(writeInt32(resultCode)); 74 | result.add(writeString(token)); 75 | result.add(writeString(account)); 76 | result.add(writeInt64(uid)); 77 | result.add(writeString(avator)); 78 | result.add(writeString(name)); 79 | 80 | result.add(writeInt32(sex)); 81 | result.add(writeString(desc)); 82 | result.add(writeInt32(age)); 83 | 84 | return Uint8List.fromList(result.expand((x) => x).toList()); 85 | } 86 | 87 | @override 88 | int getCode() { 89 | return Codes.CODE_LOGIN_RESP; 90 | } 91 | } 92 | 93 | //退出登录 94 | class LoginOutReq extends AuthBaseBean { 95 | int uid; 96 | 97 | LoginOutReq(this.uid) : super(Account.getToken()); 98 | 99 | @override 100 | int decodeModel(Uint8List rawData) { 101 | uid = readInt64(rawData); 102 | return getReadIndex(); 103 | } 104 | 105 | @override 106 | Uint8List encodeModel(List result) { 107 | result.add(writeInt64(uid)); 108 | return null; 109 | } 110 | 111 | @override 112 | int getCode() { 113 | return Codes.CODE_LOGIN_OUT_REQ; 114 | } 115 | } 116 | 117 | /** 118 | * 注销登录消息 响应 119 | */ 120 | class LoginOutResp extends Codec { 121 | int resultCode; 122 | 123 | @override 124 | int decode(Uint8List rawData) { 125 | resetReadIndex(); 126 | resultCode = readInt32(rawData); 127 | 128 | return getReadIndex(); 129 | } 130 | 131 | @override 132 | Uint8List encode() { 133 | List result = []; 134 | result.add(writeInt32(resultCode)); 135 | return Uint8List.fromList(result.expand((x) => x).toList()); 136 | } 137 | 138 | @override 139 | int getCode() { 140 | return Codes.CODE_LOGIN_OUT_RESP; 141 | } 142 | } 143 | 144 | //自动登录 请求 145 | class AutoLoginReq extends AuthBaseBean{ 146 | int synType = 1; 147 | 148 | AutoLoginReq() : super(Account.getToken()); 149 | 150 | @override 151 | int decodeModel(Uint8List rawData) { 152 | synType = readInt32(rawData); 153 | 154 | return getReadIndex(); 155 | } 156 | 157 | @override 158 | Uint8List encodeModel(List result) { 159 | result.add(writeInt32(synType)); 160 | return null; 161 | } 162 | 163 | @override 164 | int getCode() { 165 | return Codes.CODE_AUTO_LOGIN_REQ; 166 | } 167 | } 168 | 169 | class AutoLoginResp extends Codec{ 170 | int resultCode; 171 | 172 | @override 173 | int decode(Uint8List rawData) { 174 | resetReadIndex(); 175 | resultCode = readInt32(rawData); 176 | 177 | return getReadIndex(); 178 | } 179 | 180 | @override 181 | Uint8List encode() { 182 | List result = []; 183 | result.add(writeInt32(resultCode)); 184 | return Uint8List.fromList(result.expand((x) => x).toList()); 185 | } 186 | 187 | @override 188 | int getCode() { 189 | return Codes.CODE_AUTO_LOGIN_REQ; 190 | } 191 | } 192 | 193 | -------------------------------------------------------------------------------- /lib/model/Msg.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:imclient/model/Codec.dart'; 4 | import 'package:imclient/util/GenUtil.dart'; 5 | 6 | //通信msg 7 | class Msg extends Codec { 8 | int length; 9 | int uuid; 10 | int code; 11 | Uint8List data; 12 | 13 | bool resend = false; 14 | 15 | Msg(); 16 | 17 | static Msg buildMsg(int code, Codec data) { 18 | final Msg msg = new Msg(); 19 | 20 | msg.code = code; 21 | msg.uuid = GenUtil.genUuid(); 22 | msg.length = 4 + 4 + 8; 23 | 24 | msg.resend = data.needResend(); 25 | 26 | if (data != null) { 27 | Uint8List dataBytes = data.encode(); 28 | msg.data = dataBytes; 29 | 30 | if (msg.data != null) { 31 | msg.length += msg.data.length; 32 | } 33 | } 34 | return msg; 35 | } 36 | 37 | @override 38 | int decode(Uint8List rawData) { 39 | resetReadIndex(); 40 | 41 | length = readInt32(rawData); 42 | uuid = readInt64(rawData); 43 | code = readInt32(rawData); 44 | 45 | data = rawData.sublist(4 + 4 + 8); 46 | 47 | return getReadIndex(); 48 | } 49 | 50 | @override 51 | Uint8List encode() { 52 | List result = []; 53 | 54 | result.add(writeInt32(length)); 55 | result.add(writeInt64(uuid)); 56 | result.add(writeInt32(code)); 57 | 58 | result.add(data); 59 | 60 | return Uint8List.fromList(result.expand((x) => x).toList()); 61 | } 62 | } //end class 63 | -------------------------------------------------------------------------------- /lib/model/Person.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:typed_data'; 3 | import 'package:imclient/model/Codec.dart'; 4 | 5 | class PesonResp extends Codec{ 6 | String content; 7 | num time; 8 | 9 | @override 10 | Uint8List encode() { 11 | List result = []; 12 | result.add(writeString(content)); 13 | result.add(writeInt64(time)); 14 | return Uint8List.fromList(result.expand((x)=>x).toList()); 15 | } 16 | 17 | @override 18 | int decode(Uint8List rawData) { 19 | resetReadIndex(); 20 | 21 | content = readString(rawData); 22 | time = readInt64(rawData); 23 | 24 | return getReadIndex(); 25 | } 26 | } 27 | 28 | class Person extends Codec{ 29 | String name; 30 | int age; 31 | String desc; 32 | 33 | Person({this.name , this.age = 31, this.desc = "你是个好人"}); 34 | 35 | factory Person.build(Uint8List rawData) { 36 | Person p = new Person(); 37 | p.decode(rawData); 38 | return p; 39 | } 40 | 41 | @override 42 | Uint8List encode() { 43 | List result = []; 44 | result.add(writeString(name)); 45 | result.add(writeInt32(age)); 46 | result.add(writeString(desc)); 47 | return Uint8List.fromList(result.expand((x)=>x).toList()); 48 | } 49 | 50 | @override 51 | int decode(Uint8List rawData) { 52 | resetReadIndex(); 53 | 54 | name = readString(rawData); 55 | age = readInt32(rawData); 56 | desc = readString(rawData); 57 | 58 | return getReadIndex(); 59 | } 60 | }//end class 61 | -------------------------------------------------------------------------------- /lib/model/RecipeAck.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:imclient/core/Codes.dart'; 4 | import 'package:imclient/model/Codec.dart'; 5 | 6 | class RecipeAck extends Codec { 7 | int uuid; 8 | 9 | @override 10 | int decode(Uint8List rawData) { 11 | resetReadIndex(); 12 | uuid = readInt64(rawData); 13 | return getReadIndex(); 14 | } 15 | 16 | @override 17 | Uint8List encode() { 18 | List result = []; 19 | result.add(writeInt64(uuid)); 20 | return Uint8List.fromList(result.expand((x) => x).toList()); 21 | } 22 | 23 | @override 24 | int getCode() { 25 | return Codes.CODE_RECIPE_ACK; 26 | } 27 | } 28 | 29 | //测试重发消息 30 | class RecipeHello extends Codec { 31 | String content; 32 | 33 | @override 34 | int decode(Uint8List rawData) { 35 | resetReadIndex(); 36 | content = readString(rawData); 37 | return getReadIndex(); 38 | } 39 | 40 | @override 41 | Uint8List encode() { 42 | List result = []; 43 | result.add(writeString(content)); 44 | return Uint8List.fromList(result.expand((x) => x).toList()); 45 | } 46 | 47 | @override 48 | int getCode() { 49 | return Codes.CODE_RECIPE_HELLO; 50 | } 51 | 52 | @override 53 | bool needResend() { 54 | return true; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/model/RecipeMsg.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:imclient/model/Codec.dart'; 4 | import 'package:imclient/util/GenUtil.dart'; 5 | 6 | /// 7 | /// 具有超时重发能力的msg 8 | /// 9 | abstract class RecipeMsg extends Codec{ 10 | int uuid; 11 | 12 | //生成 13 | static int gen16Uuid(){ 14 | return GenUtil.genUuid(); 15 | } 16 | 17 | @override 18 | Uint8List encode() { 19 | List result = []; 20 | result.add(writeInt64(uuid)); 21 | 22 | encodeReciptMsgBody(result); 23 | 24 | return Uint8List.fromList(result.expand((x)=>x).toList()); 25 | } 26 | 27 | @override 28 | int decode(Uint8List rawData) { 29 | resetReadIndex(); 30 | uuid = readInt64(rawData); 31 | 32 | decodeRecipeMsgBody(rawData); 33 | 34 | return getReadIndex(); 35 | } 36 | 37 | //子类实现 编码消息体 38 | Uint8List encodeReciptMsgBody(List result); 39 | 40 | //子类实现 解码消息体 41 | int decodeRecipeMsgBody(Uint8List rawData); 42 | 43 | }//end class -------------------------------------------------------------------------------- /lib/model/bytebuf.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'dart:convert' show utf8; 3 | import 'package:imclient/model/Msg.dart'; 4 | 5 | 6 | class ByteBufUtil { 7 | 8 | static Msg readMsg(Uint8List rawData){ 9 | if(rawData == null) 10 | return null; 11 | 12 | Msg msg = new Msg(); 13 | msg.decode(rawData); 14 | 15 | // print("======================================"); 16 | // print(msg); 17 | // print("======================================"); 18 | return msg; 19 | } 20 | 21 | static String readString(Uint8List data){ 22 | return utf8.decode(data); 23 | //return String.fromCharCodes(data); 24 | } 25 | 26 | // 27 | static List convertUint8ListToMutable(Uint8List data){ 28 | List result = []; 29 | if(data == null) 30 | return result; 31 | 32 | for(int i = 0 ; i list){ 39 | Uint8List bytes = Uint8List.fromList(list); 40 | int result = bytes.sublist(0).buffer.asInt32List()[0]; 41 | return result; 42 | } 43 | } 44 | 45 | 46 | -------------------------------------------------------------------------------- /lib/page/BaseState.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | import 'package:imclient/core/IMClient.dart'; 4 | 5 | abstract class BaseState extends State with ClientCallback{ 6 | @override 7 | void initState(){ 8 | super.initState(); 9 | IMClient.getInstance().addListener(this); 10 | } 11 | 12 | @override 13 | void dispose(){ 14 | IMClient.getInstance().removeListener(this); 15 | super.dispose(); 16 | } 17 | }//end class 18 | -------------------------------------------------------------------------------- /lib/page/ContactsPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:imclient/core/Codes.dart'; 3 | import 'package:imclient/core/IMClient.dart'; 4 | import 'package:imclient/im/FriendDataCache.dart'; 5 | import 'package:imclient/model/Friend.dart'; 6 | import 'package:imclient/model/Msg.dart'; 7 | import 'package:imclient/page/BaseState.dart'; 8 | import 'package:imclient/page/FriendCardPage.dart'; 9 | 10 | class ContactsPage extends StatefulWidget{ 11 | @override 12 | State createState() => ContactsPageState(); 13 | } 14 | 15 | class ContactsPageState extends BaseState { 16 | static const int TAB_INDEX_CONTACT =1; 17 | 18 | static const String content = "好友"; 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | } 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Scaffold( 28 | body: Align( 29 | child: ListView.builder( 30 | padding: EdgeInsets.fromLTRB(0, 10, 0, 0), 31 | itemCount: FriendDataCache.instance().get().length, 32 | itemBuilder: _craeteContactItem 33 | ), 34 | ), 35 | ); 36 | } 37 | 38 | //创建每一项 39 | Widget _craeteContactItem(BuildContext context, int index){ 40 | final Friend itemData = FriendDataCache.instance().get()[index]; 41 | 42 | return Column( 43 | children: [ 44 | ListTile( 45 | leading:ClipOval( 46 | child:Container( 47 | child: Hero(tag: itemData.avator, child: Image.network(itemData.avator,fit: BoxFit.cover,)), 48 | color: Colors.grey, 49 | width: 50.0, 50 | height: 50.0, 51 | ), 52 | ), 53 | title: Text(itemData.nick), 54 | onTap: (){ 55 | Navigator.of(context).push(MaterialPageRoute( 56 | builder: (BuildContext context) => FriendCardPage(itemData) 57 | )); 58 | }, 59 | ), 60 | Divider( 61 | height:10.0, 62 | color: Colors.grey, 63 | indent:70.0 64 | ) 65 | ], 66 | ); 67 | 68 | } 69 | 70 | @override 71 | void dispose() { 72 | super.dispose(); 73 | } 74 | 75 | @override 76 | void onReceivedMsg(Msg msg) { 77 | if(msg.code == Codes.CODE_FRIEND_LIST_RESP){//更新了好友列表数据 78 | setState(() {}); 79 | } 80 | } 81 | 82 | @override 83 | void onNetStatusChange(NetStatus oldStatus, NetStatus newStatus) { 84 | 85 | } 86 | }//end class 87 | -------------------------------------------------------------------------------- /lib/page/FriendCardPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ffi'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:imclient/model/Friend.dart'; 5 | import 'package:imclient/model/Msg.dart'; 6 | import 'package:imclient/core/IMClient.dart'; 7 | import 'package:imclient/page/MessagePage.dart'; 8 | 9 | import 'BaseState.dart'; 10 | 11 | /// 12 | /// 好友名片页 13 | /// 14 | class FriendCardPage extends StatefulWidget{ 15 | final Friend _friend; 16 | 17 | FriendCardPage(this._friend); 18 | 19 | @override 20 | State createState() => _FriendCardPageState(_friend); 21 | } 22 | 23 | class _FriendCardPageState extends BaseState{ 24 | final Friend friend; 25 | 26 | _FriendCardPageState(this.friend); 27 | 28 | @override 29 | void initState() { 30 | super.initState(); 31 | } 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return Scaffold( 36 | appBar: AppBar( 37 | title: Text("${friend.displayName()}"), 38 | ), 39 | body: Column( 40 | children: [ 41 | Hero( 42 | tag: friend.avator, 43 | child: Image.network( 44 | friend.avator, 45 | width: double.infinity, 46 | height: 230.0, 47 | fit: BoxFit.fitWidth, 48 | ), 49 | ), 50 | Padding( 51 | padding: EdgeInsets.fromLTRB(10, 5, 10, 5), 52 | child: Align( 53 | alignment:Alignment.topLeft, 54 | child: Text(friend.nick,style: TextStyle(fontSize: 28.0),textAlign:TextAlign.left), 55 | ) 56 | ), 57 | Divider( 58 | height: 1.0, 59 | color: Colors.white, 60 | indent: 10.0, 61 | endIndent: 10.0, 62 | ), 63 | Padding( 64 | padding: EdgeInsets.fromLTRB(10, 0, 10, 0), 65 | child: Align( 66 | alignment:Alignment.topLeft, 67 | child: Text( 68 | friend.desc, 69 | style: TextStyle(fontSize: 18.0,color: Colors.grey), 70 | textAlign:TextAlign.left 71 | ), 72 | ) 73 | ), 74 | Expanded(child: SizedBox(height: double.infinity)), 75 | Padding( 76 | padding: EdgeInsets.fromLTRB(20, 0, 20, 0), 77 | child: MaterialButton( 78 | child:Text("发送消息",style: TextStyle(color:Colors.white)), 79 | color: Colors.blue, 80 | minWidth: double.infinity, 81 | onPressed: (){ //点击进入单聊 82 | Navigator.of(context).pop(); 83 | 84 | Navigator.of(context).push(MaterialPageRoute( 85 | builder: (BuildContext context) => P2PMessagePage(friend) 86 | )); 87 | }, 88 | ) 89 | ), 90 | Padding( 91 | padding: EdgeInsets.fromLTRB(20, 0, 20, 20), 92 | child: MaterialButton( 93 | child:Text("删除好友",style: TextStyle(color:Colors.white)), 94 | minWidth: double.infinity, 95 | color: Colors.red, 96 | onPressed: (){ 97 | }, 98 | ) 99 | ) 100 | ], 101 | ), 102 | ); 103 | } 104 | 105 | @override 106 | void onNetStatusChange(NetStatus oldStatus, NetStatus newStatus) { 107 | 108 | } 109 | 110 | @override 111 | void onReceivedMsg(Msg msg) { 112 | 113 | } 114 | 115 | }//end class -------------------------------------------------------------------------------- /lib/page/LoginPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:imclient/core/IMClient.dart'; 3 | import 'package:imclient/model/Msg.dart'; 4 | import 'package:imclient/model/Person.dart'; 5 | import 'package:imclient/core/Codes.dart'; 6 | import 'package:imclient/page/MainPage.dart'; 7 | import 'package:imclient/util/TextUtil.dart'; 8 | import 'package:fluttertoast/fluttertoast.dart'; 9 | 10 | // 11 | class LoginPage extends StatefulWidget { 12 | @override 13 | State createState() => _LoginPageState(); 14 | } 15 | 16 | // 17 | class _LoginPageState extends State 18 | with ClientCallback, AuthCallback { 19 | String _statusContent = "未连接"; 20 | 21 | String _account; 22 | String _pwd; 23 | 24 | bool _loginBtnEnable = false; //提交按钮是否可用 25 | 26 | @override 27 | void initState() { 28 | super.initState(); 29 | IMClient.getInstance().addListener(this); 30 | IMClient.getInstance().addAuthCallback(this); 31 | } 32 | 33 | @override 34 | void dispose() { 35 | IMClient.getInstance().removeListener(this); 36 | IMClient.getInstance().clearAuthCallback(); 37 | super.dispose(); 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | return Scaffold( 43 | appBar: AppBar( 44 | title: Text("登录"), 45 | ), 46 | body: Padding( 47 | padding: EdgeInsets.fromLTRB(20, 0, 20, 0), 48 | child: Column( 49 | children: [ 50 | TextField( 51 | maxLines: 1, 52 | decoration: 53 | InputDecoration(labelText: "用户名", hintText: "输入用户名"), 54 | onChanged: (text) { 55 | //内容改变的回调 56 | _account = text; 57 | _onInputTextChange(); 58 | }, 59 | ), 60 | TextField( 61 | maxLines: 1, 62 | obscureText: true, 63 | decoration: InputDecoration( 64 | labelText: "密码", 65 | hintText: "输入登录密码", 66 | ), 67 | onChanged: (inputPwd) { 68 | //内容改变的回调 69 | _pwd = inputPwd; 70 | _onInputTextChange(); 71 | }), 72 | SizedBox( 73 | height: 15, 74 | ), 75 | ButtonTheme( 76 | minWidth: double.infinity, 77 | child: RaisedButton( 78 | onPressed: _loginBtnEnable ? _onClickLoginButton : null, 79 | textColor: Colors.white, 80 | color: Colors.blue, 81 | child: Text("登录"), 82 | )) 83 | ], 84 | ), 85 | ) 86 | 87 | // floatingActionButton: FloatingActionButton( 88 | // onPressed: () { 89 | // }, 90 | // child: Icon(Icons.add), 91 | // ), 92 | ); 93 | } 94 | 95 | //输入内容改变 联动修改提交按钮状态 96 | void _onInputTextChange() { 97 | //print("accout = $_account pwd = $_pwd"); 98 | bool enbaleSubmit = !TextUtil.isEmpty(_account) && !TextUtil.isEmpty(_pwd); 99 | 100 | if (enbaleSubmit != _loginBtnEnable) { 101 | setState(() { 102 | _loginBtnEnable = enbaleSubmit; 103 | }); 104 | } 105 | } 106 | 107 | //点击登录按钮 108 | void _onClickLoginButton() { 109 | print("accout = $_account pwd = $_pwd"); 110 | IMClient.getInstance().auth(_account, _pwd); 111 | } 112 | 113 | @override 114 | void onNetStatusChange(NetStatus oldStatus, NetStatus newStatus) { 115 | String content; 116 | switch (newStatus) { 117 | case NetStatus.offline: 118 | content = "未连接"; 119 | break; 120 | case NetStatus.connecting: 121 | content = "连接中..."; 122 | break; 123 | case NetStatus.online: 124 | content = "已连接"; 125 | break; 126 | } //end switch 127 | 128 | print("content : $content"); 129 | setState(() { 130 | _statusContent = content; 131 | }); 132 | } 133 | 134 | @override 135 | void onReceivedMsg(Msg msg) { 136 | if (msg.code == Codes.CODE_PERSON_RESP) { 137 | PesonResp pesonResp = PesonResp(); 138 | pesonResp.decode(msg.data); 139 | 140 | DateTime t = DateTime.fromMillisecondsSinceEpoch(pesonResp.time); 141 | 142 | String info = "${t.toLocal().toString()} ${pesonResp.content}"; 143 | print("$info"); 144 | } 145 | } 146 | 147 | void _skipToMain() async { 148 | await Navigator.of(context).pop(); 149 | 150 | await Navigator.of(context) 151 | .push(MaterialPageRoute(builder: (BuildContext context) => MainPage())); 152 | } 153 | 154 | @override 155 | void onAuthError(int errorCode) { 156 | print("login Error code = $errorCode"); 157 | FlutterToast.showToast(msg: "用户名或密码错误"); 158 | } 159 | 160 | @override 161 | void onAuthSuccess(String token, String account, int uid) { 162 | print("success login! token = $token , accout = $account uid=$uid"); 163 | 164 | _skipToMain(); 165 | } 166 | } // 167 | -------------------------------------------------------------------------------- /lib/page/MainPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fluttertoast/fluttertoast.dart'; 3 | import 'package:imclient/page/ContactsPage.dart'; 4 | import 'package:imclient/page/LoginPage.dart'; 5 | import 'package:imclient/page/SessionPage.dart'; 6 | import 'package:imclient/page/SettingPage.dart'; 7 | import 'package:imclient/core/IMClient.dart'; 8 | 9 | //主界面 10 | class MainPage extends StatefulWidget { 11 | @override 12 | State createState() => _MainPageState(); 13 | } 14 | 15 | //main page 16 | class _MainPageState extends State with LoginOutCallback { 17 | String _title = SessionPageState.content; 18 | int mTabIndex = SessionPageState.TAB_INDEX_SESSION; 19 | 20 | List mMainTabs = [ 21 | SessionPage(), 22 | ContactsPage(), 23 | SettingPage() 24 | ]; 25 | 26 | @override 27 | void initState() { 28 | super.initState(); 29 | IMClient.getInstance().addLoginOutCallback(this); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return Scaffold( 35 | appBar: AppBar( 36 | title: Text(_title), 37 | ), 38 | body: Center( 39 | child: mMainTabs[mTabIndex], 40 | ), 41 | bottomNavigationBar: BottomNavigationBar( 42 | currentIndex: mTabIndex, 43 | type: BottomNavigationBarType.fixed, 44 | backgroundColor: Color.fromARGB(255, 245, 245, 245), 45 | selectedFontSize: 12.0, 46 | unselectedFontSize: 12.0, 47 | onTap: _switchTab, 48 | items: [ 49 | BottomNavigationBarItem(title: Text("对话"), icon: Icon(Icons.chat)), 50 | BottomNavigationBarItem( 51 | title: Text("好友"), icon: Icon(Icons.contact_mail)), 52 | BottomNavigationBarItem( 53 | title: Text("设置"), icon: Icon(Icons.settings)) 54 | ]), 55 | ); 56 | } 57 | 58 | _switchTab(int newTabIndex) { 59 | setState(() { 60 | mTabIndex = newTabIndex; 61 | switch (mTabIndex) { 62 | case SessionPageState.TAB_INDEX_SESSION: 63 | _title = SessionPageState.content; 64 | break; 65 | case ContactsPageState.TAB_INDEX_CONTACT: 66 | _title = ContactsPageState.content; 67 | break; 68 | case SettingPageState.TAB_INDEX_SETTING: 69 | _title = SettingPageState.content; 70 | break; 71 | } //end switch 72 | }); 73 | } 74 | 75 | @override 76 | void loginOutError() { 77 | FlutterToast.showToast(msg: "注销失败"); 78 | } 79 | 80 | @override 81 | void loginOutSuccess() async { 82 | await Navigator.of(context).pop(); 83 | 84 | await Navigator.of(context).push(MaterialPageRoute( 85 | builder: (BuildContext context) => LoginPage() 86 | )); 87 | } 88 | 89 | @override 90 | void dispose(){ 91 | IMClient.getInstance().clearLoginOutCallback(); 92 | super.dispose(); 93 | } 94 | } //end class 95 | -------------------------------------------------------------------------------- /lib/page/MessagePage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:imclient/model/Friend.dart'; 3 | import 'package:imclient/model/IMMessage.dart'; 4 | import 'package:imclient/util/TextUtil.dart'; 5 | 6 | class P2PMessagePage extends StatefulWidget{ 7 | final Friend _friend; 8 | 9 | P2PMessagePage(this._friend); 10 | 11 | @override 12 | State createState() => P2PMessageState(_friend); 13 | } 14 | 15 | class P2PMessageState extends State{ 16 | final Friend _friend; 17 | 18 | List messageList = []; 19 | 20 | P2PMessageState(this._friend); 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | } 26 | 27 | @override 28 | void dispose() { 29 | super.dispose(); 30 | } 31 | 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return Scaffold( 36 | appBar: AppBar( 37 | title: Text(_friend.displayName()), 38 | ), 39 | body: Column( 40 | children: [ 41 | Expanded( 42 | child: Text("列表"), 43 | ), 44 | Row( 45 | children: [ 46 | Expanded( 47 | flex:5, 48 | child: TextField( 49 | obscureText: false, 50 | decoration: InputDecoration( 51 | border: OutlineInputBorder(), 52 | ), 53 | ), 54 | ), 55 | Expanded( 56 | flex:1, 57 | child: RaisedButton( 58 | child: Text("发送"), 59 | onPressed: (){ 60 | 61 | }, 62 | ), 63 | ), 64 | ], 65 | ) 66 | ], 67 | ), 68 | ); 69 | } 70 | 71 | //发送文本消息 72 | void sendTextIMMessage(String content){ 73 | if(TextUtil.isEmpty(content)) 74 | return; 75 | 76 | IMMessage imMsg = IMMessage.createTextIMMessage(SessionType.P2P, _friend.uid, content); 77 | 78 | } 79 | 80 | }//end class -------------------------------------------------------------------------------- /lib/page/SessionPage.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | import 'package:imclient/core/IMClient.dart'; 4 | import 'package:imclient/model/Bytebuf.dart'; 5 | import 'package:imclient/model/Msg.dart'; 6 | import 'package:imclient/page/BaseState.dart'; 7 | 8 | class SessionPage extends StatefulWidget{ 9 | @override 10 | State createState() => SessionPageState(); 11 | } 12 | 13 | class SessionPageState extends BaseState { 14 | static const int TAB_INDEX_SESSION =0; 15 | 16 | static String content = "会话"; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Scaffold( 21 | body: Center( 22 | child: Text( 23 | content, 24 | style: TextStyle( 25 | fontSize: 40, 26 | color: Colors.blueAccent, 27 | ), 28 | ), 29 | ), 30 | ); 31 | } 32 | 33 | @override 34 | void onReceivedMsg(Msg msg) { 35 | 36 | } 37 | 38 | @override 39 | void onNetStatusChange(NetStatus oldStatus, NetStatus newStatus) { 40 | 41 | } 42 | 43 | }//end class 44 | -------------------------------------------------------------------------------- /lib/page/SettingPage.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | import 'package:imclient/core/Account.dart'; 4 | import 'package:imclient/core/IMClient.dart'; 5 | import 'package:imclient/model/Friend.dart'; 6 | import 'package:imclient/model/Msg.dart'; 7 | import 'package:imclient/page/BaseState.dart'; 8 | 9 | class SettingPage extends StatefulWidget{ 10 | @override 11 | State createState() => SettingPageState(); 12 | } 13 | 14 | class SettingPageState extends BaseState { 15 | static const int TAB_INDEX_SETTING =2; 16 | 17 | static const String content = "设置"; 18 | 19 | @override 20 | void initState() { 21 | super.initState(); 22 | } 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | print("--->${Account.getAvator()}"); 27 | return Scaffold( 28 | body: Padding( 29 | padding: EdgeInsets.all(20.0), 30 | child: Column( 31 | children: [ 32 | Align( 33 | alignment:Alignment.center, 34 | child: CircleAvatar( 35 | radius: 50.0, 36 | backgroundImage: NetworkImage(Account.getAvator()), 37 | backgroundColor:Colors.black12, 38 | ), 39 | ), 40 | SizedBox( 41 | height: 10.0, 42 | ), 43 | Align( 44 | alignment:Alignment.center, 45 | child: Row( 46 | mainAxisAlignment: MainAxisAlignment.center, 47 | children: [ 48 | Image.asset(Account.sex==Friend.SEX_FEMALE? "assets/images/icon_female.png":"assets/images/icon_male.png",width: 20.0,height: 20.0,), 49 | SizedBox(width: 10.0,), 50 | Text(Account.name,style: TextStyle(fontSize: 25.0)), 51 | ], 52 | ) 53 | ), 54 | SizedBox( 55 | height: 10.0, 56 | ), 57 | Align( 58 | alignment:Alignment.center, 59 | child: Text(Account.desc,style: TextStyle(fontSize: 15.0 ,color: Colors.grey)), 60 | ), 61 | Expanded( 62 | child: SizedBox( 63 | ), 64 | ), 65 | ButtonTheme( 66 | minWidth:double.infinity, 67 | child: FlatButton( 68 | onPressed: (){ 69 | _loginOut(); 70 | }, 71 | textColor: Colors.white, 72 | color: Colors.redAccent, 73 | padding: EdgeInsets.all(8), 74 | child:Text("退出登录" , style:TextStyle(fontSize: 18.0)), 75 | ) 76 | ) 77 | ], 78 | ) 79 | ), 80 | ); 81 | } 82 | 83 | void _loginOut(){ 84 | showDialog( 85 | context: context , 86 | builder: (context){ 87 | return AlertDialog( 88 | content: Text("您确定要退出登录吗?"), 89 | actions: [ 90 | FlatButton( 91 | child: Text("取消"), 92 | onPressed: () => Navigator.of(context).pop(), // 关闭对话框 93 | ), 94 | FlatButton( 95 | child: Text("退出登录"), 96 | onPressed: () { 97 | IMClient.getInstance().loginOut(); 98 | Navigator.of(context).pop(); 99 | }, // 100 | ) 101 | ], 102 | ); 103 | } 104 | ); 105 | } 106 | 107 | void _doLoginOut(){ 108 | 109 | } 110 | 111 | @override 112 | void dispose() { 113 | super.dispose(); 114 | } 115 | 116 | @override 117 | void onReceivedMsg(Msg msg) { 118 | 119 | } 120 | 121 | @override 122 | void onNetStatusChange(NetStatus oldStatus, NetStatus newStatus) { 123 | 124 | } 125 | 126 | }//end class 127 | -------------------------------------------------------------------------------- /lib/page/pages.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:imclient/core/IMClient.dart'; 3 | import 'package:imclient/model/Msg.dart'; 4 | import 'package:imclient/model/bytebuf.dart'; 5 | import 'SessionPage.dart'; 6 | 7 | 8 | class WebSocketRoute extends StatefulWidget{ 9 | @override 10 | State createState() => _WebSocketRouteState(); 11 | } 12 | 13 | class _WebSocketRouteState extends State 14 | with WidgetsBindingObserver , ClientCallback { 15 | String netStatusShow; 16 | String content; 17 | 18 | @override 19 | void initState() { 20 | super.initState(); 21 | WidgetsBinding.instance.addObserver(this); 22 | IMClient.getInstance().init(); 23 | 24 | content = "[]"; 25 | 26 | IMClient.getInstance().addListener(this); 27 | } 28 | 29 | @override 30 | void didChangeAppLifecycleState(AppLifecycleState state) { 31 | print(state); 32 | if(state == AppLifecycleState.detached){ 33 | print("page dispose()"); 34 | IMClient.getInstance().dispose(); 35 | } 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return Scaffold( 41 | appBar: AppBar( 42 | title: Text(netStatusShow), 43 | ), 44 | body: Center( 45 | child: Text(content), 46 | ), 47 | floatingActionButton: FloatingActionButton( 48 | onPressed: () async { 49 | await Navigator.of(context).push(MaterialPageRoute( 50 | builder: (context) => SessionPage(), 51 | )); 52 | }, 53 | child: Icon(Icons.add), 54 | ), 55 | ); 56 | } 57 | 58 | @override 59 | void dispose() { 60 | IMClient.getInstance().removeListener(this); 61 | WidgetsBinding.instance.removeObserver(this); 62 | super.dispose(); 63 | } 64 | 65 | @override 66 | void onReceivedMsg(final Msg msg) { 67 | String str = ByteBufUtil.readString(msg.data); 68 | setState(() { 69 | content = str; 70 | }); 71 | //print("received msg ${msg.code}"); 72 | } 73 | 74 | @override 75 | void onNetStatusChange(NetStatus oldStatus, NetStatus newStatus) { 76 | 77 | } 78 | }//end class -------------------------------------------------------------------------------- /lib/util/GenUtil.dart: -------------------------------------------------------------------------------- 1 | import 'package:sprintf/sprintf.dart'; 2 | import 'package:uuid/uuid.dart'; 3 | 4 | class GenUtil{ 5 | static const String GEN_BY_SERVER = "11"; 6 | static const String GEN_BY_MOBILE = "22"; 7 | 8 | static int genUuid(){ 9 | String machineId = GEN_BY_MOBILE; 10 | var uuid = Uuid(); 11 | int hashCode = uuid.v1().hashCode; 12 | 13 | if(hashCode < 0){ 14 | hashCode = -hashCode; 15 | } 16 | 17 | var now =new DateTime.now(); 18 | String dayTime = fillNum2Weight(now.month) + fillNum2Weight(now.day); 19 | //print("dayTime = $dayTime"); 20 | int value = int.tryParse(machineId + dayTime + sprintf("%010d", [hashCode])); 21 | return value; 22 | } 23 | 24 | static String fillNum2Weight(int value){ 25 | if(value < 0){ 26 | value = -value; 27 | } 28 | 29 | return (value >= 10)?"$value":"0$value"; 30 | } 31 | } -------------------------------------------------------------------------------- /lib/util/LruCache.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | /// 4 | ///固定大小的LruCache 5 | /// by panyi 6 | /// 7 | 8 | class LruCache{ 9 | static const int DEFAULT_CACHE_SIZE = 256; 10 | 11 | LinkedHashMap _map = LinkedHashMap(); 12 | int _cacheSize = DEFAULT_CACHE_SIZE; 13 | 14 | LruCache({cacheSize: DEFAULT_CACHE_SIZE}){ 15 | _cacheSize = cacheSize; 16 | } 17 | 18 | void put(K key , V value){ 19 | _map[key] = value; 20 | _afterPutMap(); 21 | } 22 | 23 | V get(K key){ 24 | V result = _map[key]; 25 | return result; 26 | } 27 | 28 | void _afterPutMap(){ 29 | while(_map.length > _cacheSize){ 30 | var keyIter = _map.keys; 31 | //print(keyIter); 32 | _map.remove(keyIter.first); 33 | //print("remove elem $rmV"); 34 | }//end while 35 | } 36 | } 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /lib/util/TextUtil.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | class TextUtil{ 4 | static bool isEmpty(String str){ 5 | return str == null || str.length == 0; 6 | } 7 | 8 | }//end class 9 | 10 | -------------------------------------------------------------------------------- /lib/util/TimeUtil.dart: -------------------------------------------------------------------------------- 1 | class TimeUtil{ 2 | 3 | // 取得当前毫秒时间 4 | static int getNowMilliseconds(){ 5 | var date = DateTime.now(); 6 | return date.millisecondsSinceEpoch; 7 | } 8 | 9 | }//end class -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.4.1" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.0.0" 18 | charcode: 19 | dependency: transitive 20 | description: 21 | name: charcode 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.1.3" 25 | clock: 26 | dependency: transitive 27 | description: 28 | name: clock 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.0.1" 32 | collection: 33 | dependency: transitive 34 | description: 35 | name: collection 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.14.12" 39 | cupertino_icons: 40 | dependency: "direct main" 41 | description: 42 | name: cupertino_icons 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "0.1.3" 46 | fake_async: 47 | dependency: transitive 48 | description: 49 | name: fake_async 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.1.0" 53 | flutter: 54 | dependency: "direct main" 55 | description: flutter 56 | source: sdk 57 | version: "0.0.0" 58 | flutter_test: 59 | dependency: "direct dev" 60 | description: flutter 61 | source: sdk 62 | version: "0.0.0" 63 | matcher: 64 | dependency: transitive 65 | description: 66 | name: matcher 67 | url: "https://pub.dartlang.org" 68 | source: hosted 69 | version: "0.12.6" 70 | meta: 71 | dependency: transitive 72 | description: 73 | name: meta 74 | url: "https://pub.dartlang.org" 75 | source: hosted 76 | version: "1.1.8" 77 | path: 78 | dependency: transitive 79 | description: 80 | name: path 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "1.7.0" 84 | sky_engine: 85 | dependency: transitive 86 | description: flutter 87 | source: sdk 88 | version: "0.0.99" 89 | source_span: 90 | dependency: transitive 91 | description: 92 | name: source_span 93 | url: "https://pub.dartlang.org" 94 | source: hosted 95 | version: "1.7.0" 96 | stack_trace: 97 | dependency: transitive 98 | description: 99 | name: stack_trace 100 | url: "https://pub.dartlang.org" 101 | source: hosted 102 | version: "1.9.3" 103 | stream_channel: 104 | dependency: transitive 105 | description: 106 | name: stream_channel 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "2.0.0" 110 | string_scanner: 111 | dependency: transitive 112 | description: 113 | name: string_scanner 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "1.0.5" 117 | term_glyph: 118 | dependency: transitive 119 | description: 120 | name: term_glyph 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "1.1.0" 124 | test_api: 125 | dependency: transitive 126 | description: 127 | name: test_api 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "0.2.16" 131 | typed_data: 132 | dependency: transitive 133 | description: 134 | name: typed_data 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "1.1.6" 138 | vector_math: 139 | dependency: transitive 140 | description: 141 | name: vector_math 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "2.0.8" 145 | sdks: 146 | dart: ">=2.7.0 <3.0.0" 147 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: imclient 2 | description: a im client written by Flutter 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.7.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | 27 | # The following adds the Cupertino Icons font to your application. 28 | # Use with the CupertinoIcons class for iOS style icons. 29 | shared_preferences: ^0.5.7+3 30 | cupertino_icons: ^0.1.3 31 | fluttertoast: ^5.0.1 32 | uuid: 2.2.0 33 | sprintf: ^4.0.0 34 | 35 | dev_dependencies: 36 | #shared_preferences: ^0.5.7+3 37 | 38 | flutter_test: 39 | sdk: flutter 40 | 41 | # For information on the generic Dart part of this file, see the 42 | # following page: https://dart.dev/tools/pub/pubspec 43 | 44 | # The following section is specific to Flutter. 45 | flutter: 46 | 47 | # The following line ensures that the Material Icons font is 48 | # included with your application, so that you can use the icons in 49 | # the material Icons class. 50 | uses-material-design: true 51 | 52 | # To add assets to your application, add an assets section, like this: 53 | assets: 54 | - assets/images/ 55 | 56 | # An image asset can refer to one or more resolution-specific "variants", see 57 | # https://flutter.dev/assets-and-images/#resolution-aware. 58 | 59 | # For details regarding adding assets from package dependencies, see 60 | # https://flutter.dev/assets-and-images/#from-packages 61 | 62 | # To add custom fonts to your application, add a fonts section here, 63 | # in this "flutter" section. Each entry in this list should have a 64 | # "family" key with the font family name, and a "fonts" key with a 65 | # list giving the asset and other descriptors for the font. For 66 | # example: 67 | # fonts: 68 | # - family: Schyler 69 | # fonts: 70 | # - asset: fonts/Schyler-Regular.ttf 71 | # - asset: fonts/Schyler-Italic.ttf 72 | # style: italic 73 | # - family: Trajan Pro 74 | # fonts: 75 | # - asset: fonts/TrajanPro.ttf 76 | # - asset: fonts/TrajanPro_Bold.ttf 77 | # weight: 700 78 | # 79 | # For details regarding fonts from package dependencies, 80 | # see https://flutter.dev/custom-fonts/#from-packages 81 | -------------------------------------------------------------------------------- /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:imclient/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | --------------------------------------------------------------------------------