增加任务列表和下载功能

This commit is contained in:
squallzhjch 2023-04-23 16:35:45 +08:00
parent ccfd30228e
commit a1170db7a9
35 changed files with 1083 additions and 137 deletions

View File

@ -27,7 +27,7 @@ class Constant {
/**
* 服务器地址
*/
const val SERVER_ADDRESS = "http://fastmap.navinfo.com/drdc/"
const val SERVER_ADDRESS = "http://fastmap.navinfo.com/"
const val DEBUG = true

View File

@ -3,6 +3,7 @@ package com.navinfo.omqs
import android.app.Application
import android.util.Log
import com.navinfo.collect.library.data.dao.impl.MapLifeDataBase
import com.navinfo.omqs.db.MyRealmModule
import com.navinfo.omqs.db.TraceDataBase
import com.navinfo.omqs.tools.FileManager
import com.navinfo.omqs.ui.manager.TakePhotoManager
@ -29,12 +30,11 @@ class OMQSApplication : Application() {
val password = "encryp".encodeToByteArray().copyInto(ByteArray(64))
// 656e6372797000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Log.d("OMQSApplication", "密码是: ${byteArrayToHexString(password)}")
// 1110000011000010111001101110011011101110110111101110010011001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
val config = RealmConfiguration.Builder()
.directory(File(Constant.DATA_PATH))
.name("OMQS.realm")
.encryptionKey(password)
// .modules(Realm.getDefaultModule(), MyRealmModule())
.modules(Realm.getDefaultModule(), MyRealmModule())
.schemaVersion(1)
.build()
Realm.setDefaultConfiguration(config)

View File

@ -0,0 +1,21 @@
package com.navinfo.omqs.bean
import io.realm.RealmObject
import io.realm.annotations.RealmClass
@RealmClass
open class HadLinkDvoBean @JvmOverloads constructor(
/**
* 图幅号
*/
var mesh: String = "",
/**
* linkPid
*/
var linkPid: String = "",
/**
* (几何)加偏后
*/
var geometry: String = ""
) : RealmObject()

View File

@ -3,6 +3,7 @@ package com.navinfo.omqs.bean
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.navinfo.omqs.tools.FileManager
import kotlinx.parcelize.Parcelize
@ -17,19 +18,9 @@ data class OfflineMapCityBean @JvmOverloads constructor(
var version: Long = 0L,
var fileSize: Long = 0L,
var currentSize: Long = 0L,
var status: Int = NONE
var status: Int = FileManager.Companion.FileDownloadStatus.NONE
) : Parcelable {
companion object Status {
const val NONE = 0 //无状态
const val WAITING = 1 //等待中
const val LOADING = 2 //下载中
const val PAUSE = 3 //暂停
const val ERROR = 4 //错误
const val DONE = 5 //完成
const val UPDATE = 6 //有新版本要更新
}
// // status的转换对象
// var statusEnum: StatusEnum
// get() {

View File

@ -0,0 +1,64 @@
package com.navinfo.omqs.bean
import com.google.gson.annotations.SerializedName
import com.navinfo.omqs.Constant
import com.navinfo.omqs.system.SystemConstant
import com.navinfo.omqs.tools.FileManager.Companion.FileDownloadStatus
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
import io.realm.annotations.RealmClass
@RealmClass
open class TaskBean @JvmOverloads constructor(
/**
* 测评任务id
*/
@PrimaryKey
var id: Int = 0,
/**
* 测评任务名称
*/
var evaluationTaskName: String = "",
/**
* 市编码
*/
var cityCode: String = "",
/**
*市名称
*/
var cityName: String = "",
/**
* omdb标准版
*/
var dataVersion: String = "",
/**
* 测评人名称
*/
var evaluatorName: String = "",
/**
* 项目标签
*/
var project: String = "",
/**
* 图幅号
*/
@SerializedName("hadLinkDvo")
var hadLinkDvoList: RealmList<HadLinkDvoBean> = RealmList<HadLinkDvoBean>(),
/**
* 文件大小
*/
var fileSize: Long = 0L,
/**
* 当前下载进度
*/
var currentSize: Long = 0L,
/**
* 当前下载状态
*/
var status: Int = FileDownloadStatus.NONE
) : RealmObject(){
fun getDownLoadUrl():String{
return "${Constant.SERVER_ADDRESS}devcp/download?fileStr=26"
}
}

View File

@ -1,7 +1,9 @@
package com.navinfo.omqs.db
import com.navinfo.collect.library.data.entity.QsRecordBean
import com.navinfo.omqs.bean.HadLinkDvoBean
import com.navinfo.omqs.bean.TaskBean
//@io.realm.annotations.RealmModule(classes = [QsRecordBean::class])
//class MyRealmModule {
//}
@io.realm.annotations.RealmModule(classes = [TaskBean::class, HadLinkDvoBean::class])
class MyRealmModule {
}

View File

@ -5,6 +5,7 @@ import com.navinfo.omqs.db.RealmOperateHelper
import com.navinfo.omqs.db.RoomAppDatabase
import com.navinfo.omqs.http.RetrofitNetworkServiceAPI
import com.navinfo.omqs.http.offlinemapdownload.OfflineMapDownloadManager
import com.navinfo.omqs.http.taskdownload.TaskDownloadManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -34,6 +35,16 @@ class MainActivityModule {
): OfflineMapDownloadManager =
OfflineMapDownloadManager(networkServiceAPI, roomAppDatabase, mapController)
/**
* 注入任务下载
*/
@ActivityRetainedScoped
@Provides
fun providesTaskListDownloadManager(
networkServiceAPI: RetrofitNetworkServiceAPI,
): TaskDownloadManager =
TaskDownloadManager(networkServiceAPI)
/**
* 实验失败这样创建viewmodel不会在activity销毁的时候同时销毁
* 4-14:因为没有传入activity的 owner,无法检测生命周期

View File

@ -0,0 +1,7 @@
package com.navinfo.omqs.http
class DefaultTaskResponse<T> {
var success: Boolean = false
var msg: String = ""
var obj: T? = null
}

View File

@ -1,6 +1,7 @@
package com.navinfo.omqs.http
import com.navinfo.omqs.bean.OfflineMapCityBean
import com.navinfo.omqs.bean.TaskBean
/**
@ -11,4 +12,8 @@ interface NetworkService {
* 获取离线地图城市列表
*/
suspend fun getOfflineMapCityList():NetResult<List<OfflineMapCityBean>>
/**
* 获取任务列表
*/
suspend fun getTaskList(evaluatorNo:String): NetResult<DefaultTaskResponse<List<TaskBean>>>
}

View File

@ -1,6 +1,7 @@
package com.navinfo.omqs.http
import com.navinfo.omqs.bean.OfflineMapCityBean
import com.navinfo.omqs.bean.TaskBean
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.inject.Inject
@ -32,4 +33,23 @@ class NetworkServiceImpl @Inject constructor(
NetResult.Error(e)
}
}
override suspend fun getTaskList(evaluatorNo: String): NetResult<DefaultTaskResponse<List<TaskBean>>> =
//在IO线程中运行
withContext(Dispatchers.IO) {
return@withContext try {
val result = netApi.retrofitGetTaskList(evaluatorNo)
if (result.isSuccessful) {
if (result.code() == 200) {
NetResult.Success(result.body())
} else {
NetResult.Failure(result.code(), result.message())
}
} else {
NetResult.Failure(result.code(), result.message())
}
} catch (e: Exception) {
NetResult.Error(e)
}
}
}

View File

