add confirm exit dialog when there is active download/queue

This commit is contained in:
AmirHossein Abdolmotallebi 2024-11-21 18:56:33 +03:30
parent 054be24597
commit 3387a6d7c9
9 changed files with 217 additions and 17 deletions

View File

@ -48,7 +48,6 @@ import ir.amirab.util.compose.asStringSource
import ir.amirab.util.compose.combineStringSources import ir.amirab.util.compose.combineStringSources
import ir.amirab.util.flow.mapStateFlow import ir.amirab.util.flow.mapStateFlow
import ir.amirab.util.osfileutil.FileUtils import ir.amirab.util.osfileutil.FileUtils
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -732,10 +731,31 @@ class AppComponent(
} }
} }
fun requestClose() { private val _showConfirmExitDialog = MutableStateFlow(false)
val showConfirmExitDialog = _showConfirmExitDialog.asStateFlow()
fun exitAppAsync() {
scope.launch { exitApp() }
}
suspend fun exitApp() {
downloadSystem.stopAnything()
exitProcess(0) exitProcess(0)
} }
fun closeConfirmExit() {
_showConfirmExitDialog.value = false
}
suspend fun requestExitApp() {
val hasActiveDownloads = downloadSystem.downloadMonitor.activeDownloadCount.value > 0
if (hasActiveDownloads) {
_showConfirmExitDialog.value = true
return
}
exitApp()
}
fun openAbout() { fun openAbout() {
showAboutPage.update { true } showAboutPage.update { true }
} }

View File

@ -1,9 +1,9 @@
package com.abdownloadmanager.desktop package com.abdownloadmanager.desktop
import com.abdownloadmanager.desktop.actions.exitAction
import com.abdownloadmanager.desktop.utils.IntegrationPortBroadcaster import com.abdownloadmanager.desktop.utils.IntegrationPortBroadcaster
import com.abdownloadmanager.desktop.utils.singleInstance.Command import com.abdownloadmanager.desktop.utils.singleInstance.Command
import com.abdownloadmanager.desktop.utils.singleInstance.MutableSingleInstanceServerHandler import com.abdownloadmanager.desktop.utils.singleInstance.MutableSingleInstanceServerHandler
import kotlinx.coroutines.runBlocking
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
object Commands { object Commands {
@ -26,7 +26,9 @@ object SingleInstanceServerInitializer:KoinComponent {
appComponent.isReady() appComponent.isReady()
} }
mutableHandler.add(Commands.exit) { mutableHandler.add(Commands.exit) {
exitAction() runBlocking {
appComponent.exitApp()
}
} }
} }
} }

View File

