mirror of
https://github.com/KRTirtho/spotube.git
synced 2026-08-05 19:59:51 +00:00
feat(systemtray): self-manage tray lifecycle via Koin DI
SystemTrayService now observes settings internally and creates/removes the tray icon when minimizeToTray toggles. No more external start/stop calls from composables — eliminates infinite tray icon recreation. - SystemTrayService init block subscribes to settings changes - Close button uses hideWindow() when tray enabled - Double-click uses hideWindow()/showWindow() for consistent state - main.kt stripped of LaunchedEffect/remember tray management - ApplicationMainBar close button checks minimizeToTray setting
This commit is contained in:
parent
3448e16ed2
commit
cfaea59e2f
@ -18,7 +18,7 @@
|
|||||||
package dev.krtirtho.spotube.core.paths
|
package dev.krtirtho.spotube.core.paths
|
||||||
|
|
||||||
@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
|
@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
|
||||||
expect class Paths {
|
expect class Paths() {
|
||||||
fun getApplicationCacheDirPath(): String
|
fun getApplicationCacheDirPath(): String
|
||||||
fun getApplicationDataDirPath(): String
|
fun getApplicationDataDirPath(): String
|
||||||
fun getUserDownloadsDirPath(): String
|
fun getUserDownloadsDirPath(): String
|
||||||
|
|||||||
@ -29,6 +29,8 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
@ -38,8 +40,6 @@ import java.awt.Image
|
|||||||
import java.awt.SystemTray
|
import java.awt.SystemTray
|
||||||
import java.awt.Toolkit
|
import java.awt.Toolkit
|
||||||
import java.awt.TrayIcon
|
import java.awt.TrayIcon
|
||||||
import java.awt.event.FocusAdapter
|
|
||||||
import java.awt.event.FocusEvent
|
|
||||||
import java.awt.event.MouseAdapter
|
import java.awt.event.MouseAdapter
|
||||||
import java.awt.event.MouseEvent
|
import java.awt.event.MouseEvent
|
||||||
import java.awt.event.WindowAdapter
|
import java.awt.event.WindowAdapter
|
||||||
@ -50,10 +50,8 @@ import javax.swing.BoxLayout
|
|||||||
import javax.swing.JCheckBoxMenuItem
|
import javax.swing.JCheckBoxMenuItem
|
||||||
import javax.swing.JDialog
|
import javax.swing.JDialog
|
||||||
import javax.swing.JLabel
|
import javax.swing.JLabel
|
||||||
import javax.swing.JMenu
|
|
||||||
import javax.swing.JMenuItem
|
import javax.swing.JMenuItem
|
||||||
import javax.swing.JPanel
|
import javax.swing.JPanel
|
||||||
import javax.swing.JPopupMenu
|
|
||||||
import javax.swing.SwingUtilities
|
import javax.swing.SwingUtilities
|
||||||
import javax.swing.UIManager
|
import javax.swing.UIManager
|
||||||
|
|
||||||
@ -71,26 +69,87 @@ class SystemTrayService(
|
|||||||
private var menuDialog: TrayMenuDialog? = null
|
private var menuDialog: TrayMenuDialog? = null
|
||||||
|
|
||||||
private var windowVisible: Boolean = true
|
private var windowVisible: Boolean = true
|
||||||
private var onToggleWindowVisibility: (() -> Unit)? = null
|
|
||||||
private var onExit: (() -> Unit)? = null
|
|
||||||
|
|
||||||
@Volatile
|
private var onToggleWindowVisibility: () -> Unit = {}
|
||||||
private var started = false
|
private var onExit: () -> Unit = {}
|
||||||
|
|
||||||
private val volumeStep = 0.05f
|
private val volumeStep = 0.05f
|
||||||
|
private var trayEnabled = false
|
||||||
|
|
||||||
fun start(
|
@Volatile
|
||||||
|
private var currentState: TrayState? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
scope.launch {
|
||||||
|
settingsProvider.settingsState
|
||||||
|
.map { it?.minimizeToTray ?: false }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.collect { enabled ->
|
||||||
|
trayEnabled = enabled
|
||||||
|
if (enabled) {
|
||||||
|
ensureTrayCreated()
|
||||||
|
} else {
|
||||||
|
removeTray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.launch {
|
||||||
|
combine(
|
||||||
|
audioPlayer.playerStateFlow,
|
||||||
|
audioPlayer.currentMediaItemFlow,
|
||||||
|
audioPlayer.loopStateFlow,
|
||||||
|
audioPlayer.shuffleModeFlow,
|
||||||
|
audioPlayer.volumeFlow,
|
||||||
|
audioPlayerQueue.currentQueueEntryFlow,
|
||||||
|
savedTracksRepository.savedTracksIdsFlow,
|
||||||
|
) { values ->
|
||||||
|
TrayState(
|
||||||
|
playerState = values[0] as PlayerState,
|
||||||
|
mediaItem = values[1] as dev.krtirtho.spotube.core.audioplayer.MediaItem?,
|
||||||
|
loopState = values[2] as LoopState,
|
||||||
|
shuffleEnabled = values[3] as Boolean,
|
||||||
|
volume = values[4] as Float,
|
||||||
|
currentEntry = values[5] as QueueEntry?,
|
||||||
|
savedTrackIds = @Suppress("UNCHECKED_CAST") (values[6] as Set<String>),
|
||||||
|
)
|
||||||
|
}.collect { state ->
|
||||||
|
currentState = state
|
||||||
|
SwingUtilities.invokeLater {
|
||||||
|
updateTooltip(state)
|
||||||
|
menuDialog?.let { dialog ->
|
||||||
|
val panel = dialog.contentPane.getComponent(0) as? JPanel
|
||||||
|
if (panel != null) rebuildMenuPanel(panel, dialog)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setCallbacks(
|
||||||
onToggleWindowVisibility: () -> Unit,
|
onToggleWindowVisibility: () -> Unit,
|
||||||
onExit: () -> Unit,
|
onExit: () -> Unit,
|
||||||
) {
|
) {
|
||||||
this.onToggleWindowVisibility = onToggleWindowVisibility
|
this.onToggleWindowVisibility = onToggleWindowVisibility
|
||||||
this.onExit = onExit
|
this.onExit = onExit
|
||||||
|
}
|
||||||
|
|
||||||
if (started) {
|
fun hideWindow() {
|
||||||
return
|
if (windowVisible) {
|
||||||
|
windowVisible = false
|
||||||
|
onToggleWindowVisibility()
|
||||||
}
|
}
|
||||||
started = true
|
}
|
||||||
|
|
||||||
|
fun showWindow() {
|
||||||
|
if (!windowVisible) {
|
||||||
|
windowVisible = true
|
||||||
|
onToggleWindowVisibility()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ensureTrayCreated() {
|
||||||
|
if (trayIcon != null) return
|
||||||
if (!SystemTray.isSupported()) {
|
if (!SystemTray.isSupported()) {
|
||||||
logger.w { "System tray is not supported on this platform" }
|
logger.w { "System tray is not supported on this platform" }
|
||||||
return
|
return
|
||||||
@ -104,25 +163,39 @@ class SystemTrayService(
|
|||||||
icon.addMouseListener(object : MouseAdapter() {
|
icon.addMouseListener(object : MouseAdapter() {
|
||||||
override fun mouseClicked(e: MouseEvent) {
|
override fun mouseClicked(e: MouseEvent) {
|
||||||
if (e.button == MouseEvent.BUTTON1 && e.clickCount == 2) {
|
if (e.button == MouseEvent.BUTTON1 && e.clickCount == 2) {
|
||||||
onToggleWindowVisibility.invoke()
|
if (windowVisible) hideWindow() else showWindow()
|
||||||
} else if (e.button == MouseEvent.BUTTON3) {
|
} else if (e.button == MouseEvent.BUTTON3) {
|
||||||
showMenuDialog(icon, e.xOnScreen, e.yOnScreen)
|
showMenuDialog(icon, e.xOnScreen, e.yOnScreen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
val systemTray = SystemTray.getSystemTray()
|
SystemTray.getSystemTray().add(icon)
|
||||||
systemTray.add(icon)
|
trayIcon = icon
|
||||||
this.trayIcon = icon
|
logger.i { "System tray created" }
|
||||||
|
|
||||||
logger.i { "System tray initialized" }
|
|
||||||
observeState()
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger.e(e) { "Failed to initialize system tray" }
|
logger.e(e) { "Failed to create system tray" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun removeTray() {
|
||||||
|
menuDialog?.dispose()
|
||||||
|
menuDialog = null
|
||||||
|
trayIcon?.let {
|
||||||
|
try {
|
||||||
|
SystemTray.getSystemTray().remove(it)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.e(e) { "Failed to remove tray icon" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trayIcon = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
removeTray()
|
||||||
|
}
|
||||||
|
|
||||||
private fun showMenuDialog(icon: TrayIcon, x: Int, y: Int) {
|
private fun showMenuDialog(icon: TrayIcon, x: Int, y: Int) {
|
||||||
val dialog = menuDialog
|
val dialog = menuDialog
|
||||||
if (dialog != null && dialog.isVisible) {
|
if (dialog != null && dialog.isVisible) {
|
||||||
@ -135,7 +208,6 @@ class SystemTrayService(
|
|||||||
isUndecorated = true
|
isUndecorated = true
|
||||||
isAlwaysOnTop = true
|
isAlwaysOnTop = true
|
||||||
focusableWindowState = true
|
focusableWindowState = true
|
||||||
setAutoRequestFocus(true)
|
|
||||||
background = Color(0, 0, 0, 0)
|
background = Color(0, 0, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +262,7 @@ class SystemTrayService(
|
|||||||
panel.add(createSeparator())
|
panel.add(createSeparator())
|
||||||
|
|
||||||
panel.add(createMenuItem(if (windowVisible) "Hide Window" else "Show Window") {
|
panel.add(createMenuItem(if (windowVisible) "Hide Window" else "Show Window") {
|
||||||
onToggleWindowVisibility?.invoke()
|
if (windowVisible) hideWindow() else showWindow()
|
||||||
})
|
})
|
||||||
panel.add(createSeparator())
|
panel.add(createSeparator())
|
||||||
|
|
||||||
@ -219,18 +291,12 @@ class SystemTrayService(
|
|||||||
val loopAll = javax.swing.JRadioButtonMenuItem("Loop: ALL", state.loopState == LoopState.ALL)
|
val loopAll = javax.swing.JRadioButtonMenuItem("Loop: ALL", state.loopState == LoopState.ALL)
|
||||||
|
|
||||||
val hoverBackground = UIManager.getColor("MenuItem.selectionBackground") ?: Color(75, 110, 175)
|
val hoverBackground = UIManager.getColor("MenuItem.selectionBackground") ?: Color(75, 110, 175)
|
||||||
|
|
||||||
listOf(loopNone, loopOne, loopAll).forEach { item ->
|
listOf(loopNone, loopOne, loopAll).forEach { item ->
|
||||||
item.isOpaque = true
|
item.isOpaque = true
|
||||||
val normalBackground = item.background
|
val normalBg = item.background
|
||||||
item.addMouseListener(object : MouseAdapter() {
|
item.addMouseListener(object : MouseAdapter() {
|
||||||
override fun mouseEntered(e: MouseEvent) {
|
override fun mouseEntered(e: MouseEvent) { item.background = hoverBackground }
|
||||||
item.background = hoverBackground
|
override fun mouseExited(e: MouseEvent) { item.background = normalBg }
|
||||||
}
|
|
||||||
|
|
||||||
override fun mouseExited(e: MouseEvent) {
|
|
||||||
item.background = normalBackground
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,53 +345,16 @@ class SystemTrayService(
|
|||||||
})
|
})
|
||||||
panel.add(createSeparator())
|
panel.add(createSeparator())
|
||||||
|
|
||||||
panel.add(createMenuItem("Exit") { onExit?.invoke() })
|
panel.add(createMenuItem("Exit") { onExit() })
|
||||||
|
|
||||||
panel.revalidate()
|
panel.revalidate()
|
||||||
panel.repaint()
|
panel.repaint()
|
||||||
dialog.pack()
|
dialog.pack()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Volatile
|
|
||||||
private var currentState: TrayState? = null
|
|
||||||
|
|
||||||
private fun observeState() {
|
|
||||||
scope.launch {
|
|
||||||
combine(
|
|
||||||
audioPlayer.playerStateFlow,
|
|
||||||
audioPlayer.currentMediaItemFlow,
|
|
||||||
audioPlayer.loopStateFlow,
|
|
||||||
audioPlayer.shuffleModeFlow,
|
|
||||||
audioPlayer.volumeFlow,
|
|
||||||
audioPlayerQueue.currentQueueEntryFlow,
|
|
||||||
savedTracksRepository.savedTracksIdsFlow,
|
|
||||||
) { values ->
|
|
||||||
TrayState(
|
|
||||||
playerState = values[0] as PlayerState,
|
|
||||||
mediaItem = values[1] as dev.krtirtho.spotube.core.audioplayer.MediaItem?,
|
|
||||||
loopState = values[2] as LoopState,
|
|
||||||
shuffleEnabled = values[3] as Boolean,
|
|
||||||
volume = values[4] as Float,
|
|
||||||
currentEntry = values[5] as QueueEntry?,
|
|
||||||
savedTrackIds = @Suppress("UNCHECKED_CAST") (values[6] as Set<String>),
|
|
||||||
)
|
|
||||||
}.collect { state ->
|
|
||||||
currentState = state
|
|
||||||
SwingUtilities.invokeLater {
|
|
||||||
updateTooltip(state)
|
|
||||||
menuDialog?.let { dialog ->
|
|
||||||
val panel = dialog.contentPane.getComponent(0) as? JPanel
|
|
||||||
if (panel != null) rebuildMenuPanel(panel, dialog)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateTooltip(state: TrayState) {
|
private fun updateTooltip(state: TrayState) {
|
||||||
val icon = trayIcon ?: return
|
|
||||||
val media = state.mediaItem
|
val media = state.mediaItem
|
||||||
icon.toolTip = if (media != null) {
|
trayIcon?.toolTip = if (media != null) {
|
||||||
"Spotube - ${media.title} by ${media.artist}"
|
"Spotube - ${media.title} by ${media.artist}"
|
||||||
} else {
|
} else {
|
||||||
"Spotube"
|
"Spotube"
|
||||||
@ -338,20 +367,11 @@ class SystemTrayService(
|
|||||||
isOpaque = true
|
isOpaque = true
|
||||||
maximumSize = Dimension(Int.MAX_VALUE, preferredSize.height)
|
maximumSize = Dimension(Int.MAX_VALUE, preferredSize.height)
|
||||||
addActionListener { action() }
|
addActionListener { action() }
|
||||||
|
val normalBg = background
|
||||||
val normalBackground = background
|
val hoverBg = UIManager.getColor("MenuItem.selectionBackground") ?: Color(75, 110, 175)
|
||||||
val hoverBackground = UIManager.getColor("MenuItem.selectionBackground") ?: Color(75, 110, 175)
|
|
||||||
|
|
||||||
addMouseListener(object : MouseAdapter() {
|
addMouseListener(object : MouseAdapter() {
|
||||||
override fun mouseEntered(e: MouseEvent) {
|
override fun mouseEntered(e: MouseEvent) { if (isEnabled) background = hoverBg }
|
||||||
if (isEnabled) {
|
override fun mouseExited(e: MouseEvent) { background = normalBg }
|
||||||
background = hoverBackground
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun mouseExited(e: MouseEvent) {
|
|
||||||
background = normalBackground
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -361,20 +381,11 @@ class SystemTrayService(
|
|||||||
isOpaque = true
|
isOpaque = true
|
||||||
maximumSize = Dimension(Int.MAX_VALUE, preferredSize.height)
|
maximumSize = Dimension(Int.MAX_VALUE, preferredSize.height)
|
||||||
addActionListener { action() }
|
addActionListener { action() }
|
||||||
|
val normalBg = background
|
||||||
val normalBackground = background
|
val hoverBg = UIManager.getColor("MenuItem.selectionBackground") ?: Color(75, 110, 175)
|
||||||
val hoverBackground = UIManager.getColor("MenuItem.selectionBackground") ?: Color(75, 110, 175)
|
|
||||||
|
|
||||||
addMouseListener(object : MouseAdapter() {
|
addMouseListener(object : MouseAdapter() {
|
||||||
override fun mouseEntered(e: MouseEvent) {
|
override fun mouseEntered(e: MouseEvent) { if (isEnabled) background = hoverBg }
|
||||||
if (isEnabled) {
|
override fun mouseExited(e: MouseEvent) { background = normalBg }
|
||||||
background = hoverBackground
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun mouseExited(e: MouseEvent) {
|
|
||||||
background = normalBackground
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -396,26 +407,7 @@ class SystemTrayService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createSeparator(): javax.swing.JSeparator {
|
private fun createSeparator(): javax.swing.JSeparator {
|
||||||
return javax.swing.JSeparator().apply {
|
return javax.swing.JSeparator().apply { maximumSize = Dimension(Int.MAX_VALUE, 2) }
|
||||||
maximumSize = Dimension(Int.MAX_VALUE, 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setWindowVisible(visible: Boolean) {
|
|
||||||
windowVisible = visible
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() {
|
|
||||||
menuDialog?.dispose()
|
|
||||||
menuDialog = null
|
|
||||||
trayIcon?.let {
|
|
||||||
try {
|
|
||||||
SystemTray.getSystemTray().remove(it)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logger.e(e) { "Failed to remove tray icon" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
trayIcon = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createTrayIconImage(): Image {
|
private fun createTrayIconImage(): Image {
|
||||||
|
|||||||
@ -84,52 +84,34 @@ private fun WindowButtons(modifier: Modifier = Modifier) {
|
|||||||
val windowState = LocalWindowState.current
|
val windowState = LocalWindowState.current
|
||||||
val applicationScope = LocalApplicationScope.current
|
val applicationScope = LocalApplicationScope.current
|
||||||
val settingsProvider = koinInject<SettingsProvider>()
|
val settingsProvider = koinInject<SettingsProvider>()
|
||||||
val systemTrayService = koinInject<SystemTrayService>()
|
val trayService = koinInject<SystemTrayService>()
|
||||||
val settings by settingsProvider.settingsState.collectAsState(initial = null)
|
val settings by settingsProvider.settingsState.collectAsState(initial = null)
|
||||||
val minimizeToTray = settings?.minimizeToTray ?: false
|
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
) {
|
) {
|
||||||
IconButton(
|
IconButton(onClick = { windowState.isMinimized = true }) {
|
||||||
onClick = {
|
Icon(Iconsax.FluentMinus, "Minimize", modifier = Modifier.size(14.dp))
|
||||||
windowState.isMinimized = true
|
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
Iconsax.FluentMinus, "Minimize", modifier = Modifier.size(14.dp)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
IconButton(
|
IconButton(onClick = {
|
||||||
onClick = {
|
windowState.placement = if (windowState.placement == WindowPlacement.Maximized)
|
||||||
windowState.placement = if (windowState.placement == WindowPlacement.Maximized) {
|
WindowPlacement.Floating else WindowPlacement.Maximized
|
||||||
WindowPlacement.Floating
|
}) {
|
||||||
} else {
|
|
||||||
WindowPlacement.Maximized
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
if (windowState.placement == WindowPlacement.Floating) {
|
if (windowState.placement == WindowPlacement.Floating) {
|
||||||
Icon(
|
Icon(Iconsax.FluentMaximize, "Maximize", modifier = Modifier.size(14.dp))
|
||||||
Iconsax.FluentMaximize, "Maximize", modifier = Modifier.size(14.dp)
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
Icon(
|
Icon(Iconsax.FluentSquareMultiple, "Restore", modifier = Modifier.size(14.dp))
|
||||||
Iconsax.FluentSquareMultiple, "Restore", modifier = Modifier.size(14.dp)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IconButton(
|
IconButton(onClick = {
|
||||||
onClick = {
|
if (settings?.minimizeToTray == true) {
|
||||||
if (minimizeToTray) {
|
trayService.hideWindow()
|
||||||
windowState.isMinimized = true
|
} else {
|
||||||
systemTrayService.setWindowVisible(false)
|
applicationScope.exitApplication()
|
||||||
} else {
|
}
|
||||||
applicationScope.exitApplication()
|
}) {
|
||||||
}
|
Icon(Iconsax.FluentDismiss, "Close", modifier = Modifier.size(14.dp))
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
Iconsax.FluentDismiss, "Close", modifier = Modifier.size(14.dp)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -20,6 +20,9 @@ package dev.krtirtho.spotube
|
|||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Window
|
import androidx.compose.ui.window.Window
|
||||||
@ -68,22 +71,33 @@ fun main() {
|
|||||||
val settings by settingsProvider.settingsState.collectAsState(initial = null)
|
val settings by settingsProvider.settingsState.collectAsState(initial = null)
|
||||||
val minimizeToTray = settings?.minimizeToTray ?: false
|
val minimizeToTray = settings?.minimizeToTray ?: false
|
||||||
|
|
||||||
val systemTrayService = KoinServicesProvider.systemTrayService
|
val trayService = KoinServicesProvider.systemTrayService
|
||||||
|
|
||||||
|
var isWindowVisible by remember { mutableStateOf(true) }
|
||||||
|
|
||||||
|
trayService.setCallbacks(
|
||||||
|
onToggleWindowVisibility = { isWindowVisible = !isWindowVisible },
|
||||||
|
onExit = {
|
||||||
|
trayService.close()
|
||||||
|
appScope.cancel()
|
||||||
|
exitApplication()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
Window(
|
Window(
|
||||||
state = windowState,
|
state = windowState,
|
||||||
onCloseRequest = {
|
onCloseRequest = {
|
||||||
if (minimizeToTray) {
|
if (minimizeToTray) {
|
||||||
windowState.isMinimized = true
|
isWindowVisible = false
|
||||||
systemTrayService.setWindowVisible(false)
|
|
||||||
} else {
|
} else {
|
||||||
|
trayService.close()
|
||||||
appScope.cancel()
|
appScope.cancel()
|
||||||
exitApplication()
|
exitApplication()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title = "Spotube",
|
title = "Spotube",
|
||||||
decoration = WindowDecoration.Undecorated(),
|
decoration = WindowDecoration.Undecorated(),
|
||||||
visible = true,
|
visible = isWindowVisible,
|
||||||
) {
|
) {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalApplicationScope provides this@application,
|
LocalApplicationScope provides this@application,
|
||||||
@ -93,17 +107,5 @@ fun main() {
|
|||||||
App()
|
App()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
systemTrayService.start(
|
|
||||||
onToggleWindowVisibility = {
|
|
||||||
windowState.isMinimized = !windowState.isMinimized
|
|
||||||
systemTrayService.setWindowVisible(!windowState.isMinimized)
|
|
||||||
},
|
|
||||||
onExit = {
|
|
||||||
systemTrayService.close()
|
|
||||||
appScope.cancel()
|
|
||||||
exitApplication()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user