Support grid view in DownloadsScreen (#714)

* Support grid view in DownloadsScreen

* Fix aspect

* Refine
This commit is contained in:
FooIbar authored and GitHub committed 2024-02-12 09:37:45 +00:00
1 parent 7ef70aa16e
commit 51c0efebc4
4 files changed
+133 -78

No files matched your search

@@ -151,6 +151,7 @@ object Settings : DataStorePreferences(null) {
var downloadOriginImage by boolPref("download_origin_image", false)
var archiveMetadata by boolPref("archive_metadata", true)
var enableCronet by boolPref("enable_cronet", true)
var gridView by boolPref("grid_view", false)
var recentFavCat by intPref("recent_fav_cat", FavListUrlBuilder.FAV_CAT_LOCAL)
var defaultFavSlot by intPref("default_favorite_slot", -2)
var securityDelay by intPref("require_unlock_delay", 0)
@@ -7,6 +7,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import coil3.compose.AsyncImage
@@ -26,11 +27,13 @@ fun requestOf(model: GalleryInfo): ImageRequest {
fun EhAsyncThumb(
model: GalleryInfo,
modifier: Modifier = Modifier,
placeholder: Painter? = null,
contentScale: ContentScale = ContentScale.Fit,
) = AsyncImage(
model = requestOf(model),
contentDescription = "",
modifier = modifier,
placeholder = placeholder,
contentScale = contentScale,
)
@@ -24,11 +24,15 @@ import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.BrushPainter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.text.font.FontStyle
@@ -191,11 +195,6 @@ fun GalleryInfoGridItem(
info: GalleryInfo,
modifier: Modifier = Modifier,
) {
val aspect = if (info.thumbHeight != 0) {
(info.thumbWidth.toFloat() / info.thumbHeight).coerceIn(MIN_ASPECT, MAX_ASPECT)
} else {
DEFAULT_ASPECT
}
val simpleLang = info.simpleLanguage
ElevatedCard(
modifier = modifier,
@@ -203,9 +202,18 @@ fun GalleryInfoGridItem(
onLongClick = onLongClick,
) {
Box {
val placeholder = remember {
val aspect = if (info.thumbHeight != 0) {
(info.thumbWidth.toFloat() / info.thumbHeight).coerceIn(MIN_ASPECT, MAX_ASPECT)
} else {
DEFAULT_ASPECT
}
BrushPainter(Brush.linearGradient(PlaceholderColors, end = Offset(aspect, 1f)))
}
EhAsyncThumb(
model = info,
modifier = Modifier.aspectRatio(aspect).fillMaxWidth(),
modifier = Modifier.fillMaxWidth(),
placeholder = placeholder,
contentScale = ContentScale.Crop,
)
val categoryColor = EhUtils.getCategoryColor(info.category)
@@ -222,6 +230,8 @@ fun GalleryInfoGridItem(
}
}
private val PlaceholderColors = listOf(Color.Transparent, Color.Transparent)
private const val MIN_ASPECT = 0.33F
private const val MAX_ASPECT = 1.5F
const val DEFAULT_ASPECT = 0.67F
@@ -20,13 +20,16 @@ import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
import androidx.compose.foundation.lazy.staggeredgrid.items
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.DriveFileMove
import androidx.compose.material.icons.automirrored.outlined.Label
import androidx.compose.material.icons.automirrored.filled.ViewList
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.DoneAll
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.GridView
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.NewLabel
import androidx.compose.material.icons.filled.Pause
@@ -68,9 +71,11 @@ import androidx.compose.ui.res.stringArrayResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import arrow.core.partially1
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.collectAsState
import com.hippo.ehviewer.dao.DownloadInfo
import com.hippo.ehviewer.download.DownloadManager
import com.hippo.ehviewer.download.DownloadManager.labelList
@@ -83,11 +88,13 @@ import com.hippo.ehviewer.ui.MainActivity
import com.hippo.ehviewer.ui.confirmRemoveDownloadRange
import com.hippo.ehviewer.ui.main.DownloadCard
import com.hippo.ehviewer.ui.main.FabLayout
import com.hippo.ehviewer.ui.main.GalleryInfoGridItem
import com.hippo.ehviewer.ui.navToReader
import com.hippo.ehviewer.ui.showMoveDownloadLabelList
import com.hippo.ehviewer.ui.tools.Deferred
import com.hippo.ehviewer.ui.tools.DragHandle
import com.hippo.ehviewer.ui.tools.FastScrollLazyColumn
import com.hippo.ehviewer.ui.tools.FastScrollLazyVerticalStaggeredGrid
import com.hippo.ehviewer.ui.tools.LocalDialogState
import com.hippo.ehviewer.ui.tools.SwipeToDismissBox2
import com.hippo.ehviewer.ui.tools.draggingHapticFeedback
@@ -111,6 +118,7 @@ import sh.calvin.reorderable.rememberReorderableLazyColumnState
@Composable
fun DownloadsScreen(navigator: DestinationsNavigator) {
var label by Settings::recentDownloadLabel.observed
var gridView by Settings::gridView.observed
var keyword by rememberSaveable { mutableStateOf<String?>(null) }
var filterType by rememberSaveable { mutableIntStateOf(-1) }
var searchBarOffsetY by remember(label) { mutableIntStateOf(0) }
@@ -333,14 +341,24 @@ fun DownloadsScreen(navigator: DestinationsNavigator) {
trailingIcon = {
var expanded by remember { mutableStateOf(false) }
val sideSheetState = LocalSideSheetState.current
IconButton(onClick = { coroutineScope.launch { sideSheetState.open() } }) {
Icon(imageVector = Icons.AutoMirrored.Outlined.Label, contentDescription = stringResource(id = R.string.download_labels))
IconButton(onClick = { gridView = !gridView }) {
val icon = if (gridView) Icons.AutoMirrored.Default.ViewList else Icons.Default.GridView
Icon(imageVector = icon, contentDescription = null)
}
IconButton(onClick = { expanded = !expanded }) {
Icon(imageVector = Icons.Default.MoreVert, contentDescription = null)
}
val states = stringArrayResource(id = R.array.download_state)
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
DropdownMenuItem(
text = { Text(text = stringResource(id = R.string.download_labels)) },
onClick = {
expanded = false
coroutineScope.launch {
sideSheetState.open()
}
},
)
DropdownMenuItem(
text = { Text(text = stringResource(id = R.string.download_filter)) },
onClick = {
@@ -409,13 +427,6 @@ fun DownloadsScreen(navigator: DestinationsNavigator) {
start = contentPadding.calculateStartPadding(layoutDirection) + marginH,
end = contentPadding.calculateEndPadding(layoutDirection) + marginH,
)
val listState = rememberLazyListState()
val reorderableState = rememberReorderableLazyColumnState(listState) { from, to ->
val fromIndex = from.index - 1
val toIndex = to.index - 1
list.apply { add(toIndex, removeAt(fromIndex)) }
view.performHapticFeedback(draggingHapticFeedback)
}
val searchBarConnection = remember {
val topPaddingPx = with(density) { contentPadding.calculateTopPadding().roundToPx() }
object : NestedScrollConnection {
@@ -425,70 +436,100 @@ fun DownloadsScreen(navigator: DestinationsNavigator) {
}
}
}
var fromIndex by remember { mutableIntStateOf(-1) }
FastScrollLazyColumn(
modifier = Modifier.nestedScroll(searchBarConnection).fillMaxSize(),
state = listState,
contentPadding = realPadding,
verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.gallery_list_interval)),
) {
// Fix the first item's reorder animation
item {}
itemsIndexed(list, key = { _, item -> item.gid }) { index, info ->
ReorderableItem(reorderableState, key = info.gid) {
val checked = info.gid in checkedInfoMap
CheckableItem(checked = checked) {
DownloadCard(
onClick = {
if (selectMode) {
if (checked) {
checkedInfoMap.remove(info.gid)
} else {
checkedInfoMap[info.gid] = info
}
} else {
coroutineScope.launchIO {
EhDB.putHistoryInfo(info.galleryInfo)
}
context.navToReader(info.galleryInfo)
}
},
onThumbClick = {
navigator.navigate(info.galleryInfo.asDst())
},
onLongClick = {
checkedInfoMap[info.gid] = info
},
onStart = {
val intent = Intent(activity, DownloadService::class.java)
intent.action = DownloadService.ACTION_START
intent.putExtra(DownloadService.KEY_GALLERY_INFO, info.galleryInfo)
ContextCompat.startForegroundService(activity, intent)
},
onStop = {
coroutineScope.launchIO {
DownloadManager.stopDownload(info.gid)
}
},
onDragStarted = {
fromIndex = index
},
onDragStopped = {
if (fromIndex != -1) {
val direction = (index - fromIndex).sign
if (direction != 0) {
val toItem = list[index - direction]
coroutineScope.launchIO {
val toUpdate = DownloadManager.moveDownload(info, toItem)
EhDB.updateDownloadInfo(toUpdate)
fun onItemClick(info: DownloadInfo) {
coroutineScope.launchIO {
EhDB.putHistoryInfo(info.galleryInfo)
}
context.navToReader(info.galleryInfo)
}
if (gridView) {
val gridInterval = dimensionResource(R.dimen.gallery_grid_interval)
val thumbColumns by Settings.thumbColumns.collectAsState()
FastScrollLazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Fixed(thumbColumns),
modifier = Modifier.nestedScroll(searchBarConnection).fillMaxSize(),
verticalItemSpacing = gridInterval,
horizontalArrangement = Arrangement.spacedBy(gridInterval),
contentPadding = realPadding,
) {
items(list) {
GalleryInfoGridItem(
onClick = ::onItemClick.partially1(it),
onLongClick = { navigator.navigate(it.galleryInfo.asDst()) },
info = it,
)
}
}
} else {
val listState = rememberLazyListState()
val reorderableState = rememberReorderableLazyColumnState(listState) { from, to ->
val fromIndex = from.index - 1
val toIndex = to.index - 1
list.apply { add(toIndex, removeAt(fromIndex)) }
view.performHapticFeedback(draggingHapticFeedback)
}
var fromIndex by remember { mutableIntStateOf(-1) }
FastScrollLazyColumn(
modifier = Modifier.nestedScroll(searchBarConnection).fillMaxSize(),
state = listState,
contentPadding = realPadding,
verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.gallery_list_interval)),
) {
// Fix the first item's reorder animation
item {}
itemsIndexed(list, key = { _, item -> item.gid }) { index, info ->
ReorderableItem(reorderableState, key = info.gid) {
val checked = info.gid in checkedInfoMap
CheckableItem(checked = checked) {
DownloadCard(
onClick = {
if (selectMode) {
if (checked) {
checkedInfoMap.remove(info.gid)
} else {
checkedInfoMap[info.gid] = info
}
} else {
onItemClick(info)
}
fromIndex = -1
}
},
info = info,
modifier = Modifier.height(height),
)
},
onThumbClick = {
navigator.navigate(info.galleryInfo.asDst())
},
onLongClick = {
checkedInfoMap[info.gid] = info
},
onStart = {
val intent = Intent(activity, DownloadService::class.java)
intent.action = DownloadService.ACTION_START
intent.putExtra(DownloadService.KEY_GALLERY_INFO, info.galleryInfo)
ContextCompat.startForegroundService(activity, intent)
},
onStop = {
coroutineScope.launchIO {
DownloadManager.stopDownload(info.gid)
}
},
onDragStarted = {
fromIndex = index
},
onDragStopped = {
if (fromIndex != -1) {
val direction = (index - fromIndex).sign
if (direction != 0) {
val toItem = list[index - direction]
coroutineScope.launchIO {
val toUpdate = DownloadManager.moveDownload(info, toItem)
EhDB.updateDownloadInfo(toUpdate)
}
}
fromIndex = -1
}
},
info = info,
modifier = Modifier.height(height),
)
}
}
}
}