feat: update locomotive type support and optimized position display format
This commit is contained in:
@@ -3,14 +3,15 @@
|
|||||||
LBJ Console 是一款 Android 应用程序,用于通过 BLE 从 [SX1276_Receive_LBJ](https://github.com/undef-i/SX1276_Receive_LBJ) 设备接收并显示列车预警消息,功能包括:
|
LBJ Console 是一款 Android 应用程序,用于通过 BLE 从 [SX1276_Receive_LBJ](https://github.com/undef-i/SX1276_Receive_LBJ) 设备接收并显示列车预警消息,功能包括:
|
||||||
|
|
||||||
- 接收列车预警消息,支持可选的手机推送通知。
|
- 接收列车预警消息,支持可选的手机推送通知。
|
||||||
- 显示预警消息的 GPS 信息于地图。
|
- 在地图上显示预警消息的 GPS 信息。
|
||||||
- 基于内置数据文件显示机车配属和车次类型。
|
- 基于内置数据文件显示机车配属,机车类型和车次类型。
|
||||||
|
|
||||||
|
|
||||||
## 数据文件
|
## 数据文件
|
||||||
|
|
||||||
LBJ Console 依赖以下数据文件,位于 `app/src/main/assets/` 目录,用于支持机车配属和车次信息的展示:
|
LBJ Console 依赖以下数据文件,位于 `app/src/main/assets/` 目录,用于支持机车配属和车次信息的展示:
|
||||||
- `loco_info.csv`:包含机车配属信息,格式为 `机车型号,机车编号起始值,机车编号结束值,所属铁路局及机务段,备注`。
|
- `loco_info.csv`:包含机车配属信息,格式为 `机车型号,机车编号起始值,机车编号结束值,所属铁路局及机务段,备注`。
|
||||||
|
- `loco_type_info.csv`:包含机车类型编码信息,格式为 `机车类型编码,机车类型`。
|
||||||
- `train_info.csv`:包含车次类型信息,格式为 `正则表达式,车次类型`。
|
- `train_info.csv`:包含车次类型信息,格式为 `正则表达式,车次类型`。
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ android {
|
|||||||
applicationId = "org.noxylva.lbjconsole"
|
applicationId = "org.noxylva.lbjconsole"
|
||||||
minSdk = 29
|
minSdk = 29
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 11
|
versionCode = 13
|
||||||
versionName = "0.1.2"
|
versionName = "0.1.3"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ class NotificationService(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isValidValue(trainRecord.position)) {
|
if (isValidValue(trainRecord.position)) {
|
||||||
remoteViews.setTextViewText(R.id.notification_position, "${trainRecord.position.trim()}K")
|
remoteViews.setTextViewText(R.id.notification_position, "${trainRecord.position.trim().removeSuffix(".")}K")
|
||||||
remoteViews.setViewVisibility(R.id.notification_position, View.VISIBLE)
|
remoteViews.setViewVisibility(R.id.notification_position, View.VISIBLE)
|
||||||
} else {
|
} else {
|
||||||
remoteViews.setViewVisibility(R.id.notification_position, View.GONE)
|
remoteViews.setViewVisibility(R.id.notification_position, View.GONE)
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ class TrainRecord(jsonData: JSONObject? = null) {
|
|||||||
time = jsonData.optString("time", "")
|
time = jsonData.optString("time", "")
|
||||||
loco = jsonData.optString("loco", "")
|
loco = jsonData.optString("loco", "")
|
||||||
|
|
||||||
// 不再直接从JSON获取loco_type,而是从loco字段前三位获取
|
|
||||||
locoType = if (loco.isNotEmpty()) {
|
locoType = if (loco.isNotEmpty()) {
|
||||||
val prefix = if (loco.length >= 3) loco.take(3) else loco
|
val prefix = if (loco.length >= 3) loco.take(3) else loco
|
||||||
LocoTypeUtil?.getLocoTypeByCode(prefix) ?: ""
|
LocoTypeUtil?.getLocoTypeByCode(prefix) ?: ""
|
||||||
@@ -161,12 +160,14 @@ class TrainRecord(jsonData: JSONObject? = null) {
|
|||||||
trainDisplay?.takeIf { it.isNotEmpty() }?.let { map["train"] = it }
|
trainDisplay?.takeIf { it.isNotEmpty() }?.let { map["train"] = it }
|
||||||
|
|
||||||
if (directionText != "未知") map["direction"] = directionText
|
if (directionText != "未知") map["direction"] = directionText
|
||||||
if (isValidValue(speed)) map["speed"] = "速度: ${speed.trim()} km/h"
|
if (isValidValue(speed)) map["speed"] = "${speed.trim()} km/h"
|
||||||
if (isValidValue(position)) map["position"] = "位置: ${position.trim()} km"
|
if (isValidValue(position)) {
|
||||||
|
map["position"] = "${position.trim().removeSuffix(".")} K"
|
||||||
|
}
|
||||||
val timeToDisplay = if (showDetailedTime) {
|
val timeToDisplay = if (showDetailedTime) {
|
||||||
val dateFormat = java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss", java.util.Locale.getDefault())
|
val dateFormat = java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss", java.util.Locale.getDefault())
|
||||||
if (isValidValue(time)) {
|
if (isValidValue(time)) {
|
||||||
"列车时间: $time\n接收时间: ${dateFormat.format(receivedTimestamp)}"
|
"$time\n${dateFormat.format(receivedTimestamp)}"
|
||||||
} else {
|
} else {
|
||||||
dateFormat.format(receivedTimestamp)
|
dateFormat.format(receivedTimestamp)
|
||||||
}
|
}
|
||||||
@@ -180,13 +181,13 @@ class TrainRecord(jsonData: JSONObject? = null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
map["time"] = timeToDisplay
|
map["time"] = timeToDisplay
|
||||||
if (isValidValue(loco)) map["loco"] = "机车号: ${loco.trim()}"
|
if (isValidValue(loco)) map["loco"] = "${loco.trim()}"
|
||||||
if (isValidValue(locoType)) map["loco_type"] = "型号: ${locoType.trim()}"
|
if (isValidValue(locoType)) map["loco_type"] = "${locoType.trim()}"
|
||||||
if (isValidValue(route)) map["route"] = "线路: ${route.trim()}"
|
if (isValidValue(route)) map["route"] = "${route.trim()}"
|
||||||
if (isValidValue(positionInfo) && !positionInfo.trim().matches(Regex(".*(<NUL>|\\s)*.*"))) {
|
if (isValidValue(positionInfo) && !positionInfo.trim().matches(Regex(".*(<NUL>|\\s)*.*"))) {
|
||||||
map["position_info"] = "位置信息: ${positionInfo.trim()}"
|
map["position_info"] = "${positionInfo.trim()}"
|
||||||
}
|
}
|
||||||
if (rssi != 0.0) map["rssi"] = "信号强度: $rssi dBm"
|
if (rssi != 0.0) map["rssi"] = "$rssi dBm"
|
||||||
|
|
||||||
return map
|
return map
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,172 +0,0 @@
|
|||||||
package org.noxylva.lbjconsole.ui.components
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material3.*
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
|
||||||
import androidx.compose.ui.window.Dialog
|
|
||||||
import androidx.compose.ui.window.DialogProperties
|
|
||||||
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
|
|
||||||
import org.osmdroid.views.MapView
|
|
||||||
import org.osmdroid.views.overlay.Marker
|
|
||||||
import org.noxylva.lbjconsole.model.TrainRecord
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun TrainDetailDialog(
|
|
||||||
trainRecord: TrainRecord,
|
|
||||||
onDismiss: () -> Unit
|
|
||||||
) {
|
|
||||||
val recordMap = trainRecord.toMap()
|
|
||||||
val coordinates = remember { trainRecord.getCoordinates() }
|
|
||||||
|
|
||||||
Dialog(
|
|
||||||
onDismissRequest = onDismiss,
|
|
||||||
properties = DialogProperties(
|
|
||||||
dismissOnBackPress = true,
|
|
||||||
dismissOnClickOutside = true
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Card(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(16.dp),
|
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(16.dp)
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
) {
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = "列车详情",
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
modifier = Modifier.padding(bottom = 16.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
|
||||||
) {
|
|
||||||
DetailItem("列车号", recordMap["train"] ?: "--")
|
|
||||||
DetailItem("方向", recordMap["direction"] ?: "未知")
|
|
||||||
}
|
|
||||||
|
|
||||||
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
|
|
||||||
|
|
||||||
|
|
||||||
DetailItem("接收时间", recordMap["timestamp"] ?: "--")
|
|
||||||
DetailItem("列车时间", recordMap["time"] ?: "--")
|
|
||||||
|
|
||||||
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
|
|
||||||
|
|
||||||
|
|
||||||
DetailItem("速度", recordMap["speed"] ?: "--")
|
|
||||||
DetailItem("位置", recordMap["position"] ?: "--")
|
|
||||||
DetailItem("位置信息", recordMap["position_info"] ?: "--")
|
|
||||||
|
|
||||||
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
|
|
||||||
|
|
||||||
|
|
||||||
DetailItem("机车号", recordMap["loco"] ?: "--")
|
|
||||||
DetailItem("机车类型", recordMap["loco_type"] ?: "--")
|
|
||||||
DetailItem("列车类型", recordMap["lbj_class"] ?: "--")
|
|
||||||
|
|
||||||
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
|
|
||||||
|
|
||||||
|
|
||||||
DetailItem("路线", recordMap["route"] ?: "--")
|
|
||||||
DetailItem("信号强度", recordMap["rssi"] ?: "--")
|
|
||||||
|
|
||||||
if (coordinates != null) {
|
|
||||||
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
|
|
||||||
|
|
||||||
DetailItem(
|
|
||||||
label = "经纬度",
|
|
||||||
value = "纬度: ${coordinates.latitude}, 经度: ${coordinates.longitude}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(200.dp)
|
|
||||||
.padding(vertical = 8.dp),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
AndroidView(
|
|
||||||
factory = { context ->
|
|
||||||
MapView(context).apply {
|
|
||||||
setTileSource(TileSourceFactory.MAPNIK)
|
|
||||||
setMultiTouchControls(true)
|
|
||||||
controller.setZoom(15.0)
|
|
||||||
controller.setCenter(coordinates)
|
|
||||||
|
|
||||||
|
|
||||||
val marker = Marker(this)
|
|
||||||
marker.position = coordinates
|
|
||||||
marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
|
|
||||||
marker.title = recordMap["train"] ?: "列车"
|
|
||||||
overlays.add(marker)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
update = { mapView ->
|
|
||||||
mapView.controller.setCenter(coordinates)
|
|
||||||
mapView.invalidate()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
|
|
||||||
Button(
|
|
||||||
onClick = onDismiss,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(top = 8.dp)
|
|
||||||
) {
|
|
||||||
Text("关闭")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun DetailItem(
|
|
||||||
label: String,
|
|
||||||
value: String,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = 4.dp)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = label,
|
|
||||||
style = MaterialTheme.typography.labelMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = value,
|
|
||||||
style = MaterialTheme.typography.bodyLarge
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -271,7 +271,7 @@ fun TrainRecordItem(
|
|||||||
|
|
||||||
if (isValidPosition) {
|
if (isValidPosition) {
|
||||||
Text(
|
Text(
|
||||||
text = "${position}K",
|
text = "${position.trim().removeSuffix(".")}K",
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
modifier = Modifier.alignByBaseline()
|
modifier = Modifier.alignByBaseline()
|
||||||
@@ -640,7 +640,7 @@ fun MergedTrainRecordItem(
|
|||||||
|
|
||||||
if (isValidPosition) {
|
if (isValidPosition) {
|
||||||
Text(
|
Text(
|
||||||
text = "${position}K",
|
text = "${position.trim().removeSuffix(".")}K",
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
modifier = Modifier.alignByBaseline()
|
modifier = Modifier.alignByBaseline()
|
||||||
@@ -923,12 +923,12 @@ fun MergedTrainRecordItem(
|
|||||||
}
|
}
|
||||||
if (recordItem.position.isNotEmpty() && recordItem.position != "<NUL>") {
|
if (recordItem.position.isNotEmpty() && recordItem.position != "<NUL>") {
|
||||||
if (isNotEmpty()) append(" ")
|
if (isNotEmpty()) append(" ")
|
||||||
append("${recordItem.position}K")
|
append("${recordItem.position.trim().removeSuffix(".")}K")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = locationText.ifEmpty { "位置未知" },
|
text = locationText.ifEmpty { "" },
|
||||||
fontSize = 11.sp,
|
fontSize = 11.sp,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,7 +7,12 @@ import android.graphics.PorterDuff
|
|||||||
import android.graphics.PorterDuffColorFilter
|
import android.graphics.PorterDuffColorFilter
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Refresh
|
import androidx.compose.material.icons.filled.Refresh
|
||||||
import androidx.compose.material.icons.filled.MyLocation
|
import androidx.compose.material.icons.filled.MyLocation
|
||||||
@@ -16,11 +21,16 @@ import androidx.compose.material3.*
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Shape
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleEventObserver
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -112,7 +122,6 @@ fun MapScreen(
|
|||||||
|
|
||||||
val recordMap = record.toMap()
|
val recordMap = record.toMap()
|
||||||
title = recordMap["train"]?.toString() ?: "列车"
|
title = recordMap["train"]?.toString() ?: "列车"
|
||||||
|
|
||||||
val latStr = String.format("%.4f", point.latitude)
|
val latStr = String.format("%.4f", point.latitude)
|
||||||
val lonStr = String.format("%.4f", point.longitude)
|
val lonStr = String.format("%.4f", point.longitude)
|
||||||
val coordStr = "${latStr}°N, ${lonStr}°E"
|
val coordStr = "${latStr}°N, ${lonStr}°E"
|
||||||
@@ -574,8 +583,8 @@ fun Context.getCompactMarkerDrawable(color: Int): Drawable {
|
|||||||
|
|
||||||
|
|
||||||
private fun Int.directionText(): String = when (this) {
|
private fun Int.directionText(): String = when (this) {
|
||||||
1 -> "↓"
|
1 -> "下行"
|
||||||
3 -> "↑"
|
3 -> "上行"
|
||||||
else -> "?"
|
else -> "?"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -585,50 +594,143 @@ private fun TrainMarkerDialog(
|
|||||||
position: GeoPoint?,
|
position: GeoPoint?,
|
||||||
onDismiss: () -> Unit
|
onDismiss: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
val recordMap = record.toMap()
|
||||||
|
|
||||||
|
val displayItems = recordMap.filterKeys {
|
||||||
|
it !in setOf("train", "direction", "time")
|
||||||
|
}.toList()
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
title = {
|
title = {
|
||||||
|
|
||||||
val recordMap = record.toMap()
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Text(text = recordMap["train"]?.toString() ?: "列车", style = MaterialTheme.typography.titleLarge)
|
Text(
|
||||||
|
text = recordMap["train"]?.toString() ?: "列车",
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
recordMap["direction"]?.let { direction ->
|
recordMap["direction"]?.let { direction ->
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text(
|
Text(
|
||||||
text = direction,
|
text = (direction as? Int)?.directionText() ?: direction.toString(),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium.copy(
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
fontSize = 12.sp
|
||||||
|
),
|
||||||
|
modifier = Modifier.padding(start = 8.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Column {
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
record.toMap().forEach { (key, value) ->
|
.fillMaxWidth()
|
||||||
if (key != "train" && key != "direction") {
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(vertical = 8.dp)
|
||||||
|
) {
|
||||||
|
displayItems.forEach { (key, value) ->
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 4.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
val title = when (key) {
|
||||||
|
"speed" -> "速度"
|
||||||
|
"position" -> "位置"
|
||||||
|
"time" -> "时间"
|
||||||
|
"loco" -> "机车号"
|
||||||
|
"loco_type" -> "机车型号"
|
||||||
|
"route" -> "线路"
|
||||||
|
"rssi" -> "信号强度"
|
||||||
|
"timestamp" -> "时间"
|
||||||
|
"receivedTimestamp" -> "接收时间"
|
||||||
|
else -> key
|
||||||
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = value,
|
text = title,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.titleMedium
|
||||||
modifier = Modifier.padding(vertical = 2.dp)
|
)
|
||||||
|
Text(
|
||||||
|
text = value.toString(),
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
position?.let {
|
position?.let {
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Row(
|
||||||
Text(
|
modifier = Modifier
|
||||||
text = "坐标: ${String.format("%.6f", it.latitude)}, ${String.format("%.6f", it.longitude)}",
|
.fillMaxWidth()
|
||||||
style = MaterialTheme.typography.bodyMedium
|
.padding(vertical = 4.dp),
|
||||||
)
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "坐标信息",
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "${String.format("%.6f", it.latitude)}, ${String.format("%.6f", it.longitude)}",
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = onDismiss) {
|
TextButton(onClick = onDismiss) {
|
||||||
Text("确定")
|
Text("关闭")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun InfoSection(title: String, items: List<Pair<String, String>>) {
|
||||||
|
Column(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
modifier = Modifier.padding(bottom = 4.dp)
|
||||||
|
)
|
||||||
|
Card(
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
),
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(12.dp)) {
|
||||||
|
items.forEach { (key, value) ->
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 2.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = value.toString(),
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun InfoSectionSimple(title: String, items: List<Pair<String, String>>) {
|
||||||
|
Column(modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
items.forEach { (key, value) ->
|
||||||
|
Text(
|
||||||
|
text = value.toString(),
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ class LocoTypeUtil(private val context: Context) {
|
|||||||
|
|
||||||
private fun loadLocoTypeMapping() {
|
private fun loadLocoTypeMapping() {
|
||||||
try {
|
try {
|
||||||
context.assets.open("loco_number_info.csv").use { inputStream ->
|
context.assets.open("loco_type_info.csv").use { inputStream ->
|
||||||
BufferedReader(InputStreamReader(inputStream)).use { reader ->
|
BufferedReader(InputStreamReader(inputStream)).use { reader ->
|
||||||
reader.lines().forEach { line ->
|
reader.lines().forEach { line ->
|
||||||
val parts = line.split(",")
|
val parts = line.split(",")
|
||||||
|
|||||||
Reference in New Issue
Block a user