chore: update project name and .gitignore file

This commit is contained in:
Nedifinita
2025-08-08 19:20:26 +08:00
parent 65bf7b52c6
commit eb33fa7feb
4 changed files with 83 additions and 312 deletions

5
.gitignore vendored
View File

@@ -18,4 +18,7 @@ local.properties
*.jks
*.keystore
*.base64
docs
docs
linux
windows
android_original

2
.idea/.name generated
View File

@@ -1 +1 @@
LBJ Receiver
LBJ_Console

View File

@@ -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
}

View File

@@ -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
)
}
}