feat(radar,cw): transponder CW panel switches to the Morse Expert engine
Background: the transponder panel CW decoder (added by the upstream fork author) used a lightweight Kotlin Bayesian engine (core/domain/cw, kept untouched as a fallback). This change makes the panel use the Morse Expert engine ported in 4.5.0, so both CW entry points share the same decoder with a live waterfall. Changes: - New mini layout cw_panel_main.xml (waterfall 80dp + decoded text, status line hidden but ID kept for controller lookup) - MainActivity.onCreate overload with applyImmersive flag; panel binds with false so the host window system bars are not touched - CwDecoderPanel now embeds the mini layout via AndroidView and drives the MainActivity controller: start/stop/reset map to the engine, lifecycle follows panel expand (start) / collapse (release mic) - radar module now depends on feature:cw (+ constraintlayout 2.2.1, same as cw) for layout + controller reuse - What's new rewritten in EN/TR/ZH for this release only Verified: :feature:radar:compileDebugKotlin and :app:compileDebugKotlin BUILD SUCCESSFUL locally; check_strings.py OK (7 files, no bare apostrophes).
This commit is contained in:
1 parent
f731775346
commit
47f3e424ff
7 files changed
+139
-49
No files matched your search
@@ -8,4 +8,8 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation(project(":feature:mutual"))
|
||||
// CW 解码面板: 复用 feature:cw 的 Morse Expert 引擎(布局 cw_panel_main + MainActivity 控制器)
|
||||
implementation(project(":feature:cw"))
|
||||
// 与 feature:cw 一致(控制器按 ID 递归查找 ConstraintLayout 视图)
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
|
||||
}
|
||||
+58
-41
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
package com.rtbishop.look4sat.feature.radar
|
||||
|
||||
import android.app.Activity
|
||||
import android.view.LayoutInflater
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
@@ -50,6 +52,7 @@ import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -60,6 +63,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
@@ -68,6 +72,8 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import com.rtbishop.look4sat.core.domain.model.SatRadio
|
||||
import com.rtbishop.look4sat.core.domain.predict.OrbitalPos
|
||||
import com.rtbishop.look4sat.core.domain.utility.DopplerFrequencyCalculator
|
||||
@@ -75,6 +81,8 @@ import com.rtbishop.look4sat.core.presentation.CardButton
|
||||
import com.rtbishop.look4sat.core.presentation.R
|
||||
import com.rtbishop.look4sat.core.presentation.formatFrequency
|
||||
import com.rtbishop.look4sat.core.presentation.infiniteMarquee
|
||||
import com.rtbishop.look4sat.feature.cw.R as CwR
|
||||
import com.ve3nea.morse_expert.MainActivity
|
||||
import java.util.Locale
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
@@ -661,19 +669,56 @@ private fun CwDecoderPanel(
|
||||
}
|
||||
|
||||
AnimatedVisibility(visible = cw.isExpanded) {
|
||||
// Morse Expert 引擎(4.5.0 移植): 小布局 cw_panel_main(瀑布图 + 解码文本)
|
||||
// 自带 Kotlin 贝叶斯引擎(CwDecoder)不再用于此面板(代码保留作回退)
|
||||
val context = LocalContext.current
|
||||
val activity = remember { context as? Activity }
|
||||
val controller = remember { MainActivity() }
|
||||
val rootView = remember {
|
||||
LayoutInflater.from(context).inflate(CwR.layout.cw_panel_main, null) as ConstraintLayout
|
||||
}
|
||||
var initialized by remember { mutableStateOf(false) }
|
||||
var listening by remember { mutableStateOf(false) }
|
||||
|
||||
// 生命周期: 展开即绑定视图(不改宿主窗口); 收起/离开自动释放
|
||||
DisposableEffect(Unit) {
|
||||
if (activity != null) {
|
||||
controller.onCreate(activity, rootView, false)
|
||||
}
|
||||
onDispose {
|
||||
controller.onPause()
|
||||
controller.onDestroy()
|
||||
}
|
||||
}
|
||||
// 权限已授予(首次进入/授权回调统一入口): 初始化引擎 + 启动录音
|
||||
LaunchedEffect(cw.hasPermission) {
|
||||
if (cw.hasPermission && !initialized) {
|
||||
controller.onPermissionGranted() // = v(): 创建音频采集 + 解码核心
|
||||
controller.onResume() // 立即启动录音
|
||||
initialized = true
|
||||
listening = true
|
||||
}
|
||||
}
|
||||
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
// Control buttons row
|
||||
// Control buttons row(映射到控制器)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
if (cw.status == CwStatus.Idle) {
|
||||
if (!listening) {
|
||||
Button(
|
||||
onClick = {
|
||||
if (!cw.hasPermission) {
|
||||
requestMicPermission()
|
||||
} else if (!initialized) {
|
||||
controller.onPermissionGranted()
|
||||
controller.onResume()
|
||||
initialized = true
|
||||
listening = true
|
||||
} else {
|
||||
onAction(RadarAction.CwStartListening)
|
||||
controller.onResume()
|
||||
listening = true
|
||||
}
|
||||
},
|
||||
modifier = Modifier.weight(1f)
|
||||
@@ -682,58 +727,30 @@ private fun CwDecoderPanel(
|
||||
}
|
||||
} else {
|
||||
Button(
|
||||
onClick = { onAction(RadarAction.CwStopListening) },
|
||||
onClick = {
|
||||
controller.onPause()
|
||||
listening = false
|
||||
},
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text(stringResource(R.string.radar_cw_stop))
|
||||
}
|
||||
}
|
||||
OutlinedButton(
|
||||
onClick = { onAction(RadarAction.CwReset) },
|
||||
onClick = { controller.clearDecoded() },
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text(stringResource(R.string.radar_cw_reset))
|
||||
}
|
||||
}
|
||||
|
||||
// Signal strength indicator
|
||||
if (cw.status == CwStatus.Listening && cw.signalStrength > 0f) {
|
||||
val strengthPct = (cw.signalStrength * 100).toInt()
|
||||
Text(
|
||||
text = "Signal: $strengthPct%",
|
||||
fontSize = 12.sp,
|
||||
color = if (cw.signalStrength > 0.5f) Color(0xFF4CAF50)
|
||||
else if (cw.signalStrength > 0.2f) Color(0xFFFFC107)
|
||||
else MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
||||
// Decoded text output
|
||||
ElevatedCard(
|
||||
// 瀑布图 + 解码文本(原版布局迷你版, 适配容器)
|
||||
AndroidView(
|
||||
factory = { rootView },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(120.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Decoded:",
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = cw.decodedText.ifEmpty { "Waiting for CW signal..." },
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
}
|
||||
.height(150.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user