diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1bf0f3bb..5e1af6aa 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -22,7 +22,7 @@ android { minSdk = 24 targetSdk = 35 versionCode = 24 - versionName = "2.1-beta08" + versionName = "2.1-beta09" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" @@ -204,6 +204,7 @@ dependencies { implementation(libs.ffmpeg.kit) implementation(libs.coil.compose) + implementation(libs.coil.network) implementation(libs.coil.gif) implementation(libs.coil.svg) implementation(libs.coil.video) diff --git a/app/src/main/java/com/skyd/anivu/App.kt b/app/src/main/java/com/skyd/anivu/App.kt index 7e383101..e1ddff19 100644 --- a/app/src/main/java/com/skyd/anivu/App.kt +++ b/app/src/main/java/com/skyd/anivu/App.kt @@ -7,7 +7,7 @@ import com.skyd.anivu.ext.dataStore import com.skyd.anivu.ext.getOrDefault import com.skyd.anivu.model.preference.appearance.DarkModePreference import com.skyd.anivu.model.worker.deletearticle.listenerDeleteArticleFrequency -import com.skyd.anivu.model.worker.rsssync.listenerRssSyncFrequency +import com.skyd.anivu.model.worker.rsssync.listenerRssSyncConfig import com.skyd.anivu.util.CrashHandler import dagger.hilt.android.HiltAndroidApp @@ -22,7 +22,7 @@ class App : Application() { CrashHandler.init(this) - listenerRssSyncFrequency(this) + listenerRssSyncConfig(this) listenerDeleteArticleFrequency(this) } } diff --git a/app/src/main/java/com/skyd/anivu/di/CoilModule.kt b/app/src/main/java/com/skyd/anivu/di/CoilModule.kt index b856a576..efc594ff 100644 --- a/app/src/main/java/com/skyd/anivu/di/CoilModule.kt +++ b/app/src/main/java/com/skyd/anivu/di/CoilModule.kt @@ -1,8 +1,10 @@ package com.skyd.anivu.di import android.content.Context -import coil.ImageLoader -import coil.util.DebugLogger +import coil3.ImageLoader +import coil3.network.okhttp.OkHttpNetworkFetcherFactory +import coil3.request.crossfade +import coil3.util.DebugLogger import com.skyd.anivu.util.debug import dagger.Module import dagger.Provides @@ -21,7 +23,7 @@ object CoilModule { @ApplicationContext context: Context, okHttpClient: OkHttpClient, ): ImageLoader = ImageLoader.Builder(context) - .okHttpClient(okHttpClient) + .components { add(OkHttpNetworkFetcherFactory(callFactory = { okHttpClient })) } .crossfade(400) .apply { debug { logger(DebugLogger()) } } .build() diff --git a/app/src/main/java/com/skyd/anivu/ext/ContextExt.kt b/app/src/main/java/com/skyd/anivu/ext/ContextExt.kt index 2bccd56f..62f891be 100644 --- a/app/src/main/java/com/skyd/anivu/ext/ContextExt.kt +++ b/app/src/main/java/com/skyd/anivu/ext/ContextExt.kt @@ -14,11 +14,12 @@ import android.os.Build.VERSION.SDK_INT import android.view.Window import androidx.core.content.ContextCompat import androidx.core.content.pm.PackageInfoCompat -import coil.ImageLoader -import coil.decode.GifDecoder -import coil.decode.ImageDecoderDecoder -import coil.decode.SvgDecoder -import coil.decode.VideoFrameDecoder +import coil3.ImageLoader +import coil3.gif.AnimatedImageDecoder +import coil3.gif.GifDecoder +import coil3.network.okhttp.OkHttpNetworkFetcherFactory +import coil3.svg.SvgDecoder +import coil3.video.VideoFrameDecoder import okhttp3.Interceptor import okhttp3.OkHttpClient @@ -128,20 +129,20 @@ fun Context.imageLoaderBuilder(): ImageLoader.Builder { return ImageLoader.Builder(this) .components { if (SDK_INT >= 28) { - add(ImageDecoderDecoder.Factory()) + add(AnimatedImageDecoder.Factory()) } else { add(GifDecoder.Factory()) } add(SvgDecoder.Factory()) add(VideoFrameDecoder.Factory()) } - .okHttpClient { - OkHttpClient.Builder() - .addNetworkInterceptor(Interceptor { chain -> + .components { + add(OkHttpNetworkFetcherFactory(callFactory = { + OkHttpClient.Builder().addNetworkInterceptor(Interceptor { chain -> chain.proceed(chain.request()).newBuilder() .header("Cache-Control", "max-age=31536000,public") .build() - }) - .build() + }).build() + })) } } \ No newline at end of file diff --git a/app/src/main/java/com/skyd/anivu/ext/PointerInputScopeExt.kt b/app/src/main/java/com/skyd/anivu/ext/PointerInputScopeExt.kt index 71c93677..90843159 100644 --- a/app/src/main/java/com/skyd/anivu/ext/PointerInputScopeExt.kt +++ b/app/src/main/java/com/skyd/anivu/ext/PointerInputScopeExt.kt @@ -27,95 +27,89 @@ suspend fun PointerInputScope.detectDoubleFingerTransformGestures( onHorizontalDragCancel: () -> Unit = { }, onHorizontalDrag: (change: PointerInputChange, dragAmount: Float) -> Unit, onGesture: (centroid: Offset, pan: Offset, zoom: Float, rotation: Float) -> Unit, -) { - awaitEachGesture { - var rotation = 0f - var zoom = 1f - var pan = Offset.Zero - var singlePan = Offset.Zero - var pastTouchSlop = false - val touchSlop = viewConfiguration.touchSlop - var lockedToPanZoom = false +) = awaitEachGesture { + var rotation = 0f + var zoom = 1f + var pan = Offset.Zero + var singlePan = Offset.Zero + var pastTouchSlop = false + val touchSlop = viewConfiguration.touchSlop + var lockedToPanZoom = false - var horizontalDrag = false - var verticalDrag = false - var transformDrag = false + var horizontalDrag = false + var verticalDrag = false + var transformDrag = false - val firstDown = awaitFirstDown(requireUnconsumed = false) - var canceled: Boolean + val firstDown = awaitFirstDown(requireUnconsumed = false) + var canceled: Boolean - do { - val event = awaitPointerEvent() - canceled = event.changes.fastAny { it.isConsumed } - val count: Int = if (event.changes.size > 2) { - event.changes.takeIf { it.last().id != it.first().id }?.size ?: 1 - } else event.changes.size + do { + val event = awaitPointerEvent() + canceled = event.changes.fastAny { it.isConsumed } + if (canceled) continue + val count: Int = if (event.changes.size > 2) { + event.changes.takeIf { it.last().id != it.first().id }?.size ?: 1 + } else event.changes.size - if (!canceled) { - val zoomChange = event.calculateZoom() - val panChange = event.calculatePan() - val rotationChange = event.calculateRotation() - if (!pastTouchSlop) { - if (count == 1) { - singlePan += panChange - val singlePanMotion = singlePan.getDistance() - if (singlePanMotion > touchSlop) { - pastTouchSlop = true - if (abs(singlePan.x) > abs(singlePan.y)) { - horizontalDrag = true - onHorizontalDragStart(firstDown.position) - } else { - verticalDrag = true - onVerticalDragStart(firstDown.position) - } - } - } else if (count > 1) { - zoom *= zoomChange - rotation += rotationChange - pan += panChange + val zoomChange = event.calculateZoom() + val panChange = event.calculatePan() + val rotationChange = event.calculateRotation() + if (!pastTouchSlop) { + if (count == 1) { + singlePan += panChange + val singlePanMotion = singlePan.getDistance() + if (singlePanMotion > touchSlop) { + pastTouchSlop = true + if (abs(singlePan.x) > abs(singlePan.y)) { + horizontalDrag = true + onHorizontalDragStart(firstDown.position) + } else { + verticalDrag = true + onVerticalDragStart(firstDown.position) + } + } + } else if (count > 1) { + zoom *= zoomChange + rotation += rotationChange + pan += panChange - val centroidSize = event.calculateCentroidSize(useCurrent = false) - val zoomMotion = abs(1 - zoom) * centroidSize - val rotationMotion = abs(rotation * PI.toFloat() * centroidSize / 180f) - val panMotion = pan.getDistance() + val centroidSize = event.calculateCentroidSize(useCurrent = false) + val zoomMotion = abs(1 - zoom) * centroidSize + val rotationMotion = abs(rotation * PI.toFloat() * centroidSize / 180f) + val panMotion = pan.getDistance() - if (zoomMotion > touchSlop || - rotationMotion > touchSlop || - panMotion > touchSlop - ) { - transformDrag = true - pastTouchSlop = true - lockedToPanZoom = rotationMotion < touchSlop - } - } + if (zoomMotion > touchSlop || rotationMotion > touchSlop || panMotion > touchSlop) { + transformDrag = true + pastTouchSlop = true + lockedToPanZoom = rotationMotion < touchSlop } - if (pastTouchSlop) { - if (horizontalDrag) { - onHorizontalDrag(event.changes.first(), panChange.x) - } else if (verticalDrag) { - onVerticalDrag(event.changes.first(), panChange.y) - } else if (transformDrag) { - val centroid = event.calculateCentroid(useCurrent = false) - val effectiveRotation = if (lockedToPanZoom) 0f else rotationChange - if (effectiveRotation != 0f || - zoomChange != 1f || - panChange != Offset.Zero - ) { - onGesture(centroid, panChange, zoomChange, effectiveRotation) - } - } - event.changes.fastForEach { - if (it.positionChanged()) { - it.consume() - } - } + } + } + if (pastTouchSlop) { + if (horizontalDrag) { + onHorizontalDrag(event.changes.first(), panChange.x) + } else if (verticalDrag) { + onVerticalDrag(event.changes.first(), panChange.y) + } else if (transformDrag) { + val centroid = event.calculateCentroid(useCurrent = false) + val effectiveRotation = if (lockedToPanZoom) 0f else rotationChange + if (effectiveRotation != 0f || + zoomChange != 1f || + panChange != Offset.Zero + ) { + onGesture(centroid, panChange, zoomChange, effectiveRotation) + } + } + event.changes.fastForEach { + if (it.positionChanged()) { + it.consume() } } - } while (!canceled && event.changes.fastAny { it.pressed }) - if (horizontalDrag) { - if (canceled) onHorizontalDragCancel() else onHorizontalDragEnd() - } else if (verticalDrag) { - if (canceled) onVerticalDragCancel() else onVerticalDragEnd() } + } while (!canceled && event.changes.fastAny { it.pressed }) + if (horizontalDrag) { + if (canceled) onHorizontalDragCancel() else onHorizontalDragEnd() + } else if (verticalDrag) { + if (canceled) onVerticalDragCancel() else onVerticalDragEnd() } } \ No newline at end of file diff --git a/app/src/main/java/com/skyd/anivu/model/repository/ArticleRepository.kt b/app/src/main/java/com/skyd/anivu/model/repository/ArticleRepository.kt index 99d5f8e4..b47182f7 100644 --- a/app/src/main/java/com/skyd/anivu/model/repository/ArticleRepository.kt +++ b/app/src/main/java/com/skyd/anivu/model/repository/ArticleRepository.kt @@ -101,24 +101,21 @@ class ArticleRepository @Inject constructor( val requests = mutableListOf>() feedUrls.forEach { feedUrl -> requests += async { - val articleBeanListAsync = async { - runCatching { - rssHelper.queryRssXml( - feed = feedDao.getFeed(feedUrl), - latestLink = articleDao.queryLatestByFeedUrl(feedUrl)?.link, - )?.also { feedWithArticle -> - feedDao.updateFeed(feedWithArticle.feed) - }?.articles - }.onFailure { e -> - if (e !is CancellationException) { - e.printStackTrace() - (feedUrl + "\n" + e.message).showToast() - } - }.getOrNull() - } - val articleBeanList = articleBeanListAsync.await() ?: return@async + val articleBeanList = runCatching { + rssHelper.queryRssXml( + feed = feedDao.getFeed(feedUrl), + latestLink = articleDao.queryLatestByFeedUrl(feedUrl)?.link, + )?.also { feedWithArticle -> + feedDao.updateFeed(feedWithArticle.feed) + }?.articles + }.onFailure { e -> + if (e !is CancellationException) { + e.printStackTrace() + (feedUrl + "\n" + e.message).showToast() + } + }.getOrNull() - if (articleBeanList.isEmpty()) return@async + if (articleBeanList.isNullOrEmpty()) return@async articleDao.insertListIfNotExist(articleBeanList.map { articleWithEnclosure -> if (articleWithEnclosure.article.feedUrl != feedUrl) { diff --git a/app/src/main/java/com/skyd/anivu/model/repository/ReadRepository.kt b/app/src/main/java/com/skyd/anivu/model/repository/ReadRepository.kt index 1c508aa2..8c370f96 100644 --- a/app/src/main/java/com/skyd/anivu/model/repository/ReadRepository.kt +++ b/app/src/main/java/com/skyd/anivu/model/repository/ReadRepository.kt @@ -1,9 +1,9 @@ package com.skyd.anivu.model.repository -import coil.request.CachePolicy -import coil.request.ErrorResult -import coil.request.ImageRequest -import coil.request.SuccessResult +import coil3.request.CachePolicy +import coil3.request.ErrorResult +import coil3.request.ImageRequest +import coil3.request.SuccessResult import com.skyd.anivu.appContext import com.skyd.anivu.base.BaseRepository import com.skyd.anivu.config.Const.TEMP_PICTURES_DIR diff --git a/app/src/main/java/com/skyd/anivu/model/repository/SearchRepository.kt b/app/src/main/java/com/skyd/anivu/model/repository/SearchRepository.kt index 60054db6..6f35438e 100644 --- a/app/src/main/java/com/skyd/anivu/model/repository/SearchRepository.kt +++ b/app/src/main/java/com/skyd/anivu/model/repository/SearchRepository.kt @@ -27,6 +27,7 @@ import dagger.hilt.components.SingletonComponent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn import javax.inject.Inject @@ -63,7 +64,7 @@ class SearchRepository @Inject constructor( feedUrls: List, articleIds: List, ): Flow> { - return searchQuery.flatMapLatest { query -> + return searchQuery.debounce(70).flatMapLatest { query -> Pager(pagingConfig) { articleDao.getArticlePagingSource(genSql( tableName = ARTICLE_TABLE_NAME, diff --git a/app/src/main/java/com/skyd/anivu/model/worker/rsssync/Util.kt b/app/src/main/java/com/skyd/anivu/model/worker/rsssync/Util.kt index 1cfebbbd..9aec5e19 100644 --- a/app/src/main/java/com/skyd/anivu/model/worker/rsssync/Util.kt +++ b/app/src/main/java/com/skyd/anivu/model/worker/rsssync/Util.kt @@ -35,7 +35,7 @@ private data class RssSyncConfiguration( val requireBatteryNotLow: Boolean, ) -fun listenerRssSyncFrequency(context: Context) { +fun listenerRssSyncConfig(context: Context) { coroutineScope.launch { context.dataStore.data.map { RssSyncConfiguration( @@ -60,7 +60,7 @@ fun listenerRssSyncFrequency(context: Context) { val requireBatteryNotLow = rssSyncConfiguration.requireBatteryNotLow if (rssSyncFrequency == RssSyncFrequencyPreference.MANUAL) { - if (workInfo == null || !workInfo.state.isFinished) { + if (workInfo != null && !workInfo.state.isFinished) { stopRssSyncWorker(context) } } else { @@ -95,7 +95,7 @@ fun listenerRssSyncFrequency(context: Context) { } } -fun updateRssSyncWorker( +private fun updateRssSyncWorker( context: Context, rssSyncFrequency: Long, requireWifi: Boolean, @@ -128,7 +128,7 @@ fun updateRssSyncWorker( ) } -fun startRssSyncWorker( +private fun startRssSyncWorker( context: Context, rssSyncFrequency: Long, requireWifi: Boolean, @@ -147,11 +147,11 @@ fun startRssSyncWorker( ) } -fun stopRssSyncWorker(context: Context) { +private fun stopRssSyncWorker(context: Context) { WorkManager.getInstance(context).cancelUniqueWork(RssSyncWorker.UNIQUE_WORK_NAME) } -fun getRssSyncWorkRequest( +private fun getRssSyncWorkRequest( rssSyncFrequency: Long, requireWifi: Boolean, requireCharging: Boolean, diff --git a/app/src/main/java/com/skyd/anivu/ui/component/AniVuFloatingActionButton.kt b/app/src/main/java/com/skyd/anivu/ui/component/AniVuFloatingActionButton.kt index dab2a910..b619a84f 100644 --- a/app/src/main/java/com/skyd/anivu/ui/component/AniVuFloatingActionButton.kt +++ b/app/src/main/java/com/skyd/anivu/ui/component/AniVuFloatingActionButton.kt @@ -93,7 +93,7 @@ fun AniVuFloatingActionButton( } else { TooltipBox( modifier = modifier, - positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + positionProvider = TooltipDefaults.rememberTooltipPositionProvider(), tooltip = { PlainTooltip { Text(contentDescription) @@ -143,11 +143,9 @@ fun AniVuExtendedFloatingActionButton( } else { TooltipBox( modifier = modifier, - positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + positionProvider = TooltipDefaults.rememberTooltipPositionProvider(), tooltip = { - PlainTooltip { - Text(contentDescription) - } + PlainTooltip { Text(contentDescription) } }, state = rememberTooltipState() ) { diff --git a/app/src/main/java/com/skyd/anivu/ui/component/AniVuIconButton.kt b/app/src/main/java/com/skyd/anivu/ui/component/AniVuIconButton.kt index 575112e9..f3f1fb0a 100644 --- a/app/src/main/java/com/skyd/anivu/ui/component/AniVuIconButton.kt +++ b/app/src/main/java/com/skyd/anivu/ui/component/AniVuIconButton.kt @@ -95,7 +95,7 @@ fun AniVuIconButton( } else { TooltipBox( modifier = modifier, - positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + positionProvider = TooltipDefaults.rememberTooltipPositionProvider(), tooltip = { PlainTooltip { Text(contentDescription) @@ -162,7 +162,7 @@ fun AniVuIconToggleButton( } else { TooltipBox( modifier = modifier, - positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + positionProvider = TooltipDefaults.rememberTooltipPositionProvider(), tooltip = { PlainTooltip { Text(contentDescription) diff --git a/app/src/main/java/com/skyd/anivu/ui/component/AniVuImage.kt b/app/src/main/java/com/skyd/anivu/ui/component/AniVuImage.kt index bc66eba8..00cad04b 100644 --- a/app/src/main/java/com/skyd/anivu/ui/component/AniVuImage.kt +++ b/app/src/main/java/com/skyd/anivu/ui/component/AniVuImage.kt @@ -8,13 +8,15 @@ import androidx.compose.ui.graphics.DefaultAlpha import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.compose.LocalLifecycleOwner -import coil.ComponentRegistry -import coil.EventListener -import coil.ImageLoader -import coil.compose.AsyncImage -import coil.request.CachePolicy -import coil.request.ImageRequest -import coil.util.DebugLogger +import coil3.ComponentRegistry +import coil3.EventListener +import coil3.ImageLoader +import coil3.compose.AsyncImage +import coil3.request.CachePolicy +import coil3.request.ImageRequest +import coil3.request.crossfade +import coil3.request.lifecycle +import coil3.util.DebugLogger import com.skyd.anivu.ext.imageLoaderBuilder diff --git a/app/src/main/java/com/skyd/anivu/ui/component/Placeholder.kt b/app/src/main/java/com/skyd/anivu/ui/component/Placeholder.kt index e3ddb1bb..4f2c106c 100644 --- a/app/src/main/java/com/skyd/anivu/ui/component/Placeholder.kt +++ b/app/src/main/java/com/skyd/anivu/ui/component/Placeholder.kt @@ -31,17 +31,34 @@ fun EmptyPlaceholder( ) } +@Composable +fun ErrorPlaceholder( + modifier: Modifier = Modifier, + text: String, + contentPadding: PaddingValues = PaddingValues(), +) { + AnimatedPlaceholder( + modifier = modifier, + contentPadding = contentPadding, + resId = R.raw.lottie_error_1, + tip = text, + maxLines = 4, + ) +} + @Composable fun AnimatedPlaceholder( modifier: Modifier = Modifier, contentPadding: PaddingValues = PaddingValues(), @androidx.annotation.RawRes resId: Int, tip: String, + maxLines: Int = Int.MAX_VALUE, ) { WithTextPlaceholder( modifier = modifier, contentPadding = contentPadding, text = tip, + maxLines = maxLines, ) { AniVuLottieAnimation(resId = resId) } @@ -52,6 +69,7 @@ fun WithTextPlaceholder( modifier: Modifier = Modifier, contentPadding: PaddingValues = PaddingValues(), text: String, + maxLines: Int = Int.MAX_VALUE, content: @Composable BoxScope.() -> Unit, ) { BasePlaceholder( @@ -71,6 +89,7 @@ fun WithTextPlaceholder( modifier = Modifier.padding(top = 10.dp), text = text, textAlign = TextAlign.Center, + maxLines = maxLines, style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onBackground, ) diff --git a/app/src/main/java/com/skyd/anivu/ui/component/html/ImageGetter.kt b/app/src/main/java/com/skyd/anivu/ui/component/html/ImageGetter.kt index f641e9d3..866a7c41 100644 --- a/app/src/main/java/com/skyd/anivu/ui/component/html/ImageGetter.kt +++ b/app/src/main/java/com/skyd/anivu/ui/component/html/ImageGetter.kt @@ -9,10 +9,13 @@ import android.text.Html import androidx.appcompat.content.res.AppCompatResources import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner -import coil.drawable.ScaleDrawable -import coil.request.ErrorResult -import coil.request.ImageRequest -import coil.request.SuccessResult +import coil3.asDrawable +import coil3.request.ErrorResult +import coil3.request.ImageRequest +import coil3.request.SuccessResult +import coil3.request.error +import coil3.request.placeholder +import coil3.size.ScaleDrawable import com.skyd.anivu.R import com.skyd.anivu.ext.imageLoaderBuilder @@ -59,13 +62,15 @@ class ImageGetter( .error(R.drawable.ic_error_24) .listener( onSuccess = { request, result -> - preProcessDrawable(result.drawable) - setAndResizeDrawable(result.drawable) + val resultDrawable = result.image.asDrawable(context.resources) + preProcessDrawable(resultDrawable) + setAndResizeDrawable(resultDrawable) onSuccess(request, result) }, onError = { request, result -> - if (result.drawable != null) { - setAndResizeDrawable(result.drawable!!) + val resultDrawable = result.image?.asDrawable(context.resources) + if (resultDrawable != null) { + setAndResizeDrawable(resultDrawable) } onError(request, result) }, diff --git a/app/src/main/java/com/skyd/anivu/ui/mpv/controller/PlayerController.kt b/app/src/main/java/com/skyd/anivu/ui/mpv/controller/PlayerController.kt index dd79e62c..56c347de 100644 --- a/app/src/main/java/com/skyd/anivu/ui/mpv/controller/PlayerController.kt +++ b/app/src/main/java/com/skyd/anivu/ui/mpv/controller/PlayerController.kt @@ -131,7 +131,8 @@ internal fun PlayerController( controllerWidth = it.size.width controllerHeight = it.size.height controllerLayoutCoordinates = it - }// detectControllerGestures should be called before detectPressGestures + } + // detectControllerGestures should be called before detectPressGestures // to avoid responding to swipes when long pressing .detectControllerGestures( enabled = enabled, diff --git a/app/src/main/java/com/skyd/anivu/ui/screen/about/AboutScreen.kt b/app/src/main/java/com/skyd/anivu/ui/screen/about/AboutScreen.kt index 2c5bc096..a9f13b74 100644 --- a/app/src/main/java/com/skyd/anivu/ui/screen/about/AboutScreen.kt +++ b/app/src/main/java/com/skyd/anivu/ui/screen/about/AboutScreen.kt @@ -64,7 +64,6 @@ import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import coil.compose.AsyncImage import com.skyd.anivu.R import com.skyd.anivu.config.Const import com.skyd.anivu.ext.getAppVersionName @@ -73,16 +72,17 @@ import com.skyd.anivu.ext.openBrowser import com.skyd.anivu.ext.plus import com.skyd.anivu.model.bean.OtherWorksBean import com.skyd.anivu.ui.component.AniVuIconButton +import com.skyd.anivu.ui.component.AniVuImage import com.skyd.anivu.ui.component.AniVuTopBar import com.skyd.anivu.ui.component.AniVuTopBarStyle import com.skyd.anivu.ui.component.dialog.AniVuDialog import com.skyd.anivu.ui.component.shape.CloverShape import com.skyd.anivu.ui.component.shape.CurlyCornerShape import com.skyd.anivu.ui.component.shape.SquircleShape -import com.skyd.anivu.ui.screen.about.update.UpdateDialog -import com.skyd.anivu.ui.screen.about.license.LICENSE_SCREEN_ROUTE import com.skyd.anivu.ui.local.LocalNavController import com.skyd.anivu.ui.local.LocalWindowSizeClass +import com.skyd.anivu.ui.screen.about.license.LICENSE_SCREEN_ROUTE +import com.skyd.anivu.ui.screen.about.update.UpdateDialog import kotlinx.coroutines.launch import java.util.Calendar @@ -458,7 +458,7 @@ private fun OtherWorksItem( .padding(15.dp) ) { Row(verticalAlignment = Alignment.CenterVertically) { - AsyncImage( + AniVuImage( modifier = Modifier .size(30.dp) .aspectRatio(1f), diff --git a/app/src/main/java/com/skyd/anivu/ui/screen/article/Article1Item.kt b/app/src/main/java/com/skyd/anivu/ui/screen/article/Article1Item.kt index f6b992a3..aadcbef0 100644 --- a/app/src/main/java/com/skyd/anivu/ui/screen/article/Article1Item.kt +++ b/app/src/main/java/com/skyd/anivu/ui/screen/article/Article1Item.kt @@ -60,18 +60,18 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.navigation.NavController -import coil.EventListener -import coil.request.ErrorResult -import coil.request.ImageRequest +import coil3.EventListener +import coil3.request.ErrorResult +import coil3.request.ImageRequest import com.skyd.anivu.R import com.skyd.anivu.ext.dataStore import com.skyd.anivu.ext.getOrDefault import com.skyd.anivu.ext.readable import com.skyd.anivu.ext.toDateTimeString -import com.skyd.anivu.model.bean.feed.FeedBean import com.skyd.anivu.model.bean.article.ArticleBean import com.skyd.anivu.model.bean.article.ArticleWithEnclosureBean import com.skyd.anivu.model.bean.article.ArticleWithFeed +import com.skyd.anivu.model.bean.feed.FeedBean import com.skyd.anivu.model.preference.behavior.article.ArticleSwipeActionPreference import com.skyd.anivu.model.preference.behavior.article.ArticleSwipeLeftActionPreference import com.skyd.anivu.model.preference.behavior.article.ArticleSwipeRightActionPreference @@ -503,7 +503,7 @@ fun FeedIcon(modifier: Modifier = Modifier, data: FeedBean, size: Dp = 22.dp) { .size(size) .clip(CircleShape), model = icon, - imageLoader = rememberAniVuImageLoader(listener = object : EventListener { + imageLoader = rememberAniVuImageLoader(listener = object : EventListener() { override fun onError(request: ImageRequest, result: ErrorResult) { if (icon == data.customIcon) { icon = data.icon diff --git a/app/src/main/java/com/skyd/anivu/ui/screen/article/ArticleScreen.kt b/app/src/main/java/com/skyd/anivu/ui/screen/article/ArticleScreen.kt index 3c28adbb..da46076d 100644 --- a/app/src/main/java/com/skyd/anivu/ui/screen/article/ArticleScreen.kt +++ b/app/src/main/java/com/skyd/anivu/ui/screen/article/ArticleScreen.kt @@ -18,6 +18,7 @@ import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.foundation.lazy.grid.LazyVerticalGrid @@ -79,6 +80,7 @@ import com.skyd.anivu.ui.component.AniVuTopBar import com.skyd.anivu.ui.component.BackIcon import com.skyd.anivu.ui.component.CircularProgressPlaceholder import com.skyd.anivu.ui.component.EmptyPlaceholder +import com.skyd.anivu.ui.component.ErrorPlaceholder import com.skyd.anivu.ui.component.dialog.AniVuDialog import com.skyd.anivu.ui.component.dialog.WaitingDialog import com.skyd.anivu.ui.local.LocalArticleItemMinWidth @@ -367,8 +369,16 @@ private fun Content( bottom = contentPadding.calculateBottomPadding(), ) + PaddingValues(vertical = 4.dp) when (val articleListState = uiState.articleListState) { - is ArticleListState.Failed -> Unit - is ArticleListState.Init -> CircularProgressPlaceholder(contentPadding = currentContentPadding) + is ArticleListState.Init -> CircularProgressPlaceholder( + contentPadding = currentContentPadding, + ) + + is ArticleListState.Failed -> ErrorPlaceholder( + modifier = Modifier.sizeIn(maxHeight = 200.dp), + text = articleListState.msg, + contentPadding = currentContentPadding, + ) + is ArticleListState.Success -> ArticleList( modifier = Modifier.nestedScroll(nestedScrollConnection), articles = articleListState.articlePagingDataFlow.collectAsLazyPagingItems(), diff --git a/app/src/main/java/com/skyd/anivu/ui/screen/media/list/Media1Item.kt b/app/src/main/java/com/skyd/anivu/ui/screen/media/list/Media1Item.kt index 8acf623d..24ed7485 100644 --- a/app/src/main/java/com/skyd/anivu/ui/screen/media/list/Media1Item.kt +++ b/app/src/main/java/com/skyd/anivu/ui/screen/media/list/Media1Item.kt @@ -39,10 +39,12 @@ import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnitType import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.LocalLifecycleOwner -import coil.EventListener -import coil.request.CachePolicy -import coil.request.ErrorResult -import coil.request.ImageRequest +import coil3.EventListener +import coil3.request.CachePolicy +import coil3.request.ErrorResult +import coil3.request.ImageRequest +import coil3.request.crossfade +import coil3.request.lifecycle import com.skyd.anivu.R import com.skyd.anivu.ext.fileSize import com.skyd.anivu.ext.openWith @@ -129,7 +131,7 @@ fun Media1Item( .crossfade(true) .build() }, - imageLoader = rememberAniVuImageLoader(listener = object : EventListener { + imageLoader = rememberAniVuImageLoader(listener = object : EventListener() { override fun onError(request: ImageRequest, result: ErrorResult) { showThumbnail = false } diff --git a/app/src/main/java/com/skyd/anivu/ui/screen/read/ReadScreen.kt b/app/src/main/java/com/skyd/anivu/ui/screen/read/ReadScreen.kt index 6e05f545..85d9fa60 100644 --- a/app/src/main/java/com/skyd/anivu/ui/screen/read/ReadScreen.kt +++ b/app/src/main/java/com/skyd/anivu/ui/screen/read/ReadScreen.kt @@ -75,10 +75,10 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavController import androidx.navigation.NavOptions -import coil.EventListener -import coil.decode.VideoFrameDecoder -import coil.request.ErrorResult -import coil.request.ImageRequest +import coil3.EventListener +import coil3.request.ErrorResult +import coil3.request.ImageRequest +import coil3.video.VideoFrameDecoder import com.skyd.anivu.R import com.skyd.anivu.base.mvi.MviEventListener import com.skyd.anivu.base.mvi.getDispatcher @@ -565,7 +565,7 @@ private fun MediaCover( AniVuImage( modifier = Modifier.fillMaxHeight(), imageLoader = rememberAniVuImageLoader( - listener = object : EventListener { + listener = object : EventListener() { override fun onError(request: ImageRequest, result: ErrorResult) { if (cover != null && realImage != cover) { realImage = cover diff --git a/app/src/main/java/com/skyd/anivu/util/favicon/FaviconExtractor.kt b/app/src/main/java/com/skyd/anivu/util/favicon/FaviconExtractor.kt index 5de45bec..01ee258d 100644 --- a/app/src/main/java/com/skyd/anivu/util/favicon/FaviconExtractor.kt +++ b/app/src/main/java/com/skyd/anivu/util/favicon/FaviconExtractor.kt @@ -20,7 +20,7 @@ class FaviconExtractor @Inject constructor( fun extractFavicon(url: String): String? = runBlocking { extractors - .map { async { it.intercept(url) } } + .map { async { it.extract(url) } } .map { it.await() } .flatten() .fastMaxBy { diff --git a/app/src/main/java/com/skyd/anivu/util/favicon/extractor/BaseUrlIconTagExtractor.kt b/app/src/main/java/com/skyd/anivu/util/favicon/extractor/BaseUrlIconTagExtractor.kt index 570c12b0..dfad0f4d 100644 --- a/app/src/main/java/com/skyd/anivu/util/favicon/extractor/BaseUrlIconTagExtractor.kt +++ b/app/src/main/java/com/skyd/anivu/util/favicon/extractor/BaseUrlIconTagExtractor.kt @@ -7,9 +7,9 @@ import javax.inject.Inject class BaseUrlIconTagExtractor @Inject constructor( retrofit: Retrofit, ) : IconTagExtractor(retrofit) { - override fun intercept(url: String): List = runBlocking { + override fun extract(url: String): List = runBlocking { baseUrl(url) - ?.let { base -> super.intercept(base) } + ?.let { base -> super.extract(base) } .orEmpty() } } \ No newline at end of file diff --git a/app/src/main/java/com/skyd/anivu/util/favicon/extractor/Extractor.kt b/app/src/main/java/com/skyd/anivu/util/favicon/extractor/Extractor.kt index 35e191a1..ea191b7e 100644 --- a/app/src/main/java/com/skyd/anivu/util/favicon/extractor/Extractor.kt +++ b/app/src/main/java/com/skyd/anivu/util/favicon/extractor/Extractor.kt @@ -10,7 +10,7 @@ interface Extractor { fun Response.isSvg() = headers()["Content-Type"]?.contains("svg", ignoreCase = true) == true - fun intercept(url: String): List + fun extract(url: String): List data class IconData( val url: String, diff --git a/app/src/main/java/com/skyd/anivu/util/favicon/extractor/HardCodedExtractor.kt b/app/src/main/java/com/skyd/anivu/util/favicon/extractor/HardCodedExtractor.kt index 9efb627a..30c9b1f0 100644 --- a/app/src/main/java/com/skyd/anivu/util/favicon/extractor/HardCodedExtractor.kt +++ b/app/src/main/java/com/skyd/anivu/util/favicon/extractor/HardCodedExtractor.kt @@ -16,7 +16,7 @@ class HardCodedExtractor @Inject constructor( "/apple-touch-icon-precomposed.png", ) - override fun intercept(url: String): List = runBlocking { + override fun extract(url: String): List = runBlocking { try { val baseUrl = baseUrl(url) ?: return@runBlocking emptyList() val request = mutableListOf>() diff --git a/app/src/main/java/com/skyd/anivu/util/favicon/extractor/IconTagExtractor.kt b/app/src/main/java/com/skyd/anivu/util/favicon/extractor/IconTagExtractor.kt index 9192bffa..c0d55919 100644 --- a/app/src/main/java/com/skyd/anivu/util/favicon/extractor/IconTagExtractor.kt +++ b/app/src/main/java/com/skyd/anivu/util/favicon/extractor/IconTagExtractor.kt @@ -14,7 +14,7 @@ import javax.inject.Inject open class IconTagExtractor @Inject constructor( private val retrofit: Retrofit, ) : Extractor { - override fun intercept(url: String): List = runBlocking { + override fun extract(url: String): List = runBlocking { try { val html = retrofit.create(HttpService::class.java).body(url).run { source().use { source -> diff --git a/app/src/main/res/raw/lottie_error_1.lottie b/app/src/main/res/raw/lottie_error_1.lottie new file mode 100644 index 00000000..a9be8ed0 Binary files /dev/null and b/app/src/main/res/raw/lottie_error_1.lottie differ diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 27571538..440fa0fe 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,10 +1,10 @@ [versions] adaptive = "1.0.0" -coil = "2.7.0" +coil = "3.0.2" hilt = "2.52" libtorrent4j = "2.1.0-31" -composeMaterial = "1.7.4" -composeMaterial3 = "1.4.0-alpha02" +composeMaterial = "1.7.5" +composeMaterial3 = "1.4.0-alpha04" okhttp3 = "4.12.0" rome = "2.1.0" room = "2.6.1" @@ -12,12 +12,13 @@ room = "2.6.1" kotlin = "2.0.21" [libraries] -androidx-core-ktx = { module = "androidx.core:core-ktx", version = "1.13.1" } + +androidx-core-ktx = { module = "androidx.core:core-ktx", version = "1.15.0" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.0" } androidx-activity-ktx = { module = "androidx.activity:activity-ktx", version = "1.9.3" } androidx-constraintlayout-compose = { module = "androidx.constraintlayout:constraintlayout-compose", version = "1.1.0-rc01" } -androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version = "2.8.3" } -androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version = "2.8.6" } +androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version = "2.8.4" } +androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version = "2.8.7" } androidx-compose-ui = { module = "androidx.compose.ui:ui", version.ref = "composeMaterial" } androidx-compose-material = { module = "androidx.compose.material:material", version.ref = "composeMaterial" } androidx-compose-icons = { module = "androidx.compose.material:material-icons-extended", version.ref = "composeMaterial" } @@ -55,12 +56,13 @@ kotlinx-coroutines-guava = { module = "org.jetbrains.kotlinx:kotlinx-coroutines- aniyomi-mpv-lib = { module = "com.github.aniyomiorg:aniyomi-mpv-lib", version = "1.15.n" } ffmpeg-kit = { module = "com.github.jmir1:ffmpeg-kit", version = "1.15" } -coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" } -coil-gif = { module = "io.coil-kt:coil-gif", version.ref = "coil" } -coil-svg = { module = "io.coil-kt:coil-svg", version.ref = "coil" } -coil-video = { module = "io.coil-kt:coil-video", version.ref = "coil" } +coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } +coil-network = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" } +coil-gif = { module = "io.coil-kt.coil3:coil-gif", version.ref = "coil" } +coil-svg = { module = "io.coil-kt.coil3:coil-svg", version.ref = "coil" } +coil-video = { module = "io.coil-kt.coil3:coil-video", version.ref = "coil" } -lottie-compose = { module = "com.airbnb.android:lottie-compose", version = "6.5.2" } +lottie-compose = { module = "com.airbnb.android:lottie-compose", version = "6.6.0" } rome = { module = "com.rometools:rome", version.ref = "rome" } rome-modules = { module = "com.rometools:rome-modules", version.ref = "rome" } @@ -80,7 +82,7 @@ androidx-junit = { module = "androidx.test.ext:junit", version = "1.2.1" } androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version = "3.6.1" } [plugins] -android-application = { id = "com.android.application", version = "8.7.1" } +android-application = { id = "com.android.application", version = "8.7.2" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }