Jelajahi Sumber

android 消息模块功能,修复无法下载文件等bug

fancy 5 tahun lalu
induk
melakukan
ca0fea5d14
43 mengubah file dengan 1773 tambahan dan 643 penghapusan
  1. 3 0
      o2android/app/build.gradle
  2. 4 2
      o2android/app/src/main/AndroidManifest.xml
  3. 4 0
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/O2App.kt
  4. 18 38
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/bbs/view/BBSWebViewSubjectActivity.kt
  5. 84 56
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/clouddrive/CloudDriveActivity.kt
  6. 84 54
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/clouddrive/v2/CloudDiskFileDownloadHelper.kt
  7. 44 62
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/clouddrive/viewer/CloudDrivePictureViewerFragment.kt
  8. 49 37
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/cms/view/CMSWebViewPresenter.kt
  9. 385 40
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/O2ChatActivity.kt
  10. 5 1
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/O2ChatContract.kt
  11. 94 19
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/O2ChatMessageAdapter.kt
  12. 97 15
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/O2ChatPresenter.kt
  13. 4 0
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/O2IM.kt
  14. 291 0
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/O2LocationActivity.kt
  15. 7 5
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/fm/O2IMConversationFragment.kt
  16. 0 1
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/meeting/invited/MeetingDetailInfoActivity.kt
  17. 16 73
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/meeting/invited/MeetingDetailInfoPresenter.kt
  18. 107 54
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/o2/webview/DownloadDocument.kt
  19. 93 74
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/o2/webview/TaskWebViewPresenter.kt
  20. 12 2
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/core/service/PictureLoaderService.kt
  21. 73 0
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/utils/O2FileDownloadHelper.kt
  22. 13 0
      o2android/app/src/main/res/drawable/f5_circle.xml
  23. 5 4
      o2android/app/src/main/res/layout/activity_local_image_view.xml
  24. 38 2
      o2android/app/src/main/res/layout/activity_o2_chat.xml
  25. 38 0
      o2android/app/src/main/res/layout/activity_o2_location.xml
  26. 46 0
      o2android/app/src/main/res/layout/item_o2_chat_message_text_left.xml
  27. 46 0
      o2android/app/src/main/res/layout/item_o2_chat_message_text_right.xml
  28. 11 0
      o2android/app/src/main/res/menu/menu_location_send.xml
  29. TEMPAT SAMPAH
      o2android/app/src/main/res/mipmap-xhdpi/chat_icon_audio_play.png
  30. TEMPAT SAMPAH
      o2android/app/src/main/res/mipmap-xhdpi/chat_location_background.png
  31. 3 0
      o2android/app/src/main/res/values/strings.xml
  32. 24 0
      o2android/o2_auth_sdk/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/core/component/api/APIAddressHelper.kt
  33. 1 8
      o2android/o2_auth_sdk/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/core/component/api/service/BBSAssembleControlService.kt
  34. 0 9
      o2android/o2_auth_sdk/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/core/component/api/service/CMSAssembleControlService.kt
  35. 0 10
      o2android/o2_auth_sdk/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/core/component/api/service/CloudFileControlService.kt
  36. 1 8
      o2android/o2_auth_sdk/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/core/component/api/service/FileAssembleControlService.kt
  37. 0 8
      o2android/o2_auth_sdk/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/core/component/api/service/MeetingAssembleControlService.kt
  38. 16 0
      o2android/o2_auth_sdk/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/core/component/api/service/MessageCommunicateService.kt
  39. 1 9
      o2android/o2_auth_sdk/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/core/component/api/service/OrgAssembleExpressService.kt
  40. 19 19
      o2android/o2_auth_sdk/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/core/component/api/service/ProcessAssembleSurfaceService.kt
  41. 13 26
      o2android/o2_auth_sdk/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/model/bo/api/im/IMMessage.kt
  42. 12 7
      o2android/o2_auth_sdk/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/model/bo/api/im/IMMessageBody.kt
  43. 12 0
      o2android/o2_auth_sdk/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/model/bo/api/im/IMMessageFileData.kt

+ 3 - 0
o2android/app/build.gradle

@@ -317,6 +317,9 @@ dependencies {
         exclude group: 'com.android.support', module: 'support-media-compat'
     }
 
+    //mp3录音
+    implementation 'com.github.zhaolewei:ZlwAudioRecorder:v1.07'
+
 
 }
 

+ 4 - 2
o2android/app/src/main/AndroidManifest.xml

@@ -41,7 +41,8 @@
         android:label="@string/app_name"
         android:roundIcon="@mipmap/logo_round"
         android:theme="@style/XBPMTheme.NoActionBar">
-        <activity android:name=".app.im.O2InstantMessageActivity"></activity>
+        <activity android:name=".app.im.O2LocationActivity"></activity>
+        <activity android:name=".app.im.O2InstantMessageActivity" />
         <activity android:name=".app.im.O2ChatActivity" />
         <activity android:name=".app.VideoPlayerActivity" />
         <activity
@@ -427,7 +428,8 @@
         <service android:name=".core.service.RestartSelfService" /> <!-- jpush -->
         <service
             android:name=".core.service.WebSocketService"
-            android:exported="false" />
+            android:exported="false" /> <!-- 录音 转mp3 service -->
+        <service android:name="com.zlw.main.recorderlib.recorder.RecordService" />
 
         <receiver
             android:name=".core.receiver.JpushNoticeBroadReceiver"

+ 4 - 0
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/O2App.kt

@@ -16,6 +16,7 @@ import com.baidu.mapapi.SDKInitializer
 import com.facebook.drawee.backends.pipeline.Fresco
 import com.tencent.bugly.crashreport.CrashReport
 import com.tencent.smtt.sdk.QbSdk
+import com.zlw.main.recorderlib.RecordManager
 import io.realm.Realm
 import jiguang.chat.application.JGApplication
 import jiguang.chat.entity.NotificationClickEventReceiver