@ -1,10 +1,12 @@
package com.navinfo.omqs.http
import com.navinfo.omqs.bean.OfflineMapCityBean
import com.navinfo.omqs.bean.TaskBean
import okhttp3.ResponseBody
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Query
import retrofit2.http.Streaming
import retrofit2.http.Url
@ -46,8 +48,15 @@ interface RetrofitNetworkServiceAPI {
*/
@Streaming
@GET
suspend fun retrofitDownLoadFile(@Header("RANGE") start: String? = "0", @Url url: String):Response<ResponseBody>
suspend fun retrofitDownLoadFile(
@Header("RANGE") start: String? = "0",
@Url url: String
): Response<ResponseBody>
@GET("/devcp/task?evaluatType=2")
suspend fun retrofitGetTaskList(
@Query("evaluatorNo") evaluatorNo: String,
): Response<DefaultTaskResponse<List<TaskBean>>>
/**
* @FormUrlEncoded 请求格式注解请求实体是一个From表单每个键值对需要使用@Field注解

View File

@ -112,6 +112,4 @@ class OfflineMapDownloadManager(
scopeMap[id]!!.removeObserver()
}
}
}

View File

@ -6,6 +6,8 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import com.navinfo.omqs.Constant
import com.navinfo.omqs.bean.OfflineMapCityBean
import com.navinfo.omqs.tools.FileManager
import com.navinfo.omqs.tools.FileManager.Companion.FileDownloadStatus
import kotlinx.coroutines.*
import java.io.File
import java.io.IOException
@ -23,7 +25,7 @@ class OfflineMapDownloadScope(
private val downloadManager: OfflineMapDownloadManager,
val cityBean: OfflineMapCityBean,
) :
CoroutineScope by CoroutineScope(Dispatchers.IO) {
CoroutineScope by CoroutineScope(Dispatchers.IO + CoroutineName("OfflineMapDownLoad")) {
/**
*下载任务用来取消的
*/
@ -46,7 +48,7 @@ class OfflineMapDownloadScope(
//改进的代码
fun start() {
change(OfflineMapCityBean.WAITING)
change(FileDownloadStatus.WAITING)
downloadManager.launchScope(this@OfflineMapDownloadScope)
}
@ -56,7 +58,7 @@ class OfflineMapDownloadScope(
*/
fun pause() {
downloadJob?.cancel("pause")
change(OfflineMapCityBean.PAUSE)
change(FileDownloadStatus.PAUSE)
}
/**
@ -65,11 +67,8 @@ class OfflineMapDownloadScope(
*/
fun launch() {
downloadJob = launch() {
Log.e("jingo", "启动下载1")
download()
Log.e("jingo", "启动下载2")
downloadManager.launchNext(cityBean.id)
Log.e("jingo", "启动下载3")
}
}
@ -78,7 +77,7 @@ class OfflineMapDownloadScope(
* 是否是等待任务
*/
fun isWaiting(): Boolean {
return cityBean.status == OfflineMapCityBean.WAITING
return cityBean.status == FileDownloadStatus.WAITING
}
/**
@ -86,7 +85,7 @@ class OfflineMapDownloadScope(
* @param status [OfflineMapCityBean.Status]
*/
private fun change(status: Int) {
if (cityBean.status != status || status == OfflineMapCityBean.LOADING) {
if (cityBean.status != status || status == FileDownloadStatus.LOADING) {
cityBean.status = status
downloadData.postValue(cityBean)
launch(Dispatchers.IO) {
@ -128,7 +127,7 @@ class OfflineMapDownloadScope(
url = cityBean.url
)
val responseBody = response.body()
change(OfflineMapCityBean.LOADING)
change(FileDownloadStatus.LOADING)
responseBody ?: throw IOException("jingo ResponseBody is null")
//写入文件
randomAccessFile = RandomAccessFile(fileTemp, "rwd")
@ -144,7 +143,7 @@ class OfflineMapDownloadScope(
if (readLength != -1) {
randomAccessFile.write(buffer, 0, readLength)
cityBean.currentSize += readLength
change(OfflineMapCityBean.LOADING)
change(FileDownloadStatus.LOADING)
} else {
break
}
@ -155,15 +154,15 @@ class OfflineMapDownloadScope(
val res =
fileTemp.renameTo(File("${Constant.OFFLINE_MAP_PATH}${cityBean.fileName}"))
Log.e("jingo", "文件下载完成 修改文件 $res")
change(OfflineMapCityBean.DONE)
change(FileDownloadStatus.DONE)
withContext(Dispatchers.Main) {
downloadManager.mapController.layerManagerHandler.loadBaseMap()
}
} else {
change(OfflineMapCityBean.PAUSE)
change(FileDownloadStatus.PAUSE)
}
} catch (e: Throwable) {
change(OfflineMapCityBean.ERROR)
change(FileDownloadStatus.ERROR)
} finally {
inputStream?.close()
randomAccessFile?.close()

View File

@ -0,0 +1,113 @@
package com.navinfo.omqs.http.taskdownload
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import com.navinfo.omqs.bean.TaskBean
import com.navinfo.omqs.http.RetrofitNetworkServiceAPI
import java.util.concurrent.ConcurrentHashMap
/**
* 管理任务数据下载
*/
class TaskDownloadManager(
val netApi: RetrofitNetworkServiceAPI,
) {
/**
* 最多同时下载数量
*/
private val MAX_SCOPE = 3
/**
* 存储有哪些城市需要下载的队列
*/
private val scopeMap: ConcurrentHashMap<Int, TaskDownloadScope> by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
ConcurrentHashMap<Int, TaskDownloadScope>()
}
/**
* 存储正在下载的城市队列
*/
private val taskScopeMap: ConcurrentHashMap<Int, TaskDownloadScope> by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
ConcurrentHashMap<Int, TaskDownloadScope>()
}
/**
* 启动下载任务
* 请不要直接使用此方法启动下载任务,它是交由[OfflineMapDownloadScope]进行调用
*/
fun launchScope(scope: TaskDownloadScope) {
if (taskScopeMap.size >= MAX_SCOPE) {
return
}
if (taskScopeMap.contains(scope.taskBean.id)) {
return
}
taskScopeMap[scope.taskBean.id] = scope
scope.launch()
}
/**
* 启动下一个任务,如果有正在等待中的任务的话
* 请不要直接使用此方法启动下载任务,它是交由[OfflineMapDownloadScope]进行调用
* @param previousUrl 上一个下载任务的下载连接
*/
fun launchNext(id: Int) {
taskScopeMap.remove(id)
for (entrySet in scopeMap) {
val downloadScope = entrySet.value
if (downloadScope.isWaiting()) {
launchScope(downloadScope)
break
}
}
}
/**
* 暂停任务
* 只有等待中的任务和正在下载中的任务才可以进行暂停操作
*/
fun pause(id: Int) {
if (taskScopeMap.containsKey(id)) {
val downloadScope = taskScopeMap[id]
downloadScope?.let {
downloadScope.pause()
}
launchNext(id)
}
}
/**
* 将下载任务加入到协程作用域的下载队列里
* 请求一个下载任务[OfflineMapDownloadScope]
* 这是创建[OfflineMapDownloadScope]的唯一途径,请不要通过其他方式创建[OfflineMapDownloadScope]
*/
fun start(id: Int) {
scopeMap[id]?.start()
}
fun addTask(taskBean: TaskBean) {
if (!scopeMap.containsKey(taskBean.id)) {
scopeMap[taskBean.id] = TaskDownloadScope(this, taskBean)
}
}
fun observer(
id: Int, lifecycleOwner: LifecycleOwner, observer: Observer<TaskBean>
) {
if (scopeMap.containsKey(id)) {
scopeMap[id]!!.observer(lifecycleOwner, observer)
}
}
fun removeObserver(id: Int) {
if (scopeMap.containsKey(id)) {
scopeMap[id]!!.removeObserver()
}
}
}

View File

@ -0,0 +1,168 @@
package com.navinfo.omqs.http.taskdownload
import android.util.Log
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import com.navinfo.omqs.Constant
import com.navinfo.omqs.bean.TaskBean
import com.navinfo.omqs.tools.FileManager.Companion.FileDownloadStatus
import kotlinx.coroutines.*
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.io.RandomAccessFile
class TaskDownloadScope(
private val downloadManager: TaskDownloadManager,
val taskBean: TaskBean,
) :
CoroutineScope by CoroutineScope(Dispatchers.IO + CoroutineName("OfflineMapDownLoad")) {
/**
*下载任务用来取消的
*/
private var downloadJob: Job? = null
/**
* 管理观察者同时只有一个就行了
*/
private val observer = Observer<Any> {}
// private var lifecycleOwner: LifecycleOwner? = null
/**
*通知UI更新
*/
private val downloadData = MutableLiveData<TaskBean>()
init {
downloadData.value = taskBean
}
//改进的代码
fun start() {
change(FileDownloadStatus.WAITING)
downloadManager.launchScope(this@TaskDownloadScope)
}
/**
* 暂停任务
* 其实就是取消任务移除监听
*/
fun pause() {
downloadJob?.cancel("pause")
change(FileDownloadStatus.PAUSE)
}
/**
* 启动协程进行下载
* 请不要尝试在外部调用此方法,那样会脱离[OfflineMapDownloadManager]的管理
*/
fun launch() {
downloadJob = launch() {
download()
downloadManager.launchNext(taskBean.id)
}
}
/**
* 是否是等待任务
*/
fun isWaiting(): Boolean {
return taskBean.status == FileDownloadStatus.WAITING
}
/**
* 更新任务
* @param status [OfflineMapCityBean.Status]
*/
private fun change(status: Int) {
if (taskBean.status != status || status == FileDownloadStatus.LOADING) {
taskBean.status = status
downloadData.postValue(taskBean)
launch(Dispatchers.IO) {
// downloadManager.roomDatabase.getOfflineMapDao().update(taskBean)
}
}
}
/**
* 添加下载任务观察者
*/
fun observer(owner: LifecycleOwner, ob: Observer<TaskBean>) {
removeObserver()
// this.lifecycleOwner = owner
downloadData.observe(owner, ob)
}
/**
* 下载文件
*/
private suspend fun download() {
var inputStream: InputStream? = null
var randomAccessFile: RandomAccessFile? = null
try {
//创建离线地图 下载文件夹,.map文件夹的下一级
val fileDir = File("${Constant.OFFLINE_MAP_PATH}download")
if (!fileDir.exists()) {
fileDir.mkdirs()
}
val fileTemp =
File("${Constant.OFFLINE_MAP_PATH}download/${taskBean.id}_${taskBean.dataVersion}")
val startPosition = taskBean.currentSize
//验证断点有效性
if (startPosition < 0) throw IOException("jingo Start position less than zero")
val response = downloadManager.netApi.retrofitDownLoadFile(
start = "bytes=$startPosition-",
url = taskBean.getDownLoadUrl()
)
val responseBody = response.body()
change(FileDownloadStatus.LOADING)
responseBody ?: throw IOException("jingo ResponseBody is null")
//写入文件
randomAccessFile = RandomAccessFile(fileTemp, "rwd")
randomAccessFile.seek(startPosition)
taskBean.currentSize = startPosition
inputStream = responseBody.byteStream()
val bufferSize = 1024 * 2
val buffer = ByteArray(bufferSize)
var readLength = 0
while (downloadJob?.isActive == true) {
readLength = inputStream.read(buffer)
if (readLength != -1) {
randomAccessFile.write(buffer, 0, readLength)
taskBean.currentSize += readLength
change(FileDownloadStatus.LOADING)
} else {
break
}
}
Log.e("jingo", "文件下载完成 ${taskBean.currentSize} == ${taskBean.fileSize}")
if (taskBean.currentSize == taskBean.fileSize) {
val res =
fileTemp.renameTo(File("${Constant.OFFLINE_MAP_PATH}${taskBean.evaluationTaskName}.zip"))
Log.e("jingo", "文件下载完成 修改文件 $res")
change(FileDownloadStatus.DONE)
} else {
change(FileDownloadStatus.PAUSE)
}
} catch (e: Throwable) {
change(FileDownloadStatus.ERROR)
} finally {
inputStream?.close()
randomAccessFile?.close()
}
}
fun removeObserver() {
downloadData.observeForever(observer)
// lifecycleOwner?.let {
downloadData.removeObserver(observer)
// null
// }
}
}

View File

@ -6,7 +6,19 @@ import com.navinfo.omqs.bean.OfflineMapCityBean
import java.io.File
class FileManager {
companion object {
object FileDownloadStatus {
const val NONE = 0 //无状态
const val WAITING = 1 //等待中
const val LOADING = 2 //下载中
const val PAUSE = 3 //暂停
const val ERROR = 4 //错误
const val DONE = 5 //完成
const val UPDATE = 6 //有新版本要更新
}
//初始化数据文件夹
fun initRootDir(context:Context){
// 在SD卡创建项目目录
@ -51,7 +63,7 @@ class FileManager {
if (item.isFile && item.name.startsWith(cityBean.id)) {
//如果本地文件与从网络获取到版本号一致,表示这个文件已经下载完毕,不用处理了
if (item.name.contains("_${cityBean.version}.map")) {
cityBean.status = OfflineMapCityBean.DONE
cityBean.status = FileDownloadStatus.DONE
return
}
//文件存在,版本号不对应,留给下面流程处理
@ -72,16 +84,16 @@ class FileManager {
if (item.renameTo(File("${Constant.OFFLINE_MAP_PATH}${cityBean.fileName}"))) {
//删除旧版本数据
mapFile?.delete()
cityBean.status = OfflineMapCityBean.DONE
cityBean.status = FileDownloadStatus.DONE
return
}
} else { // 临时文件大小和目标不一致,说明下载了一半
cityBean.status = OfflineMapCityBean.PAUSE
cityBean.status = FileDownloadStatus.PAUSE
cityBean.currentSize = item.length()
return
}
} else { //虽然省市id开头一致但是版本号不一致说明之前版本下载了一部分现在要更新了原来下载的文件直接删除
cityBean.status = OfflineMapCityBean.UPDATE
cityBean.status = FileDownloadStatus.UPDATE
item.delete()
return
}

View File

@ -5,6 +5,7 @@ import android.util.Log
import androidx.activity.viewModels
import androidx.core.view.WindowCompat
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import com.blankj.utilcode.util.ToastUtils
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewModelScope
@ -18,6 +19,8 @@ import com.navinfo.omqs.db.TraceDataBase
import com.navinfo.omqs.http.offlinemapdownload.OfflineMapDownloadManager
import com.navinfo.omqs.system.SystemConstant
import com.navinfo.omqs.ui.activity.BaseActivity
import com.navinfo.omqs.ui.fragment.evaluationresult.EvaluationResultFragment
import com.navinfo.omqs.ui.fragment.evaluationresult.EvaluationResultViewModel
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@ -57,12 +60,8 @@ class MainActivity : BaseActivity() {
binding.viewModel = viewModel
viewModel.liveDataQsRecordIdList.observe(this) {
if (it.size == 1) {
val bundle = Bundle()
bundle.putString("QsId", it[0])
val naviController = findNavController(R.id.main_activity_right_fragment)
naviController.navigate(R.id.EvaluationResultFragment, bundle)
}
//处理页面跳转
viewModel.navigation(this, it)
}
}
@ -74,11 +73,11 @@ class MainActivity : BaseActivity() {
mapController.locationLayerHandler.startLocation()
//启动轨迹存储
mapController.locationLayerHandler.setNiLocationListener(NiLocationListener {
binding!!.viewModel!!.addSaveTrace(it)
binding!!.viewModel!!.startSaveTraceThread(this)
viewModel.addSaveTrace(it)
// binding.viewModel!!.startSaveTraceThread(this)
})
//显示轨迹图层
mapController.layerManagerHandler.showNiLocationLayer(Constant.DATA_PATH+ SystemConstant.USER_ID+"/trace.sqlite")
// mapController.layerManagerHandler.showNiLocationLayer(Constant.DATA_PATH+ SystemConstant.USER_ID+"/trace.sqlite")
}
override fun onPause() {
@ -108,7 +107,7 @@ class MainActivity : BaseActivity() {
* 打开相机预览
*/
fun openCamera() {
binding!!.viewModel!!.onClickCameraButton(this)
binding.viewModel!!.onClickCameraButton(this)
}
/**

View File

@ -2,9 +2,11 @@ package com.navinfo.omqs.ui.activity.map
import android.content.Context
import android.content.DialogInterface
import android.os.Bundle
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.navigation.findNavController
import com.blankj.utilcode.util.ToastUtils
import com.navinfo.collect.library.data.entity.NiLocation
import com.navinfo.collect.library.map.NIMapController
@ -33,7 +35,7 @@ class MainViewModel @Inject constructor(
val liveDataQsRecordIdList = MutableLiveData<List<String>>()
private var mCameraDialog: CommonDialog? = null
private var niLocationList:MutableList<NiLocation> = ArrayList<NiLocation>()
private var niLocationList: MutableList<NiLocation> = ArrayList<NiLocation>()
init {
mapController.layerManagerHandler.setOnQsRecordItemClickListener(object :
@ -56,14 +58,21 @@ class MainViewModel @Inject constructor(
}
//点击相机按钮
fun onClickCameraButton(context: Context){
fun onClickCameraButton(context: Context) {
Log.e("qj", LibVlcUtil.hasCompatibleCPU(context).toString())
ToastUtils.showShort("点击了相机")
if (mCameraDialog == null) {
mCameraDialog = CommonDialog(context, context.resources.getDimension(R.dimen.head_img_width).toInt() * 3 + context.resources.getDimension(R.dimen.ten).toInt() + context.resources.getDimension(R.dimen.twenty_four).toInt(), context.resources.getDimension(R.dimen.head_img_width).toInt() + 10, 1)
mCameraDialog = CommonDialog(
context,
context.resources.getDimension(R.dimen.head_img_width)
.toInt() * 3 + context.resources.getDimension(R.dimen.ten)
.toInt() + context.resources.getDimension(R.dimen.twenty_four).toInt(),
context.resources.getDimension(R.dimen.head_img_width).toInt() + 10,
1
)
mCameraDialog!!.setCancelable(true)
}
mCameraDialog!!.openCamear(mCameraDialog!!.getmShareUtil().continusTakePhotoState)
@ -72,10 +81,11 @@ class MainViewModel @Inject constructor(
mCameraDialog!!.hideLoading()
mCameraDialog!!.stopVideo()
try {
if (!mCameraDialog!!.getmShareUtil().connectstate){
if (!mCameraDialog!!.getmShareUtil().connectstate) {
mCameraDialog!!.updateCameraResources(1, mCameraDialog!!.getmDeviceNum())
}
TakePhotoManager.getInstance().getCameraVedioClent(mCameraDialog!!.getmDeviceNum()).StopSearch()
TakePhotoManager.getInstance().getCameraVedioClent(mCameraDialog!!.getmDeviceNum())
.StopSearch()
} catch (e: Exception) {
}
})
@ -87,12 +97,12 @@ class MainViewModel @Inject constructor(
})
}
fun startSaveTraceThread(context: Context){
fun startSaveTraceThread(context: Context) {
Thread(Runnable {
try {
while (true){
while (true) {
if(niLocationList!=null&&niLocationList.size>0){
if (niLocationList != null && niLocationList.size > 0) {
var niLocation = niLocationList[0]
var doubleArray = doubleArrayOf()
@ -112,23 +122,60 @@ class MainViewModel @Inject constructor(
}
}
TraceDataBase.getDatabase(context, Constant.DATA_PATH+ SystemConstant.USER_ID+"/trace.sqlite").niLocationDao.insert(niLocation)
TraceDataBase.getDatabase(
context,
Constant.DATA_PATH + SystemConstant.USER_ID + "/trace.sqlite"
).niLocationDao.insert(niLocation)
niLocationList.removeAt(0)
Log.e("qj","saveTrace")
Log.e("qj", "saveTrace")
}
Thread.sleep(30)
}
} catch (e: InterruptedException) {
e.printStackTrace()
Log.e("qj","异常==${e.message}")
Log.e("qj", "异常==${e.message}")
}
}).start()
}
//增加轨迹存储
fun addSaveTrace(niLocation: NiLocation){
if(niLocation!=null&&niLocationList!=null){
fun addSaveTrace(niLocation: NiLocation) {
if (niLocation != null && niLocationList != null) {
niLocationList.add(niLocation)
}
}
fun navigation(activity: MainActivity, list: List<String>) {
//获取右侧fragment容器
val naviController = activity.findNavController(R.id.main_activity_right_fragment)
naviController.currentDestination?.let { navDestination ->
// when (val fragment =
// activity.supportFragmentManager.findFragmentById(navDestination.id)) {
// //判断右侧的fragment是不是质检数据
//// is EvaluationResultFragment -> {
//// val viewModelFragment =
//// ViewModelProvider(fragment)[EvaluationResultViewModel::class.java]
//// viewModelFragment.notifyData(list)
//// }
// is EmptyFragment -> {
// if (list.size == 1) {
// val bundle = Bundle()
// bundle.putString("QsId", list[0])
// naviController.navigate(R.id.EvaluationResultFragment, bundle)
// }
// }
// }
when (navDestination.id) {
R.id.EmptyFragment -> {
if (list.size == 1) {
val bundle = Bundle()
bundle.putString("QsId", list[0])
naviController.navigate(R.id.EvaluationResultFragment, bundle)
}
}
}
}
}
}

View File

@ -61,10 +61,10 @@ class EvaluationResultFragment : BaseFragment(), View.OnClickListener {
if (id != null) {
viewModel.loadData(id)
} else {
viewModel.loadMetadata()
viewModel.initNewData()
}
} else {
viewModel.loadMetadata()
viewModel.initNewData()
}
// //监听大分类数据变化

View File

@ -4,9 +4,8 @@ import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.navinfo.collect.library.map.GeoPoint
import com.navinfo.collect.library.map.NIMapController
import com.navinfo.collect.library.data.entity.QsRecordBean
import com.navinfo.collect.library.map.NIMapController
import com.navinfo.omqs.db.RoomAppDatabase
import dagger.hilt.android.lifecycle.HiltViewModel
import io.realm.Realm
@ -46,8 +45,10 @@ class EvaluationResultViewModel @Inject constructor(
var liveDataQsRecordBean = MutableLiveData<QsRecordBean>()
var oldBean: QsRecordBean? = null
init {
liveDataQsRecordBean.value = QsRecordBean(id = UUID.randomUUID().toString(), classType = "89193")
liveDataQsRecordBean.value = QsRecordBean(id = UUID.randomUUID().toString())
Log.e("jingo", "EvaluationResultViewModel 创建了 ${hashCode()}")
mapController.markerHandle.apply {
setOnMapClickListener {
@ -69,7 +70,7 @@ class EvaluationResultViewModel @Inject constructor(
/**
* 查询数据库获取问题分类
*/
fun loadMetadata() {
fun initNewData() {
viewModelScope.launch(Dispatchers.IO) {
getClassTypeList()
getProblemLinkList()
@ -88,7 +89,6 @@ class EvaluationResultViewModel @Inject constructor(
Log.e("jingo", "getClassTypeList S")
viewModelScope.launch(Dispatchers.IO) {
val list = roomAppDatabase.getScProblemTypeDao().findClassTypeList()
Log.e("jingo", "getClassTypeList ${list?.size}")
list?.let {
if (list.isNotEmpty()) {
//通知页面更新
@ -110,6 +110,7 @@ class EvaluationResultViewModel @Inject constructor(
* 获取问题环节列表和初步问题
*/
fun getProblemLinkList() {
Log.e("jingo", "getProblemLinkList S")
viewModelScope.launch(Dispatchers.IO) {
val list = roomAppDatabase.getScRootCauseAnalysisDao().findAllData()
list?.let { tl ->
@ -128,21 +129,26 @@ class EvaluationResultViewModel @Inject constructor(
}
if (liveDataQsRecordBean.value!!.problemLink.isEmpty()) {
liveDataQsRecordBean.value!!.problemLink = middleList[0]
Log.e("jingo", "getProblemLinkList ${middleList[0]}")
}
if (liveDataQsRecordBean.value!!.cause.isEmpty()) {
liveDataQsRecordBean.value!!.cause = rightList[0].text
Log.e("jingo", "getProblemLinkList ${rightList[0].text}")
}
liveDataQsRecordBean.postValue(liveDataQsRecordBean.value)
liveDataMiddleTypeList.postValue(middleList)
liveDataRightTypeList.postValue(rightList)
}
}
}
Log.e("jingo", "getProblemLinkList E")
}
/**
* 获取问题类型列表和问题现象
*/
private suspend fun getProblemList(classType: String) {
Log.e("jingo", "getProblemList S")
val typeList = roomAppDatabase.getScProblemTypeDao().findProblemTypeList(classType)
typeList?.let { tl ->
if (tl.isNotEmpty()) {
@ -158,10 +164,20 @@ class EvaluationResultViewModel @Inject constructor(
)
)
}
if (liveDataQsRecordBean.value!!.problemType.isEmpty()) {
liveDataQsRecordBean.value!!.problemType = typeTitleList[0]
Log.e("jingo", "getProblemList ${typeTitleList[0]}")
}
liveDataMiddleTypeList.postValue(typeTitleList)
if (liveDataQsRecordBean.value!!.phenomenon.isEmpty()) {
liveDataQsRecordBean.value!!.phenomenon = phenomenonRightList[0].text
Log.e("jingo", "getProblemList ${phenomenonRightList[0].text}")
}
liveDataQsRecordBean.postValue(liveDataQsRecordBean.value)
liveDataRightTypeList.postValue(phenomenonRightList)
}
}
Log.e("jingo", "getProblemList E")
}
/**
@ -179,45 +195,42 @@ class EvaluationResultViewModel @Inject constructor(
fun setPhenomenonMiddleBean(adapterBean: RightBean) {
liveDataQsRecordBean.value!!.phenomenon = adapterBean.text
liveDataQsRecordBean.value!!.problemType = adapterBean.title
liveDataQsRecordBean.postValue(liveDataQsRecordBean.value)
}
fun setProblemLinkMiddleBean(adapterBean: RightBean) {
liveDataQsRecordBean.value!!.cause = adapterBean.text
liveDataQsRecordBean.value!!.problemLink = adapterBean.title
liveDataQsRecordBean.postValue(liveDataQsRecordBean.value)
}
fun saveData() {
viewModelScope.launch(Dispatchers.IO) {
val realm = Realm.getDefaultInstance()
Log.e("jingo","realm hashCOde ${realm.hashCode()}")
realm.executeTransaction {
it.copyToRealmOrUpdate(liveDataQsRecordBean.value)
}
realm.close()
// realm.close()
mapController.layerManagerHandler.addOrUpdateQsRecordMark(liveDataQsRecordBean.value!!)
liveDataFinish.postValue(true)
}
}
fun deleteData() {
// viewModelScope.launch(Dispatchers.IO) {
// val qsRecord = QsRecordBean(
// id = UUID.randomUUID().toString(),
// classType = liveDataCurrentClassType.value.toString(),
// type = liveDataCurrentProblemType.value.toString(),
// phenomenon = liveDataCurrentPhenomenon.value.toString(),
// problemLink = liveDataCurrentProblemLink.value.toString(),
// cause = liveDataCurrentCause.value.toString(),
// )
// qsRecord.geometry = currentGeoPoint!!.toGeometry()
// val realm = Realm.getDefaultInstance()
// realm.executeTransaction {
// it.copyToRealmOrUpdate(qsRecord)
// }
viewModelScope.launch(Dispatchers.IO) {
val realm = Realm.getDefaultInstance()
Log.e("jingo","realm hashCOde ${realm.hashCode()}")
realm.executeTransaction {
val objects = it.where(QsRecordBean::class.java)
.equalTo("id", liveDataQsRecordBean.value?.id).findFirst()
objects?.deleteFromRealm()
}
// realm.close()
// mapController.layerManagerHandler.addOrUpdateQsRecordMark(qsRecord)
// liveDataFinish.postValue(true)
// }
mapController.layerManagerHandler.removeQsRecordMark(liveDataQsRecordBean.value!!)
liveDataFinish.postValue(true)
}
}
/**
@ -229,7 +242,8 @@ class EvaluationResultViewModel @Inject constructor(
val objects = realm.where<QsRecordBean>().equalTo("id", id).findFirst()
if (objects != null) {
liveDataQsRecordBean.postValue(realm.copyFromRealm(objects))
oldBean = realm.copyFromRealm(objects)
liveDataQsRecordBean.postValue(oldBean!!.copy())
}
}
}

View File

@ -10,6 +10,8 @@ import com.navinfo.omqs.R
import com.navinfo.omqs.databinding.AdapterOfflineMapCityBinding
import com.navinfo.omqs.bean.OfflineMapCityBean
import com.navinfo.omqs.http.offlinemapdownload.OfflineMapDownloadManager
import com.navinfo.omqs.tools.FileManager
import com.navinfo.omqs.tools.FileManager.Companion.FileDownloadStatus
import com.navinfo.omqs.ui.other.BaseRecyclerViewAdapter
import com.navinfo.omqs.ui.other.BaseViewHolder
import javax.inject.Inject
@ -31,11 +33,11 @@ class OfflineMapCityListAdapter(
if (it.tag != null) {
val cityBean = data[it.tag as Int]
when (cityBean.status) {
OfflineMapCityBean.NONE, OfflineMapCityBean.UPDATE, OfflineMapCityBean.PAUSE, OfflineMapCityBean.ERROR -> {
FileDownloadStatus.NONE, FileDownloadStatus.UPDATE, FileDownloadStatus.PAUSE, FileDownloadStatus.ERROR -> {
Log.e("jingo", "开始下载 ${cityBean.status}")
downloadManager.start(cityBean.id)
}
OfflineMapCityBean.LOADING, OfflineMapCityBean.WAITING -> {
FileDownloadStatus.LOADING, FileDownloadStatus.WAITING -> {
Log.e("jingo", "暂停 ${cityBean.status}")
downloadManager.pause(cityBean.id)
}
@ -88,37 +90,37 @@ class OfflineMapCityListAdapter(
binding.offlineMapProgress.progress =
(cityBean.currentSize * 100 / cityBean.fileSize).toInt()
when (cityBean.status) {
OfflineMapCityBean.NONE -> {
FileDownloadStatus.NONE -> {
if (binding.offlineMapProgress.visibility == View.VISIBLE) binding.offlineMapProgress.visibility =
View.INVISIBLE
binding.offlineMapDownloadBtn.text = "下载"
}
OfflineMapCityBean.WAITING -> {
FileDownloadStatus.WAITING -> {
if (binding.offlineMapProgress.visibility != View.VISIBLE) binding.offlineMapProgress.visibility =
View.VISIBLE
binding.offlineMapDownloadBtn.text = "等待中"
}
OfflineMapCityBean.LOADING -> {
FileDownloadStatus.LOADING -> {
if (binding.offlineMapProgress.visibility != View.VISIBLE) binding.offlineMapProgress.visibility =
View.VISIBLE
binding.offlineMapDownloadBtn.text = "暂停"
}
OfflineMapCityBean.PAUSE -> {
FileDownloadStatus.PAUSE -> {
if (binding.offlineMapProgress.visibility != View.VISIBLE) binding.offlineMapProgress.visibility =
View.VISIBLE
binding.offlineMapDownloadBtn.text = "继续"
}
OfflineMapCityBean.ERROR -> {
FileDownloadStatus.ERROR -> {
if (binding.offlineMapProgress.visibility != View.VISIBLE) binding.offlineMapProgress.visibility =
View.VISIBLE
binding.offlineMapDownloadBtn.text = "重试"
}
OfflineMapCityBean.DONE -> {
FileDownloadStatus.DONE -> {
if (binding.offlineMapProgress.visibility == View.VISIBLE) binding.offlineMapProgress.visibility =
View.INVISIBLE
binding.offlineMapDownloadBtn.text = "已完成"
}
OfflineMapCityBean.UPDATE -> {
FileDownloadStatus.UPDATE -> {
if (binding.offlineMapProgress.visibility == View.VISIBLE) binding.offlineMapProgress.visibility =
View.INVISIBLE
binding.offlineMapDownloadBtn.text = "更新"

View File

@ -8,11 +8,12 @@ import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import com.google.android.material.tabs.TabLayoutMediator
import com.navinfo.omqs.databinding.FragmentOfflineMapBinding
import com.navinfo.omqs.ui.fragment.BaseFragment
/**
* 离线地图总页面
*/
class OfflineMapFragment : Fragment() {
class OfflineMapFragment : BaseFragment() {
private var _binding: FragmentOfflineMapBinding? = null

View File

@ -23,6 +23,7 @@ import com.navinfo.omqs.db.ImportOMDBHelper
import com.navinfo.omqs.hilt.ImportOMDBHiltFactory
import com.navinfo.omqs.tools.CoroutineUtils
import com.navinfo.omqs.ui.activity.BaseActivity
import com.navinfo.omqs.ui.fragment.BaseFragment
import dagger.hilt.android.AndroidEntryPoint
import io.realm.Realm
import io.realm.RealmDictionary
@ -38,12 +39,13 @@ import javax.inject.Inject
* 个人中心
*/
@AndroidEntryPoint
class PersonalCenterFragment : Fragment(), FSAFActivityCallbacks {
class PersonalCenterFragment : BaseFragment(), FSAFActivityCallbacks {
private var _binding: FragmentPersonalCenterBinding? = null
private val binding get() = _binding!!
private val fileChooser by lazy { FileChooser(requireContext()) }
private val viewModel by lazy { viewModels<PersonalCenterViewModel>().value }
@Inject
lateinit var importOMDBHiltFactory: ImportOMDBHiltFactory
@ -61,9 +63,9 @@ class PersonalCenterFragment : Fragment(), FSAFActivityCallbacks {
binding.root.setNavigationItemSelectedListener {
when (it.itemId) {
R.id.personal_center_menu_offline_map ->
findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
findNavController().navigate(R.id.OfflineMapFragment)
R.id.personal_center_menu_obtain_data -> { // 生成数据根据sqlite文件生成对应的zip文件
fileChooser.openChooseFileDialog(object: FileChooserCallback() {
fileChooser.openChooseFileDialog(object : FileChooserCallback() {
override fun onCancel(reason: String) {
}
@ -71,31 +73,47 @@ class PersonalCenterFragment : Fragment(), FSAFActivityCallbacks {
val file = UriUtils.uri2File(uri)
// 开始导入数据
// 656e6372797000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
val job = CoroutineUtils.launchWithLoading(requireContext(), loadingMessage = "生成数据...") {
val importOMDBHelper: ImportOMDBHelper = importOMDBHiltFactory.obtainImportOMDBHelper(requireContext(), file, File(file.parentFile, "config.json"))
val job = CoroutineUtils.launchWithLoading(
requireContext(),
loadingMessage = "生成数据..."
) {
val importOMDBHelper: ImportOMDBHelper =
importOMDBHiltFactory.obtainImportOMDBHelper(
requireContext(),
file,
File(file.parentFile, "config.json")
)
viewModel.obtainOMDBZipData(importOMDBHelper)
}
}
})
}
R.id.personal_center_menu_import_data -> { // 导入zip数据
fileChooser.openChooseFileDialog(object: FileChooserCallback() {
fileChooser.openChooseFileDialog(object : FileChooserCallback() {
override fun onCancel(reason: String) {
}
override fun onResult(uri: Uri) {
val file = UriUtils.uri2File(uri)
// 开始导入数据
CoroutineUtils.launchWithLoading(requireContext(), loadingMessage = "导入数据...") {
val importOMDBHelper: ImportOMDBHelper = importOMDBHiltFactory.obtainImportOMDBHelper(requireContext(), file, File(file.parentFile, "config.json"))
CoroutineUtils.launchWithLoading(
requireContext(),
loadingMessage = "导入数据..."
) {
val importOMDBHelper: ImportOMDBHelper =
importOMDBHiltFactory.obtainImportOMDBHelper(
requireContext(),
file,
File(file.parentFile, "config.json")
)
viewModel.importOMDBData(importOMDBHelper)
}
}
})
}
R.id.personal_center_menu_import_yuan_data->{
R.id.personal_center_menu_import_yuan_data -> {
// 用户选中导入数据,打开文件选择器,用户选择导入的数据文件目录
fileChooser.openChooseFileDialog(object: FileChooserCallback() {
fileChooser.openChooseFileDialog(object : FileChooserCallback() {
override fun onCancel(reason: String) {
}
@ -108,6 +126,9 @@ class PersonalCenterFragment : Fragment(), FSAFActivityCallbacks {
R.id.personal_center_menu_test -> {
viewModel.readRealmData()
}
R.id.personal_center_menu_task_list -> {
findNavController().navigate(R.id.TaskListFragment)
}
}
true
}

View File

@ -0,0 +1,132 @@
package com.navinfo.omqs.ui.fragment.tasklist
import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.Observer
import com.navinfo.omqs.R
import com.navinfo.omqs.bean.TaskBean
import com.navinfo.omqs.databinding.AdapterTaskListBinding
import com.navinfo.omqs.http.taskdownload.TaskDownloadManager
import com.navinfo.omqs.tools.FileManager.Companion.FileDownloadStatus
import com.navinfo.omqs.ui.other.BaseRecyclerViewAdapter
import com.navinfo.omqs.ui.other.BaseViewHolder
/**
* 离线地图城市列表 RecyclerView 适配器
*
* RecycleView ViewHolder 中监听 ViewModel LiveData然后此时传递的 lifecycleOwner 是对应的 Fragment由于 ViewHolder 的生命周期是比 Fragment 短的所以当 ViewHolder 销毁时由于 Fragment Lifecycle 还没有结束此时 ViewHolder 会发生内存泄露监听的 LiveData 没有解绑
* 这种场景下有两种解决办法
*使用 LiveData observeForever 然后在 ViewHolder 销毁前手动调用 removeObserver
*使用 LifecycleRegistry ViewHolder 分发生命周期(这里使用了这个)
*/
class TaskListAdapter(
private val downloadManager: TaskDownloadManager, private val context: Context
) : BaseRecyclerViewAdapter<TaskBean>() {
private val downloadBtnClick = View.OnClickListener() {
if (it.tag != null) {
val taskBean = data[it.tag as Int]
when (taskBean.status) {
FileDownloadStatus.NONE, FileDownloadStatus.UPDATE, FileDownloadStatus.PAUSE, FileDownloadStatus.ERROR -> {
Log.e("jingo", "开始下载 ${taskBean.status}")
downloadManager.start(taskBean.id)
}
FileDownloadStatus.LOADING, FileDownloadStatus.WAITING -> {
Log.e("jingo", "暂停 ${taskBean.status}")
downloadManager.pause(taskBean.id)
}
else -> {
Log.e("jingo", "暂停 ${taskBean.status}")
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
val viewBinding =
AdapterTaskListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return BaseViewHolder(viewBinding)
}
override fun onViewRecycled(holder: BaseViewHolder) {
super.onViewRecycled(holder)
//页面滑动时会用holder重构页面但是对进度条的监听回调会一直返回扰乱UI所以当当前holder去重构的时候移除监听
downloadManager.removeObserver(holder.tag.toInt())
}
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
val binding: AdapterTaskListBinding =
holder.viewBinding as AdapterTaskListBinding
val taskBean = data[position]
//tag 方便onclick里拿到数据
holder.tag = taskBean.id.toString()
changeViews(binding, taskBean)
downloadManager.addTask(taskBean)
downloadManager.observer(taskBean.id, holder, DownloadObserver(taskBean.id, binding))
binding.taskDownloadBtn.tag = position
binding.taskDownloadBtn.setOnClickListener(downloadBtnClick)
binding.taskName.text = taskBean.evaluationTaskName
// binding.offlineMapCitySize.text = cityBean.getFileSizeText()
}
inner class DownloadObserver(val id: Int, val binding: AdapterTaskListBinding) :
Observer<TaskBean> {
override fun onChanged(t: TaskBean?) {
if (id == t?.id)
changeViews(binding, t)
}
}
private fun changeViews(binding: AdapterTaskListBinding, cityBean: TaskBean) {
binding.taskProgress.progress =
(cityBean.currentSize * 100 / cityBean.fileSize).toInt()
when (cityBean.status) {
FileDownloadStatus.NONE -> {
if (binding.taskProgress.visibility == View.VISIBLE) binding.taskProgress.visibility =
View.INVISIBLE
binding.taskDownloadBtn.text = "下载"
}
FileDownloadStatus.WAITING -> {
if (binding.taskProgress.visibility != View.VISIBLE) binding.taskProgress.visibility =
View.VISIBLE
binding.taskDownloadBtn.text = "等待中"
}
FileDownloadStatus.LOADING -> {
if (binding.taskProgress.visibility != View.VISIBLE) binding.taskProgress.visibility =
View.VISIBLE
binding.taskDownloadBtn.text = "暂停"
}
FileDownloadStatus.PAUSE -> {
if (binding.taskProgress.visibility != View.VISIBLE) binding.taskProgress.visibility =
View.VISIBLE
binding.taskDownloadBtn.text = "继续"
}
FileDownloadStatus.ERROR -> {
if (binding.taskProgress.visibility != View.VISIBLE) binding.taskProgress.visibility =
View.VISIBLE
binding.taskDownloadBtn.text = "重试"
}
FileDownloadStatus.DONE -> {
if (binding.taskProgress.visibility == View.VISIBLE) binding.taskProgress.visibility =
View.INVISIBLE
binding.taskDownloadBtn.text = "已完成"
}
FileDownloadStatus.UPDATE -> {
if (binding.taskProgress.visibility == View.VISIBLE) binding.taskProgress.visibility =
View.INVISIBLE
binding.taskDownloadBtn.text = "更新"
}
}
}
override fun getItemViewRes(position: Int): Int {
return R.layout.adapter_offline_map_city
}
}

View File

@ -0,0 +1,55 @@
package com.navinfo.omqs.ui.fragment.tasklist
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import com.navinfo.omqs.databinding.FragmentTaskListBinding
import com.navinfo.omqs.http.taskdownload.TaskDownloadManager
import com.navinfo.omqs.ui.fragment.BaseFragment
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class TaskListFragment : BaseFragment(){
@Inject
lateinit var downloadManager: TaskDownloadManager
private var _binding: FragmentTaskListBinding? = null
private val viewModel by viewModels<TaskListViewModel>()
private val binding get() = _binding!!
private val adapter: TaskListAdapter by lazy {
TaskListAdapter(
downloadManager,
requireContext()
)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentTaskListBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val layoutManager = LinearLayoutManager(context)
//// 设置 RecyclerView 的固定大小,避免在滚动时重新计算视图大小和布局,提高性能
binding.taskRecyclerview.setHasFixedSize(true)
binding.taskRecyclerview.layoutManager = layoutManager
binding.taskRecyclerview.adapter = adapter
viewModel.liveDataTaskList.observe(viewLifecycleOwner) {
adapter.refreshData(it)
}
viewModel.getTaskList(requireContext())
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@ -0,0 +1,61 @@
package com.navinfo.omqs.ui.fragment.tasklist
import android.content.Context
import android.util.Log
import android.widget.Toast
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.navinfo.omqs.bean.TaskBean
import com.navinfo.omqs.http.NetResult
import com.navinfo.omqs.http.NetworkService
import com.navinfo.omqs.tools.FileManager
import dagger.hilt.android.lifecycle.HiltViewModel
import io.realm.Realm
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
@HiltViewModel
class TaskListViewModel @Inject constructor(
private val networkService: NetworkService
) : ViewModel() {
val liveDataTaskList = MutableLiveData<List<TaskBean>>()
fun getTaskList(context: Context) {
viewModelScope.launch(Dispatchers.IO) {
val realm = Realm.getDefaultInstance()
Log.e("jingo","realm hashCOde ${realm.hashCode()}")
when (val result = networkService.getTaskList("02911")) {
is NetResult.Success -> {
if (result.data != null) {
realm.executeTransaction {
realm.copyToRealmOrUpdate(result.data.obj)
}
}
}
is NetResult.Error -> {
withContext(Dispatchers.Main) {
Toast.makeText(context, "${result.exception.message}", Toast.LENGTH_SHORT)
.show()
}
}
is NetResult.Failure -> {
withContext(Dispatchers.Main) {
Toast.makeText(context, "${result.code}:${result.msg}", Toast.LENGTH_SHORT)
.show()
}
}
is NetResult.Loading -> {}
else -> {}
}
val objects = realm.where(TaskBean::class.java).findAll()
liveDataTaskList.postValue(realm.copyFromRealm(objects))
// realm.close()
}
}
}

View File

@ -0,0 +1,53 @@
package com.navinfo.omqs.ui.widget
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.widget.EditText
import androidx.appcompat.widget.AppCompatEditText
import com.navinfo.omqs.R
class MyEditeText @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = android.R.attr.editTextStyle //不这样写可能有些属性用不了
) : AppCompatEditText(context, attrs, defStyleAttr) {
init {
//Edittext通知父控件自己处理自己的滑动事件
setOnTouchListener { v, event ->
if (canVerticalScroll(this)) {
v.parent.requestDisallowInterceptTouchEvent(true)
if (event.action == MotionEvent.ACTION_UP) {
v.parent.requestDisallowInterceptTouchEvent(false)
}
}
false
}
}
/**
* EditText竖直方向是否可以滚动
*
* @param //editText需要判断的EditText
* @return true可以滚动 false不可以滚动
*/
private fun canVerticalScroll(contentEt: EditText): Boolean {
//滚动的距离
val scrollY = contentEt.scrollY
//控件内容的总高度
val scrollRange = contentEt.layout.height
//控件实际显示的高度
val scrollExtent =
contentEt.height - contentEt.compoundPaddingTop - contentEt.compoundPaddingBottom
//控件内容总高度与实际显示高度的差值
val scrollDifference = scrollRange - scrollExtent
if (scrollDifference == 0) {
return false
}
return (scrollY > 0) || (scrollY < scrollDifference - 1)
}
}

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<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_height="wrap_content"
android:background="@color/cv_bg_color"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="5dp"
tools:context="com.navinfo.omqs.ui.fragment.tasklist.TaskListAdapter">
<TextView
android:id="@+id/task_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="省市名称"
android:textColor="@color/white"
android:textSize="@dimen/default_font_size" />
<TextView
android:id="@+id/task_size"
style="@style/map_size_font_style"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/task_name"
android:drawableLeft="@mipmap/point_blue"
android:layout_marginTop="5dp"
android:text="文件大小"
android:textSize="@dimen/card_title_font_3size" />
<TextView
android:id="@+id/task_download_btn"
style="@style/map_download_style_btn"
android:layout_width="60dp"
android:layout_alignTop="@id/task_name"
android:layout_alignBottom="@id/task_size"
android:layout_alignParentRight="true"
android:shadowColor="@android:color/transparent"
android:text="下载"
android:textColor="@color/btn_blue_solid"
android:textSize="@dimen/card_title_font_2size" />
<TextView
android:id="@+id/task_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginBottom="10dp"
android:layout_toLeftOf="@id/task_download_btn"
android:clickable="true"
android:focusable="false"
android:shadowColor="@android:color/transparent"
android:textColor="@color/white"
android:textSize="@dimen/card_title_font_2size" />
<com.navinfo.omqs.ui.widget.MyProgressBar
android:id="@+id/task_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="16dp"
android:layout_below="@id/task_download_btn"
android:progressDrawable="@drawable/progress_bg"
android:paddingTop="10dp"
android:visibility="invisible" />
</RelativeLayout>

View File

@ -59,7 +59,7 @@
<TextView
android:id="@+id/evaluation_class_type"
text="@{viewModel.liveDataQsRecordBean.classType}"
android:text="@{viewModel.liveDataQsRecordBean.classType}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
@ -68,7 +68,7 @@
<TextView
android:id="@+id/evaluation_problem_type"
text="@{viewModel.liveDataQsRecordBean.problemType}"
android:text="@{viewModel.liveDataQsRecordBean.problemType}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
@ -77,7 +77,7 @@
<TextView
android:id="@+id/evaluation_phenomenon"
text="@{viewModel.liveDataQsRecordBean.phenomenon}"
android:text="@{viewModel.liveDataQsRecordBean.phenomenon}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
@ -86,7 +86,7 @@
<TextView
android:id="@+id/evaluation_link"
text="@{viewModel.liveDataQsRecordBean.problemLink}"
android:text="@{viewModel.liveDataQsRecordBean.problemLink}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
@ -95,13 +95,28 @@
<TextView
android:id="@+id/evaluation_cause"
text="@{viewModel.liveDataQsRecordBean.cause}"
android:text="@{viewModel.liveDataQsRecordBean.cause}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:onClick="@{fragment.onClick}"
android:background="@drawable/fm_card_map_down_status_bg" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="备注信息" />
<com.navinfo.omqs.ui.widget.MyEditeText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入备注信息"
android:gravity="start"
android:maxLines="3"
android:lines="3"
android:text="@={viewModel.liveDataQsRecordBean.description}"
android:inputType="textMultiLine"
android:background="@drawable/fm_card_map_down_status_bg"
/>
<!-- <com.google.android.material.tabs.TabLayout-->
<!-- android:id="@+id/evaluation_class_tab_layout"-->
<!-- android:layout_width="match_parent"-->

View File

@ -17,8 +17,10 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/offline_map_city_list_recyclerview"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/offline_map_search" />
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/offline_map_search"
app:layout_constraintVertical_bias="0.0"
tools:layout_editor_absoluteX="0dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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"
android:background="@color/white"
tools:context=".ui.fragment.tasklist.TaskListFragment">
<EditText
android:id="@+id/task_search"
style="@style/input_blue_type"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="搜索"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/task_recyclerview"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/task_search"
app:layout_constraintVertical_bias="0.0"
tools:layout_editor_absoluteX="0dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -32,6 +32,10 @@
android:checkableBehavior="single">
<item android:title="小标题">
<menu>
<item
android:id="@+id/personal_center_menu_task_list"
android:icon="@drawable/baseline_person_24"
android:title="任务列表" />
<item
android:id="@+id/personal_center_menu_offline_map3"
android:icon="@drawable/baseline_person_24"
@ -40,10 +44,7 @@
android:id="@+id/personal_center_menu_offline_map4"
android:icon="@drawable/baseline_person_24"
android:title="menu_gallery" />
<item
android:id="@+id/personal_center_menu_offline_map5"
android:icon="@drawable/baseline_person_24"
android:title="menu_slideshow" />
<item
android:id="@+id/personal_center_menu_test"
android:icon="@drawable/baseline_person_24"

View File

@ -13,16 +13,20 @@
<action
android:id="@+id/action_FirstFragment_to_SecondFragment"
app:destination="@id/SecondFragment" />
app:destination="@id/OfflineMapFragment" />
</fragment>
<fragment
android:id="@+id/SecondFragment"
android:id="@+id/OfflineMapFragment"
android:name="com.navinfo.omqs.ui.fragment.offlinemap.OfflineMapFragment"
android:label="@string/second_fragment_label"
android:label="离线地图"
tools:layout="@layout/fragment_offline_map">
<action
android:id="@+id/action_SecondFragment_to_FirstFragment"
app:destination="@id/PersonalCenterFragment" />
</fragment>
<fragment
android:id="@+id/TaskListFragment"
android:name="com.navinfo.omqs.ui.fragment.tasklist.TaskListFragment"
android:label="任务列表"
tools:layout="@layout/fragment_task_list">
</fragment>
</navigation>

View File

@ -4,6 +4,7 @@ import com.navinfo.collect.library.utils.GeometryToolsKt
import io.realm.RealmObject
import io.realm.RealmSet
import io.realm.annotations.PrimaryKey
import io.realm.annotations.RealmClass
/**
@ -88,6 +89,27 @@ open class QsRecordBean @JvmOverloads constructor(
) : RealmObject() {
fun copy(): QsRecordBean {
val qs = QsRecordBean(
id = id,
elementId = elementId,
linkId = linkId,
classType = classType,
problemType = problemType,
phenomenon = phenomenon,
description = description,
problemLink = problemLink,
cause = cause,
checkUserId = checkUserId,
checkTime = checkTime,
confirmUserId = confirmUserId,
t_lifecycle = t_lifecycle,
t_status = t_status,
)
qs.geometry = geometry
return qs
}
private val tileX = RealmSet<Int>() // x方向的tile编码
private val tileY = RealmSet<Int>() // y方向的tile编码

View File

@ -4,13 +4,13 @@ import android.content.Context
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Color
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.lifecycleScope
import com.navinfo.collect.library.R
import com.navinfo.collect.library.data.entity.QsRecordBean
import com.navinfo.collect.library.map.NIMapView
import com.navinfo.collect.library.map.NIMapView.LAYER_GROUPS
import com.navinfo.collect.library.map.cluster.ClusterMarkerItem
import com.navinfo.collect.library.map.cluster.ClusterMarkerRenderer
import com.navinfo.collect.library.map.layers.MyItemizedLayer
@ -32,17 +32,19 @@ import org.oscim.backend.canvas.Bitmap
import org.oscim.backend.canvas.Paint
import org.oscim.core.GeoPoint
import org.oscim.layers.GroupLayer
import org.oscim.layers.marker.*
import org.oscim.layers.marker.MarkerInterface
import org.oscim.layers.marker.MarkerItem
import org.oscim.layers.marker.MarkerRendererFactory
import org.oscim.layers.marker.MarkerSymbol
import org.oscim.layers.tile.buildings.BuildingLayer
import org.oscim.layers.tile.vector.VectorTileLayer
import org.oscim.layers.tile.vector.labeling.LabelLayer
import org.oscim.map.Map.UpdateListener
import org.oscim.layers.tile.vector.labeling.LabelTileLoaderHook
import org.oscim.map.Map.UpdateListener
import org.oscim.tiling.source.OkHttpEngine.OkHttpFactory
import org.oscim.tiling.source.mapfile.MapFileTileSource
import java.io.File
import java.util.*
import java.util.stream.Collectors
/**
* Layer 操作
@ -196,17 +198,19 @@ open class LayerManagerHandler(context: AppCompatActivity, mapView: NIMapView) :
withContext(Dispatchers.Main) {
mMapView.updateMap(true)
}
}
/**
* 删除marker
*/
suspend fun deleteQsRecordMark(data: QsRecordBean) {
suspend fun removeQsRecordMark(data: QsRecordBean) {
for (item in itemizedLayer.itemList) {
if (item is MarkerItem) {
if (item.title == data.id) {
itemizedLayer.itemList.remove(item)
mMapView.updateMap()
itemizedLayer.populate()
return
}
}
@ -285,11 +289,12 @@ open class LayerManagerHandler(context: AppCompatActivity, mapView: NIMapView) :
mContext.lifecycleScope.launch(Dispatchers.IO) {
var list = mutableListOf<QsRecordBean>()
val realm = Realm.getDefaultInstance()
Log.e("jingo","realm hashCOde ${realm.hashCode()}")
realm.executeTransaction {
val objects = realm.where<QsRecordBean>().findAll()
list = realm.copyFromRealm(objects)
}
realm.close()
// realm.close()
for (item in list) {
createMarkerItem(item)
@ -359,11 +364,7 @@ open class LayerManagerHandler(context: AppCompatActivity, mapView: NIMapView) :
}
}
}
withContext(Dispatchers.Main) {
itemizedLayer.update()
}
// itemizedLayer.populate()
itemizedLayer.populate()
}
@ -583,7 +584,8 @@ open class LayerManagerHandler(context: AppCompatActivity, mapView: NIMapView) :
mapLifeNiLocationTileSource = MapLifeNiLocationTileSource(mContext, dbName)
}
if (vectorNiLocationTileLayer == null) {
vectorNiLocationTileLayer = VectorTileLayer(mMapView.vtmMap, mapLifeNiLocationTileSource)
vectorNiLocationTileLayer =
VectorTileLayer(mMapView.vtmMap, mapLifeNiLocationTileSource)
}
if (labelNiLocationLayer == null) {
labelNiLocationLayer =
@ -596,6 +598,7 @@ open class LayerManagerHandler(context: AppCompatActivity, mapView: NIMapView) :
fun hideNiLocationLayer() {
removeLayer(labelNiLocationLayer)
}
}
interface OnQsRecordItemClickListener {