@ -13,7 +13,6 @@ import ir.amirab.util.compose.action.simpleAction
import com.abdownloadmanager.desktop.utils.getIcon import com.abdownloadmanager.desktop.utils.getIcon
import com.abdownloadmanager.desktop.utils.getName import com.abdownloadmanager.desktop.utils.getName
import com.abdownloadmanager.resources.Res import com.abdownloadmanager.resources.Res
import com.abdownloadmanager.resources.*
import com.abdownloadmanager.utils.category.Category import com.abdownloadmanager.utils.category.Category
import ir.amirab.downloader.downloaditem.DownloadCredentials import ir.amirab.downloader.downloaditem.DownloadCredentials
import ir.amirab.downloader.queue.DownloadQueue import ir.amirab.downloader.queue.DownloadQueue
@ -115,11 +114,12 @@ val stopAllAction = simpleAction(
}.apply { }.apply {
} }
val exitAction = simpleAction( // ui exit
val requestExitAction = simpleAction(
Res.string.exit.asStringSource(), Res.string.exit.asStringSource(),
MyIcons.exit, MyIcons.exit,
) { ) {
appComponent.requestClose() scope.launch { appComponent.requestExitApp() }
} }
val browserIntegrations = MenuItem.SubMenu( val browserIntegrations = MenuItem.SubMenu(

View File

@ -0,0 +1,24 @@
package com.abdownloadmanager.desktop.pages.confirmexit
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import com.abdownloadmanager.desktop.AppComponent
import com.abdownloadmanager.desktop.ui.widget.ConfirmDialog
import com.abdownloadmanager.desktop.ui.widget.ConfirmDialogType
import com.abdownloadmanager.resources.Res
import ir.amirab.util.compose.asStringSource
@Composable
fun ConfirmExit(appComponent: AppComponent) {
val showExitDialog by appComponent.showConfirmExitDialog.collectAsState()
if (showExitDialog) {
ConfirmDialog(
Res.string.confirm_exit.asStringSource(),
Res.string.confirm_exit_description.asStringSource(),
onCancel = appComponent::closeConfirmExit,
onConfirm = appComponent::exitAppAsync,
type = ConfirmDialogType.Warning,
)
}
}

View File

@ -522,7 +522,7 @@ class HomeComponent(
+newDownloadFromClipboardAction +newDownloadFromClipboardAction
+batchDownloadAction +batchDownloadAction
separator() separator()
+exitAction +requestExitAction
} }
subMenu(Res.string.tasks.asStringSource()) { subMenu(Res.string.tasks.asStringSource()) {
@ -897,7 +897,7 @@ class HomeComponent(
"ctrl V" to newDownloadFromClipboardAction "ctrl V" to newDownloadFromClipboardAction
"ctrl C" to downloadActions.copyDownloadLinkAction "ctrl C" to downloadActions.copyDownloadLinkAction
"ctrl alt S" to gotoSettingsAction "ctrl alt S" to gotoSettingsAction
"ctrl W" to exitAction "ctrl W" to requestExitAction
"DELETE" to downloadActions.deleteAction "DELETE" to downloadActions.deleteAction
"ctrl O" to downloadActions.openFileAction "ctrl O" to downloadActions.openFileAction
"ctrl F" to downloadActions.openFolderAction "ctrl F" to downloadActions.openFolderAction

View File

@ -14,9 +14,6 @@ import com.abdownloadmanager.desktop.pages.singleDownloadPage.ShowDownloadDialog
import com.abdownloadmanager.desktop.ui.icon.MyIcons import com.abdownloadmanager.desktop.ui.icon.MyIcons
import com.abdownloadmanager.desktop.ui.theme.ABDownloaderTheme import com.abdownloadmanager.desktop.ui.theme.ABDownloaderTheme
import com.abdownloadmanager.desktop.ui.widget.tray.ComposeTray import com.abdownloadmanager.desktop.ui.widget.tray.ComposeTray
import com.abdownloadmanager.desktop.ui.widget.ProvideNotificationManager
import com.abdownloadmanager.desktop.ui.widget.ShowMessageDialogs
import com.abdownloadmanager.desktop.ui.widget.useNotification
import com.abdownloadmanager.desktop.utils.AppInfo import com.abdownloadmanager.desktop.utils.AppInfo
import com.abdownloadmanager.desktop.utils.GlobalAppExceptionHandler import com.abdownloadmanager.desktop.utils.GlobalAppExceptionHandler
import com.abdownloadmanager.desktop.utils.ProvideGlobalExceptionHandler import com.abdownloadmanager.desktop.utils.ProvideGlobalExceptionHandler
@ -24,16 +21,15 @@ import ir.amirab.util.compose.action.buildMenu
import com.abdownloadmanager.desktop.utils.isInDebugMode import com.abdownloadmanager.desktop.utils.isInDebugMode
import com.abdownloadmanager.desktop.utils.mvi.HandleEffects import com.abdownloadmanager.desktop.utils.mvi.HandleEffects
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.window.* import androidx.compose.ui.window.*
import com.abdownloadmanager.desktop.pages.batchdownload.BatchDownloadWindow import com.abdownloadmanager.desktop.pages.batchdownload.BatchDownloadWindow
import com.abdownloadmanager.desktop.pages.category.ShowCategoryDialogs import com.abdownloadmanager.desktop.pages.category.ShowCategoryDialogs
import com.abdownloadmanager.desktop.pages.confirmexit.ConfirmExit
import com.abdownloadmanager.desktop.pages.credits.translators.ShowTranslators import com.abdownloadmanager.desktop.pages.credits.translators.ShowTranslators
import com.abdownloadmanager.desktop.pages.editdownload.EditDownloadWindow import com.abdownloadmanager.desktop.pages.editdownload.EditDownloadWindow
import com.abdownloadmanager.desktop.pages.home.HomeWindow import com.abdownloadmanager.desktop.pages.home.HomeWindow
import com.abdownloadmanager.desktop.pages.settings.ThemeManager import com.abdownloadmanager.desktop.pages.settings.ThemeManager
import com.abdownloadmanager.desktop.ui.widget.ProvideLanguageManager import com.abdownloadmanager.desktop.ui.widget.*
import com.abdownloadmanager.utils.compose.ProvideDebugInfo import com.abdownloadmanager.utils.compose.ProvideDebugInfo
import ir.amirab.util.compose.localizationmanager.LanguageManager import ir.amirab.util.compose.localizationmanager.LanguageManager
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -100,6 +96,7 @@ object Ui : KoinComponent {
ShowMessageDialogs(appComponent) ShowMessageDialogs(appComponent)
ShowOpenSourceLibraries(appComponent) ShowOpenSourceLibraries(appComponent)
ShowTranslators(appComponent) ShowTranslators(appComponent)
ConfirmExit(appComponent)
} }
} }
} }
@ -140,7 +137,7 @@ private fun ApplicationScope.SystemTray(
buildMenu { buildMenu {
+showDownloadList +showDownloadList
+gotoSettingsAction +gotoSettingsAction
+exitAction +requestExitAction
} }
} }
) )

