From b35a370edb25b9e941e753603a71af9e3ae856e6 Mon Sep 17 00:00:00 2001 From: AnsjDev Date: Thu, 25 Jun 2026 13:35:28 -0300 Subject: [PATCH] feat: add option to hide permanent island in landscape - Add hidePermanentIslandLandscape settings preference and UI switch - Update PermanentIslandManager to dismiss the empty island when in landscape --- .../hyperbridge/data/AppPreferences.kt | 12 +++++++ .../service/NotificationReaderService.kt | 15 ++++++++ .../service/PermanentIslandManager.kt | 25 +++++++++++-- .../settings/PermanentIslandConfigScreen.kt | 35 ++++++++++++++++--- app/src/main/res/values-pt-rBR/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 6 files changed, 83 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/d4viddf/hyperbridge/data/AppPreferences.kt b/app/src/main/java/com/d4viddf/hyperbridge/data/AppPreferences.kt index 0300022..f821393 100644 --- a/app/src/main/java/com/d4viddf/hyperbridge/data/AppPreferences.kt +++ b/app/src/main/java/com/d4viddf/hyperbridge/data/AppPreferences.kt @@ -508,6 +508,7 @@ class AppPreferences(context: Context) { private val SHOW_PERMANENT_ISLAND = "show_permanent_island" private val PERMANENT_ISLAND_WIDTH = "permanent_island_width" + private val HIDE_PERMANENT_ISLAND_LANDSCAPE = "hide_permanent_island_landscape" val isPermanentIslandEnabledFlow: Flow = dao.getSettingFlow(SHOW_PERMANENT_ISLAND) .map { it?.toBoolean() ?: false } @@ -515,6 +516,9 @@ class AppPreferences(context: Context) { val permanentIslandWidthFlow: Flow = dao.getSettingFlow(PERMANENT_ISLAND_WIDTH) .map { it?.toIntOrNull() ?: 0 } + val hidePermanentIslandLandscapeFlow: Flow = dao.getSettingFlow(HIDE_PERMANENT_ISLAND_LANDSCAPE) + .map { it?.toBoolean() ?: false } + suspend fun setPermanentIslandEnabled(value: Boolean) { save(SHOW_PERMANENT_ISLAND, value.toString()) } @@ -523,6 +527,14 @@ class AppPreferences(context: Context) { save(PERMANENT_ISLAND_WIDTH, value.toString()) } + suspend fun setHidePermanentIslandLandscape(value: Boolean) { + save(HIDE_PERMANENT_ISLAND_LANDSCAPE, value.toString()) + } + + fun hidePermanentIslandLandscapeSync(): Boolean { + return memoryCache[HIDE_PERMANENT_ISLAND_LANDSCAPE]?.toBoolean() ?: false + } + // ======================================================================== // SYNCHRONOUS CACHE GETTERS // ======================================================================== diff --git a/app/src/main/java/com/d4viddf/hyperbridge/service/NotificationReaderService.kt b/app/src/main/java/com/d4viddf/hyperbridge/service/NotificationReaderService.kt index 3b5cea9..9f78968 100644 --- a/app/src/main/java/com/d4viddf/hyperbridge/service/NotificationReaderService.kt +++ b/app/src/main/java/com/d4viddf/hyperbridge/service/NotificationReaderService.kt @@ -524,8 +524,23 @@ class NotificationReaderService : NotificationListenerService() { } } + private fun logStateChange(isLandscape: Boolean) { + val orientation = if (isLandscape) "Landscape" else "Portrait" + val isIslandExhibited = activeIslands.isNotEmpty() || activeWidgets.isNotEmpty() || nativeIslands.isNotEmpty() || permanentIslandManager.isIslandActive() + val islandState = if (isIslandExhibited) "Showing Island" else "No Island" + Log.d(TAG, "State: $orientation | $islandState") + } + private fun updatePermanentIsland() { permanentIslandManager.onActiveNotificationsChanged(activeIslands.size + activeWidgets.size, nativeIslands.isNotEmpty()) + val isLandscape = resources.configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE + logStateChange(isLandscape) + } + + override fun onConfigurationChanged(newConfig: android.content.res.Configuration) { + super.onConfigurationChanged(newConfig) + permanentIslandManager.onOrientationChanged() + logStateChange(newConfig.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE) } // ========================================================================= diff --git a/app/src/main/java/com/d4viddf/hyperbridge/service/PermanentIslandManager.kt b/app/src/main/java/com/d4viddf/hyperbridge/service/PermanentIslandManager.kt index 4c22445..983b1d0 100644 --- a/app/src/main/java/com/d4viddf/hyperbridge/service/PermanentIslandManager.kt +++ b/app/src/main/java/com/d4viddf/hyperbridge/service/PermanentIslandManager.kt @@ -28,8 +28,11 @@ class PermanentIslandManager( private var isPermanentIslandEnabled = false private var isIslandActive = false private var currentRealNotifications = 0 + + fun isIslandActive(): Boolean = isIslandActive private var hasNativeIsland = false private var currentWidth = 0 + private var isHideInLandscapeEnabled = false init { scope.launch { @@ -40,6 +43,14 @@ class PermanentIslandManager( } } } + scope.launch { + preferences.hidePermanentIslandLandscapeFlow.collectLatest { hide -> + if (isHideInLandscapeEnabled != hide) { + isHideInLandscapeEnabled = hide + updateState() + } + } + } scope.launch { preferences.permanentIslandWidthFlow.collectLatest { width -> if (currentWidth != width) { @@ -58,8 +69,19 @@ class PermanentIslandManager( updateState() } + fun onOrientationChanged() { + updateState() + } private fun updateState() { - if (isPermanentIslandEnabled && currentRealNotifications == 0 && !hasNativeIsland) { + val isLandscape = context.resources.configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE + val shouldShow = isPermanentIslandEnabled && + currentRealNotifications == 0 && + !hasNativeIsland && + !(isHideInLandscapeEnabled && isLandscape) + + Log.d(TAG, "updateState: shouldShow=$shouldShow, isLandscape=$isLandscape, isHideInLandscapeEnabled=$isHideInLandscapeEnabled") + + if (shouldShow) { if (!isIslandActive) { dispatchPermanentIsland() isIslandActive = true @@ -71,7 +93,6 @@ class PermanentIslandManager( } } } - @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS) private fun dispatchPermanentIsland() { try { diff --git a/app/src/main/java/com/d4viddf/hyperbridge/ui/screens/settings/PermanentIslandConfigScreen.kt b/app/src/main/java/com/d4viddf/hyperbridge/ui/screens/settings/PermanentIslandConfigScreen.kt index 448c3d2..36301f7 100644 --- a/app/src/main/java/com/d4viddf/hyperbridge/ui/screens/settings/PermanentIslandConfigScreen.kt +++ b/app/src/main/java/com/d4viddf/hyperbridge/ui/screens/settings/PermanentIslandConfigScreen.kt @@ -48,10 +48,12 @@ fun PermanentIslandConfigScreen( val isEnabled by prefs.isPermanentIslandEnabledFlow.collectAsState(initial = false) val islandWidth by prefs.permanentIslandWidthFlow.collectAsState(initial = 0) + val hideInLandscape by prefs.hidePermanentIslandLandscapeFlow.collectAsState(initial = false) PermanentIslandConfigContent( isEnabled = isEnabled, islandWidth = islandWidth, + hideInLandscape = hideInLandscape, onEnabledChange = { checked -> scope.launch { prefs.setPermanentIslandEnabled(checked) @@ -62,6 +64,11 @@ fun PermanentIslandConfigScreen( prefs.setPermanentIslandWidth(width) } }, + onHideInLandscapeChange = { hide -> + scope.launch { + prefs.setHidePermanentIslandLandscape(hide) + } + }, onBack = onBack ) } @@ -71,8 +78,10 @@ fun PermanentIslandConfigScreen( fun PermanentIslandConfigContent( isEnabled: Boolean, islandWidth: Int, + hideInLandscape: Boolean, onEnabledChange: (Boolean) -> Unit, onWidthChange: (Int) -> Unit, + onHideInLandscapeChange: (Boolean) -> Unit, onBack: () -> Unit ) { var sliderValue by androidx.compose.runtime.remember { androidx.compose.runtime.mutableFloatStateOf(0f) } @@ -105,8 +114,6 @@ fun PermanentIslandConfigContent( PermanentIslandPreview(islandWidthValue = if (isDragging) sliderValue.toInt() else islandWidth) Spacer(Modifier.height(24.dp)) - - Card( colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainer), @@ -125,7 +132,6 @@ fun PermanentIslandConfigContent( style = MaterialTheme.typography.titleMedium, fontWeight = androidx.compose.ui.text.font.FontWeight.Medium ) - } Switch( checked = isEnabled, @@ -152,6 +158,24 @@ fun PermanentIslandConfigContent( }, valueRange = 0f..20f ) + Spacer(Modifier.height(16.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = stringResource(R.string.permanent_island_hide_landscape), + style = MaterialTheme.typography.titleSmall, + fontWeight = androidx.compose.ui.text.font.FontWeight.Medium + ) + } + Switch( + checked = hideInLandscape, + onCheckedChange = onHideInLandscapeChange + ) + } } } } @@ -161,7 +185,6 @@ fun PermanentIslandConfigContent( style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant ) - } } } @@ -173,9 +196,13 @@ fun PermanentIslandConfigScreenPreview() { PermanentIslandConfigContent( isEnabled = true, islandWidth = 10, + hideInLandscape = false, onEnabledChange = {}, onWidthChange = {}, + onHideInLandscapeChange = {}, onBack = {} ) } } + + diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 17dea62..d78cc28 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -45,6 +45,7 @@ Mantenha a Ilha Dinâmica permanentemente visível na tela quando não houver notificações ativas. Quando ativada, uma Ilha Dinâmica vazia será exibida permanentemente na sua tela. Ela se ocultará automaticamente quando notificações reais chegarem e reaparecerá quando todas as notificações forem apagadas. Ela não ficará visível na sua área de notificações. Largura da ilha + Ocultar na Horizontal Sobre Desenvolvedor Visite d4viddf.com diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 90bfe5e..3cfd59d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -56,6 +56,7 @@ Keep the Dynamic Island permanently visible on screen when there are no active notifications. When enabled, an empty Dynamic Island will be shown permanently on your screen. It will automatically hide when real notifications arrive and reappear when all notifications are cleared. It will not be visible in your notification shade. Island Width + Hide in Landscape About Developer