package com.lcw.routel.ui.patrol
import android.Manifest
import android.annotation.TargetApi
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context.NOTIFICATION_SERVICE
import android.content.Intent
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.addCallback
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import com.amap.api.location.AMapLocationClient
import com.amap.api.location.AMapLocationClientOption
import com.amap.api.location.AMapLocationClientOption.AMapLocationMode
import com.amap.api.location.AMapLocationClientOption.AMapLocationProtocol
import com.amap.api.maps.AMap
import com.amap.api.maps.AMap.CancelableCallback
import com.amap.api.maps.CameraUpdateFactory
import com.amap.api.maps.Projection
import com.amap.api.maps.TextureMapView
import com.amap.api.maps.model.*
import com.amap.api.track.AMapTrackClient
import com.amap.api.track.ErrorCode
import com.amap.api.track.OnTrackLifecycleListener
import com.amap.api.track.TrackParam
import com.amap.api.track.query.model.*
import com.hjq.permissions.OnPermissionCallback
import com.hjq.permissions.XXPermissions
import com.lcw.routel.App
import com.lcw.routel.MainActivity
import com.lcw.routel.R
import com.lcw.routel.base.BaseFragment
import com.lcw.routel.base.Constants
import com.lcw.routel.databinding.FragmentPatrolBinding
import com.lcw.routel.entity.LocationEntity
import com.lcw.routel.entity.PostPatrolRecordEntity
import com.lcw.routel.entity.PostPatrolRecordStatusEntity
import com.lcw.routel.net.easyhttp.http.EasyLog
import com.lcw.routel.util.*
import com.lcw.routel.widget.dialog.RequestDialog
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
/**
* desc: 一键巡查
*
* create by lcz on 2021-12-14
*/
@AndroidEntryPoint
class PatrolFragment : BaseFragment(), PatrolActionHandler {
@Inject
lateinit var prefs: PreferenceStorage
private lateinit var binding: FragmentPatrolBinding
private val mViewModel: PatrolViewModel by lazy {
ViewModelProvider(this)[PatrolViewModel::class.java]
}
private var mAMap: AMap? = null
private var mMapView: TextureMapView? = null
private var postEntity: PostPatrolRecordEntity = PostPatrolRecordEntity()
private var postStatusEntity: PostPatrolRecordStatusEntity = PostPatrolRecordStatusEntity()
private var singleLocationClient: AMapLocationClient? = null//单次定位
private var continueLocationClient: AMapLocationClient? = null//连续定位
private var singleLocationOption: AMapLocationClientOption? = null
private var continueLocationOption: AMapLocationClientOption? = null
private var locationMarker: Marker? = null //自定义定位小蓝点的Marker
private var projection: Projection? = null//坐标和经纬度转换工具
private var useMoveToLocationWithMapMode = true
private var myCancelCallback: MyCancelCallback = MyCancelCallback()
private var locationAddress = ""
private lateinit var locationLatLng: LatLng
private var latitude = 0.0
private var longitude = 0.0
private var isServiceRunning = false//viewModel 暂存
private var aMapTrackClient: AMapTrackClient? = null
private var terminalId: Long = 0
private var trackId: Long = 0
private val needPermissions = arrayOf(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
)
override fun initContentView(inflater: LayoutInflater, container: ViewGroup?): View {
binding = FragmentPatrolBinding.inflate(inflater, container, false).apply {
lifecycleOwner = viewLifecycleOwner
viewModel = mViewModel
}
binding.actionHandler = this
mMapView = binding.mMapView
return binding.root
}
override fun process(savedInstanceState: Bundle?) {
// 不要使用Activity作为Context传入
aMapTrackClient = AMapTrackClient(App.appContext)
aMapTrackClient?.setInterval(4, 8)
mMapView?.onCreate(savedInstanceState)
if (mAMap == null) {
mAMap = mMapView?.map
setUpMap()
}
initLocation()
XXPermissions.with(this).permission(needPermissions)
.request(object : OnPermissionCallback {
override fun onGranted(permissions: MutableList?, all: Boolean) {
if (all) {
// startSingleLocation()
// 启用地图内置定位
mAMap?.moveCamera(CameraUpdateFactory.zoomTo(17f))
mAMap?.isMyLocationEnabled = true
mAMap?.myLocationStyle = MyLocationStyle().interval(3000)
.myLocationIcon(BitmapDescriptorFactory.fromResource(R.mipmap.location))
.myLocationType(MyLocationStyle.LOCATION_TYPE_FOLLOW)
.strokeColor(Color.argb(0, 0, 0, 0))
.radiusFillColor(Color.argb(0, 0, 0, 0))
mAMap?.addOnMyLocationChangeListener {
val location = GsonUtil.json2Bean(
GsonUtil.bean2Json(it),
LocationEntity::class.java
)
latitude = location.latitude!! //纬 度
longitude = location.longitude!!//经 度
locationAddress = location.address.toString()
locationLatLng = LatLng(latitude, longitude)
EasyLog.print("经度---$longitude 纬度---$latitude 地址---$locationAddress")
// if (location.errorCode == 0) {
// EasyLog.print("定位成功")
// latitude = location.latitude//纬 度
// longitude = location.longitude//经 度
// locationAddress = location.address
// EasyLog.print("经 度---" + longitude + " 纬 度---" + latitude + " 地 址---" + location.address)
// locationLatLng = LatLng(latitude, longitude)
// if (locationMarker == null) {
// locationMarker = mAMap?.addMarker(
// MarkerOptions()
// .position(locationLatLng)
// .icon(BitmapDescriptorFactory.fromResource(R.mipmap.location))
// .anchor(0.5f, 0.5f)
// )
// mAMap?.moveCamera(
// CameraUpdateFactory.newLatLngZoom(
// locationLatLng,
// 17f
// )
// )
// } else {
// if (useMoveToLocationWithMapMode) {
// startMoveLocationAndMap(locationLatLng)
// } else {
// startChangeLocation(locationLatLng)
// }
// }
// } else {
// EasyLog.print("定位失败---" + location.errorCode + " 错误信息---" + location.errorInfo + " 错误描述---" + location.locationDetail)
// }
}
} else {
"部分权限获取失败".showToast()
}
}
override fun onDenied(permissions: MutableList?, never: Boolean) {
if (never) {
"被永久拒绝授权,请手动授予权限".showToast()
} else {
"权限获取失败".showToast()
}
}
})
mViewModel.isServiceRunning.observe(this, Observer {
EasyLog.print("isServiceRunning--------------$it")
isServiceRunning = it
if (it) {
mViewModel.startRefreshTimer()
binding.mTrackAction.text = "停止巡查"
binding.mTrackAction.setBackgroundResource(R.drawable.stop_track_selector)
// 开启巡查持续时间统计
onActionLocation()
} else {
mViewModel.stopRefreshTimer()
binding.mTrackAction.text = "开始巡查"
binding.mTrackAction.setBackgroundResource(R.drawable.track_selector)
if (isPressBack) {
findNavController().popBackStack()
}
}
})
mViewModel.terminalId.observe(this, EventObserver {
EasyLog.print("savedStateHandle terminal_id--------------$it")
terminalId = it
})
mViewModel.trackId.observe(this, EventObserver {
EasyLog.print("savedStateHandle trackId--------------$it")
trackId = it
})
}
private fun setUpMap() {
mAMap?.uiSettings?.isZoomControlsEnabled = false//缩放按钮
}
/*开启单次定位*/
private fun startSingleLocation() {
try {
// 启动定位
singleLocationClient!!.startLocation()
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
/*开启连续定位*/
private fun startContinueLocation() {
try {
// 启动定位
continueLocationClient!!.startLocation()
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
/*关闭连续定位*/
private fun stopContinueLocation() {
try {
// 启动定位
continueLocationClient!!.stopLocation()
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
private fun initLocation() {
try {
//单次定位
singleLocationClient = AMapLocationClient(App.appContext)
singleLocationOption = getDefaultOption(true)
singleLocationClient?.setLocationOption(singleLocationOption)
singleLocationClient?.setLocationListener { location ->
if (location.errorCode == 0) {
EasyLog.print("定位成功")
latitude = location.latitude//纬 度
longitude = location.longitude//经 度
locationAddress = location.address
EasyLog.print("经 度---" + longitude + " 纬 度---" + latitude + " 地 址---" + location.address)
locationLatLng = LatLng(latitude, longitude)
if (locationMarker == null) {
locationMarker = mAMap?.addMarker(
MarkerOptions()
.position(locationLatLng)
.icon(BitmapDescriptorFactory.fromResource(R.mipmap.location))
.anchor(0.5f, 0.5f)
)
mAMap?.moveCamera(CameraUpdateFactory.newLatLngZoom(locationLatLng, 17f))
} else {
if (useMoveToLocationWithMapMode) {
startMoveLocationAndMap(locationLatLng)
} else {
startChangeLocation(locationLatLng)
}
}
} else {
EasyLog.print("定位失败---" + location.errorCode + " 错误信息---" + location.errorInfo + " 错误描述---" + location.locationDetail)
}
}
//连续定位
continueLocationClient = AMapLocationClient(App.appContext)
continueLocationOption = getDefaultOption(false)
continueLocationClient?.setLocationOption(continueLocationOption)
continueLocationClient?.setLocationListener { location ->
if (location.errorCode == 0) {
EasyLog.print("定位成功")
latitude = location.latitude//纬 度
longitude = location.longitude//经 度
locationAddress = location.address
EasyLog.print("经 度---" + longitude + " 纬 度---" + latitude + " 地 址---" + location.address)
locationLatLng = LatLng(latitude, longitude)
if (locationMarker == null) {
locationMarker = mAMap?.addMarker(
MarkerOptions()
.position(locationLatLng)
.icon(BitmapDescriptorFactory.fromResource(R.mipmap.location))
.anchor(0.5f, 0.5f)
)
mAMap?.moveCamera(CameraUpdateFactory.newLatLngZoom(locationLatLng, 17f))
} else {
if (useMoveToLocationWithMapMode) {
startMoveLocationAndMap(locationLatLng)
} else {
startChangeLocation(locationLatLng)
}
}
} else {
EasyLog.print("定位失败---" + location.errorCode + " 错误信息---" + location.errorInfo + " 错误描述---" + location.locationDetail)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* 默认的定位参数 单次定位 连续定位
*/
private fun getDefaultOption(isOnceLocation: Boolean): AMapLocationClientOption? {
val mOption = AMapLocationClientOption()
mOption.locationMode =
AMapLocationMode.Hight_Accuracy //可选,设置定位模式,可选的模式有高精度、仅设备、仅网络。默认为高精度模式
mOption.isGpsFirst = false //可选,设置是否gps优先,只在高精度模式下有效。默认关闭
mOption.httpTimeOut = 30000 //可选,设置网络请求超时时间。默认为30秒。在仅设备模式下无效
mOption.interval = 2000 //可选,设置定位间隔。默认为2秒
mOption.isNeedAddress = true //可选,设置是否返回逆地理地址信息。默认是true
mOption.isOnceLocation = isOnceLocation //可选,设置是否单次定位。默认是false
mOption.isOnceLocationLatest =
false //可选,设置是否等待wifi刷新,默认为false.如果设置为true,会自动变为单次定位,持续定位时不要使用
AMapLocationClientOption.setLocationProtocol(AMapLocationProtocol.HTTP) //可选, 设置网络请求的协议。可选HTTP或者HTTPS。默认为HTTP
mOption.isSensorEnable = false //可选,设置是否使用传感器。默认是false
mOption.isWifiScan =
true //可选,设置是否开启wifi扫描。默认为true,如果设置为false会同时停止主动刷新,停止以后完全依赖于系统刷新,定位位置可能存在误差
mOption.isLocationCacheEnable = true //可选,设置是否使用缓存定位,默认为true
mOption.geoLanguage =
AMapLocationClientOption.GeoLanguage.DEFAULT //可选,设置逆地理信息的语言,默认值为默认语言(根据所在地区选择语言)
return mOption
}
/**
* 修改自定义定位小蓝点的位置
* @param latLng
*/
private fun startChangeLocation(latLng: LatLng) {
if (locationMarker != null) {
val curLatlng = locationMarker!!.position
if (curLatlng == null || curLatlng != latLng) {
locationMarker!!.position = latLng
}
}
}
/**
* 监控地图动画移动情况,如果结束或者被打断,都需要执行响应的操作
*/
inner class MyCancelCallback : CancelableCallback {
private var targetLatlng: LatLng? = null
fun setTargetLatlng(latlng: LatLng?) {
targetLatlng = latlng
}
override fun onFinish() {
if (locationMarker != null && targetLatlng != null) {
locationMarker!!.position = targetLatlng
}
}
override fun onCancel() {
if (locationMarker != null && targetLatlng != null) {
locationMarker!!.position = targetLatlng
}
}
}
/**
* 同时修改自定义定位小蓝点和地图的位置
* @param latLng
*/
private fun startMoveLocationAndMap(latLng: LatLng) {
//将小蓝点提取到屏幕上
if (projection == null) {
projection = mAMap?.projection
}
if (locationMarker != null && projection != null) {
val markerLocation = locationMarker!!.position
val screenPosition: android.graphics.Point? =
mAMap?.projection?.toScreenLocation(markerLocation)
locationMarker!!.setPositionByPixels(screenPosition!!.x, screenPosition!!.y)
}
//移动地图,移动结束后,将小蓝点放到放到地图上
myCancelCallback.setTargetLatlng(latLng)
//动画移动的时间,最好不要比定位间隔长,如果定位间隔2000ms 动画移动时间最好小于2000ms,可以使用1000ms
//如果超过了,需要在myCancelCallback中进行处理被打断的情况
// mAMap?.animateCamera(CameraUpdateFactory.changeLatLng(latLng), 1000, myCancelCallback)
mAMap?.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 17f), 1000, myCancelCallback)
}
override fun onResume() {
super.onResume()
mMapView?.onResume()
EasyLog.print("onResume---------------")
if (isServiceRunning) {//页面被销毁,重新 bindService
startTrack()
}
}
override fun onPause() {
super.onPause()
EasyLog.print("onPause---------------")
mMapView?.onPause()
}
override fun onStop() {
super.onStop()
EasyLog.print("onStop---------------")
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mMapView?.onSaveInstanceState(outState)
}
override fun onDestroyView() {
super.onDestroyView()
EasyLog.print("onDestroyView---------------")
mMapView?.onDestroy()
if (isServiceRunning) {
aMapTrackClient?.stopTrack(
TrackParam(Constants.SERVICE_ID, terminalId),
SimpleOnTrackLifecycleListener()
)
}
destroyLocation()
//fix Navigation 销毁 Fragment 导致 mMapView?.onSaveInstanceState(outState) 失败
mAMap?.clear()
mAMap = null
locationMarker = null
}
private fun destroyLocation() {
if (null != singleLocationClient) {
/**
* 如果AMapLocationClient是在当前Activity实例化的,
* 在Activity的onDestroy中一定要执行AMapLocationClient的onDestroy
*/
singleLocationClient!!.onDestroy()
singleLocationClient = null
singleLocationOption = null
}
if (null != continueLocationClient) {
/**
* 如果AMapLocationClient是在当前Activity实例化的,
* 在Activity的onDestroy中一定要执行AMapLocationClient的onDestroy
*/
continueLocationClient!!.onDestroy()
continueLocationClient = null
continueLocationOption = null
}
}
override fun onActionStartPatrol() {
if (isServiceRunning) {
stopTrackAction("确定要结束巡查吗?")
} else {
startTrack()
}
}
/*开始巡查 上报巡查记录到高德*/
private fun addPatrolServiceRecord() {
postEntity.terminal_id = terminalId.toString()
postEntity.track_id = trackId.toString()
postEntity.detail_address = locationAddress
mViewModel.submitMapPatrolRecord(postEntity)
}
/*结束巡查 更新巡查记录状态*/
private fun updatePatrolServiceStatus() {
// stopContinueLocation()
postStatusEntity.track_id = trackId.toString()
postStatusEntity.detail_address = locationAddress
mViewModel.updateMapPatrolRecordStatus(postStatusEntity)
}
private fun stopTrack() {
aMapTrackClient!!.stopTrack(
TrackParam(Constants.SERVICE_ID, terminalId),
onTrackListener
)
}
private fun startTrack() {
// startContinueLocation()
// 先根据Terminal名称查询Terminal ID,如果Terminal还不存在,就尝试创建,拿到Terminal ID后,
// 用Terminal ID开启轨迹服务
aMapTrackClient?.queryTerminal(
QueryTerminalRequest(
Constants.SERVICE_ID,
prefs.userId
), object : SimpleOnTrackListener() {
override fun onQueryTerminalCallback(queryTerminalResponse: QueryTerminalResponse) {
if (queryTerminalResponse.isSuccess) {
if (queryTerminalResponse.isTerminalExist) {
// 当前终端已经创建过,直接使用查询到的terminal id
terminalId = queryTerminalResponse.tid
mViewModel.saveTerminalId(terminalId)
aMapTrackClient!!.addTrack(
AddTrackRequest(
Constants.SERVICE_ID,
terminalId
), object : SimpleOnTrackListener() {
override fun onAddTrackCallback(addTrackResponse: AddTrackResponse) {
if (addTrackResponse.isSuccess) {
// trackId需要在启动服务后设置才能生效,因此这里不设置,而是在startGather之前设置了track id
if (trackId == 0L) {
trackId = addTrackResponse.trid
mViewModel.saveTrackId(trackId)
}
val trackParam =
TrackParam(Constants.SERVICE_ID, terminalId)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
trackParam.notification = createNotification()
}
//已经启动过的终端,重新bind
aMapTrackClient!!.startTrack(
trackParam,
onTrackListener
)
} else {
"网络请求失败".showToast()
}
}
})
} else {
// 当前终端是新终端,还未创建过,创建该终端并使用新生成的terminal id
aMapTrackClient!!.addTerminal(
AddTerminalRequest(
prefs.userId,
Constants.SERVICE_ID
), object : SimpleOnTrackListener() {
override fun onCreateTerminalCallback(addTerminalResponse: AddTerminalResponse) {
if (addTerminalResponse.isSuccess) {
terminalId = addTerminalResponse.tid
mViewModel.saveTerminalId(terminalId)
val trackParam =
TrackParam(Constants.SERVICE_ID, terminalId)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
trackParam.notification = createNotification()
}
aMapTrackClient!!.startTrack(
trackParam,
onTrackListener
)
} else {
"网络请求失败".showToast()
}
}
})
}
} else {
"网络请求失败".showToast()
}
}
})
}
private val onTrackListener: OnTrackLifecycleListener =
object : SimpleOnTrackLifecycleListener() {
override fun onBindServiceCallback(status: Int, msg: String) {
EasyLog.print("onBindServiceCallback, status: $status, msg: $msg")
}
override fun onStartTrackCallback(status: Int, msg: String) {
when (status) {
ErrorCode.TrackListen.START_TRACK_SUCEE, ErrorCode.TrackListen.START_TRACK_SUCEE_NO_NETWORK -> {
// 成功启动
"巡查服务启动成功".showToast()
if (!isServiceRunning) {
addPatrolServiceRecord()
}
mViewModel.isRunningServiceStatusChanged(true)
startGather()
}
ErrorCode.TrackListen.START_TRACK_ALREADY_STARTED -> {
// 已经启动
"巡查服务已经启动".showToast()
startGather()
}
else -> {
EasyLog.print("error onStartTrackCallback, status: $status, msg: $msg")
"error onStartTrackCallback, status: $status, msg: $msg".showToast()
}
}
}
override fun onStopTrackCallback(status: Int, msg: String) {
if (status == ErrorCode.TrackListen.STOP_TRACK_SUCCE) {
// 成功停止
"巡查服务已结束".showToast()
mViewModel.isRunningServiceStatusChanged(false)
updatePatrolServiceStatus()
} else {
EasyLog.print("error onStopTrackCallback, status: $status, msg: $msg")
"error onStopTrackCallback, status: $status, msg: $msg".showToast()
}
}
override fun onStartGatherCallback(status: Int, msg: String) {
when (status) {
ErrorCode.TrackListen.START_GATHER_SUCEE -> {
"定位采集开启成功".showToast()
}
ErrorCode.TrackListen.START_GATHER_ALREADY_STARTED -> {
"定位采集已经开启".showToast()
}
else -> {
EasyLog.print("error onStartGatherCallback, status: $status, msg: $msg")
"error onStartGatherCallback, status: $status, msg: $msg".showToast()
}
}
}
override fun onStopGatherCallback(status: Int, msg: String) {
if (status == ErrorCode.TrackListen.STOP_GATHER_SUCCE) {
"定位采集停止成功".showToast()
stopTrack()
} else {
EasyLog.print("error onStopGatherCallback, status: $status, msg: $msg")
"error onStopGatherCallback, status: $status, msg: $msg".showToast()
}
}
}
private fun startGather() {
aMapTrackClient?.startGather(onTrackListener)
}
private fun stopGather() {
aMapTrackClient?.trackId = trackId
aMapTrackClient?.stopGather(onTrackListener)
}
/**
* 在8.0以上手机,如果app切到后台,系统会限制定位相关接口调用频率
* 可以在启动轨迹上报服务时提供一个通知,这样Service启动时会使用该通知成为前台Service,可以避免此限制
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private fun createNotification(): Notification? {
val builder: Notification.Builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val nm =
mContext.getSystemService(NOTIFICATION_SERVICE) as NotificationManager?
val channel = NotificationChannel(
"CHANNEL_ID_SERVICE_RUNNING",
"app service",
NotificationManager.IMPORTANCE_LOW
)
nm!!.createNotificationChannel(channel)
Notification.Builder(App.appContext, "CHANNEL_ID_SERVICE_RUNNING")
} else {
Notification.Builder(App.appContext)
}
val nfIntent = Intent(context, MainActivity::class.java)
nfIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
builder.setContentIntent(
PendingIntent.getActivity(
context,
0,
nfIntent,
0
)
)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("道路云养护运行中")
.setContentText("道路云养护运行中")
return builder.build()
}
private fun stopTrackAction(tip: String) {
EasyLog.print("stopTrackAction-------------$terminalId")
// 弹框提示是否结束
RequestDialog(mContext, tip) {
stopGather()
}
}
override fun onActionEditReport() {
// if (!isServiceRunning) {
// "请先开始巡查".showToast()
// return
// }
findNavController().safeNavigate(
PatrolFragmentDirections.actionPatrolFragmentToMapReportFragment(
locationAddress, latitude.toFloat(), longitude.toFloat(), trackId.toString()
)
)
}
private var isPressBack = false
override fun onActionBack() {
isPressBack = true
if (isServiceRunning) {
stopTrackAction("确定要结束巡查吗?")
} else {
findNavController().popBackStack()
}
}
override fun onActionLocation() {
}
override fun initViewListener() {
activity?.onBackPressedDispatcher?.addCallback(this) {
isPressBack = true
if (isServiceRunning) {
stopTrackAction("确定要结束巡查吗?")
} else {
findNavController().popBackStack()
}
}
}
companion object {
fun newInstance() = PatrolFragment()
}
}