From d7ad83b71f10b86c61e92c86162ae71eb2023c47 Mon Sep 17 00:00:00 2001 From: Ali Alavi Date: Mon, 24 Aug 2026 06:25:48 +0000 Subject: [PATCH] fix(sync): instant input clearing, app switch auto-canvas reset, and bidirectional empty state synchronization --- .../java/com/soniox/remotemic/MainActivity.kt | 36 ++++++++----------- mac/src/FocusedInputSync.swift | 11 +++--- 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/android/app/src/main/java/com/soniox/remotemic/MainActivity.kt b/android/app/src/main/java/com/soniox/remotemic/MainActivity.kt index 8597d62..47dabba 100644 --- a/android/app/src/main/java/com/soniox/remotemic/MainActivity.kt +++ b/android/app/src/main/java/com/soniox/remotemic/MainActivity.kt @@ -89,7 +89,7 @@ class MainActivity : AppCompatActivity() { setupUI() checkPermissions() - AppLogger.log("Main", "اپلیکیشن همگام‌سازی صوتی سانی‌اوکس راه‌اندازی شد (نسخه بهینه‌شده فوق‌سریع v5.7)") + AppLogger.log("Main", "اپلیکیشن همگام‌سازی صوتی سانی‌اوکس راه‌اندازی شد (نسخه بهینه‌شده فوق‌سریع v5.8)") // Initialize Collaborative WebSocket Client streamDictationClient = StreamDictationClient( @@ -105,8 +105,8 @@ class MainActivity : AppCompatActivity() { if (state.source != "android") { val timeSinceLocalEdit = System.currentTimeMillis() - lastLocalUserEditTime - // If user is actively typing or recording right now on phone, do not override - if ((timeSinceLocalEdit < 1000L && binding.etTranscript.hasFocus()) || isCurrentlyRecording) { + // If user is actively typing right now on phone, do not override + if ((timeSinceLocalEdit < 600L && binding.etTranscript.hasFocus()) || isCurrentlyRecording) { return@StreamDictationClient } @@ -115,12 +115,8 @@ class MainActivity : AppCompatActivity() { return@StreamDictationClient } - // Ghost empty sync protection for opaque apps - if (state.text.isEmpty() && lastLocalText.isNotEmpty() && state.source == "mac") { - if (state.app.isNotEmpty() && state.app != "Desktop" && state.app != "App") { - binding.tvMacStatus.text = "متصل به ${state.app} 🖥️" - } - return@StreamDictationClient + if (state.app.isNotEmpty() && state.app != "Desktop" && state.app != "App") { + binding.tvMacStatus.text = "متصل به ${state.app} 🖥️" } if (state.text != lastLocalText) { @@ -128,22 +124,15 @@ class MainActivity : AppCompatActivity() { lastLocalText = state.text currentRevision = state.revision - val currentCursor = binding.etTranscript.selectionStart binding.etTranscript.setText(state.text) - - val targetCursor = if (binding.etTranscript.hasFocus() && currentCursor >= 0) { - currentCursor.coerceIn(0, state.text.length) - } else { - state.cursor.coerceIn(0, state.text.length) - } + val targetCursor = state.cursor.coerceIn(0, state.text.length) binding.etTranscript.setSelection(targetCursor) + val wordCount = if (state.text.trim().isEmpty()) 0 else state.text.trim().split("\\s+".toRegex()).size + binding.tvCharCount.text = "$wordCount کلمه" + isApplyingRemoteUpdate = false } - - if (state.app.isNotEmpty() && state.app != "Desktop" && state.app != "App") { - binding.tvMacStatus.text = "متصل به ${state.app} 🖥️" - } } }, onPartialSpeechText = { livePartial -> @@ -297,13 +286,18 @@ class MainActivity : AppCompatActivity() { isApplyingRemoteUpdate = true binding.etTranscript.setText("") lastLocalText = "" + binding.tvCharCount.text = "0 کلمه" lastLocalUserEditTime = System.currentTimeMillis() isApplyingRemoteUpdate = false pendingSyncRunnable?.let { debounceHandler.removeCallbacks(it) } pendingSyncRunnable = null - streamDictationClient?.sendPhoneEdit("", 0) + // Cleanly clear active box on Mac as well (Cmd+A -> Backspace) + streamDictationClient?.sendForceReplace("", 0) + lifecycleScope.launch { + sendDirectPaste("", 0) + } binding.tvInstruction.text = getString(R.string.hold_to_speak) binding.tvInstruction.setTextColor(ContextCompat.getColor(this, R.color.text_secondary)) } diff --git a/mac/src/FocusedInputSync.swift b/mac/src/FocusedInputSync.swift index 3d5d074..11613df 100644 --- a/mac/src/FocusedInputSync.swift +++ b/mac/src/FocusedInputSync.swift @@ -173,12 +173,14 @@ public final class FocusedInputSync { let appChanged = (appName != lastObservedApp) if appChanged { lastObservedApp = appName + lastObservedText = "" + lastObservedCursor = 0 } guard let elem = elemOpt else { - if appChanged && !lastObservedText.isEmpty { + if appChanged { localRevision += 1 - return MacInputState(app: appName, text: lastObservedText, cursor: lastObservedCursor, selection: 0, revision: localRevision) + return MacInputState(app: appName, text: "", cursor: 0, selection: 0, revision: localRevision) } return nil } @@ -214,11 +216,6 @@ public final class FocusedInputSync { } } - // Ghost empty suppression for non-native containers - if text.isEmpty && !lastObservedText.isEmpty && !isTextRole { - return nil - } - var cursor = text.count var selLen = 0 var rangeObj: CFTypeRef?