feat: add custom train information notification layout
This commit is contained in:
@@ -276,6 +276,33 @@ class BLEClient(private val context: Context) : BluetoothGattCallback() {
|
|||||||
return isConnected
|
return isConnected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
fun checkActualConnectionState(): Boolean {
|
||||||
|
bluetoothGatt?.let { gatt ->
|
||||||
|
try {
|
||||||
|
val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
||||||
|
val connectedDevices = bluetoothManager.getConnectedDevices(BluetoothProfile.GATT)
|
||||||
|
val isActuallyConnected = connectedDevices.any { it.address == deviceAddress }
|
||||||
|
|
||||||
|
if (isActuallyConnected && !isConnected) {
|
||||||
|
Log.d(TAG, "Found existing GATT connection, updating internal state")
|
||||||
|
isConnected = true
|
||||||
|
return true
|
||||||
|
} else if (!isActuallyConnected && isConnected) {
|
||||||
|
Log.d(TAG, "GATT connection lost, updating internal state")
|
||||||
|
isConnected = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return isActuallyConnected
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error checking actual connection state: ${e.message}")
|
||||||
|
return isConnected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isConnected
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
fun disconnect() {
|
fun disconnect() {
|
||||||
|
|||||||
@@ -825,12 +825,20 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
bleClient.setHighFrequencyReconnect(true)
|
bleClient.setHighFrequencyReconnect(true)
|
||||||
|
|
||||||
if (hasBluetoothPermissions() && !bleClient.isConnected() && autoConnectEnabled) {
|
if (hasBluetoothPermissions()) {
|
||||||
Log.d(TAG, "App resumed and not connected, starting auto scan")
|
val actuallyConnected = bleClient.checkActualConnectionState()
|
||||||
startAutoScanAndConnect()
|
|
||||||
} else if (bleClient.isConnected()) {
|
if (actuallyConnected) {
|
||||||
showDisconnectButton = true
|
showDisconnectButton = true
|
||||||
deviceStatus = "已连接"
|
deviceStatus = "已连接"
|
||||||
|
Log.d(TAG, "App resumed - connection verified")
|
||||||
|
} else if (autoConnectEnabled) {
|
||||||
|
Log.d(TAG, "App resumed and not connected, starting auto scan")
|
||||||
|
startAutoScanAndConnect()
|
||||||
|
} else {
|
||||||
|
deviceStatus = "未连接"
|
||||||
|
showDisconnectButton = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,14 @@ import android.content.Intent
|
|||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.RemoteViews
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import org.noxylva.lbjconsole.model.TrainRecord
|
import org.noxylva.lbjconsole.model.TrainRecord
|
||||||
|
|
||||||
|
|
||||||
class NotificationService(private val context: Context) {
|
class NotificationService(private val context: Context) {
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "NotificationService"
|
const val TAG = "NotificationService"
|
||||||
@@ -86,12 +89,7 @@ class NotificationService(private val context: Context) {
|
|||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
)
|
)
|
||||||
|
|
||||||
val directionText = when (trainRecord.direction) {
|
val remoteViews = RemoteViews(context.packageName, R.layout.notification_train_record)
|
||||||
1 -> "下行"
|
|
||||||
3 -> "上行"
|
|
||||||
else -> "未知"
|
|
||||||
}
|
|
||||||
|
|
||||||
val trainDisplay = if (isValidValue(trainRecord.lbjClass) && isValidValue(trainRecord.train)) {
|
val trainDisplay = if (isValidValue(trainRecord.lbjClass) && isValidValue(trainRecord.train)) {
|
||||||
"${trainRecord.lbjClass.trim()}${trainRecord.train.trim()}"
|
"${trainRecord.lbjClass.trim()}${trainRecord.train.trim()}"
|
||||||
} else if (isValidValue(trainRecord.lbjClass)) {
|
} else if (isValidValue(trainRecord.lbjClass)) {
|
||||||
@@ -99,26 +97,83 @@ class NotificationService(private val context: Context) {
|
|||||||
} else if (isValidValue(trainRecord.train)) {
|
} else if (isValidValue(trainRecord.train)) {
|
||||||
trainRecord.train.trim()
|
trainRecord.train.trim()
|
||||||
} else "列车"
|
} else "列车"
|
||||||
|
remoteViews.setTextViewText(R.id.notification_train_number, trainDisplay)
|
||||||
|
|
||||||
|
val directionText = when (trainRecord.direction) {
|
||||||
|
1 -> "下"
|
||||||
|
3 -> "上"
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
if (directionText.isNotEmpty()) {
|
||||||
|
remoteViews.setTextViewText(R.id.notification_direction, directionText)
|
||||||
|
remoteViews.setViewVisibility(R.id.notification_direction, View.VISIBLE)
|
||||||
|
} else {
|
||||||
|
remoteViews.setViewVisibility(R.id.notification_direction, View.GONE)
|
||||||
|
}
|
||||||
|
|
||||||
|
val locoInfo = when {
|
||||||
|
isValidValue(trainRecord.locoType) && isValidValue(trainRecord.loco) -> {
|
||||||
|
val shortLoco = if (trainRecord.loco.length > 5) {
|
||||||
|
trainRecord.loco.takeLast(5)
|
||||||
|
} else {
|
||||||
|
trainRecord.loco
|
||||||
|
}
|
||||||
|
"${trainRecord.locoType}-${shortLoco}"
|
||||||
|
}
|
||||||
|
isValidValue(trainRecord.locoType) -> trainRecord.locoType
|
||||||
|
isValidValue(trainRecord.loco) -> trainRecord.loco
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
if (locoInfo.isNotEmpty()) {
|
||||||
|
remoteViews.setTextViewText(R.id.notification_loco_info, locoInfo)
|
||||||
|
remoteViews.setViewVisibility(R.id.notification_loco_info, View.VISIBLE)
|
||||||
|
} else {
|
||||||
|
remoteViews.setViewVisibility(R.id.notification_loco_info, View.GONE)
|
||||||
|
}
|
||||||
|
|
||||||
val title = trainDisplay
|
|
||||||
val content = buildString {
|
|
||||||
append(directionText)
|
|
||||||
if (isValidValue(trainRecord.route)) {
|
if (isValidValue(trainRecord.route)) {
|
||||||
append("\n线路: ${trainRecord.route.trim()}")
|
remoteViews.setTextViewText(R.id.notification_route, trainRecord.route.trim())
|
||||||
}
|
remoteViews.setViewVisibility(R.id.notification_route, View.VISIBLE)
|
||||||
if (isValidValue(trainRecord.speed)) {
|
} else {
|
||||||
append("\n速度: ${trainRecord.speed.trim()} km/h")
|
remoteViews.setViewVisibility(R.id.notification_route, View.GONE)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isValidValue(trainRecord.position)) {
|
if (isValidValue(trainRecord.position)) {
|
||||||
append("\n位置: ${trainRecord.position.trim()} km")
|
remoteViews.setTextViewText(R.id.notification_position, "${trainRecord.position.trim()}K")
|
||||||
|
remoteViews.setViewVisibility(R.id.notification_position, View.VISIBLE)
|
||||||
|
} else {
|
||||||
|
remoteViews.setViewVisibility(R.id.notification_position, View.GONE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isValidValue(trainRecord.speed)) {
|
||||||
|
remoteViews.setTextViewText(R.id.notification_speed, "${trainRecord.speed.trim()} km/h")
|
||||||
|
remoteViews.setViewVisibility(R.id.notification_speed, View.VISIBLE)
|
||||||
|
} else {
|
||||||
|
remoteViews.setViewVisibility(R.id.notification_speed, View.GONE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
remoteViews.setOnClickPendingIntent(R.id.notification_train_number, pendingIntent)
|
||||||
|
|
||||||
|
val summaryParts = mutableListOf<String>()
|
||||||
|
|
||||||
|
val routeAndDirection = when {
|
||||||
|
isValidValue(trainRecord.route) && directionText.isNotEmpty() -> "${trainRecord.route.trim()}${directionText}行"
|
||||||
|
isValidValue(trainRecord.route) -> trainRecord.route.trim()
|
||||||
|
directionText.isNotEmpty() -> "${directionText}行"
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
routeAndDirection?.let { summaryParts.add(it) }
|
||||||
|
if (locoInfo.isNotEmpty()) summaryParts.add(locoInfo)
|
||||||
|
|
||||||
|
val summaryText = summaryParts.joinToString(" • ")
|
||||||
|
|
||||||
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
.setSmallIcon(R.drawable.ic_notification)
|
.setSmallIcon(R.drawable.ic_notification)
|
||||||
.setContentTitle(title)
|
.setContentTitle(trainDisplay)
|
||||||
.setContentText(content)
|
.setContentText(summaryText)
|
||||||
.setStyle(NotificationCompat.BigTextStyle().bigText(content))
|
.setCustomContentView(remoteViews)
|
||||||
|
.setCustomBigContentView(remoteViews)
|
||||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
@@ -131,7 +186,7 @@ class NotificationService(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
notificationManager.notify(notificationId, notification)
|
notificationManager.notify(notificationId, notification)
|
||||||
Log.d(TAG, "Notification sent for train: ${trainRecord.train}")
|
Log.d(TAG, "Custom notification sent for train: ${trainRecord.train}")
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to show notification: ${e.message}", e)
|
Log.e(TAG, "Failed to show notification: ${e.message}", e)
|
||||||
|
|||||||
99
app/src/main/res/layout/notification_train_record.xml
Normal file
99
app/src/main/res/layout/notification_train_record.xml
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
<?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="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="4dp"
|
||||||
|
android:background="@android:color/transparent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/notification_train_number"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="G1234"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
android:layout_marginEnd="4dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/notification_direction"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:text="下"
|
||||||
|
android:textSize="10sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:background="@android:color/black"
|
||||||
|
android:gravity="center"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/notification_loco_info"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="CRH380D-1234"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="?android:attr/textColorPrimary" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/notification_route"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="京沪高铁"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
android:layout_marginEnd="4dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/notification_position"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="1234K"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="?android:attr/textColorPrimary" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/notification_speed"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="300 km/h"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="?android:attr/textColorPrimary" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
Reference in New Issue
Block a user