Przeglądaj źródła

Merge branch 'develop' into 'feature/teamwork_0522'

# Conflicts:
#   o2web/source/x_component_TeamWork/TaskAttachmentList.js
o2null 5 lat temu
rodzic
commit
55b5f19173
100 zmienionych plików z 3189 dodań i 754 usunięć
  1. 108 142
      README.md
  2. 100 7
      gulpfile.js
  3. 5 1
      o2android/app/build.gradle
  4. 5 2
      o2android/app/src/main/AndroidManifest.xml
  5. 4 0
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/O2App.kt
  6. 5 3
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/attendance/main/AttendanceCheckInContract.kt
  7. 34 5
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/attendance/main/AttendanceCheckInFragment.kt
  8. 15 1
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/attendance/main/AttendanceCheckInPresenter.kt
  9. 18 38
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/bbs/view/BBSWebViewSubjectActivity.kt
  10. 1 1
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/calendar/vm/CalendarViewModel.kt
  11. 84 56
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/clouddrive/CloudDriveActivity.kt
  12. 1 1
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/clouddrive/CloudDriveCooperationFileFragment.kt
  13. 84 54
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/clouddrive/v2/CloudDiskFileDownloadHelper.kt
  14. 44 62
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/clouddrive/viewer/CloudDrivePictureViewerFragment.kt
  15. 49 37
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/cms/view/CMSWebViewPresenter.kt
  16. 564 20
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/O2ChatActivity.kt
  17. 9 1
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/O2ChatContract.kt
  18. 114 17
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/O2ChatMessageAdapter.kt
  19. 150 16
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/O2ChatPresenter.kt
  20. 211 0
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/O2IM.kt
  21. 99 0
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/O2InstantMessageActivity.kt
  22. 18 0
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/O2InstantMessageContract.kt
  23. 13 0
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/O2InstantMessagePresenter.kt
  24. 291 0
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/O2LocationActivity.kt
  25. 6 0
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/fm/O2IMConversationContract.kt
  26. 136 9
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/fm/O2IMConversationFragment.kt
  27. 62 0
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/fm/O2IMConversationPresenter.kt
  28. 0 1
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/meeting/invited/MeetingDetailInfoActivity.kt
  29. 16 73
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/meeting/invited/MeetingDetailInfoPresenter.kt
  30. 44 11
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/o2/main/MainActivity.kt
  31. 13 9
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/o2/organization/NewOrganizationPresenter.kt
  32. 49 35
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/o2/person/PersonActivity.kt
  33. 4 0
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/o2/person/PersonContract.kt
  34. 27 0
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/o2/person/PersonPresenter.kt
  35. 107 54
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/o2/webview/DownloadDocument.kt
  36. 93 74
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/o2/webview/TaskWebViewPresenter.kt
  37. 12 2
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/core/service/PictureLoaderService.kt
  38. 73 0
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/utils/O2FileDownloadHelper.kt
  39. 96 0
      o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/widgets/KeyboardLayout.java
  40. 13 0
      o2android/app/src/main/res/drawable/f5_circle.xml
  41. 5 4
      o2android/app/src/main/res/layout/activity_local_image_view.xml
  42. 1 0
      o2android/app/src/main/res/layout/activity_login.xml
  43. 62 4
      o2android/app/src/main/res/layout/activity_o2_chat.xml
  44. 19 0
      o2android/app/src/main/res/layout/activity_o2_instant_message.xml
  45. 38 0
      o2android/app/src/main/res/layout/activity_o2_location.xml
  46. 2 1
      o2android/app/src/main/res/layout/fragment_attendance_check_in.xml
  47. 15 0
      o2android/app/src/main/res/layout/fragment_main_bottom_bar_image.xml
  48. 96 3
      o2android/app/src/main/res/layout/fragment_o2_im_conversation.xml
  49. 2 0
      o2android/app/src/main/res/layout/item_attendance_check_in_record_list.xml
  50. 53 0
      o2android/app/src/main/res/layout/item_o2_chat_message_text_left.xml
  51. 53 0
      o2android/app/src/main/res/layout/item_o2_chat_message_text_right.xml
  52. 17 0
      o2android/app/src/main/res/layout/item_o2_im_chat_emoji.xml
  53. 27 4
      o2android/app/src/main/res/layout/item_o2_im_conversation.xml
  54. 11 6
      o2android/app/src/main/res/menu/menu_chat.xml
  55. 11 0
      o2android/app/src/main/res/menu/menu_location_send.xml
  56. BIN
      o2android/app/src/main/res/mipmap-xhdpi/chat_camera.png
  57. BIN
      o2android/app/src/main/res/mipmap-xhdpi/chat_camera_light.png
  58. BIN
      o2android/app/src/main/res/mipmap-xhdpi/chat_camera_light_blue.png
  59. BIN
      o2android/app/src/main/res/mipmap-xhdpi/chat_icon_audio_play.png
  60. BIN
      o2android/app/src/main/res/mipmap-xhdpi/chat_icon_ditu.png
  61. BIN
      o2android/app/src/main/res/mipmap-xhdpi/chat_iicon_tupian.png
  62. BIN
      o2android/app/src/main/res/mipmap-xhdpi/chat_iicon_xiangji.png
  63. BIN
      o2android/app/src/main/res/mipmap-xhdpi/chat_iicon_yuyin.png
  64. BIN
      o2android/app/src/main/res/mipmap-xhdpi/chat_img.png
  65. BIN
      o2android/app/src/main/res/mipmap-xhdpi/chat_img_light.png
  66. BIN
      o2android/app/src/main/res/mipmap-xhdpi/chat_img_light_blue.png
  67. BIN
      o2android/app/src/main/res/mipmap-xhdpi/chat_location.png
  68. BIN
      o2android/app/src/main/res/mipmap-xhdpi/chat_location_background.png
  69. BIN
      o2android/app/src/main/res/mipmap-xhdpi/chat_location_light.png
  70. BIN
      o2android/app/src/main/res/mipmap-xhdpi/chat_location_light_blue.png
  71. BIN
      o2android/app/src/main/res/mipmap-xhdpi/chat_mic_light.png
  72. BIN
      o2android/app/src/main/res/mipmap-xhdpi/chat_mic_light_blue.png
  73. BIN
      o2android/app/src/main/res/mipmap-xhdpi/group_default.png
  74. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_01.png
  75. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_02.png
  76. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_03.png
  77. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_04.png
  78. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_05.png
  79. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_06.png
  80. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_07.png
  81. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_08.png
  82. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_09.png
  83. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_10.png
  84. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_11.png
  85. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_12.png
  86. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_13.png
  87. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_14.png
  88. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_15.png
  89. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_16.png
  90. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_17.png
  91. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_18.png
  92. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_19.png
  93. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_20.png
  94. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_21.png
  95. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_22.png
  96. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_23.png
  97. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_24.png
  98. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_25.png
  99. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_26.png
  100. BIN
      o2android/app/src/main/res/mipmap-xhdpi/im_emotion_27.png

+ 108 - 142
README.md

@@ -10,7 +10,7 @@
 
 
 ***
 ***
 
 
-# O2OA : Java企业信息化系统,开源OA
+# O2OA : Java企业信息化系统,开源OA openSource OA Platform
 
 
 O2OA是基于J2EE架构,集成移动办公、智能办公,支持私有化部署,自适应负载能力的,能够很大程度上节约企业软件开发成本的基于AGPL协议开放源代码的企业信息化系统需求定制开发解决方案,对外提供专业的开发运维等技术服务。
 O2OA是基于J2EE架构,集成移动办公、智能办公,支持私有化部署,自适应负载能力的,能够很大程度上节约企业软件开发成本的基于AGPL协议开放源代码的企业信息化系统需求定制开发解决方案,对外提供专业的开发运维等技术服务。
 
 
@@ -78,25 +78,25 @@ O2OA自带的H2数据库是一个内嵌式的内存数据库,适合用于开
 另外,O2OA提供数据定期备份和恢复的能力,建议您开启正式环境的数据定期备份的功能,以确保数据库异常时可以进行数据恢复。
 另外,O2OA提供数据定期备份和恢复的能力,建议您开启正式环境的数据定期备份的功能,以确保数据库异常时可以进行数据恢复。
 
 
 
 
-# 最新版本服务器安装包下载[o2server_V5.0.1]\:
+# 最新版本服务器安装包下载[o2server_V5.1.1]\:
 
 
-windows 64Bit : http://download.o2oa.net/download/o2server-5.0.1-windows.zip
+windows 64Bit : http://download.o2oa.net/download/o2server-5.1.1-windows.zip
 
 
-Linux 64Bit : http://download.o2oa.net/download/o2server-5.0.1-linux.zip
+Linux 64Bit : http://download.o2oa.net/download/o2server-5.1.1-linux.zip
 
 
-MacOS : http://download.o2oa.net/download/o2server-5.0.1-macos.zip
+MacOS : http://download.o2oa.net/download/o2server-5.1.1-macos.zip
 
 
-AIX : http://download.o2oa.net/download/o2server-5.0.1-aix.zip
+AIX : http://download.o2oa.net/download/o2server-5.1.1-aix.zip
 
 
-raspberrypi(树莓派):http://download.o2oa.net/download/o2server-5.0.1-raspberrypi.zip
+raspberrypi(树莓派):http://download.o2oa.net/download/o2server-5.1.1-raspberrypi.zip
 
 
 ARM[深度Linux(deepin),优麒麟(Ubuntu),中标麒麟(NeoKylin),威科乐恩Linux(WiOS)]:
 ARM[深度Linux(deepin),优麒麟(Ubuntu),中标麒麟(NeoKylin),威科乐恩Linux(WiOS)]:
 
 
-http://download.o2oa.net/download/o2server-5.0.1-arm.zip
+http://download.o2oa.net/download/o2server-5.1.1-arm.zip
 
 
 RISC-V[Debian GNU/Linux,银河麒麟飞腾]:
 RISC-V[Debian GNU/Linux,银河麒麟飞腾]:
 
 
-http://download.o2oa.net/download/o2server-5.0.1-risc.zip
+http://download.o2oa.net/download/o2server-5.1.1-risc.zip
 
 
 
 
 
 
@@ -107,239 +107,205 @@ http://download.o2oa.net/download/o2server-5.0.1-risc.zip
 腾讯微云:https://share.weiyun.com/5krUMjj
 腾讯微云:https://share.weiyun.com/5krUMjj
 
 
 
 
-# 最新版本 v5.0.1\:
+# 最新版本 v5.1.1\:
 
 
 功能新增
 功能新增
 
 
-[平台管理]新增了一键下载图标功能。
+[安全认证]认证能力增强,支持微软oa/adfs单点认证及AD用户认证、用户组织数据同步能力
 
 
-[平台配置]新增了获取app下载地址接口。
+[安全认证]增加在用户登录时新增判断密码过期的能力,如果是先修改密码再重新登录
 
 
-[系统配置]新增了系统默认的数据库连接配置。
+[脚本设计]新增monaco脚本编辑器
 
 
-[中心服务]新增了断开O2云连接接口服务。
+[考勤管理]为考勤结果申诉新增自定义审批流程能力
 
 
-[流程管理]新增了流程版本管理和切换功能。
+[考勤管理]移动考勤打卡策略可选择一天二次,三次,四次打卡,目前实现三种策略
 
 
-[流程管理]新增了流程启动模式"草稿模式",创建流程时避免启动流程实例
+[流程管理]新增流程自定义提交功能
 
 
-[流程管理]新增了流程提交以后提示框的自定义接口方法。
+[流程管理]增加字典路径为null条件判断,解决oracel,kingbase数据获取问题
 
 
-[流程管理]新增了管理员撤回流程接口
+[流程维护]增加处理人搜索,业务数据映射搜索
 
 
-[流程管理]新增了流程意见中立刻显示手写意见的选项
+[流程维护]增加业务数据、是否流转等相关过滤
 
 
-[流程管理]新增了流程保存服务参数,提供静默保存能力。
+[平台功能]平台增加在线部署资源、部署服务的功能
 
 
-[流程管理]新增了流程组件:"服务"活动,实现子流程调用后主流程自动等待的功能。
+[平台功能]新版应用市场数据同步及展现
 
 
-[流程管理]新增了新版本天谷天印附件(电子签章后的附件)上传接口适配。
+[企业社区]企业社区增加对匿名访问权限的相关配置
 
 
-[表单设计]新增了数据网格中取消编辑的功能。
+[表单设计]人员选择组件增加群组中的身份
 
 
-[版式文件]新增了版式文件痕迹保留功能的移动端支持。
+[移动办公]Android端聊天表情和创建单聊功能
 
 
-[版式文件]新增了版式正文痕迹保留是否允许查看痕迹配置功能。
+[移动办公]Android移动办公应用添加底部消息未读数量,支持群聊
 
 
-[版式文件]新增了正文痕迹保留和回放功能。
+[移动办公]IOS聊天消息增加位置消息功能
 
 
-[通用组件]新增了日期选择组件日视图中的清除按钮。
+[移动办公]IOS移动办公IM消息应用增加语音消息、图片消息等功能
 
 
-[内容管理]新增了定时重新计算和维护文档权限信息的功能。
+[移动办公]IOS移动办公应用增加消息展现和聊天消息功能
 
 
-[数据中心]新增了视图中分页相关设置。
+[移动办公]平台增加antv组件,增加移动端脚本调试工具Eruda
 
 
-[数据中心]新增了视图中新增了自定义操作条、新增了视图事件、新增了视图API。
+[人员组织]增加人员组织升降序排序相关配置
 
 
-[数据中心]新增了视图分页圆角方角相关设置。
+[数据中心]增加清空自建表数据的功能
 
 
-[企业社区]新增了控制我的回贴列表排序方式的配置项。
+[数据中心]增加视图查询对数组的支持
 
 
-[企业社区]新增了两个配置适应对回帖的排序和对回帖删除的方式控制功能。
+[系统日志]增加了以集群方式在WEB端查看所有服务器的功能
 
 
-[考勤管理]新增了考勤管理同步钉钉打卡数据的功能。
+[移动办公]增加流程表单移动端蓝白界面样式
 
 
-[个人通讯录]新增了通讯录二维码与导出vcf文件的功能。
+功能优化
 
 
-[个人通讯录]新增了个人通讯录查询组织权限配置设计。
+[表单设计]优化版式正文编辑的部分功能,优化WORD拷贝缩进优化以及半角空格转全角空显示
 
 
-[系统日志]新增了系统日志管理日志分级标志。
+[表单设计]优化表单人员选择器,允许根据职务选择的时候进行跨层级全选
 
 
-[日志管理]新增了系统日志管理实时查看服务器日志的功能。
+[表单设计]优化表单转换功能,表单转PDF默认为A4的长宽
 
 
-[日志管理]新增了系统日志获取接口支持集群日志合并展现。
+[平台设计]优化表单设计,修复表单设计元素部分样式的问题
 
 
-[移动办公]新增了日程管理应用组织日历功能。
+[会议管理]优化会议显示,在列表中增加正在进行中会议的显示
 
 
-问题修复
-
-[系统功能]修复了WebSocket连接不稳定的一些问题。
-
-[系统功能]修复了响应头中x-token获取不到,无法更新下次请求头的x-token信息。
-
-[系统功能]修复了首页未激活移动办公app无法打开的问题。
-
-[权限管理]修复了角色中设置群组权限无效的的问题。
-
-[权限管理]修复了组织管理权限,修复RoleManager无法管理角色的问题。
-
-[单点认证]修复了使用oauth登录时RestActions对象找不到的问题
-
-[人员组织]修复了人员名称变更身份名称未同时更新的问题。
-
-[登录认证]修复了使用oauth登录时RestActions对象找不到的问题。
-
-[登录注销]修复了注销(logout)中在某些情况下用户会话未完全清除的问题。
+[脚本设计]优化流程表单中的弹出选择器组件,允许自定义弹出选择器选中分类
 
 
-[登录注销]修复了Logout时某些情况下没有成功删除token可以重新获取登录信息的问题。
+[考勤管理]优化考勤管理移动打卡API,以及三次打卡中的相关打卡时间计算逻辑和分析逻辑
 
 
-[平台首页]修复了新建流程在某些情况下可能报错的问题。
+[流程管理]优化架构,增加对长文本标题的处理逻辑
 
 
-[平台首页]修复了个人设置更改头像出错的问题。
+[流程管理]优化流程表单载入机制,大幅提升流程启动时表单展现效率
 
 
-[办公中心]修复了办公中心启动新流程界面中进行搜索后无法启动流程的问题。
+[流程管理]优化和补充审批日志中的部分信息
 
 
-[系统风格]修复了除Blue外其他风格模板html中少了mouseover事件绑定,导致无法显示刷新按钮的问题。
+[内容管理]优化内容管理文档权限机制,适应二段[如:unique@P]结构组织对象权限设定
 
 
-[系统风格]修复了某些情况下滚动条出现错误的问题。
+[平台架构]架构优化,集群切换功能优化,增加集群应用掉线判断的能力,避免在运行过程中出现集群应用掉线的情况
 
 
-[流程管理]修复了保存流程新版本时,弹出窗口大小不合适的问题。
+[平台架构]前端架构优化,支持主流前端技术集成:Vue, React, Angular
 
 
-[流程管理]修复了新建流程的时候在某些情况下传入身份参数出错的问题。
+[数据中心]视图关于内容管理权限查询优化
 
 
-[流程管理]修复了删除流程工作在某些情况下有一定机率导致的线程池锁死的问题。
-
-[流程管理]修复了流程应用管理中某些情况下的调度错误的问题。
-
-[流程管理]修复了在某些情况下禁止召回和无法替换表单的问题。
-
-[流程管理]修复了流程在某些情况下出现的调度错误的问题。
-
-[流程管理]修复了附件上传指定格式文件异常的问题。
-
-[流程管理]修复了版式文件保存原始版本会多次保存的问题。
-
-[流程管理]修复了流程表单中选择视图界面不能展开视图的问题。
-
-[流程管理]修复了某些情况下不能重置处理人的问题。
-
-[流程管理]修复了移动表单mobile_blue_flat_style样式的一些小问题。
+问题修复
 
 
-[流程管理]修复了流程提交的时候意见可能为undefined的问题。
+[表单设计]修复表单设计Tab页展现不正确的问题
 
 
-[流程管理]修复了新建流程实例界面搜索后不能启动的问题。
+[表单设计]修复移动端日期选择组件描述选择问题
 
 
-[流程管理]修复了流程脚本上下文环境中某些情况下deleteWork权限判断的错误的问题。
+[表单设计]修正版式正文从WORD文件粘贴后段落首行缩进不正确的问题
 
 
-[表单设计]修复了源代码Form.js文件中afterDelete拼写错误。
+[表单设计]修正某些情况下版式文件日期显示错误的问题
 
 
-[表单设计]修复了数据网格有合计的时候忽略非数字进行合计,而不是显示NaN的问题。
+[表单设计]修正某些情况下表单单选框选项不显示的问题
 
 
-[表单设计]修复了数据网格总计数值的时候,没有填写数据显示NaN的问题。
+[会议管理]修复了某些情况下会议管理重复选择邀请人等问题
 
 
-[表单设计]修复了数字组件设置为显示时计算时没有显示为0的问题。
+[脚本设计]修复拷贝、粘贴或剪切脚本时,会影响到表单和流程设计元素的问题
 
 
-[表单设计]修复了手机端选择时间某些情况出错的问题。
+[脚本设计]修复流程脚本在使用monaco编辑器时,点击保存按钮无法保存的问题
 
 
-[表单设计]修复了流程表单中多选按钮最后一个值不能被取消的问题。
+[考勤管理]修复考勤管理中请假半天计算出勤天数不正确的问题
 
 
-[表单设计]修复了表单中嵌入视图,过滤条件为时间不起作用的问题。
+[考勤管理]修复考勤优化修改后,无法根据个人请假信息正确计算考勤结果的问题
 
 
-[表单设计]修复了提交自动保存数据网格时,在数据网格事件中如果修改了data数据不会被保存的问题。
+[考勤管理]修复了考勤申诉使用流程时,启动流程和流程状态同步的问题
 
 
-[表单组件]修复了移动端表单选择主送的时候最后一项不显示的问题。
+[考勤管理]修复了移动打卡数据服务无法指定打卡时间的问题
 
 
-[表单组件]修复了表单上按职务按组织选择人员的时候某些情况下会出现重复选项的问题。
+[考勤管理]修复了以前的打卡在某些情况下会被误判为缺勤的问题,添加了每日定时分析前一天所有未签退的打卡信息
 
 
-[表单组件]修复了人员选择框在某些情况下高度异常的问题。
+[考勤管理]修复了在某些情况下移动打卡后无法查询出结果的问题
 
 
-[附件下载]修复了无法识别新版office(如docx)的mimetype类型的问题。
+[考勤管理]修复了在某些情况下移动打卡无效的问题
 
 
-[内容管理]修复了内容管理打开栏目设置界面报错的问题。
+[考勤管理]修复在某些特殊情况下,日程管理编辑时报错的问题以及对可见日程列表查询的相关优化
 
 
-[内容管理]修复了设置栏目分类的显示权限错误的问题。
+[流程管理]修复弹出选择器某些情况下无法取消选择分类的问题
 
 
-[门户管理]修复了部件设计脚本页面显示不正常的问题。
+[流程管理]修复了某些情况下移动端数据网格设置数据异常的问题
 
 
-[数据中心]修复了某些情况下表单中的视图加载完成后loading不消失的问题。
+[流程管理]修复了确认框取消按钮某些情况下无背景色的问题
 
 
-[数据中心]修复了一些条件没有正确显示的问题。
+[流程管理]修复了数据网格和多选按钮校验异常的问题
 
 
-[数据中心]修复了视图某些情况下使用setFilter方法报错的问题。
+[流程管理]修复了提交界面某些情况下选择人员字样没有隐藏的问题
 
 
-[数据中心]修复了视图公文样式的展示异常的问题。
+[流程管理]修复流程表单操作条组件设置操作图标的时候未及时关闭图标选择器的问题
 
 
-[数据中心]修复了视图时间字段转义后有时间差的问题。
+[流程管理]修复流程分支合并后待阅仍发送多条通知的问题
 
 
-[企业社区]修复了移动端论坛打不开的问题。
+[流程管理]修复流程结束后,没有“一键下载”和“打印”按钮的问题
 
 
-[企业社区]修复了投票贴在列表中具有编辑入口的问题。
+[流程管理]修复某些情况下流程应用中'无标题'显示为'undefined'的问题
 
 
-[企业社区]修复了删除主回复后还显示下层回复的问题。
+[流程管理]修复某些情况下普通人员打开流程应用编号视图报无权限错误的问题
 
 
-[企业社区]修复了已经采纳的解决方案回贴被删除后 ,主贴展示会出现回复不存在的报错的问题。
+[流程管理]修复数据网格删除最后一条数据保存后仍存在的问题
 
 
-[企业社区]修复了企业社区无主贴列表服务无法正常分页的问题。
+[流程管理]修复新建流程的时候,有可能会出现[object]字样的问题
 
 
-[企业社区]修复了主贴查询服务无法通过关键词过滤标题的问题。
+[流程管理]修复在某些情况下自定义弹出框分类未选中的问题
 
 
-[企业社区]修复了问题贴重复进行回帖采纳后,缓存未及时更新的问题。
+[门户管理]修复了某些情况下门户分类显示'null'的问题
 
 
-[企业社区]修复了社区发表回复时某些情况下头像和组织管理中不一致的问题。
+[门户管理]修复门户管理中某些情况下分类展现应用图标未正确获取的问题
 
 
-[企业社区]修复了企业社区中用户不能正常注销的问题。
+[内容管理]新增了管理员根据人员标识查询人员可以看到的文档列表服务
 
 
-[个人设置]修复了个人设置中修改密码校验不通过仍可修改问题。
+[内容管理]修复了某些情况下内容管理脚本报错的问题
 
 
-[个人设置]修复了个人设置中修改密码功能在桌面模式下显示不正常的问题。
+[内容管理]修复内容管理表单操作条不能设置样式的问题
 
 
-[脑图管理]修复了脑图管理中引入kity错误问题。
+[内容管理]修复内容管理表单脚本编辑器无法正常拷贝粘贴的问题
 
 
-[脑图管理]修复了脑图的列表在扁平风格模式下显示不正常的问题
+[内容管理]修复内容管理信息删除后,相关的热点图片不会立即自动删除的问题(需要等待热点图片自动检测后才能删除)
 
 
-[日程安排]修复月视图中日程标题超过表格宽度的时候不显示的问题。
+[内容管理]修复在存在发布文档的情况下分类仍可以在无任何提醒的情况下删除的问题
 
 
-[考勤管理]修复工作场所在https环境下不能正确显示地图的问题。
+[人员组织]修复部分版本中钉钉同步员工中编码不正确或者无法同步的问题
 
 
-[会议管理]修复会议在编辑的时候按钮换行显示的问题。
+[人员组织]修复某些情况下群组新增报错问题
 
 
-[消息管理]修复了websocket连接不上时会重复发起请求的问题。
+[日程管理]修复在某些特殊情况下,日程管理会展示两条系统自建日历的问题
 
 
-[消息管理]修复了消息管理相关的配置文件的缺陷。
+[数据中心]修复了视图某些情况下跳页报错的问题
 
 
-[移动办公]修复了IOS最新版本13.4会闪退的问题。
+[数据中心]修复视图某些情况下不能正确删除列和排序列顺序不对的问题
 
 
-[移动办公]修复了移动端表单撤回弹出alert的问题。
+[数据中心]修复视图搜索和高级搜索切换显示的数据未切换的问题
 
 
-系统优化
+[系统首页]修正首页日历高度在过低分辨率下无法对齐的问题
 
 
-[控制系统]优化了系统控制台命令,升级命令去掉口令验证。
+[移动办公]修复Android云文件分享的文件在某些情况下无法打开的问题
 
 
-[平台配置]系统配置messages.json文件错误文字修改。
+[移动办公]修复滚动图片不显示的的问题以及IM图片音频消息无法下载的问题
 
 
-[登录页]修改了APP的下载地址。
+[移动办公]修复了某些手机型下Android版脑图应用无法打开,闪退的问题
 
 
-[组件配置]去除了前端component相关配置。
+[移动办公]修复论坛帖子附件在某些情况下无法查看的问题
 
 
-[流程管理]为流程提交以后提示框的自定义接口方法提供更多的参数。
+[移动办公]修复某些情况下应用窗口关闭时移动端无法正常关闭的问题
 
 
-[流程管理]修改自动流转判断身份改为人员,x-token获取顺序改为 parameter->head->cookie->auth。
+[移动办公]修复消息模块功能中某些情况下修复无法下载文件的问题
 
 
-[内容管理]栏目管理修改列示分类相关服务。
+[移动办公]修复移动打卡在某些情况下无法定位的问题
 
 
-[数据中心]视图样式自定义功能、整理视图过滤条件。
+[移动办公]修复移动考勤打卡按钮在某些情况下无法显示的问题
 
 
-[数据中心]优化视图条件,整理视图过滤条件设置,根据不同的数据类型显示相关的过滤条件方式。
+[应用管理]修复某些情况下应用无法正常导出的问题
 
 
-[数据中心]优化视图条件,视图搜索条件中的时间选择允许选择秒。
+[云文件]修复了某些情况下不同用户上传同一文件到根目录失败的问题
 
 
-[用户认证]优化人员认证过程,调整获取认证token顺序。
+[云文件]修复了云文件在Oracle和人大金仓数据库环境下查询顶层列表不正确的问题
 
 
-[日志管理]优化实时查看服务器日志的功能,调整日志显示样式。
+[组织管理]修复了组织管理没有输入内容搜索结果异常的问题
 
 
-[企业社区]调整代码结构,优化了部分服务逻辑。
+[组织管理]修复组织管理搜索翻页后会出现范围外的内容的问题
 
 
-[移动办公]IOS源码移除部分体积较大的框架以减少APP体积。
+[组织管理]修复组织管理性别选择其他可能显示不正常的问题
 
 
 
 
 # 配置编译环境\:
 # 配置编译环境\:

