Compose DownloadCard (#339)
* Cleanup res * Compose DownloadCard * Refine * Fix CI * Fixes * Fix recompose * Fix textStyle * Fixes * Fixes * Restore design specs and clean up --------- Co-authored-by: GG Bond <whatever@tagkong.com>
This commit is contained in:
1 parent
618f14a732
commit
198960f93c
13 files changed
+342
-604
No files matched your search
@@ -18,6 +18,12 @@ package com.hippo.ehviewer.download
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.SparseLongArray
|
import android.util.SparseLongArray
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisallowComposableCalls
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import com.google.android.material.math.MathUtils
|
import com.google.android.material.math.MathUtils
|
||||||
import com.hippo.ehviewer.EhDB
|
import com.hippo.ehviewer.EhDB
|
||||||
import com.hippo.ehviewer.Settings
|
import com.hippo.ehviewer.Settings
|
||||||
@@ -47,6 +53,7 @@ import kotlinx.coroutines.coroutineScope
|
|||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.shareIn
|
import kotlinx.coroutines.flow.shareIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
@@ -246,15 +253,12 @@ object DownloadManager : OnSpiderListener {
|
|||||||
|
|
||||||
private val callbackFlowScope = CoroutineScope(Dispatchers.IO)
|
private val callbackFlowScope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
private val _stateFlow = callbackFlow {
|
val _stateFlow = callbackFlow {
|
||||||
val listener = object : DownloadInfoListener {
|
val listener = object : DownloadInfoListener {
|
||||||
override fun onUpdate(info: DownloadInfo) {
|
override fun onUpdate(info: DownloadInfo) {
|
||||||
trySend(info.gid)
|
trySend(info)
|
||||||
}
|
|
||||||
|
|
||||||
override fun onUpdateAll() {
|
|
||||||
trySend(0)
|
|
||||||
}
|
}
|
||||||
|
override fun onUpdateAll() {}
|
||||||
}
|
}
|
||||||
mDownloadInfoListener = listener
|
mDownloadInfoListener = listener
|
||||||
awaitClose {
|
awaitClose {
|
||||||
@@ -262,7 +266,21 @@ object DownloadManager : OnSpiderListener {
|
|||||||
}
|
}
|
||||||
}.shareIn(callbackFlowScope, SharingStarted.Eagerly)
|
}.shareIn(callbackFlowScope, SharingStarted.Eagerly)
|
||||||
|
|
||||||
fun stateFlow(gid: Long) = _stateFlow.filter { it == gid }
|
@Stable
|
||||||
|
@Composable
|
||||||
|
fun collectDownloadState(gid: Long): State<Int> {
|
||||||
|
val flow = remember { stateFlow(gid).map { getDownloadState(it) } }
|
||||||
|
return flow.collectAsState(getDownloadState(gid))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
@Composable
|
||||||
|
inline fun <T> updatedDownloadInfo(info: DownloadInfo, crossinline transform: @DisallowComposableCalls DownloadInfo.() -> T): T {
|
||||||
|
val flow = remember { _stateFlow.filter { it.gid == info.gid }.map { transform(it) } }
|
||||||
|
return flow.collectAsState(transform(info)).value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stateFlow(gid: Long) = _stateFlow.map { it.gid }.filter { it == gid }
|
||||||
|
|
||||||
fun stateFlow() = _stateFlow
|
fun stateFlow() = _stateFlow
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,278 @@
|
|||||||
|
package com.hippo.ehviewer.ui.main
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.DragHandle
|
||||||
|
import androidx.compose.material.icons.filled.Pause
|
||||||
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ProvideTextStyle
|
||||||
|
import androidx.compose.material3.ShapeDefaults
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
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.graphics.Color
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.constraintlayout.compose.ConstraintLayout
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import coil.compose.AsyncImagePainter
|
||||||
|
import com.hippo.ehviewer.EhApplication
|
||||||
|
import com.hippo.ehviewer.R
|
||||||
|
import com.hippo.ehviewer.Settings
|
||||||
|
import com.hippo.ehviewer.client.EhUtils
|
||||||
|
import com.hippo.ehviewer.coil.read
|
||||||
|
import com.hippo.ehviewer.dao.DownloadInfo
|
||||||
|
import com.hippo.ehviewer.download.DownloadManager
|
||||||
|
import com.hippo.ehviewer.download.downloadDir
|
||||||
|
import com.hippo.ehviewer.ktbuilder.imageRequest
|
||||||
|
import com.hippo.ehviewer.ui.tools.CropDefaults
|
||||||
|
import com.hippo.ehviewer.ui.tools.CrystalCard
|
||||||
|
import com.hippo.ehviewer.ui.tools.GalleryListCardRating
|
||||||
|
import com.hippo.ehviewer.util.FileUtils
|
||||||
|
import com.hippo.ehviewer.util.sendTo
|
||||||
|
import com.hippo.unifile.UniFile
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AsyncThumb(
|
||||||
|
info: DownloadInfo,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val path = remember(info.gid) { info.downloadDir?.subFile(".thumb") }
|
||||||
|
var contentScale by remember(info.gid) { mutableStateOf(ContentScale.Fit) }
|
||||||
|
val coroutineScope = rememberCoroutineScope { Dispatchers.IO }
|
||||||
|
val context = LocalContext.current
|
||||||
|
var localReq by remember(info.gid) {
|
||||||
|
path?.takeIf { it.isFile }?.uri?.let {
|
||||||
|
context.imageRequest {
|
||||||
|
data(it.toString())
|
||||||
|
memoryCacheKey(info.thumbKey)
|
||||||
|
}
|
||||||
|
}.let { mutableStateOf(it) }
|
||||||
|
}
|
||||||
|
AsyncImage(
|
||||||
|
model = localReq ?: requestOf(info),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = modifier,
|
||||||
|
onState = { state ->
|
||||||
|
if (state is AsyncImagePainter.State.Success) {
|
||||||
|
state.result.drawable.run {
|
||||||
|
if (CropDefaults.shouldCrop(intrinsicWidth, intrinsicHeight)) {
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
path?.let {
|
||||||
|
coroutineScope.launch {
|
||||||
|
runCatching {
|
||||||
|
if (!path.exists() && path.ensureFile()) {
|
||||||
|
val key = info.thumbKey!!
|
||||||
|
EhApplication.imageCache.read(key) {
|
||||||
|
UniFile.fromFile(data.toFile())!!.openFileDescriptor("r").use { src ->
|
||||||
|
path.openFileDescriptor("w").use { dst ->
|
||||||
|
src sendTo dst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.onFailure {
|
||||||
|
it.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (state is AsyncImagePainter.State.Error) {
|
||||||
|
path?.let {
|
||||||
|
coroutineScope.launch {
|
||||||
|
if (path.exists()) {
|
||||||
|
path.delete()
|
||||||
|
localReq = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
contentScale = contentScale,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DownloadCard(
|
||||||
|
onClick: () -> Unit,
|
||||||
|
onThumbClick: () -> Unit,
|
||||||
|
onLongClick: () -> Unit,
|
||||||
|
onStart: () -> Unit,
|
||||||
|
onStop: () -> Unit,
|
||||||
|
onDrag: () -> Unit,
|
||||||
|
info: DownloadInfo,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
CrystalCard(
|
||||||
|
modifier = modifier,
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
) {
|
||||||
|
Row {
|
||||||
|
Card(onClick = onThumbClick) {
|
||||||
|
AsyncThumb(
|
||||||
|
info = info,
|
||||||
|
modifier = Modifier.aspectRatio(DEFAULT_ASPECT).fillMaxSize(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val stateFailed = stringResource(R.string.download_state_failed)
|
||||||
|
val stateFailed2 = stringResource(R.string.download_state_failed_2, info.legacy)
|
||||||
|
val downloadState by DownloadManager.collectDownloadState(info.gid)
|
||||||
|
val stateText = when (downloadState) {
|
||||||
|
DownloadInfo.STATE_NONE -> stringResource(R.string.download_state_none)
|
||||||
|
DownloadInfo.STATE_WAIT -> stringResource(R.string.download_state_wait)
|
||||||
|
DownloadInfo.STATE_DOWNLOAD -> null
|
||||||
|
DownloadInfo.STATE_FAILED -> if (info.legacy <= 0) stateFailed else stateFailed2
|
||||||
|
DownloadInfo.STATE_FINISH -> stringResource(R.string.download_state_finish)
|
||||||
|
else -> error("Illegal State $downloadState!!!")
|
||||||
|
}
|
||||||
|
ConstraintLayout(modifier = Modifier.padding(8.dp, 4.dp).fillMaxSize()) {
|
||||||
|
val (
|
||||||
|
titleRef, uploaderRef, ratingRef, categoryRef, actionsRef, stateTextRef,
|
||||||
|
progressBarRef, progressTextRef, speedRef,
|
||||||
|
) = createRefs()
|
||||||
|
Text(
|
||||||
|
text = EhUtils.getSuitableTitle(info),
|
||||||
|
maxLines = 2,
|
||||||
|
modifier = Modifier.constrainAs(titleRef) {
|
||||||
|
top.linkTo(parent.top)
|
||||||
|
start.linkTo(parent.start)
|
||||||
|
}.fillMaxWidth(),
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
)
|
||||||
|
if (downloadState != DownloadInfo.STATE_DOWNLOAD) {
|
||||||
|
ProvideTextStyle(MaterialTheme.typography.labelLarge) {
|
||||||
|
info.uploader?.let {
|
||||||
|
Text(
|
||||||
|
text = it,
|
||||||
|
modifier = Modifier.constrainAs(uploaderRef) {
|
||||||
|
start.linkTo(parent.start)
|
||||||
|
bottom.linkTo(ratingRef.top)
|
||||||
|
}.alpha(if (info.disowned) 0.5f else 1f),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
GalleryListCardRating(
|
||||||
|
rating = info.rating,
|
||||||
|
modifier = Modifier.constrainAs(ratingRef) {
|
||||||
|
start.linkTo(parent.start)
|
||||||
|
bottom.linkTo(categoryRef.top)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
val categoryColor = EhUtils.getCategoryColor(info.category)
|
||||||
|
val categoryText = EhUtils.getCategory(info.category).uppercase()
|
||||||
|
Text(
|
||||||
|
text = categoryText,
|
||||||
|
modifier = Modifier.constrainAs(categoryRef) {
|
||||||
|
start.linkTo(parent.start)
|
||||||
|
bottom.linkTo(parent.bottom)
|
||||||
|
}.clip(ShapeDefaults.Small).background(categoryColor).padding(vertical = 2.dp, horizontal = 8.dp),
|
||||||
|
color = if (Settings.harmonizeCategoryColor) Color.Unspecified else EhUtils.categoryTextColor,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val (total, finished, speed) = DownloadManager.updatedDownloadInfo(info) {
|
||||||
|
Triple(total, finished, speed)
|
||||||
|
}
|
||||||
|
ProvideTextStyle(MaterialTheme.typography.labelMedium) {
|
||||||
|
if (total <= 0 || finished < 0) {
|
||||||
|
LinearProgressIndicator(
|
||||||
|
modifier = Modifier.constrainAs(progressBarRef) {
|
||||||
|
bottom.linkTo(actionsRef.top)
|
||||||
|
}.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
LinearProgressIndicator(
|
||||||
|
progress = { finished.toFloat() / total.toFloat() },
|
||||||
|
modifier = Modifier.constrainAs(progressBarRef) {
|
||||||
|
bottom.linkTo(actionsRef.top)
|
||||||
|
}.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "$finished/$total",
|
||||||
|
modifier = Modifier.constrainAs(progressTextRef) {
|
||||||
|
bottom.linkTo(progressBarRef.top)
|
||||||
|
start.linkTo(progressBarRef.start)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = FileUtils.humanReadableByteCount(speed.coerceAtLeast(0), false) + "/S",
|
||||||
|
modifier = Modifier.constrainAs(speedRef) {
|
||||||
|
bottom.linkTo(progressBarRef.top)
|
||||||
|
end.linkTo(progressBarRef.end)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.constrainAs(actionsRef) {
|
||||||
|
end.linkTo(parent.end)
|
||||||
|
bottom.linkTo(parent.bottom)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
IconButton(onClick = { }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.DragHandle,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.pointerInput(info.gid) {
|
||||||
|
awaitPointerEventScope { onDrag() }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (downloadState == DownloadInfo.STATE_WAIT || downloadState == DownloadInfo.STATE_DOWNLOAD) {
|
||||||
|
IconButton(onClick = onStop) {
|
||||||
|
Icon(imageVector = Icons.Default.Pause, contentDescription = null)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
IconButton(onClick = onStart) {
|
||||||
|
Icon(imageVector = Icons.Default.PlayArrow, contentDescription = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (stateText != null) {
|
||||||
|
Text(
|
||||||
|
text = stateText,
|
||||||
|
modifier = Modifier.constrainAs(stateTextRef) {
|
||||||
|
end.linkTo(parent.end, 16.dp)
|
||||||
|
bottom.linkTo(actionsRef.top)
|
||||||
|
},
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,24 +16,17 @@
|
|||||||
package com.hippo.ehviewer.ui.scene
|
package com.hippo.ehviewer.ui.scene
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.DriveFileMove
|
import androidx.compose.material.icons.automirrored.filled.DriveFileMove
|
||||||
import androidx.compose.material.icons.automirrored.outlined.Label
|
import androidx.compose.material.icons.automirrored.outlined.Label
|
||||||
@@ -42,29 +35,22 @@ import androidx.compose.material.icons.filled.DoneAll
|
|||||||
import androidx.compose.material.icons.filled.MoreVert
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
import androidx.compose.material.icons.filled.Pause
|
import androidx.compose.material.icons.filled.Pause
|
||||||
import androidx.compose.material.icons.filled.PlayArrow
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
import androidx.compose.material3.Card
|
|
||||||
import androidx.compose.material3.DropdownMenu
|
import androidx.compose.material3.DropdownMenu
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.ShapeDefaults
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.key
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.platform.ComposeView
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringArrayResource
|
import androidx.compose.ui.res.stringArrayResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
@@ -75,27 +61,17 @@ import androidx.recyclerview.widget.ListAdapter
|
|||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||||
import androidx.viewbinding.ViewBinding
|
import androidx.viewbinding.ViewBinding
|
||||||
import arrow.core.partially1
|
|
||||||
import coil.compose.AsyncImage
|
|
||||||
import coil.compose.AsyncImagePainter
|
|
||||||
import com.hippo.ehviewer.EhApplication.Companion.imageCache
|
|
||||||
import com.hippo.ehviewer.EhDB
|
import com.hippo.ehviewer.EhDB
|
||||||
import com.hippo.ehviewer.R
|
import com.hippo.ehviewer.R
|
||||||
import com.hippo.ehviewer.Settings
|
import com.hippo.ehviewer.Settings
|
||||||
import com.hippo.ehviewer.Settings.detailSize
|
import com.hippo.ehviewer.Settings.detailSize
|
||||||
import com.hippo.ehviewer.client.EhUtils
|
|
||||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
|
||||||
import com.hippo.ehviewer.coil.read
|
|
||||||
import com.hippo.ehviewer.dao.DownloadInfo
|
import com.hippo.ehviewer.dao.DownloadInfo
|
||||||
import com.hippo.ehviewer.databinding.DrawerListRvBinding
|
import com.hippo.ehviewer.databinding.DrawerListRvBinding
|
||||||
import com.hippo.ehviewer.databinding.ItemDownloadBinding
|
|
||||||
import com.hippo.ehviewer.databinding.ItemDrawerListBinding
|
import com.hippo.ehviewer.databinding.ItemDrawerListBinding
|
||||||
import com.hippo.ehviewer.databinding.SceneDownloadBinding
|
import com.hippo.ehviewer.databinding.SceneDownloadBinding
|
||||||
import com.hippo.ehviewer.download.DownloadManager
|
import com.hippo.ehviewer.download.DownloadManager
|
||||||
import com.hippo.ehviewer.download.DownloadService
|
import com.hippo.ehviewer.download.DownloadService
|
||||||
import com.hippo.ehviewer.download.DownloadService.Companion.clear
|
import com.hippo.ehviewer.download.DownloadService.Companion.clear
|
||||||
import com.hippo.ehviewer.download.downloadDir
|
|
||||||
import com.hippo.ehviewer.ktbuilder.imageRequest
|
|
||||||
import com.hippo.ehviewer.ui.confirmRemoveDownload
|
import com.hippo.ehviewer.ui.confirmRemoveDownload
|
||||||
import com.hippo.ehviewer.ui.confirmRemoveDownloadRange
|
import com.hippo.ehviewer.ui.confirmRemoveDownloadRange
|
||||||
import com.hippo.ehviewer.ui.legacy.AutoStaggeredGridLayoutManager
|
import com.hippo.ehviewer.ui.legacy.AutoStaggeredGridLayoutManager
|
||||||
@@ -103,23 +79,17 @@ import com.hippo.ehviewer.ui.legacy.BaseDialogBuilder
|
|||||||
import com.hippo.ehviewer.ui.legacy.EditTextDialogBuilder
|
import com.hippo.ehviewer.ui.legacy.EditTextDialogBuilder
|
||||||
import com.hippo.ehviewer.ui.legacy.HandlerDrawable
|
import com.hippo.ehviewer.ui.legacy.HandlerDrawable
|
||||||
import com.hippo.ehviewer.ui.legacy.ViewTransition
|
import com.hippo.ehviewer.ui.legacy.ViewTransition
|
||||||
import com.hippo.ehviewer.ui.main.DEFAULT_ASPECT
|
import com.hippo.ehviewer.ui.main.DownloadCard
|
||||||
import com.hippo.ehviewer.ui.main.requestOf
|
|
||||||
import com.hippo.ehviewer.ui.navToReader
|
import com.hippo.ehviewer.ui.navToReader
|
||||||
import com.hippo.ehviewer.ui.setMD3Content
|
import com.hippo.ehviewer.ui.setMD3Content
|
||||||
import com.hippo.ehviewer.ui.tools.CropDefaults
|
|
||||||
import com.hippo.ehviewer.ui.tools.DialogState
|
import com.hippo.ehviewer.ui.tools.DialogState
|
||||||
import com.hippo.ehviewer.util.FileUtils
|
|
||||||
import com.hippo.ehviewer.util.containsIgnoreCase
|
import com.hippo.ehviewer.util.containsIgnoreCase
|
||||||
import com.hippo.ehviewer.util.mapToLongArray
|
import com.hippo.ehviewer.util.mapToLongArray
|
||||||
import com.hippo.ehviewer.util.sendTo
|
|
||||||
import com.hippo.unifile.UniFile
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
||||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import rikka.core.res.resolveColor
|
import rikka.core.res.resolveColor
|
||||||
@@ -555,16 +525,6 @@ class DownloadsScene : SearchBarScene() {
|
|||||||
return drawerBinding.root
|
return drawerBinding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onItemClick(position: Int): Boolean {
|
|
||||||
val context = context ?: return false
|
|
||||||
val list = mList ?: return false
|
|
||||||
if (position < 0 || position >= list.size) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
context.navToReader(list[position].galleryInfo)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private class DownloadLabelHolder(val binding: ItemDrawerListBinding) :
|
private class DownloadLabelHolder(val binding: ItemDrawerListBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root)
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
@@ -666,218 +626,58 @@ class DownloadsScene : SearchBarScene() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
class DownloadHolder(val view: ComposeView) : RecyclerView.ViewHolder(view)
|
||||||
private inner class DownloadHolder(
|
|
||||||
private val binding: ItemDownloadBinding,
|
|
||||||
) : RecyclerView.ViewHolder(binding.root), View.OnClickListener {
|
|
||||||
|
|
||||||
init {
|
|
||||||
// TODO cancel on click listener when select items
|
|
||||||
binding.start.setOnClickListener(this)
|
|
||||||
binding.stop.setOnClickListener(this)
|
|
||||||
binding.thumb.setMD3Content {
|
|
||||||
val height by collectListThumbSizeAsState()
|
|
||||||
Spacer(modifier = Modifier.height(height).fillMaxWidth())
|
|
||||||
}
|
|
||||||
binding.handle.setOnTouchListener { _, event ->
|
|
||||||
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
|
||||||
mItemTouchHelper!!.startDrag(this)
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
|
||||||
val context = context
|
|
||||||
val activity: Activity? = mainActivity
|
|
||||||
val recyclerView = this@DownloadsScene.binding.recyclerView
|
|
||||||
if (null == context || null == activity || tracker.isInCustomChoice) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val list = mList ?: return
|
|
||||||
val size = list.size
|
|
||||||
val index = recyclerView.getChildAdapterPosition(itemView)
|
|
||||||
if (index < 0 || index >= size) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
when (v) {
|
|
||||||
binding.thumb -> {
|
|
||||||
navAnimated(
|
|
||||||
R.id.galleryDetailScene,
|
|
||||||
bundleOf(GalleryDetailScene.KEY_ARGS to GalleryInfoArgs(list[index].galleryInfo)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.start -> {
|
|
||||||
val intent = Intent(activity, DownloadService::class.java)
|
|
||||||
intent.action = DownloadService.ACTION_START
|
|
||||||
intent.putExtra(DownloadService.KEY_GALLERY_INFO, list[index].galleryInfo)
|
|
||||||
ContextCompat.startForegroundService(activity, intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.stop -> {
|
|
||||||
lifecycleScope.launchIO {
|
|
||||||
DownloadManager.stopDownload(list[index].gid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun bind(info: DownloadInfo, isChecked: Boolean) {
|
|
||||||
binding.root.isChecked = isChecked
|
|
||||||
binding.thumb.setMD3Content {
|
|
||||||
val height by collectListThumbSizeAsState()
|
|
||||||
Card(onClick = ::onClick.partially1(binding.thumb)) {
|
|
||||||
CompanionAsyncThumb(
|
|
||||||
info = info,
|
|
||||||
path = info.downloadDir?.subFile(".thumb"),
|
|
||||||
modifier = Modifier.height(height).aspectRatio(DEFAULT_ASPECT),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
binding.title.text = EhUtils.getSuitableTitle(info)
|
|
||||||
binding.uploader.text = info.uploader
|
|
||||||
binding.rating.rating = info.rating
|
|
||||||
binding.category.setMD3Content {
|
|
||||||
val categoryColor = EhUtils.getCategoryColor(info.category)
|
|
||||||
val categoryText = EhUtils.getCategory(info.category).uppercase()
|
|
||||||
Text(
|
|
||||||
text = categoryText,
|
|
||||||
modifier = Modifier.clip(ShapeDefaults.Small).background(categoryColor).padding(vertical = 2.dp, horizontal = 8.dp),
|
|
||||||
color = if (Settings.harmonizeCategoryColor) Color.Unspecified else EhUtils.categoryTextColor,
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
style = MaterialTheme.typography.labelLarge,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
bindForState(info)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun bindForState(info: DownloadInfo) {
|
|
||||||
val context = context ?: return
|
|
||||||
when (info.state) {
|
|
||||||
DownloadInfo.STATE_NONE -> bindState(
|
|
||||||
info,
|
|
||||||
context.getString(R.string.download_state_none),
|
|
||||||
)
|
|
||||||
|
|
||||||
DownloadInfo.STATE_WAIT -> bindState(
|
|
||||||
info,
|
|
||||||
context.getString(R.string.download_state_wait),
|
|
||||||
)
|
|
||||||
|
|
||||||
DownloadInfo.STATE_DOWNLOAD -> bindProgress(info)
|
|
||||||
DownloadInfo.STATE_FAILED -> {
|
|
||||||
val text: String = if (info.legacy <= 0) {
|
|
||||||
context.getString(R.string.download_state_failed)
|
|
||||||
} else {
|
|
||||||
context.getString(R.string.download_state_failed_2, info.legacy)
|
|
||||||
}
|
|
||||||
bindState(info, text)
|
|
||||||
}
|
|
||||||
|
|
||||||
DownloadInfo.STATE_FINISH -> bindState(
|
|
||||||
info,
|
|
||||||
context.getString(R.string.download_state_finish),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun bindState(info: DownloadInfo, newState: String) {
|
|
||||||
binding.run {
|
|
||||||
uploader.visibility = View.VISIBLE
|
|
||||||
rating.visibility = View.VISIBLE
|
|
||||||
category.visibility = View.VISIBLE
|
|
||||||
state.visibility = View.VISIBLE
|
|
||||||
progressBar.visibility = View.GONE
|
|
||||||
percent.visibility = View.GONE
|
|
||||||
speed.visibility = View.GONE
|
|
||||||
if (info.state == DownloadInfo.STATE_WAIT || info.state == DownloadInfo.STATE_DOWNLOAD) {
|
|
||||||
start.visibility = View.GONE
|
|
||||||
stop.visibility = View.VISIBLE
|
|
||||||
} else {
|
|
||||||
start.visibility = View.VISIBLE
|
|
||||||
stop.visibility = View.GONE
|
|
||||||
}
|
|
||||||
state.text = newState
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
|
||||||
private fun bindProgress(info: DownloadInfo) {
|
|
||||||
binding.run {
|
|
||||||
uploader.visibility = View.GONE
|
|
||||||
rating.visibility = View.GONE
|
|
||||||
category.visibility = View.GONE
|
|
||||||
state.visibility = View.GONE
|
|
||||||
progressBar.visibility = View.VISIBLE
|
|
||||||
percent.visibility = View.VISIBLE
|
|
||||||
speed.visibility = View.VISIBLE
|
|
||||||
if (info.state == DownloadInfo.STATE_WAIT || info.state == DownloadInfo.STATE_DOWNLOAD) {
|
|
||||||
start.visibility = View.GONE
|
|
||||||
stop.visibility = View.VISIBLE
|
|
||||||
} else {
|
|
||||||
start.visibility = View.VISIBLE
|
|
||||||
stop.visibility = View.GONE
|
|
||||||
}
|
|
||||||
if (info.total <= 0 || info.finished < 0) {
|
|
||||||
percent.text = null
|
|
||||||
progressBar.isIndeterminate = true
|
|
||||||
} else {
|
|
||||||
percent.text = info.finished.toString() + "/" + info.total
|
|
||||||
progressBar.isIndeterminate = false
|
|
||||||
progressBar.max = info.total
|
|
||||||
progressBar.progress = info.finished
|
|
||||||
}
|
|
||||||
speed.text =
|
|
||||||
FileUtils.humanReadableByteCount(info.speed.coerceAtLeast(0), false) + "/S"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val diffCallback = object : DiffUtil.ItemCallback<DownloadInfo>() {
|
private val diffCallback = object : DiffUtil.ItemCallback<DownloadInfo>() {
|
||||||
override fun areItemsTheSame(oldItem: DownloadInfo, newItem: DownloadInfo) = oldItem.gid == newItem.gid
|
override fun areItemsTheSame(oldItem: DownloadInfo, newItem: DownloadInfo) = oldItem.gid == newItem.gid
|
||||||
override fun areContentsTheSame(oldItem: DownloadInfo, newItem: DownloadInfo) = oldItem == newItem
|
override fun areContentsTheSame(oldItem: DownloadInfo, newItem: DownloadInfo) = oldItem.gid == newItem.gid
|
||||||
override fun getChangePayload(oldItem: DownloadInfo, newItem: DownloadInfo): Any? {
|
|
||||||
return if (oldItem.downloadInfo != newItem.downloadInfo) {
|
|
||||||
PAYLOAD_DOWNLOAD_INFO
|
|
||||||
} else {
|
|
||||||
super.getChangePayload(oldItem, newItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class DownloadAdapter : ListAdapter<DownloadInfo, DownloadHolder>(diffCallback) {
|
private inner class DownloadAdapter : ListAdapter<DownloadInfo, DownloadHolder>(diffCallback) {
|
||||||
private val mInflater: LayoutInflater = layoutInflater
|
|
||||||
var tracker: GallerySelectionTracker<DownloadInfo>? = null
|
var tracker: GallerySelectionTracker<DownloadInfo>? = null
|
||||||
|
|
||||||
override fun getItemId(position: Int) = getItem(position).gid
|
override fun getItemId(position: Int) = getItem(position).gid
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = DownloadHolder(ComposeView(parent.context))
|
||||||
val holder = DownloadHolder(ItemDownloadBinding.inflate(mInflater, parent, false))
|
|
||||||
holder.itemView.setOnClickListener { onItemClick(holder.bindingAdapterPosition) }
|
|
||||||
return holder
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: DownloadHolder, position: Int) {
|
override fun onBindViewHolder(holder: DownloadHolder, position: Int) {
|
||||||
val info = getItem(position)
|
val info = getItem(position)
|
||||||
holder.bind(info, tracker?.isSelected(info.gid) ?: false)
|
val checked = tracker?.isSelected(info.gid) ?: false
|
||||||
}
|
holder.view.setMD3Content {
|
||||||
|
val context = LocalContext.current
|
||||||
override fun onBindViewHolder(
|
val height by collectListThumbSizeAsState()
|
||||||
holder: DownloadHolder,
|
key(info.gid) {
|
||||||
position: Int,
|
CheckableItem(checked = checked) {
|
||||||
payloads: MutableList<Any>,
|
DownloadCard(
|
||||||
) {
|
onClick = {
|
||||||
payloads.forEach { payload ->
|
context.navToReader(info.galleryInfo)
|
||||||
when (payload) {
|
},
|
||||||
PAYLOAD_DOWNLOAD_INFO -> {
|
onThumbClick = {
|
||||||
val info = getItem(position)
|
navAnimated(
|
||||||
holder.bindForState(info)
|
R.id.galleryDetailScene,
|
||||||
return
|
bundleOf(GalleryDetailScene.KEY_ARGS to GalleryInfoArgs(info.galleryInfo)),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onLongClick = {
|
||||||
|
},
|
||||||
|
onStart = {
|
||||||
|
val intent = Intent(activity, DownloadService::class.java)
|
||||||
|
intent.action = DownloadService.ACTION_START
|
||||||
|
intent.putExtra(DownloadService.KEY_GALLERY_INFO, info.galleryInfo)
|
||||||
|
ContextCompat.startForegroundService(requireActivity(), intent)
|
||||||
|
},
|
||||||
|
onStop = {
|
||||||
|
lifecycleScope.launchIO {
|
||||||
|
DownloadManager.stopDownload(info.gid)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDrag = { mItemTouchHelper?.startDrag(holder) },
|
||||||
|
info = info,
|
||||||
|
modifier = Modifier.height(height),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super.onBindViewHolder(holder, position, payloads)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1021,65 +821,3 @@ class DownloadsScene : SearchBarScene() {
|
|||||||
private const val PAYLOAD_DOWNLOAD_INFO = 0
|
private const val PAYLOAD_DOWNLOAD_INFO = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun CompanionAsyncThumb(
|
|
||||||
info: GalleryInfo,
|
|
||||||
path: UniFile?,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
var contentScale by remember(info.gid) { mutableStateOf(ContentScale.Fit) }
|
|
||||||
val coroutineScope = rememberCoroutineScope { Dispatchers.IO }
|
|
||||||
val context = LocalContext.current
|
|
||||||
var localReq by remember(info.gid) {
|
|
||||||
path?.takeIf { it.isFile }?.uri?.let {
|
|
||||||
context.imageRequest {
|
|
||||||
data(it.toString())
|
|
||||||
memoryCacheKey(info.thumbKey)
|
|
||||||
}
|
|
||||||
}.let { mutableStateOf(it) }
|
|
||||||
}
|
|
||||||
AsyncImage(
|
|
||||||
model = localReq ?: requestOf(info),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = modifier,
|
|
||||||
onState = { state ->
|
|
||||||
if (state is AsyncImagePainter.State.Success) {
|
|
||||||
state.result.drawable.run {
|
|
||||||
if (CropDefaults.shouldCrop(intrinsicWidth, intrinsicHeight)) {
|
|
||||||
contentScale = ContentScale.Crop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
path?.let {
|
|
||||||
coroutineScope.launch {
|
|
||||||
runCatching {
|
|
||||||
if (!path.exists() && path.ensureFile()) {
|
|
||||||
val key = info.thumbKey!!
|
|
||||||
imageCache.read(key) {
|
|
||||||
UniFile.fromFile(data.toFile())!!.openFileDescriptor("r").use { src ->
|
|
||||||
path.openFileDescriptor("w").use { dst ->
|
|
||||||
src sendTo dst
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.onFailure {
|
|
||||||
it.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (state is AsyncImagePainter.State.Error) {
|
|
||||||
path?.let {
|
|
||||||
coroutineScope.launch {
|
|
||||||
if (path.exists()) {
|
|
||||||
path.delete()
|
|
||||||
localReq = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
contentScale = contentScale,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!--
|
|
||||||
~ Copyright 2018 Hippo Seven
|
|
||||||
~
|
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
~ you may not use this file except in compliance with the License.
|
|
||||||
~ You may obtain a copy of the License at
|
|
||||||
~
|
|
||||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
~
|
|
||||||
~ Unless required by applicable law or agreed to in writing, software
|
|
||||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
~ See the License for the specific language governing permissions and
|
|
||||||
~ limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="120dp"
|
|
||||||
android:height="120dp"
|
|
||||||
android:tint="?colorTertiary"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M16.5,10.8c-0.61,0-1.1,0.49-1.1,1.1c0,0.61,0.49,1.1,1.1,1.1s1.1-0.49,1.1-1.1C17.6,11.29,17.11,10.8,16.5,10.8z" />
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M8.2,10.1c-0.61,0-1.1,0.49-1.1,1.1s0.49,1.1,1.1,1.1s1.1-0.49,1.1-1.1S8.81,10.1,8.2,10.1z" />
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M22.1,16.4c-0.46-3.33-2.24-5.93-4.81-7.45L19.5,6.3c0.1-0.1,0.1-0.3,0-0.4c-0.1-0.1-0.3-0.1-0.4,0l-2.27,2.79c-1.26-0.66-2.69-1.07-4.23-1.19c-1.4-0.15-2.77-0.01-4.05,0.37L6.7,4.7C6.6,4.6,6.4,4.5,6.3,4.6C6.2,4.7,6.1,4.9,6.2,5l1.79,3.05c-2.83,1.02-5.12,3.3-6.19,6.55l15.44,1.37c0,0.02-0.01,0.04-0.01,0.05c0,0.59,0.48,1.07,1.07,1.07c0.54,0,0.97-0.41,1.04-0.93L22.1,16.4z M2.63,14.07c1.51-3.74,4.83-6.03,8.84-6.03c0.35,0,0.71,0.02,1.07,0.06c4.55,0.36,7.94,3.33,8.84,7.64l-2.12-0.19c-0.27-0.68-0.95-1.46-0.95-1.46s-0.58,0.66-0.89,1.29L2.63,14.07z" />
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!--
|
|
||||||
~ Copyright 2016 Hippo Seven
|
|
||||||
~
|
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
~ you may not use this file except in compliance with the License.
|
|
||||||
~ You may obtain a copy of the License at
|
|
||||||
~
|
|
||||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
~
|
|
||||||
~ Unless required by applicable law or agreed to in writing, software
|
|
||||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
~ See the License for the specific language governing permissions and
|
|
||||||
~ limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="@string/pd_help_circle" />
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!--
|
|
||||||
~ Copyright 2016 Hippo Seven
|
|
||||||
~
|
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
~ you may not use this file except in compliance with the License.
|
|
||||||
~ You may obtain a copy of the License at
|
|
||||||
~
|
|
||||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
~
|
|
||||||
~ Unless required by applicable law or agreed to in writing, software
|
|
||||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
~ See the License for the specific language governing permissions and
|
|
||||||
~ limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="@string/pd_play" />
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!--
|
|
||||||
~ Copyright 2016 Hippo Seven
|
|
||||||
~
|
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
~ you may not use this file except in compliance with the License.
|
|
||||||
~ You may obtain a copy of the License at
|
|
||||||
~
|
|
||||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
~
|
|
||||||
~ Unless required by applicable law or agreed to in writing, software
|
|
||||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
~ See the License for the specific language governing permissions and
|
|
||||||
~ limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
style="@style/Widget.Material3.CardView.Outlined"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:checkable="true"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
app:strokeColor="@android:color/transparent">
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<androidx.compose.ui.platform.ComposeView
|
|
||||||
android:id="@+id/thumb"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/title"
|
|
||||||
style="@style/CardTitle"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:layout_marginLeft="8dp"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:layout_marginRight="8dp"
|
|
||||||
android:layout_toEndOf="@id/thumb" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/uploader"
|
|
||||||
style="@style/CardMessage"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_above="@id/rating"
|
|
||||||
android:layout_alignStart="@id/title"
|
|
||||||
android:layout_marginTop="2dp" />
|
|
||||||
|
|
||||||
<com.hippo.ehviewer.ui.legacy.SimpleRatingView
|
|
||||||
android:id="@+id/rating"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_above="@id/category"
|
|
||||||
android:layout_alignStart="@id/title"
|
|
||||||
android:layout_marginBottom="2dp" />
|
|
||||||
|
|
||||||
<androidx.compose.ui.platform.ComposeView
|
|
||||||
android:id="@+id/category"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignStart="@id/title"
|
|
||||||
android:layout_alignBottom="@id/thumb"
|
|
||||||
android:layout_marginBottom="8dp" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/handle"
|
|
||||||
android:layout_width="40dp"
|
|
||||||
android:layout_height="40dp"
|
|
||||||
android:layout_alignBottom="@+id/thumb"
|
|
||||||
android:layout_toStartOf="@+id/actions"
|
|
||||||
android:background="?selectableItemBackgroundBorderless"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:padding="8dp"
|
|
||||||
app:srcCompat="@drawable/ic_baseline_reorder_24" />
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/actions"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignBottom="@+id/thumb"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:paddingEnd="8dp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/start"
|
|
||||||
android:layout_width="40dp"
|
|
||||||
android:layout_height="40dp"
|
|
||||||
android:background="?selectableItemBackgroundBorderless"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:padding="8dp"
|
|
||||||
app:srcCompat="@drawable/v_play_x24" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/stop"
|
|
||||||
android:layout_width="40dp"
|
|
||||||
android:layout_height="40dp"
|
|
||||||
android:background="?selectableItemBackgroundBorderless"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:padding="8dp"
|
|
||||||
app:srcCompat="@drawable/v_pause_x24" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/state"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_above="@id/actions"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:paddingEnd="16dp" />
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/progress_bar"
|
|
||||||
style="?android:attr/progressBarStyleHorizontal"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_above="@id/actions"
|
|
||||||
android:layout_alignStart="@id/title"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:indeterminate="false" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/percent"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_above="@id/progress_bar"
|
|
||||||
android:layout_alignStart="@id/title"
|
|
||||||
android:textSize="@dimen/text_super_small" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/speed"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_above="@id/progress_bar"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:textSize="@dimen/text_super_small" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!--
|
|
||||||
~ Copyright 2016 Hippo Seven
|
|
||||||
~
|
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
~ you may not use this file except in compliance with the License.
|
|
||||||
~ You may obtain a copy of the License at
|
|
||||||
~
|
|
||||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
~
|
|
||||||
~ Unless required by applicable law or agreed to in writing, software
|
|
||||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
~ See the License for the specific language governing permissions and
|
|
||||||
~ limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_help"
|
|
||||||
android:icon="@drawable/v_help_circle_x24"
|
|
||||||
android:title="@string/add"
|
|
||||||
app:showAsAction="always" />
|
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_add"
|
|
||||||
android:icon="@drawable/v_plus_dark_x24"
|
|
||||||
android:title="@string/add"
|
|
||||||
app:showAsAction="always" />
|
|
||||||
|
|
||||||
</menu>
|
|
||||||
@@ -23,6 +23,4 @@
|
|||||||
|
|
||||||
<dimen name="gallery_list_margin_h">20dp</dimen>
|
<dimen name="gallery_list_margin_h">20dp</dimen>
|
||||||
|
|
||||||
<dimen name="search_layout_margin_h">20dp</dimen>
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -18,6 +18,4 @@
|
|||||||
|
|
||||||
<dimen name="gallery_list_margin_h">76dp</dimen>
|
<dimen name="gallery_list_margin_h">76dp</dimen>
|
||||||
|
|
||||||
<dimen name="search_layout_margin_h">76dp</dimen>
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -27,7 +27,6 @@
|
|||||||
|
|
||||||
<!-- Text size -->
|
<!-- Text size -->
|
||||||
<dimen name="text_little_small">16sp</dimen>
|
<dimen name="text_little_small">16sp</dimen>
|
||||||
<dimen name="text_super_small">12sp</dimen>
|
|
||||||
|
|
||||||
<!-- RatingView -->
|
<!-- RatingView -->
|
||||||
<dimen name="rating_size">16dp</dimen>
|
<dimen name="rating_size">16dp</dimen>
|
||||||
@@ -39,12 +38,9 @@
|
|||||||
|
|
||||||
<dimen name="gallery_grid_interval">4dp</dimen>
|
<dimen name="gallery_grid_interval">4dp</dimen>
|
||||||
|
|
||||||
<dimen name="gallery_search_bar_margin_v">10dp</dimen>
|
|
||||||
|
|
||||||
<dimen name="gallery_list_column_width_long">480dp</dimen>
|
<dimen name="gallery_list_column_width_long">480dp</dimen>
|
||||||
<dimen name="gallery_list_column_width_short">320dp</dimen>
|
<dimen name="gallery_list_column_width_short">320dp</dimen>
|
||||||
|
|
||||||
<dimen name="search_layout_margin_h">4dp</dimen>
|
|
||||||
<dimen name="search_layout_margin_v">4dp</dimen>
|
<dimen name="search_layout_margin_v">4dp</dimen>
|
||||||
|
|
||||||
<!-- Search item -->
|
<!-- Search item -->
|
||||||
|
|||||||
@@ -17,9 +17,7 @@
|
|||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<string name="pd_pause" translatable="false">M14,19.14H18V5.14H14M6,19.14H10V5.14H6V19.14Z</string>
|
<string name="pd_pause" translatable="false">M14,19.14H18V5.14H14M6,19.14H10V5.14H6V19.14Z</string>
|
||||||
<string name="pd_play" translatable="false">M8,5.14V19.14L19,12.14L8,5.14Z</string>
|
|
||||||
<string name="pd_plus" translatable="false">M21,13H13V21H11V13H3V11H11V3H13V11H21V13Z</string>
|
<string name="pd_plus" translatable="false">M21,13H13V21H11V13H3V11H11V3H13V11H21V13Z</string>
|
||||||
<string name="pd_help_circle" translatable="false">M15.07,11.25L14.17,12.17C13.45,12.89 13,13.5 13,15H11V14.5C11,13.39 11.45,12.39 12.17,11.67L13.41,10.41C13.78,10.05 14,9.55 14,9C14,7.89 13.1,7 12,7A2,2 0 0,0 10,9H8A4,4 0 0,1 12,5A4,4 0 0,1 16,9C16,9.88 15.64,10.67 15.07,11.25M13,19H11V17H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z</string>
|
|
||||||
<string name="pd_download_box" translatable="false">M19,3H5C3.9,3,3,3.9,3,5V19C3,20.1,3.9,21,5,21H19C20.1,21,21,20.1,21,19V5C21,3.9,20.1,3,19,3ZM12,17L7,12H10V8H14V12H17L12,17Z</string>
|
<string name="pd_download_box" translatable="false">M19,3H5C3.9,3,3,3.9,3,5V19C3,20.1,3.9,21,5,21H19C20.1,21,21,20.1,21,19V5C21,3.9,20.1,3,19,3ZM12,17L7,12H10V8H14V12H17L12,17Z</string>
|
||||||
<string name="pd_refresh" translatable="false">M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z</string>
|
<string name="pd_refresh" translatable="false">M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z</string>
|
||||||
<string name="pd_file_image" translatable="false">M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M6,20H15L18,20V12L14,16L12,14L6,20M8,9A2,2 0 0,0 6,11A2,2 0 0,0 8,13A2,2 0 0,0 10,11A2,2 0 0,0 8,9Z</string>
|
<string name="pd_file_image" translatable="false">M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M6,20H15L18,20V12L14,16L12,14L6,20M8,9A2,2 0 0,0 6,11A2,2 0 0,0 8,13A2,2 0 0,0 10,11A2,2 0 0,0 8,9Z</string>
|
||||||
|
|||||||
@@ -29,19 +29,6 @@
|
|||||||
<item name="android:minHeight">48dp</item>
|
<item name="android:minHeight">48dp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="CardTitle">
|
|
||||||
<item name="android:textAppearance">@style/TextAppearance.Material3.TitleSmall</item>
|
|
||||||
<item name="android:ellipsize">end</item>
|
|
||||||
<item name="android:maxLines">2</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="CardMessage">
|
|
||||||
<item name="android:textAppearance">@style/TextAppearance.Material3.LabelLarge</item>
|
|
||||||
<item name="android:ellipsize">end</item>
|
|
||||||
<item name="android:singleLine">true</item>
|
|
||||||
<item name="android:maxLines">1</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="Snackbar" parent="Widget.Material3.Snackbar">
|
<style name="Snackbar" parent="Widget.Material3.Snackbar">
|
||||||
<item name="materialThemeOverlay">@style/SnackbarTheme</item>
|
<item name="materialThemeOverlay">@style/SnackbarTheme</item>
|
||||||
<item name="actionTextColorAlpha">1</item>
|
<item name="actionTextColorAlpha">1</item>
|
||||||
|
|||||||
Reference in new issue
Block a user