Split category and location configuration options in multi download page (#328)

* Split category and location configuration options in add multi download page
This commit is contained in:
Ehsan Narmani 2024-12-28 23:10:28 +03:30 committed by GitHub
parent f58e28b36d
commit a2077c7e4b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 131 additions and 367 deletions

View File

@ -9,8 +9,6 @@ import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import com.abdownloadmanager.desktop.pages.addDownload.multiple.AddMultiItemSaveMode.*
import com.abdownloadmanager.desktop.utils.asState
import com.abdownloadmanager.utils.FileIconProvider import com.abdownloadmanager.utils.FileIconProvider
import com.abdownloadmanager.utils.category.Category import com.abdownloadmanager.utils.category.Category
import com.abdownloadmanager.utils.category.CategoryItem import com.abdownloadmanager.utils.category.CategoryItem
@ -68,15 +66,22 @@ class AddMultiDownloadComponent(
private val categoryManager: CategoryManager by inject() private val categoryManager: CategoryManager by inject()
val categories = categoryManager.categoriesFlow val categories = categoryManager.categoriesFlow
private val _selectedCategory = MutableStateFlow(categories.value.firstOrNull()) private val _selectedCategory = MutableStateFlow<Category?>(null)
val selectedCategory = _selectedCategory.asStateFlow() val selectedCategory = _selectedCategory.asStateFlow()
fun setSelectedCategory(category: Category) { fun setSelectedCategory(category: Category?) {
_selectedCategory.update { _selectedCategory.update {
category category
} }
} }
private val _allInSameLocation = MutableStateFlow(false)
val allInSameLocation = _allInSameLocation.asStateFlow()
fun setAllItemsInSameLocation(sameLocation: Boolean) {
_allInSameLocation.update { sameLocation }
}
fun requestAddCategory() { fun requestAddCategory() {
onRequestAddCategory() onRequestAddCategory()
} }
@ -103,12 +108,6 @@ class AddMultiDownloadComponent(
} }
var list: List<DownloadUiChecker> by mutableStateOf(emptyList()) var list: List<DownloadUiChecker> by mutableStateOf(emptyList())
private val _saveMode = MutableStateFlow(EachFileInTheirOwnCategory)
val saveMode = _saveMode.asStateFlow()
fun setSaveMode(saveMode: AddMultiItemSaveMode) {
_saveMode.update { saveMode }
}
private val checkList = MutableSharedFlow<DownloadUiChecker>() private val checkList = MutableSharedFlow<DownloadUiChecker>()
private fun enqueueCheck(links: List<DownloadUiChecker>) { private fun enqueueCheck(links: List<DownloadUiChecker>) {
@ -161,19 +160,8 @@ class AddMultiDownloadComponent(
} }
} }
val isCategoryModeHasValidState by run {
val category by selectedCategory.asState(scope)
val saveMode by saveMode.asState(scope)
derivedStateOf {
when (saveMode) {
EachFileInTheirOwnCategory -> true
AllInOneCategory -> category != null
InSameLocation -> true
}
}
}
val canClickAdd by derivedStateOf { val canClickAdd by derivedStateOf {
selectionList.isNotEmpty() && isCategoryModeHasValidState selectionList.isNotEmpty()
} }
private val queueManager: QueueManager by inject() private val queueManager: QueueManager by inject()
val queueList = queueManager.queues val queueList = queueManager.queues
@ -211,16 +199,11 @@ class AddMultiDownloadComponent(
fun requestAddDownloads( fun requestAddDownloads(
queueId: Long?, queueId: Long?,
) { ) {
val saveMode = saveMode.value
val categorySelectionMode = when (saveMode) {
EachFileInTheirOwnCategory -> CategorySelectionMode.Auto
AllInOneCategory -> selectedCategory.value?.let {
CategorySelectionMode.Fixed(it.id)
}
InSameLocation -> { val categorySelectionMode = when {
if (alsoAutoCategorize.value) CategorySelectionMode.Auto alsoAutoCategorize.value -> CategorySelectionMode.Auto
else null else -> selectedCategory.value?.let {
CategorySelectionMode.Fixed(it.id)
} }
} }
val itemsToAdd = list val itemsToAdd = list
@ -237,7 +220,7 @@ class AddMultiDownloadComponent(
url = it.credentials.value.link, url = it.credentials.value.link,
fleName = it.name.value, fleName = it.name.value,
defaultFolder = it.folder.value, defaultFolder = it.folder.value,
allInSameLocation = saveMode == InSameLocation allInSameLocation = allInSameLocation.value
), ),
name = it.name.value, name = it.name.value,
link = it.credentials.value.link, link = it.credentials.value.link,
@ -252,7 +235,7 @@ class AddMultiDownloadComponent(
categorySelectionMode = categorySelectionMode categorySelectionMode = categorySelectionMode
) )
val folder = folder.value val folder = folder.value
if (this.saveMode.value == InSameLocation) { if (allInSameLocation.value) {
addToLastUsedLocations(folder) addToLastUsedLocations(folder)
} }
requestClose() requestClose()

View File

@ -1,45 +1,25 @@
package com.abdownloadmanager.desktop.pages.addDownload.multiple package com.abdownloadmanager.desktop.pages.addDownload.multiple
import androidx.compose.animation.animateContentSize import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import com.abdownloadmanager.desktop.ui.theme.myTextSizes
import com.abdownloadmanager.desktop.ui.widget.ActionButton
import com.abdownloadmanager.desktop.ui.widget.Text
import com.abdownloadmanager.utils.compose.WithContentAlpha
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.onClick import androidx.compose.foundation.onClick
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.window.WindowDraggableArea
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.rememberDialogState import com.abdownloadmanager.desktop.pages.addDownload.shared.CategoryAddButton
import com.abdownloadmanager.desktop.pages.addDownload.shared.* import com.abdownloadmanager.desktop.pages.addDownload.shared.CategorySelect
import com.abdownloadmanager.desktop.ui.customwindow.BaseOptionDialog import com.abdownloadmanager.desktop.pages.addDownload.shared.LocationTextField
import com.abdownloadmanager.desktop.ui.icon.MyIcons import com.abdownloadmanager.desktop.pages.addDownload.shared.ShowAddToQueueDialog
import com.abdownloadmanager.desktop.ui.theme.myColors import com.abdownloadmanager.desktop.ui.theme.myColors
import com.abdownloadmanager.desktop.ui.util.ifThen import com.abdownloadmanager.desktop.ui.theme.myTextSizes
import com.abdownloadmanager.desktop.ui.widget.CheckBox import com.abdownloadmanager.desktop.ui.widget.*
import com.abdownloadmanager.desktop.utils.div import com.abdownloadmanager.desktop.utils.div
import com.abdownloadmanager.desktop.utils.windowUtil.moveSafe
import com.abdownloadmanager.resources.Res import com.abdownloadmanager.resources.Res
import com.abdownloadmanager.resources.* import com.abdownloadmanager.utils.category.Category
import com.abdownloadmanager.utils.compose.WithContentColor import com.abdownloadmanager.utils.compose.WithContentAlpha
import com.abdownloadmanager.utils.compose.widget.MyIcon
import ir.amirab.util.compose.resources.myStringResource import ir.amirab.util.compose.resources.myStringResource
import ir.amirab.util.compose.StringSource
import ir.amirab.util.compose.asStringSource
import java.awt.MouseInfo
@Composable @Composable
fun AddMultiItemPage( fun AddMultiItemPage(
@ -105,10 +85,9 @@ fun Footer(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
SaveSettings( SaveSettings(
modifier = Modifier.width(300.dp), modifier = Modifier.fillMaxWidth().weight(1f),
component = component, component = component,
) )
Spacer(Modifier.weight(1f))
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(8.dp))
Row(Modifier.align(Alignment.Bottom)) { Row(Modifier.align(Alignment.Bottom)) {
ActionButton( ActionButton(
@ -137,324 +116,125 @@ private fun SaveSettings(
modifier: Modifier, modifier: Modifier,
component: AddMultiDownloadComponent, component: AddMultiDownloadComponent,
) { ) {
Column( val selectedCategory by component.selectedCategory.collectAsState()
modifier.animateContentSize()
) { val folder by component.folder.collectAsState()
var dropdownOpen by remember { mutableStateOf(false) }
val saveMode by component.saveMode.collectAsState() Column(modifier) {
Text("${myStringResource(Res.string.save_to)}:") Text("${myStringResource(Res.string.save_to)}:")
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(8.dp))
SaveSolution( Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
saveMode = saveMode, CategorySaveOption(selectedCategory, component)
setSaveMode = { Spacer(Modifier.width(8.dp))
component.setSaveMode(it) LocationSaveOption(component, folder)
}, Spacer(Modifier)
isSelectionOpen = dropdownOpen, }
setSelectionOpen = { }
dropdownOpen = it }
}
)
when (saveMode) {
AddMultiItemSaveMode.EachFileInTheirOwnCategory -> {
//
}
AddMultiItemSaveMode.AllInOneCategory -> { @Composable
Spacer(Modifier.height(8.dp)) private fun RowScope.LocationSaveOption(
Row( component: AddMultiDownloadComponent,
Modifier.height(IntrinsicSize.Max), folder: String
verticalAlignment = Alignment.CenterVertically, ) {
) { val allItemsInSameLocation by component.allInSameLocation.collectAsState()
CategorySelect( SaveOption(
categories = component.categories.collectAsState().value, title = myStringResource(Res.string.all_items_in_one_Location),
modifier = Modifier.weight(1f), selectedHelp = myStringResource(Res.string.all_items_in_one_Location_description),
selectedCategory = component.selectedCategory.collectAsState().value, unselectedHelp = myStringResource(Res.string.unselected_all_items_in_specific_location_description),
onCategorySelected = { selected = allItemsInSameLocation,
component.setSelectedCategory(it) onSelectedChange = {
} component.setAllItemsInSameLocation(it)
) },
Spacer(Modifier.width(8.dp)) selectedContent = {
CategoryAddButton( LocationTextField(
Modifier.fillMaxHeight(), text = folder,
enabled = true, setText = {
onClick = { component.setFolder(it)
component.requestAddCategory() },
}, modifier = Modifier.fillMaxWidth(),
) lastUsedLocations = component.lastUsedLocations.collectAsState().value
} )
} }
)
}
AddMultiItemSaveMode.InSameLocation -> { @Composable
Spacer(Modifier.height(8.dp)) private fun RowScope.CategorySaveOption(
AllFilesInSameDirectory( selectedCategory: Category?,
Modifier, component: AddMultiDownloadComponent
folder = component.folder.collectAsState().value, ) {
setFolder = {
component.setFolder(it) SaveOption(
title = myStringResource(Res.string.all_items_in_one_category),
selectedHelp = myStringResource(Res.string.all_items_in_one_category_description),
unselectedHelp = myStringResource(Res.string.each_item_on_its_own_category_description),
selected = selectedCategory != null,
onSelectedChange = {
if (it) {
component.setSelectedCategory(component.categories.value.firstOrNull())
} else {
component.setSelectedCategory(null)
}
component.setAlsoAutoCategorize(!it)
},
selectedContent = {
Row(
Modifier.height(IntrinsicSize.Max).fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
CategorySelect(
categories = component.categories.collectAsState().value,
modifier = Modifier.weight(1f),
selectedCategory = component.selectedCategory.collectAsState().value,
onCategorySelected = {
component.setSelectedCategory(it)
}
)
Spacer(Modifier.width(8.dp))
CategoryAddButton(
Modifier.fillMaxHeight(),
enabled = true,
onClick = {
component.requestAddCategory()
}, },
alsoCategorize = component.alsoAutoCategorize.collectAsState().value,
setAlsoCategorize = component::setAlsoAutoCategorize,
knownLocations = component.lastUsedLocations.collectAsState().value,
) )
} }
} }
}
}
@Composable
private fun SaveSolution(
saveMode: AddMultiItemSaveMode,
setSaveMode: (AddMultiItemSaveMode) -> Unit,
isSelectionOpen: Boolean,
setSelectionOpen: (Boolean) -> Unit,
) {
SaveSolutionHeader(
saveMode = saveMode,
onClick = {
setSelectionOpen(!isSelectionOpen)
},
) )
if (isSelectionOpen) {
SaveSolutionPopup(
selectedItem = saveMode,
onIteSelected = setSaveMode,
onDismiss = {
setSelectionOpen(false)
}
)
}
} }
@Composable @Composable
private fun SaveSolutionPopup( private fun RowScope.SaveOption(
selectedItem: AddMultiItemSaveMode,
onIteSelected: (AddMultiItemSaveMode) -> Unit,
onDismiss: () -> Unit,
) {
val state = rememberDialogState(
size = DpSize(
height = Dp.Unspecified,
width = Dp.Unspecified,
),
)
val close = {
onDismiss()
}
BaseOptionDialog(
onCloseRequest = close,
state = state,
resizeable = false,
) {
LaunchedEffect(window) {
window.moveSafe(
MouseInfo.getPointerInfo().location.run {
DpOffset(
x = x.dp,
y = y.dp
)
}
)
}
val shape = RoundedCornerShape(6.dp)
Column(
Modifier
.clip(shape)
.border(2.dp, myColors.onBackground / 10, shape)
.background(
Brush.linearGradient(
listOf(
myColors.surface,
myColors.background,
)
)
)
) {
WithContentColor(myColors.onBackground) {
Column(
Modifier.widthIn(max = 300.dp)
) {
WindowDraggableArea(Modifier) {
Column(
Modifier.padding(16.dp)
) {
Text(
myStringResource(Res.string.where_should_each_item_saved),
Modifier,
fontSize = myTextSizes.base
)
Spacer(Modifier.height(8.dp))
WithContentAlpha(0.75f) {
Text(
myStringResource(Res.string.there_are_multiple_items_please_select_a_way_you_want_to_save_them),
Modifier,
fontSize = myTextSizes.sm,
)
}
}
}
Column(
Modifier
.padding(horizontal = 8.dp)
.padding(bottom = 8.dp)
) {
Spacer(Modifier.height(4.dp))
Spacer(
Modifier.fillMaxWidth()
.height(1.dp)
.background(myColors.onBackground / 10),
)
Spacer(Modifier.height(4.dp))
Column {
for (item in AddMultiItemSaveMode.entries) {
SaveSolutionItem(
title = item.title.rememberString(),
description = item.description.rememberString(),
isSelected = selectedItem == item,
onClick = {
onIteSelected(item)
close()
}
)
}
}
}
}
}
}
}
}
@Composable
private fun SaveSolutionHeader(
saveMode: AddMultiItemSaveMode,
onClick: () -> Unit,
enabled: Boolean = true,
) {
val borderColor = myColors.onBackground / 0.1f
val background = myColors.surface / 50
val shape = RoundedCornerShape(6.dp)
Row(
Modifier
.height(IntrinsicSize.Max)
.clip(shape)
.ifThen(!enabled) {
alpha(0.5f)
}
.border(1.dp, borderColor, shape)
.background(background)
.clickable(
enabled = enabled
) { onClick() }
.padding(horizontal = 8.dp)
) {
val contentModifier = Modifier
.padding(vertical = 8.dp)
.weight(1f)
Text(
saveMode.title.rememberString(),
contentModifier,
)
Spacer(
Modifier
.padding(horizontal = 8.dp)
.fillMaxHeight().padding(vertical = 1.dp)
.width(1.dp)
.background(borderColor)
)
MyIcon(
MyIcons.down,
null,
Modifier
.align(Alignment.CenterVertically)
.size(16.dp),
)
}
}
@Composable
private fun SaveSolutionItem(
title: String, title: String,
description: String, selectedHelp:String,
isSelected: Boolean?, unselectedHelp:String,
onClick: () -> Unit, selected: Boolean,
onSelectedChange: (Boolean) -> Unit,
selectedContent: @Composable () -> Unit
) { ) {
Row( ExpandableItem(
verticalAlignment = Alignment.CenterVertically, modifier=Modifier.fillMaxWidth().weight(1f),
modifier = Modifier isExpanded = selected,
.fillMaxWidth() header = {
.clickable(onClick = onClick) Row(
.padding(8.dp) modifier=Modifier.onClick { onSelectedChange(!selected) },
) { verticalAlignment = Alignment.CenterVertically,
isSelected?.let { horizontalArrangement = Arrangement.spacedBy(8.dp)
CheckBox(isSelected, { onClick() }, size = 12.dp) ) {
} CheckBox(
Spacer(Modifier.width(8.dp)) value = selected,
Column { onValueChange = onSelectedChange
Text(
title,
fontSize = myTextSizes.base,
fontWeight = FontWeight.Bold
)
Spacer(Modifier.height(4.dp))
WithContentAlpha(0.7f) {
Text(
text = description,
fontSize = myTextSizes.sm,
modifier = Modifier
) )
Text(title)
Help(if (selected) selectedHelp else unselectedHelp)
}
},
body = {
Column {
Spacer(Modifier.height(8.dp))
selectedContent()
} }
} }
}
}
@Composable
private fun AllFilesInSameDirectory(
modifier: Modifier,
folder: String,
setFolder: (String) -> Unit,
alsoCategorize: Boolean,
setAlsoCategorize: (Boolean) -> Unit,
knownLocations: List<String>,
) {
LocationTextField(
text = folder,
setText = {
setFolder(it)
},
modifier = modifier,
lastUsedLocations = knownLocations
) )
Spacer(Modifier.height(8.dp))
Row(
Modifier.onClick {
setAlsoCategorize(!alsoCategorize)
},
verticalAlignment = Alignment.CenterVertically,
) {
CheckBox(
value = alsoCategorize,
onValueChange = setAlsoCategorize
)
Spacer(Modifier.width(4.dp))
Text(myStringResource(Res.string.auto_categorize_downloads))
}
} }
enum class AddMultiItemSaveMode(
val title: StringSource,
val description: StringSource,
) {
EachFileInTheirOwnCategory(
title = Res.string.each_item_on_its_own_category.asStringSource(),
description = Res.string.each_item_on_its_own_category_description.asStringSource(),
),
AllInOneCategory(
title = Res.string.all_items_in_one_category.asStringSource(),
description = Res.string.all_items_in_one_category_description.asStringSource(),
),
InSameLocation(
title = Res.string.all_items_in_one_Location.asStringSource(),
description = Res.string.all_items_in_one_Location_description.asStringSource(),
);
}

View File

@ -110,9 +110,10 @@ there_are_multiple_items_please_select_a_way_you_want_to_save_them=There are mul
each_item_on_its_own_category=Each item on its own category each_item_on_its_own_category=Each item on its own category
each_item_on_its_own_category_description=Each item will be placed in a category that have that file type each_item_on_its_own_category_description=Each item will be placed in a category that have that file type
all_items_in_one_category=All items in one Category all_items_in_one_category=All items in one Category
all_items_in_one_category_description=All files will be saved in the selected category location all_items_in_one_category_description=All files will be saved in the selected category
all_items_in_one_Location=All items in one Location all_items_in_one_Location=All items in one Location
all_items_in_one_Location_description=All items will be saved in the selected directory all_items_in_one_Location_description=All items will be saved in the selected directory
unselected_all_items_in_specific_location_description=All files will be saved in the selected category location
no_category_selected=No Category Selected no_category_selected=No Category Selected
download_location=Download Location download_location=Download Location
location=Location location=Location