Skip to content

Commit

Permalink
Merge pull request #58 from KevinSchildhorn/ks/AddingNewCache
Browse files Browse the repository at this point in the history
Updating how we get images
  • Loading branch information
KevinSchildhorn authored Oct 16, 2024
2 parents 5e6014b + 2f67ae9 commit 2f7508c
Show file tree
Hide file tree
Showing 15 changed files with 230 additions and 68 deletions.
4 changes: 3 additions & 1 deletion androidApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ kotlin {
implementation(project(":shared"))
implementation(libs.koin.android)
implementation(libs.firebase.crashlytics)
implementation(libs.coil)
implementation(libs.accompanist.permissions)
implementation(libs.kermit)

}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
package com.kevinschildhorn

import MainView
import android.Manifest
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import co.touchlab.kermit.Logger
import coil3.ImageLoader
import coil3.compose.setSingletonImageLoaderFactory
import com.kevinschildhorn.fotopresenter.data.datasources.image.CachedImageDataSource
import com.kevinschildhorn.fotopresenter.ui.SMBJFetcher
import com.kevinschildhorn.fotopresenter.data.network.NetworkHandler
import coil3.disk.DiskCache
import coil3.disk.directory
import coil3.memory.MemoryCache
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberPermissionState
import com.kevinschildhorn.fotopresenter.baseLogger
import com.kevinschildhorn.fotopresenter.data.network.SMBJHandler
import com.kevinschildhorn.fotopresenter.data.repositories.ImageRepository
import com.kevinschildhorn.fotopresenter.startKoin
import com.kevinschildhorn.fotopresenter.ui.ByteArrayFetcher
import com.kevinschildhorn.fotopresenter.ui.SMBJFetcher
import com.kevinschildhorn.fotopresenter.ui.screens.directory.DirectoryViewModel
import com.kevinschildhorn.fotopresenter.ui.screens.login.LoginViewModel
import com.kevinschildhorn.fotopresenter.ui.screens.playlist.PlaylistViewModel
Expand All @@ -32,10 +41,23 @@ class MainActivity : AppCompatActivity(), KoinComponent {
startKoin(this)

setContent {

setSingletonImageLoaderFactory { context ->
ImageLoader.Builder(context)
.components {
add(SMBJFetcher.Factory(imageRepository))
add(SMBJFetcher.Factory(imageRepository, baseLogger))
add(ByteArrayFetcher.Factory(Logger.withTag("ByteArrayFetcher")))
}
.memoryCache {
MemoryCache.Builder()
.maxSizePercent(context,0.25)
.build()
}
.diskCache {
DiskCache.Builder()
.directory(context.cacheDir.resolve("image_cache"))
.maxSizePercent(0.02)
.build()
}
.build()
}
Expand Down
31 changes: 31 additions & 0 deletions desktopApp/src/jvmMain/kotlin/Main.kt
Original file line number Diff line number Diff line change
@@ -1,28 +1,59 @@
import KoinPurse.imageRepository
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import co.touchlab.kermit.Logger
import coil3.ImageLoader
import com.kevinschildhorn.fotopresenter.UseCaseFactory
import com.kevinschildhorn.fotopresenter.baseLogger
import com.kevinschildhorn.fotopresenter.ui.SMBJFetcher
import com.kevinschildhorn.fotopresenter.ui.screens.directory.DirectoryViewModel
import com.kevinschildhorn.fotopresenter.ui.screens.login.LoginViewModel
import com.kevinschildhorn.fotopresenter.ui.screens.playlist.PlaylistViewModel
import com.kevinschildhorn.fotopresenter.ui.screens.slideshow.SlideshowViewModel
import coil3.compose.setSingletonImageLoaderFactory
import com.kevinschildhorn.fotopresenter.data.datasources.image.CachedImageDataSource
import com.kevinschildhorn.fotopresenter.data.datasources.image.NetworkImageDataSource
import com.kevinschildhorn.fotopresenter.data.network.SMBJHandler
import com.kevinschildhorn.fotopresenter.data.repositories.ImageRepository
import com.kevinschildhorn.fotopresenter.ui.ByteArrayFetcher
import com.kevinschildhorn.fotopresenter.ui.shared.SharedFileCache


object KoinPurse {
private val remoteImageDataSource: NetworkImageDataSource = NetworkImageDataSource(SMBJHandler)
private val localImageDataSource: CachedImageDataSource =
CachedImageDataSource(SharedFileCache("cache"), Logger.withTag("CachedImageDataSource"))

val loginViewModel =
LoginViewModel(Logger.withTag("LoginViewModel"), UseCaseFactory.credentialsRepository)
val directoryViewModel =
DirectoryViewModel(UseCaseFactory.playlistRepository, Logger.withTag("DirectoryViewModel"))
val slideshowViewModel = SlideshowViewModel(Logger.withTag("SlideshowViewModel"))
val playlistViewModel =
PlaylistViewModel(UseCaseFactory.playlistRepository, Logger.withTag("PlaylistViewModel"))
val imageRepository =
ImageRepository(
remoteImageDataSource,
localImageDataSource,
Logger.withTag("ImageRepository")
)
}

fun main() = application {
Window(
title = "FotoPresenter",
onCloseRequest = ::exitApplication
) {

setSingletonImageLoaderFactory { context ->
ImageLoader.Builder(context)
.components {
add(SMBJFetcher.Factory(imageRepository, Logger.withTag("SMBJFetcher")))
add(ByteArrayFetcher.Factory(Logger.withTag("ByteArrayFetcher")))
}
.build()
}

MainView(
KoinPurse.loginViewModel,
KoinPurse.directoryViewModel,
Expand Down
6 changes: 5 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[versions]
accompanistPermissions = "0.36.0"
activity-compose = "1.9.2"
agp = "8.5.2"
appcompat = "1.7.0"
Expand All @@ -7,6 +8,7 @@ cache4k = "0.12.0"
coil = "3.0.0-rc01"
core-ktx = "1.13.1"
eva-icons = "1.1.0"
fileKache = "2.1.0"
firebase-crashlytics = "19.2.0"
kermit = "2.0.4"
kermit-koin = "1.2.2"
Expand All @@ -33,19 +35,21 @@ sqlite-driver = "2.0.1"
turbine = "1.0.0"

## SDK Versions
minSdk = "26"
minSdk = "29"
targetSdk = "34"
compileSdk = "34"
java = "21"

[libraries]
accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanistPermissions" }
activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activity-compose" }
android-driver = { module = "app.cash.sqldelight:android-driver", version.ref = "sqlite-driver" }
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
atomik = { module = "io.github.kevinschildhorn:atomik", version.ref = "atomik" }
cache4k = { module = "io.github.reactivecircus.cache4k:cache4k", version.ref = "cache4k" }
core-ktx = { module = "androidx.core:core-ktx", version.ref = "core-ktx" }
eva-icons = { module = "br.com.devsrsouza.compose.icons:eva-icons", version.ref = "eva-icons" }
file-kache = { module = "com.mayakapps.kache:file-kache", version.ref = "fileKache" }
firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics", version.ref = "firebase-crashlytics" }
kermit = { module = "co.touchlab:kermit", version.ref = "kermit" }
kermit-koin = { module = "co.touchlab:kermit-koin", version.ref = "kermit-koin" }
Expand Down
3 changes: 2 additions & 1 deletion shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ kotlin {
implementation(libs.multiplatform.settings)
implementation(libs.kotlinx.datetime)
implementation(libs.kim)
implementation(libs.coil)
api(libs.coil)
implementation(libs.file.kache)
}
}
val commonTest by getting {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import app.cash.sqldelight.db.SqlDriver
import com.kevinschildhorn.fotopresenter.data.datasources.CredentialsDataSource
import com.kevinschildhorn.fotopresenter.data.network.NetworkHandler
import com.kevinschildhorn.fotopresenter.data.network.SMBJHandler
import com.kevinschildhorn.fotopresenter.ui.shared.CacheInterface
import com.kevinschildhorn.fotopresenter.ui.shared.DriverFactory
import com.kevinschildhorn.fotopresenter.ui.shared.SharedFileCache
import com.russhwolf.settings.Settings
import com.russhwolf.settings.SharedPreferencesSettings
import org.koin.core.KoinApplication
Expand Down Expand Up @@ -47,6 +49,14 @@ internal actual val platformModule: Module =
SMBJHandler
}
single<SqlDriver> { DriverFactory(context = get()).createDriver() }
single<NetworkHandler> {
SMBJHandler
}
single<SqlDriver> { DriverFactory(context = get()).createDriver() }
single<CacheInterface> {
val context: Context = get()
SharedFileCache(context.cacheDir.path)
}
}

@OptIn(KoinInternalApi::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,25 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import coil3.Image
import coil3.asImage
import coil3.decode.DataSource
import coil3.fetch.FetchResult
import coil3.fetch.ImageFetchResult

actual open class SharedImage actual constructor(actual val byteArray: ByteArray) {
actual fun getCoilImage(size: Int): Image? = getAndroidBitmap(byteArray, size)?.asImage()
actual fun getFetchResult(size: Int): FetchResult? {
val image: Image? = getAndroidBitmap(byteArray, size)?.asImage()
return if (image != null) {
ImageFetchResult(
image = image,
isSampled = true,
dataSource = DataSource.NETWORK,
)
} else {
null
}
}

private fun getCoilImage(size: Int): Image? = getAndroidBitmap(byteArray, size)?.asImage()

private fun getAndroidBitmap(
byteArray: ByteArray,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ import com.kevinschildhorn.fotopresenter.ui.screens.directory.DirectoryViewModel
import com.kevinschildhorn.fotopresenter.ui.screens.login.LoginViewModel
import com.kevinschildhorn.fotopresenter.ui.screens.playlist.PlaylistViewModel
import com.kevinschildhorn.fotopresenter.ui.screens.slideshow.SlideshowViewModel
import com.kevinschildhorn.fotopresenter.ui.shared.CacheInterface
import com.kevinschildhorn.fotopresenter.ui.shared.SharedInMemoryCache
import org.koin.core.module.Module
import org.koin.dsl.module

Expand All @@ -37,13 +35,12 @@ val commonModule =
module {

// Data
single<CacheInterface> { SharedInMemoryCache }
single { NetworkImageDataSource(get()) }
single { CredentialsDataSource(get()) }
single { CredentialsRepository(get()) }
single { DirectoryDataSource(get(), baseLogger.withTag("DirectoryDataSource")) }
single { DirectoryRepository(get(), get()) }
single { CachedImageDataSource(get(), get(), baseLogger.withTag("ImageCacheDataSource")) }
single { CachedImageDataSource(get(), baseLogger.withTag("ImageCacheDataSource"), get()) }
single { PlaylistFileDataSource(baseLogger.withTag("PlaylistDataSource"), get()) }
single { PlaylistSQLDataSource(get(), baseLogger.withTag("PlaylistDataSource")) }
single { PlaylistRepository(get(), get()) }
Expand Down Expand Up @@ -75,7 +72,7 @@ val commonModule =
baseLogger.withTag("RetrieveDirectoryContentsUseCase"),
)
}
factory { RetrieveImageUseCase(get(), baseLogger.withTag("RetrieveImagesUseCase")) }
factory { RetrieveImageUseCase(get(), baseLogger.withTag("RetrieveImageUseCase")) }
factory { SaveMetadataForPathUseCase(get()) }
// UI
single { LoginViewModel(baseLogger.withTag("LoginViewModel"), get()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,37 @@ import com.kevinschildhorn.fotopresenter.PlaylistDatabase
import com.kevinschildhorn.fotopresenter.data.network.NetworkDirectoryDetails
import com.kevinschildhorn.fotopresenter.ui.shared.CacheInterface
import com.kevinschildhorn.fotopresenter.ui.shared.SharedImage
import com.kevinschildhorn.fotopresenter.Image as SQLImage

class CachedImageDataSource(
private val cache: CacheInterface,
driver: SqlDriver,
private val logger: Logger,
driver: SqlDriver,
) {
private val database = PlaylistDatabase(driver)

fun getImage(directory: NetworkDirectoryDetails): SharedImage? {
suspend fun getImage(directory: NetworkDirectoryDetails): SharedImage? {
logger.i { "Getting Image from Cache ${directory.cacheId}" }
return try {
val image: SQLImage = database.imageQueries.selectImageByName(directory.cacheId).executeAsOne()
SharedImage(image.image)
cache.getImage(directory.cacheId)
// val image = database.imageQueries.selectImageByName(directory.cacheId).executeAsOne()
// SharedImage(image.image)
} catch (e: Exception) {
logger.e(e) { "Image NOT found" }
logger.e { e.localizedMessage ?: "" }
null
}
}

fun saveImage(
suspend fun saveImage(
directory: NetworkDirectoryDetails,
image: SharedImage,
) {
logger.i { "Saving Image To Cache ${directory.cacheId}" }
database.imageQueries.insertImage(
directory.cacheId,
image.byteArray,
)
cache.cacheImage(directory.cacheId, image)
// database.imageQueries.insertImage(
// directory.cacheId,
// image.byteArray,
// )
logger.i { "Image Saved" }
// cache.cacheImage(directory.cacheId, image) TODO
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,42 @@
package com.kevinschildhorn.fotopresenter.data.repositories

import co.touchlab.kermit.Logger
import coil3.Image
import coil3.fetch.FetchResult
import com.kevinschildhorn.fotopresenter.data.datasources.image.CachedImageDataSource
import com.kevinschildhorn.fotopresenter.data.datasources.image.NetworkImageDataSource
import com.kevinschildhorn.fotopresenter.data.network.NetworkDirectoryDetails
import com.kevinschildhorn.fotopresenter.ui.shared.SharedImage

class ImageRepository(
private val remoteImageDataSource: NetworkImageDataSource,
private val localImageDataSource: CachedImageDataSource,
private val logger: Logger?,
) {
suspend fun getCoilImage(
suspend fun getFetchResult(
directoryDetails: NetworkDirectoryDetails,
size: Int,
): Image? {
logger?.i { "Getting Image from Cache" }
): FetchResult? {
val image = getImage(directoryDetails, size)
return image?.getFetchResult(size)
}

private suspend fun getImage(
directoryDetails: NetworkDirectoryDetails,
size: Int,
): SharedImage? {
logger?.i { "Getting Image from Cache: ${directoryDetails.name}" }
val cachedImage = localImageDataSource.getImage(directoryDetails)
if (cachedImage != null) return cachedImage.getCoilImage(size)
if (cachedImage != null) {
logger?.i { "Cached image found from Cache: ${directoryDetails.name}" }
return cachedImage
}

logger?.i { "No cached image found, getting image from directory" }
val image = remoteImageDataSource.getImage(directoryDetails)
if (image != null) {
logger?.i { "Storing image in cache" }
logger?.i { "Storing image in cache: ${directoryDetails.name}" }
localImageDataSource.saveImage(directoryDetails, image)
}
return image?.getCoilImage(size)
return image
}
}
Loading

0 comments on commit 2f7508c

Please sign in to comment.