Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78cc909ec8 | ||
|
|
077e0e4266 |
144
.gitignore
vendored
144
.gitignore
vendored
@@ -21,4 +21,146 @@ local.properties
|
|||||||
docs
|
docs
|
||||||
linux
|
linux
|
||||||
windows
|
windows
|
||||||
android_original
|
flutter/ephemeral/
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
*.[Cc]ache
|
||||||
|
!*.[Cc]ache/
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
local.properties
|
||||||
|
*.log
|
||||||
|
captures/
|
||||||
|
.externalNativeBuild/
|
||||||
|
.cxx/
|
||||||
|
*.apk
|
||||||
|
output.json
|
||||||
|
*.iml
|
||||||
|
.idea/
|
||||||
|
misc.xml
|
||||||
|
deploymentTargetDropDown.xml
|
||||||
|
render.experimental.xml
|
||||||
|
*.jks
|
||||||
|
*.keystore
|
||||||
|
google-services.json
|
||||||
|
*.hprof
|
||||||
|
gen-external-apklibs
|
||||||
|
**/doc/api/
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.fvm/flutter_sdk
|
||||||
|
.packages
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
coverage/
|
||||||
|
lib/generated_plugin_registrant.dart
|
||||||
|
**/android/**/gradle-wrapper.jar
|
||||||
|
**/android/.gradle
|
||||||
|
**/android/captures/
|
||||||
|
**/android/gradlew
|
||||||
|
**/android/gradlew.bat
|
||||||
|
**/android/key.properties
|
||||||
|
**/android/local.properties
|
||||||
|
**/android/**/GeneratedPluginRegistrant.java
|
||||||
|
**/ios/**/*.mode1v3
|
||||||
|
**/ios/**/*.mode2v3
|
||||||
|
**/ios/**/*.moved-aside
|
||||||
|
**/ios/**/*.pbxuser
|
||||||
|
**/ios/**/*.perspectivev3
|
||||||
|
**/ios/**/*sync/
|
||||||
|
**/ios/**/.sconsign.dblite
|
||||||
|
**/ios/**/.tags*
|
||||||
|
**/ios/**/.vagrant/
|
||||||
|
**/ios/**/DerivedData/
|
||||||
|
**/ios/**/Icon?
|
||||||
|
**/ios/**/Pods/
|
||||||
|
**/ios/**/.symlinks/
|
||||||
|
**/ios/**/profile
|
||||||
|
**/ios/**/xcuserdata
|
||||||
|
**/ios/.generated/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
**/ios/Flutter/App.framework
|
||||||
|
**/ios/Flutter/Flutter.framework
|
||||||
|
**/ios/Flutter/Flutter.podspec
|
||||||
|
**/ios/Flutter/Generated.xcconfig
|
||||||
|
**/ios/Flutter/app.flx
|
||||||
|
**/ios/Flutter/app.zip
|
||||||
|
**/ios/Flutter/flutter_assets/
|
||||||
|
**/ios/Flutter/flutter_export_environment.sh
|
||||||
|
**/ios/ServiceDefinitions.json
|
||||||
|
**/ios/Runner/GeneratedPluginRegistrant.*
|
||||||
|
!**/ios/**/default.mode1v3
|
||||||
|
!**/ios/**/default.mode2v3
|
||||||
|
!**/ios/**/default.pbxuser
|
||||||
|
!**/ios/**/default.perspectivev3
|
||||||
|
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||||
|
*.ap_
|
||||||
|
*.aab
|
||||||
|
*.dex
|
||||||
|
*.class
|
||||||
|
bin/
|
||||||
|
gen/
|
||||||
|
out/
|
||||||
|
.gradle
|
||||||
|
.signing/
|
||||||
|
proguard/
|
||||||
|
/*/build/
|
||||||
|
/*/local.properties
|
||||||
|
/*/out
|
||||||
|
/*/*/build
|
||||||
|
/*/*/production
|
||||||
|
.navigation/
|
||||||
|
*.ipr
|
||||||
|
*~
|
||||||
|
*.swp
|
||||||
|
.externalNativeBuild
|
||||||
|
obj/
|
||||||
|
*.iws
|
||||||
|
/out/
|
||||||
|
.idea/caches/
|
||||||
|
.idea/libraries/
|
||||||
|
.idea/shelf/
|
||||||
|
.idea/workspace.xml
|
||||||
|
.idea/tasks.xml
|
||||||
|
.idea/.name
|
||||||
|
.idea/compiler.xml
|
||||||
|
.idea/copyright/profiles_settings.xml
|
||||||
|
.idea/encodings.xml
|
||||||
|
.idea/misc.xml
|
||||||
|
.idea/modules.xml
|
||||||
|
.idea/scopes/scope_settings.xml
|
||||||
|
.idea/dictionaries
|
||||||
|
.idea/vcs.xml
|
||||||
|
.idea/jsLibraryMappings.xml
|
||||||
|
.idea/datasources.xml
|
||||||
|
.idea/dataSources.ids
|
||||||
|
.idea/sqlDataSources.xml
|
||||||
|
.idea/dynamic.xml
|
||||||
|
.idea/uiDesigner.xml
|
||||||
|
.idea/assetWizardSettings.xml
|
||||||
|
.idea/gradle.xml
|
||||||
|
.idea/jarRepositories.xml
|
||||||
|
.idea/navEditor.xml
|
||||||
|
.classpath
|
||||||
|
.project
|
||||||
|
.cproject
|
||||||
|
.settings/
|
||||||
|
.mtj.tmp/
|
||||||
|
*.war
|
||||||
|
*.ear
|
||||||
|
hs_err_pid*
|
||||||
|
.idea_modules/
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
.idea/mongoSettings.xml
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
!/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
macos/Flutter/ephemeral/flutter_export_environment.sh
|
||||||
|
macos/Flutter/ephemeral/Flutter-Generated.xcconfig
|
||||||
|
|||||||
6
.idea/deploymentTargetSelector.xml
generated
6
.idea/deploymentTargetSelector.xml
generated
@@ -2,6 +2,12 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="deploymentTargetSelector">
|
<component name="deploymentTargetSelector">
|
||||||
<selectionStates>
|
<selectionStates>
|
||||||
|
<SelectionState runConfigName="Unnamed">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
</SelectionState>
|
||||||
|
<SelectionState runConfigName="lbjconsole_android">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
</SelectionState>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
</SelectionState>
|
</SelectionState>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# LBJ Console
|
# LBJ Console
|
||||||
|
|
||||||
LBJ Console 是一款 Android 应用程序,用于通过 BLE 从 [SX1276_Receive_LBJ](https://github.com/undef-i/SX1276_Receive_LBJ) 设备接收并显示列车预警消息,功能包括:
|
LBJ Console 是一款应用程序,用于通过 BLE 从 [SX1276_Receive_LBJ](https://github.com/undef-i/SX1276_Receive_LBJ) 设备接收并显示列车预警消息,功能包括:
|
||||||
|
|
||||||
- 接收列车预警消息,支持可选的手机推送通知。
|
- 接收列车预警消息,支持可选的手机推送通知。
|
||||||
- 在地图上显示预警消息的 GPS 信息。
|
- 在地图上显示预警消息的 GPS 信息。
|
||||||
- 基于内置数据文件显示机车配属,机车类型和车次类型。
|
- 基于内置数据文件显示机车配属,机车类型和车次类型。
|
||||||
|
|
||||||
|
主分支目前只适配了 Android 。如需在其它平台上面使用,请参考 [flutter](https://github.com/undef-i/LBJ_Console/tree/flutter) 分支自行编译。
|
||||||
## 数据文件
|
## 数据文件
|
||||||
|
|
||||||
LBJ Console 依赖以下数据文件,位于 `app/src/main/assets/` 目录,用于支持机车配属和车次信息的展示:
|
LBJ Console 依赖以下数据文件,位于 `app/src/main/assets/` 目录,用于支持机车配属和车次信息的展示:
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ android {
|
|||||||
applicationId = "org.noxylva.lbjconsole"
|
applicationId = "org.noxylva.lbjconsole"
|
||||||
minSdk = 29
|
minSdk = 29
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 13
|
versionCode = 14
|
||||||
versionName = "0.1.3"
|
versionName = "0.1.4"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
@@ -92,4 +92,5 @@ dependencies {
|
|||||||
implementation(libs.androidx.room.ktx)
|
implementation(libs.androidx.room.ktx)
|
||||||
ksp(libs.androidx.room.compiler)
|
ksp(libs.androidx.room.compiler)
|
||||||
implementation(libs.androidx.startup.runtime)
|
implementation(libs.androidx.startup.runtime)
|
||||||
|
implementation("com.google.code.gson:gson:2.10.1")
|
||||||
}
|
}
|
||||||
@@ -15,6 +15,9 @@
|
|||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
|
android:maxSdkVersion="32" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
|
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
|
||||||
|
|
||||||
@@ -42,11 +45,10 @@
|
|||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".SettingsActivity"
|
android:name=".FilePickerActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:label="Settings"
|
android:theme="@style/Theme.LBJConsole"
|
||||||
android:parentActivityName=".MainActivity"
|
android:label="数据管理" />
|
||||||
android:theme="@style/Theme.LBJConsole" />
|
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".BackgroundService"
|
android:name=".BackgroundService"
|
||||||
|
|||||||
123
app/src/main/java/org/noxylva/lbjconsole/FilePickerActivity.kt
Normal file
123
app/src/main/java/org/noxylva/lbjconsole/FilePickerActivity.kt
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
package org.noxylva.lbjconsole
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.noxylva.lbjconsole.util.DatabaseExportImportUtil
|
||||||
|
|
||||||
|
class FilePickerActivity : ComponentActivity() {
|
||||||
|
private val exportFilePicker = registerForActivityResult(
|
||||||
|
ActivityResultContracts.CreateDocument("application/json")
|
||||||
|
) { uri ->
|
||||||
|
uri?.let { exportDatabase(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private val importFilePicker = registerForActivityResult(
|
||||||
|
ActivityResultContracts.OpenDocument()
|
||||||
|
) { uri ->
|
||||||
|
uri?.let { importDatabase(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
val action = intent.getStringExtra("action") ?: "export"
|
||||||
|
|
||||||
|
setContent {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
when (action) {
|
||||||
|
"export" -> {
|
||||||
|
val fileName = "lbj_console_backup_${System.currentTimeMillis()}.json"
|
||||||
|
exportFilePicker.launch(fileName)
|
||||||
|
}
|
||||||
|
"import" -> {
|
||||||
|
importFilePicker.launch(arrayOf("application/json", "text/plain"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun exportDatabase(uri: Uri) {
|
||||||
|
val coroutineScope = kotlinx.coroutines.MainScope()
|
||||||
|
coroutineScope.launch {
|
||||||
|
try {
|
||||||
|
val databaseUtil = DatabaseExportImportUtil(this@FilePickerActivity)
|
||||||
|
val json = databaseUtil.exportDatabase()
|
||||||
|
|
||||||
|
contentResolver.openOutputStream(uri)?.use { outputStream ->
|
||||||
|
outputStream.write(json.toByteArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
Toast.makeText(
|
||||||
|
this@FilePickerActivity,
|
||||||
|
"数据导出成功",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Toast.makeText(
|
||||||
|
this@FilePickerActivity,
|
||||||
|
"导出失败: ${e.message}",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
} finally {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun importDatabase(uri: Uri) {
|
||||||
|
val coroutineScope = kotlinx.coroutines.MainScope()
|
||||||
|
coroutineScope.launch {
|
||||||
|
try {
|
||||||
|
val databaseUtil = DatabaseExportImportUtil(this@FilePickerActivity)
|
||||||
|
val success = databaseUtil.importDatabase(uri)
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
Toast.makeText(
|
||||||
|
this@FilePickerActivity,
|
||||||
|
"数据导入成功",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
this@FilePickerActivity,
|
||||||
|
"数据导入失败",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Toast.makeText(
|
||||||
|
this@FilePickerActivity,
|
||||||
|
"导入失败: ${e.message}",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
} finally {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun createExportIntent(context: android.content.Context): Intent {
|
||||||
|
return Intent(context, FilePickerActivity::class.java).apply {
|
||||||
|
putExtra("action", "export")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createImportIntent(context: android.content.Context): Intent {
|
||||||
|
return Intent(context, FilePickerActivity::class.java).apply {
|
||||||
|
putExtra("action", "import")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -364,11 +364,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
},
|
},
|
||||||
appVersion = getAppVersion(),
|
appVersion = getAppVersion(),
|
||||||
locoInfoUtil = locoInfoUtil,
|
locoInfoUtil = locoInfoUtil,
|
||||||
trainTypeUtil = trainTypeUtil,
|
trainTypeUtil = trainTypeUtil
|
||||||
onOpenSettings = {
|
|
||||||
val intent = Intent(this@MainActivity, SettingsActivity::class.java)
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (showConnectionDialog) {
|
if (showConnectionDialog) {
|
||||||
@@ -877,9 +873,7 @@ fun MainContent(
|
|||||||
mapCenterPosition: Pair<Double, Double>?,
|
mapCenterPosition: Pair<Double, Double>?,
|
||||||
mapZoomLevel: Double,
|
mapZoomLevel: Double,
|
||||||
mapRailwayLayerVisible: Boolean,
|
mapRailwayLayerVisible: Boolean,
|
||||||
onMapStateChange: (Pair<Double, Double>?, Double, Boolean) -> Unit,
|
onMapStateChange: (Pair<Double, Double>?, Double, Boolean) -> Unit
|
||||||
|
|
||||||
onOpenSettings: () -> Unit
|
|
||||||
) {
|
) {
|
||||||
val statusColor = if (isConnected) Color(0xFF4CAF50) else Color(0xFFFF5722)
|
val statusColor = if (isConnected) Color(0xFF4CAF50) else Color(0xFFFF5722)
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ class NotificationService(private val context: Context) {
|
|||||||
context,
|
context,
|
||||||
0,
|
0,
|
||||||
intent,
|
intent,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
|
||||||
)
|
)
|
||||||
|
|
||||||
val remoteViews = RemoteViews(context.packageName, R.layout.notification_train_record)
|
val remoteViews = RemoteViews(context.packageName, R.layout.notification_train_record)
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
package org.noxylva.lbjconsole
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.widget.Switch
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.noxylva.lbjconsole.database.AppSettingsRepository
|
|
||||||
|
|
||||||
class SettingsActivity : AppCompatActivity() {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
suspend fun isBackgroundServiceEnabled(context: Context): Boolean {
|
|
||||||
val repository = AppSettingsRepository(context)
|
|
||||||
return repository.getSettings().backgroundServiceEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun setBackgroundServiceEnabled(context: Context, enabled: Boolean) {
|
|
||||||
val repository = AppSettingsRepository(context)
|
|
||||||
repository.updateBackgroundServiceEnabled(enabled)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private lateinit var backgroundServiceSwitch: Switch
|
|
||||||
private lateinit var appSettingsRepository: AppSettingsRepository
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
setContentView(R.layout.activity_settings)
|
|
||||||
|
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
|
||||||
supportActionBar?.title = "Settings"
|
|
||||||
|
|
||||||
appSettingsRepository = AppSettingsRepository(this)
|
|
||||||
|
|
||||||
initViews()
|
|
||||||
setupListeners()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initViews() {
|
|
||||||
backgroundServiceSwitch = findViewById(R.id.switch_background_service)
|
|
||||||
lifecycleScope.launch {
|
|
||||||
backgroundServiceSwitch.isChecked = isBackgroundServiceEnabled(this@SettingsActivity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupListeners() {
|
|
||||||
backgroundServiceSwitch.setOnCheckedChangeListener { _, isChecked ->
|
|
||||||
lifecycleScope.launch {
|
|
||||||
setBackgroundServiceEnabled(this@SettingsActivity, isChecked)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isChecked) {
|
|
||||||
BackgroundService.startService(this)
|
|
||||||
} else {
|
|
||||||
BackgroundService.stopService(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSupportNavigateUp(): Boolean {
|
|
||||||
onBackPressed()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.noxylva.lbjconsole.ui.screens
|
package org.noxylva.lbjconsole.ui.screens
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
@@ -19,9 +20,10 @@ import kotlinx.coroutines.launch
|
|||||||
import org.noxylva.lbjconsole.model.MergeSettings
|
import org.noxylva.lbjconsole.model.MergeSettings
|
||||||
import org.noxylva.lbjconsole.model.GroupBy
|
import org.noxylva.lbjconsole.model.GroupBy
|
||||||
import org.noxylva.lbjconsole.model.TimeWindow
|
import org.noxylva.lbjconsole.model.TimeWindow
|
||||||
import org.noxylva.lbjconsole.SettingsActivity
|
import org.noxylva.lbjconsole.database.AppSettingsRepository
|
||||||
import org.noxylva.lbjconsole.BackgroundService
|
import org.noxylva.lbjconsole.BackgroundService
|
||||||
import org.noxylva.lbjconsole.NotificationService
|
import org.noxylva.lbjconsole.NotificationService
|
||||||
|
import org.noxylva.lbjconsole.FilePickerActivity
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
@@ -199,7 +201,8 @@ fun SettingsScreen(
|
|||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
LaunchedEffect(context) {
|
LaunchedEffect(context) {
|
||||||
backgroundServiceEnabled = SettingsActivity.isBackgroundServiceEnabled(context)
|
val repository = AppSettingsRepository(context)
|
||||||
|
backgroundServiceEnabled = repository.getSettings().backgroundServiceEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
var notificationEnabled by remember(context, notificationService) {
|
var notificationEnabled by remember(context, notificationService) {
|
||||||
@@ -231,7 +234,8 @@ fun SettingsScreen(
|
|||||||
onCheckedChange = { enabled ->
|
onCheckedChange = { enabled ->
|
||||||
backgroundServiceEnabled = enabled
|
backgroundServiceEnabled = enabled
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
SettingsActivity.setBackgroundServiceEnabled(context, enabled)
|
val repository = AppSettingsRepository(context)
|
||||||
|
repository.updateBackgroundServiceEnabled(enabled)
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
BackgroundService.startService(context)
|
BackgroundService.startService(context)
|
||||||
} else {
|
} else {
|
||||||
@@ -424,6 +428,66 @@ fun SettingsScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f)
|
||||||
|
),
|
||||||
|
shape = RoundedCornerShape(16.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(20.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Storage,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"数据管理",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceEvenly
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
val intent = FilePickerActivity.createExportIntent(context)
|
||||||
|
context.startActivity(intent)
|
||||||
|
},
|
||||||
|
modifier = Modifier.weight(1f).padding(horizontal = 4.dp)
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.Upload, contentDescription = null)
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
Text("导出")
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
val intent = FilePickerActivity.createImportIntent(context)
|
||||||
|
context.startActivity(intent)
|
||||||
|
},
|
||||||
|
modifier = Modifier.weight(1f).padding(horizontal = 4.dp)
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.Download, contentDescription = null)
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
Text("导入")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "LBJ Console v$appVersion by undef-i",
|
text = "LBJ Console v$appVersion by undef-i",
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package org.noxylva.lbjconsole.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.noxylva.lbjconsole.database.AppSettingsEntity
|
||||||
|
import org.noxylva.lbjconsole.database.TrainDatabase
|
||||||
|
import org.noxylva.lbjconsole.database.TrainRecordEntity
|
||||||
|
import java.io.*
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.resumeWithException
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
|
class DatabaseExportImportUtil(private val context: Context) {
|
||||||
|
private val gson = Gson()
|
||||||
|
private val database = TrainDatabase.getDatabase(context)
|
||||||
|
|
||||||
|
data class SimpleRecordBackup(
|
||||||
|
val records: List<TrainRecordEntity>
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun exportDatabase(): String = withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val trainRecords = database.trainRecordDao().getAllRecords()
|
||||||
|
|
||||||
|
val backup = SimpleRecordBackup(
|
||||||
|
records = trainRecords
|
||||||
|
)
|
||||||
|
|
||||||
|
val json = gson.toJson(backup)
|
||||||
|
json
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("DatabaseExport", "导出失败", e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun importDatabase(uri: Uri): Boolean = withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
context.contentResolver.openInputStream(uri)?.use { inputStream ->
|
||||||
|
val json = inputStream.bufferedReader().use { it.readText() }
|
||||||
|
val backup: SimpleRecordBackup = gson.fromJson(json, object : TypeToken<SimpleRecordBackup>() {}.type)
|
||||||
|
|
||||||
|
database.trainRecordDao().deleteAllRecords()
|
||||||
|
|
||||||
|
if (backup.records.isNotEmpty()) {
|
||||||
|
database.trainRecordDao().insertRecords(backup.records)
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
} ?: false
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("DatabaseImport", "导入失败", e)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getExportFileUri(): Uri {
|
||||||
|
val filePath = exportDatabase()
|
||||||
|
return Uri.parse("file://$filePath")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFFFFF"
|
|
||||||
android:pathData="M12,6m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFFFFF"
|
|
||||||
android:pathData="M12,10L12,10c-2.2,0 -4,1.8 -4,4v6h8v-6C16,11.8 14.2,10 12,10z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:padding="16dp"
|
|
||||||
android:background="?android:attr/selectableItemBackground"
|
|
||||||
android:clickable="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Background Service"
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:textColor="@android:color/black"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Keep app running in background"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:textColor="@android:color/darker_gray"
|
|
||||||
android:layout_marginTop="4dp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<Switch
|
|
||||||
android:id="@+id/switch_background_service"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1dp"
|
|
||||||
android:background="@android:color/darker_gray"
|
|
||||||
android:layout_marginHorizontal="16dp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
Reference in New Issue
Block a user