Эх сурвалжийг харах

1、案件上传页面增加了相册浏览及照片选择功能,选择照片的路径及拷贝转存已经完成
2、编写图片滑动浏览组件,基本功能完成,缺少指示器、回调等相关功能

不会爬树的猴 1 жил өмнө
parent
commit
82d5f77e66

+ 1 - 1
.idea/deploymentTargetDropDown.xml

@@ -12,6 +12,6 @@
         </deviceKey>
       </Target>
     </runningDeviceTargetSelectedWithDropDown>
-    <timeTargetWasSelectedWithDropDown value="2023-06-13T03:38:25.482356Z" />
+    <timeTargetWasSelectedWithDropDown value="2023-06-16T03:11:04.542232Z" />
   </component>
 </project>

+ 10 - 0
app/src/main/AndroidManifest.xml

@@ -40,6 +40,16 @@
         android:supportsRtl="true"
         android:networkSecurityConfig="@xml/network_security_config"
         android:theme="@style/Theme.CrUAV">
+        <!--注册provider 用于文件路径解析-->
+        <provider
+            android:authorities="${applicationId}.provider"
+            android:name="androidx.core.content.FileProvider"
+            android:exported="false"
+            android:grantUriPermissions="true">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/filepath"/>
+        </provider>
         <activity
             android:name=".AvMain"
             android:exported="false"

+ 3 - 3
app/src/main/java/com/cr/adapter/DownloadDataAdapter.kt

@@ -10,7 +10,7 @@ import android.view.ViewGroup
 import android.widget.ImageView
 import android.widget.ProgressBar
 import android.widget.TextView
-import com.cr.common.FileManager
+import com.cr.common.CrFileManager
 import com.cr.cruav.R
 import com.cr.data.CrUtil
 import com.cr.dialog.DialogNormal
