chore: update project name and .gitignore file
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -18,4 +18,7 @@ local.properties
|
||||
*.jks
|
||||
*.keystore
|
||||
*.base64
|
||||
docs
|
||||
docs
|
||||
linux
|
||||
windows
|
||||
android_original
|
||||
2
.idea/.name
generated
2
.idea/.name
generated
@@ -1 +1 @@
|
||||
LBJ Receiver
|
||||
LBJ_Console
|
||||
@@ -9,7 +9,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
@Database(
|
||||
entities = [TrainRecordEntity::class, AppSettingsEntity::class],
|
||||
version = 3,
|
||||
version = 4,
|
||||
exportSchema = false
|
||||
)
|
||||
abstract class TrainDatabase : RoomDatabase() {
|
||||
@@ -54,13 +54,89 @@ abstract class TrainDatabase : RoomDatabase() {
|
||||
}
|
||||
}
|
||||
|
||||
val MIGRATION_3_4 = object : Migration(3, 4) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
// Since we can't determine the exact schema change, we'll use fallback migration
|
||||
// This will preserve data where possible while updating the schema
|
||||
|
||||
// Create new table with correct schema
|
||||
database.execSQL("""
|
||||
CREATE TABLE IF NOT EXISTS `app_settings_new` (
|
||||
`id` INTEGER NOT NULL,
|
||||
`deviceName` TEXT NOT NULL DEFAULT 'LBJReceiver',
|
||||
`currentTab` INTEGER NOT NULL DEFAULT 0,
|
||||
`historyEditMode` INTEGER NOT NULL DEFAULT 0,
|
||||
`historySelectedRecords` TEXT NOT NULL DEFAULT '',
|
||||
`historyExpandedStates` TEXT NOT NULL DEFAULT '',
|
||||
`historyScrollPosition` INTEGER NOT NULL DEFAULT 0,
|
||||
`historyScrollOffset` INTEGER NOT NULL DEFAULT 0,
|
||||
`settingsScrollPosition` INTEGER NOT NULL DEFAULT 0,
|
||||
`mapCenterLat` REAL,
|
||||
`mapCenterLon` REAL,
|
||||
`mapZoomLevel` REAL NOT NULL DEFAULT 10.0,
|
||||
`mapRailwayLayerVisible` INTEGER NOT NULL DEFAULT 1,
|
||||
`specifiedDeviceAddress` TEXT,
|
||||
`searchOrderList` TEXT NOT NULL DEFAULT '',
|
||||
`autoConnectEnabled` INTEGER NOT NULL DEFAULT 1,
|
||||
`backgroundServiceEnabled` INTEGER NOT NULL DEFAULT 0,
|
||||
`notificationEnabled` INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(`id`)
|
||||
)
|
||||
""")
|
||||
|
||||
// Copy data from old table to new table, handling missing columns
|
||||
try {
|
||||
database.execSQL("""
|
||||
INSERT INTO `app_settings_new` (
|
||||
id, deviceName, currentTab, historyEditMode, historySelectedRecords,
|
||||
historyExpandedStates, historyScrollPosition, historyScrollOffset,
|
||||
settingsScrollPosition, mapCenterLat, mapCenterLon, mapZoomLevel,
|
||||
mapRailwayLayerVisible
|
||||
)
|
||||
SELECT
|
||||
COALESCE(id, 1),
|
||||
COALESCE(deviceName, 'LBJReceiver'),
|
||||
COALESCE(currentTab, 0),
|
||||
COALESCE(historyEditMode, 0),
|
||||
COALESCE(historySelectedRecords, ''),
|
||||
COALESCE(historyExpandedStates, ''),
|
||||
COALESCE(historyScrollPosition, 0),
|
||||
COALESCE(historyScrollOffset, 0),
|
||||
COALESCE(settingsScrollPosition, 0),
|
||||
mapCenterLat,
|
||||
mapCenterLon,
|
||||
COALESCE(mapZoomLevel, 10.0),
|
||||
COALESCE(mapRailwayLayerVisible, 1)
|
||||
FROM `app_settings`
|
||||
""")
|
||||
} catch (e: Exception) {
|
||||
// If the old table doesn't exist or has different structure, insert default
|
||||
database.execSQL("""
|
||||
INSERT INTO `app_settings_new` (
|
||||
id, deviceName, currentTab, historyEditMode, historySelectedRecords,
|
||||
historyExpandedStates, historyScrollPosition, historyScrollOffset,
|
||||
settingsScrollPosition, mapZoomLevel, mapRailwayLayerVisible,
|
||||
searchOrderList, autoConnectEnabled, backgroundServiceEnabled,
|
||||
notificationEnabled
|
||||
) VALUES (
|
||||
1, 'LBJReceiver', 0, 0, '', '', 0, 0, 0, 10.0, 1, '', 1, 0, 0
|
||||
)
|
||||
""")
|
||||
}
|
||||
|
||||
// Drop old table and rename new table
|
||||
database.execSQL("DROP TABLE IF EXISTS `app_settings`")
|
||||
database.execSQL("ALTER TABLE `app_settings_new` RENAME TO `app_settings`")
|
||||
}
|
||||
}
|
||||
|
||||
fun getDatabase(context: Context): TrainDatabase {
|
||||
return INSTANCE ?: synchronized(this) {
|
||||
val instance = Room.databaseBuilder(
|
||||
context.applicationContext,
|
||||
TrainDatabase::class.java,
|
||||
"train_database"
|
||||
).addMigrations(MIGRATION_1_2, MIGRATION_2_3).build()
|
||||
).addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4).build()
|
||||
INSTANCE = instance
|
||||
instance
|
||||
}
|
||||
|
||||
@@ -1,308 +0,0 @@
|
||||
package org.noxylva.lbjconsole.ui.screens
|
||||
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.clickable
|
||||
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import kotlinx.coroutines.delay
|
||||
import org.noxylva.lbjconsole.model.TrainRecord
|
||||
import org.noxylva.lbjconsole.ui.components.TrainDetailDialog
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@Composable
|
||||
fun MonitorScreen(
|
||||
latestRecord: TrainRecord?,
|
||||
recentRecords: List<TrainRecord>,
|
||||
lastUpdateTime: Date?,
|
||||
temporaryStatusMessage: String? = null,
|
||||
onRecordClick: (TrainRecord) -> Unit,
|
||||
onClearLog: () -> Unit
|
||||
) {
|
||||
var showDetailDialog by remember { mutableStateOf(false) }
|
||||
var selectedRecord by remember { mutableStateOf<TrainRecord?>(null) }
|
||||
var isPressed by remember { mutableStateOf(false) }
|
||||
|
||||
val scale by animateFloatAsState(
|
||||
targetValue = if (isPressed) 0.98f else 1f,
|
||||
animationSpec = tween(durationMillis = 120),
|
||||
label = "content_scale"
|
||||
)
|
||||
|
||||
LaunchedEffect(isPressed) {
|
||||
if (isPressed) {
|
||||
delay(100)
|
||||
isPressed = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val timeSinceLastUpdate = remember { mutableStateOf<String?>(null) }
|
||||
LaunchedEffect(key1 = lastUpdateTime) {
|
||||
if (lastUpdateTime != null) {
|
||||
while (true) {
|
||||
val now = Date()
|
||||
val diffInSec = (now.time - lastUpdateTime.time) / 1000
|
||||
timeSinceLastUpdate.value = when {
|
||||
diffInSec < 60 -> "${diffInSec}秒前"
|
||||
diffInSec < 3600 -> "${diffInSec / 60}分钟前"
|
||||
else -> "${diffInSec / 3600}小时前"
|
||||
}
|
||||
val updateInterval = if (diffInSec < 60) 500L else if (diffInSec < 3600) 30000L else 300000L
|
||||
delay(updateInterval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize().padding(16.dp)) {
|
||||
Card(modifier = Modifier.fillMaxSize()) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(20.dp)
|
||||
) {
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = timeSinceLastUpdate.value ?: "暂无数据",
|
||||
fontSize = 14.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = latestRecord,
|
||||
transitionSpec = {
|
||||
fadeIn(
|
||||
animationSpec = tween(
|
||||
durationMillis = 300,
|
||||
easing = FastOutSlowInEasing
|
||||
)
|
||||
) + slideInVertically(
|
||||
initialOffsetY = { it / 4 },
|
||||
animationSpec = tween(
|
||||
durationMillis = 300,
|
||||
easing = FastOutSlowInEasing
|
||||
)
|
||||
) togetherWith fadeOut(
|
||||
animationSpec = tween(
|
||||
durationMillis = 150,
|
||||
easing = FastOutLinearInEasing
|
||||
)
|
||||
) + slideOutVertically(
|
||||
targetOffsetY = { -it / 4 },
|
||||
animationSpec = tween(
|
||||
durationMillis = 150,
|
||||
easing = FastOutLinearInEasing
|
||||
)
|
||||
)
|
||||
},
|
||||
label = "content_animation"
|
||||
) { record ->
|
||||
if (record != null) {
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(bounded = true)
|
||||
) {
|
||||
isPressed = true
|
||||
selectedRecord = record
|
||||
showDetailDialog = true
|
||||
onRecordClick(record)
|
||||
}
|
||||
.padding(8.dp)
|
||||
.graphicsLayer {
|
||||
scaleX = scale
|
||||
scaleY = scale
|
||||
}
|
||||
) {
|
||||
|
||||
val recordMap = record.toMap()
|
||||
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
text = recordMap["train"]?.toString() ?: "",
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 20.sp,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
|
||||
Text(
|
||||
text = recordMap["direction"]?.toString() ?: "",
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 16.sp,
|
||||
color = when(recordMap["direction"]?.toString()) {
|
||||
"上行" -> MaterialTheme.colorScheme.primary
|
||||
"下行" -> MaterialTheme.colorScheme.secondary
|
||||
else -> MaterialTheme.colorScheme.onSurface
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
|
||||
|
||||
if (recordMap.containsKey("time")) {
|
||||
recordMap["time"]?.split("\n")?.forEach { timeLine ->
|
||||
Text(
|
||||
text = timeLine,
|
||||
fontSize = 14.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider(thickness = 0.5.dp)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
recordMap["speed"]?.let { speed ->
|
||||
Text(
|
||||
text = speed,
|
||||
fontSize = 14.sp,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
recordMap["position"]?.let { position ->
|
||||
Text(
|
||||
text = position,
|
||||
fontSize = 14.sp,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
recordMap.forEach { (key, value) ->
|
||||
when (key) {
|
||||
"timestamp", "train", "direction", "time", "speed", "position", "position_info" -> {}
|
||||
else -> {
|
||||
Text(
|
||||
text = value,
|
||||
fontSize = 14.sp,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (recordMap.containsKey("position_info")) {
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = recordMap["position_info"] ?: "",
|
||||
fontSize = 14.sp,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text(
|
||||
"暂无列车信息",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.outline
|
||||
)
|
||||
|
||||
if (lastUpdateTime != null) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
"上次接收数据: ${SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(lastUpdateTime)}",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (showDetailDialog && selectedRecord != null) {
|
||||
TrainDetailDialog(
|
||||
trainRecord = selectedRecord!!,
|
||||
onDismiss = { showDetailDialog = false }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InfoItem(
|
||||
label: String,
|
||||
value: String,
|
||||
fontSize: TextUnit = 14.sp
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 2.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "$label: ",
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = fontSize,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
||||
Text(
|
||||
text = value,
|
||||
fontSize = fontSize,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user