+ 100 - 7
gulpfile.js

@@ -8,11 +8,13 @@ var gulp = require('gulp'),
     dateFormat = require('dateformat'),
     dateFormat = require('dateformat'),
     progress = require('progress-stream'),
     progress = require('progress-stream'),
     request = require("request"),
     request = require("request"),
-    uglify = require('gulp-tm-uglify'),
+    //uglify = require('gulp-tm-uglify'),
+    uglify = require('gulp-uglify-es').default,
     rename = require('gulp-rename'),
     rename = require('gulp-rename'),
     changed = require('gulp-changed'),
     changed = require('gulp-changed'),
     gulpif = require('gulp-if'),
     gulpif = require('gulp-if'),
     http = require('http');
     http = require('http');
+    concat = require('gulp-concat');
 var fg = require('fast-glob');
 var fg = require('fast-glob');
 var logger = require('gulp-logger');
 var logger = require('gulp-logger');
 var assetRev = require('gulp-tm-asset-rev');
 var assetRev = require('gulp-tm-asset-rev');
@@ -359,7 +361,7 @@ function build_web_minimize(cb) {
 ---------------------------------------------------------------------`);
 ---------------------------------------------------------------------`);
 
 
     var dest = 'target/o2server/servers/webServer/';
     var dest = 'target/o2server/servers/webServer/';
-    var src_min = ['o2web/source/**/*.js', '!**/*.spec.js', '!**/test/**', '!o2web/source/o2_lib/**/*'];
+    var src_min = ['o2web/source/**/*.js', '!o2web/source/o2_core/o2.js', '!**/*.spec.js', '!**/test/**', '!o2web/source/o2_lib/**/*'];
 
 
     var entries = fg.sync(src_min, { dot: false});
     var entries = fg.sync(src_min, { dot: false});
     var size = entries.length;
     var size = entries.length;
