feat: Initial Close to system tray implementation #165
This commit is contained in:
parent
84565fbcc8
commit
46f337189c
|
@ -85,6 +85,8 @@ interface SettingsReadRepository {
|
|||
|
||||
fun getUseExternalBrowser(): Flow<Boolean>
|
||||
|
||||
fun getCloseToTray(): Flow<Boolean>
|
||||
|
||||
fun getColors(): Flow<AppColors?>
|
||||
|
||||
fun getLocale(): Flow<String?>
|
||||
|
|
|
@ -157,6 +157,10 @@ interface SettingsReadWriteRepository : SettingsReadRepository {
|
|||
useExternalBrowser: Boolean,
|
||||
): IO<Unit>
|
||||
|
||||
fun setCloseToTray(
|
||||
closeToTray: Boolean,
|
||||
): IO<Unit>
|
||||
|
||||
fun setColors(
|
||||
colors: AppColors?,
|
||||
): IO<Unit>
|
||||
|
|
|
@ -67,6 +67,7 @@ class SettingsRepositoryImpl(
|
|||
private const val KEY_TWO_PANEL_LAYOUT_LANDSCAPE = "two_panel_layout_landscape"
|
||||
private const val KEY_TWO_PANEL_LAYOUT_PORTRAIT = "two_panel_layout_portrait"
|
||||
private const val KEY_USE_EXTERNAL_BROWSER = "use_external_browser"
|
||||
private const val KEY_CLOSE_TO_TRAY = "close_to_tray"
|
||||
private const val KEY_FONT = "font"
|
||||
private const val KEY_THEME = "theme"
|
||||
private const val KEY_THEME_USE_AMOLED_DARK = "theme_use_amoled_dark"
|
||||
|
@ -166,6 +167,9 @@ class SettingsRepositoryImpl(
|
|||
private val useExternalBrowserPref =
|
||||
store.getBoolean(KEY_USE_EXTERNAL_BROWSER, false)
|
||||
|
||||
private val closeToTrayPref =
|
||||
store.getBoolean(KEY_CLOSE_TO_TRAY, false)
|
||||
|
||||
private val navAnimationPref =
|
||||
store.getEnumNullable(KEY_NAV_ANIMATION, lens = NavAnimation::key)
|
||||
|
||||
|
@ -412,6 +416,13 @@ class SettingsRepositoryImpl(
|
|||
override fun setUseExternalBrowser(useExternalBrowser: Boolean) = useExternalBrowserPref
|
||||
.setAndCommit(useExternalBrowser)
|
||||
|
||||
override fun setCloseToTray(
|
||||
closeToTray: Boolean,
|
||||
) = closeToTrayPref
|
||||
.setAndCommit(closeToTray)
|
||||
|
||||
override fun getCloseToTray() = closeToTrayPref
|
||||
|
||||
override fun setColors(colors: AppColors?) = colorsPref
|
||||
.setAndCommit(colors)
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package com.artemchep.keyguard.common.usecase
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface GetCloseToTray : () -> Flow<Boolean>
|
|
@ -0,0 +1,5 @@
|
|||
package com.artemchep.keyguard.common.usecase
|
||||
|
||||
import com.artemchep.keyguard.common.io.IO
|
||||
|
||||
interface PutCloseToTray : (Boolean) -> IO<Unit>
|
|
@ -0,0 +1,20 @@
|
|||
package com.artemchep.keyguard.common.usecase.impl
|
||||
|
||||
import com.artemchep.keyguard.common.service.settings.SettingsReadRepository
|
||||
import com.artemchep.keyguard.common.usecase.GetCloseToTray
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import org.kodein.di.DirectDI
|
||||
import org.kodein.di.instance
|
||||
|
||||
class GetCloseToTrayImpl(
|
||||
settingsReadRepository: SettingsReadRepository,
|
||||
) : GetCloseToTray {
|
||||
private val sharedFlow = settingsReadRepository.getCloseToTray()
|
||||
.distinctUntilChanged()
|
||||
|
||||
constructor(directDI: DirectDI) : this(
|
||||
settingsReadRepository = directDI.instance(),
|
||||
)
|
||||
|
||||
override fun invoke() = sharedFlow
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package com.artemchep.keyguard.common.usecase.impl
|
||||
|
||||
import com.artemchep.keyguard.common.io.IO
|
||||
import com.artemchep.keyguard.common.service.settings.SettingsReadWriteRepository
|
||||
import com.artemchep.keyguard.common.usecase.PutCloseToTray
|
||||
import org.kodein.di.DirectDI
|
||||
import org.kodein.di.instance
|
||||
|
||||
class PutCloseToTrayImpl(
|
||||
private val settingsReadWriteRepository: SettingsReadWriteRepository,
|
||||
) : PutCloseToTray {
|
||||
constructor(directDI: DirectDI) : this(
|
||||
settingsReadWriteRepository = directDI.instance(),
|
||||
)
|
||||
|
||||
override fun invoke(closeToTray: Boolean): IO<Unit> = settingsReadWriteRepository
|
||||
.setCloseToTray(closeToTray)
|
||||
}
|
|
@ -41,6 +41,7 @@ import com.artemchep.keyguard.feature.home.settings.component.settingClearCache
|
|||
import com.artemchep.keyguard.feature.home.settings.component.settingClipboardAutoClearProvider
|
||||
import com.artemchep.keyguard.feature.home.settings.component.settingClipboardAutoRefreshProvider
|
||||
import com.artemchep.keyguard.feature.home.settings.component.settingClipboardNotificationSettingsProvider
|
||||
import com.artemchep.keyguard.feature.home.settings.component.settingCloseToTrayProvider
|
||||
import com.artemchep.keyguard.feature.home.settings.component.settingColorAccentProvider
|
||||
import com.artemchep.keyguard.feature.home.settings.component.settingColorSchemeProvider
|
||||
import com.artemchep.keyguard.feature.home.settings.component.settingConcealFieldsProvider
|
||||
|
@ -166,6 +167,7 @@ object Setting {
|
|||
const val TWO_PANEL_LAYOUT_LANDSCAPE = "two_panel_layout_landscape"
|
||||
const val TWO_PANEL_LAYOUT_PORTRAIT = "two_panel_layout_portrait"
|
||||
const val USE_EXTERNAL_BROWSER = "use_external_browser"
|
||||
const val CLOSE_TO_TRAY = "close_to_tray"
|
||||
const val APP_ICONS = "app_icons"
|
||||
const val WEBSITE_ICONS = "website_icons"
|
||||
const val CHECK_PWNED_PASSWORDS = "check_pwned_passwords"
|
||||
|
@ -246,6 +248,7 @@ val hub = mapOf<String, (DirectDI) -> SettingComponent>(
|
|||
Setting.TWO_PANEL_LAYOUT_LANDSCAPE to ::settingTwoPanelLayoutLandscapeProvider,
|
||||
Setting.TWO_PANEL_LAYOUT_PORTRAIT to ::settingTwoPanelLayoutPortraitProvider,
|
||||
Setting.USE_EXTERNAL_BROWSER to ::settingUseExternalBrowserProvider,
|
||||
Setting.CLOSE_TO_TRAY to ::settingCloseToTrayProvider,
|
||||
Setting.APP_ICONS to ::settingAppIconsProvider,
|
||||
Setting.WEBSITE_ICONS to ::settingWebsiteIconsProvider,
|
||||
Setting.CHECK_PWNED_PASSWORDS to ::settingCheckPwnedPasswordsProvider,
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
package com.artemchep.keyguard.feature.home.settings.component
|
||||
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.CloseFullscreen
|
||||
import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import arrow.core.partially1
|
||||
import com.artemchep.keyguard.common.io.launchIn
|
||||
import com.artemchep.keyguard.common.usecase.GetCloseToTray
|
||||
import com.artemchep.keyguard.common.usecase.PutCloseToTray
|
||||
import com.artemchep.keyguard.common.usecase.WindowCoroutineScope
|
||||
import com.artemchep.keyguard.platform.CurrentPlatform
|
||||
import com.artemchep.keyguard.platform.Platform
|
||||
import com.artemchep.keyguard.res.Res
|
||||
import com.artemchep.keyguard.ui.FlatItem
|
||||
import com.artemchep.keyguard.ui.icons.icon
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.kodein.di.DirectDI
|
||||
import org.kodein.di.instance
|
||||
|
||||
fun settingCloseToTrayProvider(
|
||||
directDI: DirectDI,
|
||||
) = settingCloseToTrayProvider(
|
||||
getCloseToTray = directDI.instance(),
|
||||
putCloseToTray = directDI.instance(),
|
||||
windowCoroutineScope = directDI.instance(),
|
||||
)
|
||||
|
||||
fun settingCloseToTrayProvider(
|
||||
getCloseToTray: GetCloseToTray,
|
||||
putCloseToTray: PutCloseToTray,
|
||||
windowCoroutineScope: WindowCoroutineScope,
|
||||
): SettingComponent = getCloseToTray().map { closeToTray ->
|
||||
val onCheckedChange = { shouldCloseToTray: Boolean ->
|
||||
putCloseToTray(shouldCloseToTray)
|
||||
.launchIn(windowCoroutineScope)
|
||||
Unit
|
||||
}
|
||||
|
||||
if (CurrentPlatform is Platform.Desktop) {
|
||||
SettingIi(
|
||||
search = SettingIi.Search(
|
||||
group = "ux",
|
||||
tokens = listOf(
|
||||
"close",
|
||||
"tray",
|
||||
"taskbar",
|
||||
),
|
||||
),
|
||||
) {
|
||||
SettingCloseToTray(
|
||||
checked = closeToTray,
|
||||
onCheckedChange = onCheckedChange,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SettingCloseToTray(
|
||||
checked: Boolean,
|
||||
onCheckedChange: ((Boolean) -> Unit)?,
|
||||
) {
|
||||
FlatItem(
|
||||
leading = icon<RowScope>(Icons.Outlined.CloseFullscreen),
|
||||
trailing = {
|
||||
CompositionLocalProvider(
|
||||
LocalMinimumInteractiveComponentEnforcement provides false,
|
||||
) {
|
||||
Switch(
|
||||
checked = checked,
|
||||
enabled = onCheckedChange != null,
|
||||
onCheckedChange = onCheckedChange,
|
||||
)
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(Res.strings.pref_item_close_to_tray_title),
|
||||
)
|
||||
},
|
||||
onClick = onCheckedChange?.partially1(!checked),
|
||||
)
|
||||
}
|
|
@ -42,6 +42,7 @@ fun UiSettingsScreen() {
|
|||
list = listOf(
|
||||
SettingPaneItem.Item(Setting.USE_EXTERNAL_BROWSER),
|
||||
SettingPaneItem.Item(Setting.KEEP_SCREEN_ON),
|
||||
SettingPaneItem.Item(Setting.CLOSE_TO_TRAY),
|
||||
),
|
||||
),
|
||||
SettingPaneItem.Group(
|
||||
|
|
|
@ -896,6 +896,7 @@
|
|||
<string name="pref_item_color_scheme_title">Theme</string>
|
||||
<string name="pref_item_color_scheme_amoled_dark_title">Use contrast black theme</string>
|
||||
<string name="pref_item_open_links_in_external_browser_title">Open links in external browser</string>
|
||||
<string name="pref_item_close_to_tray_title">Close to system tray</string>
|
||||
<string name="pref_item_conceal_fields_title">Conceal fields</string>
|
||||
<string name="pref_item_conceal_fields_text">Conceal sensitive info, such as passwords and credit card numbers</string>
|
||||
<!--
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
|
@ -108,6 +108,7 @@ import com.artemchep.keyguard.common.usecase.GetClipboardAutoClear
|
|||
import com.artemchep.keyguard.common.usecase.GetClipboardAutoClearVariants
|
||||
import com.artemchep.keyguard.common.usecase.GetClipboardAutoRefresh
|
||||
import com.artemchep.keyguard.common.usecase.GetClipboardAutoRefreshVariants
|
||||
import com.artemchep.keyguard.common.usecase.GetCloseToTray
|
||||
import com.artemchep.keyguard.common.usecase.GetColors
|
||||
import com.artemchep.keyguard.common.usecase.GetColorsVariants
|
||||
import com.artemchep.keyguard.common.usecase.GetConcealFields
|
||||
|
@ -165,6 +166,7 @@ import com.artemchep.keyguard.common.usecase.PutCheckPwnedServices
|
|||
import com.artemchep.keyguard.common.usecase.PutCheckTwoFA
|
||||
import com.artemchep.keyguard.common.usecase.PutClipboardAutoClear
|
||||
import com.artemchep.keyguard.common.usecase.PutClipboardAutoRefresh
|
||||
import com.artemchep.keyguard.common.usecase.PutCloseToTray
|
||||
import com.artemchep.keyguard.common.usecase.PutColors
|
||||
import com.artemchep.keyguard.common.usecase.PutConcealFields
|
||||
import com.artemchep.keyguard.common.usecase.PutDebugPremium
|
||||
|
@ -228,6 +230,7 @@ import com.artemchep.keyguard.common.usecase.impl.GetClipboardAutoClearImpl
|
|||
import com.artemchep.keyguard.common.usecase.impl.GetClipboardAutoClearVariantsImpl
|
||||
import com.artemchep.keyguard.common.usecase.impl.GetClipboardAutoRefreshImpl
|
||||
import com.artemchep.keyguard.common.usecase.impl.GetClipboardAutoRefreshVariantsImpl
|
||||
import com.artemchep.keyguard.common.usecase.impl.GetCloseToTrayImpl
|
||||
import com.artemchep.keyguard.common.usecase.impl.GetColorsImpl
|
||||
import com.artemchep.keyguard.common.usecase.impl.GetColorsVariantsImpl
|
||||
import com.artemchep.keyguard.common.usecase.impl.GetConcealFieldsImpl
|
||||
|
@ -282,6 +285,7 @@ import com.artemchep.keyguard.common.usecase.impl.PutCheckPwnedServicesImpl
|
|||
import com.artemchep.keyguard.common.usecase.impl.PutCheckTwoFAImpl
|
||||
import com.artemchep.keyguard.common.usecase.impl.PutClipboardAutoClearImpl
|
||||
import com.artemchep.keyguard.common.usecase.impl.PutClipboardAutoRefreshImpl
|
||||
import com.artemchep.keyguard.common.usecase.impl.PutCloseToTrayImpl
|
||||
import com.artemchep.keyguard.common.usecase.impl.PutColorsImpl
|
||||
import com.artemchep.keyguard.common.usecase.impl.PutConcealFieldsImpl
|
||||
import com.artemchep.keyguard.common.usecase.impl.PutDebugPremiumImpl
|
||||
|
@ -617,6 +621,16 @@ fun globalModuleJvm() = DI.Module(
|
|||
directDI = this,
|
||||
)
|
||||
}
|
||||
bindSingleton<GetCloseToTray> {
|
||||
GetCloseToTrayImpl(
|
||||
directDI = this,
|
||||
)
|
||||
}
|
||||
bindSingleton<PutCloseToTray> {
|
||||
PutCloseToTrayImpl(
|
||||
directDI = this,
|
||||
)
|
||||
}
|
||||
bindSingleton<PutAllowTwoPanelLayoutInLandscape> {
|
||||
PutAllowTwoPanelLayoutInLandscapeImpl(
|
||||
directDI = this,
|
||||
|
|
|
@ -6,11 +6,17 @@ import androidx.compose.material3.contentColorFor
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.luminance
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.window.ApplicationScope
|
||||
import androidx.compose.ui.window.Tray
|
||||
import androidx.compose.ui.window.Window
|
||||
import androidx.compose.ui.window.application
|
||||
import androidx.compose.ui.window.isTraySupported
|
||||
import androidx.compose.ui.window.rememberTrayState
|
||||
import androidx.compose.ui.window.rememberWindowState
|
||||
import com.artemchep.keyguard.common.AppWorker
|
||||
import com.artemchep.keyguard.common.io.bind
|
||||
|
@ -19,6 +25,7 @@ import com.artemchep.keyguard.common.model.PersistedSession
|
|||
import com.artemchep.keyguard.common.model.ToastMessage
|
||||
import com.artemchep.keyguard.common.service.vault.KeyReadWriteRepository
|
||||
import com.artemchep.keyguard.common.usecase.GetAccounts
|
||||
import com.artemchep.keyguard.common.usecase.GetCloseToTray
|
||||
import com.artemchep.keyguard.common.usecase.GetLocale
|
||||
import com.artemchep.keyguard.common.usecase.GetVaultPersist
|
||||
import com.artemchep.keyguard.common.usecase.GetVaultSession
|
||||
|
@ -37,11 +44,12 @@ import com.artemchep.keyguard.feature.navigation.NavigationIntent
|
|||
import com.artemchep.keyguard.feature.navigation.NavigationNode
|
||||
import com.artemchep.keyguard.feature.navigation.NavigationRouterBackHandler
|
||||
import com.artemchep.keyguard.platform.lifecycle.LeLifecycleState
|
||||
import com.artemchep.keyguard.res.Res
|
||||
import com.artemchep.keyguard.ui.LocalComposeWindow
|
||||
import com.artemchep.keyguard.ui.surface.LocalSurfaceColor
|
||||
import com.artemchep.keyguard.ui.theme.KeyguardTheme
|
||||
import com.mayakapps.compose.windowstyler.WindowBackdrop
|
||||
import com.mayakapps.compose.windowstyler.WindowStyle
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import dev.icerock.moko.resources.desc.StringDesc
|
||||
import io.kamel.core.config.KamelConfig
|
||||
import io.kamel.core.config.takeFrom
|
||||
|
@ -61,6 +69,7 @@ import kotlinx.coroutines.flow.onEach
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.datetime.Clock
|
||||
import org.kodein.di.DI
|
||||
import org.kodein.di.bindSingleton
|
||||
import org.kodein.di.compose.rememberInstance
|
||||
import org.kodein.di.compose.withDI
|
||||
import org.kodein.di.direct
|
||||
|
@ -69,13 +78,16 @@ import java.util.Locale
|
|||
import kotlin.reflect.KClass
|
||||
|
||||
fun main() {
|
||||
val appDi = DI.invoke {
|
||||
import(diFingerprintRepositoryModule())
|
||||
}
|
||||
val kamelConfig = KamelConfig {
|
||||
this.takeFrom(KamelConfig.Default)
|
||||
mapper(FaviconUrlMapper)
|
||||
}
|
||||
val appDi = DI.invoke {
|
||||
import(diFingerprintRepositoryModule())
|
||||
bindSingleton {
|
||||
kamelConfig
|
||||
}
|
||||
}
|
||||
|
||||
val getVaultSession by appDi.di.instance<GetVaultSession>()
|
||||
val getVaultPersist by appDi.di.instance<GetVaultPersist>()
|
||||
|
@ -184,25 +196,74 @@ fun main() {
|
|||
// }
|
||||
// }
|
||||
|
||||
val getCloseToTray: GetCloseToTray = appDi.direct.instance()
|
||||
|
||||
application(exitProcessOnExit = true) {
|
||||
withDI(appDi) {
|
||||
val isWindowOpenState = remember {
|
||||
mutableStateOf(true)
|
||||
}
|
||||
|
||||
// Show a tray icon and allow the app to be collapsed into
|
||||
// the tray on supported platforms.
|
||||
val getCloseToTrayState = if (isTraySupported) {
|
||||
remember { getCloseToTray() }
|
||||
.collectAsState(false)
|
||||
} else {
|
||||
// If the tray is not supported then we
|
||||
// can never close to it.
|
||||
remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
}
|
||||
if (getCloseToTrayState.value) {
|
||||
val trayState = rememberTrayState()
|
||||
Tray(
|
||||
icon = painterResource(Res.images.ic_keyguard),
|
||||
state = trayState,
|
||||
onAction = {
|
||||
isWindowOpenState.value = true
|
||||
},
|
||||
menu = {
|
||||
Item(
|
||||
stringResource(Res.strings.close),
|
||||
onClick = ::exitApplication,
|
||||
)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
isWindowOpenState.value = true
|
||||
}
|
||||
if (isWindowOpenState.value) {
|
||||
KeyguardWindow(
|
||||
onCloseRequest = {
|
||||
val shouldCloseToTray = getCloseToTrayState.value
|
||||
if (shouldCloseToTray) {
|
||||
isWindowOpenState.value = false
|
||||
} else {
|
||||
exitApplication()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ApplicationScope.KeyguardWindow(
|
||||
onCloseRequest: () -> Unit,
|
||||
) {
|
||||
val windowState = rememberWindowState()
|
||||
Window(
|
||||
::exitApplication,
|
||||
state = rememberWindowState(),
|
||||
onCloseRequest = onCloseRequest,
|
||||
icon = painterResource(Res.images.ic_keyguard),
|
||||
state = windowState,
|
||||
title = "Keyguard",
|
||||
) {
|
||||
// this.window.addWindowStateListener {
|
||||
// println("event $it")
|
||||
// }
|
||||
KeyguardTheme {
|
||||
val containerColor = MaterialTheme.colorScheme.surfaceContainerHighest
|
||||
val contentColor = contentColorFor(containerColor)
|
||||
|
||||
WindowStyle(
|
||||
isDarkTheme = containerColor.luminance() < 0.5f,
|
||||
backdropType = WindowBackdrop.Default,
|
||||
)
|
||||
|
||||
Surface(
|
||||
modifier = Modifier.semantics {
|
||||
// Allows to use testTag() for UiAutomator's resource-id.
|
||||
|
@ -213,6 +274,7 @@ fun main() {
|
|||
color = containerColor,
|
||||
contentColor = contentColor,
|
||||
) {
|
||||
val kamelConfig by rememberInstance<KamelConfig>()
|
||||
CompositionLocalProvider(
|
||||
LocalSurfaceColor provides containerColor,
|
||||
LocalComposeWindow provides this.window,
|
||||
|
@ -228,8 +290,6 @@ fun main() {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal val FaviconUrlMapper: Mapper<FaviconUrl, Url> = object : Mapper<FaviconUrl, Url> {
|
||||
override val inputKClass: KClass<FaviconUrl>
|
||||
|
|
Loading…
Reference in New Issue