Attachments: implements proper caching
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user