@@ -69,7 +69,7 @@ class DownloadDataAdapter @JvmOverloads constructor(
         var model:DownloadDataModel = dataList!![position]
         // todo: 2023/4/12 判断文件是否存在
         var fullName = CrUtil.CONFIG_PATH + model.fileName
-        model.isDownload = FileManager.isExists(fullName)
+        model.isDownload = CrFileManager.isExists(fullName)
         model.let {
             viewHolder.lblTitle?.text = it.title
             viewHolder.lblIsDownload?.text = if (model.isDownload) "已下载" else "未下载"
@@ -120,7 +120,7 @@ class DownloadDataAdapter @JvmOverloads constructor(
 
                     // todo: 2023/4/12 下载完成
                     override fun onComplete() {
-                        holder.model?.isDownload = FileManager.isExists(fullName)
+                        holder.model?.isDownload = CrFileManager.isExists(fullName)
                         holder.lblIsDownload?.text = if (holder.model!!.isDownload) "已下载" else "未下载"
                         holder.lblIsDownload?.setTextColor(
                             if (holder.model!!.isDownload) Color.argb(

+ 2 - 2
app/src/main/java/com/cr/common/FileManager.kt → app/src/main/java/com/cr/common/CrFileManager.kt

@@ -12,7 +12,7 @@ import java.text.DecimalFormat
  * 创建日期:2023/3/31 09:58
  * 描述:文件管理器
  */
-class FileManager {
+class CrFileManager {
     companion object{
 
         /**
@@ -130,7 +130,7 @@ class FileManager {
          * 移植数据库
          * @param context
          */
-        fun MoveDataBase(context: Context): String? {
+        fun moveDataBase(context: Context): String? {
             val dbPath = CrUtil.DATABASE_PATH
             val dbFile = File(dbPath)
             if (dbFile.exists()) {

+ 110 - 0
app/src/main/java/com/cr/common/CrPictureManager.kt

@@ -0,0 +1,110 @@
+package com.cr.common
+
+import android.annotation.TargetApi
+import android.content.ContentUris
+import android.content.Context
+import android.content.Intent
+import android.database.Cursor
+import android.net.Uri
+import android.os.Build
+import android.os.Environment
+import android.provider.DocumentsContract
+import android.provider.MediaStore
+
+/**
+ * 操作系统:MAC系统
+ * 创建者:王成
+ * 创建日期:2023/6/16 10:32
+ * 描述: 图片管理
+ */
+class CrPictureManager {
+    companion object{
+        /**
+         * android4.4之前可直接获取图片真实uri
+         * @param context Context 上下文
+         * @param data Intent 数据
+         * @return String
+         */
+        fun handleImageBeforeKitKat(context: Context,data: Intent):String {
+            val uri = data.data
+            return getImagePath(context!!,uri!!, null, null)!!
+        }
+
+        /**
+         * android4.4之后,需要解析获取图片真实路径
+         * @param context Context 上下文
+         * @param data Intent 数据
+         */
+        @TargetApi(Build.VERSION_CODES.KITKAT)
+        fun handleImageAfterKitKat(context: Context,data: Intent): String? {
+            val uri = data.data
+            // todo: 2023/6/16 document类型的Uri
+            when {
+                DocumentsContract.isDocumentUri(context, uri) -> {
+                    // todo: 2023/6/16 通过documentId处理
+                    val docId = DocumentsContract.getDocumentId(uri)
+                    when (uri?.authority) {
+                        "com.android.externalstorage.documents" -> {
+                            val type = docId.split(":")[0]
+                            if ("primary".equals(type, ignoreCase = true)) {
+                                return Environment.getExternalStorageDirectory().toString() + "/" + docId.split(":")[1]
+                            }
+                        }
+                        // todo: 2023/6/16 media类型解析
+                        "com.android.providers.media.documents" -> {
+                            val id = docId.split(":")[1]
+                            val type = docId.split(":")[0]
+                            val contentUri: Uri? = when (type) {
+                                "image" -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
+                                "video" -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
+                                "audio" -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
+                                else -> null
+                            }
+                            val selection = "_id=?"
+                            val selectionArgs: Array<String> = arrayOf(id)
+                            return getImagePath(context!!,contentUri!!, selection, selectionArgs)
+                        }
+                        // todo: 2023/6/16 downloads文件解析
+                        "com.android.providers.downloads.documents" -> {
+                            ContentUris.withAppendedId(
+                                Uri.parse("content://downloads/public_downloads"), docId.toLong()
+                            ).apply {
+                                return getImagePath(context!!,this, null, null)!!
+                            }
+                        }
+                        else -> {
+                        }
+                    }
+                }
+                "content".equals(uri?.scheme, ignoreCase = true) ->
+                    //content类型数据不需要解析,直接传入生成即可
+                    return getImagePath(context!!,uri!!, null, null)
+                "file".equals(uri?.scheme, ignoreCase = true) ->
+                    //file类型的uri直接获取图片路径即可
+                    return uri!!.path!!
+            }
+            return null
+        }
+
+        /**
+         * 获取图片路径
+         * @param context Context 上下文
+         * @param uri Uri uri地址
+         * @param selection String? 选择地址
+         * @param selectionArgs Array<String>? 选择地址集
+         * @return String?
+         */
+        private fun getImagePath(context: Context, uri: Uri, selection: String?, selectionArgs: Array<String>?): String? {
+            var cursor: Cursor? = null
+            try {
+                cursor = context!!.contentResolver.query(uri, null, selection, selectionArgs, null)
+                if (cursor?.moveToFirst()!!) {
+                    return cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA))
+                }
+            } finally {
+                cursor?.close()
+            }
+            return null
+        }
+    }
+}

+ 9 - 0
app/src/main/java/com/cr/common/CrUnitManager.kt

@@ -120,5 +120,14 @@ class CrUnitManager {
             var format = SimpleDateFormat("yyyyMMddHHmmss")
             return format.format(Date())
         }
+
+        /**
+         * 获取系统日期
+         * @return String 系统日期 yyyy-MM-dd HH:mm:ss
+         */
+        fun toSystemYMDHMSDate():String{
+            var format = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
+            return format.format(Date())
+        }
     }
 }

+ 13 - 13
app/src/main/java/com/cr/cruav/AvLogin.kt

@@ -17,7 +17,7 @@ import androidx.core.content.ContextCompat
 import com.cr.common.DataManager
 import com.cr.common.DatabaseAppManager
 import com.cr.common.DatabaseConfigManager
-import com.cr.common.FileManager
+import com.cr.common.CrFileManager
 import com.cr.data.CrConfig
 import com.cr.data.CrUtil
 import com.cr.data.CrUtil.Companion.onStart
@@ -30,8 +30,8 @@ import com.cr.network.TCPDataTask
 import com.cr.pages.FragmentSetIpAndCom
 import com.cr.widget.CrEditTextWidget
 import com.squareup.otto.Subscribe
-import kotlinx.android.synthetic.main.av_login.*
 import org.json.JSONArray
+import kotlinx.android.synthetic.main.av_login.*
 
 class AvLogin : CrActivity(), OnClickListener {
     // define: 2023/3/31 权限列表
@@ -257,37 +257,37 @@ class AvLogin : CrActivity(), OnClickListener {
      */
     private fun init() {
         // todo: 2023/3/31 检查并创建工程文件夹
-        if (!FileManager.isExists(CrUtil.PROJECT_PATH)) {
-            if (!FileManager.createFolder(CrUtil.PROJECT_PATH)) {
+        if (!CrFileManager.isExists(CrUtil.PROJECT_PATH)) {
+            if (!CrFileManager.createFolder(CrUtil.PROJECT_PATH)) {
                 CrUtil.showMessage("工程文件夹创建失败,请检查权限!")
             }
         }
         // todo: 2023/3/31 检查并创建资源文件夹
-        if (!FileManager.isExists(CrUtil.IMAGE_PATH)) {
-            if (!FileManager.createFolder(CrUtil.IMAGE_PATH)) {
+        if (!CrFileManager.isExists(CrUtil.IMAGE_PATH)) {
+            if (!CrFileManager.createFolder(CrUtil.IMAGE_PATH)) {
                 CrUtil.showMessage("资源文件夹创建失败,请检查权限!")
             }
         }
         // todo: 2023/3/31 检查并创建缓存文件夹
-        if (!FileManager.isExists(CrUtil.PROJECT_CACHE_PATH)) {
-            if (!FileManager.createFolder(CrUtil.PROJECT_CACHE_PATH)) {
+        if (!CrFileManager.isExists(CrUtil.PROJECT_CACHE_PATH)) {
+            if (!CrFileManager.createFolder(CrUtil.PROJECT_CACHE_PATH)) {
                 CrUtil.showMessage("缓存文件夹创建失败,请检查权限!")
             }
         }
         // todo: 2023/3/31 检查并创建配置文件夹
-        if (!FileManager.isExists(CrUtil.CONFIG_PATH)) {
-            if (!FileManager.createFolder(CrUtil.CONFIG_PATH)) {
+        if (!CrFileManager.isExists(CrUtil.CONFIG_PATH)) {
+            if (!CrFileManager.createFolder(CrUtil.CONFIG_PATH)) {
                 CrUtil.showMessage("配置文件夹创建失败,请检查权限!")
             }
         }
         // todo: 2023/4/3 拷贝配置库
-        FileManager.MoveDataBase(this)
+        CrFileManager.moveDataBase(this)
         // todo: 2023/4/3 判断数据库是否存在 存在则启动
         var databaseIsOpen: Boolean = DatabaseAppManager.getInstance().openConfigDatabase()
         // todo: 2023/4/3 最终判断是否可以正常使用App
-        if (FileManager.isExists(CrUtil.PROJECT_PATH) && FileManager.isExists(CrUtil.IMAGE_PATH) && FileManager.isExists(
+        if (CrFileManager.isExists(CrUtil.PROJECT_PATH) && CrFileManager.isExists(CrUtil.IMAGE_PATH) && CrFileManager.isExists(
                 CrUtil.PROJECT_CACHE_PATH
-            ) && FileManager.isExists(CrUtil.CONFIG_PATH) && FileManager.isExists(CrUtil.DATABASE_PATH) && databaseIsOpen
+            ) && CrFileManager.isExists(CrUtil.CONFIG_PATH) && CrFileManager.isExists(CrUtil.DATABASE_PATH) && databaseIsOpen
         ) {
             // todo: 2023/4/3 自检通过 可正常使用
             DialogNormal(this, "操作提示", "系统自检通过,可正常使用!").show()

+ 1 - 0
app/src/main/java/com/cr/map/CaseModel.kt

@@ -41,6 +41,7 @@ class CaseModel {
         this.joinPolygon = CaseLocalPolygonModel()
         this.joinNetCaseAJHArray = ArrayList()
         this.imgArray = ArrayList()
+        this.date = CrUnitManager.toSystemYMDHMSDate()
     }
 
     /**

+ 3 - 3
app/src/main/java/com/cr/map/MapTouch.kt

@@ -39,7 +39,7 @@ class MapTouch constructor(context: Context, mapView: MapView) :
         fun onAppendWaypoint(location:Point,longitude: String,latitude: String)
 
         // todo: 2023/6/14 删除案件点
-        fun onRemoveWaypoint(location:Point)
+        fun onRemoveWaypoint(screenPoint: android.graphics.Point)
 
         // todo: 2023/6/14 移动案件点
         fun onMoveWaypointBySelect(location: Point)
@@ -109,8 +109,8 @@ class MapTouch constructor(context: Context, mapView: MapView) :
             }
             // todo: 2023/6/14 删除案件点
             MapAction.MapTapDeleteWaypoint->{
-                var mapPoint = mapView?.screenToLocation(android.graphics.Point(e!!.x.toInt(),e.y.toInt()))
-                if(listener != null) listener?.onRemoveWaypoint(mapPoint!!)
+                var screenPoint = android.graphics.Point(e!!.x.toInt(),e.y.toInt())
+                if(listener != null) listener?.onRemoveWaypoint(screenPoint!!)
                 this.action = MapAction.MapTapNon
             }
             // todo: 2023/6/14 移动案件点

+ 25 - 0
app/src/main/java/com/cr/pages/CrFragment.kt

@@ -5,6 +5,7 @@ import android.os.Handler
 import android.os.Looper
 import android.view.View
 import androidx.fragment.app.Fragment
+import com.cr.dialog.DialogNormal
 
 /**
  * 操作系统:MAC系统
@@ -45,6 +46,30 @@ open class CrFragment :Fragment(){
     }
 
     /**
+     * 显示警告消息
+     * @param warning String 警告消息
+     */
+    protected fun showWarning(warning: String) {
+        DialogNormal(context!!, "警告", warning).show()
+    }
+
+    /**
+     * 显示错误信息
+     * @param error String 错误消息
+     */
+    protected fun showError(error: String) {
+        DialogNormal(context!!, "错误", error).show()
+    }
+
+    /**
+     * 显示提示信息
+     * @param information String 提示消息
+     */
+    protected fun showInformation(information: String) {
+        DialogNormal(context!!, "提示", information).show()
+    }
+
+    /**
      * 获取上下文
      * @return Context?
      */

+ 85 - 48
app/src/main/java/com/cr/pages/FragmentMap.kt

@@ -8,7 +8,7 @@ import android.view.View
 import android.view.ViewGroup
 import com.cr.common.CrUnitManager
 import com.cr.common.DataManager
-import com.cr.common.FileManager
+import com.cr.common.CrFileManager
 import com.cr.cruav.CrApplication
 import com.cr.cruav.R
 import com.cr.data.CrUtil
@@ -341,8 +341,8 @@ class FragmentMap : CrAnimationFragment() {
         }
 
         // todo: 2023/6/14 删除案件点
-        override fun onRemoveWaypoint(location: Point) {
-            caseRemoveCaseWaypoing(location)
+        override fun onRemoveWaypoint(screenPoint: android.graphics.Point) {
+            caseRemoveCaseWaypoing(screenPoint)
         }
 
         // todo: 2023/6/14 选择移动的案件点
@@ -413,7 +413,7 @@ class FragmentMap : CrAnimationFragment() {
      * 添加基础矢量地图集合
      */
     private fun addBaseGeoDatabaseToMap() {
-        if (!FileManager.isExists(CrUtil.MAP_PATH_BASE)) return
+        if (!CrFileManager.isExists(CrUtil.MAP_PATH_BASE)) return
         var geoDatabase = Geodatabase(CrUtil.MAP_PATH_BASE)
         // todo: 2023/4/13 加载完成监听
         geoDatabase.addDoneLoadingListener(Runnable {
@@ -444,7 +444,7 @@ class FragmentMap : CrAnimationFragment() {
      * 加载可编辑数据
      */
     private fun addEditGeoDatabaseToMap() {
-        if (!FileManager.isExists(CrUtil.MAP_PATH_EDIT)) return
+        if (!CrFileManager.isExists(CrUtil.MAP_PATH_EDIT)) return
         var geoDatabase = Geodatabase(CrUtil.MAP_PATH_EDIT)
         // todo: 2023/4/14 数据加载完监听
         geoDatabase.addDoneLoadingListener(Runnable {
@@ -1285,25 +1285,17 @@ class FragmentMap : CrAnimationFragment() {
 
     /**
      * 删除案件点
-     * @param queryPoint Point 查询产靠位置
+     * @param screenPoint android.graphics.Point 查询产靠位置
      */
-    private fun caseRemoveCaseWaypoing(queryPoint: Point) {
-        // todo: 2023/6/14 创建缓冲区
-        var queryGeometry = GeometryEngine.buffer(queryPoint, 10.0)
-        // todo: 2023/6/14 设置查询参数
-        var queryParameters = QueryParameters()
-        queryParameters.spatialRelationship = QueryParameters.SpatialRelationship.CONTAINS
-        queryParameters.geometry = queryGeometry
-        queryParameters.whereClause = "1=1"
+    private fun caseRemoveCaseWaypoing(screenPoint: android.graphics.Point) {
         // todo: 2023/6/14 开始查询
-        var identifyAsync = fTableMedia?.queryFeaturesAsync(queryParameters)
+        var identifyAsync = map_mapView?.identifyLayerAsync(fLayerMedia,screenPoint,6.0,false)
         identifyAsync?.addDoneListener(Runnable {
             try {
                 if (identifyAsync.isDone) {
-                    var queryFeatures = identifyAsync.get()
                     var features = mutableListOf<Feature>()
-                    for (fea in queryFeatures) {
-                        features.add(fea)
+                    for (element in identifyAsync.get().elements) {
+                        features.add(element as Feature)
                     }
                     if (features.size > 0) {
                         var dig = DialogNormal(context!!)
@@ -1313,14 +1305,13 @@ class FragmentMap : CrAnimationFragment() {
                         dig.setListener(object : DialogNormal.DialogNormalListener {
                             // todo: 2023/6/14 确认删除
                             override fun completion() {
-                                var deleteAsync = fTableMedia?.deleteFeaturesAsync(features)
-                                deleteAsync?.addDoneListener(Runnable {
-                                    try {
-                                        if (deleteAsync.isDone) {
-                                            showInformation("案件点删除成功!")
+                                caseRemoveWaypointAndUpdateCasePolygon(features,object:iCompletion{
+                                    override fun onCompletion(result: ResultModel) {
+                                        if(result.isSuccess == true){
+                                            CrUtil.showMessage("删除成功!")
+                                        }else{
+                                            showError(result.message!!)
                                         }
-                                    } catch (ex: java.lang.IllegalArgumentException) {
-                                        showError("案件点删除失败!")
                                     }
                                 })
                             }
@@ -1342,6 +1333,75 @@ class FragmentMap : CrAnimationFragment() {
     }
 
     /**
+     * 删除案件点并更新与之相对应的案件面
+     * @param features List<Feature> 案件点集合
+     * @param callback iCompletion 完成回调
+     */
+    private fun caseRemoveWaypointAndUpdateCasePolygon(features:List<Feature>,callback: iCompletion){
+        var deleteAsync = fTableMedia?.deleteFeaturesAsync(features)
+        deleteAsync?.addDoneListener(Runnable {
+            try {
+                if (deleteAsync.isDone) {
+                    // todo: 2023/6/16 更新
+                    var wheres = mutableListOf<String>()
+                    for(fea in features){
+                        wheres.add(fea.attributes[FIELD_CASE_NAME].toString())
+                    }
+                    caseUpdateCasePolygonByWhere(wheres,callback)
+                }
+            } catch (ex: java.lang.IllegalArgumentException) {
+                if (callback != null) callback.onCompletion(ResultModel(false,"案件点删除失败!"))
+            }
+        })
+    }
+
+    /**
+     * 根据更新条件更新案件点关联的案件面为 未关联案件点状态
+     * @param wheres List<String> 案件Id的集合
+     * @param callback iCompletion 完成回调
+     */
+    private fun caseUpdateCasePolygonByWhere(wheres:List<String>,callback:iCompletion){
+        var updateWhere = ""
+        // todo: 2023/6/16 组合条件
+        for(where in wheres){
+            updateWhere += if(updateWhere == ""){
+                String.format("'%s'",where)
+            }else{
+                String.format(",'%s'",where)
+            }
+        }
+        // todo: 2023/6/16 开始查询
+        var queryParameters = QueryParameters()
+        queryParameters.whereClause = updateWhere
+        var asyncQuery = fTableCasePolygon?.queryFeaturesAsync(queryParameters)
+        asyncQuery?.addDoneListener(Runnable{
+            try {
+                if(asyncQuery.isDone){
+                    var features = mutableListOf<Feature>()
+                    for(fea in asyncQuery.get()){
+                        fea.attributes[FIELD_CASE_POLYGON_ANJID] = ""
+                        fea.attributes[FIELD_CASE_POLYGON_BZ] = FIELD_CASE_POLYGON_BZ_VALUE_NO
+                        features.add(fea)
+                    }
+                    // todo: 2023/6/16 开始更新
+                    var asyncUpdate = fTableCasePolygon?.updateFeaturesAsync(features)
+                    asyncUpdate?.addDoneListener(Runnable {
+                        try {
+                            if(asyncUpdate.isDone){
+                                if(callback != null) callback.onCompletion(ResultModel(true,""))
+                            }
+                        }catch (ex:java.lang.IllegalArgumentException){
+                            if(callback != null) callback.onCompletion(ResultModel(false,"关联案件更新失败!"))
+                        }
+                    })
+                }
+            }catch(ex:java.lang.IllegalArgumentException) {
+                if(callback != null) callback.onCompletion(ResultModel(false,"关联案件查询失败!"))
+            }
+        })
+    }
+
+    /**
      * 移动案件点
      */
     private fun caseMoveWaypoint() {
@@ -1460,29 +1520,6 @@ class FragmentMap : CrAnimationFragment() {
         })
     }
 
-    /**
-     * 显示警告信息
-     * @param warning String 警告消息
-     */
-    private fun showWarning(warning: String) {
-        DialogNormal(context!!, "警告", warning).show()
-    }
-
-    /**
-     * 显示错误信息
-     * @param error String 错误消息
-     */
-    private fun showError(error: String) {
-        DialogNormal(context!!, "错误", error).show()
-    }
-
-    /**
-     * 显示提示信息
-     * @param information String 提示消息
-     */
-    private fun showInformation(information: String) {
-        DialogNormal(context!!, "提示", information).show()
-    }
 
     /**
      * 订阅地图事件执行动作

+ 50 - 1
app/src/main/java/com/cr/pages/FragmentUploadCaseMain.kt

@@ -1,13 +1,21 @@
 package com.cr.pages
 
+import android.app.Activity.RESULT_OK
+import android.content.Intent
+import android.os.Build
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import android.widget.LinearLayout
 import android.widget.TextView
+import com.cr.common.CrPictureManager
+import com.cr.common.CrFileManager
+import com.cr.common.CrUnitManager
 import com.cr.cruav.R
+import com.cr.data.CrUtil
 import com.cr.map.CaseModel
+import com.cr.widget.CrImageBrowserWidget
 
 /**
  * 操作系统:MAC系统
@@ -16,6 +24,10 @@ import com.cr.map.CaseModel
  * 描述:案件上传主页面
  */
 class FragmentUploadCaseMain : CrNavigationFragment(), View.OnClickListener {
+    companion object {
+        const val OPEN_ALBUM = 3  // define: 2023/6/16 相册
+    }
+
     private var lblCaseName: TextView? = null // define: 2023/6/15 案件名称
     private var lblCaseImageCount: TextView? = null // define: 2023/6/15 案件照片数量
     private var lblCaseCreateDate: TextView? = null // define: 2023/6/15 案件创建日期
@@ -24,6 +36,7 @@ class FragmentUploadCaseMain : CrNavigationFragment(), View.OnClickListener {
     private var btnEdit: LinearLayout? = null // define: 2023/6/15 编辑按钮
     private var btnPhoto: LinearLayout? = null // define: 2023/6/15 相册按钮
     private var btnUpload: LinearLayout? = null // define: 2023/6/15 上传按钮
+    private var imageBrowser: CrImageBrowserWidget? = null // define: 2023/6/16 图片浏览控件
 
     private var joinCase: CaseModel? = null // define: 2023/6/15 关联的案件点
 
@@ -69,6 +82,8 @@ class FragmentUploadCaseMain : CrNavigationFragment(), View.OnClickListener {
 
             btnUpload = it.findViewById(R.id.case_btn_upload)
             btnUpload?.setOnClickListener(this)
+
+            imageBrowser = it.findViewById(R.id.case_image_browser)
         }
     }
 
@@ -84,7 +99,10 @@ class FragmentUploadCaseMain : CrNavigationFragment(), View.OnClickListener {
             }
             // todo: 2023/6/15 打开相册
             R.id.case_btn_photo -> {
-
+                Intent(Intent.ACTION_PICK).apply {
+                    type = "image/*"
+                    startActivityForResult(this, OPEN_ALBUM)
+                }
             }
             // todo: 2023/6/15 上传
             R.id.case_btn_upload -> {
@@ -106,4 +124,35 @@ class FragmentUploadCaseMain : CrNavigationFragment(), View.OnClickListener {
         lblCaseMediaName?.text = ""
         lblCaseMediaSize?.text = ""
     }
+
+    /**
+     * 覆盖外部调用返回接受并处理
+     * @param requestCode Int
+     * @param resultCode Int
+     * @param data Intent?
+     */
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        super.onActivityResult(requestCode, resultCode, data)
+        if (requestCode == OPEN_ALBUM && resultCode == RESULT_OK) {
+            var imagePath: String? = null
+            imagePath = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+                CrPictureManager.handleImageAfterKitKat(context!!, data!!)
+            } else {
+                CrPictureManager.handleImageBeforeKitKat(context!!, data!!)
+            }
+            if (imagePath != null) {
+                // todo: 2023/6/16 转存文件
+                var newFilePath =
+                    String.format("%sUAV%s.jpg", CrUtil.IMAGE_PATH, CrUnitManager.toSystemDate())
+                var res = CrFileManager.copyFile(imagePath, newFilePath)
+                if (res) {
+                    imageBrowser?.crAppendImage(newFilePath)
+                } else {
+                    showError("文件转存失败!")
+                }
+            } else {
+                showError("选择文件异常!")
+            }
+        }
+    }
 }

+ 43 - 0
app/src/main/java/com/cr/widget/CrConstraintLayoutWidget.kt

@@ -0,0 +1,43 @@
+package com.cr.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import androidx.constraintlayout.widget.ConstraintLayout
+
+/**
+ * 操作系统:MAC系统
+ * 创建者:王成
+ * 创建日期:2023/6/16 13:46
+ * 描述:限制布局基础类
+ */
+open class CrConstraintLayoutWidget@JvmOverloads constructor(
+    context:Context,
+    attrs: AttributeSet?=null,
+    defStyleAttr: Int = 0
+):ConstraintLayout(context,attrs,defStyleAttr) {
+    // define: 2023/4/14 主视图
+    protected var mainView:View ?= null
+    /**
+     * 初始化视图
+     * @param layoutId Int 视图xml文件Id
+     */
+    fun initView(layoutId:Int){
+        mainView = View.inflate(context,layoutId,this);
+    }
+
+    /**
+     * 链接控件
+     */
+    open fun joinControls(){
+
+    }
+
+    /**
+     * 初始化属性
+     * @param attrs AttributeSet 属性管理器
+     */
+    open fun initProperty(attrs: AttributeSet){
+
+    }
+}

+ 123 - 0
app/src/main/java/com/cr/widget/CrImageBrowserWidget.kt

@@ -0,0 +1,123 @@
+package com.cr.widget
+
+import android.content.Context
+import android.graphics.BitmapFactory
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import androidx.recyclerview.widget.RecyclerView
+import androidx.viewpager2.widget.ViewPager2
+import com.cr.cruav.R
+import com.cr.data.CrUtil
+import okhttp3.internal.notify
+import java.io.FileInputStream
+import java.io.FileNotFoundException
+
+/**
+ * 操作系统:MAC系统
+ * 创建者:王成
+ * 创建日期:2023/6/16 16:19
+ * 描述:图片浏览器
+ */
+class CrImageBrowserWidget@JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet?=null,
+    defStyleAttr: Int = 0
+) :CrConstraintLayoutWidget(context,attrs,defStyleAttr){
+    private var viewPager:ViewPager2?= null // define: 2023/6/16 页面滑动组件
+    private var dataList:MutableList<String> = mutableListOf() // define: 2023/6/16 数据集合
+    private var adapter:BaseAdapter?= null // define: 2023/6/16 适配器
+
+    /**
+     * 初始化
+     */
+    init {
+        initView(R.layout.wg_image_browser)
+        joinControls()
+    }
+
+    /**
+     * 挂载控件
+     */
+    override fun joinControls() {
+        super.joinControls()
+        // todo: 2023/6/16 挂载视图控件
+        viewPager = findViewById(R.id.wg_pager)
+        // todo: 2023/6/16 设置适配器
+        adapter = BaseAdapter(dataList)
+        viewPager?.adapter = adapter
+    }
+
+    /**
+     * 适配器
+     * @property dataList List<String>?
+     * @constructor
+     */
+    internal class BaseAdapter(dataList:List<String>):RecyclerView.Adapter<BaseAdapter.BaseViewHolder?>(){
+        var dataList:List<String>? = null // define: 2023/6/16 数据集合
+
+        // todo: 2023/6/16 创建视图
+        override fun onCreateViewHolder(
+            parent: ViewGroup,
+            viewType: Int
+        ): BaseAdapter.BaseViewHolder {
+            var itemView:View = LayoutInflater.from(parent.context).inflate(R.layout.item_image,parent,false)
+            return BaseViewHolder(itemView)
+        }
+
+        // todo: 2023/6/16 绑定视图
+        override fun onBindViewHolder(holder: BaseAdapter.BaseViewHolder, position: Int) {
+            var imagePath = this.dataList!![position]
+            try {
+                var fis = FileInputStream(imagePath)
+                var bitmap = BitmapFactory.decodeStream(fis)
+                holder.imageView?.setImageBitmap(bitmap)
+            }catch (ex:FileNotFoundException){
+                CrUtil.print("加载图片错误:" + ex.message)
+            }
+        }
+
+        // todo: 2023/6/16 返回视图数量
+        override fun getItemCount(): Int {
+            return dataList!!.size
+        }
+
+        inner class BaseViewHolder(itemView: View):RecyclerView.ViewHolder(itemView){
+            var imageView:ImageView?= null  // define: 2023/6/16 图片展示控件
+
+            /**
+             * 初始化
+             */
+            init {
+                imageView = itemView.findViewById(R.id.item_img_image)
+            }
+        }
+
+        /**
+         * 初始化
+         */
+        init {
+            this.dataList = dataList
+        }
+
+        /**
+         * 更新数据
+         * @param dataList List<String>
+         */
+        fun update(dataList: List<String>){
+            this.dataList = dataList
+            notifyDataSetChanged()
+        }
+    }
+
+    /**
+     * 添加图片
+     * @param imagePath String 图片路径
+     */
+    fun crAppendImage(imagePath:String){
+        this.dataList.add(imagePath)
+        adapter?.update(this.dataList)
+    }
+}

+ 5 - 0
app/src/main/res/layout/frag_case_upload_main.xml

@@ -102,4 +102,9 @@
         </LinearLayout>
     </LinearLayout>
     <View style="@style/view_split_h1"/>
+    <com.cr.widget.CrImageBrowserWidget
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:id="@+id/case_image_browser"/>
 </LinearLayout>

+ 10 - 0
app/src/main/res/layout/item_image.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <ImageView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@+id/item_img_image"/>
+</LinearLayout>

+ 26 - 0
app/src/main/res/layout/wg_image_browser.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <androidx.viewpager2.widget.ViewPager2
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@+id/wg_pager"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent"/>
+    <androidx.constraintlayout.widget.Guideline
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/wg_line"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_percent="0.95"/>
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintTop_toBottomOf="@id/wg_line"
+        app:layout_constraintRight_toRightOf="parent"
+        android:id="@+id/wg_indicator"/>
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 11 - 0
app/src/main/res/xml/filepath.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<paths>
+    <!--拍照存储路径-->
+    <external-path
+        name="pocket"
+        path="pocket/picture/" />
+    <!--访问相册路径-->
+    <external-path
+        name="external"
+        path="." />
+</paths>