Merge branch 'master' of gitlab.navinfo.com:CollectVehicle/OneMapQS

 Conflicts:
	app/src/main/java/com/navinfo/omqs/Constant.kt
	app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt
	app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginActivity.kt
	app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt
	app/src/main/res/layout/adapter_offline_map_city.xml
This commit is contained in:
squallzhjch 2023-04-03 10:46:26 +08:00
commit dbb1572688
19 changed files with 452 additions and 43 deletions

View File

@ -3,8 +3,8 @@ plugins {
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'com.google.dagger.hilt.android'
id 'realm-android'
}
android {
namespace 'com.navinfo.omqs'
compileSdk 33
@ -26,8 +26,8 @@ android {
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = '1.8'
@ -50,8 +50,12 @@ dependencies {
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
//
implementation 'com.github.getActivity:XXPermissions:16.5'
// https://github.com/getActivity/XXPermissions
implementation 'com.github.getActivity:XXPermissions:16.8'
// https://github.com/K1rakishou/Fuck-Storage-Access-Framework
implementation 'com.github.K1rakishou:Fuck-Storage-Access-Framework:v1.1.3'
// Android工具类库 https://blankj.com/2016/07/31/android-utils-code/
implementation 'com.blankj:utilcodex:1.30.1'
//
//hilt
implementation "com.google.dagger:hilt-android:2.44"

View File

@ -34,11 +34,10 @@
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:supportsRtl="true"
android:largeHeap="true"
android:theme="@style/Theme.OMQualityInspection"
tools:targetApi="31">
android:requestLegacyExternalStorage="true">
<activity
android:name=".ui.activity.login.LoginActivity"
android:exported="true"
@ -58,6 +57,7 @@
android:screenOrientation="landscape"
android:theme="@style/Theme.OMQualityInspection" />
<meta-data android:name="ScopedStorage" android:value="true" />
</application>
</manifest>

View File

@ -0,0 +1,95 @@
# coding:utf-8
# 合并指定目录下的omdbsqlite数据
import os
import sys
import json
import sqlite3
# 定义遍历目录的函数
def traverse_dir(path):
fileList = list()
for root, dirs, files in os.walk(path):
for file in files:
if str(file).endswith(".omdb"):
# 文件的完整路径
file_path = os.path.join(root, file)
# 处理文件,例如读取文件内容等
print(file_path)
fileList.append(file_path)
return fileList
# 打开配置文件,读取用户配置的
def openConfigJson(path):
# 读取json配置获取要抽取的表名
with open(path, "r") as f:
configMap = json.load(f)
return configMap
# 按照tableList中指定的表名合并多个源数据库到指定目标数据库中
def mergeSqliteData(originSqliteList, destSqlite, tableList):
destConn = sqlite3.connect(destSqlite)
destCursor = destConn.cursor()
for originSqlite in originSqliteList:
originConn = sqlite3.connect(originSqlite)
originCursor = originConn.cursor()
# 从源数据库中遍历取出表list中的数据
for table in tableList:
# 检查目标数据库中是否存在指定的表
containsTable = destCursor.execute(
"SELECT sql FROM sqlite_master WHERE type='table' AND name='%s'" % (table)).fetchall()
if not containsTable or len(containsTable) <= 0:
# 复制表结构
originCursor.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='%s'" % (table))
createTableSql = originCursor.fetchone()[0]
destCursor.execute(createTableSql)
destConn.commit()
originCursor.execute("Select * From " + table)
# 获取到源数据库中该表的所有数据
originData = originCursor.fetchall()
# 获取一行数据中包含多少列以此动态设置sql语句中的个数
if originData and len(originData)>0:
num_cols = len(originData[0])
placeholders = ",".join(["?"] * num_cols)
for row in originData:
destCursor.execute("INSERT INTO "+table+" VALUES ({})".format(placeholders), row)
print("{}数据已导入!".format(originSqlite))
originCursor.close()
originConn.close()
destConn.commit()
destCursor.close()
destConn.close()
if __name__ == '__main__':
params = sys.argv[1:] # 截取参数
if params:
if not params[0]:
print("请输入要合并的omdb数据的文件夹")
raise AttributeError("请输入要合并的omdb数据的文件夹")
# 获取导出文件的表配置
jsonPath = params[0] + "/config.json"
if not os.path.exists(jsonPath):
raise AttributeError("指定目录下缺少config.json配置文件")
omdbDir = params[0]
originSqliteList = traverse_dir(omdbDir) # 获取到所有的omdb数据库的路径
tableNameList = list()
configMap = openConfigJson(jsonPath)
if configMap["tables"] and len(configMap["tables"]) > 0:
for tableName in set(configMap["tables"]):
tableNameList.append(tableName)
print(tableNameList)
else:
raise AttributeError("config.json文件中没有配置抽取数据的表名")
# 开始分别连接Sqlite数据库按照指定表名合并数据
mergeSqliteData(originSqliteList, params[0]+"/output.sqlite", tableNameList)
else:
raise AttributeError("缺少参数请输入要合并的omdb数据的文件夹")

View File

@ -1,5 +1,7 @@
package com.navinfo.omqs
import io.realm.Realm
class Constant {
companion object {
/**
@ -7,7 +9,9 @@ class Constant {
*/
lateinit var ROOT_PATH: String
lateinit var MAP_PATH: String
lateinit var DATA_PATH: String
lateinit var OFFLINE_MAP_PATH: String
/**
* 服务器地址
*/
@ -20,7 +24,7 @@ class Constant {
const val message_version_right_off = "1" //立即发送
const val MESSAGE_PAGE_SIZE = 30 //消息列表一页最多数量
lateinit var realm: Realm
}
}

View File

@ -2,7 +2,12 @@ package com.navinfo.omqs
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
import io.realm.Realm
@HiltAndroidApp
class OMQSApplication : Application() {
override fun onCreate() {
super.onCreate()
Realm.init(this)
}
}

View File

@ -1,30 +1,37 @@
package com.navinfo.omqs.bean
data class OfflineMapCityBean(
val id: String,
/**
* 文件名称
*/
val fileName: String,
/**
* 城市名称
*/
val name: String,
val url: String,
val version: Long,
var fileSize: Long,
var currentSize:Long = 0,
var status:Int = NONE
) {
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 //有新版本要更新
}
import io.realm.RealmObject
enum class StatusEnum(val status: Int) {
NONE(0), WAITING(1), LOADING(2), PAUSE(3),
ERROR(4), DONE(5), UPDATE(6)
}
open class OfflineMapCityBean{
var id: String = ""
var fileName: String = ""
var name: String = ""
var url: String = ""
var version: Long = 0L
var fileSize: Long = 0L
var currentSize:Long = 0L
var status: Int = StatusEnum.NONE.status
// status的转换对象
var statusEnum:StatusEnum
get() {
return try {
StatusEnum.values().find { it.status == status }!!
} catch (e: IllegalArgumentException) {
StatusEnum.NONE
}
}
set(value) {
status = value.status
}
constructor() : super()
fun getFileSizeText(): String {
return if (fileSize < 1024.0)
"$fileSize B"

View File

@ -0,0 +1,17 @@
package com.navinfo.omqs.bean
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
open class OfflineMapCityRealmObject(){
@PrimaryKey
var id: String = ""
var fileName: String=""
var name: String = ""
var url: String = ""
var version: Long = 0
var fileSize: Long = 0
var currentSize:Long = 0
var status:Int = 0
}

View File

@ -0,0 +1,8 @@
package com.navinfo.omqs.data.process
/**
* 数据处理引擎数据导入导出及转换处理
* */
class DataEngine {
}

View File

@ -0,0 +1,90 @@
package com.navinfo.omqs.ui
import android.content.Intent
import android.os.Bundle
import androidx.core.view.WindowCompat
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import android.view.Menu
import android.view.MenuItem
import com.github.k1rakishou.fsaf.FileChooser
import com.github.k1rakishou.fsaf.callback.FSAFActivityCallbacks
import com.navinfo.omqs.R
import com.navinfo.omqs.databinding.ActivityMainBinding
import com.navinfo.omqs.ui.activity.PermissionsActivity
class MainActivity : PermissionsActivity(), FSAFActivityCallbacks {
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var binding: ActivityMainBinding
private val fileChooser by lazy { FileChooser(this@MainActivity) }
override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// val navController = findNavController(R.id.nav_host_fragment_content_main)
// appBarConfiguration = AppBarConfiguration(navController.graph)
// setupActionBarWithNavController(navController, appBarConfiguration)
fileChooser.setCallbacks(this@MainActivity)
// binding.fab.setOnClickListener { view ->
// Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
// .setAnchorView(R.id.fab)
// .setAction("Action", null).show()
// // 开始数据导入功能
// fileChooser.openChooseFileDialog(object: FileChooserCallback() {
// override fun onCancel(reason: String) {
// }
//
// override fun onResult(uri: Uri) {
// val file = UriUtils.uri2File(uri)
// Snackbar.make(view, "文件大小为:${file.length()}", Snackbar.LENGTH_LONG)
// .show()
// }
// })
// }
}
override fun onPermissionsGranted() {
}
override fun onPermissionsDenied() {
}
// override fun onCreateOptionsMenu(menu: Menu): Boolean {
// // Inflate the menu; this adds items to the action bar if it is present.
// menuInflater.inflate(R.menu.menu_main, menu)
// return true
// }
// override fun onOptionsItemSelected(item: MenuItem): Boolean {
// // Handle action bar item clicks here. The action bar will
// // automatically handle clicks on the Home/Up button, so long
// // as you specify a parent activity in AndroidManifest.xml.
// return when (item.itemId) {
// R.id.action_settings -> true
// else -> super.onOptionsItemSelected(item)
// }
// }
//
// override fun onSupportNavigateUp(): Boolean {
// val navController = findNavController(R.id.nav_host_fragment_content_main)
// return navController.navigateUp(appBarConfiguration)
// || super.onSupportNavigateUp()
// }
override fun fsafStartActivityForResult(intent: Intent, requestCode: Int) {
startActivityForResult(intent, requestCode)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
fileChooser.onActivityResult(requestCode, resultCode, data)
}
}

View File

@ -16,9 +16,10 @@ open class PermissionsActivity : BaseActivity() {
val permissionList = mutableListOf<String>()
if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.TIRAMISU) {
//文件读写
permissionList.add(Permission.READ_MEDIA_IMAGES)
permissionList.add(Permission.READ_MEDIA_AUDIO)
permissionList.add(Permission.READ_MEDIA_VIDEO)
// permissionList.add(Permission.READ_MEDIA_IMAGES)
// permissionList.add(Permission.READ_MEDIA_AUDIO)
// permissionList.add(Permission.READ_MEDIA_VIDEO)
permissionList.add(Permission.MANAGE_EXTERNAL_STORAGE)
} else {
//文件读写
permissionList.add(Permission.WRITE_EXTERNAL_STORAGE)

View File

@ -10,9 +10,12 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.navinfo.omqs.Constant
import com.navinfo.omqs.bean.LoginUserBean
import io.realm.Realm
import io.realm.RealmConfiguration
import kotlinx.coroutines.*
import okio.IOException
import java.io.File
import java.math.BigInteger
enum class LoginStatus {
/**
@ -60,6 +63,17 @@ class LoginViewModel(
loginUser.value = LoginUserBean(username = "admin", password = "123456")
}
private fun initRealm() {
val password = "password".encodeToByteArray().copyInto(ByteArray(64))
// 1110000011000010111001101110011011101110110111101110010011001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Log.d("", "密码是: ${BigInteger(1, password).toString(2).padStart(64, '0')}")
val config = RealmConfiguration.Builder()
.directory(File(Constant.DATA_PATH))
.name("HDData")
// .encryptionKey(password)
.build()
Constant.realm = Realm.getInstance(config)
}
/**
* 处理注册按钮
@ -100,6 +114,8 @@ class LoginViewModel(
try {
loginStatus.postValue(LoginStatus.LOGIN_STATUS_FOLDER_INIT)
createRootFolder(context)
// 初始化Realm
initRealm()
} catch (e: IOException) {
loginStatus.postValue(LoginStatus.LOGIN_STATUS_FOLDER_FAILURE)
}
@ -122,6 +138,12 @@ class LoginViewModel(
val file = File(Constant.MAP_PATH)
if (!file.exists()) {
file.mkdirs()
Constant.DATA_PATH = Constant.ROOT_PATH + "/data/"
with(File(Constant.MAP_PATH)) {
if(!this.exists()) this.mkdirs()
}
with(File(Constant.DATA_PATH)) {
if(!this.exists()) this.mkdirs()
}
}
}

View File

@ -1,22 +1,35 @@
package com.navinfo.omqs.ui.fragment.personalcenter
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.viewModels
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.get
import androidx.navigation.fragment.findNavController
import com.blankj.utilcode.util.UriUtils
import com.github.k1rakishou.fsaf.FileChooser
import com.github.k1rakishou.fsaf.callback.FSAFActivityCallbacks
import com.github.k1rakishou.fsaf.callback.FileChooserCallback
import com.google.android.material.snackbar.Snackbar
import com.navinfo.omqs.R
import com.navinfo.omqs.databinding.FragmentPersonalCenterBinding
/**
* 个人中心
*/
class PersonalCenterFragment : Fragment() {
class PersonalCenterFragment : Fragment(), 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 }
override fun onCreateView(
@ -25,7 +38,6 @@ class PersonalCenterFragment : Fragment() {
): View {
_binding = FragmentPersonalCenterBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -35,13 +47,37 @@ class PersonalCenterFragment : Fragment() {
when (it.itemId) {
R.id.personal_center_menu_offline_map ->
findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
R.id.personal_center_menu_import_data -> {
// 用户选中导入数据,打开文件选择器,用户选择导入的数据文件目录
fileChooser.openChooseFileDialog(object: FileChooserCallback() {
override fun onCancel(reason: String) {
}
override fun onResult(uri: Uri) {
val file = UriUtils.uri2File(uri)
// 开始导入数据
viewModel.importOmdbData(file)
}
})
}
}
true
}
fileChooser.setCallbacks(this@PersonalCenterFragment)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun fsafStartActivityForResult(intent: Intent, requestCode: Int) {
startActivityForResult(intent, requestCode)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
fileChooser.onActivityResult(requestCode, resultCode, data)
}
}

View File

@ -0,0 +1,16 @@
package com.navinfo.omqs.ui.fragment.personalcenter
import androidx.lifecycle.ViewModel
import java.io.File
class PersonalCenterViewModel: ViewModel() {
fun importOmdbData(omdbFile: File) {
// 检查File是否为sqlite数据库
if (omdbFile == null || omdbFile.exists()) {
throw Exception("文件不存在")
}
if (!omdbFile.name.endsWith(".sqlite") and !omdbFile.name.endsWith("db")) {
throw Exception("文件不存在")
}
}
}

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#747D8C"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M9,3L5,6.99h3L8,14h2L10,6.99h3L9,3zM16,17.01L16,10h-2v7.01h-3L15,21l4,-3.99h-3z"/>
</vector>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.navinfo.collect.library.map.NIMapView
android:id="@+id/main_activity_map1"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -11,9 +11,9 @@
android:icon="@drawable/baseline_map_24"
android:title="离线地图" />
<item
android:id="@+id/personal_center_menu_offline_map1"
android:icon="@drawable/baseline_person_24"
android:title="menu_gallery" />
android:id="@+id/personal_center_menu_import_data"
android:icon="@drawable/ic_baseline_import_export_24"
android:title="导入数据" />
<item
android:id="@+id/personal_center_menu_offline_map2"
android:icon="@drawable/baseline_person_24"

View File

@ -2,13 +2,12 @@
buildscript {
dependencies {
classpath "io.realm:realm-gradle-plugin:10.10.1"
classpath "io.realm:realm-gradle-plugin:10.11.1"
}
}
plugins {
id 'com.android.application' version '7.3.1' apply false
id 'com.android.library' version '7.3.1' apply false
id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
id 'io.realm.kotlin' version '0.10.0' apply false
id 'com.google.dagger.hilt.android' version '2.44' apply false
}

View File

@ -0,0 +1,48 @@
package com.navinfo.collect.library.data.entity
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
enum class StatusEnum(val status: Int) {
NONE(0), WAITING(1), LOADING(2), PAUSE(3),
ERROR(4), DONE(5), UPDATE(6)
}
open class OfflineMapCityBean @JvmOverloads constructor(@PrimaryKey var id: String = "",
var fileName: String = "",
var name: String = "",
var url: String = "",
var version: Long = 0L,
var fileSize: Long = 0L,
var currentSize: Long = 0L,
var status: Int =0) : RealmObject(){
// status的转换对象
var statusEnum:StatusEnum
get() {
return try {
StatusEnum.values().find { it.status == status }!!
} catch (e: IllegalArgumentException) {
StatusEnum.NONE
}
}
set(value) {
status = value.status
}
fun getFileSizeText(): String {
return if (fileSize < 1024.0)
"$fileSize B"
else if (fileSize < 1048576.0)
"%.2f K".format(fileSize / 1024.0)
else if (fileSize < 1073741824.0)
"%.2f M".format(fileSize / 1048576.0)
else
"%.2f M".format(fileSize / 1073741824.0)
}
// constructor(){
//
// }
//
}

View File

@ -0,0 +1,43 @@
package com.navinfo.collect.library.data.entity
import io.realm.RealmModel
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
import io.realm.annotations.RealmClass
@RealmClass
open class OfflineMapCityRealmObject: RealmModel {
@PrimaryKey
var id: String = ""
var fileName: String=""
var name: String = ""
var url: String = ""
var version: Long = 0
var fileSize: Long = 0
var currentSize:Long = 0
var status:Int = 0
constructor(){
}
constructor(
id: String,
fileName: String,
name: String,
url: String,
version: Long,
fileSize: Long,
currentSize: Long,
status: Int
) {
this.id = id
this.fileName = fileName
this.name = name
this.url = url
this.version = version
this.fileSize = fileSize
this.currentSize = currentSize
this.status = status
}
}