Skip to content

Commit

Permalink
Merge pull request #34 from KevinSchildhorn/CachingImages
Browse files Browse the repository at this point in the history
Adding cache and cleaning up
  • Loading branch information
KevinSchildhorn authored Dec 8, 2023
2 parents 75082ca + 91c3d30 commit 2435fe6
Show file tree
Hide file tree
Showing 29 changed files with 475 additions and 67 deletions.
2 changes: 1 addition & 1 deletion androidApp/src/androidMain/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<resources>
<string name="app_name">My application</string>
<string name="app_name">FotoPresenter</string>
</resources>
2 changes: 1 addition & 1 deletion iosApp/Configuration/Config.xcconfig
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
TEAM_ID=
BUNDLE_ID=com.kevinschildhorn.fotopresenter
APP_NAME=My application
APP_NAME=Foto Presenter
2 changes: 2 additions & 0 deletions shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ kotlin {
implementation(compose.components.resources)
implementation("br.com.devsrsouza.compose.icons:eva-icons:1.1.0")

implementation("io.github.reactivecircus.cache4k:cache4k:0.12.0")

implementation("io.insert-koin:koin-core:3.4.0")
implementation("androidx.security:security-crypto:1.1.0-alpha06")
implementation("co.touchlab:kermit:1.2.2")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,5 @@ actual fun getBitmapFromFile(file: File, size: Int): ImageBitmap? {
return Bitmap.createScaledBitmap(it, dimensions.first, dimensions.second, false)
.asImageBitmap()
}

return null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.kevinschildhorn.fotopresenter.ui.shared

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asAndroidBitmap
import androidx.compose.ui.graphics.asImageBitmap
import java.nio.ByteBuffer

actual object SharedImageConverter {
actual fun convertBytes(byteArray: ByteArray): ImageBitmap {
val bmp = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
return bmp.asImageBitmap()
}

actual fun convertImage(imageBitmap: ImageBitmap): ByteArray {
return imageBitmap.asAndroidBitmap().convertToByteArray()
}

private fun Bitmap.convertToByteArray(): ByteArray {
val size = this.byteCount
val buffer = ByteBuffer.allocate(size)
val bytes = ByteArray(size)
this.copyPixelsToBuffer(buffer)
buffer.rewind()
buffer.get(bytes)
return bytes
}
}
8 changes: 7 additions & 1 deletion shared/src/commonMain/kotlin/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@ fun App(
when (currentScreen.value) {
Screen.LOGIN ->
LoginScreen(loginViewModel) {
directoryViewModel.setLoggedIn()
currentScreen.value = Screen.DIRECTORY
}
Screen.DIRECTORY -> DirectoryScreen(directoryViewModel)

Screen.DIRECTORY ->
DirectoryScreen(directoryViewModel) {
loginViewModel.setLoggedOut()
currentScreen.value = Screen.LOGIN
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import com.kevinschildhorn.fotopresenter.data.repositories.ImageRepository
import com.kevinschildhorn.fotopresenter.domain.AutoConnectUseCase
import com.kevinschildhorn.fotopresenter.domain.ChangeDirectoryUseCase
import com.kevinschildhorn.fotopresenter.domain.ConnectToServerUseCase
import com.kevinschildhorn.fotopresenter.domain.LogoutUseCase
import com.kevinschildhorn.fotopresenter.domain.RetrieveDirectoryContentsUseCase
import com.kevinschildhorn.fotopresenter.domain.RetrieveImagesUseCase
import com.kevinschildhorn.fotopresenter.domain.SaveCredentialsUseCase
import com.kevinschildhorn.fotopresenter.ui.viewmodel.DirectoryViewModel
import com.kevinschildhorn.fotopresenter.ui.viewmodel.LoginViewModel
Expand All @@ -35,13 +37,15 @@ val commonModule =
factory { ChangeDirectoryUseCase(get(), baseLogger.withTag("ChangeDirectoryUseCase")) }
factory { AutoConnectUseCase(get(), get(), baseLogger.withTag("AutoConnectUseCase")) }
factory { SaveCredentialsUseCase(get(), baseLogger.withTag("SaveCredentialsUseCase")) }
factory { LogoutUseCase(get(), get(), baseLogger.withTag("LogoutUseCase")) }
factory {
RetrieveDirectoryContentsUseCase(
get(),
get(),
baseLogger.withTag("RetrieveDirectoryContentsUseCase"),
)
}
factory { RetrieveImagesUseCase(baseLogger.withTag("RetrieveImagesUseCase")) }

// UI
single { LoginViewModel(baseLogger.withTag("LoginViewModel"), get()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ sealed class State<out DATA> {
return this
}

val value: DATA?
get() = (this as? SUCCESS)?.data

@Composable
fun asComposable(modifier: Modifier = Modifier) {
when (this) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.kevinschildhorn.fotopresenter.data.datasources

import androidx.compose.ui.graphics.ImageBitmap
import com.kevinschildhorn.fotopresenter.data.network.NetworkDirectoryDetails
import com.kevinschildhorn.fotopresenter.ui.shared.SharedCache

class ImageCacheDataSource {
fun getImage(directory: NetworkDirectoryDetails): ImageBitmap? = SharedCache.getImage(directory.cacheId)

fun saveImage(
directory: NetworkDirectoryDetails,
bitmap: ImageBitmap,
) {
SharedCache.cacheImage(directory.cacheId, bitmap)
}

private val NetworkDirectoryDetails.cacheId: String
get() = "$name.$id"
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,8 @@ class CredentialsRepository(
dataSource.sharedFolder = sharedFolder
dataSource.shouldAutoConnect = shouldAutoConnect
}

fun clearAutoConnect() {
dataSource.shouldAutoConnect = false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.kevinschildhorn.fotopresenter.domain

import co.touchlab.kermit.Logger
import com.kevinschildhorn.fotopresenter.data.network.NetworkHandler
import com.kevinschildhorn.fotopresenter.data.repositories.CredentialsRepository

/**
Logging out of App
**/
class LogoutUseCase(
private val credentialsRepository: CredentialsRepository,
private val networkHandler: NetworkHandler,
private val logger: Logger,
) {
suspend operator fun invoke() {
networkHandler.disconnect()
credentialsRepository.clearAutoConnect()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.kevinschildhorn.fotopresenter.domain

import androidx.compose.ui.graphics.ImageBitmap
import co.touchlab.kermit.Logger
import com.kevinschildhorn.fotopresenter.data.ImageDirectory
import com.kevinschildhorn.fotopresenter.data.State
import com.kevinschildhorn.fotopresenter.ui.shared.SharedCache

/**
Retrieving Directories from Location
**/
class RetrieveImagesUseCase(
private val logger: Logger,
) {
suspend operator fun invoke(
directory: ImageDirectory,
callback: suspend (State<ImageBitmap>) -> Unit,
) {
callback(State.LOADING)
SharedCache.getImage(directory.name)?.let {
callback(State.SUCCESS(it))
}
val imageBitmap: ImageBitmap? = directory.image?.getImageBitmap(400)

callback(
imageBitmap?.let {
State.SUCCESS(it)
} ?: State.ERROR("No Image Found"),
)
imageBitmap?.let {
SharedCache.cacheImage(directory.name, it)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.kevinschildhorn.fotopresenter.extension

fun List<Any>.getNextIndex(index: Int): Int {
val nextIndex = index + 1
return if (nextIndex >= this.count()) {
0
} else {
nextIndex
}
}

fun List<Any>.getPreviousIndex(index: Int): Int {
val previousIndex = index - 1
return if (previousIndex < 0) {
this.count() - 1
} else {
previousIndex
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,59 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.kevinschildhorn.fotopresenter.data.State
import com.kevinschildhorn.fotopresenter.ui.atoms.Padding
import com.kevinschildhorn.fotopresenter.ui.compose.common.PrimaryButton
import com.kevinschildhorn.fotopresenter.ui.compose.directory.DirectoryGrid
import com.kevinschildhorn.fotopresenter.ui.viewmodel.DirectoryViewModel

@Composable
fun DirectoryScreen(viewModel: DirectoryViewModel) {
fun DirectoryScreen(
viewModel: DirectoryViewModel,
onLogout: () -> Unit,
) {
LaunchedEffect(Unit) {
viewModel.refreshScreen()
}
val uiState by viewModel.uiState.collectAsState()

if (!uiState.loggedIn)
{
onLogout()
}
uiState.state.asComposable(
modifier =
Modifier.padding(
horizontal = Padding.STANDARD.dp,
vertical = Padding.SMALL.dp,
),
)
PrimaryButton("Logout") {
viewModel.logout()
}
DirectoryGrid(
uiState.directoryGridState,
modifier =
Modifier
.padding(top = Padding.EXTRA_LARGE.dp),
onFolderPressed = {
uiState.selectedImage = State.IDLE
viewModel.changeDirectory(it)
},
onImageDirectoryPressed = {
uiState.selectedImage = it
viewModel.setSelectedImageById(it)
},
)
if (uiState.selectedImage != State.IDLE) {
ImagePreviewOverlay(uiState.selectedImage) {
uiState.selectedImage = State.IDLE
}
uiState.selectedImage?.let {
ImagePreviewOverlay(
it,
onDismiss = {
viewModel.clearPresentedImage()
},
onBack = {
viewModel.showPreviousImage()
},
onForward = {
viewModel.showNextImage()
},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,37 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Text
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.unit.dp
import com.kevinschildhorn.atomik.color.base.composeColor
import com.kevinschildhorn.fotopresenter.data.State
import com.kevinschildhorn.fotopresenter.ui.atoms.FotoColors
import com.kevinschildhorn.fotopresenter.ui.atoms.Padding
import com.kevinschildhorn.fotopresenter.ui.compose.common.Overlay
import compose.icons.EvaIcons
import compose.icons.evaicons.Fill
import compose.icons.evaicons.fill.ArrowLeft
import compose.icons.evaicons.fill.ArrowRight

@Composable
fun ImagePreviewOverlay(
imageState: State<ImageBitmap>,
image: ImageBitmap,
onDismiss: () -> Unit,
onBack: () -> Unit,
onForward: () -> Unit,
) {
val interactionSource = remember { MutableInteractionSource() }

Expand All @@ -40,19 +49,64 @@ fun ImagePreviewOverlay(
onClick = onDismiss,
),
) {
imageState.onSuccess {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceBetween,
) {
// imageState.onSuccess {
Image(
bitmap = it,
bitmap = image,
contentDescription = null,
modifier = Modifier.fillMaxSize().padding(Padding.IMAGE.dp),
modifier =
Modifier
.fillMaxWidth()
.fillMaxHeight(.9f)
.padding(Padding.IMAGE.dp),
)
Row(
modifier =
Modifier
.fillMaxWidth()
.padding(Padding.STANDARD.dp)
.height(44.dp),
horizontalArrangement = Arrangement.SpaceBetween,
) {
Button(
onClick = onBack,
colors =
ButtonDefaults.buttonColors(
backgroundColor = FotoColors.primary.composeColor,
),
) {
Icon(
EvaIcons.Fill.ArrowLeft,
contentDescription = null,
tint = FotoColors.surface.composeColor,
)
}
Button(
onClick = onForward,
colors =
ButtonDefaults.buttonColors(
backgroundColor = FotoColors.primary.composeColor,
),
) {
Icon(
EvaIcons.Fill.ArrowRight,
contentDescription = null,
tint = FotoColors.surface.composeColor,
)
}
}
}
/*
}.onLoading {
CircularProgressIndicator(
modifier = Modifier.width(75.dp).align(Alignment.Center),
color = FotoColors.primary.composeColor,
)
}.onError {
Text("Error")
}
}*/
}
}
Loading

0 comments on commit 2435fe6

Please sign in to comment.