共享元素动画优化
This commit is contained in:
10 files changed
+219
-183
No files matched your search
@@ -5,6 +5,9 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
|
import androidx.compose.animation.AnimatedVisibilityScope
|
||||||
|
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||||
|
import androidx.compose.animation.SharedTransitionScope
|
||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
@@ -82,6 +85,7 @@ import io.legado.app.ui.widget.components.book.SearchBookListItem
|
|||||||
import io.legado.app.ui.widget.components.text.AppText
|
import io.legado.app.ui.widget.components.text.AppText
|
||||||
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
|
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
|
||||||
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
||||||
|
import io.legado.app.ui.main.bookCoverSharedElementKey
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
import org.koin.compose.koinInject
|
import org.koin.compose.koinInject
|
||||||
@@ -89,7 +93,7 @@ import org.koin.compose.koinInject
|
|||||||
@SuppressLint("LocalContextConfigurationRead", "ConfigurationScreenWidthHeight")
|
@SuppressLint("LocalContextConfigurationRead", "ConfigurationScreenWidthHeight")
|
||||||
@OptIn(
|
@OptIn(
|
||||||
ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class,
|
ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class,
|
||||||
ExperimentalMaterial3ExpressiveApi::class
|
ExperimentalMaterial3ExpressiveApi::class, ExperimentalSharedTransitionApi::class
|
||||||
)
|
)
|
||||||
@Composable
|
@Composable
|
||||||
fun ExploreShowScreen(
|
fun ExploreShowScreen(
|
||||||
@@ -98,7 +102,9 @@ fun ExploreShowScreen(
|
|||||||
exploreUrl: String?,
|
exploreUrl: String?,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onBookClick: (SearchBook) -> Unit,
|
onBookClick: (SearchBook) -> Unit,
|
||||||
viewModel: ExploreShowViewModel = koinViewModel()
|
viewModel: ExploreShowViewModel = koinViewModel(),
|
||||||
|
sharedTransitionScope: SharedTransitionScope? = null,
|
||||||
|
animatedVisibilityScope: AnimatedVisibilityScope? = null,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
LaunchedEffect(sourceUrl, exploreUrl, viewModel) {
|
LaunchedEffect(sourceUrl, exploreUrl, viewModel) {
|
||||||
@@ -448,7 +454,9 @@ fun ExploreShowScreen(
|
|||||||
book = item.book,
|
book = item.book,
|
||||||
shelfState = item.shelfState,
|
shelfState = item.shelfState,
|
||||||
onClick = { onBookClick(item.book) },
|
onClick = { onBookClick(item.book) },
|
||||||
modifier = Modifier.animateItem()
|
modifier = Modifier.animateItem(),
|
||||||
|
sharedTransitionScope = sharedTransitionScope,
|
||||||
|
animatedVisibilityScope = animatedVisibilityScope,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,7 +488,9 @@ fun ExploreShowScreen(
|
|||||||
book = item.book,
|
book = item.book,
|
||||||
shelfState = item.shelfState,
|
shelfState = item.shelfState,
|
||||||
onClick = { onBookClick(item.book) },
|
onClick = { onBookClick(item.book) },
|
||||||
modifier = Modifier.animateItem()
|
modifier = Modifier.animateItem(),
|
||||||
|
sharedTransitionScope = sharedTransitionScope,
|
||||||
|
animatedVisibilityScope = animatedVisibilityScope,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,33 +510,45 @@ fun ExploreShowScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ExploreBookItem(
|
fun ExploreBookItem(
|
||||||
book: SearchBook,
|
book: SearchBook,
|
||||||
shelfState: BookShelfState,
|
shelfState: BookShelfState,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier,
|
||||||
|
sharedTransitionScope: SharedTransitionScope? = null,
|
||||||
|
animatedVisibilityScope: AnimatedVisibilityScope? = null,
|
||||||
) {
|
) {
|
||||||
SearchBookListItem(
|
SearchBookListItem(
|
||||||
book = book,
|
book = book,
|
||||||
shelfState = shelfState,
|
shelfState = shelfState,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
modifier = modifier
|
modifier = modifier,
|
||||||
|
sharedTransitionScope = sharedTransitionScope,
|
||||||
|
animatedVisibilityScope = animatedVisibilityScope,
|
||||||
|
sharedCoverKey = bookCoverSharedElementKey(book.bookUrl)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ExploreBookGridItem(
|
fun ExploreBookGridItem(
|
||||||
book: SearchBook,
|
book: SearchBook,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
shelfState: BookShelfState,
|
shelfState: BookShelfState,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier,
|
||||||
|
sharedTransitionScope: SharedTransitionScope? = null,
|
||||||
|
animatedVisibilityScope: AnimatedVisibilityScope? = null,
|
||||||
) {
|
) {
|
||||||
SearchBookGridItem(
|
SearchBookGridItem(
|
||||||
book = book,
|
book = book,
|
||||||
shelfState = shelfState,
|
shelfState = shelfState,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
modifier = modifier
|
modifier = modifier,
|
||||||
|
sharedTransitionScope = sharedTransitionScope,
|
||||||
|
animatedVisibilityScope = animatedVisibilityScope,
|
||||||
|
sharedCoverKey = bookCoverSharedElementKey(book.bookUrl)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package io.legado.app.ui.book.info
|
|||||||
import androidx.compose.animation.AnimatedVisibilityScope
|
import androidx.compose.animation.AnimatedVisibilityScope
|
||||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||||
import androidx.compose.animation.SharedTransitionScope
|
import androidx.compose.animation.SharedTransitionScope
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
@@ -12,6 +14,7 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
@@ -54,6 +57,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.draw.blur
|
import androidx.compose.ui.draw.blur
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
@@ -501,6 +505,12 @@ private fun BookInfoBackdrop(book: Book) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val backdropAlpha by animateFloatAsState(
|
||||||
|
targetValue = if (showBackdropImage) 1f else 0f,
|
||||||
|
animationSpec = tween(800),
|
||||||
|
label = "BackdropFade"
|
||||||
|
)
|
||||||
|
|
||||||
val backdropRequest = remember(cover, sourceOrigin, loadOnlyWifi, context) {
|
val backdropRequest = remember(cover, sourceOrigin, loadOnlyWifi, context) {
|
||||||
buildCoverImageRequest(
|
buildCoverImageRequest(
|
||||||
context = context,
|
context = context,
|
||||||
@@ -516,15 +526,16 @@ private fun BookInfoBackdrop(book: Book) {
|
|||||||
0.42f
|
0.42f
|
||||||
)
|
)
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
if (!cover.isNullOrBlank() && showBackdropImage) {
|
if (!cover.isNullOrBlank()) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = backdropRequest,
|
model = backdropRequest,
|
||||||
imageLoader = imageLoader,
|
imageLoader = imageLoader,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(360.dp)
|
.height(480.dp)
|
||||||
.blur(24.dp),
|
.blur(24.dp)
|
||||||
|
.alpha(backdropAlpha),
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -540,10 +551,10 @@ private fun BookInfoBackdrop(book: Book) {
|
|||||||
Brush.verticalGradient(
|
Brush.verticalGradient(
|
||||||
colorStops = arrayOf(
|
colorStops = arrayOf(
|
||||||
0f to Color.Transparent,
|
0f to Color.Transparent,
|
||||||
0.18f to seedOverlay.copy(alpha = 0.10f),
|
0.20f to seedOverlay.copy(alpha = 0.10f),
|
||||||
0.34f to seedOverlay.copy(alpha = 0.18f),
|
0.40f to seedOverlay.copy(alpha = 0.18f),
|
||||||
0.52f to LegadoTheme.colorScheme.surface.copy(alpha = 0.82f),
|
0.60f to LegadoTheme.colorScheme.surface.copy(alpha = 0.85f),
|
||||||
0.72f to LegadoTheme.colorScheme.surface,
|
0.80f to LegadoTheme.colorScheme.surface,
|
||||||
1f to LegadoTheme.colorScheme.surface,
|
1f to LegadoTheme.colorScheme.surface,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -687,25 +698,16 @@ private fun BookInfoHeader(
|
|||||||
.width(112.dp)
|
.width(112.dp)
|
||||||
.combinedClickable(onClick = onCoverClick, onLongClick = onCoverLongClick)
|
.combinedClickable(onClick = onCoverClick, onLongClick = onCoverLongClick)
|
||||||
) {
|
) {
|
||||||
val coverModifier = with(sharedTransitionScope) {
|
|
||||||
if (this != null && animatedVisibilityScope != null && sharedCoverKey != null) {
|
|
||||||
Modifier
|
|
||||||
.width(112.dp)
|
|
||||||
.sharedElement(
|
|
||||||
sharedContentState = rememberSharedContentState(sharedCoverKey),
|
|
||||||
animatedVisibilityScope = animatedVisibilityScope,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Modifier.width(112.dp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CoilBookCover(
|
CoilBookCover(
|
||||||
name = book.name,
|
name = book.name,
|
||||||
author = book.author,
|
author = book.author,
|
||||||
path = book.getDisplayCover(),
|
path = book.getDisplayCover(),
|
||||||
sourceOrigin = book.origin,
|
sourceOrigin = book.origin,
|
||||||
modifier = coverModifier,
|
modifier = Modifier.width(112.dp).aspectRatio(5f / 7f),
|
||||||
showLoadingPlaceholder = sharedCoverKey == null,
|
showLoadingPlaceholder = sharedCoverKey == null,
|
||||||
|
sharedTransitionScope = sharedTransitionScope,
|
||||||
|
animatedVisibilityScope = animatedVisibilityScope,
|
||||||
|
sharedCoverKey = sharedCoverKey,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Column(
|
Column(
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package io.legado.app.ui.book.search
|
package io.legado.app.ui.book.search
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.AnimatedVisibilityScope
|
||||||
|
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||||
|
import androidx.compose.animation.SharedTransitionScope
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -91,18 +94,21 @@ import io.legado.app.ui.widget.components.icon.AppIcons
|
|||||||
import io.legado.app.ui.widget.components.list.TopFloatingStickyItem
|
import io.legado.app.ui.widget.components.list.TopFloatingStickyItem
|
||||||
import io.legado.app.ui.widget.components.tabRow.AppTabRow
|
import io.legado.app.ui.widget.components.tabRow.AppTabRow
|
||||||
import io.legado.app.ui.widget.components.text.AppText
|
import io.legado.app.ui.widget.components.text.AppText
|
||||||
|
import io.legado.app.ui.main.bookCoverSharedElementKey
|
||||||
import io.legado.app.utils.toastOnUi
|
import io.legado.app.utils.toastOnUi
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.flow.debounce
|
import kotlinx.coroutines.flow.debounce
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
|
||||||
@OptIn(FlowPreview::class, ExperimentalMaterial3Api::class)
|
@OptIn(FlowPreview::class, ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SearchScreen(
|
fun SearchScreen(
|
||||||
viewModel: SearchViewModel,
|
viewModel: SearchViewModel,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onOpenBookInfo: (name: String, author: String, bookUrl: String) -> Unit,
|
onOpenBookInfo: (name: String, author: String, bookUrl: String) -> Unit,
|
||||||
onOpenSourceManage: () -> Unit,
|
onOpenSourceManage: () -> Unit,
|
||||||
|
sharedTransitionScope: SharedTransitionScope? = null,
|
||||||
|
animatedVisibilityScope: AnimatedVisibilityScope? = null,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
@@ -362,7 +368,10 @@ fun SearchScreen(
|
|||||||
shelfState = item.shelfState,
|
shelfState = item.shelfState,
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.onIntent(SearchIntent.OpenSearchBook(item.book))
|
viewModel.onIntent(SearchIntent.OpenSearchBook(item.book))
|
||||||
}
|
},
|
||||||
|
sharedTransitionScope = sharedTransitionScope,
|
||||||
|
animatedVisibilityScope = animatedVisibilityScope,
|
||||||
|
sharedCoverKey = bookCoverSharedElementKey(item.book.bookUrl)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -609,7 +609,18 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
entry<MainRouteSearch> { route ->
|
entry<MainRouteSearch>(
|
||||||
|
metadata = NavDisplay.transitionSpec {
|
||||||
|
fadeIn(animationSpec = tween(300)) togetherWith
|
||||||
|
fadeOut(animationSpec = tween(300))
|
||||||
|
} + NavDisplay.popTransitionSpec {
|
||||||
|
fadeIn(animationSpec = tween(300)) togetherWith
|
||||||
|
fadeOut(animationSpec = tween(300))
|
||||||
|
} + NavDisplay.predictivePopTransitionSpec { _ ->
|
||||||
|
fadeIn(animationSpec = tween(300)) togetherWith
|
||||||
|
fadeOut(animationSpec = tween(300))
|
||||||
|
}
|
||||||
|
) { route ->
|
||||||
val searchViewModel = koinViewModel<SearchViewModel>()
|
val searchViewModel = koinViewModel<SearchViewModel>()
|
||||||
val lifecycleOwner = LocalLifecycleOwner.current
|
val lifecycleOwner = LocalLifecycleOwner.current
|
||||||
|
|
||||||
@@ -657,7 +668,9 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
|
|||||||
},
|
},
|
||||||
onOpenSourceManage = {
|
onOpenSourceManage = {
|
||||||
this@MainActivity.startActivity<BookSourceActivity>()
|
this@MainActivity.startActivity<BookSourceActivity>()
|
||||||
}
|
},
|
||||||
|
sharedTransitionScope = this@SharedTransitionLayout,
|
||||||
|
animatedVisibilityScope = LocalNavAnimatedContentScope.current,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -730,7 +743,18 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
entry<MainRouteExploreShow> { route ->
|
entry<MainRouteExploreShow>(
|
||||||
|
metadata = NavDisplay.transitionSpec {
|
||||||
|
fadeIn(animationSpec = tween(300)) togetherWith
|
||||||
|
fadeOut(animationSpec = tween(300))
|
||||||
|
} + NavDisplay.popTransitionSpec {
|
||||||
|
fadeIn(animationSpec = tween(300)) togetherWith
|
||||||
|
fadeOut(animationSpec = tween(300))
|
||||||
|
} + NavDisplay.predictivePopTransitionSpec { _ ->
|
||||||
|
fadeIn(animationSpec = tween(300)) togetherWith
|
||||||
|
fadeOut(animationSpec = tween(300))
|
||||||
|
}
|
||||||
|
) { route ->
|
||||||
ExploreShowScreen(
|
ExploreShowScreen(
|
||||||
title = route.title ?: "探索",
|
title = route.title ?: "探索",
|
||||||
sourceUrl = route.sourceUrl,
|
sourceUrl = route.sourceUrl,
|
||||||
@@ -745,7 +769,9 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
|
|||||||
bookUrl = book.bookUrl
|
bookUrl = book.bookUrl
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
|
sharedTransitionScope = this@SharedTransitionLayout,
|
||||||
|
animatedVisibilityScope = LocalNavAnimatedContentScope.current,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -349,7 +349,13 @@ fun MainScreen(
|
|||||||
) {
|
) {
|
||||||
HorizontalPager(
|
HorizontalPager(
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.then(
|
||||||
|
with(sharedTransitionScope) {
|
||||||
|
if (this != null) Modifier.skipToLookaheadSize() else Modifier
|
||||||
|
}
|
||||||
|
),
|
||||||
userScrollEnabled = true,
|
userScrollEnabled = true,
|
||||||
beyondViewportPageCount = 1
|
beyondViewportPageCount = 1
|
||||||
) { page ->
|
) { page ->
|
||||||
|
|||||||
@@ -536,28 +536,21 @@ fun BookItem(
|
|||||||
}
|
}
|
||||||
} else null,
|
} else null,
|
||||||
cover = { modifier ->
|
cover = { modifier ->
|
||||||
val coverModifier = with(sharedTransitionScope) {
|
|
||||||
if (this != null && animatedVisibilityScope != null && sharedCoverKey != null) {
|
|
||||||
Modifier.fillMaxWidth().sharedElement(
|
|
||||||
sharedContentState = rememberSharedContentState(sharedCoverKey),
|
|
||||||
animatedVisibilityScope = animatedVisibilityScope,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Modifier.fillMaxWidth()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BookshelfCover(
|
BookshelfCover(
|
||||||
name = book.name,
|
name = book.name,
|
||||||
author = book.author,
|
author = book.author,
|
||||||
path = book.getDisplayCover(),
|
path = book.getDisplayCover(),
|
||||||
isUpdating = isUpdating,
|
isUpdating = isUpdating,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
coverModifier = coverModifier,
|
coverModifier = Modifier.fillMaxWidth().aspectRatio(5f / 7f),
|
||||||
sourceOrigin = book.origin,
|
sourceOrigin = book.origin,
|
||||||
badgeText = if (layoutMode != 0) unreadText else null,
|
badgeText = if (layoutMode != 0) unreadText else null,
|
||||||
showBadgeDot = BookshelfConfig.showUnread && BookshelfConfig.showUnreadNew && book.isNew,
|
showBadgeDot = BookshelfConfig.showUnread && BookshelfConfig.showUnreadNew && book.isNew,
|
||||||
leftBottomText = matchedSourceLabel ?: bookTypeLabel,
|
leftBottomText = matchedSourceLabel ?: bookTypeLabel,
|
||||||
showLoadingPlaceholder = sharedCoverKey == null,
|
showLoadingPlaceholder = true,
|
||||||
|
sharedTransitionScope = sharedTransitionScope,
|
||||||
|
animatedVisibilityScope = animatedVisibilityScope,
|
||||||
|
sharedCoverKey = sharedCoverKey,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
title = book.name,
|
title = book.name,
|
||||||
|
|||||||
@@ -632,7 +632,12 @@ fun BookshelfScreen(
|
|||||||
FastScrollLazyVerticalGrid(
|
FastScrollLazyVerticalGrid(
|
||||||
columns = GridCells.Fixed(folderColumns.coerceAtLeast(1)),
|
columns = GridCells.Fixed(folderColumns.coerceAtLeast(1)),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize(),
|
.fillMaxSize()
|
||||||
|
.then(
|
||||||
|
with(sharedTransitionScope) {
|
||||||
|
if (this != null) Modifier.skipToLookaheadSize() else Modifier
|
||||||
|
}
|
||||||
|
),
|
||||||
contentPadding = adaptiveContentPaddingBookshelf(
|
contentPadding = adaptiveContentPaddingBookshelf(
|
||||||
top = paddingValues.calculateTopPadding(),
|
top = paddingValues.calculateTopPadding(),
|
||||||
bottom = 120.dp,
|
bottom = 120.dp,
|
||||||
@@ -717,7 +722,13 @@ fun BookshelfScreen(
|
|||||||
} else {
|
} else {
|
||||||
HorizontalPager(
|
HorizontalPager(
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.then(
|
||||||
|
with(sharedTransitionScope) {
|
||||||
|
if (this != null) Modifier.skipToLookaheadSize() else Modifier
|
||||||
|
}
|
||||||
|
),
|
||||||
beyondViewportPageCount = 2,
|
beyondViewportPageCount = 2,
|
||||||
key = { if (it < uiState.groups.size) uiState.groups[it].groupId else it }
|
key = { if (it < uiState.groups.size) uiState.groups[it].groupId else it }
|
||||||
) { pageIndex ->
|
) { pageIndex ->
|
||||||
@@ -1078,11 +1089,26 @@ fun BookshelfPage(
|
|||||||
onDragFinished()
|
onDragFinished()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.then(
|
||||||
|
with(sharedTransitionScope) {
|
||||||
|
if (this != null) Modifier.skipToLookaheadSize() else Modifier
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) {
|
||||||
FastScrollLazyVerticalGrid(
|
FastScrollLazyVerticalGrid(
|
||||||
columns = GridCells.Fixed(columns.coerceAtLeast(1)),
|
columns = GridCells.Fixed(columns.coerceAtLeast(1)),
|
||||||
state = gridState,
|
state = gridState,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize(),
|
.fillMaxSize()
|
||||||
|
.then(
|
||||||
|
with(sharedTransitionScope) {
|
||||||
|
if (this != null) Modifier.skipToLookaheadSize() else Modifier
|
||||||
|
}
|
||||||
|
),
|
||||||
contentPadding = adaptiveContentPaddingBookshelf(
|
contentPadding = adaptiveContentPaddingBookshelf(
|
||||||
top = paddingValues.calculateTopPadding(),
|
top = paddingValues.calculateTopPadding(),
|
||||||
bottom = 120.dp,
|
bottom = 120.dp,
|
||||||
@@ -1133,7 +1159,7 @@ fun BookshelfPage(
|
|||||||
searchKey = uiState.searchKey,
|
searchKey = uiState.searchKey,
|
||||||
sharedTransitionScope = sharedTransitionScope,
|
sharedTransitionScope = sharedTransitionScope,
|
||||||
animatedVisibilityScope = animatedVisibilityScope,
|
animatedVisibilityScope = animatedVisibilityScope,
|
||||||
sharedCoverKey = bookCoverSharedElementKey(book.bookUrl),
|
sharedCoverKey = if (isCurrentPage) bookCoverSharedElementKey(book.bookUrl) else null,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (uiState.isEditMode) {
|
if (uiState.isEditMode) {
|
||||||
onToggleBookSelection(book)
|
onToggleBookSelection(book)
|
||||||
@@ -1156,4 +1182,5 @@ fun BookshelfPage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
package io.legado.app.ui.widget.components.book
|
package io.legado.app.ui.widget.components.book
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibilityScope
|
||||||
|
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||||
|
import androidx.compose.animation.SharedTransitionScope
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.horizontalScroll
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -18,8 +21,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Check
|
import androidx.compose.material.icons.filled.Check
|
||||||
import androidx.compose.material.icons.filled.Shuffle
|
import androidx.compose.material.icons.filled.Shuffle
|
||||||
import androidx.compose.material.icons.filled.SwapHoriz
|
|
||||||
import androidx.compose.material.icons.filled.Warning
|
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
@@ -34,7 +35,7 @@ import androidx.compose.ui.unit.sp
|
|||||||
import io.legado.app.data.entities.SearchBook
|
import io.legado.app.data.entities.SearchBook
|
||||||
import io.legado.app.domain.model.BookShelfState
|
import io.legado.app.domain.model.BookShelfState
|
||||||
import io.legado.app.ui.theme.LegadoTheme
|
import io.legado.app.ui.theme.LegadoTheme
|
||||||
import io.legado.app.ui.widget.components.cover.Cover
|
import io.legado.app.ui.widget.components.cover.CoilBookCover
|
||||||
import io.legado.app.ui.widget.components.text.AppText
|
import io.legado.app.ui.widget.components.text.AppText
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
@@ -54,47 +55,33 @@ fun SearchBookListItem(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SearchBookListItem(
|
fun SearchBookListItem(
|
||||||
book: SearchBook,
|
book: SearchBook,
|
||||||
shelfState: BookShelfState,
|
shelfState: BookShelfState,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
sharedTransitionScope: SharedTransitionScope? = null,
|
||||||
|
animatedVisibilityScope: AnimatedVisibilityScope? = null,
|
||||||
|
sharedCoverKey: String? = null,
|
||||||
) {
|
) {
|
||||||
val badgeContent: (@Composable RowScope.() -> Unit)? = when (shelfState) {
|
|
||||||
BookShelfState.IN_SHELF -> {
|
|
||||||
{
|
|
||||||
androidx.compose.material3.Icon(
|
|
||||||
imageVector = Icons.Default.Check,
|
|
||||||
contentDescription = "已在书架",
|
|
||||||
modifier = Modifier.size(12.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BookShelfState.SAME_NAME_AUTHOR -> {
|
|
||||||
{
|
|
||||||
androidx.compose.material3.Icon(
|
|
||||||
imageVector = Icons.Default.Shuffle,
|
|
||||||
contentDescription = "同名书籍",
|
|
||||||
modifier = Modifier.size(12.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BookShelfState.NOT_IN_SHELF -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable(onClick = onClick)
|
.clickable(onClick = onClick)
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
) {
|
) {
|
||||||
Cover(
|
CoilBookCover(
|
||||||
|
name = book.name,
|
||||||
|
author = book.author,
|
||||||
path = book.coverUrl,
|
path = book.coverUrl,
|
||||||
modifier = Modifier.width(72.dp),
|
modifier = Modifier.width(72.dp).aspectRatio(5f / 7f),
|
||||||
badgeContent = badgeContent,
|
sourceOrigin = book.origin,
|
||||||
|
sharedTransitionScope = sharedTransitionScope,
|
||||||
|
animatedVisibilityScope = animatedVisibilityScope,
|
||||||
|
sharedCoverKey = sharedCoverKey,
|
||||||
|
showLoadingPlaceholder = sharedCoverKey == null
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
@@ -180,45 +167,35 @@ fun SearchBookGridItem(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SearchBookGridItem(
|
fun SearchBookGridItem(
|
||||||
book: SearchBook,
|
book: SearchBook,
|
||||||
shelfState: BookShelfState,
|
shelfState: BookShelfState,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
sharedTransitionScope: SharedTransitionScope? = null,
|
||||||
|
animatedVisibilityScope: AnimatedVisibilityScope? = null,
|
||||||
|
sharedCoverKey: String? = null,
|
||||||
) {
|
) {
|
||||||
val badgeText: String? = when (shelfState) {
|
|
||||||
BookShelfState.IN_SHELF -> "已在书架"
|
|
||||||
BookShelfState.SAME_NAME_AUTHOR -> "同名书籍"
|
|
||||||
BookShelfState.NOT_IN_SHELF -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
val badgeContent: (@Composable RowScope.() -> Unit)? = if (!badgeText.isNullOrBlank()) {
|
|
||||||
{
|
|
||||||
AppText(
|
|
||||||
text = badgeText,
|
|
||||||
style = LegadoTheme.typography.labelSmall.copy(
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
fontSize = 9.sp,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.width(IntrinsicSize.Min)
|
.width(IntrinsicSize.Min)
|
||||||
.clickable(onClick = onClick)
|
.clickable(onClick = onClick)
|
||||||
.padding(4.dp)
|
.padding(4.dp)
|
||||||
) {
|
) {
|
||||||
Cover(
|
CoilBookCover(
|
||||||
|
name = book.name,
|
||||||
|
author = book.author,
|
||||||
path = book.coverUrl,
|
path = book.coverUrl,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.aspectRatio(12 / 17f),
|
.aspectRatio(5f / 7f),
|
||||||
badgeContent = badgeContent,
|
sourceOrigin = book.origin,
|
||||||
|
sharedTransitionScope = sharedTransitionScope,
|
||||||
|
animatedVisibilityScope = animatedVisibilityScope,
|
||||||
|
sharedCoverKey = sharedCoverKey,
|
||||||
|
showLoadingPlaceholder = sharedCoverKey == null
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package io.legado.app.ui.widget.components.cover
|
package io.legado.app.ui.widget.components.cover
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibilityScope
|
||||||
|
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||||
|
import androidx.compose.animation.SharedTransitionScope
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
@@ -14,6 +17,7 @@ import io.legado.app.ui.theme.LegadoTheme
|
|||||||
import io.legado.app.ui.widget.components.AppLinearProgressIndicator
|
import io.legado.app.ui.widget.components.AppLinearProgressIndicator
|
||||||
import io.legado.app.ui.widget.components.card.TextCard
|
import io.legado.app.ui.widget.components.card.TextCard
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun BookshelfCover(
|
fun BookshelfCover(
|
||||||
name: String?,
|
name: String?,
|
||||||
@@ -28,6 +32,9 @@ fun BookshelfCover(
|
|||||||
sourceOrigin: String? = null,
|
sourceOrigin: String? = null,
|
||||||
onLoadFinish: (() -> Unit)? = null,
|
onLoadFinish: (() -> Unit)? = null,
|
||||||
showLoadingPlaceholder: Boolean = true,
|
showLoadingPlaceholder: Boolean = true,
|
||||||
|
sharedTransitionScope: SharedTransitionScope? = null,
|
||||||
|
animatedVisibilityScope: AnimatedVisibilityScope? = null,
|
||||||
|
sharedCoverKey: String? = null,
|
||||||
) {
|
) {
|
||||||
Box(modifier = modifier) {
|
Box(modifier = modifier) {
|
||||||
CoilBookCover(
|
CoilBookCover(
|
||||||
@@ -38,6 +45,9 @@ fun BookshelfCover(
|
|||||||
sourceOrigin = sourceOrigin,
|
sourceOrigin = sourceOrigin,
|
||||||
onLoadFinish = onLoadFinish,
|
onLoadFinish = onLoadFinish,
|
||||||
showLoadingPlaceholder = showLoadingPlaceholder,
|
showLoadingPlaceholder = showLoadingPlaceholder,
|
||||||
|
sharedTransitionScope = sharedTransitionScope,
|
||||||
|
animatedVisibilityScope = animatedVisibilityScope,
|
||||||
|
sharedCoverKey = sharedCoverKey,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!badgeText.isNullOrEmpty()) {
|
if (!badgeText.isNullOrEmpty()) {
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ import android.text.Layout
|
|||||||
import android.text.StaticLayout
|
import android.text.StaticLayout
|
||||||
import android.text.TextPaint
|
import android.text.TextPaint
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
|
import androidx.compose.animation.AnimatedVisibilityScope
|
||||||
|
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||||
|
import androidx.compose.animation.SharedTransitionScope
|
||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
@@ -27,6 +29,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.shadow
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
|
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
|
||||||
@@ -34,17 +37,15 @@ import androidx.compose.ui.graphics.nativeCanvas
|
|||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.graphics.withSave
|
import androidx.core.graphics.withSave
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import io.legado.app.ui.config.coverConfig.CoverConfig
|
import io.legado.app.ui.config.coverConfig.CoverConfig
|
||||||
import io.legado.app.ui.theme.LegadoTheme
|
import io.legado.app.ui.theme.LegadoTheme
|
||||||
import io.legado.app.ui.widget.components.card.NormalCard
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import org.koin.compose.koinInject
|
import org.koin.compose.koinInject
|
||||||
import io.legado.app.model.BookCover as BookCoverModel
|
import io.legado.app.model.BookCover as BookCoverModel
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun CoilBookCover(
|
fun CoilBookCover(
|
||||||
name: String?,
|
name: String?,
|
||||||
@@ -55,6 +56,9 @@ fun CoilBookCover(
|
|||||||
onLoadFinish: (() -> Unit)? = null,
|
onLoadFinish: (() -> Unit)? = null,
|
||||||
ignoreUseDefaultCover: Boolean = false,
|
ignoreUseDefaultCover: Boolean = false,
|
||||||
showLoadingPlaceholder: Boolean = true,
|
showLoadingPlaceholder: Boolean = true,
|
||||||
|
sharedTransitionScope: SharedTransitionScope? = null,
|
||||||
|
animatedVisibilityScope: AnimatedVisibilityScope? = null,
|
||||||
|
sharedCoverKey: String? = null,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val isNight = isSystemInDarkTheme()
|
val isNight = isSystemInDarkTheme()
|
||||||
@@ -62,11 +66,7 @@ fun CoilBookCover(
|
|||||||
val useDefault = !ignoreUseDefaultCover && CoverConfig.useDefaultCover
|
val useDefault = !ignoreUseDefaultCover && CoverConfig.useDefaultCover
|
||||||
val finalPath = if (useDefault) null else path
|
val finalPath = if (useDefault) null else path
|
||||||
|
|
||||||
val randomPath = remember(
|
val randomPath = remember(name, author, path, isNight) {
|
||||||
name, author, path, isNight,
|
|
||||||
CoverConfig.defaultCover,
|
|
||||||
CoverConfig.defaultCoverDark
|
|
||||||
) {
|
|
||||||
BookCoverModel.getRandomDefaultPath(
|
BookCoverModel.getRandomDefaultPath(
|
||||||
seed = name ?: author ?: path ?: "",
|
seed = name ?: author ?: path ?: "",
|
||||||
isNight = isNight
|
isNight = isNight
|
||||||
@@ -74,57 +74,46 @@ fun CoilBookCover(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val hasCustomDefault = !randomPath.isNullOrBlank()
|
val hasCustomDefault = !randomPath.isNullOrBlank()
|
||||||
|
|
||||||
var isOnlineCoverLoaded by remember(path) { mutableStateOf(false) }
|
var isOnlineCoverLoaded by remember(path) { mutableStateOf(false) }
|
||||||
var isFinalPathLoaded by remember(path) { mutableStateOf(false) }
|
|
||||||
var isFinalPathLoadFinished by remember(path) { mutableStateOf(false) }
|
|
||||||
var isPlaceholderAllowed by remember(path, showLoadingPlaceholder) {
|
|
||||||
mutableStateOf(showLoadingPlaceholder)
|
|
||||||
}
|
|
||||||
LaunchedEffect(finalPath) {
|
|
||||||
isOnlineCoverLoaded = false
|
|
||||||
isFinalPathLoaded = false
|
|
||||||
isFinalPathLoadFinished = finalPath == null
|
|
||||||
}
|
|
||||||
LaunchedEffect(finalPath, showLoadingPlaceholder) {
|
|
||||||
if (showLoadingPlaceholder) {
|
|
||||||
isPlaceholderAllowed = true
|
|
||||||
} else {
|
|
||||||
isPlaceholderAllowed = false
|
|
||||||
delay(600)
|
|
||||||
isPlaceholderAllowed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NormalCard(
|
Box(
|
||||||
cornerRadius = 4.dp,
|
|
||||||
containerColor = if (!hasCustomDefault && !isOnlineCoverLoaded) {
|
|
||||||
LegadoTheme.colorScheme.surfaceContainerLow
|
|
||||||
} else
|
|
||||||
LegadoTheme.colorScheme.surfaceContainerLow.copy(alpha = 0f)
|
|
||||||
){
|
|
||||||
BoxWithConstraints(
|
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.aspectRatio(5f / 7f)
|
|
||||||
.then(
|
.then(
|
||||||
if (CoverConfig.coverShowShadow) {
|
if (CoverConfig.coverShowShadow) {
|
||||||
Modifier.shadow(4.dp, RoundedCornerShape(4.dp))
|
Modifier.shadow(4.dp, RoundedCornerShape(4.dp))
|
||||||
} else Modifier
|
} else Modifier
|
||||||
)
|
)
|
||||||
|
.background(
|
||||||
|
if (!hasCustomDefault && !isOnlineCoverLoaded) {
|
||||||
|
LegadoTheme.colorScheme.surfaceContainerLow
|
||||||
|
} else Color.Transparent,
|
||||||
|
RoundedCornerShape(4.dp)
|
||||||
|
)
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
) {
|
) {
|
||||||
val density = LocalDensity.current
|
val imageContentModifier = Modifier
|
||||||
val iconSizeDp = with(density) {
|
.fillMaxSize()
|
||||||
(minOf(maxWidth, maxHeight).toPx() / 3f).toDp()
|
.then(
|
||||||
|
with(sharedTransitionScope) {
|
||||||
|
if (this != null && animatedVisibilityScope != null && sharedCoverKey != null) {
|
||||||
|
Modifier.sharedElement(
|
||||||
|
sharedContentState = rememberSharedContentState(sharedCoverKey),
|
||||||
|
animatedVisibilityScope = animatedVisibilityScope,
|
||||||
|
renderInOverlayDuringTransition = true
|
||||||
|
)
|
||||||
|
} else Modifier
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if (hasCustomDefault && !isFinalPathLoaded) {
|
Box(modifier = imageContentModifier) {
|
||||||
|
if (hasCustomDefault && !isOnlineCoverLoaded) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = buildCoverImageRequest(
|
model = buildCoverImageRequest(
|
||||||
context = context,
|
context = context,
|
||||||
data = randomPath,
|
data = randomPath,
|
||||||
sourceOrigin = null,
|
sourceOrigin = null,
|
||||||
loadOnlyWifi = false,
|
loadOnlyWifi = false,
|
||||||
crossfade = true,
|
crossfade = showLoadingPlaceholder,
|
||||||
),
|
),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
imageLoader = koinInject(),
|
imageLoader = koinInject(),
|
||||||
@@ -140,7 +129,7 @@ fun CoilBookCover(
|
|||||||
data = finalPath,
|
data = finalPath,
|
||||||
sourceOrigin = sourceOrigin,
|
sourceOrigin = sourceOrigin,
|
||||||
loadOnlyWifi = CoverConfig.loadCoverOnlyWifi,
|
loadOnlyWifi = CoverConfig.loadCoverOnlyWifi,
|
||||||
crossfade = true,
|
crossfade = showLoadingPlaceholder,
|
||||||
),
|
),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
imageLoader = koinInject(),
|
imageLoader = koinInject(),
|
||||||
@@ -148,13 +137,10 @@ fun CoilBookCover(
|
|||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
isOnlineCoverLoaded = true
|
isOnlineCoverLoaded = true
|
||||||
isFinalPathLoaded = true
|
|
||||||
isFinalPathLoadFinished = true
|
|
||||||
onLoadFinish?.invoke()
|
onLoadFinish?.invoke()
|
||||||
},
|
},
|
||||||
onError = {
|
onError = {
|
||||||
isOnlineCoverLoaded = false
|
isOnlineCoverLoaded = false
|
||||||
isFinalPathLoadFinished = true
|
|
||||||
onLoadFinish?.invoke()
|
onLoadFinish?.invoke()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -163,29 +149,25 @@ fun CoilBookCover(
|
|||||||
onLoadFinish?.invoke()
|
onLoadFinish?.invoke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val showPlaceholder = isPlaceholderAllowed && (showLoadingPlaceholder || isFinalPathLoadFinished)
|
if (showLoadingPlaceholder && !isOnlineCoverLoaded) {
|
||||||
|
if (!hasCustomDefault) {
|
||||||
if (!hasCustomDefault && !isOnlineCoverLoaded && showPlaceholder) {
|
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Book,
|
Icons.Default.Book,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = LegadoTheme.colorScheme.secondary,
|
tint = LegadoTheme.colorScheme.secondary,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(iconSizeDp)
|
.fillMaxSize(0.35f)
|
||||||
.align(Alignment.Center)
|
.align(Alignment.Center)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isOnlineCoverLoaded && showPlaceholder) {
|
|
||||||
CoverTextOverlay(
|
CoverTextOverlay(
|
||||||
name = name,
|
name = name,
|
||||||
author = author,
|
author = author,
|
||||||
isNight = isNight
|
isNight = isNight
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,8 +178,7 @@ private fun CoverTextOverlay(
|
|||||||
isNight: Boolean
|
isNight: Boolean
|
||||||
) {
|
) {
|
||||||
val showName = if (isNight) CoverConfig.coverShowNameN else CoverConfig.coverShowName
|
val showName = if (isNight) CoverConfig.coverShowNameN else CoverConfig.coverShowName
|
||||||
val showAuthor =
|
val showAuthor = (if (isNight) CoverConfig.coverShowAuthorN else CoverConfig.coverShowAuthor) && showName
|
||||||
(if (isNight) CoverConfig.coverShowAuthorN else CoverConfig.coverShowAuthor) && showName
|
|
||||||
|
|
||||||
if (!showName && !showAuthor) return
|
if (!showName && !showAuthor) return
|
||||||
|
|
||||||
@@ -231,9 +212,7 @@ private fun CoverTextOverlay(
|
|||||||
|
|
||||||
if (isHorizontal) {
|
if (isHorizontal) {
|
||||||
val maxWidth = (viewWidth * 0.8f).toInt()
|
val maxWidth = (viewWidth * 0.8f).toInt()
|
||||||
val textPaint = TextPaint(paint).apply {
|
val textPaint = TextPaint(paint).apply { textAlign = Paint.Align.LEFT }
|
||||||
textAlign = Paint.Align.LEFT
|
|
||||||
}
|
|
||||||
val layout = StaticLayout.Builder
|
val layout = StaticLayout.Builder
|
||||||
.obtain(name, 0, name.length, textPaint, maxWidth)
|
.obtain(name, 0, name.length, textPaint, maxWidth)
|
||||||
.setAlignment(Layout.Alignment.ALIGN_CENTER)
|
.setAlignment(Layout.Alignment.ALIGN_CENTER)
|
||||||
@@ -296,12 +275,7 @@ private fun CoverTextOverlay(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isHorizontal) {
|
if (isHorizontal) {
|
||||||
val authorText = TextUtils.ellipsize(
|
val authorText = TextUtils.ellipsize(author, TextPaint(paint), viewWidth * 0.9f, TextUtils.TruncateAt.END)
|
||||||
author,
|
|
||||||
TextPaint(paint),
|
|
||||||
viewWidth * 0.9f,
|
|
||||||
TextUtils.TruncateAt.END
|
|
||||||
)
|
|
||||||
if (CoverConfig.coverShowStroke) {
|
if (CoverConfig.coverShowStroke) {
|
||||||
val strokePaint = Paint(paint).apply {
|
val strokePaint = Paint(paint).apply {
|
||||||
color = Color.White.toArgb()
|
color = Color.White.toArgb()
|
||||||
@@ -309,19 +283,9 @@ private fun CoverTextOverlay(
|
|||||||
strokeWidth = paint.textSize / 10
|
strokeWidth = paint.textSize / 10
|
||||||
clearShadowLayer()
|
clearShadowLayer()
|
||||||
}
|
}
|
||||||
nativeCanvas.drawText(
|
nativeCanvas.drawText(authorText.toString(), viewWidth / 2, viewHeight * 0.75f, strokePaint)
|
||||||
authorText.toString(),
|
|
||||||
viewWidth / 2,
|
|
||||||
viewHeight * 0.75f,
|
|
||||||
strokePaint
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
nativeCanvas.drawText(
|
nativeCanvas.drawText(authorText.toString(), viewWidth / 2, viewHeight * 0.75f, paint)
|
||||||
authorText.toString(),
|
|
||||||
viewWidth / 2,
|
|
||||||
viewHeight * 0.75f,
|
|
||||||
paint
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
val startX = viewWidth * 0.84f
|
val startX = viewWidth * 0.84f
|
||||||
val fm = paint.fontMetrics
|
val fm = paint.fontMetrics
|
||||||
|
|||||||
Reference in new issue
Block a user