View File

@ -0,0 +1,36 @@
package com.abdownloadmanager.desktop.ui.widget
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.abdownloadmanager.desktop.ui.theme.myColors
import com.abdownloadmanager.desktop.utils.div
@Composable
fun ActionContainer(
modifier: Modifier,
contentPadding: PaddingValues = PaddingValues(
horizontal = 16.dp,
vertical = 8.dp,
),
content: @Composable () -> Unit,
) {
Column(modifier) {
Spacer(
Modifier
.fillMaxWidth()
.height(1.dp)
.background(myColors.onBackground / 0.15f)
)
Box(
Modifier
.fillMaxWidth()
.background(myColors.surface / 0.5f)
.padding(contentPadding),
) {
content()
}
}
}

View File

@ -0,0 +1,119 @@
package com.abdownloadmanager.desktop.ui.widget
import com.abdownloadmanager.desktop.ui.customwindow.CustomWindow
import com.abdownloadmanager.desktop.ui.customwindow.WindowTitle
import com.abdownloadmanager.utils.compose.widget.MyIcon
import com.abdownloadmanager.desktop.ui.icon.MyIcons
import com.abdownloadmanager.desktop.ui.theme.myColors
import com.abdownloadmanager.desktop.ui.theme.myTextSizes
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.WindowPosition
import androidx.compose.ui.window.rememberWindowState
import com.abdownloadmanager.resources.Res
import ir.amirab.util.compose.StringSource
import ir.amirab.util.compose.resources.myStringResource
import java.awt.Dimension
@Suppress("unused")
sealed class ConfirmDialogType {
data object Success : ConfirmDialogType()
data object Info : ConfirmDialogType()
data object Error : ConfirmDialogType()
data object Warning : ConfirmDialogType()
}
@Composable
fun ConfirmDialog(
title: StringSource,
message: StringSource,
type: ConfirmDialogType,
onConfirm: () -> Unit,
onCancel: () -> Unit,
) {
val h = 180
val w = 400
val state = rememberWindowState(
size = DpSize(w.dp, h.dp),
position = WindowPosition.Aligned(Alignment.Center)
)
CustomWindow(
state,
onRequestMinimize = null,
onRequestToggleMaximize = null,
onCloseRequest = onCancel,
alwaysOnTop = true,
) {
LaunchedEffect(Unit) {
window.minimumSize = Dimension(w, h)
}
val typeName = type.toString()
WindowTitle(typeName)
Column {
Row(
Modifier
.weight(1f)
.padding(8.dp),
) {
val color = when (type) {
ConfirmDialogType.Error -> myColors.info
ConfirmDialogType.Info -> myColors.warning
ConfirmDialogType.Success -> myColors.success
ConfirmDialogType.Warning -> myColors.warning
}
MyIcon(
icon = MyIcons.info,
tint = color,
modifier = Modifier
.padding(16.dp)
.requiredSize(36.dp),
contentDescription = null,
)
Column {
Text(
title.rememberString(),
fontSize = myTextSizes.xl,
fontWeight = FontWeight.Bold,
)
Spacer(Modifier.height(8.dp))
Text(
message.rememberString(),
fontSize = myTextSizes.base,
modifier = Modifier
.weight(1f)
.fillMaxWidth()
.verticalScroll(rememberScrollState())
)
Spacer(Modifier.height(8.dp))
}
}
ActionContainer(
Modifier.fillMaxWidth()
) {
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End,
) {
ActionButton(
myStringResource(Res.string.ok),
onClick = onConfirm
)
Spacer(Modifier.width(8.dp))
ActionButton(
myStringResource(Res.string.cancel),
onClick = onCancel
)
}
}
}
}
}

View File

@ -305,4 +305,6 @@ translators_contribute_title=Improve Translations
translators_contribute_description=Want to help improve this project? If your language isn't listed or needs some tweaks, you can contribute your translations and make it better\! translators_contribute_description=Want to help improve this project? If your language isn't listed or needs some tweaks, you can contribute your translations and make it better\!
contribute=Contribute contribute=Contribute
meet_the_translators=Meet the Translators meet_the_translators=Meet the Translators
localized_by_translators=Localized by Translators localized_by_translators=Localized by Translators
confirm_exit=Confirm Exit
confirm_exit_description=Are you sure you want to exit AB Download Manager?\nActive downloads/queues will be stopped!