@@ -382,7 +384,7 @@ function build_web_minimize(cb) {
 
 
 function build_web_move() {
 function build_web_move() {
     var dest = 'target/o2server/servers/webServer/';
     var dest = 'target/o2server/servers/webServer/';
-    var src_move = ['o2web/source/**/*', '!**/*.spec.js', '!**/test/**'];
+    var src_move = ['o2web/source/**/*', '!o2web/source/o2_core/o2.js', '!**/*.spec.js', '!**/test/**'];
 
 
     var entries = fg.sync(src_move, { dot: false});
     var entries = fg.sync(src_move, { dot: false});
     var size = entries.length;
     var size = entries.length;
@@ -401,6 +403,94 @@ function build_web_move() {
 }
 }
 exports.build_web_move = build_web_move;
 exports.build_web_move = build_web_move;
 
 
+function build_concat_o2(){
+    var src = [
+        'o2web/source/o2_lib/mootools/mootools-1.6.0_all.js',
+        'o2web/source/o2_lib/mootools/plugin/mBox.js',
+        'o2web/source/o2_core/o2.js'
+    ];
+    var dest = 'target/o2server/servers/webServer/o2_core/';
+    return gulp.src(src)
+        .pipe(concat('o2.js'))
+        .pipe(gulp.dest(dest))
+        .pipe(uglify())
+        .pipe(rename({ extname: '.min.js' }))
+        .pipe(gulp.dest(dest))
+}
+function build_concat_desktop(){
+    let path = "o2_core";
+    var src = [
+        'o2web/source/'+path+'/o2/widget/Common.js',
+        'o2web/source/'+path+'/o2/widget/Dialog.js',
+        'o2web/source/'+path+'/o2/widget/UUID.js',
+        'o2web/source/'+path+'/o2/xDesktop/Common.js',
+        'o2web/source/'+path+'/o2/xDesktop/Actions/RestActions.js',
+        'o2web/source/'+path+'/o2/xAction/RestActions.js',
+        'o2web/source/'+path+'/o2/xDesktop/Access.js',
+        'o2web/source/'+path+'/o2/xDesktop/Dialog.js',
+        'o2web/source/'+path+'/o2/xDesktop/Menu.js',
+        'o2web/source/'+path+'/o2/xDesktop/UserData.js',
+        'o2web/source/x_component_Template/MPopupForm.js',
+        'o2web/source/'+path+'/o2/xDesktop/Authentication.js',
+        'o2web/source/'+path+'/o2/xDesktop/Dialog.js',
+        'o2web/source/'+path+'/o2/xDesktop/Window.js',
+        'o2web/source/x_component_Common/Main.js'
+    ];
+    var dest = 'target/o2server/servers/webServer/o2_core/o2/xDesktop/';
+    return gulp.src(src)
+        .pipe(concat('$all.js'))
+        .pipe(gulp.dest(dest))
+        .pipe(uglify())
+        .pipe(rename({ extname: '.min.js' }))
+        .pipe(gulp.dest(dest))
+}
+function build_concat_xform(){
+    let path = "x_component_process_Xform";
+    var src = [
+        'o2web/source/' + path + '/Form.js',
+        'o2web/source/' + path + '/$Module.js',
+        'o2web/source/' + path + '/$Input.js',
+        'o2web/source/' + path + '/Div.js',
+        'o2web/source/' + path + '/Combox.js',
+        'o2web/source/' + path + '/DatagridMobile.js',
+        'o2web/source/' + path + '/DatagridPC.js',
+        'o2web/source/' + path + '/Textfield.js',
+        'o2web/source/' + path + '/Personfield.js',
+        'o2web/source/' + path + '/*.js',
+        '!o2web/source/' + path + '/Office.js'
+    ];
+    var dest = 'target/o2server/servers/webServer/'+path+'/';
+    return gulp.src(src)
+        .pipe(concat('$all.js'))
+        .pipe(gulp.dest(dest))
+        .pipe(uglify())
+        .pipe(rename({ extname: '.min.js' }))
+        .pipe(gulp.dest(dest))
+}
+
+function build_bundle(){
+    let path = "o2_core";
+    var src = [
+        'o2web/source/o2_lib/mootools/mootools-1.6.0_all.js',
+        'o2web/source/o2_lib/mootools/plugin/mBox.js',
+        'o2web/source/o2_core/o2.js',
+        'o2web/source/x_desktop/js/base.js',
+        "o2web/source/o2_core/o2/framework.js"
+    ];
+    var dest = 'target/o2server/servers/webServer/'+path+'/';
+    return gulp.src(src)
+        .pipe(concat('bundle.js'))
+        .pipe(gulp.dest(dest))
+        .pipe(uglify())
+        .pipe(rename({ extname: '.min.js' }))
+        .pipe(gulp.dest(dest))
+}
+// function build_concat(){
+//     return gulp.parallel(build_concat_o2, build_concat_desktop, build_concat_xform);
+// }
+exports.build_concat = gulp.parallel(build_concat_o2, build_concat_desktop, build_concat_xform, build_bundle);
+
+
 function build_web_v_html() {
 function build_web_v_html() {
     var src = 'o2web/source/x_desktop/*.html';
     var src = 'o2web/source/x_desktop/*.html';
     var dest = 'target/o2server/servers/webServer/x_desktop/';
     var dest = 'target/o2server/servers/webServer/x_desktop/';
@@ -410,7 +500,7 @@ function build_web_v_html() {
         .pipe(gutil.noop());
         .pipe(gutil.noop());
 }
 }
 function build_web_v_o2() {
 function build_web_v_o2() {
-    var src = 'o2web/source/o2_core/o2.js';
+    var src = 'target/o2server/servers/webServer/o2_core//o2.js';
     var dest = 'target/o2server/servers/webServer/o2_core/';
     var dest = 'target/o2server/servers/webServer/o2_core/';
     return gulp.src(src)
     return gulp.src(src)
         .pipe(assetRev())
         .pipe(assetRev())
@@ -487,9 +577,12 @@ function chmod_commons(){
 function chmod_sh(){
 function chmod_sh(){
     return (shell.task('chmod 777 target/o2server/*.sh'))();
     return (shell.task('chmod 777 target/o2server/*.sh'))();
 }
 }
-exports.build_web = gulp.series(build_web_minimize, build_web_move, build_web_v_html, build_web_v_o2);
+function chmod_servers(){
+    return (shell.task('chmod 777 -R target/o2server/servers'))();
+}
+exports.build_web = gulp.series(build_web_minimize, build_web_move, gulp.parallel(build_concat_o2, build_concat_desktop, build_concat_xform, build_bundle), build_web_v_html, build_web_v_o2);
 if (os.platform().indexOf("win")==-1){
 if (os.platform().indexOf("win")==-1){
-    exports.deploy = gulp.series(deploy_server, chmod_jvm, chmod_commons, chmod_sh);
+    exports.deploy = gulp.series(deploy_server, chmod_jvm, chmod_commons, chmod_sh, chmod_servers);
 }else{
 }else{
     exports.deploy = gulp.series(deploy_server);
     exports.deploy = gulp.series(deploy_server);
-}
+}

+ 5 - 1
o2android/app/build.gradle

@@ -265,7 +265,8 @@ dependencies {
     implementation 'cn.jiguang.sdk:jcore:1.1.9'
     implementation 'cn.jiguang.sdk:jcore:1.1.9'
 
 
     //im
     //im
-    implementation 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT'
+    implementation 'com.michaelpardo:activeandroid:3.1.0'
+//    implementation files('libs/activeandroid-3.1.0-SNAPSHOT.jar')
     implementation 'com.jakewharton:butterknife:8.4.0'
     implementation 'com.jakewharton:butterknife:8.4.0'
     kapt 'com.jakewharton:butterknife-compiler:8.4.0'
     kapt 'com.jakewharton:butterknife-compiler:8.4.0'
     implementation 'com.github.chrisbanes.photoview:library:1.2.4'
     implementation 'com.github.chrisbanes.photoview:library:1.2.4'
@@ -316,6 +317,9 @@ dependencies {
         exclude group: 'com.android.support', module: 'support-media-compat'
         exclude group: 'com.android.support', module: 'support-media-compat'
     }
     }
 
 
+    //mp3录音
+    implementation 'com.github.zhaolewei:ZlwAudioRecorder:v1.07'
+
 
 
 }
 }
 
 

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

@@ -41,7 +41,9 @@
         android:label="@string/app_name"
         android:label="@string/app_name"
         android:roundIcon="@mipmap/logo_round"
         android:roundIcon="@mipmap/logo_round"
         android:theme="@style/XBPMTheme.NoActionBar">
         android:theme="@style/XBPMTheme.NoActionBar">
-        <activity android:name=".app.im.O2ChatActivity"></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 android:name=".app.VideoPlayerActivity" />
         <activity
         <activity
             android:name=".app.clouddrive.v2.viewer.BigImageViewActivity"
             android:name=".app.clouddrive.v2.viewer.BigImageViewActivity"
@@ -426,7 +428,8 @@
         <service android:name=".core.service.RestartSelfService" /> <!-- jpush -->
         <service android:name=".core.service.RestartSelfService" /> <!-- jpush -->
         <service
         <service
             android:name=".core.service.WebSocketService"
             android:name=".core.service.WebSocketService"
-            android:exported="false" />
+            android:exported="false" /> <!-- 录音 转mp3 service -->
+        <service android:name="com.zlw.main.recorderlib.recorder.RecordService" />
 
 
         <receiver
         <receiver
             android:name=".core.receiver.JpushNoticeBroadReceiver"
             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.facebook.drawee.backends.pipeline.Fresco
 import com.tencent.bugly.crashreport.CrashReport
 import com.tencent.bugly.crashreport.CrashReport
 import com.tencent.smtt.sdk.QbSdk
 import com.tencent.smtt.sdk.QbSdk
+import com.zlw.main.recorderlib.RecordManager
 import io.realm.Realm
 import io.realm.Realm
 import jiguang.chat.application.JGApplication
 import jiguang.chat.application.JGApplication
 import jiguang.chat.entity.NotificationClickEventReceiver
 import jiguang.chat.entity.NotificationClickEventReceiver
@@ -116,6 +117,9 @@ class O2App : MultiDexApplication() {
         //注册日志记录器
         //注册日志记录器
         LogSingletonService.instance().registerApp(this)
         LogSingletonService.instance().registerApp(this)
 
 
+        //录音
+        RecordManager.getInstance().init(this, false)
+
         Log.i("O2app", "O2app init.....................................................")
         Log.i("O2app", "O2app init.....................................................")
         //stetho developer tool
         //stetho developer tool
 //        Stetho.initializeWithDefaults(this)
 //        Stetho.initializeWithDefaults(this)

+ 5 - 3
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/attendance/main/AttendanceCheckInContract.kt

@@ -4,11 +4,12 @@ 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.app.base.BaseView
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.attendance.MobileCheckInJson
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.attendance.MobileCheckInJson
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.attendance.MobileCheckInWorkplaceInfoJson
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.attendance.MobileCheckInWorkplaceInfoJson
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.attendance.MobileMyRecords
 
 
 
 
 object AttendanceCheckInContract {
 object AttendanceCheckInContract {
     interface View : BaseView {
     interface View : BaseView {
-
+        fun myRecords(records: MobileMyRecords?)
         fun workplaceList(list: List<MobileCheckInWorkplaceInfoJson>)
         fun workplaceList(list: List<MobileCheckInWorkplaceInfoJson>)
         fun todayCheckInRecord(list: List<MobileCheckInJson>)
         fun todayCheckInRecord(list: List<MobileCheckInJson>)
         fun checkIn(result: Boolean)
         fun checkIn(result: Boolean)
@@ -16,8 +17,9 @@ object AttendanceCheckInContract {
 
 
     interface Presenter : BasePresenter<View> {
     interface Presenter : BasePresenter<View> {
 
 
-        fun findTodayCheckInRecord(cId: String)
+        fun listMyRecords()
+        fun findTodayCheckInRecord(person: String)
         fun loadAllWorkplace()
         fun loadAllWorkplace()
-        fun checkIn(latitude: String, longitude: String, addrStr: String?, signDesc: String, signDate: String, signTime: String, id: String)
+        fun checkIn(latitude: String, longitude: String, addrStr: String?, signDesc: String, signDate: String, signTime: String, id: String, checkType: String?)
     }
     }
 }
 }

+ 34 - 5
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/attendance/main/AttendanceCheckInFragment.kt

@@ -4,6 +4,7 @@ import android.os.Handler
 import android.os.Message
 import android.os.Message
 import android.support.v4.content.ContextCompat
 import android.support.v4.content.ContextCompat
 import android.support.v7.widget.LinearLayoutManager
 import android.support.v7.widget.LinearLayoutManager
+import android.text.TextUtils
 import com.baidu.location.BDLocation
 import com.baidu.location.BDLocation
 import com.baidu.location.BDLocationListener
 import com.baidu.location.BDLocationListener
 import com.baidu.location.LocationClient
 import com.baidu.location.LocationClient
@@ -29,8 +30,8 @@ import org.jetbrains.anko.uiThread
 import java.util.*
 import java.util.*
 import com.baidu.mapapi.map.MapStatusUpdateFactory
 import com.baidu.mapapi.map.MapStatusUpdateFactory
 import com.baidu.mapapi.map.MapStatus
 import com.baidu.mapapi.map.MapStatus
-
-
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.attendance.MobileFeature
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.attendance.MobileMyRecords
 
 
 
 
 class AttendanceCheckInFragment : BaseMVPViewPagerFragment<AttendanceCheckInContract.View, AttendanceCheckInContract.Presenter>(),
 class AttendanceCheckInFragment : BaseMVPViewPagerFragment<AttendanceCheckInContract.View, AttendanceCheckInContract.Presenter>(),
@@ -45,7 +46,17 @@ class AttendanceCheckInFragment : BaseMVPViewPagerFragment<AttendanceCheckInCont
     private val recordAdapter: CommonRecycleViewAdapter<MobileCheckInJson> by lazy {
     private val recordAdapter: CommonRecycleViewAdapter<MobileCheckInJson> by lazy {
         object : CommonRecycleViewAdapter<MobileCheckInJson>(activity, recordList, R.layout.item_attendance_check_in_record_list) {
         object : CommonRecycleViewAdapter<MobileCheckInJson>(activity, recordList, R.layout.item_attendance_check_in_record_list) {
             override fun convert(holder: CommonRecyclerViewHolder?, t: MobileCheckInJson?) {
             override fun convert(holder: CommonRecyclerViewHolder?, t: MobileCheckInJson?) {
-                holder?.setText(R.id.tv_item_attendance_check_in_time, t?.signTime)
+                if (!TextUtils.isEmpty(t?.checkin_type)) {
+                    holder?.setText(R.id.tv_item_attendance_check_in_type, t?.checkin_type)
+                }else {
+                    holder?.setText(R.id.tv_item_attendance_check_in_type, getString(R.string.attendance_check_in_time_label))
+                }
+                var time = if(!TextUtils.isEmpty(t?.signTime) && t?.signTime?.length ?: 0 > 5) {
+                     t?.signTime?.substring(0, 5) ?: ""
+                }else {
+                    ""
+                }
+                holder?.setText(R.id.tv_item_attendance_check_in_time, time)
                         ?.setText(R.id.tv_item_attendance_check_in_location, t?.recordAddress)
                         ?.setText(R.id.tv_item_attendance_check_in_location, t?.recordAddress)
             }
             }
         }
         }
@@ -57,6 +68,7 @@ class AttendanceCheckInFragment : BaseMVPViewPagerFragment<AttendanceCheckInCont
     private var myLocation: BDLocation? = null //当前我的位置
     private var myLocation: BDLocation? = null //当前我的位置
     private var checkInPosition: MobileCheckInWorkplaceInfoJson? = null//离的最近的工作地点位置
     private var checkInPosition: MobileCheckInWorkplaceInfoJson? = null//离的最近的工作地点位置
     private var isInCheckInPositionRange = false
     private var isInCheckInPositionRange = false
+    private var feature : MobileFeature? = null
 
 
 
 
     val handler = Handler { msg ->
     val handler = Handler { msg ->
@@ -105,12 +117,14 @@ class AttendanceCheckInFragment : BaseMVPViewPagerFragment<AttendanceCheckInCont
             tv_attendance_check_in_time.gone()
             tv_attendance_check_in_time.gone()
             val signDate = DateHelper.nowByFormate("yyyy-MM-dd")
             val signDate = DateHelper.nowByFormate("yyyy-MM-dd")
             val signTime = DateHelper.nowByFormate("HH:mm:ss")
             val signTime = DateHelper.nowByFormate("HH:mm:ss")
-            mPresenter.checkIn(myLocation!!.latitude.toString(), myLocation!!.longitude.toString(), myLocation!!.addrStr, "", signDate, signTime, "")
+            mPresenter.checkIn(myLocation!!.latitude.toString(), myLocation!!.longitude.toString(),
+                    myLocation!!.addrStr, "", signDate, signTime, "", this.feature?.checkinType)
         }
         }
     }
     }
 
 
     override fun lazyLoad() {
     override fun lazyLoad() {
         mPresenter.findTodayCheckInRecord(O2SDKManager.instance().distinguishedName)
         mPresenter.findTodayCheckInRecord(O2SDKManager.instance().distinguishedName)
+        mPresenter.listMyRecords()
         mPresenter.loadAllWorkplace()
         mPresenter.loadAllWorkplace()
     }
     }
 
 
@@ -179,6 +193,20 @@ class AttendanceCheckInFragment : BaseMVPViewPagerFragment<AttendanceCheckInCont
         drawCheckInWorkplaceCircle()
         drawCheckInWorkplaceCircle()
     }
     }
 
 
+    override fun myRecords(records: MobileMyRecords?) {
+         if (records != null) {
+             this.feature = records.feature
+             if (this.feature?.signSeq ?: -1 > 0) {
+                 ll_attendance_check_in_button.visible()
+             }else {
+                 ll_attendance_check_in_button.gone()
+             }
+         }else {//兼容老版本 没有这个接口就开放打卡功能
+             this.feature = null
+             ll_attendance_check_in_button.visible()
+         }
+
+    }
 
 
     override fun todayCheckInRecord(list: List<MobileCheckInJson>) {
     override fun todayCheckInRecord(list: List<MobileCheckInJson>) {
         XLog.debug("todayCheckInRecord  size:${list.size}")
         XLog.debug("todayCheckInRecord  size:${list.size}")
@@ -196,6 +224,7 @@ class AttendanceCheckInFragment : BaseMVPViewPagerFragment<AttendanceCheckInCont
             XToast.toastShort(activity, "打卡失败!")
             XToast.toastShort(activity, "打卡失败!")
         }
         }
         mPresenter.findTodayCheckInRecord(O2SDKManager.instance().distinguishedName)
         mPresenter.findTodayCheckInRecord(O2SDKManager.instance().distinguishedName)
+        mPresenter.listMyRecords()
     }
     }
 
 
     /**
     /**
@@ -249,7 +278,7 @@ class AttendanceCheckInFragment : BaseMVPViewPagerFragment<AttendanceCheckInCont
      * 找到最近的打卡地点
      * 找到最近的打卡地点
      */
      */
     private fun calNearestWorkplace() {
     private fun calNearestWorkplace() {
-        if (!workplaceList.isEmpty() && myLocation!=null) {
+        if (workplaceList.isNotEmpty() && myLocation!=null) {
             var minDistance: Double = -1.0
             var minDistance: Double = -1.0
             XLog.debug("calNearestWorkplace...................")
             XLog.debug("calNearestWorkplace...................")
             workplaceList.map {
             workplaceList.map {

+ 15 - 1
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/attendance/main/AttendanceCheckInPresenter.kt

@@ -19,6 +19,19 @@ import rx.schedulers.Schedulers
 
 
 class AttendanceCheckInPresenter : BasePresenterImpl<AttendanceCheckInContract.View>(), AttendanceCheckInContract.Presenter {
 class AttendanceCheckInPresenter : BasePresenterImpl<AttendanceCheckInContract.View>(), AttendanceCheckInContract.Presenter {
 
 
+    override fun listMyRecords() {
+        getAttendanceAssembleControlService(mView?.getContext())?.let { service ->
+            service.listMyRecords().subscribeOn(Schedulers.io())
+                    .observeOn(AndroidSchedulers.mainThread())
+                    .o2Subscribe {
+                        onNext { mView?.myRecords(it.data) }
+                        onError { e, _ ->
+                            XLog.error("", e)
+                            mView?.myRecords(null)
+                        }
+                    }
+        }
+    }
 
 
     override fun findTodayCheckInRecord(person: String) {
     override fun findTodayCheckInRecord(person: String) {
         val queryBean = MobileCheckInQueryFilterJson()
         val queryBean = MobileCheckInQueryFilterJson()
@@ -64,7 +77,7 @@ class AttendanceCheckInPresenter : BasePresenterImpl<AttendanceCheckInContract.V
         }
         }
     }
     }
 
 
-    override fun checkIn(latitude: String, longitude: String, addrStr: String?, signDesc: String, signDate: String, signTime: String, id: String) {
+    override fun checkIn(latitude: String, longitude: String, addrStr: String?, signDesc: String, signDate: String, signTime: String, id: String, checkType: String?) {
         val form = MobileCheckInJson()
         val form = MobileCheckInJson()
         if (!TextUtils.isEmpty(id)) {
         if (!TextUtils.isEmpty(id)) {
             form.id = id
             form.id = id
@@ -77,6 +90,7 @@ class AttendanceCheckInPresenter : BasePresenterImpl<AttendanceCheckInContract.V
         form.optMachineType = AndroidUtils.getDeviceBrand() + "-" + AndroidUtils.getDeviceModelNumber()
         form.optMachineType = AndroidUtils.getDeviceBrand() + "-" + AndroidUtils.getDeviceModelNumber()
         form.optSystemName = O2.DEVICE_TYPE
         form.optSystemName = O2.DEVICE_TYPE
         form.recordAddress = addrStr ?: ""
         form.recordAddress = addrStr ?: ""
+        form.checkin_type = checkType
         val json = O2SDKManager.instance().gson.toJson(form)
         val json = O2SDKManager.instance().gson.toJson(form)
         val body = RequestBody.create(MediaType.parse("text/json"), json)
         val body = RequestBody.create(MediaType.parse("text/json"), json)
         getAttendanceAssembleControlService(mView?.getContext())?.let { service ->
         getAttendanceAssembleControlService(mView?.getContext())?.let { service ->

+ 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.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.APIAddressHelper
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.RetrofitClient
 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.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.ReplyFormJson
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.bbs.SubjectReplyInfoJson
 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.*
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.gone
 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.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.utils.extension.visible
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.widgets.AttachPopupWindow
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.widgets.AttachPopupWindow
 import org.jetbrains.anko.dip
 import org.jetbrains.anko.dip
 import org.jetbrains.anko.doAsync
 import org.jetbrains.anko.doAsync
 import org.jetbrains.anko.uiThread
 import org.jetbrains.anko.uiThread
+import rx.android.schedulers.AndroidSchedulers
+import rx.schedulers.Schedulers
 import java.io.DataInputStream
 import java.io.DataInputStream
 import java.io.DataOutputStream
 import java.io.DataOutputStream
 import java.io.File
 import java.io.File
@@ -274,46 +278,22 @@ class BBSWebViewSubjectActivity : BaseMVPActivity<BBSWebViewSubjectContract.View
 
 
     override fun startDownLoadFile(id: String?) {
     override fun startDownLoadFile(id: String?) {
         if (!TextUtils.isEmpty(id)){
         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, "下载附件失败!")
                     }
                     }
-                }
-            }
         }
         }
     }
     }
 
 

+ 1 - 1
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/calendar/vm/CalendarViewModel.kt

@@ -48,7 +48,7 @@ class CalendarViewModel(app: Application) : BaseO2ViewModel(app) {
                         onNext { list ->
                         onNext { list ->
                             groups.value = list
                             groups.value = list
                         }
                         }
-                        onError { e, isNetworkError ->
+                        onError { e, _ ->
                             XLog.error("我的日历查询异常", e)
                             XLog.error("我的日历查询异常", e)
                             groups.value = null
                             groups.value = null
                         }
                         }

+ 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.base.BaseMVPActivity
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.o2.organization.ContactPickerActivity
 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.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.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.gone
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.visible
 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.File
-import java.io.FileOutputStream
-import java.util.concurrent.Future
 
 
 
 
 class CloudDriveActivity : BaseMVPActivity<CloudDriveContract.View, CloudDriveContract.Presenter>(), CloudDriveContract.View {
 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 {
     override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
         if (keyCode == KeyEvent.KEYCODE_BACK) {
         if (keyCode == KeyEvent.KEYCODE_BACK) {
             //有下载文件的 取消下载
             //有下载文件的 取消下载
-            if (!downloadTaskMap.isEmpty()) {
+            if (downloadTaskMap.isNotEmpty()) {
                 downloadTaskMap.map {
                 downloadTaskMap.map {
-                    it.value.cancel(true)
+                    it.value.unsubscribe()
                 }
                 }
                 downloadTaskMap.clear()
                 downloadTaskMap.clear()
                 yunpan_download_file_id.gone()
                 yunpan_download_file_id.gone()
@@ -146,55 +142,87 @@ class CloudDriveActivity : BaseMVPActivity<CloudDriveContract.View, CloudDriveCo
         super.onActivityResult(requestCode, resultCode, data)
         super.onActivityResult(requestCode, resultCode, data)
     }
     }
 
 
-    val downloadTaskMap = HashMap<String, Future<Unit>>()
+    val downloadTaskMap = HashMap<String, Subscription>()
     fun openYunPanFile(id: String, fileName: String) {
     fun openYunPanFile(id: String, fileName: String) {
         XLog.debug("download id:$id, , file:$fileName")
         XLog.debug("download id:$id, , file:$fileName")
         if (!downloadTaskMap.containsKey(id)) {
         if (!downloadTaskMap.containsKey(id)) {
             yunpan_download_file_id.visible()
             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, "下载附件失败!")
+//                    }
+//                }
+//            })
+
         }
         }
 
 
     }
     }

+ 1 - 1
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/clouddrive/CloudDriveCooperationFileFragment.kt

@@ -228,7 +228,7 @@ class CloudDriveCooperationFileFragment : BaseMVPViewPagerFragment<CloudDriveCoo
                         activity.go<PictureViewActivity>(bundle)
                         activity.go<PictureViewActivity>(bundle)
                     } else {
                     } else {
                         val activity = activity as CloudDriveActivity
                         val activity = activity as CloudDriveActivity
-                        activity.openYunPanFile(item.id, item.fileName)
+                        activity.openYunPanFile(item.id, item.name)
                     }
                     }
                 }
                 }
             }
             }

+ 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
 package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.clouddrive.v2
 
 
 import android.app.Activity
 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.FileExtensionHelper
+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.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.File
-import java.io.FileOutputStream
-import java.util.concurrent.Future
 
 
 
 
 /**
 /**
@@ -21,66 +21,96 @@ class CloudDiskFileDownloadHelper(val activity: Activity) {
     var showLoading: (()->Unit)? = null
     var showLoading: (()->Unit)? = null
     var hideLoading: (()->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) {
     fun startDownload(fileId: String, extension: String, result: (file: File?)->Unit) {
         showLoading?.invoke()
         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() {
     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 kotlinx.android.synthetic.main.fragment_picture_viewer.*
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.R
 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.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.gone
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.visible
 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.File
-import java.io.FileOutputStream
-
-
 
 
 
 
 class CloudDrivePictureViewerFragment : BaseMVPViewPagerFragment<CloudDrivePictureViewerContract.View, CloudDrivePictureViewerContract.Presenter>(), CloudDrivePictureViewerContract.View {
 class CloudDrivePictureViewerFragment : BaseMVPViewPagerFragment<CloudDrivePictureViewerContract.View, CloudDrivePictureViewerContract.Presenter>(), CloudDrivePictureViewerContract.View {
@@ -47,59 +42,46 @@ class CloudDrivePictureViewerFragment : BaseMVPViewPagerFragment<CloudDrivePictu
         }else {
         }else {
             circle_progress_fragment_picture_view.visible()
             circle_progress_fragment_picture_view.visible()
             zoomImage_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 android.text.TextUtils
 import net.muliba.accounting.app.ExceptionHandler
 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.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.ResponseHandler
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.RetrofitClient
 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.IdData
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.cms.CMSDocumentAttachmentJson
 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.model.vo.AttachmentItemVO
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.FileExtensionHelper
 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.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.XLog
 import okhttp3.MediaType
 import okhttp3.MediaType
 import okhttp3.MultipartBody
 import okhttp3.MultipartBody
@@ -100,47 +103,56 @@ class CMSWebViewPresenter : BasePresenterImpl<CMSWebViewContract.View>(), CMSWeb
                     .flatMap { res->
                     .flatMap { res->
                         val attachInfo = res.data
                         val attachInfo = res.data
                         if (attachInfo != null) {
                         if (attachInfo != null) {
+
                             val filePath = FileExtensionHelper.getXBPMCMSAttachFolder() + File.separator + attachInfo.name
                             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 ->
                             Observable.create { t ->
-                                val thisfile = File(filePath)
-                                if (file.exists()) {
-                                    t?.onNext(thisfile)
-                                } else {
-                                    t?.onError(Exception("附件下载异常,找不到文件!"))
-                                }
+                                t?.onError(Exception("没有获取到附件信息,无法下载附件!"))
                                 t?.onCompleted()
                                 t?.onCompleted()
                             }
                             }
-                        }else {
-                            Observable.create(object : Observable.OnSubscribe<File> {
-                                override fun call(t: Subscriber<in File>?) {
-                                    t?.onError(Exception("没有获取到附件信息,无法下载附件!"))
-                                    t?.onCompleted()
-                                }
-                            })
                         }
                         }
                     }.observeOn(AndroidSchedulers.mainThread())
                     }.observeOn(AndroidSchedulers.mainThread())
                     .subscribe({ file -> mView?.downloadAttachmentSuccess(file) }, { e ->
                     .subscribe({ file -> mView?.downloadAttachmentSuccess(file) }, { e ->

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

@@ -1,33 +1,56 @@
 package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.im
 package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.im
 
 
+import android.Manifest
 import android.app.Activity
 import android.app.Activity
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
+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.Bundle
+import android.os.CountDownTimer
+import android.provider.MediaStore
+import android.provider.Settings
+import android.support.v7.widget.GridLayoutManager
 import android.support.v7.widget.LinearLayoutManager
 import android.support.v7.widget.LinearLayoutManager
 import android.text.Editable
 import android.text.Editable
 import android.text.TextUtils
 import android.text.TextUtils
 import android.text.TextWatcher
 import android.text.TextWatcher
+import android.view.*
+import android.view.inputmethod.InputMethodManager
+import android.widget.EditText
+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 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.O2SDKManager
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.R
 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.base.BaseMVPActivity
-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.app.o2.organization.ContactPickerActivity
+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.*
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.vo.ContactPickerResult
+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.go
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.gone
 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.utils.extension.visible
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.permission.PermissionRequester
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.permission.PermissionResult
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.widgets.dialog.O2DialogSupport
+import org.jetbrains.anko.find
+import java.io.File
 import java.util.*
 import java.util.*
-import android.content.IntentFilter
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.IMConversationInfo
+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 {
     companion object {
         const val con_id_key = "con_id_key"
         const val con_id_key = "con_id_key"
@@ -44,8 +67,17 @@ class O2ChatActivity : BaseMVPActivity<O2ChatContract.View, O2ChatContract.Prese
     override fun layoutResId(): Int = R.layout.activity_o2_chat
     override fun layoutResId(): Int = R.layout.activity_o2_chat
 
 
 
 
-
     private val adapter: O2ChatMessageAdapter by lazy { O2ChatMessageAdapter() }
     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 {
+        object : CommonRecycleViewAdapter<String>(this, emojiList, R.layout.item_o2_im_chat_emoji) {
+            override fun convert(holder: CommonRecyclerViewHolder?, t: String?) {
+                if (t != null) {
+                    holder?.setImageViewResource(R.id.image_item_o2_im_chat_emoji, O2IM.emojiResId(t))
+                }
+            }
+        }
+    }
 
 
     //
     //
     private val defaultTitle = "聊天界面"
     private val defaultTitle = "聊天界面"
@@ -54,11 +86,53 @@ class O2ChatActivity : BaseMVPActivity<O2ChatContract.View, O2ChatContract.Prese
     private var conversationId = ""
     private var conversationId = ""
 
 
     private var conversationInfo: IMConversationInfo? = null
     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 val cameraImageUri: Uri by lazy { FileUtil.getUriFromFile(this, File(FileExtensionHelper.getCameraCacheFilePath())) }
+    private val camera_result_code = 10240
+
+    //是否能修改群名 群成员
+    private var canUpdate = false
+
+
+
+
+//    private var mKeyboardHeight = 150 // 输入法默认高度为400
 
 
 
 
     override fun afterSetContentView(savedInstanceState: Bundle?) {
     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)
         setupToolBar(defaultTitle, setupBackButton = true)
 
 
@@ -72,7 +146,22 @@ class O2ChatActivity : BaseMVPActivity<O2ChatContract.View, O2ChatContract.Prese
         rv_o2_chat_messages.adapter = adapter
         rv_o2_chat_messages.adapter = adapter
         adapter.eventListener = object : O2ChatMessageAdapter.MessageEventListener {
         adapter.eventListener = object : O2ChatMessageAdapter.MessageEventListener {
             override fun resendClick(message: IMMessage) {
             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)
             }
             }
         }
         }
         //输入法切换的时候滚动到底部
         //输入法切换的时候滚动到底部
@@ -82,23 +171,166 @@ class O2ChatActivity : BaseMVPActivity<O2ChatContract.View, O2ChatContract.Prese
             }
             }
         }
         }
 
 
+        //表情初始化
+        rv_o2_chat_emoji_box.layoutManager = GridLayoutManager(this, 10)
+        rv_o2_chat_emoji_box.adapter = emojiAdapter
+        emojiAdapter.setOnItemClickListener { _, position ->
+            val key = emojiList[position]
+            XLog.debug(key)
+            newEmojiMessage(key)
+            //更新阅读时间
+            mPresenter.readConversation(conversationId)
+        }
+
         initListener()
         initListener()
 
 
         getPageData()
         getPageData()
 
 
+        //录音格式
+        initAudioRecord()
+
         registerBroadcast()
         registerBroadcast()
     }
     }
 
 
 
 
+    override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
+        menu?.clear()
+        if (canUpdate) {
+            menuInflater.inflate(R.menu.menu_chat, menu)
+        }
+        return super.onPrepareOptionsMenu(menu)
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem?): Boolean {
+        when(item?.itemId) {
+            R.id.menu_chat_update_title -> {
+                updateTitle()
+                return true
+            }
+            R.id.menu_chat_update_member -> {
+                updateMembers()
+                return true
+            }
+        }
+        return super.onOptionsItemSelected(item)
+    }
+
+    private fun updateTitle() {
+        val dialog = O2DialogSupport.openCustomViewDialog(this, "修改群名", R.layout.dialog_name_modify) { dialog ->
+            val text = dialog.findViewById<EditText>(R.id.dialog_name_editText_id)
+            val content = text.text.toString()
+            dialog.dismiss()
+            if (TextUtils.isEmpty(content)) {
+                XToast.toastShort(this@O2ChatActivity, "群名不能为空!")
+            } else {
+                showLoadingDialog()
+                mPresenter.updateConversationTitle(conversationId, content)
+            }
+        }
+        val edit = dialog.findViewById<EditText>(R.id.dialog_name_editText_id)
+        edit.hint = "请输入群名!"
+    }
+
+    private fun updateMembers() {
+        val users = conversationInfo?.personList ?: ArrayList<String>()
+        ActivityResult.of(this)
+                .className(ContactPickerActivity::class.java)
+                .params(ContactPickerActivity.startPickerBundle(pickerModes = arrayListOf(ContactPickerActivity.personPicker), multiple = true, initUserList = users))
+                .greenChannel().forResult { _, data ->
+                    val result = data?.getParcelableExtra<ContactPickerResult>(ContactPickerActivity.CONTACT_PICKED_RESULT)
+                    if (result != null && result.users.isNotEmpty()) {
+                        val a = arrayListOf<String>()
+                        a.addAll(result.users.map { it.distinguishedName })
+                        if (!a.any { it == O2SDKManager.instance().distinguishedName }) {
+                            a.add(O2SDKManager.instance().distinguishedName)
+                        }
+                        showLoadingDialog()
+                        mPresenter.updateConversationPeople(conversationId, a)
+                    }else {
+                        XLog.debug("没有选择人员!!!!")
+                    }
+                }
+    }
+
+
     override fun onDestroy() {
     override fun onDestroy() {
         super.onDestroy()
         super.onDestroy()
         if (mReceiver != null) {
         if (mReceiver != null) {
             unregisterReceiver(mReceiver)
             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 updateSuccess(info: IMConversationInfo) {
+        hideLoadingDialog()
+        this.conversationInfo?.title = info.title
+        updateToolbarTitle(info.title)
+        this.conversationInfo?.personList = info.personList
+    }
+
+    override fun updateFail(msg: String) {
+        hideLoadingDialog()
+        XToast.toastShort(this, msg)
     }
     }
 
 
     override fun conversationInfo(info: IMConversationInfo) {
     override fun conversationInfo(info: IMConversationInfo) {
         conversationInfo = info
         conversationInfo = info
+        if (conversationInfo?.adminPerson == O2SDKManager.instance().distinguishedName) {
+            canUpdate = true
+            invalidateOptionsMenu()
+        }
         //
         //
         var title = defaultTitle
         var title = defaultTitle
         if (O2IM.conversation_type_single == conversationInfo?.type) {
         if (O2IM.conversation_type_single == conversationInfo?.type) {
@@ -109,7 +341,7 @@ class O2ChatActivity : BaseMVPActivity<O2ChatContract.View, O2ChatContract.Prese
                     title = person.substring(0, person.indexOf("@"))
                     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
             title = conversationInfo?.title ?: defaultTitle
         }
         }
         updateToolbarTitle(title)
         updateToolbarTitle(title)
@@ -121,7 +353,7 @@ class O2ChatActivity : BaseMVPActivity<O2ChatContract.View, O2ChatContract.Prese
     }
     }
 
 
     override fun backPageMessages(list: List<IMMessage>) {
     override fun backPageMessages(list: List<IMMessage>) {
-        if(list.isNotEmpty()) {
+        if (list.isNotEmpty()) {
             page++
             page++
             adapter.addPageMessage(list)
             adapter.addPageMessage(list)
         }
         }
@@ -141,6 +373,25 @@ class O2ChatActivity : BaseMVPActivity<O2ChatContract.View, O2ChatContract.Prese
         adapter.sendMessageFail(id)
         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)
+    }
+
     /**
     /**
      * 监听
      * 监听
      */
      */
@@ -156,26 +407,252 @@ class O2ChatActivity : BaseMVPActivity<O2ChatContract.View, O2ChatContract.Prese
                 if (s != null && !TextUtils.isEmpty(s)) {
                 if (s != null && !TextUtils.isEmpty(s)) {
                     btn_o2_chat_send.visible()
                     btn_o2_chat_send.visible()
                     btn_o2_chat_emotion.gone()
                     btn_o2_chat_emotion.gone()
-                }else {
+                } else {
                     btn_o2_chat_emotion.visible()
                     btn_o2_chat_emotion.visible()
                     btn_o2_chat_send.gone()
                     btn_o2_chat_send.gone()
                 }
                 }
             }
             }
         })
         })
+        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 (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 {
         btn_o2_chat_emotion.setOnClickListener {
-            hideSoftInput()
+            //关闭语音框
+            tv_o2_chat_audio_send_box.gone()
+            if (rv_o2_chat_emoji_box_out.isKeyboardActive) { //输入法激活时
+                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
+                    imm.hideSoftInputFromWindow(et_o2_chat_input.applicationWindowToken, 0)
+                    rv_o2_chat_emoji_box.visibility = View.VISIBLE
+                } else {
+                    rv_o2_chat_emoji_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 {
+                window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
+                if (rv_o2_chat_emoji_box.visibility == View.GONE) {
+                    rv_o2_chat_emoji_box.visibility = View.VISIBLE
+                } else {
+                    rv_o2_chat_emoji_box.visibility = View.GONE
+                }
+            }
+
         }
         }
         btn_o2_chat_send.setOnClickListener {
         btn_o2_chat_send.setOnClickListener {
             sendTextMessage()
             sendTextMessage()
         }
         }
+
+        //bottom toolbar
+        image_o2_chat_audio_speak_btn.setOnTouchListener(this)
+        ll_o2_chat_audio_btn.setOnClickListener {
+            //先检查录音权限
+            PermissionRequester(this@O2ChatActivity)
+                    .request(Manifest.permission.RECORD_AUDIO)
+                    .o2Subscribe {
+                        onNext { (granted, _, _) ->
+                            if (!granted){
+                                O2DialogSupport.openAlertDialog(this@O2ChatActivity, "需要定位权限, 去设置", { permissionSetting() })
+                            }
+                        }
+                        onError { e, _ ->
+                            XLog.error("", e)
+                        }
+                    }
+            //关闭表情框
+            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 permissionSetting() {
+        val packageUri = Uri.parse("package:$packageName")
+        startActivity(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageUri))
     }
     }
 
 
+    // 设置表情栏的高度
+//    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() {
     private fun getPageData() {
+        mPresenter.getConversation(conversationId)
         mPresenter.getMessage(page + 1, conversationId)
         mPresenter.getMessage(page + 1, conversationId)
         //更新阅读时间
         //更新阅读时间
         mPresenter.readConversation(conversationId)
         mPresenter.readConversation(conversationId)
     }
     }
 
 
+    /**
+     * 滚动消息到底部
+     */
     private fun scroll2Bottom() {
     private fun scroll2Bottom() {
         rv_o2_chat_messages.scrollToPosition(adapter.lastPosition())
         rv_o2_chat_messages.scrollToPosition(adapter.lastPosition())
     }
     }
@@ -198,14 +675,81 @@ class O2ChatActivity : BaseMVPActivity<O2ChatContract.View, O2ChatContract.Prese
      */
      */
     private fun newTextMessage(text: String) {
     private fun newTextMessage(text: String) {
         val time = DateHelper.now()
         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.sendIMMessage(message)//发送到服务器
+        scroll2Bottom()
+    }
+
+    /**
+     * 创建表情消息
+     */
+    private fun newEmojiMessage(emoji: String) {
+        val time = DateHelper.now()
+        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)
         val bodyJson = O2SDKManager.instance().gson.toJson(body)
         XLog.debug("body: $bodyJson")
         XLog.debug("body: $bodyJson")
         val uuid = UUID.randomUUID().toString()
         val uuid = UUID.randomUUID().toString()
         val message = IMMessage(uuid, conversationId, bodyJson,
         val message = IMMessage(uuid, conversationId, bodyJson,
                 O2SDKManager.instance().distinguishedName, time, 1)
                 O2SDKManager.instance().distinguishedName, time, 1)
         adapter.addMessage(message)
         adapter.addMessage(message)
-        mPresenter.sendTextMessage(message)//发送到服务器
+        mPresenter.sendIMMessage(message)//发送到服务器
         scroll2Bottom()
         scroll2Bottom()
     }
     }
 
 

+ 9 - 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.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.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.IMMessage
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.IMMessageBody
 
 
 object O2ChatContract  {
 object O2ChatContract  {
 
 
@@ -13,11 +14,18 @@ object O2ChatContract  {
         fun sendFail(id: String)
         fun sendFail(id: String)
         fun conversationInfo(info: IMConversationInfo)
         fun conversationInfo(info: IMConversationInfo)
         fun conversationGetFail()
         fun conversationGetFail()
+        fun localFile(filePath: String, msgType: String, position: Int)
+        fun downloadFileFail(msg: String)
+        fun updateSuccess(info: IMConversationInfo)
+        fun updateFail(msg: String)
     }
     }
     interface Presenter: BasePresenter<View> {
     interface Presenter: BasePresenter<View> {
-        fun sendTextMessage(msg: IMMessage)
+        fun sendIMMessage(msg: IMMessage)
         fun getMessage(page: Int, conversationId: String)
         fun getMessage(page: Int, conversationId: String)
         fun readConversation(conversationId: String)
         fun readConversation(conversationId: String)
         fun getConversation(id: String)
         fun getConversation(id: String)
+        fun getFileFromNetOrLocal(position: Int, body: IMMessageBody)
+        fun updateConversationTitle(id: String, title: String)
+        fun updateConversationPeople(id: String, users: ArrayList<String>)
     }
     }
 }
 }

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

@@ -1,23 +1,25 @@
 package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.im
 package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.im
 
 
 import android.support.v7.widget.RecyclerView
 import android.support.v7.widget.RecyclerView
+import android.text.TextUtils
 import android.view.LayoutInflater
 import android.view.LayoutInflater
 import android.view.ViewGroup
 import android.view.ViewGroup
 import android.view.animation.Animation
 import android.view.animation.Animation
 import android.view.animation.AnimationUtils
 import android.view.animation.AnimationUtils
-import android.widget.ImageButton
-import android.widget.ImageView
+import android.widget.*
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.O2SDKManager
 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.R
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.adapter.CommonRecyclerViewHolder
 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.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.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.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.DateHelper
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.gone
 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.extension.visible
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.imageloader.O2ImageLoaderManager
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.imageloader.O2ImageLoaderManager
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.widgets.CircleImageView
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.widgets.CircleImageView
+import retrofit2.http.POST
 
 
 
 
 class O2ChatMessageAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
 class O2ChatMessageAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
@@ -68,18 +70,24 @@ class O2ChatMessageAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
 
 
     override fun getItemViewType(position: Int): Int {
     override fun getItemViewType(position: Int): Int {
         val message = messages[position]
         val message = messages[position]
-        val body = message.messageBody()
-        if(body != null) {
-            if (body is IMMessageBody.Text) {
-                return if (message.createPerson == O2SDKManager.instance().distinguishedName) {
-                    TEXT_right
-                }else {
-                    TEXT_left
-                }
-            }
-            //其它
+        return if (message.createPerson == O2SDKManager.instance().distinguishedName) {
+            TEXT_right
+        }else {
+            TEXT_left
         }
         }
-        return 0
+//
+//        val body = message.messageBody()
+//        if(body != null) {
+//            if (body is IMMessageBody.Text) {
+//                return if (message.createPerson == O2SDKManager.instance().distinguishedName) {
+//                    TEXT_right
+//                }else {
+//                    TEXT_left
+//                }
+//            }
+//            //其它
+//        }
+//        return 0
     }
     }
 
 
     override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
     override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
@@ -113,11 +121,19 @@ class O2ChatMessageAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
                     time = DateHelper.imChatMessageTime(message.createTime)
                     time = DateHelper.imChatMessageTime(message.createTime)
                 }
                 }
             }
             }
-            if (messageBody!= null && messageBody is IMMessageBody.Text) {
-                holder.setText(R.id.tv_o2_chat_message_body, messageBody.body)
-                        .setText(R.id.tv_o2_chat_message_person_name, name)
-                        .setText(R.id.tv_o2_chat_message_time, time)
+            holder.setText(R.id.tv_o2_chat_message_person_name, name)
+                    .setText(R.id.tv_o2_chat_message_time, time)
+
+            if (messageBody != null) {
+                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)
+                }
             }
             }
+
             //头像
             //头像
             val avatar = holder.getView<CircleImageView>(R.id.image_o2_chat_message_avatar)
             val avatar = holder.getView<CircleImageView>(R.id.image_o2_chat_message_avatar)
             val url = APIAddressHelper.instance().getPersonAvatarUrlWithId(message.createPerson)
             val url = APIAddressHelper.instance().getPersonAvatarUrlWithId(message.createPerson)
@@ -141,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 {
     interface MessageEventListener {
         //重新发送消息
         //重新发送消息
         fun resendClick(message: IMMessage)
         fun resendClick(message: IMMessage)
+        fun playAudio(position: Int, msgBody: IMMessageBody)
+        fun openOriginImage(position: Int, msgBody: IMMessageBody)
+        fun openLocation(msgBody: IMMessageBody)
     }
     }
 }
 }

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

@@ -1,14 +1,26 @@
 package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.im
 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.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.app.base.BasePresenterImpl
-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.IMMessageForm
+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.*
+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.XLog
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.o2Subscribe
 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.Observable
 import rx.android.schedulers.AndroidSchedulers
 import rx.android.schedulers.AndroidSchedulers
 import rx.schedulers.Schedulers
 import rx.schedulers.Schedulers
+import java.io.*
+import java.net.HttpURLConnection
+import java.net.URL
 
 
 class O2ChatPresenter : BasePresenterImpl<O2ChatContract.View>(), O2ChatContract.Presenter  {
 class O2ChatPresenter : BasePresenterImpl<O2ChatContract.View>(), O2ChatContract.Presenter  {
 
 
@@ -32,22 +44,116 @@ class O2ChatPresenter : BasePresenterImpl<O2ChatContract.View>(), O2ChatContract
                 }
                 }
     }
     }
 
 
-    override fun sendTextMessage(msg: IMMessage) {
+    override fun updateConversationTitle(id: String, title: String) {
+        if (id.isEmpty() || title.isEmpty()) {
+            mView?.updateFail("参数不正确,无法修改")
+            return
+        }
         val service = getMessageCommunicateService(mView?.getContext())
         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)
+        val form = IMConversationUpdateForm()
+        form.id = id
+        form.title = title
+        service?.updateConversation(form)
+                ?.subscribeOn(Schedulers.io())
+                ?.observeOn(AndroidSchedulers.mainThread())
+                ?.o2Subscribe {
+                    onNext {
+                        if (it.data != null) {
+                            mView?.updateSuccess(it.data)
+                        } else {
+                            mView?.updateFail("修改失败!")
+                        }
+                    }
+                    onError { e, _ ->
+                        XLog.error("", e)
+                        mView?.updateFail("修改失败!")
+                    }
                 }
                 }
-            }
-            onError { e, _ ->
-                XLog.error("", e)
-                mView?.sendFail(msg.id)
-            }
+
+    }
+
+    override fun updateConversationPeople(id: String, users: ArrayList<String>) {
+        if (id.isEmpty() || users.isEmpty()) {
+            mView?.updateFail("参数不正确,无法修改")
+            return
+        }
+        val service = getMessageCommunicateService(mView?.getContext())
+        val form = IMConversationUpdateForm()
+        form.id = id
+        form.personList = users
+        service?.updateConversation(form)
+                ?.subscribeOn(Schedulers.io())
+                ?.observeOn(AndroidSchedulers.mainThread())
+                ?.o2Subscribe {
+                    onNext {
+                        if (it.data != null) {
+                            mView?.updateSuccess(it.data)
+                        } else {
+                            mView?.updateFail("修改失败!")
+                        }
+                    }
+                    onError { e, _ ->
+                        XLog.error("", e)
+                        mView?.updateFail("修改失败!")
+                    }
+                }
+    }
+
+    override fun sendIMMessage(msg: IMMessage) {
+        val service = getMessageCommunicateService(mView?.getContext())
+        //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 +199,34 @@ class O2ChatPresenter : BasePresenterImpl<O2ChatContract.View>(), O2ChatContract
                             XLog.error("read error", e)
                             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}")
+                        }
+                    }
+
+
+
+
         }
         }
     }
     }
 }
 }

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

@@ -1,5 +1,8 @@
 package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.im
 package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.im
 
 
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.R
+
+
 object O2IM {
 object O2IM {
 
 
     const val IM_Message_Receiver_Action = "net.o2oa.android.im.message"
     const val IM_Message_Receiver_Action = "net.o2oa.android.im.message"
@@ -7,4 +10,212 @@ object O2IM {
 
 
     const val conversation_type_single = "single"
     const val conversation_type_single = "single"
     const val conversation_type_group = "group"
     const val conversation_type_group = "group"
+
+    val im_emoji_hashMap = hashMapOf<String, Int>(
+            "[01]" to R.mipmap.im_emotion_01,
+            "[02]" to R.mipmap.im_emotion_02,
+            "[03]" to R.mipmap.im_emotion_03,
+            "[04]" to R.mipmap.im_emotion_04,
+            "[05]" to R.mipmap.im_emotion_05,
+            "[06]" to R.mipmap.im_emotion_06,
+            "[07]" to R.mipmap.im_emotion_07,
+            "[08]" to R.mipmap.im_emotion_08,
+            "[09]" to R.mipmap.im_emotion_09,
+            "[10]" to R.mipmap.im_emotion_10,
+            "[11]" to R.mipmap.im_emotion_11,
+            "[12]" to R.mipmap.im_emotion_12,
+            "[13]" to R.mipmap.im_emotion_13,
+            "[14]" to R.mipmap.im_emotion_14,
+            "[15]" to R.mipmap.im_emotion_15,
+            "[16]" to R.mipmap.im_emotion_16,
+            "[17]" to R.mipmap.im_emotion_17,
+            "[18]" to R.mipmap.im_emotion_18,
+            "[19]" to R.mipmap.im_emotion_19,
+            "[20]" to R.mipmap.im_emotion_20,
+            "[21]" to R.mipmap.im_emotion_21,
+            "[22]" to R.mipmap.im_emotion_22,
+            "[23]" to R.mipmap.im_emotion_23,
+            "[24]" to R.mipmap.im_emotion_24,
+            "[25]" to R.mipmap.im_emotion_25,
+            "[26]" to R.mipmap.im_emotion_26,
+            "[27]" to R.mipmap.im_emotion_27,
+            "[28]" to R.mipmap.im_emotion_28,
+            "[29]" to R.mipmap.im_emotion_29,
+            "[30]" to R.mipmap.im_emotion_30,
+            "[31]" to R.mipmap.im_emotion_31,
+            "[32]" to R.mipmap.im_emotion_32,
+            "[33]" to R.mipmap.im_emotion_33,
+            "[34]" to R.mipmap.im_emotion_34,
+            "[35]" to R.mipmap.im_emotion_35,
+            "[36]" to R.mipmap.im_emotion_36,
+            "[37]" to R.mipmap.im_emotion_37,
+            "[38]" to R.mipmap.im_emotion_38,
+            "[39]" to R.mipmap.im_emotion_39,
+            "[40]" to R.mipmap.im_emotion_40,
+            "[41]" to R.mipmap.im_emotion_41,
+            "[42]" to R.mipmap.im_emotion_42,
+            "[43]" to R.mipmap.im_emotion_43,
+            "[44]" to R.mipmap.im_emotion_44,
+            "[45]" to R.mipmap.im_emotion_45,
+            "[46]" to R.mipmap.im_emotion_46,
+            "[47]" to R.mipmap.im_emotion_47,
+            "[48]" to R.mipmap.im_emotion_48,
+            "[49]" to R.mipmap.im_emotion_49,
+            "[50]" to R.mipmap.im_emotion_50,
+            "[51]" to R.mipmap.im_emotion_51,
+            "[52]" to R.mipmap.im_emotion_52,
+            "[53]" to R.mipmap.im_emotion_53,
+            "[54]" to R.mipmap.im_emotion_54,
+            "[55]" to R.mipmap.im_emotion_55,
+            "[56]" to R.mipmap.im_emotion_56,
+            "[57]" to R.mipmap.im_emotion_57,
+            "[58]" to R.mipmap.im_emotion_58,
+            "[59]" to R.mipmap.im_emotion_59,
+            "[60]" to R.mipmap.im_emotion_60,
+            "[61]" to R.mipmap.im_emotion_61,
+            "[62]" to R.mipmap.im_emotion_62,
+            "[63]" to R.mipmap.im_emotion_63,
+            "[64]" to R.mipmap.im_emotion_64,
+            "[65]" to R.mipmap.im_emotion_65,
+            "[66]" to R.mipmap.im_emotion_66,
+            "[67]" to R.mipmap.im_emotion_67,
+            "[68]" to R.mipmap.im_emotion_68,
+            "[69]" to R.mipmap.im_emotion_69,
+            "[70]" to R.mipmap.im_emotion_70,
+            "[71]" to R.mipmap.im_emotion_71,
+            "[72]" to R.mipmap.im_emotion_72,
+            "[73]" to R.mipmap.im_emotion_73,
+            "[74]" to R.mipmap.im_emotion_74,
+            "[75]" to R.mipmap.im_emotion_75,
+            "[76]" to R.mipmap.im_emotion_76,
+            "[77]" to R.mipmap.im_emotion_77,
+            "[78]" to R.mipmap.im_emotion_78,
+            "[79]" to R.mipmap.im_emotion_79,
+            "[80]" to R.mipmap.im_emotion_80,
+            "[81]" to R.mipmap.im_emotion_81,
+            "[82]" to R.mipmap.im_emotion_82,
+            "[83]" to R.mipmap.im_emotion_83,
+            "[84]" to R.mipmap.im_emotion_84,
+            "[85]" to R.mipmap.im_emotion_85,
+            "[86]" to R.mipmap.im_emotion_86,
+            "[87]" to R.mipmap.im_emotion_87
+    )
+
+    fun emojiResId(key: String) :Int {
+        return im_emoji_hashMap[key] ?: R.mipmap.im_emotion_01
+    }
+
+    enum class AudioPlayState {
+        playing,
+        idle
+    }
+
+    //instant message type
+
+    /**
+     * 流程类型
+     */
+    const val TYPE_APPLICATION_CREATE = "application_create"
+
+    const val TYPE_APPLICATION_UPDATE = "application_update"
+
+    const val TYPE_APPLICATION_DELETE = "application_delete"
+
+    const val TYPE_PROCESS_CREATE = "process_create"
+
+    const val TYPE_PROCESS_UPDATE = "process_update"
+
+    const val TYPE_PROCESS_DELETE = "process_delete"
+
+    /* 有新的工作通过消息节点 */
+    const val TYPE_ACTIVITY_MESSAGE = "activity_message"
+
+    const val TYPE_WORK_TO_WORKCOMPLETED = "work_to_workCompleted"
+
+    const val TYPE_WORK_CREATE = "work_create"
+
+    const val TYPE_WORK_DELETE = "work_delete"
+
+    const val TYPE_WORKCOMPLETED_CREATE = "workCompleted_create"
+
+    const val TYPE_WORKCOMPLETED_DELETE = "workCompleted_delete"
+
+    const val TYPE_TASK_TO_TASKCOMPLETED = "task_to_taskCompleted"
+
+    const val TYPE_TASK_CREATE = "task_create"
+
+    const val TYPE_TASK_DELETE = "task_delete"
+
+    const val TYPE_TASK_URGE = "task_urge"
+
+    const val TYPE_TASK_EXPIRE = "task_expire"
+
+    const val TYPE_TASK_PRESS = "task_press"
+
+    const val TYPE_TASKCOMPLETED_CREATE = "taskCompleted_create"
+
+    const val TYPE_TASKCOMPLETED_DELETE = "taskCompleted_delete"
+
+    const val TYPE_READ_TO_READCOMPLETED = "read_to_readCompleted"
+
+    const val TYPE_READ_CREATE = "read_create"
+
+    const val TYPE_READ_DELETE = "read_delete"
+
+    const val TYPE_READCOMPLETED_CREATE = "readCompleted_create"
+
+    const val TYPE_READCOMPLETED_DELETE = "readCompleted_delete"
+
+    const val TYPE_REVIEW_CREATE = "review_create"
+
+    const val TYPE_REVIEW_DELETE = "review_delete"
+
+    const val TYPE_ATTACHMENT_CREATE = "attachment_create"
+
+    const val TYPE_ATTACHMENT_DELETE = "attachment_delete"
+
+    const val TYPE_MEETING_INVITE = "meeting_invite"
+
+    const val TYPE_MEETING_DELETE = "meeting_delete"
+
+    const val TYPE_MEETING_ACCEPT = "meeting_accept"
+
+    const val TYPE_MEETING_REJECT = "meeting_reject"
+
+    const val TYPE_ATTACHMENT_SHARE = "attachment_share"
+
+    const val TYPE_ATTACHMENT_SHARECANCEL = "attachment_shareCancel"
+
+    const val TYPE_ATTACHMENT_EDITOR = "attachment_editor"
+
+    const val TYPE_ATTACHMENT_EDITORCANCEL = "attachment_editorCancel"
+
+    const val TYPE_ATTACHMENT_EDITORMODIFY = "attachment_editorModify"
+
+    const val TYPE_CALENDAR_ALARM = "calendar_alarm"
+
+    const val TYPE_CUSTOM_CREATE = "custom_create"
+
+    const val TYPE_TEAMWORK_TASKCREATE = "teamwork_taskCreate"
+
+    const val TYPE_TEAMWORK_TASKUPDATE = "teamwork_taskUpdate"
+
+    const val TYPE_TEAMWORK_TASKDELETE = "teamwork_taskDelelte"
+
+    const val TYPE_TEAMWORK_TASKOVERTIME = "teamwork_taskOvertime"
+
+    const val TYPE_TEAMWORK_CHAT = "teamwork_taskChat"
+
+    const val TYPE_CMS_PUBLISH = "cms_publish"
+
+    const val TYPE_BBS_SUBJECTCREATE = "bbs_subjectCreate"
+
+    const val TYPE_BBS_REPLYCREATE = "bbs_replyCreate"
+
+    const val TYPE_MIND_FILESEND = "mind_fileSend"
+
+    const val TYPE_MIND_FILESHARE = "mind_fileShare"
+
+    const val TYPE_IM_CREATE = "im_create"
+
 }
 }

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

@@ -0,0 +1,99 @@
+package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.im
+
+import android.app.Activity
+import android.os.Bundle
+import android.support.v7.widget.LinearLayoutManager
+import android.widget.TextView
+import kotlinx.android.synthetic.main.activity_o2_instant_message.*
+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.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.InstantMessage
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.DateHelper
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.go
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.visible
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.widgets.CircleImageView
+
+class O2InstantMessageActivity : BaseMVPActivity<O2InstantMessageContract.View, O2InstantMessageContract.Presenter>(), O2InstantMessageContract.View {
+    override var mPresenter: O2InstantMessageContract.Presenter = O2InstantMessagePresenter()
+
+
+    override fun layoutResId(): Int = R.layout.activity_o2_instant_message
+
+
+    companion object {
+        val messageListKey = "messageListKey"
+        fun openInstantActivity(instantList: ArrayList<InstantMessage>, activity: Activity) {
+            val bundle = Bundle()
+            bundle.putParcelableArrayList(messageListKey, instantList)
+            activity.go<O2InstantMessageActivity>(bundle)
+        }
+    }
+
+    private val instantList = ArrayList<InstantMessage>()
+    private val adapter: CommonRecycleViewAdapter<InstantMessage> by lazy {
+        object : CommonRecycleViewAdapter<InstantMessage>(this, instantList, R.layout.item_o2_chat_message_text_left) {
+            override fun convert(holder: CommonRecyclerViewHolder?, t: InstantMessage?) {
+                if (t != null && holder!= null) {
+                    val avatar = holder.getView<CircleImageView>(R.id.image_o2_chat_message_avatar)
+                    avatar.setImageResource(messageTypeAvatar(t.type))
+                    val titleText = holder.getView<TextView>(R.id.tv_o2_chat_message_body)
+                    titleText.text = t.title
+                    titleText.visible()
+                    val time = DateHelper.imChatMessageTime(t.createTime)
+                    holder.setText(R.id.tv_o2_chat_message_time, time)
+                }
+
+            }
+
+        }
+    }
+
+
+    override fun afterSetContentView(savedInstanceState: Bundle?) {
+        setupToolBar("通知消息", setupBackButton = true)
+        val inList = intent?.extras?.getParcelableArrayList<InstantMessage>(messageListKey)
+        if (inList != null && inList.isNotEmpty()) {
+            instantList.clear()
+            instantList.addAll(inList)
+        }
+
+        rv_o2_instant_messages.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
+        rv_o2_instant_messages.adapter = adapter
+        if (instantList.isNotEmpty()) {
+            rv_o2_instant_messages.scrollToPosition(instantList.size - 1)
+        }
+    }
+
+
+    private fun messageTypeAvatar(type: String) : Int {
+        if (type.startsWith("task_")) {
+            return R.mipmap.ic_todo_task
+        }else if (type.startsWith("taskCompleted_")) {
+            return R.mipmap.ic_todo_task_completed
+        }else if (type.startsWith("read_")) {
+            return R.mipmap.ic_todo_read
+        }else if (type.startsWith("readCompleted_")) {
+            return R.mipmap.ic_todo_read_completed
+        }else if (type.startsWith("review_")||type.startsWith("work_")||type.startsWith("process_")) {
+            return R.mipmap.ic_todo_task
+        }else if (type.startsWith("meeting_")) {
+            return R.mipmap.app_meeting
+        }else if (type.startsWith("attachment_")) {
+            return R.mipmap.app_yunpan
+        }else if (type.startsWith("calendar_")) {
+            return R.mipmap.app_calendar
+        }else if (type.startsWith("cms_")) {
+            return R.mipmap.app_cms
+        }else if (type.startsWith("bbs_")) {
+            return R.mipmap.app_bbs
+        }else if (type.startsWith("mind_")) {
+            return R.mipmap.app_mind_map
+        }else if (type.startsWith("attachment_")) {
+            return R.mipmap.app_attendance
+        }else {
+            return R.mipmap.app_o2_ai
+        }
+    }
+}

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

@@ -0,0 +1,18 @@
+package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.im
+
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BasePresenter
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BaseView
+
+
+/**
+ * Created by fancyLou on 2020-05-25.
+ * Copyright © 2020 O2. All rights reserved.
+ */
+object O2InstantMessageContract {
+    interface View: BaseView {
+
+    }
+    interface Presenter: BasePresenter<View> {
+
+    }
+}

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

@@ -0,0 +1,13 @@
+package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.im
+
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BasePresenterImpl
+
+
+/**
+ * Created by fancyLou on 2020-05-25.
+ * Copyright © 2020 O2. All rights reserved.
+ */
+
+class O2InstantMessagePresenter : BasePresenterImpl<O2InstantMessageContract.View>(), O2InstantMessageContract.Presenter  {
+
+}

+ 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)
+            }
+        }
+    }
+}
+

+ 6 - 0
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/fm/O2IMConversationContract.kt

@@ -2,14 +2,20 @@ package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.im.fm
 
 
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BasePresenter
 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.app.base.BaseView
+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.IMConversationInfo
 
 
 object O2IMConversationContract {
 object O2IMConversationContract {
     interface View: BaseView {
     interface View: BaseView {
         fun myConversationList(list: List<IMConversationInfo>)
         fun myConversationList(list: List<IMConversationInfo>)
+        fun myInstantMessageList(instantList: List<InstantMessage>)
+        fun createConvSuccess(conv: IMConversationInfo)
+        fun createConvFail(message: String)
     }
     }
 
 
     interface Presenter: BasePresenter<View> {
     interface Presenter: BasePresenter<View> {
         fun getMyConversationList()
         fun getMyConversationList()
+        fun getMyInstantMessageList()
+        fun createConversation(type: String, users: ArrayList<String>)
     }
     }
 }
 }

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

@@ -3,21 +3,31 @@ package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.im.fm
 import android.support.v7.widget.LinearLayoutManager
 import android.support.v7.widget.LinearLayoutManager
 import android.view.Menu
 import android.view.Menu
 import android.view.MenuInflater
 import android.view.MenuInflater
+import android.view.MenuItem
+import android.widget.ImageView
 import android.widget.TextView
 import android.widget.TextView
+import com.wugang.activityresult.library.ActivityResult
 import kotlinx.android.synthetic.main.fragment_o2_im_conversation.*
 import kotlinx.android.synthetic.main.fragment_o2_im_conversation.*
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.O2SDKManager
 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.R
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BaseMVPViewPagerFragment
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BaseMVPViewPagerFragment
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.im.O2ChatActivity
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.im.O2ChatActivity
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.im.O2IM
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.im.O2IM
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.im.O2InstantMessageActivity
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.o2.main.MainActivity
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.o2.organization.ContactPickerActivity
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.adapter.CommonRecycleViewAdapter
 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.core.component.adapter.CommonRecyclerViewHolder
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.APIAddressHelper
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.APIAddressHelper
+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.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.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.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.DateHelper
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XLog
 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.gone
 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.extension.visible
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.imageloader.O2ImageLoaderManager
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.imageloader.O2ImageLoaderManager
@@ -29,6 +39,7 @@ class O2IMConversationFragment : BaseMVPViewPagerFragment<O2IMConversationContra
     override var mPresenter: O2IMConversationContract.Presenter = O2IMConversationPresenter()
     override var mPresenter: O2IMConversationContract.Presenter = O2IMConversationPresenter()
 
 
     override fun layoutResId(): Int = R.layout.fragment_o2_im_conversation
     override fun layoutResId(): Int = R.layout.fragment_o2_im_conversation
+    private val instantList = ArrayList<InstantMessage>()
     private val cList = ArrayList<IMConversationInfo>()
     private val cList = ArrayList<IMConversationInfo>()
     private val adapter: CommonRecycleViewAdapter<IMConversationInfo> by lazy {
     private val adapter: CommonRecycleViewAdapter<IMConversationInfo> by lazy {
         object : CommonRecycleViewAdapter<IMConversationInfo>(activity, cList,
         object : CommonRecycleViewAdapter<IMConversationInfo>(activity, cList,
@@ -45,6 +56,9 @@ class O2IMConversationFragment : BaseMVPViewPagerFragment<O2IMConversationContra
                             val name = person.substring(0, person.indexOf("@"))
                             val name = person.substring(0, person.indexOf("@"))
                             holder.setText(R.id.tv_o2_im_con_title, name)
                             holder.setText(R.id.tv_o2_im_con_title, name)
                         }
                         }
+                    }else if(O2IM.conversation_type_group == t.type) {
+                        holder.setText(R.id.tv_o2_im_con_title, t.title )
+                                .setImageViewResource(R.id.image_o2_im_con_avatar, R.mipmap.group_default)
                     }
                     }
                     val unread = holder.getView<TextView>(R.id.tv_o2_im_con_unread_number)
                     val unread = holder.getView<TextView>(R.id.tv_o2_im_con_unread_number)
                     if (t.unreadNumber > 0) {
                     if (t.unreadNumber > 0) {
@@ -57,15 +71,32 @@ class O2IMConversationFragment : BaseMVPViewPagerFragment<O2IMConversationContra
                     if (lastMessage != null) {
                     if (lastMessage != null) {
                         val lastTime = DateHelper.convertStringToDate(lastMessage.createTime)
                         val lastTime = DateHelper.convertStringToDate(lastMessage.createTime)
                         val lastMessageBody = lastMessage.messageBody()
                         val lastMessageBody = lastMessage.messageBody()
-                        var lastMessageText = ""
-                        if (lastMessageBody != null) {
-                            lastMessageText = when(lastMessageBody) {
-                                is IMMessageBody.Text -> {lastMessageBody.body}
-                                else -> "" //其它消息类型 转化成文本
+                        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.visible()
+                                val text = holder.getView<TextView>(R.id.tv_o2_im_con_last_message)
+                                text.gone()
+                            }
+                            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)
+                                text.text = lastMessageBody.body
+                                text.visible()
+                            }
+                            else -> {
+                                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 = lastMessageBody?.body
+                                text.visible()
                             }
                             }
                         }
                         }
+
                         holder.setText(R.id.tv_o2_im_con_last_message_time, DateHelper.friendlyTime(lastTime))
                         holder.setText(R.id.tv_o2_im_con_last_message_time, DateHelper.friendlyTime(lastTime))
-                                .setText(R.id.tv_o2_im_con_last_message, lastMessageText)
+
                     }
                     }
                 }
                 }
             }
             }
@@ -76,12 +107,18 @@ class O2IMConversationFragment : BaseMVPViewPagerFragment<O2IMConversationContra
         rv_o2_im_conversation.layoutManager = LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false)
         rv_o2_im_conversation.layoutManager = LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false)
         rv_o2_im_conversation.adapter = adapter
         rv_o2_im_conversation.adapter = adapter
         adapter.setOnItemClickListener { _, position ->
         adapter.setOnItemClickListener { _, position ->
-            O2ChatActivity.startChat(activity, cList[position].id)
+            O2ChatActivity.startChat(activity, cList[position].id!!)
+        }
+        ll_o2_instant_message.setOnClickListener {
+            if (instantList.isNotEmpty()) {
+                O2InstantMessageActivity.openInstantActivity(instantList, activity)
+            }
         }
         }
     }
     }
 
 
 
 
     override fun lazyLoad() {
     override fun lazyLoad() {
+        XLog.debug("lazy load im conversation。。。。。。。。。。")
         mPresenter.getMyConversationList()
         mPresenter.getMyConversationList()
     }
     }
 
 
@@ -91,17 +128,66 @@ class O2IMConversationFragment : BaseMVPViewPagerFragment<O2IMConversationContra
         super.onCreateOptionsMenu(menu, inflater)
         super.onCreateOptionsMenu(menu, inflater)
     }
     }
 
 
+    override fun onOptionsItemSelected(item: MenuItem?): Boolean {
+        when(item?.itemId) {
+            R.id.menu_single_create -> {
+                openCreateSingleConversation()
+                return true
+            }
+            R.id.menu_tribe_create -> {
+                openCreateTribeConversation()
+                return true
+            }
+        }
+        return super.onOptionsItemSelected(item)
+    }
+
     override fun myConversationList(list: List<IMConversationInfo>) {
     override fun myConversationList(list: List<IMConversationInfo>) {
         if (list.isEmpty()) {
         if (list.isEmpty()) {
             tv_null_conversation.visible()
             tv_null_conversation.visible()
-            rv_o2_im_conversation.gone()
+            ll_o2_im_message_list.gone()
+            setUnreadNumber(0)
         } else {
         } else {
             tv_null_conversation.gone()
             tv_null_conversation.gone()
-            rv_o2_im_conversation.visible()
+            ll_o2_im_message_list.visible()
             cList.clear()
             cList.clear()
             cList.addAll(list)
             cList.addAll(list)
             adapter.notifyDataSetChanged()
             adapter.notifyDataSetChanged()
+            var allnumbers = 0
+            list.forEach { con ->
+                val number = con.unreadNumber ?: 0
+                allnumbers += number
+            }
+            setUnreadNumber(allnumbers)
+        }
+        mPresenter.getMyInstantMessageList()
+    }
+
+
+    override fun myInstantMessageList(instantList: List<InstantMessage>) {
+         if (instantList.isNotEmpty()) {
+             this.instantList.clear()
+             this.instantList.addAll(instantList)
+             ll_o2_instant_message.visible()
+             val lastMsg = instantList.last()
+             val lastTime = DateHelper.convertStringToDate(lastMsg.createTime)
+             tv_o2_in_con_last_message_time.text = DateHelper.friendlyTime(lastTime)
+             tv_o2_in_con_last_message.text = lastMsg.title
+         }else {
+             ll_o2_instant_message.gone()
+         }
+    }
+
+    override fun createConvSuccess(conv: IMConversationInfo) {
+        if (!cList.any { it.id == conv.id }) {
+            cList.add(conv)
+            adapter.notifyDataSetChanged()
         }
         }
+        O2ChatActivity.startChat(activity, conv.id!!)
+    }
+
+    override fun createConvFail(message: String) {
+        XToast.toastShort(activity, message)
     }
     }
 
 
     fun receiveMessageFromWebsocket(message: IMMessage) {
     fun receiveMessageFromWebsocket(message: IMMessage) {
@@ -115,4 +201,45 @@ class O2IMConversationFragment : BaseMVPViewPagerFragment<O2IMConversationContra
             }
             }
         }
         }
     }
     }
+
+
+    private fun setUnreadNumber(number: Int) {
+        if (activity is MainActivity) {
+            (activity as MainActivity).refreshUnreadNumber(number)
+        }
+    }
+
+    private fun openCreateSingleConversation() {
+        ActivityResult.of(activity)
+                .className(ContactPickerActivity::class.java)
+                .params(ContactPickerActivity.startPickerBundle(pickerModes = arrayListOf(ContactPickerActivity.personPicker), multiple = false))
+                .greenChannel().forResult { _, data ->
+                    val result = data?.getParcelableExtra<ContactPickerResult>(ContactPickerActivity.CONTACT_PICKED_RESULT)
+                    if (result != null && result.users.isNotEmpty()) {
+                        createSingleConversation(result.users[0].distinguishedName)
+                    }else {
+                        XLog.debug("没有选择人员!!!!")
+                    }
+                }
+    }
+
+    private fun createSingleConversation(user: String) {
+        mPresenter.createConversation("single", arrayListOf(user))
+    }
+
+    private fun openCreateTribeConversation() {
+        ActivityResult.of(activity)
+                .className(ContactPickerActivity::class.java)
+                .params(ContactPickerActivity.startPickerBundle(pickerModes = arrayListOf(ContactPickerActivity.personPicker), multiple = true))
+                .greenChannel().forResult { _, data ->
+                    val result = data?.getParcelableExtra<ContactPickerResult>(ContactPickerActivity.CONTACT_PICKED_RESULT)
+                    if (result != null && result.users.isNotEmpty()) {
+                        val a = arrayListOf<String>()
+                        a.addAll(result.users.map { it.distinguishedName })
+                        mPresenter.createConversation("group",  a)
+                    }else {
+                        XLog.debug("没有选择人员!!!!")
+                    }
+                }
+    }
 }
 }

+ 62 - 0
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/im/fm/O2IMConversationPresenter.kt

@@ -1,13 +1,73 @@
 package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.im.fm
 package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.im.fm
 
 
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BasePresenterImpl
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BasePresenterImpl
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.IMConversationInfo
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XLog
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XLog
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.o2Subscribe
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.o2Subscribe
+import rx.Observable
 import rx.android.schedulers.AndroidSchedulers
 import rx.android.schedulers.AndroidSchedulers
 import rx.schedulers.Schedulers
 import rx.schedulers.Schedulers
 
 
 class O2IMConversationPresenter : BasePresenterImpl<O2IMConversationContract.View>(), O2IMConversationContract.Presenter {
 class O2IMConversationPresenter : BasePresenterImpl<O2IMConversationContract.View>(), O2IMConversationContract.Presenter {
 
 
+
+    override fun createConversation(type: String, users: ArrayList<String>) {
+        val service = getMessageCommunicateService(mView?.getContext())
+        if (service != null) {
+            val info = IMConversationInfo()
+            info.type = type
+            info.personList = users
+            service.createConversation(info)
+                    .subscribeOn(Schedulers.io())
+                    .observeOn(AndroidSchedulers.mainThread())
+                    .o2Subscribe {
+                        onNext {
+                            if (it.data!= null) {
+                                mView?.createConvSuccess(it.data)
+                            }else{
+                                mView?.createConvFail("创建会话失败!")
+                            }
+                        }
+                        onError { e, _ ->
+                            XLog.error("", e)
+                            mView?.createConvFail("创建会话失败!${e?.message}")
+                        }
+                    }
+        }
+    }
+
+    override fun getMyInstantMessageList() {
+        val service = getMessageCommunicateService(mView?.getContext())
+        service?.let { ser ->
+            ser.instantMessageList(100)
+                    .subscribeOn(Schedulers.io())
+                    .flatMap { res ->
+                        val list = res.data
+                        if (list != null && list.isNotEmpty()) {
+                            val newList = list.sortedBy { it.createTime }
+                            Observable.just(newList)
+                        }else {
+                            Observable.just(ArrayList())
+                        }
+                    }
+                    .observeOn(AndroidSchedulers.mainThread())
+                    .o2Subscribe {
+                        onNext { list->
+                            if (list != null) {
+                                mView?.myInstantMessageList(list)
+                            }else{
+                                mView?.myInstantMessageList(ArrayList())
+                            }
+                        }
+                        onError { e, _ ->
+                            XLog.error("", e)
+                            mView?.myInstantMessageList(ArrayList())
+                        }
+                    }
+
+        }
+    }
+
     override fun getMyConversationList() {
     override fun getMyConversationList() {
         val service = getMessageCommunicateService(mView?.getContext())
         val service = getMessageCommunicateService(mView?.getContext())
         service?.let {
         service?.let {
@@ -29,4 +89,6 @@ class O2IMConversationPresenter : BasePresenterImpl<O2IMConversationContract.Vie
                     }
                     }
         }
         }
     }
     }
+
+
 }
 }

