diff --git a/core/presentation/src/main/res/values-tr/strings.xml b/core/presentation/src/main/res/values-tr/strings.xml index 87b8324f..9d9a0b98 100644 --- a/core/presentation/src/main/res/values-tr/strings.xml +++ b/core/presentation/src/main/res/values-tr/strings.xml @@ -52,8 +52,8 @@ \n\nDoğru tahminler alabilmek için veritabanını en az haftada bir güncelleyin. What\'s new in Look4Sat - * Yeni CW Kod Çözücü sayfası (Roaming ile Harita arasında): mikrofondan canlı Morse kodu çözme ve şelale (waterfall) görüntüsü - * Ayarlar: mesaj tipi, yazı boyutu ve 9 renk teması + * Transponder CW kod çözücü paneli artık geliştirilmiş kod çözme motorunu canlı şelale (waterfall) görüntüsüyle kullanıyor + * CW kod çözme sayfaları aynı motoru paylaşıyor: gezinme sayfası ve transponder paneli Geri diff --git a/core/presentation/src/main/res/values-zh/strings.xml b/core/presentation/src/main/res/values-zh/strings.xml index f036af7b..4267a597 100644 --- a/core/presentation/src/main/res/values-zh/strings.xml +++ b/core/presentation/src/main/res/values-zh/strings.xml @@ -42,8 +42,8 @@ 请务必在设置中通过GPS、经纬度或QTH定位您的位置\n建议至少每周更新一次数据库,以确保预测结果的准确性 Look4Sat Pro 更新内容 - * 新增 CW 解码页(漫游与地图之间): 麦克风实时摩尔斯电码解码 + 瀑布图显示 - * 设置: 消息类型、字体大小、9 种颜色主题 + * 转发器 CW 解码面板已接入增强解码引擎,带实时瀑布图显示 + * CW 解码入口共用同一引擎:导航页与转发器面板 后退 diff --git a/core/presentation/src/main/res/values/strings.xml b/core/presentation/src/main/res/values/strings.xml index a6a8a202..3602d047 100644 --- a/core/presentation/src/main/res/values/strings.xml +++ b/core/presentation/src/main/res/values/strings.xml @@ -54,8 +54,8 @@ \n\nPlease update the database at least weekly to get accurate predictions. What\'s new in Look4Sat - * New CW Decoder page (between Roaming and Map): live Morse code decoding from the microphone with waterfall display - * Settings: message type, font size and 9 color themes + * Transponder CW decoder panel now uses the enhanced decoding engine with a live waterfall display + * CW decoding pages share the same engine: navigation page and transponder panel Back diff --git a/feature/cw/src/main/java/com/ve3nea/morse_expert/MainActivity.java b/feature/cw/src/main/java/com/ve3nea/morse_expert/MainActivity.java index 5c1c5c8e..547a740e 100644 --- a/feature/cw/src/main/java/com/ve3nea/morse_expert/MainActivity.java +++ b/feature/cw/src/main/java/com/ve3nea/morse_expert/MainActivity.java @@ -48,9 +48,19 @@ public class MainActivity { public android.app.Activity mActivity; public void onCreate(android.app.Activity activity, ConstraintLayout constraintLayout) { + onCreate(activity, constraintLayout, true); + } + + /** + * applyImmersive=false 用于嵌入小容器(如转发器 CW 面板): + * 不触碰宿主窗口的系统栏标志(避免影响整个 Activity)。 + */ + public void onCreate(android.app.Activity activity, ConstraintLayout constraintLayout, boolean applyImmersive) { this.mActivity = activity; - // K1.a.M(getWindow(), false): immersive status bar (nav/fullscreen flags, ported as-is) - setImmersive(activity.getWindow(), false); + if (applyImmersive) { + // K1.a.M(getWindow(), false): immersive status bar (nav/fullscreen flags, ported as-is) + setImmersive(activity.getWindow(), false); + } View inflate = constraintLayout; int i4 = R.id.decodedTextView; DecodedTextView decodedTextView = (DecodedTextView) findViewRecursive(inflate, R.id.decodedTextView); diff --git a/feature/cw/src/main/res/layout/cw_panel_main.xml b/feature/cw/src/main/res/layout/cw_panel_main.xml new file mode 100644 index 00000000..bc647b7d --- /dev/null +++ b/feature/cw/src/main/res/layout/cw_panel_main.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + diff --git a/feature/radar/build.gradle.kts b/feature/radar/build.gradle.kts index 723edbae..9125bb22 100644 --- a/feature/radar/build.gradle.kts +++ b/feature/radar/build.gradle.kts @@ -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") } \ No newline at end of file diff --git a/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/TransceiversPage.kt b/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/TransceiversPage.kt index 7c93b44c..2581228d 100644 --- a/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/TransceiversPage.kt +++ b/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/TransceiversPage.kt @@ -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) + ) } } }