From 991ea8166b2d194f26b7e6bd3602e2614568be4f Mon Sep 17 00:00:00 2001 From: YashasVM Date: Sun, 2 Aug 2026 18:33:01 +0530 Subject: [PATCH] feat(android): simplify operator camera surface --- .../java/dev/openstream/app/MainActivity.kt | 27 ++- .../openstream/app/OperatorActionPolicy.kt | 10 + .../dev/openstream/app/SettingsActivity.kt | 45 ----- .../src/main/res/drawable/bg_btn_ghost.xml | 2 +- .../main/res/drawable/bg_camera_palette.xml | 6 +- android/app/src/main/res/drawable/bg_card.xml | 6 +- android/app/src/main/res/drawable/bg_chip.xml | 4 +- .../main/res/drawable/bg_focus_reticle.xml | 2 +- .../app/src/main/res/drawable/bg_hud_chip.xml | 6 +- .../res/drawable/bg_minimal_btn_ghost.xml | 2 +- .../main/res/drawable/bg_minimal_input.xml | 6 +- .../main/res/drawable/bg_minimal_panel.xml | 6 +- .../src/main/res/drawable/bg_minimal_pill.xml | 4 +- .../app/src/main/res/layout/activity_main.xml | 52 ++++-- .../src/main/res/layout/activity_settings.xml | 175 +++--------------- .../res/layout/dialog_custom_permission.xml | 6 +- .../main/res/layout/dialog_custom_update.xml | 6 +- android/app/src/main/res/values/colors.xml | 31 ++-- android/app/src/main/res/values/strings.xml | 6 +- android/app/src/main/res/values/styles.xml | 24 ++- .../app/OperatorActionPolicyTest.kt | 21 +++ .../app/OperatorSurfaceLayoutPolicyTest.kt | 69 +++++++ 22 files changed, 248 insertions(+), 268 deletions(-) create mode 100644 android/app/src/main/java/dev/openstream/app/OperatorActionPolicy.kt create mode 100644 android/app/src/test/java/dev/openstream/app/OperatorActionPolicyTest.kt create mode 100644 android/app/src/test/java/dev/openstream/app/OperatorSurfaceLayoutPolicyTest.kt diff --git a/android/app/src/main/java/dev/openstream/app/MainActivity.kt b/android/app/src/main/java/dev/openstream/app/MainActivity.kt index d7d334a..5c2faaf 100644 --- a/android/app/src/main/java/dev/openstream/app/MainActivity.kt +++ b/android/app/src/main/java/dev/openstream/app/MainActivity.kt @@ -84,6 +84,7 @@ class MainActivity : Activity() { private lateinit var btnScreenOff: TextView private lateinit var btnTorch: TextView private lateinit var btnSettings: TextView + private lateinit var btnStart: TextView private lateinit var btnStop: TextView private lateinit var screenOffOverlay: View private lateinit var identifyOverlay: TextView @@ -452,6 +453,7 @@ class MainActivity : Activity() { btnScreenOff = findViewById(R.id.btnScreenOff) btnTorch = findViewById(R.id.btnTorch) btnSettings = findViewById(R.id.btnSettings) + btnStart = findViewById(R.id.btnStart) btnStop = findViewById(R.id.btnStop) screenOffOverlay = findViewById(R.id.screenOffOverlay) identifyOverlay = findViewById(R.id.identifyOverlay) @@ -505,6 +507,13 @@ class MainActivity : Activity() { @Suppress("DEPRECATION") startActivityForResult(intent, SETTINGS_REQUEST_CODE) } + btnStart.setOnClickListener { + if (!OperatorActionPolicy.canStart(phoneServerRunning, uiState is OpenStreamUiState.Live)) { + return@setOnClickListener + } + startPreviewIfAllowed() + startPhoneServerIfAllowed() + } btnStop.setOnClickListener { stopPhoneServer(clearReservation = true) disarmRemoteOperation() @@ -515,7 +524,7 @@ class MainActivity : Activity() { btnExposurePanel.setOnClickListener { showCameraPalette(CameraPalette.Exposure) } btnFocusPanel.setOnClickListener { showCameraPalette(CameraPalette.Focus) } btnColorPanel.setOnClickListener { showCameraPalette(CameraPalette.Color) } - btnLensPanel.setOnClickListener { showCameraPalette(CameraPalette.Lens) } + btnLensPanel.setOnClickListener { selectNextLens() } hudFps.setOnClickListener { showCameraPalette(CameraPalette.Exposure) } hudShutter.setOnClickListener { showCameraPalette(CameraPalette.Exposure) } hudIso.setOnClickListener { showCameraPalette(CameraPalette.Exposure) } @@ -902,6 +911,7 @@ class MainActivity : Activity() { paletteHelp.text = "Choose a physical lens, frame with zoom, then select stabilization supported by this camera." lensSelectorRow.visibility = View.VISIBLE buildLensButtons() + btnLensPanel.text = "LENS ${currentLens.shortLabel}" setupModes("Off", "EIS", "OIS") selectMode(when (state.settings.stabilizationMode) { StabilizationMode.Off -> 0 @@ -1041,7 +1051,11 @@ class MainActivity : Activity() { btnArmRemote.text = getString(if (remoteArmed) R.string.remote_armed else R.string.remote_disarmed) btnArmRemote.isSelected = remoteArmed btnArmRemote.setTextColor(getColor(if (remoteArmed) R.color.os_black else R.color.os_text_primary)) - btnStop.visibility = if (remoteArmed || uiState is OpenStreamUiState.Live) View.VISIBLE else View.GONE + btnStop.visibility = if (OperatorActionPolicy.shouldShowStop(remoteArmed, uiState is OpenStreamUiState.Live)) { + View.VISIBLE + } else { + View.GONE + } } private fun formatShutter(shutterNs: Long): String { @@ -1127,6 +1141,13 @@ class MainActivity : Activity() { mainHandler.postDelayed(restart, LENS_RESTART_DELAY_MS) } buildLensButtons() + btnLensPanel.text = "LENS ${currentLens.shortLabel}" + } + + private fun selectNextLens() { + if (availableLenses.size < 2) return + val currentIndex = availableLenses.indexOf(currentLens).coerceAtLeast(0) + selectLens(availableLenses[(currentIndex + 1) % availableLenses.size]) } private fun cancelLensRestart() { @@ -1739,7 +1760,7 @@ class MainActivity : Activity() { val dismissBtn = dialog.findViewById(R.id.dialogUpdateDismiss) title.text = "OpenStream ready" - message.text = "You are running OpenStream v$versionName.\nFuture updates can be checked from Settings." + message.text = "You are running OpenStream v$versionName. Updates are checked automatically." progress.visibility = View.GONE progressText.visibility = View.GONE actionBtn.text = "GOT IT" diff --git a/android/app/src/main/java/dev/openstream/app/OperatorActionPolicy.kt b/android/app/src/main/java/dev/openstream/app/OperatorActionPolicy.kt new file mode 100644 index 0000000..2e63f58 --- /dev/null +++ b/android/app/src/main/java/dev/openstream/app/OperatorActionPolicy.kt @@ -0,0 +1,10 @@ +package dev.openstream.app + +/** Keeps the minimal operator surface from starting duplicate listener sessions. */ +object OperatorActionPolicy { + fun canStart(phoneServerRunning: Boolean, isLive: Boolean): Boolean = + !phoneServerRunning && !isLive + + fun shouldShowStop(remoteArmed: Boolean, isLive: Boolean): Boolean = + remoteArmed || isLive +} diff --git a/android/app/src/main/java/dev/openstream/app/SettingsActivity.kt b/android/app/src/main/java/dev/openstream/app/SettingsActivity.kt index 4844be4..4121c80 100644 --- a/android/app/src/main/java/dev/openstream/app/SettingsActivity.kt +++ b/android/app/src/main/java/dev/openstream/app/SettingsActivity.kt @@ -2,10 +2,7 @@ package dev.openstream.app import android.app.Activity import android.content.Intent -import android.os.Build import android.os.Bundle -import android.view.View -import android.view.WindowInsets import android.widget.EditText import android.widget.TextView import android.widget.Toast @@ -21,10 +18,6 @@ class SettingsActivity : Activity() { private lateinit var btnSave: TextView private lateinit var btnSaveAndConnect: TextView private lateinit var btnBack: TextView - private lateinit var btnCheckUpdates: TextView - private lateinit var btnToggleAdvanced: TextView - private lateinit var advancedSettingsPanel: View - private lateinit var versionInfo: TextView private lateinit var appUpdater: AppUpdater override fun onCreate(savedInstanceState: Bundle?) { @@ -38,27 +31,15 @@ class SettingsActivity : Activity() { btnSave = findViewById(R.id.btnSaveSettings) btnSaveAndConnect = findViewById(R.id.btnSaveAndConnect) btnBack = findViewById(R.id.btnBackSettings) - btnCheckUpdates = findViewById(R.id.btnCheckUpdates) - btnToggleAdvanced = findViewById(R.id.btnToggleAdvanced) - advancedSettingsPanel = findViewById(R.id.advancedSettingsPanel) - versionInfo = findViewById(R.id.settingsVersionInfo) appUpdater = AppUpdater(this) appUpdater.register() loadSettings() - showVersionInfo() - renderAdvancedVisibility(manualSettingsInUse()) btnSave.setOnClickListener { saveSettings(connectAfterSave = false) } btnSaveAndConnect.setOnClickListener { saveSettings(connectAfterSave = true) } btnBack.setOnClickListener { finish() } - btnCheckUpdates.setOnClickListener { - appUpdater.checkForUpdates(showAlreadyCurrent = true) - } - btnToggleAdvanced.setOnClickListener { - renderAdvancedVisibility(advancedSettingsPanel.visibility != View.VISIBLE) - } } override fun onResume() { @@ -148,32 +129,6 @@ class SettingsActivity : Activity() { inputListeningPort.error = null } - private fun showVersionInfo() { - runCatching { - val info = packageManager.getPackageInfo(packageName, 0) - val code = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - info.longVersionCode - } else { - @Suppress("DEPRECATION") - info.versionCode.toLong() - } - versionInfo.text = "OpenStream v${info.versionName} (${code})" - } - } - - private fun manualSettingsInUse(): Boolean { - val prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE) - return !prefs.getString(KEY_OBS_HOST, "").isNullOrBlank() || - prefs.getInt(KEY_OBS_PORT, ConnectionTarget.DEFAULT_PORT) != ConnectionTarget.DEFAULT_PORT || - prefs.getInt(KEY_LATENCY, ConnectionTarget.DEFAULT_LATENCY_MS) != ConnectionTarget.DEFAULT_LATENCY_MS || - prefs.getInt(KEY_LISTENING_PORT, ConnectionTarget.DEFAULT_PORT) != ConnectionTarget.DEFAULT_PORT - } - - private fun renderAdvancedVisibility(visible: Boolean) { - advancedSettingsPanel.visibility = if (visible) View.VISIBLE else View.GONE - btnToggleAdvanced.setText(if (visible) R.string.settings_hide_advanced else R.string.settings_show_advanced) - } - companion object { const val PREFS_NAME = "openstream_settings" const val KEY_OBS_HOST = "obs_host" diff --git a/android/app/src/main/res/drawable/bg_btn_ghost.xml b/android/app/src/main/res/drawable/bg_btn_ghost.xml index 9ebf369..3b0f953 100644 --- a/android/app/src/main/res/drawable/bg_btn_ghost.xml +++ b/android/app/src/main/res/drawable/bg_btn_ghost.xml @@ -3,5 +3,5 @@ android:shape="rectangle"> - + diff --git a/android/app/src/main/res/drawable/bg_camera_palette.xml b/android/app/src/main/res/drawable/bg_camera_palette.xml index 121a7fd..a896480 100644 --- a/android/app/src/main/res/drawable/bg_camera_palette.xml +++ b/android/app/src/main/res/drawable/bg_camera_palette.xml @@ -1,6 +1,6 @@ - - - + + + diff --git a/android/app/src/main/res/drawable/bg_card.xml b/android/app/src/main/res/drawable/bg_card.xml index 4d45332..c6181e7 100644 --- a/android/app/src/main/res/drawable/bg_card.xml +++ b/android/app/src/main/res/drawable/bg_card.xml @@ -1,7 +1,7 @@ - - - + + + diff --git a/android/app/src/main/res/drawable/bg_chip.xml b/android/app/src/main/res/drawable/bg_chip.xml index e4f28b0..267ce42 100644 --- a/android/app/src/main/res/drawable/bg_chip.xml +++ b/android/app/src/main/res/drawable/bg_chip.xml @@ -2,6 +2,6 @@ - - + + diff --git a/android/app/src/main/res/drawable/bg_focus_reticle.xml b/android/app/src/main/res/drawable/bg_focus_reticle.xml index 99a6208..c77aa63 100644 --- a/android/app/src/main/res/drawable/bg_focus_reticle.xml +++ b/android/app/src/main/res/drawable/bg_focus_reticle.xml @@ -2,5 +2,5 @@ - + diff --git a/android/app/src/main/res/drawable/bg_hud_chip.xml b/android/app/src/main/res/drawable/bg_hud_chip.xml index 58fb54b..aaad571 100644 --- a/android/app/src/main/res/drawable/bg_hud_chip.xml +++ b/android/app/src/main/res/drawable/bg_hud_chip.xml @@ -1,6 +1,6 @@ - - - + + + diff --git a/android/app/src/main/res/drawable/bg_minimal_btn_ghost.xml b/android/app/src/main/res/drawable/bg_minimal_btn_ghost.xml index 9ebf369..3b0f953 100644 --- a/android/app/src/main/res/drawable/bg_minimal_btn_ghost.xml +++ b/android/app/src/main/res/drawable/bg_minimal_btn_ghost.xml @@ -3,5 +3,5 @@ android:shape="rectangle"> - + diff --git a/android/app/src/main/res/drawable/bg_minimal_input.xml b/android/app/src/main/res/drawable/bg_minimal_input.xml index 6daf23c..83c5061 100644 --- a/android/app/src/main/res/drawable/bg_minimal_input.xml +++ b/android/app/src/main/res/drawable/bg_minimal_input.xml @@ -1,7 +1,7 @@ - - - + + + diff --git a/android/app/src/main/res/drawable/bg_minimal_panel.xml b/android/app/src/main/res/drawable/bg_minimal_panel.xml index 9e05551..c6181e7 100644 --- a/android/app/src/main/res/drawable/bg_minimal_panel.xml +++ b/android/app/src/main/res/drawable/bg_minimal_panel.xml @@ -1,7 +1,7 @@ - - - + + + diff --git a/android/app/src/main/res/drawable/bg_minimal_pill.xml b/android/app/src/main/res/drawable/bg_minimal_pill.xml index e4f28b0..267ce42 100644 --- a/android/app/src/main/res/drawable/bg_minimal_pill.xml +++ b/android/app/src/main/res/drawable/bg_minimal_pill.xml @@ -2,6 +2,6 @@ - - + + diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml index dd34ffd..be0884f 100644 --- a/android/app/src/main/res/layout/activity_main.xml +++ b/android/app/src/main/res/layout/activity_main.xml @@ -60,7 +60,7 @@ android:paddingStart="20dp" android:paddingTop="18dp" android:paddingEnd="20dp" - android:paddingBottom="34dp"> + android:paddingBottom="34dp"> + + @@ -105,7 +116,8 @@ android:id="@+id/authorityBadge" style="@style/OpenStream.HudStatus" android:layout_marginStart="8dp" - android:text="COLLABORATIVE" /> + android:text="COLLABORATIVE" + android:visibility="gone" /> - - - - - + + + + + - + @@ -185,7 +197,8 @@ android:text="@string/status_ready" android:textColor="@color/os_text_primary" android:textSize="20sp" - android:textStyle="bold" /> + android:fontFamily="sans-serif" + android:letterSpacing="-0.02" /> - - - + + + - + - - + + + + android:fontFamily="sans-serif" + android:letterSpacing="-0.02" /> - - - - -