Просмотр исходного кода

fix: 暂停屏幕外书源 GIF 动画

HapeLee 4 месяцев назад
Родитель
Сommit
13685b3364

+ 60 - 2
app/src/main/java/io/legado/app/ui/main/MainScreen.kt

@@ -43,12 +43,15 @@ import androidx.compose.material3.WideNavigationRailItem
 import androidx.compose.material3.WideNavigationRailValue
 import androidx.compose.material3.rememberWideNavigationRailState
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableLongStateOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -62,6 +65,11 @@ import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import androidx.core.net.toUri
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import coil.compose.AsyncImage
 import com.kyant.backdrop.backdrops.layerBackdrop
@@ -401,7 +409,11 @@ fun MainScreen(
                         beyondViewportPageCount = 4
                     ) { page ->
                         val destination = destinations.getOrNull(page) ?: return@HorizontalPager
-                        when (destination) {
+                        val pageLifecycleOwner = rememberMainPageLifecycleOwner(
+                            isActive = page == pagerState.currentPage
+                        )
+                        CompositionLocalProvider(LocalLifecycleOwner provides pageLifecycleOwner) {
+                            when (destination) {
                             MainDestination.Home -> HomeRouteScreen(
                                 onOpenBook = { book ->
                                     context.startActivityForBook(book)
@@ -454,7 +466,6 @@ fun MainScreen(
                                 onOpenExploreShow = onNavigateToExploreShow,
                             )
                             MainDestination.Rss -> RssScreen(
-                                isActive = page == pagerState.currentPage,
                                 onOpenSort = { sourceUrl, sortUrl, key ->
                                     onNavigateToRssSort(sourceUrl, sortUrl, key)
                                 },
@@ -476,6 +487,7 @@ fun MainScreen(
                                 }
                             )
                         }
+                        }
                     }
                 }
 
@@ -552,6 +564,52 @@ fun MainScreen(
     }
 }
 
+@Composable
+private fun rememberMainPageLifecycleOwner(isActive: Boolean): LifecycleOwner {
+    val parentLifecycle = LocalLifecycleOwner.current.lifecycle
+    val currentActive by rememberUpdatedState(isActive)
+    val owner = remember(parentLifecycle) { MainPageLifecycleOwner() }
+
+    DisposableEffect(parentLifecycle) {
+        val observer = LifecycleEventObserver { _, _ ->
+            owner.update(parentLifecycle.currentState, currentActive)
+        }
+        parentLifecycle.addObserver(observer)
+        owner.update(parentLifecycle.currentState, currentActive)
+        onDispose {
+            parentLifecycle.removeObserver(observer)
+            owner.destroy()
+        }
+    }
+
+    LaunchedEffect(isActive, parentLifecycle) {
+        owner.update(parentLifecycle.currentState, isActive)
+    }
+
+    return owner
+}
+
+private class MainPageLifecycleOwner : LifecycleOwner {
+
+    private val registry = LifecycleRegistry(this)
+
+    override val lifecycle: Lifecycle = registry
+
+    fun update(parentState: Lifecycle.State, isActive: Boolean) {
+        registry.currentState = when {
+            parentState == Lifecycle.State.DESTROYED -> Lifecycle.State.DESTROYED
+            parentState == Lifecycle.State.INITIALIZED -> Lifecycle.State.INITIALIZED
+            parentState == Lifecycle.State.CREATED -> Lifecycle.State.CREATED
+            isActive -> parentState
+            else -> Lifecycle.State.STARTED
+        }
+    }
+
+    fun destroy() {
+        registry.currentState = Lifecycle.State.DESTROYED
+    }
+}
+
 @Composable
 private fun BookshelfRailGroupMenu(
     expanded: Boolean,

+ 2 - 58
app/src/main/java/io/legado/app/ui/main/rss/RssScreen.kt

@@ -29,8 +29,6 @@ import androidx.compose.material.icons.filled.Subscriptions
 import androidx.compose.material.icons.filled.VerticalAlignTop
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -46,11 +44,6 @@ 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.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleEventObserver
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LifecycleRegistry
-import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import io.legado.app.R
 import io.legado.app.data.entities.RssSource
@@ -79,7 +72,6 @@ import org.koin.androidx.compose.koinViewModel
 @Composable
 fun RssScreen(
     viewModel: RssViewModel = koinViewModel(),
-    isActive: Boolean = true,
     onOpenSort: (sourceUrl: String, sortUrl: String?, key: String?) -> Unit,
     onOpenRead: (title: String?, origin: String, link: String?, openUrl: String?) -> Unit,
     onOpenRuleSub: () -> Unit,
@@ -96,7 +88,6 @@ fun RssScreen(
     val currentOnOpenRead by rememberUpdatedState(onOpenRead)
     val currentOnOpenRuleSub by rememberUpdatedState(onOpenRuleSub)
     val currentOnOpenFavorites by rememberUpdatedState(onOpenFavorites)
-    val pageLifecycleOwner = rememberRssPageLifecycleOwner(isActive)
 
     LaunchedEffect(viewModel) {
         viewModel.effects.collectLatest { effect ->
@@ -141,8 +132,7 @@ fun RssScreen(
         }
     }
 
-    CompositionLocalProvider(LocalLifecycleOwner provides pageLifecycleOwner) {
-        ListScaffold(
+    ListScaffold(
             title = stringResource(R.string.rss),
             state = uiState,
             subtitle = uiState.group.ifEmpty { stringResource(R.string.all) },
@@ -253,7 +243,6 @@ fun RssScreen(
                     )
                 }
             }
-        }
     }
 
     AppAlertDialog(
@@ -270,51 +259,6 @@ fun RssScreen(
     )
 }
 
-@Composable
-private fun rememberRssPageLifecycleOwner(isActive: Boolean): LifecycleOwner {
-    val parentLifecycle = LocalLifecycleOwner.current.lifecycle
-    val currentActive by rememberUpdatedState(isActive)
-    val owner = remember(parentLifecycle) { RssPageLifecycleOwner() }
-
-    DisposableEffect(parentLifecycle) {
-        val observer = LifecycleEventObserver { _, _ ->
-            owner.update(parentLifecycle.currentState, currentActive)
-        }
-        parentLifecycle.addObserver(observer)
-        owner.update(parentLifecycle.currentState, currentActive)
-        onDispose {
-            parentLifecycle.removeObserver(observer)
-            owner.destroy()
-        }
-    }
-
-    LaunchedEffect(isActive, parentLifecycle) {
-        owner.update(parentLifecycle.currentState, isActive)
-    }
-
-    return owner
-}
-
-private class RssPageLifecycleOwner : LifecycleOwner {
-
-    private val registry = LifecycleRegistry(this)
-
-    override val lifecycle: Lifecycle = registry
-
-    fun update(parentState: Lifecycle.State, isActive: Boolean) {
-        registry.currentState = when {
-            parentState == Lifecycle.State.DESTROYED -> Lifecycle.State.DESTROYED
-            !parentState.isAtLeast(Lifecycle.State.STARTED) -> Lifecycle.State.CREATED
-            isActive -> parentState
-            else -> Lifecycle.State.STARTED
-        }
-    }
-
-    fun destroy() {
-        registry.currentState = Lifecycle.State.DESTROYED
-    }
-}
-
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
 fun RssSourceGridItem(
@@ -341,7 +285,7 @@ fun RssSourceGridItem(
     ) {
         Box {
             SourceIcon(
-                path = source.sourceIcon.ifEmpty { R.drawable.image_rss },
+                path = source.sourceIcon,
                 sourceOrigin = source.sourceUrl,
                 modifier = Modifier.size(48.dp)
             )

+ 55 - 11
app/src/main/java/io/legado/app/ui/widget/components/image/sourceIcon/SourceIcon.kt

@@ -1,5 +1,6 @@
 package io.legado.app.ui.widget.components.image.sourceIcon
 
+import android.graphics.drawable.Animatable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.size
@@ -8,7 +9,7 @@ import androidx.compose.material.icons.filled.RssFeed
 import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -19,6 +20,9 @@ import androidx.compose.ui.draw.clip
 import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.unit.dp
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import coil.ImageLoader
 import coil.compose.AsyncImage
 import coil.request.ImageRequest
@@ -48,10 +52,43 @@ fun SourceIcon(
     }
 ) {
     val context = LocalContext.current
+    val lifecycle = LocalLifecycleOwner.current.lifecycle
     var imageLoaded by remember(path) { mutableStateOf(false) }
+    var animatedDrawable by remember(path) { mutableStateOf<Animatable?>(null) }
+    val imageRequest = remember(context, path, sourceOrigin, loadOnlyWifi) {
+        ImageRequest.Builder(context)
+            .data(path)
+            .crossfade(true)
+            .setParameter("sourceOrigin", sourceOrigin)
+            .setParameter("loadOnlyWifi", loadOnlyWifi)
+            .build()
+    }
+
+    DisposableEffect(lifecycle, animatedDrawable) {
+        val observer = LifecycleEventObserver { _, event ->
+            when (event) {
+                Lifecycle.Event.ON_RESUME -> {
+                    if (animatedDrawable?.isRunning == false) {
+                        animatedDrawable?.start()
+                    }
+                }
 
-    LaunchedEffect(path) {
-        imageLoaded = false
+                Lifecycle.Event.ON_PAUSE -> animatedDrawable?.stop()
+                else -> Unit
+            }
+        }
+        lifecycle.addObserver(observer)
+        if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
+            if (animatedDrawable?.isRunning == false) {
+                animatedDrawable?.start()
+            }
+        } else {
+            animatedDrawable?.stop()
+        }
+        onDispose {
+            lifecycle.removeObserver(observer)
+            animatedDrawable?.stop()
+        }
     }
 
     Box(
@@ -65,18 +102,25 @@ fun SourceIcon(
                 placeholderIcon()
             }
             AsyncImage(
-                model = ImageRequest.Builder(context)
-                    .data(path)
-                    .crossfade(true)
-                    .setParameter("sourceOrigin", sourceOrigin)
-                    .setParameter("loadOnlyWifi", loadOnlyWifi)
-                    .build(),
+                model = imageRequest,
                 imageLoader = imageLoader,
                 contentDescription = null,
                 contentScale = contentScale, // 不裁切
                 modifier = Modifier.fillMaxSize(),
-                onSuccess = { imageLoaded = true },
-                onError = { imageLoaded = false }
+                onSuccess = { state ->
+                    imageLoaded = true
+                    animatedDrawable = (state.result.drawable as? Animatable)?.also { drawable ->
+                        if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
+                            if (!drawable.isRunning) drawable.start()
+                        } else {
+                            drawable.stop()
+                        }
+                    }
+                },
+                onError = {
+                    imageLoaded = false
+                    animatedDrawable = null
+                }
             )
         }
     }