+ 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?) {
     override fun downloadAttachmentSuccess(file: File?) {
         hideLoadingDialog()
         hideLoadingDialog()
         XLog.debug(file?.name)
         XLog.debug(file?.name)
-        XToast.toastShort(this,"下载成功")
         if (file != null && file.exists()) AndroidUtils.openFileWithDefaultApp(this, file)
         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 android.widget.TextView
 import net.muliba.accounting.app.ExceptionHandler
 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.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.ResponseHandler
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.RetrofitClient
 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.model.bo.api.meeting.MeetingFileInfoJson
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.FileExtensionHelper
 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.SDCardHelper
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XLog
 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.doAsync
 import org.jetbrains.anko.uiThread
 import org.jetbrains.anko.uiThread
 import rx.android.schedulers.AndroidSchedulers
 import rx.android.schedulers.AndroidSchedulers
@@ -26,81 +30,20 @@ class MeetingDetailInfoPresenter : BasePresenterImpl<MeetingDetailInfoContract.V
 
 
     override fun downloadMeetingFile(meetingFileInfoJson: MeetingFileInfoJson) {
     override fun downloadMeetingFile(meetingFileInfoJson: MeetingFileInfoJson) {
         val path = FileExtensionHelper.getXBPMMEETINGAttachmentFileByName(meetingFileInfoJson.name)
         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) {
     override fun asyncLoadPersonName(nameTv: TextView, id: String) {

+ 44 - 11
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/o2/main/MainActivity.kt

@@ -81,8 +81,8 @@ class MainActivity : BaseMVPActivity<MainContract.View, MainContract.Presenter>(
         val indexId = O2SDKManager.instance().prefs().getString(O2CustomStyle.INDEX_ID_PREF_KEY, "")
         val indexId = O2SDKManager.instance().prefs().getString(O2CustomStyle.INDEX_ID_PREF_KEY, "")
         XLog.info("main activity isIndex $indexType..............")
         XLog.info("main activity isIndex $indexType..............")
 
 
-//        val newsFragment = O2IMConversationFragment()
-        val newsFragment = NewsFragment()
+        val newsFragment = O2IMConversationFragment()
+//        val newsFragment = NewsFragment()
         fragmentList.add(newsFragment)
         fragmentList.add(newsFragment)
         fragmentTitles.add(getString(R.string.tab_message))
         fragmentTitles.add(getString(R.string.tab_message))
 
 
@@ -373,7 +373,7 @@ class MainActivity : BaseMVPActivity<MainContract.View, MainContract.Presenter>(
             0 -> resetToolBar(getString(R.string.tab_message))
             0 -> resetToolBar(getString(R.string.tab_message))
             1 -> resetToolBar(getString(R.string.tab_contact))
             1 -> resetToolBar(getString(R.string.tab_contact))
             2 -> setIndexToolBar()
             2 -> setIndexToolBar()
-            3 -> resetToolBar(getString(R.string.tab_contact))
+            3 -> resetToolBar(getString(R.string.tab_app))
             4 -> resetToolBar(getString(R.string.tab_settings))
             4 -> resetToolBar(getString(R.string.tab_settings))
         }
         }
 
 
@@ -415,17 +415,18 @@ class MainActivity : BaseMVPActivity<MainContract.View, MainContract.Presenter>(
                 .setRequiresCharging(true)//充电的时候才执行
                 .setRequiresCharging(true)//充电的时候才执行
                 .setPeriodic(24 * 60 * 60 * 1000)
                 .setPeriodic(24 * 60 * 60 * 1000)
                 .build()
                 .build()
-        val collectLogComponent = ComponentName(this, CollectLogJobService::class.java)
-        val jobCollectLog = JobInfo.Builder(O2.O2_COLLECT_LOG_JOB_ID, collectLogComponent)
-                .setPersisted(true)
-                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
-                .setPeriodic(1000 * 60 * 60 * 12)
-                .build()
+//        val collectLogComponent = ComponentName(this, CollectLogJobService::class.java)
+//        val jobCollectLog = JobInfo.Builder(O2.O2_COLLECT_LOG_JOB_ID, collectLogComponent)
+//                .setPersisted(true)
+//                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+//                .setPeriodic(1000 * 60 * 60 * 12)
+//                .build()
 
 
         val jobScheduler = applicationContext.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
         val jobScheduler = applicationContext.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
         val result = jobScheduler.schedule(jobInfo)
         val result = jobScheduler.schedule(jobInfo)
-        val result2 = jobScheduler.schedule(jobCollectLog)
-        XLog.info("jobScheduler result:$result, result2:$result2")
+//        val result2 = jobScheduler.schedule(jobCollectLog)
+//        XLog.info("jobScheduler result:$result, result2:$result2")
+        XLog.info("jobScheduler result:$result")
     }
     }
 
 
 
 
@@ -495,6 +496,37 @@ class MainActivity : BaseMVPActivity<MainContract.View, MainContract.Presenter>(
     /**************im 消息接收器***************/
     /**************im 消息接收器***************/
 
 
     var mReceiver: IMMessageReceiver? = null
     var mReceiver: IMMessageReceiver? = null
+    private var unreadMsgNumber = 0
+
+    fun refreshUnreadNumber(number: Int) {
+        unreadMsgNumber = number
+        when {
+            unreadMsgNumber in 1..99 -> {
+                circle_tv_icon_main_bottom_news.visible()
+                circle_tv_icon_main_bottom_news.setText("$unreadMsgNumber")
+            }
+            unreadMsgNumber >= 100 -> {
+                circle_tv_icon_main_bottom_news.visible()
+                circle_tv_icon_main_bottom_news.setText("99..")
+            }
+            else -> circle_tv_icon_main_bottom_news.gone()
+        }
+    }
+
+    fun addUnreadMsg() {
+        unreadMsgNumber += 1
+        when {
+            unreadMsgNumber in 1..99 -> {
+                circle_tv_icon_main_bottom_news.visible()
+                circle_tv_icon_main_bottom_news.setText("$unreadMsgNumber")
+            }
+            unreadMsgNumber >= 100 -> {
+                circle_tv_icon_main_bottom_news.visible()
+                circle_tv_icon_main_bottom_news.setText("99..")
+            }
+            else -> circle_tv_icon_main_bottom_news.gone()
+        }
+    }
 
 
     private fun registerBroadcast() {
     private fun registerBroadcast() {
         mReceiver = IMMessageReceiver()
         mReceiver = IMMessageReceiver()
@@ -507,6 +539,7 @@ class MainActivity : BaseMVPActivity<MainContract.View, MainContract.Presenter>(
         if (newsFragment is O2IMConversationFragment) {
         if (newsFragment is O2IMConversationFragment) {
             newsFragment.receiveMessageFromWebsocket(message)
             newsFragment.receiveMessageFromWebsocket(message)
         }
         }
+        addUnreadMsg()
     }
     }
 
 
     inner class IMMessageReceiver : BroadcastReceiver() {
     inner class IMMessageReceiver : BroadcastReceiver() {

+ 13 - 9
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/o2/organization/NewOrganizationPresenter.kt

@@ -1,17 +1,15 @@
 package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.o2.organization
 package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.o2.organization
 
 
 import net.muliba.accounting.app.ExceptionHandler
 import net.muliba.accounting.app.ExceptionHandler
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.R
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BasePresenterImpl
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BasePresenterImpl
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.ApiResponse
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.ApiResponse
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.main.identity.IdentityLevelForm
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.main.identity.IdentityLevelForm
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.main.person.PersonListLikeForm
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.main.person.PersonListLikeForm
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.main.unit.UnitJson
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.vo.NewContactFragmentVO
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.vo.NewContactListVO
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.vo.NewContactListVO
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XLog
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.o2Subscribe
 import rx.Observable
 import rx.Observable
 import rx.android.schedulers.AndroidSchedulers
 import rx.android.schedulers.AndroidSchedulers
-import rx.functions.Action0
 import rx.functions.Action1
 import rx.functions.Action1
 import rx.schedulers.Schedulers
 import rx.schedulers.Schedulers
 
 
@@ -90,8 +88,7 @@ class NewOrganizationPresenter : BasePresenterImpl<NewOrganizationContract.View>
     }
     }
 
 
     override fun searchPersonWithKey(result: String) {
     override fun searchPersonWithKey(result: String) {
-            val form = PersonListLikeForm( result)
-            val backResult = ArrayList<NewContactListVO>()
+            val form = PersonListLikeForm(result)
             getOrganizationAssembleControlApi(mView?.getContext())?.let { service ->
             getOrganizationAssembleControlApi(mView?.getContext())?.let { service ->
                 service.personListLike(form)
                 service.personListLike(form)
                         .subscribeOn(Schedulers.io())
                         .subscribeOn(Schedulers.io())
@@ -99,6 +96,7 @@ class NewOrganizationPresenter : BasePresenterImpl<NewOrganizationContract.View>
                             val retList = ArrayList<NewContactListVO>()
                             val retList = ArrayList<NewContactListVO>()
                             val list = response.data
                             val list = response.data
                             if (list != null && list.isNotEmpty()) {
                             if (list != null && list.isNotEmpty()) {
+                                XLog.debug("size:${list.size}")
                                 list.map {
                                 list.map {
                                     retList.add(NewContactListVO.Identity(
                                     retList.add(NewContactListVO.Identity(
                                             name = it.name,
                                             name = it.name,
@@ -110,9 +108,15 @@ class NewOrganizationPresenter : BasePresenterImpl<NewOrganizationContract.View>
                             retList
                             retList
                         }
                         }
                         .observeOn(AndroidSchedulers.mainThread())
                         .observeOn(AndroidSchedulers.mainThread())
-                        .subscribe(Action1<ArrayList<NewContactListVO>> { list -> backResult.addAll(list) },
-                                ExceptionHandler(mView?.getContext()) { e -> mView?.backError(e.message ?: "") },
-                                Action0 { mView?.callbackResult(backResult) })
+                        .o2Subscribe {
+                            onNext {
+                                list -> mView?.callbackResult(list)
+                            }
+                            onError { e, _ ->
+                                XLog.error("", e)
+                                mView?.backError(e?.message ?: "")
+                            }
+                        }
             }
             }
         }
         }
 }
 }

+ 49 - 35
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/o2/person/PersonActivity.kt

@@ -1,23 +1,18 @@
 package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.o2.person
 package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.o2.person
 
 
 
 
-import android.content.Intent
 import android.os.Bundle
 import android.os.Bundle
 import android.text.TextUtils
 import android.text.TextUtils
 import android.view.Gravity
 import android.view.Gravity
 import android.view.View
 import android.view.View
-import cn.jpush.im.android.api.JMessageClient
-import cn.jpush.im.android.api.callback.GetUserInfoCallback
-import cn.jpush.im.android.api.model.UserInfo
-import jiguang.chat.activity.ChatActivity
 import kotlinx.android.synthetic.main.activity_person_info.*
 import kotlinx.android.synthetic.main.activity_person_info.*
-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.O2SDKManager
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.R
 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.base.BaseMVPActivity
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.im.O2ChatActivity
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.APIAddressHelper
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.APIAddressHelper
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.enums.GenderTypeEnums
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.enums.GenderTypeEnums
-import net.zoneland.x.bpm.mobile.v1.zoneXBPM.im.JIMConstant
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.IMConversationInfo
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.main.person.PersonJson
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.main.person.PersonJson
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.AndroidUtils
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.AndroidUtils
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XLog
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XLog
@@ -30,7 +25,6 @@ import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.imageloader.O2ImageLoaderMana
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.imageloader.O2ImageLoaderOptions
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.imageloader.O2ImageLoaderOptions
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.widgets.CommonMenuPopupWindow
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.widgets.CommonMenuPopupWindow
 import org.jetbrains.anko.email
 import org.jetbrains.anko.email
-import org.jetbrains.anko.makeCall
 import org.jetbrains.anko.sendSMS
 import org.jetbrains.anko.sendSMS
 
 
 
 
@@ -49,6 +43,7 @@ class PersonActivity : BaseMVPActivity<PersonContract.View, PersonContract.Prese
     }
     }
 
 
     var loadedPersonId = ""//用户的id字段
     var loadedPersonId = ""//用户的id字段
+    var loadedPersonDN = ""//用户的id字段
     var personId = ""
     var personId = ""
     var genderName = ""
     var genderName = ""
     var hasCollection = false
     var hasCollection = false
@@ -56,7 +51,7 @@ class PersonActivity : BaseMVPActivity<PersonContract.View, PersonContract.Prese
     val mobileClickMenu: CommonMenuPopupWindow by lazy { CommonMenuPopupWindow(mobileMenuItemList, this) }
     val mobileClickMenu: CommonMenuPopupWindow by lazy { CommonMenuPopupWindow(mobileMenuItemList, this) }
     val emailMenuItemList: ArrayList<String> = arrayListOf("发送邮件","复制")
     val emailMenuItemList: ArrayList<String> = arrayListOf("发送邮件","复制")
     val emailClickMenu: CommonMenuPopupWindow by lazy { CommonMenuPopupWindow(emailMenuItemList, this) }
     val emailClickMenu: CommonMenuPopupWindow by lazy { CommonMenuPopupWindow(emailMenuItemList, this) }
-    private var canTalkTo = false
+//    private var canTalkTo = false
 
 
     override fun afterSetContentView(savedInstanceState: Bundle?) {
     override fun afterSetContentView(savedInstanceState: Bundle?) {
         personId = intent.extras?.getString(PERSON_NAME_KEY, "")?:""
         personId = intent.extras?.getString(PERSON_NAME_KEY, "")?:""
@@ -86,31 +81,40 @@ class PersonActivity : BaseMVPActivity<PersonContract.View, PersonContract.Prese
             R.id.image_person_back -> finish()
             R.id.image_person_back -> finish()
             R.id.btn_begin_talk -> {
             R.id.btn_begin_talk -> {
                 // 开始聊天
                 // 开始聊天
-                if (O2App.instance._JMIsLogin()) {
-                    if (canTalkTo && O2SDKManager.instance().cId != loadedPersonId) {
-                        val intent = Intent(this, ChatActivity::class.java)
-                        val user = O2App.instance._JMMyUserInfo()
-                        val name = tv_person_name.text.toString()
-                        XLog.info("current user: ${user?.userName}, ${user?.nickname}, ${user?.appKey}")
-                        XLog.info("to user: $loadedPersonId, $name")
-                        intent.putExtra(JIMConstant.CONV_TITLE, name)
-                        intent.putExtra(JIMConstant.TARGET_ID, loadedPersonId)
-                        intent.putExtra(JIMConstant.TARGET_APP_KEY, O2App.instance.JM_IM_APP_KEY)
-                        startActivity(intent)
-                    }else {
-                        XToast.toastShort(this, "无法发起聊天,该用户没有启用聊天功能!")
-                    }
-
+                if (!TextUtils.isEmpty(loadedPersonDN) && O2SDKManager.instance().distinguishedName != loadedPersonDN) {
+                    startTalk()
                 }else {
                 }else {
-                    XToast.toastShort(this, "无法聊天,没有连接到IM服务器!!")
+
                 }
                 }
+//                if (O2App.instance._JMIsLogin()) {
+//                    if (canTalkTo && O2SDKManager.instance().cId != loadedPersonId) {
+//                        val intent = Intent(this, ChatActivity::class.java)
+//                        val user = O2App.instance._JMMyUserInfo()
+//                        val name = tv_person_name.text.toString()
+//                        XLog.info("current user: ${user?.userName}, ${user?.nickname}, ${user?.appKey}")
+//                        XLog.info("to user: $loadedPersonId, $name")
+//                        intent.putExtra(JIMConstant.CONV_TITLE, name)
+//                        intent.putExtra(JIMConstant.TARGET_ID, loadedPersonId)
+//                        intent.putExtra(JIMConstant.TARGET_APP_KEY, O2App.instance.JM_IM_APP_KEY)
+//                        startActivity(intent)
+//                    }else {
+//                        XToast.toastShort(this, "无法发起聊天,该用户没有启用聊天功能!")
+//                    }
+//
+//                }else {
+//                    XToast.toastShort(this, "无法聊天,没有连接到IM服务器!!")
+//                }
             }
             }
         }
         }
     }
     }
 
 
+    private fun startTalk() {
+        mPresenter.startSingleTalk(loadedPersonDN)
+    }
+
     private fun usuallyBtnClick() {
     private fun usuallyBtnClick() {
-        if (O2SDKManager.instance().distinguishedName == personId) {
-            XLog.debug("自己收藏自己。。。。" + personId)
+        if (O2SDKManager.instance().distinguishedName == loadedPersonDN) {
+            XLog.debug("自己收藏自己。。。。$personId")
             return
             return
         }
         }
         if (hasCollection) {
         if (hasCollection) {
@@ -189,6 +193,7 @@ class PersonActivity : BaseMVPActivity<PersonContract.View, PersonContract.Prese
     override fun loadPersonInfo(personInfo: PersonJson) {
     override fun loadPersonInfo(personInfo: PersonJson) {
         hideLoadingDialog()
         hideLoadingDialog()
         loadedPersonId = personInfo.id
         loadedPersonId = personInfo.id
+        loadedPersonDN = personInfo.distinguishedName
         tv_person_mobile.text = personInfo.mobile
         tv_person_mobile.text = personInfo.mobile
         tv_person_email.text = personInfo.mail
         tv_person_email.text = personInfo.mail
         if (GenderTypeEnums.FEMALE.key == personInfo.genderType) {
         if (GenderTypeEnums.FEMALE.key == personInfo.genderType) {
@@ -204,7 +209,7 @@ class PersonActivity : BaseMVPActivity<PersonContract.View, PersonContract.Prese
         }
         }
         tv_person_name.text = personInfo.name
         tv_person_name.text = personInfo.name
         tv_person_name_2.text = personInfo.name
         tv_person_name_2.text = personInfo.name
-        if (personInfo.woIdentityList != null && !personInfo.woIdentityList.isEmpty()) {
+        if (personInfo.woIdentityList.isNotEmpty()) {
             var department = ""
             var department = ""
             personInfo.woIdentityList.mapIndexed { index, woIdentityListItem ->
             personInfo.woIdentityList.mapIndexed { index, woIdentityListItem ->
                 if (index != personInfo.woIdentityList.size - 1) {
                 if (index != personInfo.woIdentityList.size - 1) {
@@ -220,16 +225,25 @@ class PersonActivity : BaseMVPActivity<PersonContract.View, PersonContract.Prese
         val url = APIAddressHelper.instance().getPersonAvatarUrlWithId(personInfo.id)
         val url = APIAddressHelper.instance().getPersonAvatarUrlWithId(personInfo.id)
         O2ImageLoaderManager.instance().showImage(image_person_avatar, url, O2ImageLoaderOptions(placeHolder = R.mipmap.icon_avatar_men))
         O2ImageLoaderManager.instance().showImage(image_person_avatar, url, O2ImageLoaderOptions(placeHolder = R.mipmap.icon_avatar_men))
 
 
-        //IM 服务端获取用户信息
-        JMessageClient.getUserInfo(loadedPersonId, object : GetUserInfoCallback(){
-            override fun gotResult(responseCode: Int, responseMessage: String?, info: UserInfo?) {
-                XLog.info("responseCode:$responseCode, responseMessage:$responseMessage ")
-                canTalkTo = responseCode == 0
-            }
-        })
+//        //IM 服务端获取用户信息
+//        JMessageClient.getUserInfo(loadedPersonId, object : GetUserInfoCallback(){
+//            override fun gotResult(responseCode: Int, responseMessage: String?, info: UserInfo?) {
+//                XLog.info("responseCode:$responseCode, responseMessage:$responseMessage ")
+//                canTalkTo = responseCode == 0
+//            }
+//        })
     }
     }
 
 
     override fun loadPersonInfoFail() {
     override fun loadPersonInfoFail() {
         hideLoadingDialog()
         hideLoadingDialog()
     }
     }
+
+    override fun createConvSuccess(conv: IMConversationInfo) {
+        O2ChatActivity.startChat(this, conv.id!!)
+    }
+
+    override fun createConvFail(message: String) {
+        XLog.error(message)
+        XToast.toastShort(this, "无法发起聊天,创建会话失败!")
+    }
 }
 }

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

@@ -2,6 +2,7 @@ package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.o2.person
 
 
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BasePresenter
 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.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.main.person.PersonJson
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.main.person.PersonJson
 
 
 
 
@@ -10,6 +11,8 @@ object PersonContract {
         fun isUsuallyPerson(flag:Boolean)
         fun isUsuallyPerson(flag:Boolean)
         fun loadPersonInfo(personInfo: PersonJson)
         fun loadPersonInfo(personInfo: PersonJson)
         fun loadPersonInfoFail()
         fun loadPersonInfoFail()
+        fun createConvSuccess(conv: IMConversationInfo)
+        fun createConvFail(message: String)
     }
     }
 
 
     interface Presenter : BasePresenter<View> {
     interface Presenter : BasePresenter<View> {
@@ -17,5 +20,6 @@ object PersonContract {
         fun collectionUsuallyPerson(owner:String, person:String, ownerDisplay:String,personDisplay:String, gender:String, mobile:String)
         fun collectionUsuallyPerson(owner:String, person:String, ownerDisplay:String,personDisplay:String, gender:String, mobile:String)
         fun deleteUsuallyPerson(owner: String, person: String)
         fun deleteUsuallyPerson(owner: String, person: String)
         fun isUsuallyPerson(owner: String, person: String)
         fun isUsuallyPerson(owner: String, person: String)
+        fun startSingleTalk(user: String)
     }
     }
 }
 }

+ 27 - 0
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/app/o2/person/PersonPresenter.kt

@@ -4,8 +4,10 @@ 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.app.base.BasePresenterImpl
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.ResponseHandler
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.ResponseHandler
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.realm.RealmDataService
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.realm.RealmDataService
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.im.IMConversationInfo
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.main.person.PersonJson
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.main.person.PersonJson
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XLog
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XLog
+import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.o2Subscribe
 import rx.android.schedulers.AndroidSchedulers
 import rx.android.schedulers.AndroidSchedulers
 import rx.schedulers.Schedulers
 import rx.schedulers.Schedulers
 
 
@@ -46,4 +48,29 @@ class PersonPresenter : BasePresenterImpl<PersonContract.View>(), PersonContract
         }
         }
 
 
     }
     }
+
+    override fun startSingleTalk(user: String) {
+        val service = getMessageCommunicateService(mView?.getContext())
+        if (service != null) {
+            val info = IMConversationInfo()
+            info.type = "single"
+            info.personList = arrayListOf(user)
+            service.createConversation(info)
+                    .subscribeOn(Schedulers.io())
+                    .observeOn(AndroidSchedulers.mainThread())
+                    .o2Subscribe {
+                        onNext {
+                            if (it.data!= null) {
+                                mView?.createConvSuccess(it.data)
+                            }else{
+                                mView?.createConvFail("创建会话失败!")
+                            }
+                        }
+                        onError { e, _ ->
+                            XLog.error("", e)
+                            mView?.createConvFail("创建会话失败!${e?.message}")
+                        }
+                    }
+        }
+    }
 }
 }

+ 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 android.text.TextUtils
 import com.tencent.smtt.sdk.QbSdk
 import com.tencent.smtt.sdk.QbSdk
 import com.tencent.smtt.sdk.ValueCallback
 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.app.tbs.FileReaderActivity
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.RetrofitClient
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.RetrofitClient
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.download.DownloadProgressHandler
 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.DataOutputStream
 import java.io.File
 import java.io.File
 import java.io.FileOutputStream
 import java.io.FileOutputStream
+import java.net.HttpURLConnection
+import java.net.URL
 
 
 /**
 /**
  * Created by fancyLou on 2018/8/22.
  * 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 {
                 .o2Subscribe {
                     onNext { result ->
                     onNext { result ->
                         XLog.info("下载文档:$result")
                         XLog.info("下载文档:$result")
-                        if (result) {
-                            openFileWithTBS(path, "")
-                        }else {
+                        if (TextUtils.isEmpty(result)) {
+                            openFileWithTBS(result, "")
+                        } else {
                             XToast.toastShort(context, "下载文档失败!")
                             XToast.toastShort(context, "下载文档失败!")
                         }
                         }
                         finishCallback()
                         finishCallback()
@@ -95,13 +102,59 @@ class DownloadDocument(val context: Activity) {
                         finishCallback()
                         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)) {
     private fun openFileWithTBS(path: String?, fileName: String) = if (!TextUtils.isEmpty(path)) {
         context.go<FileReaderActivity>(FileReaderActivity.startBundle(path!!))
         context.go<FileReaderActivity>(FileReaderActivity.startBundle(path!!))
 //        AndroidUtils.openFileWithDefaultApp(context, File(path))
 //        AndroidUtils.openFileWithDefaultApp(context, File(path))
-    }else {
+    } else {
         XLog.error("文档本地地址没有。。。。。。。。。。。")
         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.O2App
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.O2SDKManager
 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.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.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.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.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.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.TaskData
 import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.o2.WorkLog
 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 net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.o2Subscribe
 import okhttp3.MediaType
 import okhttp3.MediaType
 import okhttp3.MultipartBody
 import okhttp3.MultipartBody
@@ -208,42 +207,51 @@ class TaskWebViewPresenter : BasePresenterImpl<TaskWebViewContract.View>(), Task
                         val info: AttachmentInfo? = response.data
                         val info: AttachmentInfo? = response.data
                         if (info != null) {
                         if (info != null) {
                             val path = FileExtensionHelper.getXBPMWORKAttachmentFileByName(info.name)
                             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 {
                         } else {
                             Observable.create(object : Observable.OnSubscribe<File> {
                             Observable.create(object : Observable.OnSubscribe<File> {
                                 override fun call(t: Subscriber<in File>?) {
                                 override fun call(t: Subscriber<in File>?) {
@@ -276,42 +284,53 @@ class TaskWebViewPresenter : BasePresenterImpl<TaskWebViewContract.View>(), Task
                         val info: AttachmentInfo? = response.data
                         val info: AttachmentInfo? = response.data
                         if (info != null) {
                         if (info != null) {
                             val path = FileExtensionHelper.getXBPMWORKAttachmentFileByName(info.name)
                             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 {
                         } else {
                             Observable.create(object : Observable.OnSubscribe<File> {
                             Observable.create(object : Observable.OnSubscribe<File> {
                                 override fun call(t: Subscriber<in 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.doAsync
 import org.jetbrains.anko.uiThread
 import org.jetbrains.anko.uiThread
 import java.io.BufferedReader
 import java.io.BufferedReader
+import java.io.FileOutputStream
 import java.io.InputStream
 import java.io.InputStream
 import java.io.InputStreamReader
 import java.io.InputStreamReader
 import java.net.HttpURLConnection
 import java.net.HttpURLConnection
@@ -297,8 +298,17 @@ class PictureLoaderService(val context: Context) {
         var inputstream:InputStream? = null
         var inputstream:InputStream? = null
         try {
         try {
             XLog.debug("load avatar : $name")
             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)}
         }catch (e: Exception){XLog.error("获取头像失败", e)}
         return inputstream
         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()
+                }
+            }
+        }
+    }
+
+
+}

+ 96 - 0
o2android/app/src/main/java/net/zoneland/x/bpm/mobile/v1/zoneXBPM/widgets/KeyboardLayout.java

@@ -0,0 +1,96 @@
+package net.zoneland.x.bpm.mobile.v1.zoneXBPM.widgets;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+/**
+ * Created by fancyLou on 2020-05-19.
+ * Copyright © 2020 O2. All rights reserved.
+ */
+public class KeyboardLayout extends FrameLayout {
+
+    private KeyboardLayoutListener mListener;
+    private boolean mIsKeyboardActive = false; // 输入法是否激活
+    private int mKeyboardHeight = 0; // 输入法高度
+
+    public KeyboardLayout(Context context) {
+        this(context, null, 0);
+    }
+
+    public KeyboardLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public KeyboardLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        // 监听布局变化
+        getViewTreeObserver().addOnGlobalLayoutListener(new KeyboardOnGlobalChangeListener());
+    }
+
+    private class KeyboardOnGlobalChangeListener implements ViewTreeObserver.OnGlobalLayoutListener {
+
+        int mScreenHeight = 0;
+        Rect mRect = new Rect();
+
+        private int getScreenHeight() {
+            if (mScreenHeight > 0) {
+                return mScreenHeight;
+            }
+            mScreenHeight = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE))
+                    .getDefaultDisplay().getHeight();
+            return mScreenHeight;
+        }
+
+        @Override
+        public void onGlobalLayout() {
+//            // 获取当前页面窗口的显示范围
+            getWindowVisibleDisplayFrame(mRect);
+
+            int screenHeight = getScreenHeight();
+            int keyboardHeight = screenHeight - mRect.bottom; // 输入法的高度
+            boolean isActive = false;
+            if (Math.abs(keyboardHeight) > screenHeight / 5) {
+                isActive = true; // 超过屏幕五分之一则表示弹出了输入法
+                mKeyboardHeight = keyboardHeight;
+            }
+            mIsKeyboardActive = isActive;
+            if (mListener != null) {
+                mListener.onKeyboardStateChanged(isActive, keyboardHeight);
+            }
+        }
+    }
+
+    public void setKeyboardListener(KeyboardLayoutListener listener) {
+        mListener = listener;
+    }
+
+    public KeyboardLayoutListener getKeyboardListener() {
+        return mListener;
+    }
+
+    public boolean isKeyboardActive() {
+        return mIsKeyboardActive;
+    }
+
+    /**
+     * 获取输入法高度
+     *
+     * @return
+     */
+    public int getKeyboardHeight() {
+        return mKeyboardHeight;
+    }
+
+    public interface KeyboardLayoutListener {
+        /**
+         * @param isActive       输入法是否激活
+         * @param keyboardHeight 输入法面板高度
+         */
+        void onKeyboardStateChanged(boolean isActive, int keyboardHeight);
+    }
+
+}

+ 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">
     tools:context=".app.o2.webview.LocalImageViewActivity">
     <RelativeLayout
     <RelativeLayout
         android:layout_width="match_parent"
         android:layout_width="match_parent"
-        android:layout_height="48dp"
-        android:padding="@dimen/spacing_small">
+        android:layout_height="48dp">
         <ImageView
         <ImageView
             android:id="@+id/image_local_view_back_btn"
             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_alignParentStart="true"
             android:layout_centerVertical="true"
             android:layout_centerVertical="true"
             android:src="@mipmap/ic_back_mtrl_white_alpha"/>
             android:src="@mipmap/ic_back_mtrl_white_alpha"/>

+ 1 - 0
o2android/app/src/main/res/layout/activity_login.xml

@@ -82,6 +82,7 @@
                             android:background="@null"
                             android:background="@null"
                             android:hint="@string/activity_login_username"
                             android:hint="@string/activity_login_username"
                             android:maxLines="1"
                             android:maxLines="1"
+                            android:singleLine="true"
                             android:textColor="@color/z_color_text_primary"
                             android:textColor="@color/z_color_text_primary"
                             android:textColorHint="@color/z_color_text_hint"
                             android:textColorHint="@color/z_color_text_hint"
                             android:textSize="13sp" />
                             android:textSize="13sp" />

+ 62 - 4
o2android/app/src/main/res/layout/activity_o2_chat.xml

@@ -19,6 +19,16 @@
         app:layout_constraintBottom_toTopOf="@+id/ll_o2_chat_input_layout"
         app:layout_constraintBottom_toTopOf="@+id/ll_o2_chat_input_layout"
         app:layout_constraintTop_toBottomOf="@+id/app_bar_layout_snippet" />
         app:layout_constraintTop_toBottomOf="@+id/app_bar_layout_snippet" />
 
 
+    <net.zoneland.x.bpm.mobile.v1.zoneXBPM.widgets.KeyboardLayout
+        android:id="@+id/rv_o2_chat_emoji_box_out"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:visibility="gone">
+
+    </net.zoneland.x.bpm.mobile.v1.zoneXBPM.widgets.KeyboardLayout>
 
 
     <LinearLayout
     <LinearLayout
         android:id="@+id/ll_o2_chat_input_layout"
         android:id="@+id/ll_o2_chat_input_layout"
@@ -84,6 +94,7 @@
             android:orientation="horizontal"
             android:orientation="horizontal"
             android:baselineAligned="false">
             android:baselineAligned="false">
              <LinearLayout
              <LinearLayout
+                 android:id="@+id/ll_o2_chat_audio_btn"
                  android:layout_width="0dp"
                  android:layout_width="0dp"
                  android:layout_height="wrap_content"
                  android:layout_height="wrap_content"
                  android:layout_weight="1"
                  android:layout_weight="1"
@@ -93,9 +104,10 @@
                      android:layout_width="28dp"
                      android:layout_width="28dp"
                      android:layout_height="28dp"
                      android:layout_height="28dp"
                      android:layout_gravity="center"
                      android:layout_gravity="center"
-                     android:src="@mipmap/chat_mic" />
+                     android:src="@mipmap/chat_iicon_yuyin" />
              </LinearLayout>
              </LinearLayout>
             <LinearLayout
             <LinearLayout
+                android:id="@+id/ll_o2_chat_album_btn"
                 android:layout_width="0dp"
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_weight="1"
                 android:layout_weight="1"
@@ -105,9 +117,10 @@
                     android:layout_width="28dp"
                     android:layout_width="28dp"
                     android:layout_height="28dp"
                     android:layout_height="28dp"
                     android:layout_gravity="center"
                     android:layout_gravity="center"
-                    android:src="@mipmap/chat_img" />
+                    android:src="@mipmap/chat_iicon_tupian" />
             </LinearLayout>
             </LinearLayout>
             <LinearLayout
             <LinearLayout
+                android:id="@+id/ll_o2_chat_camera_btn"
                 android:layout_width="0dp"
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_weight="1"
                 android:layout_weight="1"
@@ -117,9 +130,10 @@
                     android:layout_width="28dp"
                     android:layout_width="28dp"
                     android:layout_height="28dp"
                     android:layout_height="28dp"
                     android:layout_gravity="center"
                     android:layout_gravity="center"
-                    android:src="@mipmap/chat_camera" />
+                    android:src="@mipmap/chat_iicon_xiangji" />
             </LinearLayout>
             </LinearLayout>
             <LinearLayout
             <LinearLayout
+                android:id="@+id/ll_o2_chat_location_btn"
                 android:layout_width="0dp"
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_weight="1"
                 android:layout_weight="1"
@@ -129,10 +143,54 @@
                     android:layout_width="28dp"
                     android:layout_width="28dp"
                     android:layout_height="28dp"
                     android:layout_height="28dp"
                     android:layout_gravity="center"
                     android:layout_gravity="center"
-                    android:src="@mipmap/chat_location" />
+                    android:src="@mipmap/chat_icon_ditu" />
             </LinearLayout>
             </LinearLayout>
         </LinearLayout>
         </LinearLayout>
 
 
+        <android.support.v7.widget.RecyclerView
+            android:id="@+id/rv_o2_chat_emoji_box"
+            android:layout_width="match_parent"
+            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_iicon_yuyin"
+                android:layout_centerInParent="true"
+                android:background="@drawable/f5_circle"
+                />
+        </RelativeLayout>
+
     </LinearLayout>
     </LinearLayout>
 
 
 </android.support.constraint.ConstraintLayout>
 </android.support.constraint.ConstraintLayout>

+ 19 - 0
o2android/app/src/main/res/layout/activity_o2_instant_message.xml

@@ -0,0 +1,19 @@
+<?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.O2InstantMessageActivity">
+
+    <include layout="@layout/snippet_appbarlayout_toolbar" />
+
+    <android.support.v7.widget.RecyclerView
+        android:id="@+id/rv_o2_instant_messages"
+        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.support.constraint.ConstraintLayout>

+ 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>

+ 2 - 1
o2android/app/src/main/res/layout/fragment_attendance_check_in.xml

@@ -64,7 +64,8 @@
         android:layout_marginEnd="@dimen/spacing_small"
         android:layout_marginEnd="@dimen/spacing_small"
         android:background="@drawable/attendance_check_in_background"
         android:background="@drawable/attendance_check_in_background"
         android:gravity="center"
         android:gravity="center"
-        android:orientation="vertical">
+        android:orientation="vertical"
+        android:visibility="gone">
 
 
         <TextView
         <TextView
             android:id="@+id/tv_attendance_check_in_button_label"
             android:id="@+id/tv_attendance_check_in_button_label"

+ 15 - 0
o2android/app/src/main/res/layout/fragment_main_bottom_bar_image.xml

@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_width="match_parent"
     android:layout_height="56dp"
     android:layout_height="56dp"
     android:orientation="vertical"
     android:orientation="vertical"
@@ -39,6 +40,20 @@
                 android:textSize="12sp"
                 android:textSize="12sp"
                 android:textColor="@color/z_color_text_primary"
                 android:textColor="@color/z_color_text_primary"
                 android:text="@string/tab_message"/>
                 android:text="@string/tab_message"/>
+            <net.zoneland.x.bpm.mobile.v1.zoneXBPM.widgets.CircleTextView
+                android:id="@+id/circle_tv_icon_main_bottom_news"
+                android:layout_width="16dp"
+                android:layout_height="16dp"
+                android:layout_marginStart="25dp"
+                app:c_height="16dp"
+                app:c_inColor="@color/z_color_primary"
+                app:c_text="5"
+                android:textAlignment="center"
+                app:c_textColor="@android:color/white"
+                app:c_textSize="@dimen/font_mini"
+                app:c_width="16dp"
+                android:visibility="gone"
+                />
         </RelativeLayout>
         </RelativeLayout>
         <RelativeLayout
         <RelativeLayout
             android:id="@+id/icon_main_bottom_contact"
             android:id="@+id/icon_main_bottom_contact"

+ 96 - 3
o2android/app/src/main/res/layout/fragment_o2_im_conversation.xml

@@ -1,5 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 <RelativeLayout 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_width="match_parent"
     android:layout_height="match_parent"
     android:layout_height="match_parent"
     android:background="@color/z_color_background">
     android:background="@color/z_color_background">
@@ -18,10 +20,101 @@
             android:textSize="16sp"
             android:textSize="16sp"
             android:visibility="gone"/>
             android:visibility="gone"/>
 
 
-        <android.support.v7.widget.RecyclerView
-            android:id="@+id/rv_o2_im_conversation"
+        <LinearLayout
+            android:id="@+id/ll_o2_im_message_list"
             android:layout_width="match_parent"
             android:layout_width="match_parent"
-            android:layout_height="match_parent"/>
+            android:layout_height="match_parent"
+            android:orientation="vertical">
+
+            <LinearLayout
+                android:id="@+id/ll_o2_instant_message"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:visibility="gone"
+                android:orientation="vertical">
+
+                <android.support.constraint.ConstraintLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_small">
+                    <net.zoneland.x.bpm.mobile.v1.zoneXBPM.widgets.CircleImageView
+                        android:id="@+id/image_o2_in_con_avatar"
+                        app:layout_constraintStart_toStartOf="parent"
+                        app:layout_constraintTop_toTopOf="parent"
+                        app:layout_constraintBottom_toBottomOf="parent"
+                        android:layout_marginStart="@dimen/spacing_small"
+                        android:layout_width="@dimen/item_head_icon_size"
+                        android:layout_height="@dimen/item_head_icon_size"
+                        android:scaleType="fitXY"
+                        android:src="@mipmap/icon_msg"/>
+
+                    <TextView
+                        android:id="@+id/tv_o2_in_con_title"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:maxWidth="250dp"
+                        android:textSize="14sp"
+                        android:textColor="@color/z_color_text_primary"
+                        android:textAlignment="textStart"
+                        app:layout_constraintTop_toTopOf="parent"
+                        app:layout_constraintStart_toEndOf="@+id/image_o2_in_con_avatar"
+                        app:layout_constraintEnd_toEndOf="parent"
+                        app:layout_constraintHorizontal_bias="0.0"
+                        android:layout_marginEnd="40dp"
+                        android:singleLine="true"
+                        android:ellipsize="end"
+                        android:layout_marginStart="@dimen/spacing_small"
+                        android:text="通知消息"/>
+
+                    <TextView
+                        android:id="@+id/tv_o2_in_con_last_message"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:textSize="12sp"
+                        android:textColor="@color/z_color_text_hint"
+                        android:textAlignment="textStart"
+                        android:ellipsize="end"
+                        android:maxWidth="200dp"
+                        android:singleLine="true"
+                        app:layout_constraintTop_toBottomOf="@+id/tv_o2_in_con_title"
+                        app:layout_constraintStart_toEndOf="@+id/image_o2_in_con_avatar"
+                        app:layout_constraintEnd_toEndOf="parent"
+                        app:layout_constraintHorizontal_bias="0.0"
+                        android:layout_marginStart="@dimen/spacing_small"
+                        android:layout_marginTop="@dimen/spacing_tiny"
+                        tools:text="消息消息消息消息消息消息消息" />
+
+
+
+                    <TextView
+                        android:id="@+id/tv_o2_in_con_last_message_time"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:textSize="14sp"
+                        android:textColor="@color/z_color_text_hint"
+                        android:textAlignment="textEnd"
+                        app:layout_constraintTop_toTopOf="parent"
+                        app:layout_constraintEnd_toEndOf="parent"
+                        android:layout_marginEnd="@dimen/spacing_small"
+                        tools:text="9:10" />
+
+
+                </android.support.constraint.ConstraintLayout>
+                <View
+                    android:layout_width="match_parent"
+                    android:layout_height="1px"
+                    android:background="@color/z_color_split_meeting_line"
+                    android:layout_marginStart="60dp"
+                    android:layout_marginTop="@dimen/spacing_small"/>
+            </LinearLayout>
+
+            <android.support.v7.widget.RecyclerView
+                android:id="@+id/rv_o2_im_conversation"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="1"/>
+        </LinearLayout>
+
 
 
     </FrameLayout>
     </FrameLayout>
 </RelativeLayout>
 </RelativeLayout>

+ 2 - 0
o2android/app/src/main/res/layout/item_attendance_check_in_record_list.xml

@@ -27,6 +27,7 @@
             android:orientation="horizontal">
             android:orientation="horizontal">
 
 
             <TextView
             <TextView
+                android:id="@+id/tv_item_attendance_check_in_type"
                 android:layout_width="wrap_content"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_height="wrap_content"
                 android:text="@string/attendance_check_in_time_label"
                 android:text="@string/attendance_check_in_time_label"
@@ -37,6 +38,7 @@
                 android:id="@+id/tv_item_attendance_check_in_time"
                 android:id="@+id/tv_item_attendance_check_in_time"
                 android:layout_width="wrap_content"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_height="wrap_content"
+                android:layout_marginStart="@dimen/spacing_tiny"
                 android:textColor="@color/z_color_text_primary"
                 android:textColor="@color/z_color_text_primary"
                 android:textSize="@dimen/font_large"
                 android:textSize="@dimen/font_large"
                 tools:text="12:09:00"/>
                 tools:text="12:09:00"/>

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

@@ -38,7 +38,60 @@
                 android:id="@+id/tv_o2_chat_message_body"
                 android:id="@+id/tv_o2_chat_message_body"
                 style="@style/o2_im_chat_message_text_style"
                 style="@style/o2_im_chat_message_text_style"
                 android:textAlignment="textStart"
                 android:textAlignment="textStart"
+                android:visibility="gone"
                 tools:text="这里是消息内容。。。。。" />
                 tools:text="这里是消息内容。。。。。" />
+            <ImageView
+                android:id="@+id/image_o2_chat_message_emoji_body"
+                android:layout_width="32dp"
+                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>
         </LinearLayout>
 
 
         <ImageButton
         <ImageButton

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

@@ -37,7 +37,60 @@
                 android:id="@+id/tv_o2_chat_message_body"
                 android:id="@+id/tv_o2_chat_message_body"
                 style="@style/o2_im_chat_message_text_style"
                 style="@style/o2_im_chat_message_text_style"
                 android:textAlignment="textEnd"
                 android:textAlignment="textEnd"
+                android:visibility="gone"
                 tools:text="这里是消息内容。。。。。" />
                 tools:text="这里是消息内容。。。。。" />
+            <ImageView
+                android:id="@+id/image_o2_chat_message_emoji_body"
+                android:layout_width="32dp"
+                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>
         </LinearLayout>
 
 
         <ImageButton
         <ImageButton

+ 17 - 0
o2android/app/src/main/res/layout/item_o2_im_chat_emoji.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <ImageView
+        android:id="@+id/image_item_o2_im_chat_emoji"
+        android:layout_width="32dp"
+        android:layout_height="32dp"
+        android:layout_margin="@dimen/spacing_tiny"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        tools:src="@mipmap/im_emotion_01"/>
+</android.support.constraint.ConstraintLayout>

+ 27 - 4
o2android/app/src/main/res/layout/item_o2_im_conversation.xml

@@ -22,26 +22,49 @@
             android:id="@+id/tv_o2_im_con_title"
             android:id="@+id/tv_o2_im_con_title"
             android:layout_width="wrap_content"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_height="wrap_content"
+            android:maxWidth="250dp"
             android:textSize="14sp"
             android:textSize="14sp"
             android:textColor="@color/z_color_text_primary"
             android:textColor="@color/z_color_text_primary"
             android:textAlignment="textStart"
             android:textAlignment="textStart"
             app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintStart_toEndOf="@+id/image_o2_im_con_avatar"
             app:layout_constraintStart_toEndOf="@+id/image_o2_im_con_avatar"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="0.0"
             android:layout_marginStart="@dimen/spacing_small"
             android:layout_marginStart="@dimen/spacing_small"
-            tools:text="FancyLou"/>
+            android:layout_marginEnd="40dp"
+            android:singleLine="true"
+            android:ellipsize="end"
+            tools:text="韩飞鸿、汤笑蕾、汤笑蕾、汤笑蕾、汤笑蕾、汤笑蕾、汤笑蕾、汤笑蕾、汤笑蕾、汤笑蕾、汤笑蕾、汤笑蕾、汤笑蕾、"/>
 
 
         <TextView
         <TextView
             android:id="@+id/tv_o2_im_con_last_message"
             android:id="@+id/tv_o2_im_con_last_message"
             android:layout_width="wrap_content"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_height="wrap_content"
-            android:textSize="12sp"
-            android:textColor="@color/z_color_text_hint"
+            android:layout_marginStart="@dimen/spacing_small"
+            android:layout_marginTop="4dp"
+            android:ellipsize="end"
+            android:maxWidth="200dp"
+            android:singleLine="true"
             android:textAlignment="textStart"
             android:textAlignment="textStart"
+            android:textColor="@color/z_color_text_hint"
+            android:textSize="12sp"
+            android:visibility="gone"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="0.0"
+            app:layout_constraintStart_toEndOf="@+id/image_o2_im_con_avatar"
+            app:layout_constraintTop_toBottomOf="@+id/tv_o2_im_con_title"
+            tools:text="消息消息消息消息消息消息消息消息消息消息消息消息消息消息消息消息消息消息消息消息消息" />
+
+        <ImageView
+            android:id="@+id/tv_o2_im_con_last_message_emoji"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
             app:layout_constraintTop_toBottomOf="@+id/tv_o2_im_con_title"
             app:layout_constraintTop_toBottomOf="@+id/tv_o2_im_con_title"
             app:layout_constraintStart_toEndOf="@+id/image_o2_im_con_avatar"
             app:layout_constraintStart_toEndOf="@+id/image_o2_im_con_avatar"
             android:layout_marginStart="@dimen/spacing_small"
             android:layout_marginStart="@dimen/spacing_small"
             android:layout_marginTop="@dimen/spacing_tiny"
             android:layout_marginTop="@dimen/spacing_tiny"
-            tools:text="消息" />
+            android:visibility="gone"
+            tools:src="@mipmap/im_emotion_01" />
 
 
         <TextView
         <TextView
             android:id="@+id/tv_o2_im_con_last_message_time"
             android:id="@+id/tv_o2_im_con_last_message_time"

+ 11 - 6
o2android/app/src/main/res/menu/menu_chat.xml

@@ -1,13 +1,18 @@
 <menu xmlns:android="http://schemas.android.com/apk/res/android"
 <menu xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     xmlns:tools="http://schemas.android.com/tools"
-    tools:context=".app.main.activity.ChatActivity">
+    tools:context=".app.im.O2ChatActivity">
 
 
-    <item android:id="@+id/menu_chat_person_info"
-        app:showAsAction="ifRoom"
+<!--    <item android:id="@+id/menu_chat_person_info"-->
+<!--        app:showAsAction="ifRoom"-->
+<!--        android:orderInCategory="90"-->
+<!--        android:icon="@mipmap/icon_menu_account_circle"-->
+<!--        android:title="@string/person_info"/>-->
+    <item android:id="@+id/menu_chat_update_title"
         android:orderInCategory="90"
         android:orderInCategory="90"
-        android:icon="@mipmap/icon_menu_account_circle"
-        android:title="@string/person_info"/>
-
+        android:title="@string/menu_im_tribe_name_update"/>
+    <item android:id="@+id/menu_chat_update_member"
+        android:orderInCategory="90"
+        android:title="@string/menu_im_tribe_member_update"/>
 
 
 </menu>
 </menu>

+ 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>

BIN
o2android/app/src/main/res/mipmap-xhdpi/chat_camera.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/chat_camera_light.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/chat_camera_light_blue.png


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


BIN
o2android/app/src/main/res/mipmap-xhdpi/chat_icon_ditu.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/chat_iicon_tupian.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/chat_iicon_xiangji.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/chat_iicon_yuyin.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/chat_img.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/chat_img_light.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/chat_img_light_blue.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/chat_location.png


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


BIN
o2android/app/src/main/res/mipmap-xhdpi/chat_location_light.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/chat_location_light_blue.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/chat_mic_light.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/chat_mic_light_blue.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/group_default.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_01.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_02.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_03.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_04.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_05.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_06.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_07.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_08.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_09.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_10.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_11.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_12.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_13.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_14.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_15.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_16.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_17.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_18.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_19.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_20.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_21.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_22.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_23.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_24.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_25.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_26.png


BIN
o2android/app/src/main/res/mipmap-xhdpi/im_emotion_27.png


Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików