commit 7283063daf743a355a9775efb89cf3a7f2fede60
parent b0434f44f437162979785dc1fc0bc1c033825812
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date: Sun, 3 Mar 2024 22:02:37 +0100
feat(app/logged_stories): save from cache
Diffstat:
1 file changed, 91 insertions(+), 54 deletions(-)
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/social/LoggedStories.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/social/LoggedStories.kt
@@ -9,9 +9,12 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ErrorOutline
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
@@ -51,7 +54,7 @@ import javax.crypto.spec.SecretKeySpec
import kotlin.math.absoluteValue
class LoggedStories : Routes.Route() {
- @OptIn(ExperimentalCoilApi::class)
+ @OptIn(ExperimentalCoilApi::class, ExperimentalLayoutApi::class)
override val content: @Composable (NavBackStackEntry) -> Unit = content@{ navBackStackEntry ->
val userId = navBackStackEntry.arguments?.getString("id") ?: return@content
@@ -65,9 +68,43 @@ class LoggedStories : Routes.Route() {
var lastStoryTimestamp by remember { mutableLongStateOf(Long.MAX_VALUE) }
var selectedStory by remember { mutableStateOf<StoryData?>(null) }
- var coilCacheFile by remember { mutableStateOf<File?>(null) }
+ var coilCachedFile by remember { mutableStateOf<File?>(null) }
selectedStory?.let { story ->
+ fun downloadSelectedStory(
+ inputMedia: InputMedia,
+ ) {
+ val mediaAuthor = friendInfo?.mutableUsername ?: userId
+ val uniqueHash = (selectedStory?.url ?: UUID.randomUUID().toString()).longHashCode().absoluteValue.toString(16)
+
+ DownloadProcessor(
+ remoteSideContext = context,
+ callback = object: DownloadCallback.Default() {
+ override fun onSuccess(outputPath: String?) {
+ context.shortToast("Downloaded to $outputPath")
+ }
+
+ override fun onFailure(message: String?, throwable: String?) {
+ context.shortToast("Failed to download $message")
+ }
+ }
+ ).enqueue(DownloadRequest(
+ inputMedias = arrayOf(inputMedia)
+ ), DownloadMetadata(
+ mediaIdentifier = uniqueHash,
+ outputPath = createNewFilePath(
+ context.config.root,
+ uniqueHash,
+ MediaDownloadSource.STORY_LOGGER,
+ mediaAuthor,
+ story.createdAt
+ ),
+ iconUrl = null,
+ mediaAuthor = friendInfo?.mutableUsername ?: userId,
+ downloadSource = MediaDownloadSource.STORY_LOGGER.translate(context.translation),
+ ))
+ }
+
Dialog(onDismissRequest = {
selectedStory = null
}) {
@@ -88,13 +125,13 @@ class LoggedStories : Routes.Route() {
DateFormat.getDateTimeInstance().format(Date(it))
}}")
- Row(
+ FlowRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
) {
Button(onClick = {
context.androidContext.externalCacheDir?.let { cacheDir ->
- val cacheFile = coilCacheFile ?: run {
+ val cacheFile = coilCachedFile ?: run {
context.shortToast("Failed to get file")
return@Button
}
@@ -118,43 +155,31 @@ class LoggedStories : Routes.Route() {
}
Button(onClick = {
- val mediaAuthor = friendInfo?.mutableUsername ?: userId
- val uniqueHash = (selectedStory?.url ?: UUID.randomUUID().toString()).longHashCode().absoluteValue.toString(16)
-
- DownloadProcessor(
- remoteSideContext = context,
- callback = object: DownloadCallback.Default() {
- override fun onSuccess(outputPath: String?) {
- context.shortToast("Downloaded to $outputPath")
- }
+ downloadSelectedStory(
+ InputMedia(
+ content = story.url,
+ type = DownloadMediaType.REMOTE_MEDIA,
+ encryption = story.key?.let { it to story.iv!! }?.toKeyPair()
+ )
+ )
+ }) {
+ Text(text = "Download")
+ }
- override fun onFailure(message: String?, throwable: String?) {
- context.shortToast("Failed to download $message")
- }
- }
- ).enqueue(DownloadRequest(
- inputMedias = arrayOf(
+ if (coilCachedFile != null) {
+ Button(onClick = {
+ downloadSelectedStory(
InputMedia(
- content = story.url,
- type = DownloadMediaType.REMOTE_MEDIA,
- encryption = story.key?.let { it to story.iv!! }?.toKeyPair()
+ content = coilCachedFile?.absolutePath ?: run {
+ context.shortToast("Failed to get file from cache")
+ return@Button
+ },
+ type = DownloadMediaType.LOCAL_MEDIA
)
)
- ), DownloadMetadata(
- mediaIdentifier = uniqueHash,
- outputPath = createNewFilePath(
- context.config.root,
- uniqueHash,
- MediaDownloadSource.STORY_LOGGER,
- mediaAuthor,
- story.createdAt
- ),
- iconUrl = null,
- mediaAuthor = friendInfo?.mutableUsername ?: userId,
- downloadSource = MediaDownloadSource.STORY_LOGGER.translate(context.translation),
- ))
- }) {
- Text(text = "Download")
+ }) {
+ Text(text = "Save from cache")
+ }
}
}
}
@@ -171,6 +196,7 @@ class LoggedStories : Routes.Route() {
contentPadding = PaddingValues(8.dp),
) {
items(stories) { story ->
+ var isFailed by remember { mutableStateOf(false) }
var imageBitmap by remember { mutableStateOf<ImageBitmap?>(null) }
val uniqueHash = remember { story.url.hashCode().absoluteValue.toString(16) }
@@ -195,6 +221,8 @@ class LoggedStories : Routes.Route() {
imageBitmap = bitmap?.asImageBitmap()
return true
+ }.onFailure {
+ context.log.error("Failed to open disk cache snapshot", it)
}
return false
}
@@ -202,9 +230,8 @@ class LoggedStories : Routes.Route() {
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
withTimeout(10000L) {
- context.imageLoader.diskCache?.openSnapshot(uniqueHash)?.let {
- openDiskCacheSnapshot(it)
- it.close()
+ context.imageLoader.diskCache?.openSnapshot(uniqueHash)?.use {
+ if (!openDiskCacheSnapshot(it)) isFailed = true
return@withTimeout
}
@@ -224,12 +251,12 @@ class LoggedStories : Routes.Route() {
decrypted.copyTo(fos)
}
commitAndOpenSnapshot()?.use { snapshot ->
- openDiskCacheSnapshot(snapshot)
- snapshot.close()
+ if (!openDiskCacheSnapshot(snapshot)) isFailed = true
}
}
}
}.onFailure {
+ isFailed = true
context.log.error("Failed to load story", it)
}
}
@@ -241,24 +268,34 @@ class LoggedStories : Routes.Route() {
.padding(8.dp)
.clickable {
selectedStory = story
- coilCacheFile = context.imageLoader.diskCache?.openSnapshot(uniqueHash).use {
- it?.data?.toFile()
- }
+ coilCachedFile = context.imageLoader.diskCache
+ ?.openSnapshot(uniqueHash)
+ .use {
+ it?.data?.toFile()
+ }
}
.heightIn(min = 128.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
- imageBitmap?.let {
- Card {
- Image(
- bitmap = it,
- modifier = Modifier.fillMaxSize(),
- contentDescription = null,
- )
+ if (isFailed) {
+ Icon(
+ imageVector = Icons.Default.ErrorOutline,
+ contentDescription = "",
+ modifier = Modifier.size(48.dp)
+ )
+ } else {
+ imageBitmap?.let {
+ Card {
+ Image(
+ bitmap = it,
+ modifier = Modifier.fillMaxSize(),
+ contentDescription = null,
+ )
+ }
+ } ?: run {
+ CircularProgressIndicator()
}
- } ?: run {
- CircularProgressIndicator()
}
}
}