diff --git a/app/build.gradle b/app/build.gradle index 69bf5cd0..a6212aa7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4e0cee42..f32f5edd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -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"> + \ No newline at end of file diff --git a/app/src/main/assets/MergeOMDB.py b/app/src/main/assets/MergeOMDB.py new file mode 100644 index 00000000..401524b1 --- /dev/null +++ b/app/src/main/assets/MergeOMDB.py @@ -0,0 +1,95 @@ +# coding:utf-8 +# 合并指定目录下的omdb(sqlite)数据 + +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数据的文件夹") diff --git a/app/src/main/java/com/navinfo/omqs/Constant.kt b/app/src/main/java/com/navinfo/omqs/Constant.kt index bc53b658..446bfc16 100644 --- a/app/src/main/java/com/navinfo/omqs/Constant.kt +++ b/app/src/main/java/com/navinfo/omqs/Constant.kt @@ -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 } } \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/OMQSApplication.kt b/app/src/main/java/com/navinfo/omqs/OMQSApplication.kt index 7e85a078..7205a54d 100644 --- a/app/src/main/java/com/navinfo/omqs/OMQSApplication.kt +++ b/app/src/main/java/com/navinfo/omqs/OMQSApplication.kt @@ -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) + } } \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt b/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt index 860173a2..d4485441 100644 --- a/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt +++ b/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt @@ -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" diff --git a/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityRealmObject.kt b/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityRealmObject.kt new file mode 100644 index 00000000..92461d60 --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityRealmObject.kt @@ -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 +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/data/process/DataEngine.kt b/app/src/main/java/com/navinfo/omqs/data/process/DataEngine.kt new file mode 100644 index 00000000..581343c3 --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/data/process/DataEngine.kt @@ -0,0 +1,8 @@ +package com.navinfo.omqs.data.process + +/** + * 数据处理引擎,数据导入、导出及转换处理 + * */ +class DataEngine { + +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/MainActivity.kt b/app/src/main/java/com/navinfo/omqs/ui/MainActivity.kt new file mode 100644 index 00000000..b0293ec3 --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/ui/MainActivity.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/PermissionsActivity.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/PermissionsActivity.kt index 37cebe65..fa584a95 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/activity/PermissionsActivity.kt +++ b/app/src/main/java/com/navinfo/omqs/ui/activity/PermissionsActivity.kt @@ -16,9 +16,10 @@ open class PermissionsActivity : BaseActivity() { val permissionList = mutableListOf() 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) diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt index ad02cc83..26072081 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt +++ b/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt @@ -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() } } } diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterFragment.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterFragment.kt index b9b9992d..37d7f1dd 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterFragment.kt +++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterFragment.kt @@ -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().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) + } } \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterViewModel.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterViewModel.kt new file mode 100644 index 00000000..a0217e7d --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterViewModel.kt @@ -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("文件不存在") + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_import_export_24.xml b/app/src/main/res/drawable/ic_baseline_import_export_24.xml new file mode 100644 index 00000000..0c6ecd72 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_import_export_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_map_test.xml b/app/src/main/res/layout/activity_map_test.xml new file mode 100644 index 00000000..0d477f41 --- /dev/null +++ b/app/src/main/res/layout/activity_map_test.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/personal_center_menu.xml b/app/src/main/res/menu/personal_center_menu.xml index e66e3705..4e3502df 100644 --- a/app/src/main/res/menu/personal_center_menu.xml +++ b/app/src/main/res/menu/personal_center_menu.xml @@ -11,9 +11,9 @@ android:icon="@drawable/baseline_map_24" android:title="离线地图" /> + android:id="@+id/personal_center_menu_import_data" + android:icon="@drawable/ic_baseline_import_export_24" + android:title="导入数据" />