feat: add BLE disconnection cleanup and enhance record management
This commit is contained in:
@@ -2,10 +2,6 @@
|
|||||||
|
|
||||||
LBJ Console is an Android app designed to receive and display LBJ messages via BLE from the [SX1276_Receive_LBJ](https://github.com/undef-i/SX1276_Receive_LBJ) device.
|
LBJ Console is an Android app designed to receive and display LBJ messages via BLE from the [SX1276_Receive_LBJ](https://github.com/undef-i/SX1276_Receive_LBJ) device.
|
||||||
|
|
||||||
## Roadmap
|
|
||||||
- Record filtering (train number, time range)
|
|
||||||
- Record management page optimization
|
|
||||||
- Optional train merge by locomotive/number
|
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ android {
|
|||||||
applicationId = "org.noxylva.lbjconsole"
|
applicationId = "org.noxylva.lbjconsole"
|
||||||
minSdk = 29
|
minSdk = 29
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 5
|
versionCode = 6
|
||||||
versionName = "0.0.5"
|
versionName = "0.0.6"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -633,4 +633,23 @@ class BLEClient(private val context: Context) : BluetoothGattCallback() {
|
|||||||
fun getConnectionAttempts(): Int = connectionAttempts
|
fun getConnectionAttempts(): Int = connectionAttempts
|
||||||
|
|
||||||
fun getLastKnownDeviceAddress(): String? = lastKnownDeviceAddress
|
fun getLastKnownDeviceAddress(): String? = lastKnownDeviceAddress
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
fun disconnectAndCleanup() {
|
||||||
|
isConnected = false
|
||||||
|
autoReconnect = false
|
||||||
|
bluetoothGatt?.let { gatt ->
|
||||||
|
try {
|
||||||
|
gatt.disconnect()
|
||||||
|
gatt.close()
|
||||||
|
Log.d(TAG, "GATT connection cleaned up")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Cleanup error: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bluetoothGatt = null
|
||||||
|
deviceAddress = null
|
||||||
|
connectionAttempts = 0
|
||||||
|
Log.d(TAG, "BLE client fully disconnected and cleaned up")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -274,6 +274,8 @@ class MainActivity : ComponentActivity() {
|
|||||||
onMergeSettingsChange = { newSettings ->
|
onMergeSettingsChange = { newSettings ->
|
||||||
mergeSettings = newSettings
|
mergeSettings = newSettings
|
||||||
trainRecordManager.updateMergeSettings(newSettings)
|
trainRecordManager.updateMergeSettings(newSettings)
|
||||||
|
historyEditMode = false
|
||||||
|
historySelectedRecords = emptySet()
|
||||||
saveSettings()
|
saveSettings()
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -334,12 +336,6 @@ class MainActivity : ComponentActivity() {
|
|||||||
scope.launch {
|
scope.launch {
|
||||||
val deletedCount = trainRecordManager.deleteRecords(records)
|
val deletedCount = trainRecordManager.deleteRecords(records)
|
||||||
if (deletedCount > 0) {
|
if (deletedCount > 0) {
|
||||||
Toast.makeText(
|
|
||||||
this@MainActivity,
|
|
||||||
"已删除 $deletedCount 条记录",
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
|
|
||||||
if (records.contains(latestRecord)) {
|
if (records.contains(latestRecord)) {
|
||||||
latestRecord = null
|
latestRecord = null
|
||||||
}
|
}
|
||||||
@@ -659,6 +655,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
saveSettings()
|
saveSettings()
|
||||||
|
if (isFinishing) {
|
||||||
|
bleClient.disconnectAndCleanup()
|
||||||
|
Log.d(TAG, "App finishing, BLE cleaned up")
|
||||||
|
}
|
||||||
Log.d(TAG, "App paused, settings saved")
|
Log.d(TAG, "App paused, settings saved")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -783,8 +783,24 @@ fun MainContent(
|
|||||||
if (historyEditMode && currentTab == 0) {
|
if (historyEditMode && currentTab == 0) {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
|
val totalSelectedCount = historySelectedRecords.sumOf { selectedId ->
|
||||||
|
allRecords.find { item ->
|
||||||
|
when (item) {
|
||||||
|
is TrainRecord -> item.uniqueId == selectedId
|
||||||
|
is org.noxylva.lbjconsole.model.MergedTrainRecord ->
|
||||||
|
item.records.any { it.uniqueId == selectedId }
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}?.let { item ->
|
||||||
|
when (item) {
|
||||||
|
is TrainRecord -> 1
|
||||||
|
is org.noxylva.lbjconsole.model.MergedTrainRecord -> item.records.size
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
} ?: 0
|
||||||
|
}
|
||||||
Text(
|
Text(
|
||||||
"已选择 ${historySelectedRecords.size} 条记录",
|
"已选择 $totalSelectedCount 条记录",
|
||||||
color = MaterialTheme.colorScheme.onPrimary
|
color = MaterialTheme.colorScheme.onPrimary
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -803,24 +819,39 @@ fun MainContent(
|
|||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (historySelectedRecords.isNotEmpty()) {
|
if (historySelectedRecords.isNotEmpty()) {
|
||||||
val recordsToDelete = mutableListOf<TrainRecord>()
|
val recordsToDelete = mutableSetOf<TrainRecord>()
|
||||||
|
val idToRecordMap = mutableMapOf<String, TrainRecord>()
|
||||||
|
val idToMergedRecordMap = mutableMapOf<String, org.noxylva.lbjconsole.model.MergedTrainRecord>()
|
||||||
|
|
||||||
allRecords.forEach { item ->
|
allRecords.forEach { item ->
|
||||||
when (item) {
|
when (item) {
|
||||||
is TrainRecord -> {
|
is TrainRecord -> {
|
||||||
if (historySelectedRecords.contains(item.timestamp.time.toString())) {
|
idToRecordMap[item.uniqueId] = item
|
||||||
recordsToDelete.add(item)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
is org.noxylva.lbjconsole.model.MergedTrainRecord -> {
|
is org.noxylva.lbjconsole.model.MergedTrainRecord -> {
|
||||||
item.records.forEach { record ->
|
item.records.forEach { record ->
|
||||||
if (historySelectedRecords.contains(record.timestamp.time.toString())) {
|
idToRecordMap[record.uniqueId] = record
|
||||||
recordsToDelete.add(record)
|
idToMergedRecordMap[record.uniqueId] = item
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onDeleteRecords(recordsToDelete)
|
|
||||||
|
val processedMergedRecords = mutableSetOf<org.noxylva.lbjconsole.model.MergedTrainRecord>()
|
||||||
|
|
||||||
|
historySelectedRecords.forEach { selectedId ->
|
||||||
|
val mergedRecord = idToMergedRecordMap[selectedId]
|
||||||
|
if (mergedRecord != null && !processedMergedRecords.contains(mergedRecord)) {
|
||||||
|
recordsToDelete.addAll(mergedRecord.records)
|
||||||
|
processedMergedRecords.add(mergedRecord)
|
||||||
|
} else if (mergedRecord == null) {
|
||||||
|
idToRecordMap[selectedId]?.let { record ->
|
||||||
|
recordsToDelete.add(record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeleteRecords(recordsToDelete.toList())
|
||||||
onHistoryStateChange(false, emptySet(), historyExpandedStates, historyScrollPosition, historyScrollOffset)
|
onHistoryStateChange(false, emptySet(), historyExpandedStates, historyScrollPosition, historyScrollOffset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -902,7 +933,16 @@ fun MainContent(
|
|||||||
)
|
)
|
||||||
3 -> MapScreen(
|
3 -> MapScreen(
|
||||||
records = if (allRecords.isNotEmpty()) {
|
records = if (allRecords.isNotEmpty()) {
|
||||||
allRecords.filterIsInstance<TrainRecord>()
|
val trainRecords = mutableListOf<TrainRecord>()
|
||||||
|
allRecords.forEach { item ->
|
||||||
|
when (item) {
|
||||||
|
is TrainRecord -> trainRecords.add(item)
|
||||||
|
is org.noxylva.lbjconsole.model.MergedTrainRecord -> {
|
||||||
|
trainRecords.addAll(item.records)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trainRecords
|
||||||
} else {
|
} else {
|
||||||
recentRecords
|
recentRecords
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,8 +9,15 @@ import org.noxylva.lbjconsole.util.LocationUtils
|
|||||||
class TrainRecord(jsonData: JSONObject? = null) {
|
class TrainRecord(jsonData: JSONObject? = null) {
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "TrainRecord"
|
const val TAG = "TrainRecord"
|
||||||
|
private var nextId = 0L
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun generateUniqueId(): String {
|
||||||
|
return "${System.currentTimeMillis()}_${++nextId}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val uniqueId: String
|
||||||
var timestamp: Date = Date()
|
var timestamp: Date = Date()
|
||||||
var receivedTimestamp: Date = Date()
|
var receivedTimestamp: Date = Date()
|
||||||
var train: String = ""
|
var train: String = ""
|
||||||
@@ -29,10 +36,15 @@ class TrainRecord(jsonData: JSONObject? = null) {
|
|||||||
private var _coordinates: GeoPoint? = null
|
private var _coordinates: GeoPoint? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
uniqueId = if (jsonData?.has("uniqueId") == true) {
|
||||||
|
jsonData.getString("uniqueId")
|
||||||
|
} else {
|
||||||
|
generateUniqueId()
|
||||||
|
}
|
||||||
|
|
||||||
jsonData?.let {
|
jsonData?.let {
|
||||||
try {
|
try {
|
||||||
if (jsonData.has("timestamp")) {
|
if (jsonData.has("timestamp")) {
|
||||||
|
|
||||||
timestamp = Date(jsonData.getLong("timestamp"))
|
timestamp = Date(jsonData.getLong("timestamp"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,6 +178,7 @@ class TrainRecord(jsonData: JSONObject? = null) {
|
|||||||
|
|
||||||
fun toJSON(): JSONObject {
|
fun toJSON(): JSONObject {
|
||||||
val json = JSONObject()
|
val json = JSONObject()
|
||||||
|
json.put("uniqueId", uniqueId)
|
||||||
json.put("timestamp", timestamp.time)
|
json.put("timestamp", timestamp.time)
|
||||||
json.put("receivedTimestamp", receivedTimestamp.time)
|
json.put("receivedTimestamp", receivedTimestamp.time)
|
||||||
json.put("train", train)
|
json.put("train", train)
|
||||||
@@ -181,4 +194,14 @@ class TrainRecord(jsonData: JSONObject? = null) {
|
|||||||
json.put("rssi", rssi)
|
json.put("rssi", rssi)
|
||||||
return json
|
return json
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is TrainRecord) return false
|
||||||
|
return uniqueId == other.uniqueId
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return uniqueId.hashCode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -207,11 +207,11 @@ class TrainRecordManager(private val context: Context) {
|
|||||||
val mergedRecords = processRecordsForMerging(allRecords, mergeSettings)
|
val mergedRecords = processRecordsForMerging(allRecords, mergeSettings)
|
||||||
|
|
||||||
val mergedRecordIds = mergedRecords.flatMap { merged ->
|
val mergedRecordIds = mergedRecords.flatMap { merged ->
|
||||||
merged.records.map { it.timestamp.time.toString() }
|
merged.records.map { it.uniqueId }
|
||||||
}.toSet()
|
}.toSet()
|
||||||
|
|
||||||
val singleRecords = allRecords.filter { record ->
|
val singleRecords = allRecords.filter { record ->
|
||||||
!mergedRecordIds.contains(record.timestamp.time.toString())
|
!mergedRecordIds.contains(record.uniqueId)
|
||||||
}
|
}
|
||||||
|
|
||||||
val mixedList = mutableListOf<Any>()
|
val mixedList = mutableListOf<Any>()
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ fun TrainRecordItem(
|
|||||||
if (isInEditMode) {
|
if (isInEditMode) {
|
||||||
onToggleSelection(record)
|
onToggleSelection(record)
|
||||||
} else {
|
} else {
|
||||||
val id = record.timestamp.time.toString()
|
val id = record.uniqueId
|
||||||
expandedStatesMap[id] = !(expandedStatesMap[id] ?: false)
|
expandedStatesMap[id] = !(expandedStatesMap[id] ?: false)
|
||||||
if (record == latestRecord) {
|
if (record == latestRecord) {
|
||||||
onRecordClick(record)
|
onRecordClick(record)
|
||||||
@@ -91,7 +91,7 @@ fun TrainRecordItem(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
) {
|
) {
|
||||||
val recordId = record.timestamp.time.toString()
|
val recordId = record.uniqueId
|
||||||
val isExpanded = expandedStatesMap[recordId] == true
|
val isExpanded = expandedStatesMap[recordId] == true
|
||||||
val recordMap = record.toMap(showDetailedTime = true)
|
val recordMap = record.toMap(showDetailedTime = true)
|
||||||
|
|
||||||
@@ -705,7 +705,7 @@ fun MergedTrainRecordItem(
|
|||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
mergedRecord.records.forEach { recordItem ->
|
mergedRecord.records.filter { it != mergedRecord.latestRecord }.forEach { recordItem ->
|
||||||
val timeFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
|
val timeFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
@@ -834,13 +834,13 @@ fun HistoryScreen(
|
|||||||
records.forEach { item ->
|
records.forEach { item ->
|
||||||
when (item) {
|
when (item) {
|
||||||
is TrainRecord -> {
|
is TrainRecord -> {
|
||||||
if (selectedRecords.contains(item.timestamp.time.toString())) {
|
if (selectedRecords.contains(item.uniqueId)) {
|
||||||
add(item)
|
add(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is MergedTrainRecord -> {
|
is MergedTrainRecord -> {
|
||||||
item.records.forEach { record ->
|
item.records.forEach { record ->
|
||||||
if (selectedRecords.contains(record.timestamp.time.toString())) {
|
if (selectedRecords.contains(record.uniqueId)) {
|
||||||
add(record)
|
add(record)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -881,20 +881,21 @@ fun HistoryScreen(
|
|||||||
|
|
||||||
|
|
||||||
LaunchedEffect(isInEditMode, selectedRecordsList.size) {
|
LaunchedEffect(isInEditMode, selectedRecordsList.size) {
|
||||||
val selectedIds = selectedRecordsList.map { it.timestamp.time.toString() }.toSet()
|
val selectedIds = selectedRecordsList.map { it.uniqueId }.toSet()
|
||||||
onStateChange(isInEditMode, selectedIds, expandedStatesMap.toMap(), listState.firstVisibleItemIndex, listState.firstVisibleItemScrollOffset)
|
onStateChange(isInEditMode, selectedIds, expandedStatesMap.toMap(), listState.firstVisibleItemIndex, listState.firstVisibleItemScrollOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(expandedStatesMap.toMap()) {
|
LaunchedEffect(expandedStatesMap.toMap()) {
|
||||||
if (!isInEditMode) {
|
if (!isInEditMode) {
|
||||||
val selectedIds = selectedRecordsList.map { it.timestamp.time.toString() }.toSet()
|
val selectedIds = selectedRecordsList.map { it.uniqueId }.toSet()
|
||||||
|
delay(50)
|
||||||
onStateChange(isInEditMode, selectedIds, expandedStatesMap.toMap(), listState.firstVisibleItemIndex, listState.firstVisibleItemScrollOffset)
|
onStateChange(isInEditMode, selectedIds, expandedStatesMap.toMap(), listState.firstVisibleItemIndex, listState.firstVisibleItemScrollOffset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(listState.firstVisibleItemIndex, listState.firstVisibleItemScrollOffset) {
|
LaunchedEffect(listState.firstVisibleItemIndex, listState.firstVisibleItemScrollOffset) {
|
||||||
if (!isInEditMode) {
|
if (!isInEditMode) {
|
||||||
val selectedIds = selectedRecordsList.map { it.timestamp.time.toString() }.toSet()
|
val selectedIds = selectedRecordsList.map { it.uniqueId }.toSet()
|
||||||
onStateChange(isInEditMode, selectedIds, expandedStatesMap.toMap(), listState.firstVisibleItemIndex, listState.firstVisibleItemScrollOffset)
|
onStateChange(isInEditMode, selectedIds, expandedStatesMap.toMap(), listState.firstVisibleItemIndex, listState.firstVisibleItemScrollOffset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -911,7 +912,6 @@ fun HistoryScreen(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(16.dp)
|
|
||||||
.weight(1.0f)
|
.weight(1.0f)
|
||||||
) {
|
) {
|
||||||
if (filteredRecords.isEmpty()) {
|
if (filteredRecords.isEmpty()) {
|
||||||
@@ -945,7 +945,8 @@ fun HistoryScreen(
|
|||||||
LazyColumn(
|
LazyColumn(
|
||||||
state = listState,
|
state = listState,
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 16.dp)
|
||||||
) {
|
) {
|
||||||
items(filteredRecords) { item ->
|
items(filteredRecords) { item ->
|
||||||
when (item) {
|
when (item) {
|
||||||
|
|||||||
@@ -99,6 +99,49 @@ fun MapScreen(
|
|||||||
|
|
||||||
var railwayLayerVisibleState by remember(railwayLayerVisible) { mutableStateOf(railwayLayerVisible) }
|
var railwayLayerVisibleState by remember(railwayLayerVisible) { mutableStateOf(railwayLayerVisible) }
|
||||||
|
|
||||||
|
fun updateMarkers() {
|
||||||
|
val mapView = mapViewRef.value ?: return
|
||||||
|
|
||||||
|
mapView.overlays.removeAll { it is Marker }
|
||||||
|
|
||||||
|
validRecords.forEach { record ->
|
||||||
|
record.getCoordinates()?.let { point ->
|
||||||
|
val marker = Marker(mapView).apply {
|
||||||
|
position = point
|
||||||
|
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
|
||||||
|
|
||||||
|
val recordMap = record.toMap()
|
||||||
|
title = recordMap["train"]?.toString() ?: "列车"
|
||||||
|
|
||||||
|
val latStr = String.format("%.4f", point.latitude)
|
||||||
|
val lonStr = String.format("%.4f", point.longitude)
|
||||||
|
val coordStr = "${latStr}°N, ${lonStr}°E"
|
||||||
|
snippet = coordStr
|
||||||
|
|
||||||
|
setInfoWindowAnchor(Marker.ANCHOR_CENTER, 0f)
|
||||||
|
|
||||||
|
setOnMarkerClickListener { clickedMarker, _ ->
|
||||||
|
selectedRecord = record
|
||||||
|
dialogPosition = point
|
||||||
|
showDetailDialog = true
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mapView.overlays.add(marker)
|
||||||
|
marker.showInfoWindow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mapView.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(records) {
|
||||||
|
if (isMapInitialized) {
|
||||||
|
updateMarkers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
DisposableEffect(lifecycleOwner) {
|
DisposableEffect(lifecycleOwner) {
|
||||||
val observer = LifecycleEventObserver { _, event ->
|
val observer = LifecycleEventObserver { _, event ->
|
||||||
@@ -136,49 +179,6 @@ fun MapScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun updateMarkers() {
|
|
||||||
val mapView = mapViewRef.value ?: return
|
|
||||||
|
|
||||||
|
|
||||||
mapView.overlays.removeAll { it is Marker }
|
|
||||||
|
|
||||||
|
|
||||||
validRecords.forEach { record ->
|
|
||||||
record.getCoordinates()?.let { point ->
|
|
||||||
val marker = Marker(mapView).apply {
|
|
||||||
position = point
|
|
||||||
|
|
||||||
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
|
|
||||||
|
|
||||||
val recordMap = record.toMap()
|
|
||||||
title = recordMap["train"]?.toString() ?: "列车"
|
|
||||||
|
|
||||||
val latStr = String.format("%.4f", point.latitude)
|
|
||||||
val lonStr = String.format("%.4f", point.longitude)
|
|
||||||
val coordStr = "${latStr}°N, ${lonStr}°E"
|
|
||||||
snippet = coordStr
|
|
||||||
|
|
||||||
setInfoWindowAnchor(Marker.ANCHOR_CENTER, 0f)
|
|
||||||
|
|
||||||
setOnMarkerClickListener { clickedMarker, _ ->
|
|
||||||
selectedRecord = record
|
|
||||||
dialogPosition = point
|
|
||||||
showDetailDialog = true
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mapView.overlays.add(marker)
|
|
||||||
marker.showInfoWindow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
mapView.invalidate()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun updateRailwayLayerVisibility(visible: Boolean) {
|
fun updateRailwayLayerVisibility(visible: Boolean) {
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ fun MergedHistoryScreen(
|
|||||||
val selectedRecordsList = remember(selectedRecords) {
|
val selectedRecordsList = remember(selectedRecords) {
|
||||||
mutableStateListOf<TrainRecord>().apply {
|
mutableStateListOf<TrainRecord>().apply {
|
||||||
addAll(mergedRecords.flatMap { it.records }.filter {
|
addAll(mergedRecords.flatMap { it.records }.filter {
|
||||||
selectedRecords.contains(it.timestamp.time.toString())
|
selectedRecords.contains(it.uniqueId)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,7 +72,7 @@ fun MergedHistoryScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
LaunchedEffect(isInEditMode, selectedRecordsList.size) {
|
LaunchedEffect(isInEditMode, selectedRecordsList.size) {
|
||||||
val selectedIds = selectedRecordsList.map { it.timestamp.time.toString() }.toSet()
|
val selectedIds = selectedRecordsList.map { it.uniqueId }.toSet()
|
||||||
onStateChange(isInEditMode, selectedIds, expandedStatesMap.toMap(),
|
onStateChange(isInEditMode, selectedIds, expandedStatesMap.toMap(),
|
||||||
listState.firstVisibleItemIndex, listState.firstVisibleItemScrollOffset)
|
listState.firstVisibleItemIndex, listState.firstVisibleItemScrollOffset)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,7 +227,6 @@ fun SettingsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(20.dp))
|
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "LBJ Console v$appVersion by undef-i",
|
text = "LBJ Console v$appVersion by undef-i",
|
||||||
@@ -240,7 +239,7 @@ fun SettingsScreen(
|
|||||||
.clickable {
|
.clickable {
|
||||||
uriHandler.openUri("https://github.com/undef-i")
|
uriHandler.openUri("https://github.com/undef-i")
|
||||||
}
|
}
|
||||||
.padding(16.dp)
|
.padding(12.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user