fix(aprs): passcode compute button, crash log, notif permission, login verify
User feedback round 2 (4.5.4): no notification shown, report not sending, no error visibility on crash. Diagnosis: APRS-IS port 14580 reachable (verified with real login test), 24580 SSL refused; login format OK. Fixes: - Settings dialog: "Compute passcode" button - fills passcode from callsign via the ported 0x73E2 algorithm (user can see the result) - Global crash handler in MainApplication: stack trace appended to files/crash_log.txt so crashes are diagnosable (user: no crash logs were available before) - POST_NOTIFICATIONS runtime permission requested when enabling APRS (Android 13+ otherwise silently hides the service notification) - AprsIsClient: read the login response (aprsc "# logresp ... unverified"/"Invalid") and surface the server message as the error - AprsForegroundService: Toast on manual report result (OK / failure with server reason) - Strings EN/ZH/TR/IN/ID +3 keys Verified: compileDebugKotlin all modules BUILD SUCCESSFUL, check_strings 9 files OK.
This commit is contained in:
1 parent
920af7bde2
commit
012ea1eeb6
9 files changed
+85
-1
No files matched your search
@@ -8,6 +8,7 @@ import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.widget.Toast
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
@@ -66,7 +67,18 @@ class AprsForegroundService : Service() {
|
||||
configProvider = { AprsStore.loadConfig(this) },
|
||||
positionProvider = { lastKnownPosition() },
|
||||
onState = { lastState = it },
|
||||
onReport = { updateNotification(cfg) }
|
||||
onReport = { report ->
|
||||
updateNotification(cfg)
|
||||
// 手动上报/失败时给即时反馈(前台可见)
|
||||
if (report.timestamp > System.currentTimeMillis() - 60_000L) {
|
||||
val msg = if (report.ok) {
|
||||
getString(R.string.aprs_toast_ok)
|
||||
} else {
|
||||
getString(R.string.aprs_toast_fail, report.detail)
|
||||
}
|
||||
runCatching { Toast.makeText(this, msg, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
}
|
||||
)
|
||||
reporter = rep
|
||||
rep.start()
|
||||
|
||||
@@ -30,10 +30,29 @@ class MainApplication : Application(), IContainerProvider {
|
||||
|
||||
private lateinit var container: IMainContainer
|
||||
|
||||
/** 全局崩溃捕获:堆栈写入 files/crash_log.txt,重启后可查看(用户要求错误报告) */
|
||||
private fun installCrashHandler() {
|
||||
val defaultHandler = Thread.getDefaultUncaughtExceptionHandler()
|
||||
Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
|
||||
runCatching {
|
||||
val log = StringBuilder()
|
||||
log.append("=== Crash ${System.currentTimeMillis()} ===\n")
|
||||
log.append("Thread: ").append(thread.name).append("\n")
|
||||
val sw = java.io.StringWriter()
|
||||
throwable.printStackTrace(java.io.PrintWriter(sw))
|
||||
log.append(sw.toString()).append("\n")
|
||||
val file = java.io.File(filesDir, "crash_log.txt")
|
||||
file.appendText(log.toString())
|
||||
}
|
||||
defaultHandler?.uncaughtException(thread, throwable)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getMainContainer(): IMainContainer = container
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
installCrashHandler()
|
||||
container = MainContainer(this)
|
||||
// trigger automatic update every 48 hours
|
||||
container.appScope.launch { checkAutoUpdate() }
|
||||
|
||||
@@ -46,6 +46,17 @@ class AprsIsClient(
|
||||
// 登录行
|
||||
val login = AprsPacket.formatLogin(callsign, ssid, passcode, version) + filter
|
||||
writer?.println(login)
|
||||
// 读登录响应(aprsc 返回 # logresp ... verified/unverified)
|
||||
runCatching {
|
||||
s.soTimeout = 8000
|
||||
val resp = reader?.readLine()
|
||||
if (resp != null && (resp.contains("Invalid", ignoreCase = true) ||
|
||||
resp.contains("unverified", ignoreCase = true))) {
|
||||
throw IllegalArgumentException(resp.trim())
|
||||
}
|
||||
// 恢复超时
|
||||
s.soTimeout = timeoutSec * 1000
|
||||
}
|
||||
}
|
||||
|
||||
/** 发送一个 APRS 包(一行) */
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
<string name="aprs_notif_connected">Terhubung ke APRS-IS</string>
|
||||
<string name="aprs_notif_error">Kesalahan koneksi</string>
|
||||
<string name="aprs_notif_stop">Berhenti</string>
|
||||
<string name="prefs_aprs_calc_passcode">Hitung kode sandi</string>
|
||||
<string name="aprs_toast_ok">APRS: laporan terkirim</string>
|
||||
<string name="aprs_toast_fail">APRS: laporan gagal - %1$s</string>
|
||||
<string name="nav_more">Lainnya</string>
|
||||
<string name="nav_log">Log</string>
|
||||
<string name="nav_amsat">AMSAT</string>
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
<string name="aprs_notif_connected">Terhubung ke APRS-IS</string>
|
||||
<string name="aprs_notif_error">Kesalahan koneksi</string>
|
||||
<string name="aprs_notif_stop">Berhenti</string>
|
||||
<string name="prefs_aprs_calc_passcode">Hitung kode sandi</string>
|
||||
<string name="aprs_toast_ok">APRS: laporan terkirim</string>
|
||||
<string name="aprs_toast_fail">APRS: laporan gagal - %1$s</string>
|
||||
<string name="nav_more">Lainnya</string>
|
||||
<string name="nav_log">Log</string>
|
||||
<string name="nav_amsat">AMSAT</string>
|
||||
|
||||
@@ -36,6 +36,9 @@
|
||||
<string name="aprs_notif_connected">APRS-IS bağlı</string>
|
||||
<string name="aprs_notif_error">Bağlantı hatası</string>
|
||||
<string name="aprs_notif_stop">Durdur</string>
|
||||
<string name="prefs_aprs_calc_passcode">Parolayı hesapla</string>
|
||||
<string name="aprs_toast_ok">APRS: rapor gönderildi</string>
|
||||
<string name="aprs_toast_fail">APRS: rapor başarısız - %1$s</string>
|
||||
<string name="nav_more">Daha Fazla</string>
|
||||
<string name="nav_log">Günlük</string>
|
||||
<string name="nav_amsat">AMSAT</string>
|
||||
|
||||
@@ -37,6 +37,9 @@
|
||||
<string name="aprs_notif_connected">已连接 APRS-IS</string>
|
||||
<string name="aprs_notif_error">连接错误</string>
|
||||
<string name="aprs_notif_stop">停止</string>
|
||||
<string name="prefs_aprs_calc_passcode">计算验证码</string>
|
||||
<string name="aprs_toast_ok">APRS: 上报成功</string>
|
||||
<string name="aprs_toast_fail">APRS: 上报失败 - %1$s</string>
|
||||
<string name="nav_more">更多</string>
|
||||
<string name="nav_log">日志</string>
|
||||
<string name="nav_amsat">AMSAT</string>
|
||||
|
||||
@@ -38,6 +38,9 @@
|
||||
<string name="aprs_notif_connected">Connected to APRS-IS</string>
|
||||
<string name="aprs_notif_error">Connection error</string>
|
||||
<string name="aprs_notif_stop">Stop</string>
|
||||
<string name="prefs_aprs_calc_passcode">Compute passcode</string>
|
||||
<string name="aprs_toast_ok">APRS: report sent OK</string>
|
||||
<string name="aprs_toast_fail">APRS: report failed - %1$s</string>
|
||||
<string name="nav_more">More</string>
|
||||
<string name="nav_log">Log</string>
|
||||
<string name="nav_amsat">AMSAT</string>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.rtbishop.look4sat.feature.settings
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -20,7 +22,9 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -29,6 +33,7 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.rtbishop.look4sat.core.data.aprs.AprsConfig
|
||||
import com.rtbishop.look4sat.core.domain.aprs.AprsPacket
|
||||
import com.rtbishop.look4sat.core.data.aprs.AprsStore
|
||||
import com.rtbishop.look4sat.core.presentation.CardButton
|
||||
import com.rtbishop.look4sat.core.presentation.R
|
||||
@@ -39,6 +44,17 @@ fun AprsCard() {
|
||||
val context = LocalContext.current
|
||||
var config by remember { mutableStateOf(AprsStore.loadConfig(context)) }
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
val notifPermissionLauncher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.RequestPermission()
|
||||
) { }
|
||||
fun requestNotifPermissionIfNeeded() {
|
||||
if (Build.VERSION.SDK_INT >= 33 &&
|
||||
context.checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS) !=
|
||||
PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
notifPermissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS)
|
||||
}
|
||||
}
|
||||
|
||||
ElevatedCard(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(
|
||||
@@ -66,6 +82,7 @@ fun AprsCard() {
|
||||
AprsSwitchRow(R.string.prefs_aprs_enable, config.enabled) { enabled ->
|
||||
config = config.copy(enabled = enabled)
|
||||
AprsStore.saveConfig(context, config)
|
||||
if (enabled) requestNotifPermissionIfNeeded()
|
||||
val intent = Intent()
|
||||
.setClassName(context.packageName, AprsStore.SERVICE_CLASS)
|
||||
.setAction(if (enabled) AprsStore.ACTION_START else AprsStore.ACTION_STOP)
|
||||
@@ -163,6 +180,16 @@ private fun AprsSettingsDialog(
|
||||
singleLine = true,
|
||||
textStyle = textStyle
|
||||
)
|
||||
// 计算验证码按钮(用户要求:让用户算出结果)
|
||||
TextButton(
|
||||
onClick = {
|
||||
val call = callsign.trim().uppercase()
|
||||
if (call.isNotBlank()) {
|
||||
passcode = AprsPacket.passcode(call).toString()
|
||||
}
|
||||
},
|
||||
enabled = callsign.trim().isNotBlank()
|
||||
) { Text(stringResource(id = R.string.prefs_aprs_calc_passcode)) }
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedTextField(
|
||||
value = ssid,
|
||||
|
||||
Reference in new issue
Block a user