@@ -116,6 +117,9 @@ class O2App : MultiDexApplication() {
         //注册日志记录器
         LogSingletonService.instance().registerApp(this)
 
+        //录音
+        RecordManager.getInstance().init(this, false)
+
         Log.i("O2app", "O2app init.....................................................")
         //stetho developer tool
 //        Stetho.initializeWithDefaults(this)

+ 18 - 38
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/bbs/view/BBSWebViewSubjectActivity.kt

@@ -24,6 +24,7 @@ import net.zoneland.x.bpm.mobile.v1.zoneXBPM.R
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BaseMVPActivity
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.APIAddressHelper
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.RetrofitClient
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.enums.APIDistributeTypeEnum
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.BBSUploadImageBO
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.bbs.ReplyFormJson
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.bbs.SubjectReplyInfoJson
@@ -32,11 +33,14 @@ import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.vo.BBSWebViewAttachmentVO
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.*
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.gone
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.hideSoftInput
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.o2Subscribe
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.visible
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.widgets.AttachPopupWindow
 import org.jetbrains.anko.dip
 import org.jetbrains.anko.doAsync
 import org.jetbrains.anko.uiThread
+import rx.android.schedulers.AndroidSchedulers
+import rx.schedulers.Schedulers
 import java.io.DataInputStream
 import java.io.DataOutputStream
 import java.io.File
@@ -274,46 +278,22 @@ class BBSWebViewSubjectActivity : BaseMVPActivity<BBSWebViewSubjectContract.View
 
     override fun startDownLoadFile(id: String?) {
         if (!TextUtils.isEmpty(id)){
-            taskMap[id!!] = doAsync {
-                val filePath = getAttachFileLocalPath(id)
-                val file = File(filePath)
-                var downloadSuccess = false
-                try {
-                    if (!file.exists()) {
-                        val call = RetrofitClient.instance().bbsAssembleControlServiceApi()
-                                .downloadAttach(id)
-                        val response = call.execute()
-                        val input = DataInputStream(response.body()?.byteStream())
-                        val output = DataOutputStream(FileOutputStream(file))
-                        val buffer = ByteArray(4096)
-                        var count = 0
-                        do {
-                            count = input.read(buffer)
-                            if (count > 0) {
-                                output.write(buffer, 0, count)
-                            }
-                        } while (count > 0)
-                        output.close()
-                        input.close()
-                    }
-                    downloadSuccess = true
-                } catch(e: Exception) {
-                    XLog.error("下载附件异常", e)
-                }
-                uiThread {
-                    if (taskMap.containsKey(id)){
-                        taskMap.remove(id)
-                    }
-                    if (downloadSuccess) {
-                        popupWindow.notifyStatusChanged()
-                    }else{
-                        if (file.exists()){
-                            file.delete()
+            val filePath = getAttachFileLocalPath(id!!)
+            val downloadUrl = APIAddressHelper.instance()
+                    .getCommonDownloadUrl(APIDistributeTypeEnum.x_bbs_assemble_control, "jaxrs/attachment/download/$id/stream/false")
+            XLog.debug("下载附件地址: $downloadUrl")
+            O2FileDownloadHelper.download(downloadUrl, filePath)
+                    .subscribeOn(Schedulers.io())
+                    .observeOn(AndroidSchedulers.mainThread())
+                    .o2Subscribe {
+                        onNext {
+                            popupWindow.notifyStatusChanged()
+                        }
+                        onError { e, _ ->
+                            XLog.error("", e)
+                            XToast.toastShort(this@BBSWebViewSubjectActivity, "下载附件失败!")
                         }
-                        XToast.toastShort(this@BBSWebViewSubjectActivity, "下载附件失败!")
                     }
-                }
-            }
         }
     }
 

+ 84 - 56
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/clouddrive/CloudDriveActivity.kt

@@ -18,21 +18,17 @@ import net.zoneland.x.bpm.mobile.v1.zoneXBPM.R
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BaseMVPActivity
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.o2.organization.ContactPickerActivity
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.adapter.CommonFragmentPagerAdapter
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.RetrofitClient
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.APIAddressHelper
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.enums.APIDistributeTypeEnum
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.enums.FileOperateType
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.AndroidUtils
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.FileExtensionHelper
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XLog
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XToast
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.*
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.gone
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.visible
-import org.jetbrains.anko.doAsync
-import org.jetbrains.anko.uiThread
-import java.io.DataInputStream
-import java.io.DataOutputStream
+import rx.Observer
+import rx.Subscription
+import rx.android.schedulers.AndroidSchedulers
+import rx.schedulers.Schedulers
 import java.io.File
-import java.io.FileOutputStream
-import java.util.concurrent.Future
 
 
 class CloudDriveActivity : BaseMVPActivity<CloudDriveContract.View, CloudDriveContract.Presenter>(), CloudDriveContract.View {
@@ -98,9 +94,9 @@ class CloudDriveActivity : BaseMVPActivity<CloudDriveContract.View, CloudDriveCo
     override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
         if (keyCode == KeyEvent.KEYCODE_BACK) {
             //有下载文件的 取消下载
-            if (!downloadTaskMap.isEmpty()) {
+            if (downloadTaskMap.isNotEmpty()) {
                 downloadTaskMap.map {
-                    it.value.cancel(true)
+                    it.value.unsubscribe()
                 }
                 downloadTaskMap.clear()
                 yunpan_download_file_id.gone()
@@ -146,55 +142,87 @@ class CloudDriveActivity : BaseMVPActivity<CloudDriveContract.View, CloudDriveCo
         super.onActivityResult(requestCode, resultCode, data)
     }
 
-    val downloadTaskMap = HashMap<String, Future<Unit>>()
+    val downloadTaskMap = HashMap<String, Subscription>()
     fun openYunPanFile(id: String, fileName: String) {
         XLog.debug("download id:$id, , file:$fileName")
         if (!downloadTaskMap.containsKey(id)) {
             yunpan_download_file_id.visible()
-            downloadTaskMap.put(id, doAsync {
-                var downfile = true
-                val path = FileExtensionHelper.getXBPMTempFolder()+ File.separator + fileName
-                XLog.debug("file path $path")
-                val file = File(path)
-                if (!file.exists()) {
-                    XLog.debug("file not exist, ${file.path}")
-                    try {
-                        val call = RetrofitClient.instance().fileAssembleControlApi()
-                                .downloadFile(id)
-                        val response = call.execute()
-                        val input  = DataInputStream(response.body()?.byteStream())
-                        val output = DataOutputStream(FileOutputStream(file))
-                        val buffer = ByteArray(4096)
-                        var count = 0
-                        do {
-                            count = input.read(buffer)
-                            if (count > 0) {
-                                output.write(buffer, 0, count)
+            val path = FileExtensionHelper.getXBPMTempFolder()+ File.separator + fileName
+            XLog.debug("file path $path")
+            val downloadUrl = APIAddressHelper.instance()
+                    .getCommonDownloadUrl(APIDistributeTypeEnum.x_file_assemble_control, "jaxrs/attachment/$id/download/stream")
+            val subscription = O2FileDownloadHelper.download(downloadUrl, path)
+                    .subscribeOn(Schedulers.io())
+                    .observeOn(AndroidSchedulers.mainThread())
+                    .subscribe(object : Observer<Boolean>{
+                        override fun onError(e: Throwable?) {
+                            yunpan_download_file_id.gone()
+                            if (downloadTaskMap.containsKey(id)){
+                                downloadTaskMap.remove(id)
                             }
-                        } while (count > 0)
-                        output.close()
-                        input.close()
-                        downfile = true
-                    }catch (e: Exception){
-                        XLog.error("download file fail", e)
-                        downfile = false
-                    }
-                }
-                uiThread {
-                    if (downloadTaskMap.containsKey(id)){
-                        downloadTaskMap.remove(id)
-                    }
-                    yunpan_download_file_id.gone()
-                    if (downfile) {
-                        AndroidUtils.openFileWithDefaultApp(this@CloudDriveActivity, file)
-                    }else {
-                        if (file.exists()){
-                            file.delete()
+                            XToast.toastShort(this@CloudDriveActivity, "下载附件失败!")
                         }
-                        XToast.toastShort(this@CloudDriveActivity, "下载附件失败!")
-                    }
-                }
-            })
+
+                        override fun onNext(t: Boolean?) {
+                            yunpan_download_file_id.gone()
+                            if (downloadTaskMap.containsKey(id)){
+                                downloadTaskMap.remove(id)
+                            }
+                            AndroidUtils.openFileWithDefaultApp(this@CloudDriveActivity, File(path))
+                        }
+
+                        override fun onCompleted() {
+
+                        }
+
+                    })
+            downloadTaskMap[id] = subscription
+
+//            downloadTaskMap.put(id, doAsync {
+//                var downfile = true
+//                val path = FileExtensionHelper.getXBPMTempFolder()+ File.separator + fileName
+//                XLog.debug("file path $path")
+//                val file = File(path)
+//                if (!file.exists()) {
+//                    XLog.debug("file not exist, ${file.path}")
+//                    try {
+//                        val call = RetrofitClient.instance().fileAssembleControlApi()
+//                                .downloadFile(id)
+//                        val response = call.execute()
+//                        val input  = DataInputStream(response.body()?.byteStream())
+//                        val output = DataOutputStream(FileOutputStream(file))
+//                        val buffer = ByteArray(4096)
+//                        var count = 0
+//                        do {
+//                            count = input.read(buffer)
+//                            if (count > 0) {
+//                                output.write(buffer, 0, count)
+//                            }
+//                        } while (count > 0)
+//                        output.close()
+//                        input.close()
+//                        downfile = true
+//                    }catch (e: Exception){
+//                        XLog.error("download file fail", e)
+//                        downfile = false
+//                    }
+//                }
+//                uiThread {
+//                    if (downloadTaskMap.containsKey(id)){
+//                        downloadTaskMap.remove(id)
+//                    }
+//                    yunpan_download_file_id.gone()
+//                    if (downfile) {
+//                        AndroidUtils.openFileWithDefaultApp(this@CloudDriveActivity, file)
+//                    }else {
+//                        if (file.exists()){
+//                            file.delete()
+//                        }
+//                        XToast.toastShort(this@CloudDriveActivity, "下载附件失败!")
+//                    }
+//                }
+//            })
+
         }
 
     }

+ 84 - 54
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/clouddrive/v2/CloudDiskFileDownloadHelper.kt

@@ -1,16 +1,16 @@
 package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.clouddrive.v2
 
 import android.app.Activity
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.RetrofitClient
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.APIAddressHelper
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.enums.APIDistributeTypeEnum
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.FileExtensionHelper
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.O2FileDownloadHelper
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XLog
-import org.jetbrains.anko.doAsync
-import org.jetbrains.anko.uiThread
-import java.io.DataInputStream
-import java.io.DataOutputStream
+import rx.Observer
+import rx.Subscription
+import rx.android.schedulers.AndroidSchedulers
+import rx.schedulers.Schedulers
 import java.io.File
-import java.io.FileOutputStream
-import java.util.concurrent.Future
 
 
 /**
@@ -21,66 +21,96 @@ class CloudDiskFileDownloadHelper(val activity: Activity) {
     var showLoading: (()->Unit)? = null
     var hideLoading: (()->Unit)? = null
 
-    var downloader: Future<Unit>? = null
+//    var downloader: Future<Unit>? = null
+    var subscription: Subscription? = null
 
     /**
      * 开始下载文件
      */
     fun startDownload(fileId: String, extension: String, result: (file: File?)->Unit) {
         showLoading?.invoke()
-        downloader = activity.doAsync {
-            var downfile = true
-            val path = FileExtensionHelper.getXBPMTempFolder()+ File.separator + fileId + "." +extension
-            XLog.debug("file path $path")
-            val file = File(path)
-            if (!file.exists()) {
-                XLog.debug("file not exist, ${file.path}")
-                try {
-                    val call = RetrofitClient.instance().cloudFileControlApi()
-                            .downloadFile(fileId)
-                    val response = call.execute()
-                    val input  = DataInputStream(response.body()?.byteStream())
-                    val output = DataOutputStream(FileOutputStream(file))
-                    val buffer = ByteArray(4096)
-                    var count = 0
-                    do {
-                        count = input.read(buffer)
-                        if (count > 0) {
-                            output.write(buffer, 0, count)
-                        }
-                    } while (count > 0)
-                    output.close()
-                    input.close()
-                    downfile = true
-                }catch (e: Exception){
-                    XLog.error("download file fail", e)
-                    file.delete()
-                    downfile = false
-                }
-            }
 
-            uiThread {
-                XLog.debug("执行了。。。。uiThread。")
-                hideLoading?.invoke()
-                if (downfile) {
-                    result(file)
-                }else {
-                    if (file.exists()){
-                        file.delete()
+        val path = FileExtensionHelper.getXBPMTempFolder()+ File.separator + fileId + "." +extension
+        XLog.debug("file path $path")
+        val downloadUrl = APIAddressHelper.instance()
+                .getCommonDownloadUrl(APIDistributeTypeEnum.x_file_assemble_control, "jaxrs/attachment2/$fileId/download/stream")
+        XLog.debug("下载 文件 url: $downloadUrl")
+
+        subscription = O2FileDownloadHelper.download(downloadUrl, path)
+                .subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe(object : Observer<Boolean>{
+                    override fun onError(e: Throwable?) {
+                        result(null)
+                    }
+
+                    override fun onNext(t: Boolean?) {
+                        result(File(path))
+                    }
+
+                    override fun onCompleted() {
+                        hideLoading?.invoke()
                     }
-                    result(null)
-                }
-            }
-        }
+
+                })
+
+//        downloader = activity.doAsync {
+//            var downfile = true
+//            val path = FileExtensionHelper.getXBPMTempFolder()+ File.separator + fileId + "." +extension
+//            XLog.debug("file path $path")
+//            val file = File(path)
+//            if (!file.exists()) {
+//                XLog.debug("file not exist, ${file.path}")
+//                try {
+//                    val call = RetrofitClient.instance().cloudFileControlApi()
+//                            .downloadFile(fileId)
+//                    val response = call.execute()
+//                    val input  = DataInputStream(response.body()?.byteStream())
+//                    val output = DataOutputStream(FileOutputStream(file))
+//                    val buffer = ByteArray(4096)
+//                    var count = 0
+//                    do {
+//                        count = input.read(buffer)
+//                        if (count > 0) {
+//                            output.write(buffer, 0, count)
+//                        }
+//                    } while (count > 0)
+//                    output.close()
+//                    input.close()
+//                    downfile = true
+//                }catch (e: Exception){
+//                    XLog.error("download file fail", e)
+//                    file.delete()
+//                    downfile = false
+//                }
+//            }
+//
+//            uiThread {
+//                XLog.debug("执行了。。。。uiThread。")
+//                hideLoading?.invoke()
+//                if (downfile) {
+//                    result(fresult(file)ile)
+//                }else {
+//                    if (file.exists()){
+//                        file.delete()
+//                    }
+//                    result(null)
+//                }
+//            }
+//        }
+
+
     }
 
     /**
      * 关闭下载
      */
     fun closeDownload() {
-        if (downloader != null) {
-            hideLoading?.invoke()
-            downloader?.cancel(true)
-        }
+//        if (downloader != null) {
+//            hideLoading?.invoke()
+//            downloader?.cancel(true)
+//        }
+        hideLoading?.invoke()
+        subscription?.unsubscribe()
     }
 }

+ 44 - 62
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/clouddrive/viewer/CloudDrivePictureViewerFragment.kt

@@ -8,21 +8,16 @@ import android.widget.ImageView
 import kotlinx.android.synthetic.main.fragment_picture_viewer.*
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.R
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BaseMVPViewPagerFragment
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.RetrofitClient
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.BitmapUtil
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.FileExtensionHelper
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XLog
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XToast
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.APIAddressHelper
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.enums.APIDistributeTypeEnum
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.*
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.gone
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.visible
-import org.jetbrains.anko.doAsync
-import org.jetbrains.anko.uiThread
-import java.io.DataInputStream
-import java.io.DataOutputStream
+import rx.Observable
+import rx.Observer
+import rx.android.schedulers.AndroidSchedulers
+import rx.schedulers.Schedulers
 import java.io.File
-import java.io.FileOutputStream
-
-
 
 
 class CloudDrivePictureViewerFragment : BaseMVPViewPagerFragment<CloudDrivePictureViewerContract.View, CloudDrivePictureViewerContract.Presenter>(), CloudDrivePictureViewerContract.View {
@@ -47,59 +42,46 @@ class CloudDrivePictureViewerFragment : BaseMVPViewPagerFragment<CloudDrivePictu
         }else {
             circle_progress_fragment_picture_view.visible()
             zoomImage_fragment_picture_view.visible()
-            doAsync {
-                val path = FileExtensionHelper.getXBPMTempFolder()+ File.separator + fileName
-                XLog.debug("file path $path")
-                val file = File(path)
-                var bitmap:Bitmap? = null
-                if (!file.exists()) {
-                    XLog.debug("file not exist, ${file.path}")
-                    try {
-                        //下载
-                        val call = RetrofitClient.instance().fileAssembleControlApi()
-                                .downloadFile(fileId)
-                        val response = call.execute()
-                        response.errorBody()?.string()
-                        val input  = DataInputStream(response.body()?.byteStream())
-                        val output = DataOutputStream(FileOutputStream(file))
-                        val buffer = ByteArray(4096)
-                        var count = 0
-                        do {
-                            count = input.read(buffer)
-                            if (count > 0) {
-                                output.write(buffer, 0, count)
+
+            val path = FileExtensionHelper.getXBPMTempFolder()+ File.separator + fileName
+            XLog.debug("file path $path")
+            val downloadUrl = APIAddressHelper.instance()
+                    .getCommonDownloadUrl(APIDistributeTypeEnum.x_file_assemble_control, "jaxrs/attachment/$fileId/download/stream")
+            O2FileDownloadHelper.download(downloadUrl, path)
+                    .subscribeOn(Schedulers.io())
+                    .flatMap {
+                        var bitmap:Bitmap? = null
+                        //压缩
+                        val options = BitmapFactory.Options()
+                        options.inJustDecodeBounds = true
+                        val imageSize = getImageViewWidthAndHeight(zoomImage_fragment_picture_view)
+                        val  newW = imageSize.width
+                        val   newH = imageSize.height
+                        XLog.debug("zoomBitmap, newW:$newW,newH:$newH")
+                        options.inSampleSize = BitmapUtil.getFitInSampleSize(newW, newH, options)
+                        options.inJustDecodeBounds = false
+                        bitmap = BitmapFactory.decodeFile(path, options)
+                        Observable.just(bitmap)
+                    }
+                    .observeOn(AndroidSchedulers.mainThread())
+                    .subscribe(object : Observer<Bitmap> {
+                        override fun onError(e: Throwable?) {
+                            XLog.error("的丁大丁大", e)
+                            circle_progress_fragment_picture_view?.gone()
+                        }
+
+                        override fun onNext(bitmap: Bitmap?) {
+                            if (bitmap!=null) {
+                                zoomImage_fragment_picture_view?.setImageBitmap(bitmap)
                             }
-                        } while (count > 0)
-                        output.close()
-                        input.close()
-                    }catch (e: Exception){
-                        try {
-                            file.delete()
-                        } catch (e: Exception) {
+                            circle_progress_fragment_picture_view?.gone()
                         }
-                        XLog.error("download file fail", e)
 
-                    }
-                }
-
-                //压缩
-                val options = BitmapFactory.Options()
-                options.inJustDecodeBounds = true
-                val imageSize = getImageViewWidthAndHeight(zoomImage_fragment_picture_view)
-                val  newW = imageSize.width
-                val   newH = imageSize.height
-                XLog.debug("zoomBitmap, newW:$newW,newH:$newH")
-                options.inSampleSize = BitmapUtil.getFitInSampleSize(newW, newH, options)
-                options.inJustDecodeBounds = false
-                bitmap = BitmapFactory.decodeFile(path, options)
-
-                uiThread {
-                    circle_progress_fragment_picture_view?.gone()
-                    if (bitmap!=null) {
-                        zoomImage_fragment_picture_view?.setImageBitmap(bitmap)
-                    }
-                }
-            }
+                        override fun onCompleted() {
+                        }
+
+                    })
+
         }
     }
 

+ 49 - 37
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/cms/view/CMSWebViewPresenter.kt

@@ -3,13 +3,16 @@ package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.cms.view
 import android.text.TextUtils
 import net.muliba.accounting.app.ExceptionHandler
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BasePresenterImpl
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.APIAddressHelper
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.ResponseHandler
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.RetrofitClient
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.enums.APIDistributeTypeEnum
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.IdData
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.cms.CMSDocumentAttachmentJson
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.vo.AttachmentItemVO
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.FileExtensionHelper
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.FileUtil
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.O2FileDownloadHelper
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XLog
 import okhttp3.MediaType
 import okhttp3.MultipartBody
@@ -100,47 +103,56 @@ class CMSWebViewPresenter : BasePresenterImpl<CMSWebViewContract.View>(), CMSWeb
                     .flatMap { res->
                         val attachInfo = res.data
                         if (attachInfo != null) {
+
                             val filePath = FileExtensionHelper.getXBPMCMSAttachFolder() + File.separator + attachInfo.name
-                            val file = File(filePath)
-                            try {
-                                if (!file.exists()) {
-                                    val call = cmsService.downloadAttach(attachmentId)
-                                    val response = call.execute()
-                                    val input = DataInputStream(response.body()?.byteStream())
-                                    val output = DataOutputStream(FileOutputStream(file))
-                                    val buffer = ByteArray(4096)
-                                    var count = 0
-                                    do {
-                                        count = input.read(buffer)
-                                        if (count > 0) {
-                                            output.write(buffer, 0, count)
-                                        }
-                                    } while (count > 0)
-                                    output.close()
-                                    input.close()
-                                }
-                            } catch (e: Exception) {
-                                XLog.error("下载附件异常", e)
-                                if (file.exists()) {
-                                    file.delete()
-                                }
-                            }
+                            val downloadUrl = APIAddressHelper.instance()
+                                    .getCommonDownloadUrl(APIDistributeTypeEnum.x_cms_assemble_control, "jaxrs/fileinfo/download/document/$attachmentId/stream")
+                            O2FileDownloadHelper.download(downloadUrl, filePath)
+                                    .flatMap {
+                                        Observable.just(File(filePath))
+                                    }
+
+
+//                            val file = File(filePath)
+//                            try {
+//                                if (!file.exists()) {
+//                                    val call = cmsService.downloadAttach(attachmentId)
+//                                    val response = call.execute()
+//                                    val input = DataInputStream(response.body()?.byteStream())
+//                                    val output = DataOutputStream(FileOutputStream(file))
+//                                    val buffer = ByteArray(4096)
+//                                    var count = 0
+//                                    do {
+//                                        count = input.read(buffer)
+//                                        if (count > 0) {
+//                                            output.write(buffer, 0, count)
+//                                        }
+//                                    } while (count > 0)
+//                                    output.close()
+//                                    input.close()
+//                                }
+//                            } catch (e: Exception) {
+//                                XLog.error("下载附件异常", e)
+//                                if (file.exists()) {
+//                                    file.delete()
+//                                }
+//                            }
+//                            Observable.create { t ->
+//                                val thisfile = File(filePath)
+//                                if (file.exists()) {
+//                                    t?.onNext(thisfile)
+//                                } else {
+//                                    t?.onError(Exception("附件下载异常,找不到文件!"))
+//                                }
+//                                t?.onCompleted()
+//                            }
+
+
+                        }else {
                             Observable.create { t ->
-                                val thisfile = File(filePath)
-                                if (file.exists()) {
-                                    t?.onNext(thisfile)
-                                } else {
-                                    t?.onError(Exception("附件下载异常,找不到文件!"))
-                                }
+                                t?.onError(Exception("没有获取到附件信息,无法下载附件!"))
                                 t?.onCompleted()
                             }
-                        }else {
-                            Observable.create(object : Observable.OnSubscribe<File> {
-                                override fun call(t: Subscriber<in File>?) {
-                                    t?.onError(Exception("没有获取到附件信息,无法下载附件!"))
-                                    t?.onCompleted()
-                                }
-                            })
                         }
                     }.observeOn(AndroidSchedulers.mainThread())
                     .subscribe({ file -> mView?.downloadAttachmentSuccess(file) }, { e ->

+ 385 - 40
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/O2ChatActivity.kt

@@ -1,38 +1,48 @@
 package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.im
 
 import android.app.Activity
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
+import android.app.Instrumentation
+import android.content.*
+import android.graphics.Bitmap
+import android.media.AudioFormat
+import android.media.MediaPlayer
+import android.net.Uri
 import android.os.Bundle
+import android.os.CountDownTimer
+import android.provider.MediaStore
 import android.support.v7.widget.GridLayoutManager
 import android.support.v7.widget.LinearLayoutManager
 import android.text.Editable
 import android.text.TextUtils
 import android.text.TextWatcher
+import android.view.MotionEvent
 import android.view.View
 import android.view.WindowManager
 import android.view.inputmethod.InputMethodManager
+import com.wugang.activityresult.library.ActivityResult
+import com.zlw.main.recorderlib.RecordManager
+import com.zlw.main.recorderlib.recorder.RecordConfig
+import com.zlw.main.recorderlib.recorder.RecordHelper
+import com.zlw.main.recorderlib.recorder.listener.RecordStateListener
 import kotlinx.android.synthetic.main.activity_o2_chat.*
+import net.muliba.fancyfilepickerlibrary.PicturePicker
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.O2SDKManager
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.R
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BaseMVPActivity
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.o2.webview.LocalImageViewActivity
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.adapter.CommonRecycleViewAdapter
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.adapter.CommonRecyclerViewHolder
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.IMConversationInfo
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.IMMessage
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.IMMessageBody
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.DateHelper
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XLog
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XToast
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.*
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.*
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.go
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.gone
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.visible
+import java.io.File
 import java.util.*
+import kotlin.math.abs
 
 
-class O2ChatActivity : BaseMVPActivity<O2ChatContract.View, O2ChatContract.Presenter>(), O2ChatContract.View {
+class O2ChatActivity : BaseMVPActivity<O2ChatContract.View, O2ChatContract.Presenter>(), O2ChatContract.View, View.OnTouchListener {
 
     companion object {
         const val con_id_key = "con_id_key"
@@ -49,10 +59,9 @@ class O2ChatActivity : BaseMVPActivity<O2ChatContract.View, O2ChatContract.Prese
     override fun layoutResId(): Int = R.layout.activity_o2_chat
 
 
-
     private val adapter: O2ChatMessageAdapter by lazy { O2ChatMessageAdapter() }
     private val emojiList = O2IM.im_emoji_hashMap.keys.toList().sortedBy { it }
-    private val emojiAdapter : CommonRecycleViewAdapter<String> by lazy {
+    private val emojiAdapter: CommonRecycleViewAdapter<String> by lazy {
         object : CommonRecycleViewAdapter<String>(this, emojiList, R.layout.item_o2_im_chat_emoji) {
             override fun convert(holder: CommonRecyclerViewHolder?, t: String?) {
                 if (t != null) {
@@ -69,17 +78,51 @@ class O2ChatActivity : BaseMVPActivity<O2ChatContract.View, O2ChatContract.Prese
     private var conversationId = ""
 
     private var conversationInfo: IMConversationInfo? = null
+    //录音服务
+    private var isAudioRecordCancel = false
+    private var audioRecordTime = 0L
+    //录音计时器
+    private val audioCountDownTimer: CountDownTimer by lazy {
+        object : CountDownTimer(60 * 1000, 1000) {
+            override fun onFinish() {
+                XLog.debug("倒计时结束!")
+                endRecordAudio()
+            }
+
+            override fun onTick(millisUntilFinished: Long) {
+                val sec = ((millisUntilFinished + 15) / 1000)
+                audioRecordTime = 60 - sec
+                runOnUiThread {
+                    val times = if (audioRecordTime > 9) {
+                        "00:$audioRecordTime"
+                    } else {
+                        "00:0$audioRecordTime"
+                    }
+                    tv_o2_chat_audio_speak_duration.text = times
+                }
+                XLog.debug("倒计时还剩余:$sec 秒")
+            }
+
+        }
+    }
 
+    //media play
+    private var mPlayer: MediaPlayer? = null
 
-    private var mKeyboardHeight = 150 // 输入法默认高度为400
+    //拍照
+    private val cameraImageUri: Uri by lazy { FileUtil.getUriFromFile(this, File(FileExtensionHelper.getCameraCacheFilePath())) }
+    private val camera_result_code = 10240
 
 
 
+
+//    private var mKeyboardHeight = 150 // 输入法默认高度为400
+
+
     override fun afterSetContentView(savedInstanceState: Bundle?) {
         // 起初的布局可自动调整大小
         window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE or WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
 
-
         setupToolBar(defaultTitle, setupBackButton = true)
 
         conversationId = intent.getStringExtra(con_id_key) ?: ""
@@ -92,7 +135,22 @@ class O2ChatActivity : BaseMVPActivity<O2ChatContract.View, O2ChatContract.Prese
         rv_o2_chat_messages.adapter = adapter
         adapter.eventListener = object : O2ChatMessageAdapter.MessageEventListener {
             override fun resendClick(message: IMMessage) {
-                mPresenter.sendTextMessage(message)//重新发送
+                mPresenter.sendIMMessage(message)//重新发送
+            }
+
+            override fun playAudio(position: Int, msgBody: IMMessageBody) {
+                XLog.debug("audio play position: $position")
+                mPresenter.getFileFromNetOrLocal(position, msgBody)
+            }
+
+            override fun openOriginImage(position: Int, msgBody: IMMessageBody) {
+                 mPresenter.getFileFromNetOrLocal(position, msgBody)
+            }
+
+            override fun openLocation(msgBody: IMMessageBody) {
+                val location = O2LocationActivity.LocationData(msgBody.address, msgBody.addressDetail, msgBody.latitude, msgBody.longitude)
+                val bundle = O2LocationActivity.showLocation(location)
+                go<O2LocationActivity>(bundle)
             }
         }
         //输入法切换的时候滚动到底部
@@ -113,22 +171,77 @@ class O2ChatActivity : BaseMVPActivity<O2ChatContract.View, O2ChatContract.Prese
             mPresenter.readConversation(conversationId)
         }
 
-
-
         initListener()
 
         getPageData()
 
+        //录音格式
+        initAudioRecord()
+
         registerBroadcast()
     }
 
 
 
+
     override fun onDestroy() {
         super.onDestroy()
         if (mReceiver != null) {
             unregisterReceiver(mReceiver)
         }
+        if (mPlayer != null) {
+            mPlayer?.release()//释放资源
+            mPlayer = null
+        }
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        super.onActivityResult(requestCode, resultCode, data)
+        if (resultCode == Activity.RESULT_OK && requestCode == camera_result_code) {
+            //拍照
+            XLog.debug("拍照//// ")
+            newImageMessage(FileExtensionHelper.getCameraCacheFilePath())
+        }
+    }
+
+    private var startY: Float = 0f
+    private var mCurPosY: Float = 0f
+
+    /**
+     * 录音按钮的touch事件
+     * 按住录音
+     * 释放发送语音消息
+     * 上滑取消发送
+     */
+    override fun onTouch(v: View?, event: MotionEvent?): Boolean {
+        if (v?.id == R.id.image_o2_chat_audio_speak_btn) {
+            when (event?.action) {
+                MotionEvent.ACTION_DOWN -> {
+                    startY = event.y
+                    startRecordAudio()
+                }
+                MotionEvent.ACTION_UP -> {
+//                    if (mCurPosY - startY > 0 && (abs(mCurPosY - startY) > 100)) {
+//                        XLog.debug("audioButtonDown() 下滑 ")
+//                    } else if (mCurPosY - startY < 0 && (abs(mCurPosY - startY) > 100)) {
+//                        XLog.debug("audioButtonDown() 上滑 ")
+//                    }else {
+//                        XLog.debug("audioButtonDown() 距离不够 ")
+//                    }
+                    if (mCurPosY - startY < 0 && (abs(mCurPosY - startY) > 100)) {
+                        cancelRecordAudio()
+                    } else {
+                        endRecordAudio()
+                    }
+                }
+                MotionEvent.ACTION_MOVE -> {
+                    mCurPosY = event.y
+                }
+            }
+            return true
+
+        }
+        return false
     }
 
     override fun conversationInfo(info: IMConversationInfo) {
@@ -143,7 +256,7 @@ class O2ChatActivity : BaseMVPActivity<O2ChatContract.View, O2ChatContract.Prese
                     title = person.substring(0, person.indexOf("@"))
                 }
             }
-        }else if(O2IM.conversation_type_group == conversationInfo?.type) {
+        } else if (O2IM.conversation_type_group == conversationInfo?.type) {
             title = conversationInfo?.title ?: defaultTitle
         }
         updateToolbarTitle(title)
@@ -155,7 +268,7 @@ class O2ChatActivity : BaseMVPActivity<O2ChatContract.View, O2ChatContract.Prese
     }
 
     override fun backPageMessages(list: List<IMMessage>) {
-        if(list.isNotEmpty()) {
+        if (list.isNotEmpty()) {
             page++
             adapter.addPageMessage(list)
         }
@@ -175,6 +288,25 @@ class O2ChatActivity : BaseMVPActivity<O2ChatContract.View, O2ChatContract.Prese
         adapter.sendMessageFail(id)
     }
 
+    override fun localFile(filePath: String, msgType: String, position: Int) {
+        XLog.debug("local file :$filePath type:$msgType")
+        when (msgType) {
+            MessageType.audio.key -> {
+                playAudio2(filePath, position)
+            }
+            MessageType.image.key -> {
+                //打开大图
+                go<LocalImageViewActivity>(LocalImageViewActivity.startBundle(filePath))
+            }
+            else -> AndroidUtils.openFileWithDefaultApp(this@O2ChatActivity, File(filePath))
+        }
+
+    }
+
+    override fun downloadFileFail(msg: String) {
+        XToast.toastShort(this, msg)
+    }
+
     /**
      * 监听
      */
@@ -190,7 +322,7 @@ class O2ChatActivity : BaseMVPActivity<O2ChatContract.View, O2ChatContract.Prese
                 if (s != null && !TextUtils.isEmpty(s)) {
                     btn_o2_chat_send.visible()
                     btn_o2_chat_emotion.gone()
-                }else {
+                } else {
                     btn_o2_chat_emotion.visible()
                     btn_o2_chat_send.gone()
                 }
@@ -199,38 +331,44 @@ class O2ChatActivity : BaseMVPActivity<O2ChatContract.View, O2ChatContract.Prese
         et_o2_chat_input.setOnClickListener {
             rv_o2_chat_emoji_box_out.postDelayed({
                 rv_o2_chat_emoji_box.gone()
+                tv_o2_chat_audio_send_box.gone()
                 window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
             }, 250)
         }
         rv_o2_chat_emoji_box_out.setKeyboardListener { isActive, keyboardHeight ->
             if (isActive) { // 输入法打开
-                if (mKeyboardHeight != keyboardHeight) { // 键盘发生改变时才设置emojiView的高度,因为会触发onGlobalLayoutChanged,导致onKeyboardStateChanged再次被调用
-                    mKeyboardHeight = keyboardHeight
-                    initEmojiView() // 每次输入法弹起时,设置emojiView的高度为键盘的高度,以便下次emojiView弹出时刚好等于键盘高度
-                }
+//                if (mKeyboardHeight != keyboardHeight) { // 键盘发生改变时才设置emojiView的高度,因为会触发onGlobalLayoutChanged,导致onKeyboardStateChanged再次被调用
+//                    mKeyboardHeight = keyboardHeight
+//                    initEmojiView() // 每次输入法弹起时,设置emojiView的高度为键盘的高度,以便下次emojiView弹出时刚好等于键盘高度
+//                }
                 if (rv_o2_chat_emoji_box.visibility == View.VISIBLE) { // 表情打开状态下
                     rv_o2_chat_emoji_box.gone()
                 }
+                if (tv_o2_chat_audio_send_box.visibility == View.VISIBLE) { // 表情打开状态下
+                    tv_o2_chat_audio_send_box.gone()
+                }
             }
         }
         btn_o2_chat_emotion.setOnClickListener {
+            //关闭语音框
+            tv_o2_chat_audio_send_box.gone()
             if (rv_o2_chat_emoji_box_out.isKeyboardActive) { //输入法激活时
-                if(rv_o2_chat_emoji_box.visibility == View.GONE) {
+                if (rv_o2_chat_emoji_box.visibility == View.GONE) {
                     window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING) //  不改变布局,隐藏键盘,emojiView弹出
-                    val imm =  it.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+                    val imm = it.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
                     imm.hideSoftInputFromWindow(et_o2_chat_input.applicationWindowToken, 0)
                     rv_o2_chat_emoji_box.visibility = View.VISIBLE
-                }else {
+                } else {
                     rv_o2_chat_emoji_box.visibility = View.GONE
-                    val imm =  it.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+                    val imm = it.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
                     imm.hideSoftInputFromWindow(et_o2_chat_input.applicationWindowToken, 0)
                     window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
                 }
-            }else {
+            } else {
                 window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
-                if(rv_o2_chat_emoji_box.visibility == View.GONE) {
+                if (rv_o2_chat_emoji_box.visibility == View.GONE) {
                     rv_o2_chat_emoji_box.visibility = View.VISIBLE
-                }else {
+                } else {
                     rv_o2_chat_emoji_box.visibility = View.GONE
                 }
             }
@@ -239,21 +377,177 @@ class O2ChatActivity : BaseMVPActivity<O2ChatContract.View, O2ChatContract.Prese
         btn_o2_chat_send.setOnClickListener {
             sendTextMessage()
         }
+
+        //bottom toolbar
+        image_o2_chat_audio_speak_btn.setOnTouchListener(this)
+        ll_o2_chat_audio_btn.setOnClickListener {
+            //关闭表情框
+            rv_o2_chat_emoji_box.gone()
+            if (rv_o2_chat_emoji_box_out.isKeyboardActive) { //输入法激活时
+                if (tv_o2_chat_audio_send_box.visibility == View.GONE) {
+                    window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING) //  不改变布局,隐藏键盘,emojiView弹出
+                    val imm = it.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+                    imm.hideSoftInputFromWindow(et_o2_chat_input.applicationWindowToken, 0)
+                    tv_o2_chat_audio_send_box.visibility = View.VISIBLE
+                } else {
+                    tv_o2_chat_audio_send_box.visibility = View.GONE
+                    val imm = it.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+                    imm.hideSoftInputFromWindow(et_o2_chat_input.applicationWindowToken, 0)
+                    window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
+                }
+            } else {
+                if (tv_o2_chat_audio_send_box.visibility == View.GONE) {
+                    tv_o2_chat_audio_send_box.visibility = View.VISIBLE
+                } else {
+                    tv_o2_chat_audio_send_box.visibility = View.GONE
+                }
+            }
+        }
+        ll_o2_chat_album_btn.setOnClickListener {
+            PicturePicker()
+                    .withActivity(this)
+                    .chooseType(PicturePicker.CHOOSE_TYPE_SINGLE).forResult { files ->
+                        if (files.isNotEmpty()) {
+                            newImageMessage(files[0])
+                        }
+                    }
+        }
+        ll_o2_chat_camera_btn.setOnClickListener {
+            val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
+            //return-data false 不是直接返回拍照后的照片Bitmap 因为照片太大会传输失败
+            intent.putExtra("return-data", false)
+            //改用Uri 传递
+            intent.putExtra(MediaStore.EXTRA_OUTPUT, cameraImageUri)
+            intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString())
+            intent.putExtra("noFaceDetection", true)
+            startActivityForResult(intent, camera_result_code)
+        }
+        ll_o2_chat_location_btn.setOnClickListener {
+            ActivityResult.of(this)
+                    .className(O2LocationActivity::class.java)
+                    .params(O2LocationActivity.startChooseLocation())
+                    .greenChannel()
+                    .forResult { resultCode, data ->
+                        if (resultCode == Activity.RESULT_OK) {
+                            val location = data.extras.getParcelable<O2LocationActivity.LocationData>(O2LocationActivity.RESULT_LOCATION_KEY)
+                            if (location != null) {
+                                newLocationMessage(location)
+                            }
+                        }
+                    }
+        }
     }
     // 设置表情栏的高度
-    private fun initEmojiView() {
-        val layoutParams = rv_o2_chat_emoji_box.layoutParams
-        layoutParams.height = mKeyboardHeight
-        rv_o2_chat_emoji_box.layoutParams = layoutParams
+//    private fun initEmojiView() {
+//        val layoutParams = rv_o2_chat_emoji_box.layoutParams
+//        layoutParams.height = mKeyboardHeight
+//        rv_o2_chat_emoji_box.layoutParams = layoutParams
+//    }
+
+    /**
+     * 初始化录音相关对象
+     */
+    private fun initAudioRecord() {
+        RecordManager.getInstance().changeFormat(RecordConfig.RecordFormat.MP3)
+        RecordManager.getInstance().changeRecordConfig(RecordManager.getInstance().recordConfig.setSampleRate(16000))
+        RecordManager.getInstance().changeRecordConfig(RecordManager.getInstance().recordConfig.setEncodingConfig(AudioFormat.ENCODING_PCM_8BIT))
+        RecordManager.getInstance().changeRecordDir(FileExtensionHelper.getXBPMTempFolder() + File.separator)
+        RecordManager.getInstance().setRecordStateListener(object : RecordStateListener {
+            override fun onError(error: String?) {
+                XLog.error("录音错误, $error")
+            }
+
+            override fun onStateChange(state: RecordHelper.RecordState?) {
+                when (state) {
+                    RecordHelper.RecordState.IDLE -> XLog.debug("录音状态, 空闲状态")
+                    RecordHelper.RecordState.RECORDING -> {
+                        XLog.debug("录音状态, 录音中")
+                        audioCountDownTimer.start()
+                    }
+                    RecordHelper.RecordState.PAUSE -> XLog.debug("录音状态, 暂停中")
+                    RecordHelper.RecordState.STOP -> XLog.debug("录音状态, 正在停止")
+                    RecordHelper.RecordState.FINISH -> XLog.debug("录音状态, 录音流程结束(转换结束)")
+                }
+
+            }
+        })
+        RecordManager.getInstance().setRecordResultListener { result ->
+            if (result == null) {
+                runOnUiThread { XToast.toastShort(this@O2ChatActivity, "录音失败!") }
+            } else {
+                XLog.debug("录音结束 返回结果 ${result.path} , 是否取消:$isAudioRecordCancel, 录音时间:$audioRecordTime")
+                if (audioRecordTime < 1) {
+                    runOnUiThread {
+                        XToast.toastShort(this@O2ChatActivity, "录音时间太短!")
+                    }
+                } else {
+                    newAudioMessage(result.path, "$audioRecordTime")
+                }
+            }
+        }
+    }
+
+    /**
+     * 开始录音
+     */
+    private fun startRecordAudio() {
+        XLog.debug("开始录音。。。。")
+        audioRecordTime = 0L
+        RecordManager.getInstance().start()
+        tv_o2_chat_audio_speak_title.text = resources.getText(R.string.activity_im_audio_speak_cancel)
+    }
+
+    /**
+     * 结束录音
+     */
+    private fun endRecordAudio() {
+        XLog.debug("结束录音。。。。")
+        audioCountDownTimer.cancel()
+        RecordManager.getInstance().stop()
+        tv_o2_chat_audio_speak_title.text = resources.getText(R.string.activity_im_audio_speak)
+        tv_o2_chat_audio_speak_duration.text = ""
+    }
+
+    /**
+     * 取消录音
+     */
+    private fun cancelRecordAudio() {
+        XLog.debug("取消录音。。。。。")
+        isAudioRecordCancel = true
+        audioCountDownTimer.cancel()
+        RecordManager.getInstance().stop()
+        tv_o2_chat_audio_speak_title.text = resources.getText(R.string.activity_im_audio_speak)
+        tv_o2_chat_audio_speak_duration.text = ""
     }
 
+    private fun playAudio2(filePath: String, position: Int) {
+        if (mPlayer != null) {
+            mPlayer?.release()
+            mPlayer = null
+        }
+        XLog.debug("uri : $filePath")
+        val uri = Uri.fromFile(File(filePath))
+        mPlayer = MediaPlayer.create(this@O2ChatActivity, uri)
+        mPlayer?.setOnCompletionListener {
+            XLog.debug("播音结束!")
+
+        }
+        mPlayer?.start()
+    }
 
+
+    /**
+     * 获取消息数据
+     */
     private fun getPageData() {
         mPresenter.getMessage(page + 1, conversationId)
         //更新阅读时间
         mPresenter.readConversation(conversationId)
     }
 
+    /**
+     * 滚动消息到底部
+     */
     private fun scroll2Bottom() {
         rv_o2_chat_messages.scrollToPosition(adapter.lastPosition())
     }
@@ -276,14 +570,14 @@ class O2ChatActivity : BaseMVPActivity<O2ChatContract.View, O2ChatContract.Prese
      */
     private fun newTextMessage(text: String) {
         val time = DateHelper.now()
-        val body = IMMessageBody.Text(text)
+        val body = IMMessageBody(type = MessageType.text.key, body = text)
         val bodyJson = O2SDKManager.instance().gson.toJson(body)
         XLog.debug("body: $bodyJson")
         val uuid = UUID.randomUUID().toString()
         val message = IMMessage(uuid, conversationId, bodyJson,
                 O2SDKManager.instance().distinguishedName, time, 1)
         adapter.addMessage(message)
-        mPresenter.sendTextMessage(message)//发送到服务器
+        mPresenter.sendIMMessage(message)//发送到服务器
         scroll2Bottom()
     }
 
@@ -292,14 +586,65 @@ class O2ChatActivity : BaseMVPActivity<O2ChatContract.View, O2ChatContract.Prese
      */
     private fun newEmojiMessage(emoji: String) {
         val time = DateHelper.now()
-        val body = IMMessageBody.Emoji(emoji)
+        val body = IMMessageBody(type = MessageType.emoji.key, body = emoji)
+        val bodyJson = O2SDKManager.instance().gson.toJson(body)
+        XLog.debug("body: $bodyJson")
+        val uuid = UUID.randomUUID().toString()
+        val message = IMMessage(uuid, conversationId, bodyJson,
+                O2SDKManager.instance().distinguishedName, time, 1)
+        adapter.addMessage(message)
+        mPresenter.sendIMMessage(message)//发送到服务器
+        scroll2Bottom()
+    }
+
+    /**
+     * 文件消息创建 并发送
+     */
+    private fun newAudioMessage(filePath: String, duration: String) {
+        val time = DateHelper.now()
+        val body = IMMessageBody(type = MessageType.audio.key, body = MessageBody.audio.body,
+                fileTempPath = filePath, audioDuration = duration)
+        val bodyJson = O2SDKManager.instance().gson.toJson(body)
+        XLog.debug("body: $bodyJson")
+        val uuid = UUID.randomUUID().toString()
+        val message = IMMessage(uuid, conversationId, bodyJson,
+                O2SDKManager.instance().distinguishedName, time, 1)
+        adapter.addMessage(message)
+        mPresenter.sendIMMessage(message)//发送到服务器
+        scroll2Bottom()
+    }
+
+    /**
+     * 图片消息 创建 并发送
+     */
+    private fun newImageMessage(filePath: String) {
+        val time = DateHelper.now()
+        val body = IMMessageBody(type = MessageType.image.key, body = MessageBody.image.body, fileTempPath = filePath)
+        val bodyJson = O2SDKManager.instance().gson.toJson(body)
+        XLog.debug("body: $bodyJson")
+        val uuid = UUID.randomUUID().toString()
+        val message = IMMessage(uuid, conversationId, bodyJson,
+                O2SDKManager.instance().distinguishedName, time, 1)
+        adapter.addMessage(message)
+        mPresenter.sendIMMessage(message)//发送到服务器
+        scroll2Bottom()
+    }
+
+    /**
+     * 位置消息 创建并发送
+     */
+    private fun newLocationMessage(location: O2LocationActivity.LocationData) {
+        val time = DateHelper.now()
+        val body = IMMessageBody(type = MessageType.location.key, body = MessageBody.location.body,
+                address = location.address, addressDetail = location.addressDetail,
+                latitude = location.latitude, longitude = location.longitude)
         val bodyJson = O2SDKManager.instance().gson.toJson(body)
         XLog.debug("body: $bodyJson")
         val uuid = UUID.randomUUID().toString()
         val message = IMMessage(uuid, conversationId, bodyJson,
                 O2SDKManager.instance().distinguishedName, time, 1)
         adapter.addMessage(message)
-        mPresenter.sendTextMessage(message)//发送到服务器
+        mPresenter.sendIMMessage(message)//发送到服务器
         scroll2Bottom()
     }
 

+ 5 - 1
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/O2ChatContract.kt

@@ -4,6 +4,7 @@ import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BasePresenter
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BaseView
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.IMConversationInfo
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.IMMessage
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.IMMessageBody
 
 object O2ChatContract  {
 
@@ -13,11 +14,14 @@ object O2ChatContract  {
         fun sendFail(id: String)
         fun conversationInfo(info: IMConversationInfo)
         fun conversationGetFail()
+        fun localFile(filePath: String, msgType: String, position: Int)
+        fun downloadFileFail(msg: String)
     }
     interface Presenter: BasePresenter<View> {
-        fun sendTextMessage(msg: IMMessage)
+        fun sendIMMessage(msg: IMMessage)
         fun getMessage(page: Int, conversationId: String)
         fun readConversation(conversationId: String)
         fun getConversation(id: String)
+        fun getFileFromNetOrLocal(position: Int, body: IMMessageBody)
     }
 }

+ 94 - 19
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/O2ChatMessageAdapter.kt

@@ -1,24 +1,25 @@
 package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.im
 
 import android.support.v7.widget.RecyclerView
+import android.text.TextUtils
 import android.view.LayoutInflater
 import android.view.ViewGroup
 import android.view.animation.Animation
 import android.view.animation.AnimationUtils
-import android.widget.ImageButton
-import android.widget.ImageView
-import android.widget.TextView
+import android.widget.*
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.O2SDKManager
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.R
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.adapter.CommonRecyclerViewHolder
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.APIAddressHelper
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.IMMessage
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.IMMessageBody
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.MessageType
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.DateHelper
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.gone
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.visible
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.imageloader.O2ImageLoaderManager
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.widgets.CircleImageView
+import retrofit2.http.POST
 
 
 class O2ChatMessageAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
@@ -120,23 +121,16 @@ class O2ChatMessageAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
                     time = DateHelper.imChatMessageTime(message.createTime)
                 }
             }
+            holder.setText(R.id.tv_o2_chat_message_person_name, name)
+                    .setText(R.id.tv_o2_chat_message_time, time)
+
             if (messageBody != null) {
-                if (messageBody is IMMessageBody.Text) {
-                    val textBody = holder.getView<TextView>(R.id.tv_o2_chat_message_body)
-                    textBody.text = messageBody.body
-                    textBody.visible()
-                    val imgBody = holder.getView<ImageView>(R.id.image_o2_chat_message_emoji_body)
-                    imgBody.gone()
-                    holder.setText(R.id.tv_o2_chat_message_person_name, name)
-                            .setText(R.id.tv_o2_chat_message_time, time)
-                }else if (messageBody is IMMessageBody.Emoji) {
-                    val textBody = holder.getView<TextView>(R.id.tv_o2_chat_message_body)
-                    textBody.gone()
-                    val imgBody = holder.getView<ImageView>(R.id.image_o2_chat_message_emoji_body)
-                    imgBody.setImageResource(O2IM.emojiResId(messageBody.body))
-                    imgBody.visible()
-                    holder.setText(R.id.tv_o2_chat_message_person_name, name)
-                            .setText(R.id.tv_o2_chat_message_time, time)
+                when(messageBody.type) {
+                    MessageType.text.key -> renderTextMessage(messageBody, holder)
+                    MessageType.emoji.key -> renderEmojiMessage(messageBody, holder)
+                    MessageType.image.key -> renderImageMessage(messageBody, holder, position)
+                    MessageType.audio.key -> renderAudioMessage(messageBody, holder, position)
+                    MessageType.location.key -> renderLocationMessage(messageBody, holder)
                 }
             }
 
@@ -163,9 +157,90 @@ class O2ChatMessageAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
         }
     }
 
+    private fun renderTextMessage(msgBody: IMMessageBody, holder: CommonRecyclerViewHolder) {
+        val textMessageView = holder.getView<TextView>(R.id.tv_o2_chat_message_body)
+        textMessageView.visible()
+        textMessageView.text = msgBody.body
+        val emojiMessageView = holder.getView<ImageView>(R.id.image_o2_chat_message_emoji_body)
+        emojiMessageView.gone()
+        val imageMessageView = holder.getView<ImageView>(R.id.image_o2_chat_message_image_body)
+        imageMessageView.gone()
+        val audioMessageView = holder.getView<LinearLayout>(R.id.ll_o2_chat_message_audio_body)
+        audioMessageView.gone()
+        val locationMessageView = holder.getView<RelativeLayout>(R.id.rl_o2_chat_message_location_body)
+        locationMessageView.gone()
+    }
+    private fun renderEmojiMessage(msgBody: IMMessageBody, holder: CommonRecyclerViewHolder) {
+        val textMessageView = holder.getView<TextView>(R.id.tv_o2_chat_message_body)
+        textMessageView.gone()
+        val emojiMessageView = holder.getView<ImageView>(R.id.image_o2_chat_message_emoji_body)
+        if (msgBody.body  != null) {
+            emojiMessageView.setImageResource(O2IM.emojiResId(msgBody.body!!))
+        }
+        emojiMessageView.visible()
+        val imageMessageView = holder.getView<ImageView>(R.id.image_o2_chat_message_image_body)
+        imageMessageView.gone()
+        val audioMessageView = holder.getView<LinearLayout>(R.id.ll_o2_chat_message_audio_body)
+        audioMessageView.gone()
+        val locationMessageView = holder.getView<RelativeLayout>(R.id.rl_o2_chat_message_location_body)
+        locationMessageView.gone()
+    }
+    private fun renderImageMessage(msgBody: IMMessageBody, holder: CommonRecyclerViewHolder,  position: Int) {
+        val textMessageView = holder.getView<TextView>(R.id.tv_o2_chat_message_body)
+        textMessageView.gone()
+        val emojiMessageView = holder.getView<ImageView>(R.id.image_o2_chat_message_emoji_body)
+        emojiMessageView.gone()
+        val imageMessageView = holder.getView<ImageView>(R.id.image_o2_chat_message_image_body)
+        if (!TextUtils.isEmpty(msgBody.fileId)) {
+            val url = APIAddressHelper.instance().getImImageDownloadUrlWithWH(msgBody.fileId!!, 144, 192)
+            O2ImageLoaderManager.instance().showImage(imageMessageView, url)
+        }else if (!TextUtils.isEmpty(msgBody.fileTempPath)) {
+            O2ImageLoaderManager.instance().showImage(imageMessageView, msgBody.fileTempPath!!)
+        }
+        imageMessageView.visible()
+        imageMessageView.setOnClickListener { eventListener?.openOriginImage(position, msgBody) }
+        val audioMessageView = holder.getView<LinearLayout>(R.id.ll_o2_chat_message_audio_body)
+        audioMessageView.gone()
+        val locationMessageView = holder.getView<RelativeLayout>(R.id.rl_o2_chat_message_location_body)
+        locationMessageView.gone()
+    }
+    private fun renderAudioMessage(msgBody: IMMessageBody, holder: CommonRecyclerViewHolder, position: Int) {
+        val textMessageView = holder.getView<TextView>(R.id.tv_o2_chat_message_body)
+        textMessageView.gone()
+        val emojiMessageView = holder.getView<ImageView>(R.id.image_o2_chat_message_emoji_body)
+        emojiMessageView.gone()
+        val imageMessageView = holder.getView<ImageView>(R.id.image_o2_chat_message_image_body)
+        imageMessageView.gone()
+        val audioMessageView = holder.getView<LinearLayout>(R.id.ll_o2_chat_message_audio_body)
+        val durationTv = holder.getView<TextView>(R.id.tv_o2_chat_message_audio_duration)
+        durationTv.text = "${msgBody.audioDuration}\""
+        audioMessageView.visible()
+        audioMessageView.setOnClickListener { eventListener?.playAudio(position, msgBody) }
+        val locationMessageView = holder.getView<RelativeLayout>(R.id.rl_o2_chat_message_location_body)
+        locationMessageView.gone()
+    }
+    private fun renderLocationMessage(msgBody: IMMessageBody, holder: CommonRecyclerViewHolder) {
+        val textMessageView = holder.getView<TextView>(R.id.tv_o2_chat_message_body)
+        textMessageView.gone()
+        val emojiMessageView = holder.getView<ImageView>(R.id.image_o2_chat_message_emoji_body)
+        emojiMessageView.gone()
+        val imageMessageView = holder.getView<ImageView>(R.id.image_o2_chat_message_image_body)
+        imageMessageView.gone()
+        val audioMessageView = holder.getView<LinearLayout>(R.id.ll_o2_chat_message_audio_body)
+        audioMessageView.gone()
+        val locationMessageView = holder.getView<RelativeLayout>(R.id.rl_o2_chat_message_location_body)
+        val addressTv = holder.getView<TextView>(R.id.tv_o2_chat_message_location_address)
+        addressTv.text = msgBody.address
+        locationMessageView.visible()
+        locationMessageView.setOnClickListener { eventListener?.openLocation(msgBody) }
+    }
+
 
     interface MessageEventListener {
         //重新发送消息
         fun resendClick(message: IMMessage)
+        fun playAudio(position: Int, msgBody: IMMessageBody)
+        fun openOriginImage(position: Int, msgBody: IMMessageBody)
+        fun openLocation(msgBody: IMMessageBody)
     }
 }

+ 97 - 15
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/O2ChatPresenter.kt

@@ -1,14 +1,29 @@
 package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.im
 
+import android.text.TextUtils
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.O2
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.O2SDKManager
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BasePresenterImpl
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.APIAddressHelper
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.RetrofitClient
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.IMMessage
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.IMMessageBody
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.IMMessageForm
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.MessageType
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.FileExtensionHelper
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.FileUtil
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.O2FileDownloadHelper
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XLog
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.o2Subscribe
+import okhttp3.MediaType
+import okhttp3.MultipartBody
+import okhttp3.RequestBody
 import rx.Observable
 import rx.android.schedulers.AndroidSchedulers
 import rx.schedulers.Schedulers
+import java.io.*
+import java.net.HttpURLConnection
+import java.net.URL
 
 class O2ChatPresenter : BasePresenterImpl<O2ChatContract.View>(), O2ChatContract.Presenter  {
 
@@ -32,22 +47,61 @@ class O2ChatPresenter : BasePresenterImpl<O2ChatContract.View>(), O2ChatContract
                 }
     }
 
-    override fun sendTextMessage(msg: IMMessage) {
+    override fun sendIMMessage(msg: IMMessage) {
         val service = getMessageCommunicateService(mView?.getContext())
-        service?.sendMessage(msg)?.subscribeOn(Schedulers.io())
-                ?.observeOn(AndroidSchedulers.mainThread())?.o2Subscribe {
-            onNext {
-                val id = it.data.id
-                if (id != null) {
-                    mView?.sendMessageSuccess(id)
-                }else {
-                    mView?.sendFail(msg.id)
-                }
-            }
-            onError { e, _ ->
-                XLog.error("", e)
-                mView?.sendFail(msg.id)
-            }
+        //audio 和 image 需要先上传文件 然后发送消息
+        val body = O2SDKManager.instance().gson.fromJson(msg.body, IMMessageBody::class.java)
+        if (body.type == MessageType.audio.key || body.type == MessageType.image.key) {
+            val file = File(body.fileTempPath)
+            val mediaType = FileUtil.getMIMEType(file)
+            val requestBody = RequestBody.create(MediaType.parse(mediaType), file)
+            val part = MultipartBody.Part.createFormData("file", file.name, requestBody)
+            service?.uploadFile(msg.conversationId, body.type!!, part)
+                    ?.subscribeOn(Schedulers.io())
+                    ?.flatMap { idData ->
+                        val id = idData.data.id
+                        val extension = idData.data.fileExtension
+                        if (!TextUtils.isEmpty(id)) {//消息体中添加fileId 并清楚暂存的本地地址fileTempPath
+                            body.fileId = id
+                            body.fileExtension = extension
+                            body.fileTempPath = null
+                            msg.body = O2SDKManager.instance().gson.toJson(body)
+                            service.sendMessage(msg)
+                        }else {
+                            throw Exception("上传附件失败")
+                        }
+                    }
+                    ?.observeOn(AndroidSchedulers.mainThread())
+                    ?.o2Subscribe {
+                        onNext {
+                            val id = it.data.id
+                            if (id != null) {
+                                mView?.sendMessageSuccess(id)
+                            }else {
+                                mView?.sendFail(msg.id)
+                            }
+                        }
+                        onError { e, _ ->
+                            XLog.error("", e)
+                            mView?.sendFail(msg.id)
+                        }
+                    }
+        }else {
+            service?.sendMessage(msg)?.subscribeOn(Schedulers.io())
+                    ?.observeOn(AndroidSchedulers.mainThread())?.o2Subscribe {
+                        onNext {
+                            val id = it.data.id
+                            if (id != null) {
+                                mView?.sendMessageSuccess(id)
+                            }else {
+                                mView?.sendFail(msg.id)
+                            }
+                        }
+                        onError { e, _ ->
+                            XLog.error("", e)
+                            mView?.sendFail(msg.id)
+                        }
+                    }
         }
     }
 
@@ -93,6 +147,34 @@ class O2ChatPresenter : BasePresenterImpl<O2ChatContract.View>(), O2ChatContract
                             XLog.error("read error", e)
                         }
                     }
+        }
+    }
+
+    override fun getFileFromNetOrLocal(position: Int, body: IMMessageBody) {
+        if (TextUtils.isEmpty(body.fileId) && !TextUtils.isEmpty(body.fileTempPath)) {
+            XLog.debug("本地文件。。。。。")
+            mView?.localFile(body.fileTempPath!!, body.type!!,  position)
+        }else if (!TextUtils.isEmpty(body.fileId)) {
+            val fileId = body.fileId!!
+            val path = FileExtensionHelper.getXBPMTempFolder()+ File.separator + fileId + "." +body.fileExtension
+            val downloadUrl = APIAddressHelper.instance().getImFileDownloadUrl(fileId)
+            O2FileDownloadHelper.download(downloadUrl, path)
+                    .subscribeOn(Schedulers.io())
+                    .observeOn(AndroidSchedulers.mainThread())
+                    .o2Subscribe {
+                        onNext {
+                            XLog.debug("返回下载地址:$it")
+                            mView?.localFile(path, body.type!!, position)
+                        }
+                        onError { e, _ ->
+                            XLog.error("", e)
+                            mView?.downloadFileFail("获取文件异常, ${e?.message}")
+                        }
+                    }
+
+
+
+
         }
     }
 }

+ 4 - 0
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/O2IM.kt

@@ -105,6 +105,10 @@ object O2IM {
         return im_emoji_hashMap[key] ?: R.mipmap.im_emotion_01
     }
 
+    enum class AudioPlayState {
+        playing,
+        idle
+    }
 
     //instant message type
 

+ 291 - 0
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/O2LocationActivity.kt

@@ -0,0 +1,291 @@
+package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.im
+
+import android.support.v7.app.AppCompatActivity
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+import android.support.v7.widget.Toolbar
+import android.view.Menu
+import android.view.MenuItem
+import android.widget.TextView
+import com.baidu.location.BDLocation
+import com.baidu.location.BDLocationListener
+import com.baidu.location.LocationClient
+import com.baidu.location.LocationClientOption
+import com.baidu.mapapi.map.*
+import com.baidu.mapapi.model.LatLng
+import com.baidu.mapapi.search.geocode.*
+import kotlinx.android.synthetic.main.activity_o2_location.*
+import kotlinx.android.synthetic.main.snippet_appbarlayout_toolbar.*
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.R
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XLog
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XToast
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.visible
+import org.jetbrains.anko.doAsync
+
+class O2LocationActivity : AppCompatActivity(), BDLocationListener {
+
+
+    companion object{
+        const val RESULT_LOCATION_KEY = "RESULT_LOCATION_KEY"
+
+        const val mode_key = "mode_key"
+        const val location_data_key = "location_data_key"
+        /**
+         * 开始选择位置
+         */
+        fun startChooseLocation(): Bundle {
+            val bundle = Bundle()
+            bundle.putInt(mode_key, 0)
+            return bundle
+        }
+
+        /**
+         * 查看位置
+         */
+        fun showLocation(data: LocationData): Bundle {
+            val bundle = Bundle()
+            bundle.putInt(mode_key, 1)
+            bundle.putParcelable(location_data_key, data)
+            return bundle
+        }
+    }
+
+    private var mode = 0 //模式 0 选择位置 1查看位置
+    private var locationData: LocationData? = null
+
+    //百度地图
+    private var mBaiduMap: BaiduMap? = null
+    private val mLocationClient: LocationClient by lazy { LocationClient(this) }
+    private var marker: Marker? = null
+    private val geoCoder: GeoCoder by lazy { GeoCoder.newInstance() }
+
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_o2_location)
+
+        //数据初始化
+        mode = intent.getIntExtra(mode_key, 0)
+        locationData = intent.getParcelableExtra(location_data_key)
+        if (mode == 0) {
+            setupToolBar("点击地图选择位置")
+        }else {
+            if (locationData == null) {
+                XToast.toastShort(this, "传入参数错误!")
+                finish()
+            }
+            setupToolBar(locationData?.address ?: "位置")
+        }
+
+
+        //地图
+        mBaiduMap = map_baidu_o2_location.map
+        val builder = MapStatus.Builder()
+        builder.zoom(19.0f)
+        mBaiduMap?.setMapStatus(MapStatusUpdateFactory.newMapStatus(builder.build()))
+        mBaiduMap?.mapType = BaiduMap.MAP_TYPE_NORMAL
+        if (mode == 0) { //选择位置模式 需要加入点击功能
+            //点击地图 设置位置数据 画图钉
+            mBaiduMap?.setOnMapClickListener(object : BaiduMap.OnMapClickListener {
+                override fun onMapClick(latLng: LatLng?) {
+                    XLog.debug("onMapClick latitude:${latLng?.latitude}, longitude:${latLng?.longitude}")
+                    markerPoint(latLng, null)
+                    searchAddress(latLng)
+                }
+
+                override fun onMapPoiClick(poi: MapPoi?): Boolean {
+                    val latLng = poi?.position
+                    XLog.debug("onMapPoiClick latitude:${latLng?.latitude}, longitude:${latLng?.longitude}")
+                    markerPoint(latLng, null)
+                    searchAddress(latLng)
+                    return false
+                }
+            })
+            // 开启定位图层
+            mBaiduMap?.isMyLocationEnabled = true
+            mLocationClient.registerLocationListener(this)
+            initBaiduLocation()
+            mLocationClient.start()
+        }else { //查看模式 把位置图钉画上去
+            val lat = LatLng(locationData?.latitude!!, locationData?.longitude!!)
+            markerPoint(lat, locationData?.addressDetail)
+            val mapStatus: MapStatusUpdate = MapStatusUpdateFactory.newLatLng(lat)
+            mBaiduMap?.setMapStatus(mapStatus)
+        }
+
+    }
+
+    override fun onDestroy() {
+        geoCoder.destroy()
+        super.onDestroy()
+    }
+    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
+        if (mode == 0) {
+            menuInflater.inflate(R.menu.menu_location_send, menu)
+        }
+        return super.onCreateOptionsMenu(menu)
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem?): Boolean {
+        when(item?.itemId) {
+            R.id.location_send -> {
+                if (locationData == null){
+                    XToast.toastShort(this, "请先选择一个位置!")
+                }else {
+                    intent.putExtra(RESULT_LOCATION_KEY, locationData)
+                    setResult(RESULT_OK, intent)
+                    finish()
+                }
+                return true
+            }
+        }
+        return super.onOptionsItemSelected(item)
+    }
+
+
+    override fun onReceiveLocation(location: BDLocation?) {
+        XLog.debug("onReceive locType:${location?.locType}, latitude:${location?.latitude}, longitude:${location?.longitude}")
+        if (location != null) {
+            doAsync {
+                // 构造定位数据
+                val locData = MyLocationData.Builder()
+                        .accuracy(location.radius)
+                        // 此处设置开发者获取到的方向信息,顺时针0-360
+                        .direction(location.direction)
+                        .latitude(location.latitude)
+                        .longitude(location.longitude).build()
+                // 设置定位数据
+                mBaiduMap?.setMyLocationData(locData)
+                // 设置定位图层的配置(定位模式,是否允许方向信息,用户自定义定位图标)
+                val bit: BitmapDescriptor = BitmapDescriptorFactory
+                        .fromResource(R.mipmap.task_red_point)
+                val config = MyLocationConfiguration(MyLocationConfiguration.LocationMode.FOLLOWING, true, bit)
+                mBaiduMap?.setMyLocationConfiguration(config)
+                //定位成功后关闭
+                mLocationClient.stop()
+            }
+
+
+        }
+    }
+
+    override fun onConnectHotSpotMessage(p0: String?, p1: Int) {
+        XLog.debug("onConnectHotSpotMessage, p0:$p0, p1:$p1")
+    }
+
+    private fun setupToolBar(title:String = "") {
+        toolbar_snippet_top_bar.title = ""
+        setSupportActionBar(toolbar_snippet_top_bar)
+        toolbar_snippet_top_bar.setNavigationIcon(R.mipmap.ic_back_mtrl_white_alpha)
+        toolbar_snippet_top_bar.setNavigationOnClickListener {
+            XLog.debug("点了 关闭了。。。。。。。")
+            finish()
+        }
+
+        tv_snippet_top_title.text = title
+    }
+
+
+    /**
+     * 标记位置到地图上
+     */
+    private fun markerPoint(latLng: LatLng?, address: String?) {
+        if (latLng==null) {
+            XLog.error("坐标为空")
+            return
+        }
+        if (marker == null) {
+            //构建Marker图标
+            val bitmap = BitmapDescriptorFactory
+                    .fromResource(R.mipmap.icon_map_location)
+            val options = MarkerOptions()
+                    .position(latLng)  //设置marker的位置
+                    .title(address ?: "")
+                    .icon(bitmap)  //设置marker图标
+                    .zIndex(9)
+            //将marker添加到地图上
+            marker = mBaiduMap?.addOverlay(options) as Marker
+        }else {
+            marker?.position = latLng
+        }
+    }
+
+    /**
+     * 根据经纬度查询地址信息
+     */
+    private fun searchAddress(latLng: LatLng?) {
+        //搜索地址信息
+        geoCoder.setOnGetGeoCodeResultListener(object : OnGetGeoCoderResultListener {
+            override fun onGetGeoCodeResult(p0: GeoCodeResult?) {
+            }
+
+            override fun onGetReverseGeoCodeResult(p0: ReverseGeoCodeResult?) {
+                if(locationData == null) {
+                    locationData = LocationData()
+                }
+                locationData?.address = p0?.address
+                locationData?.addressDetail = p0?.sematicDescription
+                locationData?.latitude = p0?.location?.latitude
+                locationData?.longitude = p0?.location?.longitude
+                runOnUiThread {
+                    tv_o2_location_address.text = p0?.address
+                    tv_o2_location_address.visible()
+                }
+            }
+        })
+        geoCoder.reverseGeoCode(ReverseGeoCodeOption().location(latLng))
+    }
+
+    private fun initBaiduLocation() {
+        val option = LocationClientOption()
+        option.locationMode = LocationClientOption.LocationMode.Hight_Accuracy//可选,默认高精度,设置定位模式,高精度,低功耗,仅设备
+        option.setCoorType("bd09ll")//百度坐标系 可选,默认gcj02,设置返回的定位结果坐标系
+        option.setScanSpan(5000)//5秒一次定位 可选,默认0,即仅定位一次,设置发起定位请求的间隔需要大于等于1000ms才是有效的
+        option.setIsNeedAddress(true)//可选,设置是否需要地址信息,默认不需要
+        option.isOpenGps = true//可选,默认false,设置是否使用gps
+        option.isLocationNotify = true//可选,默认false,设置是否当GPS有效时按照1S/1次频率输出GPS结果
+        option.setIsNeedLocationDescribe(true)//可选,默认false,设置是否需要位置语义化结果,可以在BDLocation.getLocationDescribe里得到,结果类似于“在北京天安门附近”
+        option.setIsNeedLocationPoiList(true)//可选,默认false,设置是否需要POI结果,可以在BDLocation.getPoiList里得到
+        option.setIgnoreKillProcess(false)//可选,默认true,定位SDK内部是一个SERVICE,并放到了独立进程,设置是否在stop的时候杀死这个进程,默认不杀死
+        option.SetIgnoreCacheException(false)//可选,默认false,设置是否收集CRASH信息,默认收集
+        option.setEnableSimulateGps(false)//可选,默认false,设置是否需要过滤GPS仿真结果,默认需要
+        mLocationClient.locOption = option
+    }
+
+
+    /**
+     * 地址数据对象
+     */
+    class LocationData(
+            var address: String? = null, //type=location的时候位置信息
+            var addressDetail: String? = null,
+            var latitude: Double? = null,//type=location的时候位置信息
+            var longitude: Double? = null//type=location的时候位置信息
+    ) : Parcelable {
+        constructor(source: Parcel) : this(
+                source.readString(),
+                source.readString(),
+                source.readValue(Double::class.java.classLoader) as Double?,
+                source.readValue(Double::class.java.classLoader) as Double?
+        )
+
+        override fun describeContents() = 0
+
+        override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) {
+            writeString(address)
+            writeString(addressDetail)
+            writeValue(latitude)
+            writeValue(longitude)
+        }
+
+        companion object {
+            @JvmField
+            val CREATOR: Parcelable.Creator<LocationData> = object : Parcelable.Creator<LocationData> {
+                override fun createFromParcel(source: Parcel): LocationData = LocationData(source)
+                override fun newArray(size: Int): Array<LocationData?> = arrayOfNulls(size)
+            }
+        }
+    }
+}
+

+ 7 - 5
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/fm/O2IMConversationFragment.kt

@@ -23,6 +23,7 @@ import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.InstantMessage
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.IMConversationInfo
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.IMMessage
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.IMMessageBody
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.MessageType
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.vo.ContactPickerResult
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.DateHelper
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XLog
@@ -70,15 +71,15 @@ class O2IMConversationFragment : BaseMVPViewPagerFragment<O2IMConversationContra
                     if (lastMessage != null) {
                         val lastTime = DateHelper.convertStringToDate(lastMessage.createTime)
                         val lastMessageBody = lastMessage.messageBody()
-                        when(lastMessageBody) {
-                            is IMMessageBody.Emoji -> {
+                        when(lastMessageBody?.type) {
+                            MessageType.emoji.key -> {
                                 val image = holder.getView<ImageView>(R.id.tv_o2_im_con_last_message_emoji)
-                                image.setImageResource(O2IM.emojiResId(lastMessageBody.body))
+                                image.setImageResource(O2IM.emojiResId(lastMessageBody.body!!))
                                 image.visible()
                                 val text = holder.getView<TextView>(R.id.tv_o2_im_con_last_message)
                                 text.gone()
                             }
-                            is IMMessageBody.Text -> {
+                            MessageType.text.key -> {
                                 val image = holder.getView<ImageView>(R.id.tv_o2_im_con_last_message_emoji)
                                 image.gone()
                                 val text = holder.getView<TextView>(R.id.tv_o2_im_con_last_message)
@@ -89,10 +90,11 @@ class O2IMConversationFragment : BaseMVPViewPagerFragment<O2IMConversationContra
                                 val image = holder.getView<ImageView>(R.id.tv_o2_im_con_last_message_emoji)
                                 image.gone()
                                 val text = holder.getView<TextView>(R.id.tv_o2_im_con_last_message)
-                                text.text = ""
+                                text.text = lastMessageBody?.body
                                 text.visible()
                             }
                         }
+
                         holder.setText(R.id.tv_o2_im_con_last_message_time, DateHelper.friendlyTime(lastTime))
 
                     }

+ 0 - 1
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/meeting/invited/MeetingDetailInfoActivity.kt

@@ -79,7 +79,6 @@ class MeetingDetailInfoActivity : BaseMVPActivity<MeetingDetailInfoContract.View
     override fun downloadAttachmentSuccess(file: File?) {
         hideLoadingDialog()
         XLog.debug(file?.name)
-        XToast.toastShort(this,"下载成功")
         if (file != null && file.exists()) AndroidUtils.openFileWithDefaultApp(this, file)
     }
 

+ 16 - 73
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/meeting/invited/MeetingDetailInfoPresenter.kt

@@ -3,12 +3,16 @@ package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.meeting.invited
 import android.widget.TextView
 import net.muliba.accounting.app.ExceptionHandler
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BasePresenterImpl
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.APIAddressHelper
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.ResponseHandler
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.RetrofitClient
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.enums.APIDistributeTypeEnum
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.meeting.MeetingFileInfoJson
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.FileExtensionHelper
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.O2FileDownloadHelper
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.SDCardHelper
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XLog
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.o2Subscribe
 import org.jetbrains.anko.doAsync
 import org.jetbrains.anko.uiThread
 import rx.android.schedulers.AndroidSchedulers
@@ -26,81 +30,20 @@ class MeetingDetailInfoPresenter : BasePresenterImpl<MeetingDetailInfoContract.V
 
     override fun downloadMeetingFile(meetingFileInfoJson: MeetingFileInfoJson) {
         val path = FileExtensionHelper.getXBPMMEETINGAttachmentFileByName(meetingFileInfoJson.name)
-        doAsync {
-            try {
-                val file = File(path)
-                if (!file.exists()) {
-                    val call = RetrofitClient.instance().meetingAssembleControlApi()
-                            .downloadMeetingFile(meetingFileInfoJson.id)
-                    SDCardHelper.generateNewFile(path)
-                    val responseBody = call.execute()
-                    val headerDisposition = responseBody.headers().get("Content-Disposition")
-                    XLog.debug("header disposition: $headerDisposition")
-                    val dataInput = DataInputStream(responseBody.body()?.byteStream())
-                    val fileOut = DataOutputStream(FileOutputStream(file))
-                    val buffer = ByteArray(4096)
-                    var count = 0
-                    do {
-                        count = dataInput.read(buffer)
-                        if (count > 0) {
-                            fileOut.write(buffer, 0, count)
-                        }
-                    } while (count > 0)
-                    fileOut.close()
-                    dataInput.close()
-                }
-                uiThread {
-                    mView?.downloadAttachmentSuccess(file)
-                }
-            } catch (e: Exception) {
-                XLog.error("下载附件失败!", e)
-                if (File(path).exists()) {
-                    File(path).delete()
-                }
-            }
-
-            /*val call = RetrofitClient.instance(it.getContext()).meetingAssembleControlApi()
-                    .downloadMeetingFile(meetingFileInfoJson.id)
-                try {
-                    SDCardHelper.generateNewFile(path)
-                    val responseBody = call.execute()
-                    val headerDisposition = responseBody.headers().get("Content-Disposition")
-                    XLog.debug("header disposition: $headerDisposition")
-                    val dataInput = DataInputStream(responseBody.body().byteStream())
-                    val fileOut = DataOutputStream(FileOutputStream(file))
-                    val buffer = ByteArray(4096)
-                    var count = 0
-                    do {
-                        count = dataInput.read(buffer)
-                        if (count > 0) {
-                            fileOut.write(buffer, 0, count)
-                        }
-                    } while (count > 0)
-                    fileOut.close()
-                    dataInput.close()
-                } catch (e: Exception) {
-                    XLog.error("下载附件失败!", e)
-                    if (file.exists()) {
-                        file.delete()
+        val downloadUrl = APIAddressHelper.instance()
+                .getCommonDownloadUrl(APIDistributeTypeEnum.x_meeting_assemble_control, "jaxrs/attachment/${meetingFileInfoJson.id}/download/true")
+        O2FileDownloadHelper.download(downloadUrl, path)
+                .subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .o2Subscribe {
+                    onNext {
+                        mView?.downloadAttachmentSuccess(File(path))
+                    }
+                    onError { e, _ ->
+                        XLog.error("", e)
                     }
                 }
-            Observable.create(Observable.OnSubscribe<File> { t ->
-                val thisFile = File(path)
-                if (file.exists()) {
-                    t?.onNext(thisFile)
-                } else {
-                    t?.onError(Exception("附件下载异常,找不到文件!"))
-                }
-                t?.onCompleted()
-            })
-                    .subscribeOn(Schedulers.io())
-                    .observeOn(AndroidSchedulers.mainThread())
-                    .subscribe({ file -> it.downloadAttachmentSuccess(file) }, { e ->
-                        XToast.toastShort(it.getContext(), "下载附件失败,${e.message}")
-                        XLog.error("",e)
-                        it.downloadAttachmentSuccess(null)
-                    })*/
-        }
+
     }
 
     override fun asyncLoadPersonName(nameTv: TextView, id: String) {

+ 107 - 54
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/o2/webview/DownloadDocument.kt

@@ -5,6 +5,7 @@ import android.os.Looper
 import android.text.TextUtils
 import com.tencent.smtt.sdk.QbSdk
 import com.tencent.smtt.sdk.ValueCallback
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.O2SDKManager
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.tbs.FileReaderActivity
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.RetrofitClient
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.download.DownloadProgressHandler
@@ -18,6 +19,8 @@ import java.io.DataInputStream
 import java.io.DataOutputStream
 import java.io.File
 import java.io.FileOutputStream
+import java.net.HttpURLConnection
+import java.net.URL
 
 /**
  * Created by fancyLou on 2018/8/22.
@@ -28,63 +31,67 @@ class DownloadDocument(val context: Activity) {
     /**
      * 下线公文 并打开预览
      */
-    fun downloadDocumentAndOpenIt(url: String, finishCallback: (()->Unit)) {
-        val urlBase = url.substringBeforeLast("/")
-        val id = url.substringAfterLast("/")
-        XLog.info("文档名称: $id , baseUrl: $urlBase")
-        var path = ""
-
-        RetrofitClient.instance().skinDownloadService("$urlBase/", object : DownloadProgressHandler() {
-            override fun onProgress(progress: Long, total: Long, done: Boolean) {
-                XLog.debug("$progress $total, $done")
-                XLog.debug("是否在主线程中运行" + (Looper.getMainLooper() == Looper.myLooper()).toString())
-                val myP = 100 * progress / total
-                XLog.debug(String.format("%d%% done\n", myP))
-                XLog.debug("done --->$done")
-
-            }
-        }).skinDownload(id)
-                .subscribeOn(Schedulers.io())
-                .flatMap { response ->
-                    var isDownFileSuccess = false
-                    try {
-                        val headers = response.headers()
-                        var fileName = headers.get("Content-Disposition")
-                        if (fileName!=null) {
-                            fileName = fileName.substringAfterLast("''")
-                        }
-                        XLog.debug("filename: $fileName")
-                        path = FileExtensionHelper.getXBPMWORKAttachmentFileByName(fileName)
-                        XLog.debug("path: $path")
-                        val file = File(path)
-                        if (!file.exists()) {
-                            SDCardHelper.generateNewFile(path)
-                        }
-                        val input = DataInputStream(response.body()?.byteStream())
-                        val output = DataOutputStream(FileOutputStream(file))
-                        val buffer = ByteArray(4096)
-                        var count = 0
-                        do {
-                            count = input.read(buffer)
-                            if (count > 0) {
-                                output.write(buffer, 0, count)
-                            }
-                        } while (count > 0)
-                        output.close()
-                        input.close()
-                        isDownFileSuccess = true
-                    } catch (e: Exception) {
-                        XLog.error("download file fail", e)
-                        isDownFileSuccess = false
+    fun downloadDocumentAndOpenIt(url: String, finishCallback: (() -> Unit)) {
+//        val urlBase = url.substringBeforeLast("/")
+//        val id = url.substringAfterLast("/")
+//        XLog.info("文档名称: $id , baseUrl: $urlBase")
+        XLog.info("开始下载文档: $url")
+        Observable.create<String> { subscriber ->
+            var file: File? = null
+            try {
+                val downloadUrl = URL(url)
+                val conn = downloadUrl.openConnection() as HttpURLConnection
+                conn.setRequestProperty("Accept-Encoding", "identity")
+                val newCookie = "x-token:" + O2SDKManager.instance().zToken
+                conn.setRequestProperty("Cookie", newCookie)
+                conn.setRequestProperty("x-token", O2SDKManager.instance().zToken)
+                conn.connect()
+                val inputStream = conn.inputStream
+                var fileName = conn.getHeaderField("Content-Disposition")
+                if (fileName != null) {
+                    fileName = fileName.substringAfterLast("''")
+                }
+                XLog.debug("下载文件名称: $fileName")
+                val path = FileExtensionHelper.getXBPMWORKAttachmentFileByName(fileName)
+                XLog.debug("本地文件存储地址: $path")
+                file = File(path)
+                val fos = FileOutputStream(file)
+                val buf = ByteArray(1024 * 8)
+                var currentLength = 0
+                while (true) {
+                    val num = inputStream.read(buf)
+                    currentLength += num
+                    // 计算进度条位置
+                    if (num <= 0) {
+                        break
                     }
-                    Observable.just(isDownFileSuccess)
-                }.observeOn(AndroidSchedulers.mainThread())
+                    fos.write(buf, 0, num)
+                    fos.flush()
+                }
+                XLog.debug("file length :$currentLength")
+                fos.flush()
+                fos.close()
+                inputStream.close()
+                subscriber.onNext(path)
+                subscriber.onCompleted()
+            } catch (e: Exception) {
+                try {
+                    if (file?.exists() == true) {
+                        file.delete()
+                    }
+                } catch (e: Exception) {
+                }
+                subscriber.onError(e)
+                subscriber.onCompleted()
+            }
+        }.subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
                 .o2Subscribe {
                     onNext { result ->
                         XLog.info("下载文档:$result")
-                        if (result) {
-                            openFileWithTBS(path, "")
-                        }else {
+                        if (TextUtils.isEmpty(result)) {
+                            openFileWithTBS(result, "")
+                        } else {
                             XToast.toastShort(context, "下载文档失败!")
                         }
                         finishCallback()
@@ -95,13 +102,59 @@ class DownloadDocument(val context: Activity) {
                         finishCallback()
                     }
                 }
+
+//        RetrofitClient.instance().skinDownloadService("$urlBase/", object : DownloadProgressHandler() {
+//            override fun onProgress(progress: Long, total: Long, done: Boolean) {
+//                XLog.debug("$progress $total, $done")
+//                XLog.debug("是否在主线程中运行" + (Looper.getMainLooper() == Looper.myLooper()).toString())
+//                val myP = 100 * progress / total
+//                XLog.debug(String.format("%d%% done\n", myP))
+//                XLog.debug("done --->$done")
+//
+//            }
+//        }).skinDownload(id)
+//                .subscribeOn(Schedulers.io())
+//                .flatMap { response ->
+//                    var isDownFileSuccess = false
+//                    try {
+//                        val headers = response.headers()
+//                        var fileName = headers.get("Content-Disposition")
+//                        if (fileName!=null) {
+//                            fileName = fileName.substringAfterLast("''")
+//                        }
+//                        XLog.debug("filename: $fileName")
+//                        path = FileExtensionHelper.getXBPMWORKAttachmentFileByName(fileName)
+//                        XLog.debug("path: $path")
+//                        val file = File(path)
+//                        if (!file.exists()) {
+//                            SDCardHelper.generateNewFile(path)
+//                        }
+//                        val input = DataInputStream(response.body()?.byteStream())
+//                        val output = DataOutputStream(FileOutputStream(file))
+//                        val buffer = ByteArray(4096)
+//                        var count = 0
+//                        do {
+//                            count = input.read(buffer)
+//                            if (count > 0) {
+//                                output.write(buffer, 0, count)
+//                            }
+//                        } while (count > 0)
+//                        output.close()
+//                        input.close()
+//                        isDownFileSuccess = true
+//                    } catch (e: Exception) {
+//                        XLog.error("download file fail", e)
+//                        isDownFileSuccess = false
+//                    }
+//                    Observable.just(isDownFileSuccess)
+//                }
     }
 
     //......没有集成
     private fun openFileWithTBS(path: String?, fileName: String) = if (!TextUtils.isEmpty(path)) {
         context.go<FileReaderActivity>(FileReaderActivity.startBundle(path!!))
 //        AndroidUtils.openFileWithDefaultApp(context, File(path))
-    }else {
+    } else {
         XLog.error("文档本地地址没有。。。。。。。。。。。")
     }
 }

+ 93 - 74
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/o2/webview/TaskWebViewPresenter.kt

@@ -5,16 +5,15 @@ import net.muliba.accounting.app.ExceptionHandler
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.O2App
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.O2SDKManager
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BasePresenterImpl
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.APIAddressHelper
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.ResponseHandler
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.enums.APIDistributeTypeEnum
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.IdData
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.main.AttachmentInfo
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.o2.ReadData
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.o2.TaskData
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.o2.WorkLog
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.FileExtensionHelper
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.FileUtil
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.SDCardHelper
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XLog
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.*
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.o2Subscribe
 import okhttp3.MediaType
 import okhttp3.MultipartBody
@@ -208,42 +207,51 @@ class TaskWebViewPresenter : BasePresenterImpl<TaskWebViewContract.View>(), Task
                         val info: AttachmentInfo? = response.data
                         if (info != null) {
                             val path = FileExtensionHelper.getXBPMWORKAttachmentFileByName(info.name)
-                            val file = File(path)
-                            if (!file.exists()) { //下载
-                                try {
-                                    SDCardHelper.generateNewFile(path)
-                                    val call = service.downloadWorkAttachment(attachmentId, workId)
-                                    val downloadRes = call.execute()
-                                    val headerDisposition = downloadRes.headers().get("Content-Disposition")
-                                    XLog.debug("header disposition: $headerDisposition")
-                                    val dataInput = DataInputStream(downloadRes.body()?.byteStream())
-                                    val fileOut = DataOutputStream(FileOutputStream(file))
-                                    val buffer = ByteArray(4096)
-                                    var count = 0
-                                    do {
-                                        count = dataInput.read(buffer)
-                                        if (count > 0) {
-                                            fileOut.write(buffer, 0, count)
-                                        }
-                                    } while (count > 0)
-                                    fileOut.close()
-                                    dataInput.close()
-                                } catch (e: Exception) {
-                                    XLog.error("下载附件失败!", e)
-                                    if (file.exists()) {
-                                        file.delete()
+                            SDCardHelper.generateNewFile(path)
+                            val downloadUrl = APIAddressHelper.instance()
+                                    .getCommonDownloadUrl(APIDistributeTypeEnum.x_processplatform_assemble_surface, "jaxrs/attachment/download/$attachmentId/work/$workId/stream")
+                            O2FileDownloadHelper.download(downloadUrl, path)
+                                    .flatMap {
+                                        Observable.just(File(path))
                                     }
-                                }
-                            }
-                            Observable.create { t ->
-                                val thisfile = File(path)
-                                if (file.exists()) {
-                                    t?.onNext(thisfile)
-                                } else {
-                                    t?.onError(Exception("附件下载异常,找不到文件!"))
-                                }
-                                t?.onCompleted()
-                            }
+
+
+//                            val file = File(path)
+//                            if (!file.exists()) { //下载
+//                                try {
+//                                    SDCardHelper.generateNewFile(path)
+//                                    val call = service.downloadWorkAttachment(attachmentId, workId)
+//                                    val downloadRes = call.execute()
+//                                    val headerDisposition = downloadRes.headers().get("Content-Disposition")
+//                                    XLog.debug("header disposition: $headerDisposition")
+//                                    val dataInput = DataInputStream(downloadRes.body()?.byteStream())
+//                                    val fileOut = DataOutputStream(FileOutputStream(file))
+//                                    val buffer = ByteArray(4096)
+//                                    var count = 0
+//                                    do {
+//                                        count = dataInput.read(buffer)
+//                                        if (count > 0) {
+//                                            fileOut.write(buffer, 0, count)
+//                                        }
+//                                    } while (count > 0)
+//                                    fileOut.close()
+//                                    dataInput.close()
+//                                } catch (e: Exception) {
+//                                    XLog.error("下载附件失败!", e)
+//                                    if (file.exists()) {
+//                                        file.delete()
+//                                    }
+//                                }
+//                            }
+//                            Observable.create { t ->
+//                                val thisfile = File(path)
+//                                if (file.exists()) {
+//                                    t?.onNext(thisfile)
+//                                } else {
+//                                    t?.onError(Exception("附件下载异常,找不到文件!"))
+//                                }
+//                                t?.onCompleted()
+//                            }
                         } else {
                             Observable.create(object : Observable.OnSubscribe<File> {
                                 override fun call(t: Subscriber<in File>?) {
@@ -276,42 +284,53 @@ class TaskWebViewPresenter : BasePresenterImpl<TaskWebViewContract.View>(), Task
                         val info: AttachmentInfo? = response.data
                         if (info != null) {
                             val path = FileExtensionHelper.getXBPMWORKAttachmentFileByName(info.name)
-                            val file = File(path)
-                            if (!file.exists()) { //下载
-                                try {
-                                    SDCardHelper.generateNewFile(path)
-                                    val call = service.downloadWorkCompletedAttachment(attachmentId, workCompleted)
-                                    val downloadRes = call.execute()
-                                    val headerDisposition = downloadRes.headers().get("Content-Disposition")
-                                    XLog.debug("header disposition: $headerDisposition")
-                                    val dataInput = DataInputStream(downloadRes.body()?.byteStream())
-                                    val fileOut = DataOutputStream(FileOutputStream(file))
-                                    val buffer = ByteArray(4096)
-                                    var count = 0
-                                    do {
-                                        count = dataInput.read(buffer)
-                                        if (count > 0) {
-                                            fileOut.write(buffer, 0, count)
-                                        }
-                                    } while (count > 0)
-                                    fileOut.close()
-                                    dataInput.close()
-                                } catch (e: Exception) {
-                                    XLog.error("下载附件失败!", e)
-                                    if (file.exists()) {
-                                        file.delete()
+                            SDCardHelper.generateNewFile(path)
+                            val downloadUrl = APIAddressHelper.instance()
+                                    .getCommonDownloadUrl(APIDistributeTypeEnum.x_processplatform_assemble_surface, "jaxrs/attachment/download/$attachmentId/workcompleted/$workCompleted/stream")
+                            O2FileDownloadHelper.download(downloadUrl, path)
+                                    .flatMap {
+                                        Observable.just(File(path))
                                     }
-                                }
-                            }
-                            Observable.create { t ->
-                                val thisfile = File(path)
-                                if (file.exists()) {
-                                    t?.onNext(thisfile)
-                                } else {
-                                    t?.onError(Exception("附件下载异常,找不到文件!"))
-                                }
-                                t?.onCompleted()
-                            }
+
+
+//                            val file = File(path)
+//                            if (!file.exists()) { //下载
+//                                try {
+//                                    SDCardHelper.generateNewFile(path)
+//                                    val call = service.downloadWorkCompletedAttachment(attachmentId, workCompleted)
+//                                    val downloadRes = call.execute()
+//                                    val headerDisposition = downloadRes.headers().get("Content-Disposition")
+//                                    XLog.debug("header disposition: $headerDisposition")
+//                                    val dataInput = DataInputStream(downloadRes.body()?.byteStream())
+//                                    val fileOut = DataOutputStream(FileOutputStream(file))
+//                                    val buffer = ByteArray(4096)
+//                                    var count = 0
+//                                    do {
+//                                        count = dataInput.read(buffer)
+//                                        if (count > 0) {
+//                                            fileOut.write(buffer, 0, count)
+//                                        }
+//                                    } while (count > 0)
+//                                    fileOut.close()
+//                                    dataInput.close()
+//                                } catch (e: Exception) {
+//                                    XLog.error("下载附件失败!", e)
+//                                    if (file.exists()) {
+//                                        file.delete()
+//                                    }
+//                                }
+//                            }
+//                            Observable.create { t ->
+//                                val thisfile = File(path)
+//                                if (file.exists()) {
+//                                    t?.onNext(thisfile)
+//                                } else {
+//                                    t?.onError(Exception("附件下载异常,找不到文件!"))
+//                                }
+//                                t?.onCompleted()
+//                            }
+
+
                         } else {
                             Observable.create(object : Observable.OnSubscribe<File> {
                                 override fun call(t: Subscriber<in File>?) {

+ 12 - 2
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/core/service/PictureLoaderService.kt

@@ -23,6 +23,7 @@ import org.jetbrains.anko.dip
 import org.jetbrains.anko.doAsync
 import org.jetbrains.anko.uiThread
 import java.io.BufferedReader
+import java.io.FileOutputStream
 import java.io.InputStream
 import java.io.InputStreamReader
 import java.net.HttpURLConnection
@@ -297,8 +298,17 @@ class PictureLoaderService(val context: Context) {
         var inputstream:InputStream? = null
         try {
             XLog.debug("load avatar : $name")
-            val response = RetrofitClient.instance().assembleExpressApi().loadPersonAvatar(name).execute()
-            inputstream = response.body()?.byteStream()
+//            val response = RetrofitClient.instance().assembleExpressApi().loadPersonAvatar(name).execute()
+//            inputstream = response.body()?.byteStream()
+            val downloadUrl = APIAddressHelper.instance().getCommonDownloadUrl(APIDistributeTypeEnum.x_organization_assemble_express, "servlet/icon/$name")
+                val url = URL(downloadUrl)
+                val conn = url.openConnection() as HttpURLConnection
+                conn.setRequestProperty("Accept-Encoding", "identity")
+                val newCookie = "x-token:" + O2SDKManager.instance().zToken
+                conn.setRequestProperty("Cookie", newCookie)
+                conn.setRequestProperty("x-token", O2SDKManager.instance().zToken)
+                conn.connect()
+                inputstream = conn.inputStream
         }catch (e: Exception){XLog.error("获取头像失败", e)}
         return inputstream
     }

+ 73 - 0
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/utils/O2FileDownloadHelper.kt

@@ -0,0 +1,73 @@
+package net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils
+
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.O2SDKManager
+import rx.Observable
+import java.io.File
+import java.io.FileOutputStream
+import java.net.HttpURLConnection
+import java.net.URL
+
+
+/**
+ * Created by fancyLou on 2020-06-22.
+ * Copyright © 2020 O2. All rights reserved.
+ */
+
+object O2FileDownloadHelper {
+
+    fun download(downloadUrl: String, outputFilePath: String): Observable<Boolean> {
+        XLog.debug("准备下载文件 网络下载url: $downloadUrl 本地路径: $outputFilePath")
+        return Observable.create { subscriber ->
+            val file = File(outputFilePath)
+            if (file.exists()) {
+                subscriber.onNext(true)
+                subscriber.onCompleted()
+            }else {
+                try {
+                    val url = URL(downloadUrl)
+                    val conn = url.openConnection() as HttpURLConnection
+                    conn.setRequestProperty("Accept-Encoding", "identity")
+                    val newCookie = "x-token:" + O2SDKManager.instance().zToken
+                    conn.setRequestProperty("Cookie", newCookie)
+                    conn.setRequestProperty("x-token", O2SDKManager.instance().zToken)
+                    conn.connect()
+                    val inputStream = conn.inputStream
+                    var fileName = conn.getHeaderField("Content-Disposition")
+                    if (fileName!=null) {
+                        fileName = fileName.substringAfterLast("''")
+                    }
+                    XLog.debug("下载文件名称: $fileName")
+                    val fos = FileOutputStream(file, true)
+                    val buf = ByteArray(1024 * 8)
+                    var currentLength = 0
+                    while (true) {
+                        val num = inputStream.read(buf)
+                        currentLength += num
+                        // 计算进度条位置
+                        if (num <= 0) {
+                            break
+                        }
+                        fos.write(buf, 0, num)
+                        fos.flush()
+                    }
+                    XLog.debug("file length :$currentLength")
+                    fos.flush()
+                    fos.close()
+                    inputStream.close()
+                    subscriber.onNext(true)
+                    subscriber.onCompleted()
+                }catch (e: Exception){
+                    try {
+                        if (file.exists()) {
+                            file.delete()
+                        }
+                    } catch (e: Exception) {}
+                    subscriber.onError(e)
+                    subscriber.onCompleted()
+                }
+            }
+        }
+    }
+
+
+}

+ 13 - 0
o2android/app/src/main/res/drawable/f5_circle.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+	<item>
+		<shape android:shape="oval">
+			<solid android:color="#ffffff"/>
+		</shape>
+	</item>
+	<item android:bottom="1dp" android:left="1dp" android:right="1dp" android:top="1dp">
+		<shape android:shape="oval">
+			<solid android:color="#F5F5F5"/>
+		</shape>
+	</item>
+</layer-list>

+ 5 - 4
o2android/app/src/main/res/layout/activity_local_image_view.xml

@@ -10,12 +10,13 @@
     tools:context=".app.o2.webview.LocalImageViewActivity">
     <RelativeLayout
         android:layout_width="match_parent"
-        android:layout_height="48dp"
-        android:padding="@dimen/spacing_small">
+        android:layout_height="48dp">
         <ImageView
             android:id="@+id/image_local_view_back_btn"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
+            android:layout_width="42dp"
+            android:layout_height="42dp"
+            android:padding="@dimen/spacing_small"
+            android:scaleType="fitCenter"
             android:layout_alignParentStart="true"
             android:layout_centerVertical="true"
             android:src="@mipmap/ic_back_mtrl_white_alpha"/>

+ 38 - 2
o2android/app/src/main/res/layout/activity_o2_chat.xml

@@ -92,9 +92,9 @@
             android:paddingStart="@dimen/activity_horizontal_margin"
             android:paddingEnd="@dimen/activity_horizontal_margin"
             android:orientation="horizontal"
-            android:visibility="gone"
             android:baselineAligned="false">
              <LinearLayout
+                 android:id="@+id/ll_o2_chat_audio_btn"
                  android:layout_width="0dp"
                  android:layout_height="wrap_content"
                  android:layout_weight="1"
@@ -107,6 +107,7 @@
                      android:src="@mipmap/chat_mic" />
              </LinearLayout>
             <LinearLayout
+                android:id="@+id/ll_o2_chat_album_btn"
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
                 android:layout_weight="1"
@@ -119,6 +120,7 @@
                     android:src="@mipmap/chat_img" />
             </LinearLayout>
             <LinearLayout
+                android:id="@+id/ll_o2_chat_camera_btn"
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
                 android:layout_weight="1"
@@ -131,6 +133,7 @@
                     android:src="@mipmap/chat_camera" />
             </LinearLayout>
             <LinearLayout
+                android:id="@+id/ll_o2_chat_location_btn"
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
                 android:layout_weight="1"
@@ -147,13 +150,46 @@
         <android.support.v7.widget.RecyclerView
             android:id="@+id/rv_o2_chat_emoji_box"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
+            android:layout_height="250dp"
             android:layout_marginBottom="@dimen/spacing_small"
             android:visibility="gone"
             android:paddingStart="@dimen/activity_horizontal_margin"
             android:paddingEnd="@dimen/activity_horizontal_margin"
             />
 
+        <RelativeLayout
+            android:id="@+id/tv_o2_chat_audio_send_box"
+            android:layout_width="match_parent"
+            android:layout_height="180dp"
+            android:layout_marginBottom="@dimen/spacing_small"
+            android:visibility="gone">
+            <TextView
+                android:id="@+id/tv_o2_chat_audio_speak_title"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_centerHorizontal="true"
+                android:textSize="@dimen/font_small"
+                android:textColor="@color/z_color_subtitle_font"
+                android:text="@string/activity_im_audio_speak" />
+            <TextView
+                android:id="@+id/tv_o2_chat_audio_speak_duration"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_centerHorizontal="true"
+                android:layout_marginTop="@dimen/spacing_tiny"
+                android:layout_below="@+id/tv_o2_chat_audio_speak_title"
+                android:textSize="@dimen/font_mini"
+                android:textColor="@color/z_color_primary_blur_blue"
+                tools:text="00:01" />
+            <ImageButton
+                android:id="@+id/image_o2_chat_audio_speak_btn"
+                android:layout_width="96dp"
+                android:layout_height="96dp"
+                android:src="@mipmap/chat_mic"
+                android:layout_centerInParent="true"
+                android:background="@drawable/f5_circle"
+                />
+        </RelativeLayout>
 
     </LinearLayout>
 

+ 38 - 0
o2android/app/src/main/res/layout/activity_o2_location.xml

@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".app.im.O2LocationActivity">
+    <include layout="@layout/snippet_appbarlayout_toolbar" />
+
+    <com.baidu.mapapi.map.MapView
+        android:id="@+id/map_baidu_o2_location"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/app_bar_layout_snippet"
+        android:clickable="true" />
+
+
+    <TextView
+        android:id="@+id/tv_o2_location_address"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintTop_toBottomOf="@+id/app_bar_layout_snippet"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:padding="@dimen/spacing_tiny"
+        android:background="@color/z_color_white_translucent_af"
+        android:maxLines="1"
+        android:textSize="@dimen/font_small"
+        android:textAlignment="center"
+        android:ellipsize="end"
+        android:visibility="gone"
+        tools:text="浙江省杭州市萧山区高桥路111号" />
+
+
+</android.support.constraint.ConstraintLayout>

+ 46 - 0
o2android/app/src/main/res/layout/item_o2_chat_message_text_left.xml

@@ -46,6 +46,52 @@
                 android:layout_height="32dp"
                 android:visibility="gone"
                 tools:src="@mipmap/im_emotion_01"/>
+            <ImageView
+                android:id="@+id/image_o2_chat_message_image_body"
+                android:layout_width="144dp"
+                android:layout_height="192dp"
+                android:visibility="gone"
+                android:scaleType="fitCenter"
+                tools:src="@mipmap/default_image"/>
+            <LinearLayout
+                android:id="@+id/ll_o2_chat_message_audio_body"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:visibility="gone"
+                android:orientation="horizontal">
+                <ImageView
+                    android:layout_width="24dp"
+                    android:layout_height="24dp"
+                    android:src="@mipmap/chat_icon_audio_play"/>
+                <TextView
+                    android:id="@+id/tv_o2_chat_message_audio_duration"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="@dimen/spacing_small"
+                    tools:text="60&#34;" />
+            </LinearLayout>
+            <RelativeLayout
+                android:id="@+id/rl_o2_chat_message_location_body"
+                android:layout_width="175dp"
+                android:layout_height="109dp"
+                android:visibility="gone"
+                android:orientation="horizontal">
+                <ImageView
+                    android:layout_width="175dp"
+                    android:layout_height="109dp"
+                    android:src="@mipmap/chat_location_background"/>
+                <TextView
+                    android:id="@+id/tv_o2_chat_message_location_address"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:padding="@dimen/spacing_tiny"
+                    android:background="@color/z_color_white_translucent_af"
+                    android:maxLines="1"
+                    android:textSize="@dimen/font_small"
+                    android:ellipsize="end"
+                    tools:text="浙江省杭州市萧山区高桥路111号" />
+            </RelativeLayout>
         </LinearLayout>
 
         <ImageButton

+ 46 - 0
o2android/app/src/main/res/layout/item_o2_chat_message_text_right.xml

@@ -45,6 +45,52 @@
                 android:layout_height="32dp"
                 android:visibility="gone"
                 tools:src="@mipmap/im_emotion_01"/>
+            <ImageView
+                android:id="@+id/image_o2_chat_message_image_body"
+                android:layout_width="144dp"
+                android:layout_height="192dp"
+                android:visibility="gone"
+                android:scaleType="fitCenter"
+                tools:src="@mipmap/default_image"/>
+            <LinearLayout
+                android:id="@+id/ll_o2_chat_message_audio_body"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:visibility="gone"
+                android:orientation="horizontal">
+                <ImageView
+                    android:layout_width="24dp"
+                    android:layout_height="24dp"
+                    android:src="@mipmap/chat_icon_audio_play"/>
+                <TextView
+                    android:id="@+id/tv_o2_chat_message_audio_duration"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="@dimen/spacing_small"
+                    tools:text="60&#34;" />
+            </LinearLayout>
+            <RelativeLayout
+                android:id="@+id/rl_o2_chat_message_location_body"
+                android:layout_width="175dp"
+                android:layout_height="109dp"
+                android:visibility="gone"
+                android:orientation="horizontal">
+                <ImageView
+                    android:layout_width="175dp"
+                    android:layout_height="109dp"
+                    android:src="@mipmap/chat_location_background"/>
+                <TextView
+                    android:id="@+id/tv_o2_chat_message_location_address"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:padding="@dimen/spacing_tiny"
+                    android:background="@color/z_color_white_translucent_af"
+                    android:maxLines="1"
+                    android:textSize="@dimen/font_small"
+                    android:ellipsize="end"
+                    tools:text="浙江省杭州市萧山区高桥路111号" />
+            </RelativeLayout>
         </LinearLayout>
 
         <ImageButton

+ 11 - 0
o2android/app/src/main/res/menu/menu_location_send.xml

@@ -0,0 +1,11 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    tools:context=".app.im.O2LocationActivity">
+
+    <item android:id="@+id/location_send"
+        app:showAsAction="ifRoom"
+        android:title="@string/activity_location_send"
+        android:orderInCategory="900" />
+
+</menu>

TEMPAT SAMPAH
o2android/app/src/main/res/mipmap-xhdpi/chat_icon_audio_play.png


TEMPAT SAMPAH
o2android/app/src/main/res/mipmap-xhdpi/chat_location_background.png


+ 3 - 0
o2android/app/src/main/res/values/strings.xml

@@ -215,6 +215,9 @@
     <string name="activity_im_tribe_save">保      存</string>
     <string name="activity_im_tribe_delete">删除并退出</string>
     <string name="activity_im_tribe_update">更      新</string>
+    <string name="activity_im_audio_speak">按住说话</string>
+    <string name="activity_im_audio_speak_cancel">上滑取消发送</string>
+    <string name="activity_location_send">发送</string>
 
     <!-- myinfo activity -->
     <string name="title_activity_my_info">我的资料</string>

+ 24 - 0
o2android/o2_auth_sdk/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/core/component/api/APIAddressHelper.kt

@@ -148,6 +148,30 @@ class APIAddressHelper private constructor() {
         return getAPIDistribute(APIDistributeTypeEnum.x_file_assemble_control) + "jaxrs/file/$pid/download/stream"
     }
 
+    /**
+     * 聊天消息 文件下载地址
+     */
+    fun getImFileDownloadUrl(fileId: String): String {
+        return getAPIDistribute(APIDistributeTypeEnum.x_message_assemble_communicate) + "jaxrs/im/msg/download/$fileId"
+    }
+
+    /**
+     * 通用的
+     * 下载文件的地址
+     * @param context: APIDistributeTypeEnum
+     * @param urlPath : 例如:jaxrs/im/msg/download/12222233333
+     */
+    fun getCommonDownloadUrl(context: APIDistributeTypeEnum, urlPath: String): String {
+        return getAPIDistribute(context) + urlPath
+    }
+
+    /**
+     * 聊天消息
+     */
+    fun getImImageDownloadUrlWithWH(fileId: String, width: Int, height: Int): String {
+        return getAPIDistribute(APIDistributeTypeEnum.x_message_assemble_communicate) + "jaxrs/im/msg/download/$fileId/image/width/$width/height/$height"
+    }
+
     /**
      * 云盘图片地址
      * @param fileId 图片文件id

+ 1 - 8
o2android/o2_auth_sdk/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/core/component/api/service/BBSAssembleControlService.kt

@@ -138,12 +138,5 @@ interface BBSAssembleControlService {
     fun getSubjectAttachList(@Path("id") id: String): Observable<ApiResponse<List<BBSSubjectAttachmentJson>>>
 
 
-    /**
-     * 附件下载
-     * @param attachId
-     * *
-     * @return
-     */
-    @GET("jaxrs/attachment/download/{id}/stream/{stream}")
-    fun downloadAttach(@Path("id") id: String,@Path("stream") stream: Boolean = true): Call<ResponseBody>
+
 }

+ 0 - 9
o2android/o2_auth_sdk/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/core/component/api/service/CMSAssembleControlService.kt

@@ -100,15 +100,6 @@ interface CMSAssembleControlService {
     fun getDocumentAttachList(@Path("docId") docId: String): Observable<ApiResponse<List<CMSDocumentAttachmentJson>>>
 
 
-    /**
-     * 附件下载
-     * @param id 附件id
-     * *
-     * @return
-     */
-    @GET("jaxrs/fileinfo/download/document/{id}/stream")
-    fun downloadAttach(@Path("id") id: String): Call<ResponseBody>
-
     /**
      * 附件上传
      * @param body

+ 0 - 10
o2android/o2_auth_sdk/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/core/component/api/service/CloudFileControlService.kt

@@ -127,16 +127,6 @@ interface CloudFileControlService {
     fun deleteFolder(@Path("folderId") folderId: String): Observable<ApiResponse<IdData>>
 
 
-    /**
-     * 下载文件
-     * @param id
-     * *
-     * @return
-     */
-    @GET("jaxrs/attachment2/{id}/download/stream")
-    fun downloadFile(@Path("id") id: String): Call<ResponseBody>
-
-
     /**
      * 分享
      */

+ 1 - 8
o2android/o2_auth_sdk/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/core/component/api/service/FileAssembleControlService.kt

@@ -17,14 +17,7 @@ import rx.Observable
 
 interface FileAssembleControlService {
 
-    /**
-     * 下载文件
-     * @param fileId
-     * *
-     * @return
-     */
-    @GET("jaxrs/attachment/{id}/download/stream")
-    fun downloadFile(@Path("id") id: String): Call<ResponseBody>
+
 
     /**
      * 上传文件

+ 0 - 8
o2android/o2_auth_sdk/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/core/component/api/service/MeetingAssembleControlService.kt

@@ -149,14 +149,6 @@ interface MeetingAssembleControlService {
     @DELETE("jaxrs/attachment/{id}")
     fun deleteMeetingFile(@Path("id") id: String): Observable<ApiResponse<IdData>>
 
-    /**
-     * 下载会议材料
-     * @param fileId
-     *
-     * @return
-     */
-    @GET("jaxrs/attachment/{id}/download/{stream}")
-    fun downloadMeetingFile(@Path("id") fileId: String,@Path("stream") stream: Boolean = true): Call<ResponseBody>
 
     /**
      * 更新会议

+ 16 - 0
o2android/o2_auth_sdk/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/core/component/api/service/MessageCommunicateService.kt

@@ -6,7 +6,11 @@ import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.InstantMessage
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.InstantMessageData
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.IMConversationInfo
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.IMMessage
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.IMMessageFileData
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.IMMessageForm
+import okhttp3.MultipartBody
+import okhttp3.ResponseBody
+import retrofit2.Call
 import retrofit2.http.*
 import rx.Observable
 
@@ -69,4 +73,16 @@ interface MessageCommunicateService {
     @GET("jaxrs/instant/list/currentperson/noim/count/{count}/desc")
     fun instantMessageList(@Path("count") count: Int) : Observable<ApiResponse<List<InstantMessage>>>
 
+    /**
+     * 上传文件
+     * im消息文件 图片 音频 视频等
+     * @param conversationId 会话id
+     * @param type 消息类型 image audio 等
+     */
+    @Multipart
+    @POST("jaxrs/im/msg/upload/{conversationId}/type/{type}")
+    fun uploadFile(@Path("conversationId") conversationId: String, @Path("type") type: String, @Part body: MultipartBody.Part): Observable<ApiResponse<IMMessageFileData>>
+
+
+
 }

+ 1 - 9
o2android/o2_auth_sdk/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/core/component/api/service/OrgAssembleExpressService.kt

@@ -155,15 +155,7 @@ interface OrgAssembleExpressService {
     @POST("jaxrs/unitduty/list/identity/unit/name/object")
     fun identityListByUnitAndDuty(@Body body: UnitDutyIdentityForm): Observable<ApiResponse<List<IdentityJson>>>
 
-    /**
-     * 获取用户头像
-     * @param person
-     * *
-     * @return
-     */
-    @GET("servlet/icon/{person}")
-    @Headers("Content-Type: application/octet-stream")
-    fun loadPersonAvatar(@Path("person") person: String): Call<ResponseBody>
+
 
     /**
      * 根据身份查询和层级查询组织

+ 19 - 19
o2android/o2_auth_sdk/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/core/component/api/service/ProcessAssembleSurfaceService.kt

@@ -339,25 +339,25 @@ interface ProcessAssembleSurfaceService {
     @PUT("jaxrs/attachment/update/{attachmentId}/work/{workId}")
     fun replaceAttachment(@Part body: MultipartBody.Part, @Path("attachmentId") attachmentId: String, @Path("workId") workId: String): Observable<ApiResponse<IdData>>
 
-    /**
-     * 附件下载
-     * @param attachId
-     * *
-     * @return
-     */
-    @GET("jaxrs/attachment/download/{attachId}/work/{workId}/stream")
-    @Headers("Content-Type: application/json; charset=utf-8")
-    fun downloadWorkAttachment(@Path("attachId") attachId: String, @Path("workId") workId: String): Call<ResponseBody>
-
-    /**
-     * 附件下载
-     * @param attachId
-     * *
-     * @return
-     */
-    @GET("jaxrs/attachment/download/{attachId}/workcompleted/{workCompletedId}/stream")
-    @Headers("Content-Type: application/json; charset=utf-8")
-    fun downloadWorkCompletedAttachment(@Path("attachId") attachId: String, @Path("workCompletedId") workCompletedId: String): Call<ResponseBody>
+//    /**
+//     * 附件下载
+//     * @param attachId
+//     * *
+//     * @return
+//     */
+//    @GET("jaxrs/attachment/download/{attachId}/work/{workId}/stream")
+//    @Headers("Content-Type: application/json; charset=utf-8")
+//    fun downloadWorkAttachment(@Path("attachId") attachId: String, @Path("workId") workId: String): Call<ResponseBody>
+//
+//    /**
+//     * 附件下载
+//     * @param attachId
+//     * *
+//     * @return
+//     */
+//    @GET("jaxrs/attachment/download/{attachId}/workcompleted/{workCompletedId}/stream")
+//    @Headers("Content-Type: application/json; charset=utf-8")
+//    fun downloadWorkCompletedAttachment(@Path("attachId") attachId: String, @Path("workCompletedId") workCompletedId: String): Call<ResponseBody>
 
     /**
      * 测试附件是否可用

+ 13 - 26
o2android/o2_auth_sdk/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/model/bo/api/im/IMMessage.kt

@@ -1,8 +1,8 @@
 package net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im
 
 import android.text.TextUtils
-import org.json.JSONObject
-import org.json.JSONTokener
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.O2SDKManager
+
 
 data class IMMessage(
         var id: String = "",
@@ -18,33 +18,20 @@ data class IMMessage(
         if (TextUtils.isEmpty(body)) {
             return null
         }
-        val json = JSONTokener(body).nextValue()
-        if (json is JSONObject) {
-            try {
-                if (json.has("type")) {
-                    val type = json.getString("type")
-                    if (MessageType.text.key == type) {
-                        val textBody = json.getString("body")
-                        return IMMessageBody.Text(textBody)
-                    }else if(MessageType.emoji.key == type) {
-                        val textBody = json.getString("body")
-                        return IMMessageBody.Emoji(textBody)
-                    }
-                }else {
-                    val textBody = json.getString("body")
-                    return IMMessageBody.Text(textBody)
-                }
-            } catch (e: Exception) {
-            }
-
-        }
-        return null
+        return O2SDKManager.instance().gson.fromJson(body, IMMessageBody::class.java)
     }
-
-
 }
 
 enum class MessageType(val key:String) {
     text("text"),
-    emoji("emoji")
+    emoji("emoji"),
+    image("image"),
+    audio("audio"),
+    location("location")
+}
+
+enum class MessageBody(val body:String) {
+    image("[图片]"),
+    audio("[语音]"),
+    location("[位置]")
 }

+ 12 - 7
o2android/o2_auth_sdk/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/model/bo/api/im/IMMessageBody.kt

@@ -1,11 +1,16 @@
 package net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im
 
 
+class IMMessageBody(
+        var type: String?,
+        var body: String?,
+        var fileId: String? = null, //文件id
+        var fileExtension: String? = null, //文件扩展
+        var fileTempPath: String? = null, //本地临时文件地址
+        var audioDuration: String? = null, // 音频文件时长
+        var address: String? = null, //type=location的时候位置信息
+        var addressDetail: String? = null,
+        var latitude: Double? = null,//type=location的时候位置信息
+        var longitude: Double? = null//type=location的时候位置信息
+)
 
-sealed class IMMessageBody(open var  type: String) {
-
-    class Text(var  body: String): IMMessageBody(MessageType.text.key)
-    class Emoji(var  body: String): IMMessageBody(MessageType.emoji.key)
-
-
-}

+ 12 - 0
o2android/o2_auth_sdk/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/model/bo/api/im/IMMessageFileData.kt

@@ -0,0 +1,12 @@
+package net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im
+
+
+/**
+ * Created by fancyLou on 2020-06-21.
+ * Copyright © 2020 O2. All rights reserved.
+ */
+
+data class IMMessageFileData(
+        var id: String = "",
+        var fileExtension: String = ""
+)