Cover's styling: color & loading animation (#42)
* use dominantCoverColors as background for cover so it's less tinted by theme color, also if CoverCache is corrupted, favorite entries can still show color * add loading animation while cover is fetching image * Bulk-selection using default isSelected style * fix cover's background & tint when AsyncImage is Error/Fallback/PlaceHolder * Widget's cover color when unable to load cover image
This commit is contained in:
parent
b6b7114c29
commit
4c713f118f
12 changed files with 290 additions and 31 deletions
|
|
@ -99,10 +99,8 @@ internal fun BrowseSourceComfortableGridItem(
|
||||||
ogUrl = manga.thumbnailUrl,
|
ogUrl = manga.thumbnailUrl,
|
||||||
lastModified = manga.coverLastModified,
|
lastModified = manga.coverLastModified,
|
||||||
),
|
),
|
||||||
|
isSelected = isSelected,
|
||||||
coverAlpha = when {
|
coverAlpha = when {
|
||||||
// KMK -->
|
|
||||||
isSelected -> CommonMangaItemDefaults.BrowseSelectedCoverAlpha
|
|
||||||
// KMK <--
|
|
||||||
manga.favorite -> CommonMangaItemDefaults.BrowseFavoriteCoverAlpha
|
manga.favorite -> CommonMangaItemDefaults.BrowseFavoriteCoverAlpha
|
||||||
else -> 1f
|
else -> 1f
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -99,10 +99,8 @@ internal fun BrowseSourceCompactGridItem(
|
||||||
ogUrl = manga.thumbnailUrl,
|
ogUrl = manga.thumbnailUrl,
|
||||||
lastModified = manga.coverLastModified,
|
lastModified = manga.coverLastModified,
|
||||||
),
|
),
|
||||||
|
isSelected = isSelected,
|
||||||
coverAlpha = when {
|
coverAlpha = when {
|
||||||
// KMK -->
|
|
||||||
isSelected -> CommonMangaItemDefaults.BrowseSelectedCoverAlpha
|
|
||||||
// KMK <--
|
|
||||||
manga.favorite -> CommonMangaItemDefaults.BrowseFavoriteCoverAlpha
|
manga.favorite -> CommonMangaItemDefaults.BrowseFavoriteCoverAlpha
|
||||||
else -> 1f
|
else -> 1f
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -92,10 +92,8 @@ internal fun BrowseSourceListItem(
|
||||||
ogUrl = manga.thumbnailUrl,
|
ogUrl = manga.thumbnailUrl,
|
||||||
lastModified = manga.coverLastModified,
|
lastModified = manga.coverLastModified,
|
||||||
),
|
),
|
||||||
|
isSelected = isSelected,
|
||||||
coverAlpha = when {
|
coverAlpha = when {
|
||||||
// KMK -->
|
|
||||||
isSelected -> CommonMangaItemDefaults.BrowseSelectedCoverAlpha
|
|
||||||
// KMK <--
|
|
||||||
manga.favorite -> CommonMangaItemDefaults.BrowseFavoriteCoverAlpha
|
manga.favorite -> CommonMangaItemDefaults.BrowseFavoriteCoverAlpha
|
||||||
else -> 1f
|
else -> 1f
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -78,10 +78,8 @@ internal fun MangaItem(
|
||||||
coverBadgeStart = {
|
coverBadgeStart = {
|
||||||
InLibraryBadge(enabled = isFavorite)
|
InLibraryBadge(enabled = isFavorite)
|
||||||
},
|
},
|
||||||
|
isSelected = isSelected,
|
||||||
coverAlpha = when {
|
coverAlpha = when {
|
||||||
// KMK -->
|
|
||||||
isSelected -> CommonMangaItemDefaults.BrowseSelectedCoverAlpha
|
|
||||||
// KMK <--
|
|
||||||
isFavorite -> CommonMangaItemDefaults.BrowseFavoriteCoverAlpha
|
isFavorite -> CommonMangaItemDefaults.BrowseFavoriteCoverAlpha
|
||||||
else -> 1f
|
else -> 1f
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -33,25 +33,24 @@ import androidx.compose.ui.draw.drawBehind
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.Shadow
|
import androidx.compose.ui.graphics.Shadow
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import eu.kanade.presentation.manga.components.CoverPlaceholderColor
|
||||||
import eu.kanade.presentation.manga.components.MangaCover
|
import eu.kanade.presentation.manga.components.MangaCover
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.BadgeGroup
|
import tachiyomi.presentation.core.components.BadgeGroup
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.selectedBackground
|
import tachiyomi.presentation.core.util.selectedBackground
|
||||||
|
import tachiyomi.domain.manga.model.MangaCover as DomainMangaCover
|
||||||
|
|
||||||
object CommonMangaItemDefaults {
|
object CommonMangaItemDefaults {
|
||||||
val GridHorizontalSpacer = 4.dp
|
val GridHorizontalSpacer = 4.dp
|
||||||
val GridVerticalSpacer = 4.dp
|
val GridVerticalSpacer = 4.dp
|
||||||
|
|
||||||
const val BrowseFavoriteCoverAlpha = 0.34f
|
const val BrowseFavoriteCoverAlpha = 0.34f
|
||||||
|
|
||||||
// KMK -->
|
|
||||||
const val BrowseSelectedCoverAlpha = 0.17f
|
|
||||||
// KMK <--
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val ContinueReadingButtonSize = 28.dp
|
private val ContinueReadingButtonSize = 28.dp
|
||||||
|
|
@ -66,7 +65,7 @@ private const val GridSelectedCoverAlpha = 0.76f
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaCompactGridItem(
|
fun MangaCompactGridItem(
|
||||||
coverData: tachiyomi.domain.manga.model.MangaCover,
|
coverData: DomainMangaCover,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
isSelected: Boolean = false,
|
isSelected: Boolean = false,
|
||||||
|
|
@ -86,8 +85,14 @@ fun MangaCompactGridItem(
|
||||||
MangaCover.Book(
|
MangaCover.Book(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.alpha(if (isSelected) GridSelectedCoverAlpha else coverAlpha),
|
.background(
|
||||||
|
color = DomainMangaCover.coverColorMap[coverData.mangaId]?.first?.let { Color(it) }
|
||||||
|
?: MaterialTheme.colorScheme.surface
|
||||||
|
),
|
||||||
|
alpha = if (isSelected) GridSelectedCoverAlpha else coverAlpha,
|
||||||
data = coverData,
|
data = coverData,
|
||||||
|
tint = DomainMangaCover.coverColorMap[coverData.mangaId]?.second
|
||||||
|
?: MaterialTheme.colorScheme.onSurface.toArgb(),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
badgesStart = coverBadgeStart,
|
badgesStart = coverBadgeStart,
|
||||||
|
|
@ -167,7 +172,7 @@ private fun BoxScope.CoverTextOverlay(
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaComfortableGridItem(
|
fun MangaComfortableGridItem(
|
||||||
coverData: tachiyomi.domain.manga.model.MangaCover,
|
coverData: DomainMangaCover,
|
||||||
title: String,
|
title: String,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
|
|
@ -189,8 +194,14 @@ fun MangaComfortableGridItem(
|
||||||
MangaCover.Book(
|
MangaCover.Book(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.alpha(if (isSelected) GridSelectedCoverAlpha else coverAlpha),
|
.background(
|
||||||
|
color = DomainMangaCover.coverColorMap[coverData.mangaId]?.first?.let { Color(it) }
|
||||||
|
?: MaterialTheme.colorScheme.surface
|
||||||
|
),
|
||||||
|
alpha = if (isSelected) GridSelectedCoverAlpha else coverAlpha,
|
||||||
data = coverData,
|
data = coverData,
|
||||||
|
tint = DomainMangaCover.coverColorMap[coverData.mangaId]?.second
|
||||||
|
?: MaterialTheme.colorScheme.onSurface.toArgb(),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
badgesStart = coverBadgeStart,
|
badgesStart = coverBadgeStart,
|
||||||
|
|
@ -313,14 +324,14 @@ private fun GridItemSelectable(
|
||||||
private fun Modifier.selectedOutline(
|
private fun Modifier.selectedOutline(
|
||||||
isSelected: Boolean,
|
isSelected: Boolean,
|
||||||
color: Color,
|
color: Color,
|
||||||
) = this then drawBehind { if (isSelected) drawRect(color = color) }
|
) = this.drawBehind { if (isSelected) drawRect(color = color) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Layout of list item.
|
* Layout of list item.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaListItem(
|
fun MangaListItem(
|
||||||
coverData: tachiyomi.domain.manga.model.MangaCover,
|
coverData: DomainMangaCover,
|
||||||
title: String,
|
title: String,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
|
|
@ -343,8 +354,14 @@ fun MangaListItem(
|
||||||
MangaCover.Square(
|
MangaCover.Square(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
.alpha(coverAlpha),
|
.background(
|
||||||
|
color = DomainMangaCover.coverColorMap[coverData.mangaId]?.first?.let { Color(it) }
|
||||||
|
?: MaterialTheme.colorScheme.surface
|
||||||
|
),
|
||||||
|
alpha = coverAlpha,
|
||||||
data = coverData,
|
data = coverData,
|
||||||
|
tint = DomainMangaCover.coverColorMap[coverData.mangaId]?.second
|
||||||
|
?: MaterialTheme.colorScheme.onSurface.toArgb(),
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,28 @@
|
||||||
package eu.kanade.presentation.manga.components
|
package eu.kanade.presentation.manga.components
|
||||||
|
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
|
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||||
|
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
||||||
|
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
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.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.Shape
|
import androidx.compose.ui.graphics.Shape
|
||||||
import androidx.compose.ui.graphics.painter.ColorPainter
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import eu.kanade.presentation.util.rememberResourceBitmapPainter
|
import eu.kanade.presentation.util.rememberResourceBitmapPainter
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
enum class MangaCover(val ratio: Float) {
|
enum class MangaCover(val ratio: Float) {
|
||||||
Square(1f / 1f),
|
Square(1f / 1f),
|
||||||
|
|
@ -27,15 +36,36 @@ enum class MangaCover(val ratio: Float) {
|
||||||
contentDescription: String = "",
|
contentDescription: String = "",
|
||||||
shape: Shape = MaterialTheme.shapes.extraSmall,
|
shape: Shape = MaterialTheme.shapes.extraSmall,
|
||||||
onClick: (() -> Unit)? = null,
|
onClick: (() -> Unit)? = null,
|
||||||
|
@ColorInt tint: Int = CoverPlaceholderColor,
|
||||||
|
alpha: Float = 1f,
|
||||||
) {
|
) {
|
||||||
|
val animatedImageVector = AnimatedImageVector.animatedVectorResource(R.drawable.anim_waiting)
|
||||||
|
var atEnd by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
suspend fun runAnimation() {
|
||||||
|
while (true) {
|
||||||
|
delay(LoadingAnimatedDuration)
|
||||||
|
atEnd = !atEnd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(animatedImageVector) {
|
||||||
|
runAnimation()
|
||||||
|
}
|
||||||
|
|
||||||
|
var succeed by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = data,
|
model = data,
|
||||||
placeholder = ColorPainter(CoverPlaceholderColor),
|
placeholder = rememberAnimatedVectorPainter(animatedImageVector = animatedImageVector, atEnd = atEnd),
|
||||||
error = rememberResourceBitmapPainter(id = R.drawable.cover_error),
|
error = rememberResourceBitmapPainter(id = R.drawable.cover_error, tint),
|
||||||
|
fallback = rememberResourceBitmapPainter(id = R.drawable.cover_error, tint),
|
||||||
|
onSuccess = { succeed = true },
|
||||||
contentDescription = contentDescription,
|
contentDescription = contentDescription,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.aspectRatio(ratio)
|
.aspectRatio(ratio)
|
||||||
.clip(shape)
|
.clip(shape)
|
||||||
|
.alpha(if (succeed) alpha else 1f)
|
||||||
.then(
|
.then(
|
||||||
if (onClick != null) {
|
if (onClick != null) {
|
||||||
Modifier.clickable(
|
Modifier.clickable(
|
||||||
|
|
@ -51,4 +81,6 @@ enum class MangaCover(val ratio: Float) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val CoverPlaceholderColor = Color(0x1F888888)
|
@ColorInt
|
||||||
|
internal val CoverPlaceholderColor = 0x7F888888
|
||||||
|
const val LoadingAnimatedDuration = 600L
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package eu.kanade.presentation.util
|
package eu.kanade.presentation.util
|
||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
|
@ -19,11 +20,12 @@ import androidx.core.graphics.drawable.toBitmap
|
||||||
* @return the bitmap associated with the resource
|
* @return the bitmap associated with the resource
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun rememberResourceBitmapPainter(@DrawableRes id: Int): BitmapPainter {
|
fun rememberResourceBitmapPainter(@DrawableRes id: Int, @ColorInt tint: Int? = null): BitmapPainter {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
return remember(id) {
|
return remember(id) {
|
||||||
val drawable = ContextCompat.getDrawable(context, id)
|
val drawable = ContextCompat.getDrawable(context, id)
|
||||||
?: throw Resources.NotFoundException()
|
?: throw Resources.NotFoundException()
|
||||||
|
tint?.let { drawable.setTint(it) }
|
||||||
BitmapPainter(drawable.toBitmap().asImageBitmap())
|
BitmapPainter(drawable.toBitmap().asImageBitmap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
209
app/src/main/res/drawable/anim_waiting.xml
Normal file
209
app/src/main/res/drawable/anim_waiting.xml
Normal file
|
|
@ -0,0 +1,209 @@
|
||||||
|
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt">
|
||||||
|
<aapt:attr name="android:drawable">
|
||||||
|
<vector
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="4320"
|
||||||
|
android:viewportHeight="4320">
|
||||||
|
<group android:name="_SQ">
|
||||||
|
<path
|
||||||
|
android:fillAlpha="0.1"
|
||||||
|
android:fillColor="@color/cover_placeholder_loading"
|
||||||
|
android:pathData="M0,0h4320v4320H0z" />
|
||||||
|
</group>
|
||||||
|
<group android:name="_R_G"
|
||||||
|
android:translateX="1620"
|
||||||
|
android:translateY="1620">
|
||||||
|
<group android:name="_R_G_L_2_G">
|
||||||
|
<path
|
||||||
|
android:name="_R_G_L_2_G_D_0_P_0"
|
||||||
|
android:fillAlpha="1"
|
||||||
|
android:fillColor="@color/cover_placeholder_loading"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData=" M270.46 450.51 C220.99,450.51 180.52,490.99 180.52,540.46 C180.52,589.93 220.99,630.41 270.46,630.41 C319.93,630.41 360.41,589.93 360.41,540.46 C360.41,490.99 319.93,450.51 270.46,450.51c " />
|
||||||
|
</group>
|
||||||
|
<group android:name="_R_G_L_1_G">
|
||||||
|
<path
|
||||||
|
android:name="_R_G_L_1_G_D_0_P_0"
|
||||||
|
android:fillAlpha="1"
|
||||||
|
android:fillColor="@color/cover_placeholder_loading"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData=" M540.31 450.51 C490.84,450.51 450.36,490.99 450.36,540.46 C450.36,589.93 490.84,630.41 540.31,630.41 C589.78,630.41 630.26,589.93 630.26,540.46 C630.26,490.99 589.78,450.51 540.31,450.51c " />
|
||||||
|
</group>
|
||||||
|
<group android:name="_R_G_L_0_G">
|
||||||
|
<path
|
||||||
|
android:name="_R_G_L_0_G_D_0_P_0"
|
||||||
|
android:fillAlpha="1"
|
||||||
|
android:fillColor="@color/cover_placeholder_loading"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData=" M810.16 450.51 C760.68,450.51 720.21,490.99 720.21,540.46 C720.21,589.93 760.68,630.41 810.16,630.41 C859.63,630.41 900.1,589.93 900.1,540.46 C900.1,490.99 859.63,450.51 810.16,450.51c " />
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group android:name="time_group" />
|
||||||
|
</vector>
|
||||||
|
</aapt:attr>
|
||||||
|
<target android:name="_R_G_L_2_G_D_0_P_0">
|
||||||
|
<aapt:attr name="android:animation">
|
||||||
|
<set android:ordering="together">
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="83"
|
||||||
|
android:propertyName="pathData"
|
||||||
|
android:startOffset="0"
|
||||||
|
android:valueFrom="M270.46 450.51 C220.99,450.51 180.52,490.99 180.52,540.46 C180.52,589.93 220.99,630.41 270.46,630.41 C319.93,630.41 360.41,589.93 360.41,540.46 C360.41,490.99 319.93,450.51 270.46,450.51c "
|
||||||
|
android:valueTo="M270.46 450.51 C220.99,450.51 180.52,490.99 180.52,540.46 C180.52,589.93 220.99,630.41 270.46,630.41 C319.93,630.41 360.41,589.93 360.41,540.46 C360.41,490.99 319.93,450.51 270.46,450.51c "
|
||||||
|
android:valueType="pathType">
|
||||||
|
<aapt:attr name="android:interpolator">
|
||||||
|
<pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
|
||||||
|
</aapt:attr>
|
||||||
|
</objectAnimator>
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="100"
|
||||||
|
android:propertyName="pathData"
|
||||||
|
android:startOffset="83"
|
||||||
|
android:valueFrom="M270.46 450.51 C220.99,450.51 180.52,490.99 180.52,540.46 C180.52,589.93 220.99,630.41 270.46,630.41 C319.93,630.41 360.41,589.93 360.41,540.46 C360.41,490.99 319.93,450.51 270.46,450.51c "
|
||||||
|
android:valueTo="M272.31 322.51 C222.84,322.51 182.36,362.99 182.36,412.46 C182.36,461.93 222.84,502.41 272.31,502.41 C321.78,502.41 362.26,461.93 362.26,412.46 C362.26,362.99 321.78,322.51 272.31,322.51c "
|
||||||
|
android:valueType="pathType">
|
||||||
|
<aapt:attr name="android:interpolator">
|
||||||
|
<pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
|
||||||
|
</aapt:attr>
|
||||||
|
</objectAnimator>
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="133"
|
||||||
|
android:propertyName="pathData"
|
||||||
|
android:startOffset="183"
|
||||||
|
android:valueFrom="M272.31 322.51 C222.84,322.51 182.36,362.99 182.36,412.46 C182.36,461.93 222.84,502.41 272.31,502.41 C321.78,502.41 362.26,461.93 362.26,412.46 C362.26,362.99 321.78,322.51 272.31,322.51c "
|
||||||
|
android:valueTo="M270.46 486.51 C220.99,486.51 180.52,526.99 180.52,576.46 C180.52,625.93 220.99,666.41 270.46,666.41 C319.93,666.41 360.41,625.93 360.41,576.46 C360.41,526.99 319.93,486.51 270.46,486.51c "
|
||||||
|
android:valueType="pathType">
|
||||||
|
<aapt:attr name="android:interpolator">
|
||||||
|
<pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
|
||||||
|
</aapt:attr>
|
||||||
|
</objectAnimator>
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="67"
|
||||||
|
android:propertyName="pathData"
|
||||||
|
android:startOffset="317"
|
||||||
|
android:valueFrom="M270.46 486.51 C220.99,486.51 180.52,526.99 180.52,576.46 C180.52,625.93 220.99,666.41 270.46,666.41 C319.93,666.41 360.41,625.93 360.41,576.46 C360.41,526.99 319.93,486.51 270.46,486.51c "
|
||||||
|
android:valueTo="M270.46 450.51 C220.99,450.51 180.52,490.99 180.52,540.46 C180.52,589.93 220.99,630.41 270.46,630.41 C319.93,630.41 360.41,589.93 360.41,540.46 C360.41,490.99 319.93,450.51 270.46,450.51c "
|
||||||
|
android:valueType="pathType">
|
||||||
|
<aapt:attr name="android:interpolator">
|
||||||
|
<pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
|
||||||
|
</aapt:attr>
|
||||||
|
</objectAnimator>
|
||||||
|
</set>
|
||||||
|
</aapt:attr>
|
||||||
|
</target>
|
||||||
|
<target android:name="_R_G_L_1_G_D_0_P_0">
|
||||||
|
<aapt:attr name="android:animation">
|
||||||
|
<set android:ordering="together">
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="150"
|
||||||
|
android:propertyName="pathData"
|
||||||
|
android:startOffset="0"
|
||||||
|
android:valueFrom="M540.31 450.51 C490.84,450.51 450.36,490.99 450.36,540.46 C450.36,589.93 490.84,630.41 540.31,630.41 C589.78,630.41 630.26,589.93 630.26,540.46 C630.26,490.99 589.78,450.51 540.31,450.51c "
|
||||||
|
android:valueTo="M540.31 450.51 C490.84,450.51 450.36,490.99 450.36,540.46 C450.36,589.93 490.84,630.41 540.31,630.41 C589.78,630.41 630.26,589.93 630.26,540.46 C630.26,490.99 589.78,450.51 540.31,450.51c "
|
||||||
|
android:valueType="pathType">
|
||||||
|
<aapt:attr name="android:interpolator">
|
||||||
|
<pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
|
||||||
|
</aapt:attr>
|
||||||
|
</objectAnimator>
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="100"
|
||||||
|
android:propertyName="pathData"
|
||||||
|
android:startOffset="150"
|
||||||
|
android:valueFrom="M540.31 450.51 C490.84,450.51 450.36,490.99 450.36,540.46 C450.36,589.93 490.84,630.41 540.31,630.41 C589.78,630.41 630.26,589.93 630.26,540.46 C630.26,490.99 589.78,450.51 540.31,450.51c "
|
||||||
|
android:valueTo="M542.16 322.51 C492.68,322.51 452.21,362.99 452.21,412.46 C452.21,461.93 492.68,502.41 542.16,502.41 C591.63,502.41 632.1,461.93 632.1,412.46 C632.1,362.99 591.63,322.51 542.16,322.51c "
|
||||||
|
android:valueType="pathType">
|
||||||
|
<aapt:attr name="android:interpolator">
|
||||||
|
<pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
|
||||||
|
</aapt:attr>
|
||||||
|
</objectAnimator>
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="133"
|
||||||
|
android:propertyName="pathData"
|
||||||
|
android:startOffset="250"
|
||||||
|
android:valueFrom="M542.16 322.51 C492.68,322.51 452.21,362.99 452.21,412.46 C452.21,461.93 492.68,502.41 542.16,502.41 C591.63,502.41 632.1,461.93 632.1,412.46 C632.1,362.99 591.63,322.51 542.16,322.51c "
|
||||||
|
android:valueTo="M540.31 486.51 C490.84,486.51 450.36,526.99 450.36,576.46 C450.36,625.93 490.84,666.41 540.31,666.41 C589.78,666.41 630.26,625.93 630.26,576.46 C630.26,526.99 589.78,486.51 540.31,486.51c "
|
||||||
|
android:valueType="pathType">
|
||||||
|
<aapt:attr name="android:interpolator">
|
||||||
|
<pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
|
||||||
|
</aapt:attr>
|
||||||
|
</objectAnimator>
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="67"
|
||||||
|
android:propertyName="pathData"
|
||||||
|
android:startOffset="383"
|
||||||
|
android:valueFrom="M540.31 486.51 C490.84,486.51 450.36,526.99 450.36,576.46 C450.36,625.93 490.84,666.41 540.31,666.41 C589.78,666.41 630.26,625.93 630.26,576.46 C630.26,526.99 589.78,486.51 540.31,486.51c "
|
||||||
|
android:valueTo="M540.31 450.51 C490.84,450.51 450.36,490.99 450.36,540.46 C450.36,589.93 490.84,630.41 540.31,630.41 C589.78,630.41 630.26,589.93 630.26,540.46 C630.26,490.99 589.78,450.51 540.31,450.51c "
|
||||||
|
android:valueType="pathType">
|
||||||
|
<aapt:attr name="android:interpolator">
|
||||||
|
<pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
|
||||||
|
</aapt:attr>
|
||||||
|
</objectAnimator>
|
||||||
|
</set>
|
||||||
|
</aapt:attr>
|
||||||
|
</target>
|
||||||
|
<target android:name="_R_G_L_0_G_D_0_P_0">
|
||||||
|
<aapt:attr name="android:animation">
|
||||||
|
<set android:ordering="together">
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="217"
|
||||||
|
android:propertyName="pathData"
|
||||||
|
android:startOffset="0"
|
||||||
|
android:valueFrom="M810.16 450.51 C760.68,450.51 720.21,490.99 720.21,540.46 C720.21,589.93 760.68,630.41 810.16,630.41 C859.63,630.41 900.1,589.93 900.1,540.46 C900.1,490.99 859.63,450.51 810.16,450.51c "
|
||||||
|
android:valueTo="M810.16 450.51 C760.68,450.51 720.21,490.99 720.21,540.46 C720.21,589.93 760.68,630.41 810.16,630.41 C859.63,630.41 900.1,589.93 900.1,540.46 C900.1,490.99 859.63,450.51 810.16,450.51c "
|
||||||
|
android:valueType="pathType">
|
||||||
|
<aapt:attr name="android:interpolator">
|
||||||
|
<pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
|
||||||
|
</aapt:attr>
|
||||||
|
</objectAnimator>
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="100"
|
||||||
|
android:propertyName="pathData"
|
||||||
|
android:startOffset="217"
|
||||||
|
android:valueFrom="M810.16 450.51 C760.68,450.51 720.21,490.99 720.21,540.46 C720.21,589.93 760.68,630.41 810.16,630.41 C859.63,630.41 900.1,589.93 900.1,540.46 C900.1,490.99 859.63,450.51 810.16,450.51c "
|
||||||
|
android:valueTo="M812 322.51 C762.53,322.51 722.05,362.99 722.05,412.46 C722.05,461.93 762.53,502.41 812,502.41 C861.47,502.41 901.95,461.93 901.95,412.46 C901.95,362.99 861.47,322.51 812,322.51c "
|
||||||
|
android:valueType="pathType">
|
||||||
|
<aapt:attr name="android:interpolator">
|
||||||
|
<pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
|
||||||
|
</aapt:attr>
|
||||||
|
</objectAnimator>
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="133"
|
||||||
|
android:propertyName="pathData"
|
||||||
|
android:startOffset="317"
|
||||||
|
android:valueFrom="M812 322.51 C762.53,322.51 722.05,362.99 722.05,412.46 C722.05,461.93 762.53,502.41 812,502.41 C861.47,502.41 901.95,461.93 901.95,412.46 C901.95,362.99 861.47,322.51 812,322.51c "
|
||||||
|
android:valueTo="M810.16 486.51 C760.68,486.51 720.21,526.99 720.21,576.46 C720.21,625.93 760.68,666.41 810.16,666.41 C859.63,666.41 900.1,625.93 900.1,576.46 C900.1,526.99 859.63,486.51 810.16,486.51c "
|
||||||
|
android:valueType="pathType">
|
||||||
|
<aapt:attr name="android:interpolator">
|
||||||
|
<pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
|
||||||
|
</aapt:attr>
|
||||||
|
</objectAnimator>
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="67"
|
||||||
|
android:propertyName="pathData"
|
||||||
|
android:startOffset="450"
|
||||||
|
android:valueFrom="M810.16 486.51 C760.68,486.51 720.21,526.99 720.21,576.46 C720.21,625.93 760.68,666.41 810.16,666.41 C859.63,666.41 900.1,625.93 900.1,576.46 C900.1,526.99 859.63,486.51 810.16,486.51c "
|
||||||
|
android:valueTo="M810.16 450.51 C760.68,450.51 720.21,490.99 720.21,540.46 C720.21,589.93 760.68,630.41 810.16,630.41 C859.63,630.41 900.1,589.93 900.1,540.46 C900.1,490.99 859.63,450.51 810.16,450.51c "
|
||||||
|
android:valueType="pathType">
|
||||||
|
<aapt:attr name="android:interpolator">
|
||||||
|
<pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
|
||||||
|
</aapt:attr>
|
||||||
|
</objectAnimator>
|
||||||
|
</set>
|
||||||
|
</aapt:attr>
|
||||||
|
</target>
|
||||||
|
<target android:name="time_group">
|
||||||
|
<aapt:attr name="android:animation">
|
||||||
|
<set android:ordering="together">
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="518"
|
||||||
|
android:propertyName="translateX"
|
||||||
|
android:startOffset="0"
|
||||||
|
android:valueFrom="0"
|
||||||
|
android:valueTo="1"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
</set>
|
||||||
|
</aapt:attr>
|
||||||
|
</target>
|
||||||
|
</animated-vector>
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!--suppress AndroidElementNotAllowed -->
|
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:shape="rectangle">
|
android:shape="rectangle">
|
||||||
<item>
|
<item>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@
|
||||||
<resources>
|
<resources>
|
||||||
<color name="splash">#EAEDE3</color>
|
<color name="splash">#EAEDE3</color>
|
||||||
|
|
||||||
<color name="cover_placeholder">#1F888888</color>
|
<color name="cover_placeholder">#2F888888</color>
|
||||||
|
<color name="cover_placeholder_loading">#888888</color>
|
||||||
|
|
||||||
<color name="error">#BA1B1B</color>
|
<color name="error">#BA1B1B</color>
|
||||||
<color name="errorContainer">#FFDAD4</color>
|
<color name="errorContainer">#FFDAD4</color>
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,12 @@ package tachiyomi.presentation.widget.components
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.glance.GlanceModifier
|
import androidx.glance.GlanceModifier
|
||||||
import androidx.glance.Image
|
import androidx.glance.Image
|
||||||
import androidx.glance.ImageProvider
|
import androidx.glance.ImageProvider
|
||||||
|
import androidx.glance.background
|
||||||
import androidx.glance.layout.Box
|
import androidx.glance.layout.Box
|
||||||
import androidx.glance.layout.ContentScale
|
import androidx.glance.layout.ContentScale
|
||||||
import androidx.glance.layout.fillMaxSize
|
import androidx.glance.layout.fillMaxSize
|
||||||
|
|
@ -20,10 +22,12 @@ val CoverHeight = 87.dp
|
||||||
fun UpdatesMangaCover(
|
fun UpdatesMangaCover(
|
||||||
cover: Bitmap?,
|
cover: Bitmap?,
|
||||||
modifier: GlanceModifier = GlanceModifier,
|
modifier: GlanceModifier = GlanceModifier,
|
||||||
|
color: Color? = null,
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.size(width = CoverWidth, height = CoverHeight)
|
.size(width = CoverWidth, height = CoverHeight)
|
||||||
|
.background(color ?: Color.Unspecified)
|
||||||
.appWidgetInnerRadius(),
|
.appWidgetInnerRadius(),
|
||||||
) {
|
) {
|
||||||
if (cover != null) {
|
if (cover != null) {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package tachiyomi.presentation.widget.components
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.glance.GlanceModifier
|
import androidx.glance.GlanceModifier
|
||||||
|
|
@ -23,6 +24,7 @@ import androidx.glance.text.TextStyle
|
||||||
import androidx.glance.unit.ColorProvider
|
import androidx.glance.unit.ColorProvider
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import tachiyomi.core.common.Constants
|
import tachiyomi.core.common.Constants
|
||||||
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.widget.util.calculateRowAndColumnCount
|
import tachiyomi.presentation.widget.util.calculateRowAndColumnCount
|
||||||
|
|
@ -86,6 +88,7 @@ fun UpdatesWidget(
|
||||||
UpdatesMangaCover(
|
UpdatesMangaCover(
|
||||||
cover = cover,
|
cover = cover,
|
||||||
modifier = GlanceModifier.clickable(actionStartActivity(intent)),
|
modifier = GlanceModifier.clickable(actionStartActivity(intent)),
|
||||||
|
color = MangaCover.coverColorMap[mangaId]?.first?.let { Color(it) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue