Private
Public Access
1
0

Attachments: implements proper caching

This commit is contained in:
2024-04-09 00:11:53 -07:00
parent 20e33f70a8
commit d474ce1c10
5 changed files with 67 additions and 28 deletions

View File

@@ -1,16 +1,16 @@
package net.buzzert.kordophonedroid
import android.content.Context
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import net.buzzert.kordophone.backend.db.CachedChatDatabase
import net.buzzert.kordophone.backend.server.APIClientFactory
import net.buzzert.kordophone.backend.server.Authentication
import net.buzzert.kordophone.backend.server.ChatRepository
import net.buzzert.kordophone.backend.server.RetrofitAPIClient
import net.buzzert.kordophonedroid.ui.attachments.AttachmentImageLoader
import net.buzzert.kordophonedroid.ui.shared.ServerConfigRepository
import java.net.URL
import javax.inject.Singleton
@Module
@@ -28,4 +28,14 @@ object AppModule {
val database = CachedChatDatabase.liveDatabase()
return ChatRepository(client, database)
}
@Singleton
@Provides
fun provideAttachmentFactory(
chatRepository: ChatRepository,
@ApplicationContext context: Context
): AttachmentImageLoader
{
return AttachmentImageLoader(chatRepository, context)
}
}

View File

@@ -6,29 +6,25 @@ import androidx.lifecycle.ViewModel
import coil.Coil
import coil.ImageLoader
import coil.ImageLoaderFactory
import coil.annotation.ExperimentalCoilApi
import coil.decode.DataSource
import coil.decode.ImageSource
import coil.disk.DiskCache
import coil.fetch.FetchResult
import coil.fetch.Fetcher
import coil.fetch.SourceResult
import coil.memory.MemoryCache
import coil.request.Options
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import net.buzzert.kordophone.backend.server.ChatRepository
import net.buzzert.kordophonedroid.ui.messagelist.MVM_LOG
import javax.inject.Inject
const val AVM_LOG: String = "AttachmentViewModel"
const val AVM_LOG: String = "AttachmentImageLoader"
data class AttachmentFetchData(
val guid: String,
val preview: Boolean = false
)
@HiltViewModel
class AttachmentViewModel @Inject constructor(
class AttachmentImageLoader(
private val repository: ChatRepository,
@ApplicationContext val application: Context,
) : ViewModel(), ImageLoaderFactory, Fetcher.Factory<AttachmentFetchData>
@@ -41,17 +37,6 @@ class AttachmentViewModel @Inject constructor(
override fun newImageLoader(): ImageLoader {
val factory = this
return ImageLoader.Builder(application)
.memoryCache {
MemoryCache.Builder(application)
.maxSizePercent(0.25)
.build()
}
.diskCache {
DiskCache.Builder()
.directory(application.cacheDir.resolve("attachments"))
.maxSizePercent(0.02)
.build()
}
.components {
// Adds the FetcherFactory
add(factory)
@@ -73,13 +58,59 @@ private class AttachmentFetcher(
val context: Context,
val data: AttachmentFetchData
): Fetcher {
val cache = DiskCache.Builder()
.directory(context.cacheDir.resolve("attachments"))
.maxSizePercent(0.02)
.build()
val cacheKey: String get() { return data.guid + if (data.preview) "_preview" else "" }
@OptIn(ExperimentalCoilApi::class)
override suspend fun fetch(): FetchResult {
// Try loading from cache
var snapshot = cache.openSnapshot(cacheKey)
if (snapshot != null) {
Log.d(AVM_LOG, "Found attachment ${data.guid} in disk cache")
return SourceResult(
source = snapshot.toImageSource(),
dataSource = DataSource.DISK,
mimeType = null,
)
}
Log.d(AVM_LOG, "Loading attachment ${data.guid} from network")
val source = repository.fetchAttachmentDataSource(data.guid, data.preview)
// Save to cache
val editor = cache.openEditor(cacheKey)
if (editor != null) {
Log.d(AVM_LOG, "Writing attachment ${data.guid} to disk cache")
cache.fileSystem.write(editor.data) {
source.readAll(this)
}
snapshot = editor.commitAndOpenSnapshot()
if (snapshot != null) {
return SourceResult(
source = snapshot.toImageSource(),
dataSource = DataSource.NETWORK,
mimeType = null
)
}
}
// We'll go down this path if for some reason we couldn't save to cache.
return SourceResult(
source = ImageSource(source, context),
dataSource = DataSource.NETWORK,
mimeType = null,
)
}
@OptIn(ExperimentalCoilApi::class)
private fun DiskCache.Snapshot.toImageSource(): ImageSource {
val fileSystem = cache.fileSystem
return ImageSource(data, fileSystem, cacheKey, this)
}
}

View File

@@ -25,10 +25,7 @@ import net.engawapg.lib.zoomable.rememberZoomState
import net.engawapg.lib.zoomable.zoomable
@Composable
fun AttachmentViewer(
attachmentGuid: String,
attachmentViewModel: AttachmentViewModel = hiltViewModel()
) {
fun AttachmentViewer(attachmentGuid: String) {
var topBarVisible = remember { mutableStateOf(true) }
val navController = LocalNavController.current
Scaffold(topBar = {

View File

@@ -55,7 +55,6 @@ import net.buzzert.kordophone.backend.model.GUID
import net.buzzert.kordophonedroid.ui.Destination
import net.buzzert.kordophonedroid.ui.LocalNavController
import net.buzzert.kordophonedroid.ui.attachments.AttachmentFetchData
import net.buzzert.kordophonedroid.ui.attachments.AttachmentViewModel
import net.buzzert.kordophonedroid.ui.shared.LINK_ANNOTATION_TAG
import net.buzzert.kordophonedroid.ui.shared.linkify
import net.buzzert.kordophonedroid.ui.theme.KordophoneTopAppBar
@@ -86,7 +85,6 @@ sealed class MessageListItem: MessageMetadataProvider {
fun MessageListScreen(
conversationGUID: GUID,
viewModel: MessageListViewModel = hiltViewModel(),
attachmentViewModel: AttachmentViewModel = hiltViewModel() // unused, but initialized for Coil
) {
viewModel.conversationGUID = conversationGUID

View File

@@ -3,6 +3,7 @@ package net.buzzert.kordophonedroid.ui.messagelist
import android.content.Context
import android.net.Uri
import android.webkit.MimeTypeMap
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -18,13 +19,15 @@ import net.buzzert.kordophone.backend.model.Message
import net.buzzert.kordophone.backend.model.OutgoingMessage
import net.buzzert.kordophone.backend.model.UploadingAttachmentMetadata
import net.buzzert.kordophone.backend.server.ChatRepository
import net.buzzert.kordophonedroid.ui.attachments.AttachmentImageLoader
import javax.inject.Inject
const val MVM_LOG: String = "MessageListViewModel"
@HiltViewModel
class MessageListViewModel @Inject constructor(
private val repository: ChatRepository
private val repository: ChatRepository,
private val imageLoader: AttachmentImageLoader
) : ViewModel()
{
var conversationGUID: GUID? = null