Просмотр исходного кода

refactor(更新) :
- 开关状态改为Activity
- 虹软改为增值版

周文健 3 месяцев назад
Родитель
Сommit
1ebcf205da

+ 2 - 1
app/build.gradle

@@ -140,5 +140,6 @@ dependencies {
 
     implementation 'io.github.razerdp:BasePopup:3.2.1'
 
-    implementation 'com.github.SilverIceKey.SIKExtension:SIKCore:1.1.46'
+    implementation 'com.github.SilverIceKey.SIKExtension:SIKCore:1.1.56'
+    implementation 'com.github.SilverIceKey.SIKExtension:SIKAndroid:1.1.56'
 }

BIN
app/libs/arcsoft_face.jar


+ 6 - 3
app/src/main/AndroidManifest.xml

@@ -32,11 +32,11 @@
         android:fullBackupContent="@xml/backup_rules"
         android:icon="@mipmap/logo"
         android:label="@string/app_name"
+        android:largeHeap="true"
         android:networkSecurityConfig="@xml/common_network_config"
         android:roundIcon="@mipmap/logo"
         android:supportsRtl="true"
         android:theme="@style/Theme.ISCS"
-        android:largeHeap="true"
         tools:targetApi="31">
         <activity
             android:name=".view.activity.test.face.arcsoft.ArcsoftTestActivity"
@@ -51,6 +51,10 @@
             android:name=".view.activity.HomeActivity"
             android:exported="false"
             android:windowSoftInputMode="adjustPan|adjustResize" />
+        <activity
+            android:name=".view.activity.SwitchStatusActivity"
+            android:exported="false"
+            android:windowSoftInputMode="adjustPan|adjustResize" />
         <activity
             android:name=".view.activity.LoginActivity"
             android:exported="true"
@@ -104,8 +108,7 @@
             android:exported="false" />
         <activity
             android:name=".view.activity.MainActivity"
-            android:exported="true">
-        </activity>
+            android:exported="true"></activity>
 
         <receiver
             android:name=".receivers.BootReceiver"

+ 2 - 4
app/src/main/java/com/grkj/iscs_mars/MyApplication.kt

