diff --git a/app/src/main/java/me/ash/reader/domain/service/FeverRssService.kt b/app/src/main/java/me/ash/reader/domain/service/FeverRssService.kt index e2cf7ca5e..4431a34a8 100644 --- a/app/src/main/java/me/ash/reader/domain/service/FeverRssService.kt +++ b/app/src/main/java/me/ash/reader/domain/service/FeverRssService.kt @@ -92,10 +92,9 @@ class FeverRssService @Inject constructor( * obtained is 0 or their quantity exceeds 250, at which point the pulling process stops. * * 1. Fetch the Fever groups - * 2. Fetch the Fever feeds + * 2. Fetch the Fever feeds (including favicons) * 3. Fetch the Fever articles * 4. Synchronize read/unread and starred/un-starred items - * 5. TODO: Fetch the Fever favicons */ override suspend fun sync(coroutineWorker: CoroutineWorker): ListenableWorker.Result = supervisorScope { coroutineWorker.setProgress(SyncWorker.setIsSyncing(true)) @@ -127,6 +126,9 @@ class FeverRssService @Inject constructor( } } } + + // Fetch the Fever favicons + val faviconsById = feverAPI.getFavicons().favicons?.associateBy { it.id } ?: emptyMap() feedDao.insertOrUpdate( feedsBody.feeds?.map { Feed( @@ -135,6 +137,7 @@ class FeverRssService @Inject constructor( url = it.url!!, groupId = accountId.spacerDollar(feedsGroupsMap[it.id.toString()]!!), accountId = accountId, + icon = faviconsById[it.favicon_id]?.data ) } ?: emptyList() ) @@ -191,7 +194,6 @@ class FeverRssService @Inject constructor( } } - // TODO: 5. Fetch the Fever favicons Log.i("RLog", "onCompletion: ${System.currentTimeMillis() - preTime}") accountDao.update(account.apply { diff --git a/app/src/main/java/me/ash/reader/infrastructure/rss/provider/fever/FeverDTO.kt b/app/src/main/java/me/ash/reader/infrastructure/rss/provider/fever/FeverDTO.kt index ffe7f9864..2947cd8d1 100644 --- a/app/src/main/java/me/ash/reader/infrastructure/rss/provider/fever/FeverDTO.kt +++ b/app/src/main/java/me/ash/reader/infrastructure/rss/provider/fever/FeverDTO.kt @@ -101,12 +101,12 @@ object FeverDTO { val api_version: Int?, val auth: Int?, val last_refreshed_on_time: Long?, - val favicons: Map, + val favicons: List?, ) data class Favicon( - val mime_type: String, - val data: String, + val id: Int, + val data: String?, ) /** diff --git a/app/src/main/java/me/ash/reader/ui/component/FeedIcon.kt b/app/src/main/java/me/ash/reader/ui/component/FeedIcon.kt index 907e902af..d404a94a1 100644 --- a/app/src/main/java/me/ash/reader/ui/component/FeedIcon.kt +++ b/app/src/main/java/me/ash/reader/ui/component/FeedIcon.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import me.ash.reader.ui.component.base.Base64Image import me.ash.reader.ui.component.base.RYAsyncImage @Composable @@ -26,7 +27,7 @@ fun FeedIcon( if (iconUrl == null) { Box( modifier = Modifier - .size(20.dp) + .size(size) .clip(CircleShape) .background(MaterialTheme.colorScheme.primary), contentAlignment = Alignment.Center, @@ -38,6 +39,14 @@ fun FeedIcon( fontSize = 10.sp, ) } + } + // e.g. image/gif;base64,R0lGODlh... + else if ("^image/.*;base64,.*".toRegex().matches(iconUrl)) { + Base64Image( + modifier = Modifier + .size(size) + .clip(CircleShape), + base64Uri = iconUrl) } else { RYAsyncImage( modifier = Modifier diff --git a/app/src/main/java/me/ash/reader/ui/component/base/Base64Image.kt b/app/src/main/java/me/ash/reader/ui/component/base/Base64Image.kt new file mode 100644 index 000000000..0eef81349 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/component/base/Base64Image.kt @@ -0,0 +1,30 @@ +package me.ash.reader.ui.component.base + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.util.Base64 +import androidx.compose.foundation.Image +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.asImageBitmap + +@Composable +fun Base64Image( + modifier: Modifier = Modifier, + base64Uri: String +) { + val bitmap = base64ToBitmap(base64Uri) + val imageBitmap = bitmap.asImageBitmap() + + Image( + bitmap = imageBitmap, + modifier = modifier, + contentDescription = null + ) +} + +fun base64ToBitmap(base64String: String): Bitmap { + val base64Data = base64String.substringAfter("base64,") + val imageBytes = Base64.decode(base64Data, Base64.DEFAULT) + return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) +}