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)
+ )
}
}
}