@@ -26,6 +26,7 @@ class MyApplication : Application() {
     override fun onCreate() {
         super.onCreate()
         instance = this
+        SIKCore.init(this)
 //        LogUtil.init(instance!!, FileUtil.ROOT_APP + FileUtil.LOG_DIR)
         // 路径:sdcard/Android/data/com.grkj.iscs/files/iscs/log
         LogUtil.init(this, "${FileUtil.getRootFolder(this)?.absolutePath}$LOG_DIR")
@@ -34,12 +35,9 @@ class MyApplication : Application() {
         NetHttpManager.getInstance().initCtx(this)
 
         BusinessManager.initMsgEventBus()
-        //todo 模拟器用不了
-        ArcSoftUtil.checkActiveStatus(this)
 
-        SIKCore.init(this)
+        ArcSoftUtil.checkActiveStatus(this)
 
-        SIKCore.init(this)
 
         NetApi.logout()
         SPUtils.clearLoginUser(this)

+ 1 - 1
app/src/main/java/com/grkj/iscs_mars/ble/BleConnectionManager.kt

@@ -25,7 +25,7 @@ import com.grkj.iscs_mars.util.CommonUtils
 import com.grkj.iscs_mars.util.Executor
 import com.grkj.iscs_mars.util.log.LogUtil
 import com.grkj.iscs_mars.view.base.BaseActivity
-import com.sik.sikcore.activity.ActivityTracker
+import com.sik.sikandroid.activity.ActivityTracker
 import com.sik.sikcore.thread.ThreadUtils
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay

+ 19 - 0
app/src/main/java/com/grkj/iscs_mars/model/ArcSoftLicenseConfig.kt

@@ -0,0 +1,19 @@
+package com.grkj.iscs_mars.model
+
+/**
+ * 虹软授权配置
+ */
+data class ArcSoftLicenseConfig(
+    /**
+     * appId
+     */
+    val appId: String,
+    /**
+     * sdk秘钥
+     */
+    val sdkKey: String,
+    /**
+     * 激活码
+     */
+    val activeKey: String
+)

+ 1 - 0
app/src/main/java/com/grkj/iscs_mars/model/Constants.kt

@@ -85,6 +85,7 @@ object Constants {
     /*************************  虹软ArcSoft  *************************/
     const val APP_ID = "FTN3G4pk8n2RKwjD955sRapRjbYQFefwhHd4sBZMYEz6"
     const val SDK_KEY = "BjJomNU2bQc2SYhT7NNqwvFd3WD4wTqecifNcDRuKD5G"
+    const val ACTIVE_KEY = "8551-11XJ-913K-J1CN"
 
     /*************************  作业票类型  *************************/
     data class TicketType(val type: Int, val key: String)

+ 35 - 9
app/src/main/java/com/grkj/iscs_mars/util/ArcSoftUtil.kt

@@ -20,16 +20,20 @@ import com.arcsoft.face.enums.DetectFaceOrientPriority
 import com.arcsoft.face.enums.DetectMode
 import com.grkj.iscs_mars.R
 import com.grkj.iscs_mars.extentions.isInCenterArea
+import com.grkj.iscs_mars.model.ArcSoftLicenseConfig
 import com.grkj.iscs_mars.model.Constants
 import com.grkj.iscs_mars.util.log.LogUtil
 import com.grkj.iscs_mars.view.activity.test.face.arcsoft.CameraHelper
 import com.grkj.iscs_mars.view.activity.test.face.arcsoft.CameraListener
 import com.grkj.iscs_mars.view.activity.test.face.arcsoft.NV21ToBitmap
 import com.grkj.iscs_mars.view.widget.FaceOverlayView
-import com.sik.sikcore.thread.ThreadUtils
-import kotlinx.coroutines.Runnable
+import com.sik.sikcore.extension.file
+import com.sik.sikcore.extension.toJson
+import com.sik.sikcore.file.FileStorageUtils
+import java.io.File
 
 object ArcSoftUtil {
+    private val configFileName = "ArcSoftConfig.json"
     private var cameraHelper: CameraHelper? = null
     private var previewSize: Camera.Size? = null
     private val rgbCameraId = Camera.CameraInfo.CAMERA_FACING_BACK
@@ -38,7 +42,7 @@ object ArcSoftUtil {
     private val cameraHeight: Int = 480
     private var afCode = -1
     private val processMask: Int =
-        FaceEngine.ASF_AGE or FaceEngine.ASF_FACE3DANGLE or FaceEngine.ASF_GENDER or FaceEngine.ASF_LIVENESS
+        FaceEngine.ASF_AGE or FaceEngine.ASF_GENDER or FaceEngine.ASF_LIVENESS
     private var startDetectFaceTime = 0L
     private val faceNotDetectTip: () -> Unit = {
         ToastUtils.tip(CommonUtils.getStr(R.string.not_detect_face))
@@ -56,7 +60,31 @@ object ArcSoftUtil {
     )
 
     fun checkActiveStatus(context: Context) {
-        val activeCode = FaceEngine.activeOnline(context, Constants.APP_ID, Constants.SDK_KEY)
+        val arcSoftLicenseFile =
+            "${FileUtil.getRootFolder(context)?.absolutePath}${FileUtil.CONFIG_DIR}${File.separator}${configFileName}".file()
+        val configJson = if (arcSoftLicenseFile.exists()) {
+            arcSoftLicenseFile.readText()
+        } else {
+            val defaultJson = ArcSoftLicenseConfig(
+                Constants.APP_ID,
+                Constants.SDK_KEY,
+                Constants.ACTIVE_KEY
+            ).toJson()
+            FileStorageUtils.writeText(
+                "${FileUtil.getRootFolder(context)?.absolutePath}${FileUtil.CONFIG_DIR}",
+                "$configFileName",
+                defaultJson
+            )
+            defaultJson
+        }
+        val arcSoftLicenseConfig: ArcSoftLicenseConfig =
+            gson.fromJson(configJson, ArcSoftLicenseConfig::class.java)
+        val activeCode = FaceEngine.activeOnline(
+            context,
+            arcSoftLicenseConfig.activeKey,
+            arcSoftLicenseConfig.appId,
+            arcSoftLicenseConfig.sdkKey
+        )
         when (activeCode) {
             ErrorInfo.MOK -> {
                 isActivated = true
@@ -87,9 +115,8 @@ object ArcSoftUtil {
             context,
             DetectMode.ASF_DETECT_MODE_VIDEO,
             DetectFaceOrientPriority.ASF_OP_0_ONLY,
-            16,
             1,
-            FaceEngine.ASF_FACE_DETECT or FaceEngine.ASF_AGE or FaceEngine.ASF_FACE3DANGLE or FaceEngine.ASF_GENDER or FaceEngine.ASF_LIVENESS or FaceEngine.ASF_FACE_RECOGNITION
+            FaceEngine.ASF_FACE_DETECT or FaceEngine.ASF_AGE or FaceEngine.ASF_GENDER or FaceEngine.ASF_LIVENESS or FaceEngine.ASF_FACE_RECOGNITION
         )
         LogUtil.i("initEngine:  init: $afCode")
         if (afCode != ErrorInfo.MOK) {
@@ -173,12 +200,11 @@ object ArcSoftUtil {
                     val faceLivenessInfoList: List<LivenessInfo> = ArrayList()
                     val ageCode = faceEngine!!.getAge(ageInfoList)
                     val genderCode = faceEngine!!.getGender(genderInfoList)
-                    val face3DAngleCode = faceEngine!!.getFace3DAngle(face3DAngleList)
                     val livenessCode = faceEngine!!.getLiveness(faceLivenessInfoList)
 
                     // 有其中一个的错误码不为ErrorInfo.MOK,return
-                    if ((ageCode or genderCode or face3DAngleCode or livenessCode) != ErrorInfo.MOK) {
-                        LogUtil.d("人脸检测结果:年龄、性别、角度、获取验证失败")
+                    if ((ageCode or genderCode or livenessCode) != ErrorInfo.MOK) {
+                        LogUtil.d("人脸检测结果:年龄、性别、角度、活体验证失败")
                         return
                     }
 

+ 1 - 0
app/src/main/java/com/grkj/iscs_mars/util/FileUtil.kt

@@ -18,6 +18,7 @@ object FileUtil {
     val DOWNLOAD_DIR = "${separator}download"
     val LOG_DIR = "${separator}log"
     val CRASH_DIR = "${separator}crash"
+    val CONFIG_DIR = "${separator}config"
 
     /**
      * @param permissionType:0:默认(根目录>私有可见文件缓存);1:私有可见缓存;2:私有可见文件;3:私有缓存;4:私有文件

+ 19 - 9
app/src/main/java/com/grkj/iscs_mars/view/activity/HomeActivity.kt

@@ -1,5 +1,6 @@
 package com.grkj.iscs_mars.view.activity
 
+import android.content.Intent
 import android.view.InputDevice
 import android.view.KeyEvent
 import android.widget.ImageView
@@ -104,9 +105,6 @@ class HomeActivity : BaseMvpActivity<IHomeView, HomePresenter, ActivityHomeBindi
                 )
             }
         }
-//        mMenuList.add(Menu(getString(R.string.test), R.mipmap.menu_icon_test, DockTestFragment()))
-//        mMenuList.add(Menu(getString(R.string.system_setting), R.mipmap.menu_icon_sys_setting, SystemSettingFragment()))
-
         mMenuList.add(
             Menu(
                 getString(R.string.device_status),
@@ -117,8 +115,7 @@ class HomeActivity : BaseMvpActivity<IHomeView, HomePresenter, ActivityHomeBindi
         mMenuList.add(
             Menu(
                 getString(R.string.switch_status),
-                R.mipmap.menu_icon_device_status,
-                SwitchStatusFragment()
+                R.mipmap.menu_icon_device_status
             )
         )
         mMenuList.add(
@@ -152,9 +149,18 @@ class HomeActivity : BaseMvpActivity<IHomeView, HomePresenter, ActivityHomeBindi
                     holder.setText(R.id.tv_name, data.title)
                     holder.getView<ImageView>(R.id.iv_icon).setImageResource(data.icon!!)
                     holder.setOnClickListener(R.id.root) {
-                        mBinding?.itemSetting?.root?.setBackgroundColor(0)
-                        mBinding?.vp?.currentItem = position
-                        notifyDataSetChanged()
+                        if (data.title == getString(R.string.switch_status)) {
+                            startActivity(
+                                Intent(
+                                    this@HomeActivity,
+                                    SwitchStatusActivity::class.java
+                                )
+                            )
+                        } else {
+                            mBinding?.itemSetting?.root?.setBackgroundColor(0)
+                            mBinding?.vp?.currentItem = position
+                            notifyDataSetChanged()
+                        }
                     }
                     holder.setBackgroundColor(
                         R.id.root,
@@ -215,5 +221,9 @@ class HomeActivity : BaseMvpActivity<IHomeView, HomePresenter, ActivityHomeBindi
         return HomePresenter()
     }
 
-    data class Menu(val title: String? = null, val icon: Int? = null, val fragment: BaseFragment<*>)
+    data class Menu(
+        val title: String? = null,
+        val icon: Int? = null,
+        val fragment: BaseFragment<*>? = null
+    )
 }

+ 109 - 0
app/src/main/java/com/grkj/iscs_mars/view/activity/SwitchStatusActivity.kt

@@ -0,0 +1,109 @@
+package com.grkj.iscs_mars.view.activity
+
+import android.view.GestureDetector
+import android.view.MotionEvent
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.grkj.iscs_mars.BusinessManager
+import com.grkj.iscs_mars.databinding.ActivitySwitchStatusBinding
+import com.grkj.iscs_mars.util.ToastUtils
+import com.grkj.iscs_mars.view.base.BaseMvpActivity
+import com.grkj.iscs_mars.view.iview.ISwitchStatusView
+import com.grkj.iscs_mars.view.presenter.SwitchStatusPresenter
+import com.grkj.iscs_mars.view.widget.CustomSwitchStationLayer
+import com.onlylemi.mapview.library.MapViewListener
+import com.sik.sikcore.extension.setDebouncedClickListener
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+class SwitchStatusActivity :
+    BaseMvpActivity<ISwitchStatusView, SwitchStatusPresenter, ActivitySwitchStatusBinding>() {
+    private var stationLayer: CustomSwitchStationLayer? = null
+    private lateinit var gestureDetector: GestureDetector
+
+    override fun initPresenter(): SwitchStatusPresenter {
+        return SwitchStatusPresenter()
+    }
+
+    override val viewBinding: ActivitySwitchStatusBinding
+        get() = ActivitySwitchStatusBinding.inflate(layoutInflater)
+
+    override fun initView() {
+        mBinding?.cbBack?.setDebouncedClickListener {
+            finish()
+        }
+        gestureDetector = GestureDetector(this, object :
+            GestureDetector.SimpleOnGestureListener() {
+            override fun onDoubleTap(e: MotionEvent): Boolean {
+                mBinding?.mapview?.currentRotateDegrees = 0f
+                return super.onDoubleTap(e)
+            }
+
+            override fun onFling(
+                e1: MotionEvent?,
+                e2: MotionEvent,
+                velocityX: Float,
+                velocityY: Float
+            ): Boolean {
+                mBinding?.mapview?.currentRotateDegrees = 0f
+                return super.onFling(e1, e2, velocityX, velocityY)
+            }
+        })
+        initMap()
+        lifecycleScope.launch {
+            repeatOnLifecycle(Lifecycle.State.RESUMED) {
+                delay(1000)
+                BusinessManager.updateSwitchStatus {}
+            }
+
+        }
+    }
+
+    override fun onResume() {
+        super.onResume()
+        presenter?.getMapData {
+            getMap(it)
+        }
+    }
+
+    private fun getMap(mapId: String) {
+        presenter?.getMapInfo(mapId.toLong()) { itMapInfo ->
+            presenter?.mapDataHandle(this, itMapInfo, stationLayer) { mapBmp ->
+                mBinding?.mapview?.loadMap(mapBmp)
+            }
+        }
+    }
+
+    /**
+     * 初始化地图
+     */
+    private fun initMap() {
+        mBinding?.mapview?.isScaleAndRotateTogether = false
+        mBinding?.mapview?.setOnTouchListener { _, event ->
+            gestureDetector.onTouchEvent(event)
+            false
+        }
+        mBinding?.mapview?.setMapViewListener(object : MapViewListener {
+            override fun onMapLoadSuccess() {
+                if (stationLayer != null) {
+                    mBinding?.mapview?.currentRotateDegrees = 0f
+                    return
+                }
+                stationLayer = CustomSwitchStationLayer(
+                    mBinding?.mapview,
+                    presenter?.mStationList ?: mutableListOf()
+                )
+                mBinding?.mapview?.addLayer(stationLayer)
+                stationLayer?.setRatio(presenter?.mapRatio ?: 1f)
+                stationLayer?.stopAnimation()
+                stationLayer?.startAnimation()
+                mBinding?.mapview?.refresh()
+            }
+
+            override fun onMapLoadFail() {
+                ToastUtils.tip("onMapLoadFail")
+            }
+        })
+    }
+}

+ 2 - 1
app/src/main/java/com/grkj/iscs_mars/view/adapter/MenuAdapter.kt

@@ -5,6 +5,7 @@ import androidx.fragment.app.FragmentManager
 import androidx.lifecycle.Lifecycle
 import androidx.viewpager2.adapter.FragmentStateAdapter
 import com.grkj.iscs_mars.view.activity.HomeActivity.Menu
+import com.grkj.iscs_mars.view.fragment.EmptyFragment
 
 /**
  * 首页功能菜单适配器
@@ -20,6 +21,6 @@ class MenuAdapter(
     }
 
     override fun createFragment(position: Int): Fragment {
-        return fragmentList[position].fragment
+        return fragmentList[position].fragment ?: EmptyFragment()
     }
 }

+ 18 - 0
app/src/main/java/com/grkj/iscs_mars/view/fragment/EmptyFragment.kt

@@ -0,0 +1,18 @@
+package com.grkj.iscs_mars.view.fragment
+
+import com.grkj.iscs_mars.databinding.FragmentEmptyBinding
+import com.grkj.iscs_mars.view.base.BaseFragment
+import com.grkj.iscs_mars.view.base.BaseMvpFragment
+
+/**
+ * 空白界面
+ */
+class EmptyFragment : BaseFragment<FragmentEmptyBinding>() {
+    override val viewBinding: FragmentEmptyBinding
+        get() = FragmentEmptyBinding.inflate(layoutInflater)
+
+    override fun initView() {
+
+    }
+
+}

+ 1 - 1
app/src/main/java/com/grkj/iscs_mars/view/fragment/JobExecutionFragment.kt

@@ -65,6 +65,6 @@ class JobExecutionFragment(val changePageCallback: (PageChangeBO) -> Unit) :
             return
         }
         mBinding?.vp?.currentItem = pageChangeBO.pageIdx
-        mMenuList[pageChangeBO.pageIdx].fragment.refreshPage(pageChangeBO)
+        mMenuList[pageChangeBO.pageIdx].fragment?.refreshPage(pageChangeBO)
     }
 }

+ 83 - 0
app/src/main/java/com/grkj/iscs_mars/view/presenter/SwitchStatusPresenter.kt

@@ -1,14 +1,30 @@
 package com.grkj.iscs_mars.view.presenter
 
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.PointF
 import com.grkj.iscs_mars.MyApplication
 import com.grkj.iscs_mars.extentions.serialNo
 import com.grkj.iscs_mars.model.vo.map.MapInfoRespVO
+import com.grkj.iscs_mars.util.BitmapUtil
 import com.grkj.iscs_mars.util.Executor
 import com.grkj.iscs_mars.util.NetApi
+import com.grkj.iscs_mars.util.log.LogUtil
 import com.grkj.iscs_mars.view.base.BasePresenter
 import com.grkj.iscs_mars.view.iview.ISwitchStatusView
+import com.grkj.iscs_mars.view.widget.CustomSwitchStationLayer
 
 class SwitchStatusPresenter : BasePresenter<ISwitchStatusView>() {
+    /**
+     * 地图缩放
+     */
+    var mapRatio: Float = 1f
+
+    /**
+     * 隔离点列表
+     */
+    val mStationList = mutableListOf<CustomSwitchStationLayer.IsolationPoint>()
+
     /**
      * 地图id
      */
@@ -20,6 +36,73 @@ class SwitchStatusPresenter : BasePresenter<ISwitchStatusView>() {
         }
     }
 
+    /**
+     * 地图数据处理
+     */
+    fun mapDataHandle(
+        context: Context,
+        itMapInfo: MapInfoRespVO?,
+        stationLayer: CustomSwitchStationLayer?,
+        callback: (Bitmap) -> Unit
+    ){
+        // 如果没有图 URL,直接返回
+        val imageUrl = itMapInfo?.imageUrl ?: return
+
+        BitmapUtil.loadBitmapFromUrl(context, imageUrl) { mapBmp ->
+            if (mapBmp == null) {
+                LogUtil.e("Map pic is null")
+                return@loadBitmapFromUrl
+            }
+            // 清空旧点
+            mStationList.clear()
+            // 1 格 对应的像素
+            val cellPx = 50f
+            // 后端给的“逻辑”子图原始尺寸(像素)
+            val backendW = itMapInfo.width!!.toFloat()
+            val backendH = itMapInfo.height!!.toFloat()
+            // 实际下载回来的 Bitmap 尺寸(像素)
+            val actualW = mapBmp.width.toFloat()
+            val actualH = mapBmp.height.toFloat()
+            // 计算缩放比例
+            val ratioX = actualW / backendW
+            val ratioY = actualH / backendH
+            mapRatio = ratioX
+            // 子图在全局坐标系里的左上角偏移(像素)
+            val offsetX = itMapInfo.x!!.toFloat()
+            val offsetY = itMapInfo.y!!.toFloat()
+            itMapInfo.pointList?.filter { it.x != null && it.y != null }?.forEach { pt ->
+                // 1) 格数 → 全局像素
+                val globalX = pt.x!!.toFloat() * cellPx
+                val globalY = pt.y!!.toFloat() * cellPx
+                // 2) 全局像素 - 子图偏移 = 子图内像素
+                val localX = globalX - offsetX
+                val localY = globalY - offsetY
+                // 3) 再乘缩放比,得到真实 Bitmap 上的像素坐标
+                val finalX = localX * ratioX
+                val finalY = localY * ratioY
+                // 异步加载点位图标,固定请求尺寸
+                mStationList.add(
+                    CustomSwitchStationLayer.IsolationPoint(
+                        PointF(finalX, finalY),
+                        pt.entityName!!,
+                        null,
+                        pt.entityId!!.toLong(),
+                        pt.pointSerialNumber,
+                        false
+                    )
+                )
+
+                // 全部点都加载完后,设置给 layer 并绘制
+                if (mStationList.size == itMapInfo.pointList.count { it.x != null && it.y != null }) {
+                    if (stationLayer?.inDraw == true) {
+                        return@loadBitmapFromUrl
+                    }
+                    callback(mapBmp)
+                }
+            }
+        }
+    }
+
     fun getMapData(done: (String) -> Unit) {
         NetApi.getIsLotoStationPage {
             done(

BIN
app/src/main/jniLibs/arm64-v8a/libarcsoft_face.so


BIN
app/src/main/jniLibs/arm64-v8a/libarcsoft_face_engine.so


BIN
app/src/main/jniLibs/armeabi-v7a/libarcsoft_face.so


BIN
app/src/main/jniLibs/armeabi-v7a/libarcsoft_face_engine.so


+ 42 - 0
app/src/main/res/layout/activity_switch_status.xml

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout 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"
+    android:background="@mipmap/main_bg"
+    android:divider="@drawable/divider_horizontal"
+    android:orientation="vertical"
+    android:padding="@dimen/common_spacing"
+    android:showDividers="middle">
+
+    <com.grkj.iscs_mars.view.widget.CommonBtn
+        android:id="@+id/cb_back"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_above="@id/tv_tip"
+        android:layout_alignParentRight="true"
+        android:layout_marginBottom="@dimen/common_spacing_small"
+        app:btn_bg="@drawable/common_btn_blue_bg"
+        app:btn_name="@string/back" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:divider="@drawable/divider_horizontal"
+        android:orientation="horizontal"
+        android:showDividers="middle">
+
+        <com.onlylemi.mapview.library.MapView
+            android:id="@+id/mapview"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="2" />
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/rv_list"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1" />
+    </LinearLayout>
+</LinearLayout>

+ 4 - 0
app/src/main/res/layout/fragment_empty.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" />