diff --git a/androidApp/src/androidMain/kotlin/com/kevinschildhorn/MainActivity.kt b/androidApp/src/androidMain/kotlin/com/kevinschildhorn/MainActivity.kt index 318c8123..50a72a7d 100644 --- a/androidApp/src/androidMain/kotlin/com/kevinschildhorn/MainActivity.kt +++ b/androidApp/src/androidMain/kotlin/com/kevinschildhorn/MainActivity.kt @@ -5,8 +5,9 @@ import android.os.Bundle import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity import com.kevinschildhorn.fotopresenter.startKoin -import com.kevinschildhorn.fotopresenter.ui.viewmodel.DirectoryViewModel -import com.kevinschildhorn.fotopresenter.ui.viewmodel.LoginViewModel +import com.kevinschildhorn.fotopresenter.ui.screens.directory.DirectoryViewModel +import com.kevinschildhorn.fotopresenter.ui.screens.login.LoginViewModel +import com.kevinschildhorn.fotopresenter.ui.screens.slideshow.SlideshowViewModel import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.component.KoinComponent @@ -14,13 +15,14 @@ class MainActivity : AppCompatActivity(), KoinComponent { private val loginViewModel by viewModel() private val directoryViewModel by viewModel() + private val slideshowViewModel by viewModel() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) startKoin(this) setContent { - MainView(loginViewModel, directoryViewModel) + MainView(loginViewModel, directoryViewModel, slideshowViewModel) } } } diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index ddb54951..faea6e0e 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -51,6 +51,7 @@ kotlin { implementation("com.russhwolf:multiplatform-settings-test:1.0.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") implementation("io.insert-koin:koin-test:3.4.0") + implementation("app.cash.turbine:turbine:1.0.0") } } diff --git a/shared/src/androidMain/kotlin/Main.android.kt b/shared/src/androidMain/kotlin/Main.android.kt index 77c14a47..278a77b1 100644 --- a/shared/src/androidMain/kotlin/Main.android.kt +++ b/shared/src/androidMain/kotlin/Main.android.kt @@ -1,6 +1,7 @@ import androidx.compose.runtime.Composable -import com.kevinschildhorn.fotopresenter.ui.viewmodel.DirectoryViewModel -import com.kevinschildhorn.fotopresenter.ui.viewmodel.LoginViewModel +import com.kevinschildhorn.fotopresenter.ui.screens.directory.DirectoryViewModel +import com.kevinschildhorn.fotopresenter.ui.screens.login.LoginViewModel +import com.kevinschildhorn.fotopresenter.ui.screens.slideshow.SlideshowViewModel actual fun getPlatformName(): String = "Android" @@ -8,4 +9,5 @@ actual fun getPlatformName(): String = "Android" fun MainView( loginViewModel: LoginViewModel, directoryViewModel: DirectoryViewModel, -) = App(loginViewModel, directoryViewModel) + slideshowViewModel: SlideshowViewModel, +) = App(loginViewModel, directoryViewModel, slideshowViewModel) diff --git a/shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/CommonPreviews.kt b/shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/CommonPreviews.kt index 7c332a1f..03b53bc2 100644 --- a/shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/CommonPreviews.kt +++ b/shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/CommonPreviews.kt @@ -3,9 +3,11 @@ package com.kevinschildhorn.fotopresenter.ui.compose import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview -import com.kevinschildhorn.fotopresenter.ui.compose.common.ActionSheet -import com.kevinschildhorn.fotopresenter.ui.compose.common.ButtonState -import com.kevinschildhorn.fotopresenter.ui.compose.common.PrimaryButton +import com.kevinschildhorn.fotopresenter.ui.screens.common.ActionSheetAction +import com.kevinschildhorn.fotopresenter.ui.screens.common.ActionSheetContext +import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.ActionSheet +import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.ButtonState +import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.PrimaryButton @Preview @Composable @@ -21,9 +23,13 @@ fun PrimaryButtonPreview() { @Composable fun ActionSheetPreview() { ActionSheet( - true, + visible = true, offset = 200, - values = listOf("Option A", "Option B"), - ) { - } + values = listOf( + ActionSheetContext(ActionSheetAction.START_SLIDESHOW, 1), + ActionSheetContext(ActionSheetAction.NONE, 2), + ), + onClick = {}, + onDismiss = {}, + ) } diff --git a/shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/DirectoryPreviews.kt b/shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/DirectoryPreviews.kt index 21a2f908..eab00e4c 100644 --- a/shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/DirectoryPreviews.kt +++ b/shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/DirectoryPreviews.kt @@ -3,16 +3,13 @@ package com.kevinschildhorn.fotopresenter.ui.compose import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview -import com.kevinschildhorn.fotopresenter.data.DirectoryContents -import com.kevinschildhorn.fotopresenter.data.FolderDirectory import com.kevinschildhorn.fotopresenter.data.State -import com.kevinschildhorn.fotopresenter.data.network.MockNetworkDirectoryDetails -import com.kevinschildhorn.fotopresenter.ui.compose.directory.DirectoryGridCell -import com.kevinschildhorn.fotopresenter.ui.compose.directory.DirectoryGrid -import com.kevinschildhorn.fotopresenter.ui.compose.directory.FolderDirectoryGridCell -import com.kevinschildhorn.fotopresenter.ui.state.DirectoryGridState -import com.kevinschildhorn.fotopresenter.ui.state.FolderDirectoryGridCellState -import com.kevinschildhorn.fotopresenter.ui.state.ImageDirectoryGridCellState +import com.kevinschildhorn.fotopresenter.ui.screens.directory.composables.DirectoryGridCell +import com.kevinschildhorn.fotopresenter.ui.screens.directory.composables.DirectoryGrid +import com.kevinschildhorn.fotopresenter.ui.screens.directory.composables.FolderDirectoryGridCell +import com.kevinschildhorn.fotopresenter.ui.screens.directory.DirectoryGridState +import com.kevinschildhorn.fotopresenter.ui.screens.directory.FolderDirectoryGridCellState +import com.kevinschildhorn.fotopresenter.ui.screens.directory.ImageDirectoryGridCellState @Preview @Composable @@ -44,5 +41,6 @@ fun DirectoryGridPreview() { ), onFolderPressed = {}, onImageDirectoryPressed = {}, + onStartSlideshow = {}, ) } diff --git a/shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/LoginPreviews.kt b/shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/LoginPreviews.kt index a0d5a9a3..18aad095 100644 --- a/shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/LoginPreviews.kt +++ b/shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/LoginPreviews.kt @@ -3,13 +3,13 @@ package com.kevinschildhorn.fotopresenter.ui.compose import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview -import com.kevinschildhorn.fotopresenter.ui.compose.common.ErrorView -import com.kevinschildhorn.fotopresenter.ui.compose.common.TitleView -import com.kevinschildhorn.fotopresenter.ui.compose.login.LoginPasswordTextField -import com.kevinschildhorn.fotopresenter.ui.compose.login.LoginTextField -import com.kevinschildhorn.fotopresenter.ui.compose.login.LoginScreenForm -import com.kevinschildhorn.fotopresenter.ui.state.LoginScreenState -import com.kevinschildhorn.fotopresenter.ui.state.UiState +import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.ErrorView +import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.TitleView +import com.kevinschildhorn.fotopresenter.ui.screens.login.composables.LoginPasswordTextField +import com.kevinschildhorn.fotopresenter.ui.screens.login.composables.LoginTextField +import com.kevinschildhorn.fotopresenter.ui.screens.login.composables.LoginScreenForm +import com.kevinschildhorn.fotopresenter.ui.screens.login.LoginScreenState +import com.kevinschildhorn.fotopresenter.ui.UiState @Preview @Composable diff --git a/shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/ViewModel.kt b/shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/shared/ViewModel.kt similarity index 86% rename from shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/ViewModel.kt rename to shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/shared/ViewModel.kt index b496efac..810ca1b3 100644 --- a/shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/ViewModel.kt +++ b/shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/shared/ViewModel.kt @@ -1,4 +1,4 @@ -package com.kevinschildhorn.fotopresenter.ui.viewmodel +package com.kevinschildhorn.fotopresenter.ui.shared import androidx.lifecycle.ViewModel import kotlinx.coroutines.CoroutineScope @@ -9,4 +9,4 @@ actual abstract class ViewModel actual constructor() : ViewModel() { actual override fun onCleared() { super.onCleared() } -} +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/App.kt b/shared/src/commonMain/kotlin/App.kt index 3a951d83..5291b201 100644 --- a/shared/src/commonMain/kotlin/App.kt +++ b/shared/src/commonMain/kotlin/App.kt @@ -2,16 +2,19 @@ import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import com.kevinschildhorn.fotopresenter.ui.compose.DirectoryScreen -import com.kevinschildhorn.fotopresenter.ui.compose.LoginScreen -import com.kevinschildhorn.fotopresenter.ui.compose.Screen -import com.kevinschildhorn.fotopresenter.ui.viewmodel.DirectoryViewModel -import com.kevinschildhorn.fotopresenter.ui.viewmodel.LoginViewModel +import com.kevinschildhorn.fotopresenter.ui.screens.common.Screen +import com.kevinschildhorn.fotopresenter.ui.screens.directory.DirectoryScreen +import com.kevinschildhorn.fotopresenter.ui.screens.directory.DirectoryViewModel +import com.kevinschildhorn.fotopresenter.ui.screens.login.LoginScreen +import com.kevinschildhorn.fotopresenter.ui.screens.login.LoginViewModel +import com.kevinschildhorn.fotopresenter.ui.screens.slideshow.SlideshowScreen +import com.kevinschildhorn.fotopresenter.ui.screens.slideshow.SlideshowViewModel @Composable fun App( loginViewModel: LoginViewModel, directoryViewModel: DirectoryViewModel, + slideshowViewModel: SlideshowViewModel, ) { val currentScreen = remember { mutableStateOf(Screen.LOGIN) } @@ -24,9 +27,21 @@ fun App( } Screen.DIRECTORY -> - DirectoryScreen(directoryViewModel) { - loginViewModel.setLoggedOut() - currentScreen.value = Screen.LOGIN + DirectoryScreen( + directoryViewModel, + onLogout = { + loginViewModel.setLoggedOut() + currentScreen.value = Screen.LOGIN + }, + onStartSlideshow = { + slideshowViewModel.setSlideshow(it) + currentScreen.value = Screen.SLIDESHOW + }, + ) + + Screen.SLIDESHOW -> + SlideshowScreen(slideshowViewModel) { + currentScreen.value = Screen.DIRECTORY } } } diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/Koin.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/Koin.kt index 4fd20e16..63186b53 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/Koin.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/Koin.kt @@ -4,19 +4,24 @@ import co.touchlab.kermit.Logger import co.touchlab.kermit.LoggerConfig import com.kevinschildhorn.fotopresenter.data.datasources.CredentialsDataSource import com.kevinschildhorn.fotopresenter.data.datasources.DirectoryDataSource +import com.kevinschildhorn.fotopresenter.data.datasources.ImageCacheDataSource import com.kevinschildhorn.fotopresenter.data.datasources.ImageRemoteDataSource import com.kevinschildhorn.fotopresenter.data.repositories.CredentialsRepository import com.kevinschildhorn.fotopresenter.data.repositories.DirectoryRepository 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 +import com.kevinschildhorn.fotopresenter.domain.connection.AutoConnectUseCase +import com.kevinschildhorn.fotopresenter.domain.connection.ConnectToServerUseCase +import com.kevinschildhorn.fotopresenter.domain.connection.DisconnectFromServerUseCase +import com.kevinschildhorn.fotopresenter.domain.connection.SaveCredentialsUseCase +import com.kevinschildhorn.fotopresenter.domain.directory.ChangeDirectoryUseCase +import com.kevinschildhorn.fotopresenter.domain.image.RetrieveImageDirectoriesUseCase +import com.kevinschildhorn.fotopresenter.domain.image.RetrieveImageUseCase +import com.kevinschildhorn.fotopresenter.ui.screens.directory.DirectoryViewModel +import com.kevinschildhorn.fotopresenter.ui.screens.login.LoginViewModel +import com.kevinschildhorn.fotopresenter.ui.screens.slideshow.SlideshowViewModel +import com.kevinschildhorn.fotopresenter.ui.shared.CacheInterface +import com.kevinschildhorn.fotopresenter.ui.shared.SharedCache import org.koin.core.module.Module import org.koin.dsl.module @@ -25,19 +30,22 @@ val commonModule = val baseLogger = Logger(LoggerConfig.default) // Data + single { SharedCache } single { CredentialsDataSource(get()) } single { CredentialsRepository(get()) } single { DirectoryDataSource(get()) } single { DirectoryRepository(get()) } single { ImageRemoteDataSource(get()) } single { ImageRepository(get()) } + single { ImageCacheDataSource(get()) } // Domain factory { ConnectToServerUseCase(get(), baseLogger.withTag("ConnectToServerUseCase")) } 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 { DisconnectFromServerUseCase(get(), get(), baseLogger.withTag("LogoutUseCase")) } + factory { RetrieveImageDirectoriesUseCase(baseLogger.withTag("LogoutUseCase")) } factory { RetrieveDirectoryContentsUseCase( get(), @@ -45,11 +53,12 @@ val commonModule = baseLogger.withTag("RetrieveDirectoryContentsUseCase"), ) } - factory { RetrieveImagesUseCase(baseLogger.withTag("RetrieveImagesUseCase")) } + factory { RetrieveImageUseCase(get(), baseLogger.withTag("RetrieveImagesUseCase")) } // UI single { LoginViewModel(baseLogger.withTag("LoginViewModel"), get()) } single { DirectoryViewModel(baseLogger.withTag("DirectoryViewModel")) } + single { SlideshowViewModel(baseLogger.withTag("SlideshowViewModel")) } } internal expect val platformModule: Module diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/Directory.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/Directory.kt index c13e1547..b66f0102 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/Directory.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/Directory.kt @@ -15,12 +15,16 @@ interface Directory { data class FolderDirectory( override val details: NetworkDirectoryDetails, -) : Directory +) : Directory { + override fun toString(): String = "(F:${details.fullPath}:${details.id})" +} data class ImageDirectory( override val details: NetworkDirectoryDetails, val image: SharedImage? = null, -) : Directory +) : Directory { + override fun toString(): String = "(I:${details.fullPath}:${details.id})" +} data class DirectoryContents( val folders: List = emptyList(), @@ -28,4 +32,14 @@ data class DirectoryContents( ) { val allDirectories: List get() = folders + images + + override fun toString(): String { + return """ + DirectoryContents: + Folders: ${folders.count()} + ${folders.map { it.toString() }.joinToString(", ")} + Images: ${images.count()} + ${images.map { it.toString() }.joinToString(", ")} + """ + } } diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/ImageSlideshowDetails.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/ImageSlideshowDetails.kt new file mode 100644 index 00000000..1072f212 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/ImageSlideshowDetails.kt @@ -0,0 +1,5 @@ +package com.kevinschildhorn.fotopresenter.data + +data class ImageSlideshowDetails( + val directories: List = emptyList(), +) diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/State.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/State.kt index cf5cf657..d0f7d6ef 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/State.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/State.kt @@ -2,8 +2,8 @@ package com.kevinschildhorn.fotopresenter.data import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import com.kevinschildhorn.fotopresenter.ui.compose.common.ErrorView -import com.kevinschildhorn.fotopresenter.ui.compose.common.LoadingOverlay +import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.ErrorView +import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.LoadingOverlay sealed class State { data object IDLE : State() diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/datasources/ImageCacheDataSource.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/datasources/ImageCacheDataSource.kt index b2944695..8ad60bbb 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/datasources/ImageCacheDataSource.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/datasources/ImageCacheDataSource.kt @@ -2,16 +2,16 @@ 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 +import com.kevinschildhorn.fotopresenter.ui.shared.CacheInterface -class ImageCacheDataSource { - fun getImage(directory: NetworkDirectoryDetails): ImageBitmap? = SharedCache.getImage(directory.cacheId) +class ImageCacheDataSource(private val cache: CacheInterface) { + fun getImage(directory: NetworkDirectoryDetails): ImageBitmap? = cache.getImage(directory.cacheId) fun saveImage( directory: NetworkDirectoryDetails, bitmap: ImageBitmap, ) { - SharedCache.cacheImage(directory.cacheId, bitmap) + cache.cacheImage(directory.cacheId, bitmap) } private val NetworkDirectoryDetails.cacheId: String diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/RetrieveDirectoryContentsUseCase.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/RetrieveDirectoryContentsUseCase.kt index 8b94f807..07183856 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/RetrieveDirectoryContentsUseCase.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/RetrieveDirectoryContentsUseCase.kt @@ -9,7 +9,7 @@ import com.kevinschildhorn.fotopresenter.data.repositories.ImageRepository import com.kevinschildhorn.fotopresenter.ui.shared.SharedImage /** -Retrieving Directories from Location +Retrieving Directory Contents from Path **/ class RetrieveDirectoryContentsUseCase( private val directoryRepository: DirectoryRepository, @@ -17,8 +17,10 @@ class RetrieveDirectoryContentsUseCase( private val logger: Logger, ) { suspend operator fun invoke(path: String): DirectoryContents { + logger.i { "Getting directory Contents at path $path" } val directoryContents = directoryRepository.getDirectoryContents(path) return directoryContents.updateImages { + logger.i { "Updating Image File ${it.name}" } imageRepository.getImage(it) } } diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/AutoConnectUseCase.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/connection/AutoConnectUseCase.kt similarity index 70% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/AutoConnectUseCase.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/connection/AutoConnectUseCase.kt index ac292e87..786f8730 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/AutoConnectUseCase.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/connection/AutoConnectUseCase.kt @@ -1,4 +1,4 @@ -package com.kevinschildhorn.fotopresenter.domain +package com.kevinschildhorn.fotopresenter.domain.connection import co.touchlab.kermit.Logger import com.kevinschildhorn.fotopresenter.data.network.NetworkHandler @@ -14,9 +14,12 @@ class AutoConnectUseCase( ) { suspend operator fun invoke(): Boolean = try { + logger.i { "Fetching Credentials" } val credentials = repository.fetchCredentials() + logger.i { "Connecting to the client with auto-connect:${credentials.shouldAutoConnect}" } client.connect(credentials) } catch (e: Exception) { + logger.e(e) { "Could not connect" } false } } diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/ConnectToServerUseCase.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/connection/ConnectToServerUseCase.kt similarity index 84% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/ConnectToServerUseCase.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/connection/ConnectToServerUseCase.kt index 032a9caa..cb9e9994 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/ConnectToServerUseCase.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/connection/ConnectToServerUseCase.kt @@ -1,11 +1,11 @@ -package com.kevinschildhorn.fotopresenter.domain +package com.kevinschildhorn.fotopresenter.domain.connection import co.touchlab.kermit.Logger import com.kevinschildhorn.fotopresenter.data.LoginCredentials import com.kevinschildhorn.fotopresenter.data.network.NetworkHandler /** -Connect to Server using FTPS +Connect to Server using The [NetworkHandler] **/ class ConnectToServerUseCase( private val client: NetworkHandler, diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/LogoutUseCase.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/connection/DisconnectFromServerUseCase.kt similarity index 73% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/LogoutUseCase.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/connection/DisconnectFromServerUseCase.kt index 9ee1dd66..9b9ea8ed 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/LogoutUseCase.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/connection/DisconnectFromServerUseCase.kt @@ -1,4 +1,4 @@ -package com.kevinschildhorn.fotopresenter.domain +package com.kevinschildhorn.fotopresenter.domain.connection import co.touchlab.kermit.Logger import com.kevinschildhorn.fotopresenter.data.network.NetworkHandler @@ -7,13 +7,15 @@ import com.kevinschildhorn.fotopresenter.data.repositories.CredentialsRepository /** Logging out of App **/ -class LogoutUseCase( +class DisconnectFromServerUseCase( private val credentialsRepository: CredentialsRepository, private val networkHandler: NetworkHandler, private val logger: Logger, ) { suspend operator fun invoke() { + logger.i { "Disconnecting" } networkHandler.disconnect() + logger.d { "Clearing Auto-connect" } credentialsRepository.clearAutoConnect() } } diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/SaveCredentialsUseCase.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/connection/SaveCredentialsUseCase.kt similarity index 93% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/SaveCredentialsUseCase.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/connection/SaveCredentialsUseCase.kt index 4c37bf19..d1e05cd1 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/SaveCredentialsUseCase.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/connection/SaveCredentialsUseCase.kt @@ -1,4 +1,4 @@ -package com.kevinschildhorn.fotopresenter.domain +package com.kevinschildhorn.fotopresenter.domain.connection import co.touchlab.kermit.Logger import com.kevinschildhorn.fotopresenter.data.LoginCredentials diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/ChangeDirectoryUseCase.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/directory/ChangeDirectoryUseCase.kt similarity index 68% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/ChangeDirectoryUseCase.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/directory/ChangeDirectoryUseCase.kt index 4722c432..072d556d 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/ChangeDirectoryUseCase.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/directory/ChangeDirectoryUseCase.kt @@ -1,19 +1,24 @@ -package com.kevinschildhorn.fotopresenter.domain +package com.kevinschildhorn.fotopresenter.domain.directory import co.touchlab.kermit.Logger import com.kevinschildhorn.fotopresenter.data.datasources.DirectoryDataSource import com.kevinschildhorn.fotopresenter.data.network.NetworkHandlerException import kotlin.coroutines.cancellation.CancellationException +/** +Opening the directory from the Path + **/ class ChangeDirectoryUseCase( private val dataSource: DirectoryDataSource, private val logger: Logger, ) { @Throws(NetworkHandlerException::class, CancellationException::class) - suspend operator fun invoke(path: String) = + suspend operator fun invoke(path: String): String = try { + logger.i { "Changing Directory to path $path" } dataSource.changeDirectory(path) } catch (e: Exception) { + logger.e(e) { "Error Changing Directory" } throw NetworkHandlerException(e.message ?: "") } } diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/image/RetrieveImageDirectoriesUseCase.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/image/RetrieveImageDirectoriesUseCase.kt new file mode 100644 index 00000000..7d9ffcd8 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/image/RetrieveImageDirectoriesUseCase.kt @@ -0,0 +1,37 @@ +package com.kevinschildhorn.fotopresenter.domain.image + +import co.touchlab.kermit.Logger +import com.kevinschildhorn.fotopresenter.data.ImageDirectory +import com.kevinschildhorn.fotopresenter.data.network.NetworkDirectoryDetails +import com.kevinschildhorn.fotopresenter.domain.RetrieveDirectoryContentsUseCase +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +/** +Retrieving Image Directories from Path + **/ +class RetrieveImageDirectoriesUseCase( + private val logger: Logger, +) : KoinComponent { + suspend operator fun invoke( + directoryDetails: NetworkDirectoryDetails, + recursively: Boolean = true, + ): List { + logger.i { "Retrieving images from directory ${directoryDetails.fullPath}" } + val retrieveContentsUseCase: RetrieveDirectoryContentsUseCase by inject() + val contents = retrieveContentsUseCase(directoryDetails.fullPath) + + logger.i { "Retrieved Contents: $contents" } + val folders = contents.folders + val images = contents.images.toMutableList() + + if (recursively) { + logger.i { "Recursively getting Images from sub-folder" } + val recursiveUseCase: RetrieveImageDirectoriesUseCase by inject() + folders.forEach { + images.addAll(recursiveUseCase(it.details)) + } + } + return images + } +} diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/RetrieveImagesUseCase.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/image/RetrieveImageUseCase.kt similarity index 53% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/RetrieveImagesUseCase.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/image/RetrieveImageUseCase.kt index 79e94b23..f20206c5 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/RetrieveImagesUseCase.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/domain/image/RetrieveImageUseCase.kt @@ -1,34 +1,40 @@ -package com.kevinschildhorn.fotopresenter.domain +package com.kevinschildhorn.fotopresenter.domain.image 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 +import com.kevinschildhorn.fotopresenter.data.datasources.ImageCacheDataSource /** -Retrieving Directories from Location +Retrieving Image Bitmap **/ -class RetrieveImagesUseCase( +class RetrieveImageUseCase( + private val imageCacheDataSource: ImageCacheDataSource, private val logger: Logger, ) { suspend operator fun invoke( directory: ImageDirectory, callback: suspend (State) -> Unit, ) { + logger.i { "Starting to get Image ${directory.name}" } callback(State.LOADING) - SharedCache.getImage(directory.name)?.let { + + imageCacheDataSource.getImage(directory.details)?.let { + logger.i { "Image found in cache, using that" } callback(State.SUCCESS(it)) } - val imageBitmap: ImageBitmap? = directory.image?.getImageBitmap(400) + logger.i { "Getting Image Bitmap from File ${directory.name}" } + 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) + logger.i { "Caching new Image ${directory.name}" } + imageCacheDataSource.saveImage(directory.details, it) } } } diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/state/UiState.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/UiState.kt similarity index 70% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/state/UiState.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/UiState.kt index cd152bff..50f9047e 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/state/UiState.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/UiState.kt @@ -1,9 +1,9 @@ -package com.kevinschildhorn.fotopresenter.ui.state +package com.kevinschildhorn.fotopresenter.ui import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import com.kevinschildhorn.fotopresenter.ui.compose.common.ErrorView -import com.kevinschildhorn.fotopresenter.ui.compose.common.LoadingOverlay +import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.ErrorView +import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.LoadingOverlay sealed class UiState { data object IDLE : UiState() diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/Screen.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/Screen.kt deleted file mode 100644 index dafddcdc..00000000 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/Screen.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.kevinschildhorn.fotopresenter.ui.compose - -enum class Screen { - LOGIN, - DIRECTORY, -} diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/ActionSheetContext.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/ActionSheetContext.kt new file mode 100644 index 00000000..3e625d36 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/ActionSheetContext.kt @@ -0,0 +1,11 @@ +package com.kevinschildhorn.fotopresenter.ui.screens.common + +enum class ActionSheetAction(val title: String) { + START_SLIDESHOW("Start A Slideshow"), + NONE("Nothing"), // TEMP +} + +data class ActionSheetContext( + val action: ActionSheetAction, + val id: Int, +) diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/ImageScreenState.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/ImageScreenState.kt new file mode 100644 index 00000000..2ccf8f4c --- /dev/null +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/ImageScreenState.kt @@ -0,0 +1,10 @@ +package com.kevinschildhorn.fotopresenter.ui.screens.common + +import androidx.compose.ui.graphics.ImageBitmap +import com.kevinschildhorn.fotopresenter.data.ImageDirectory + +data class ImageScreenState( + val imageDirectories: List = emptyList(), + val selectedImageIndex: Int? = null, + val selectedImage: ImageBitmap? = null, +) diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/ImageViewModel.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/ImageViewModel.kt new file mode 100644 index 00000000..9c25ed15 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/ImageViewModel.kt @@ -0,0 +1,96 @@ +package com.kevinschildhorn.fotopresenter.ui.screens.common + +import androidx.compose.ui.graphics.ImageBitmap +import com.kevinschildhorn.fotopresenter.data.ImageDirectory +import com.kevinschildhorn.fotopresenter.data.State +import com.kevinschildhorn.fotopresenter.domain.image.RetrieveImageUseCase +import com.kevinschildhorn.fotopresenter.extension.getNextIndex +import com.kevinschildhorn.fotopresenter.extension.getPreviousIndex +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +interface ImageViewModel { + var scope: CoroutineScope? + val imageUiState: StateFlow + + fun setImageDirectories(directories: List) + + fun showPreviousImage() + + fun showNextImage() + + fun setSelectedImage(index: Int?) + + fun setImageScope(coroutineScope: CoroutineScope) { + scope = coroutineScope + } + + fun clearPresentedImage() +} + +class DefaultImageViewModel() : ImageViewModel, KoinComponent { + private val _uiState = MutableStateFlow(ImageScreenState()) + override var scope: CoroutineScope? = null + override val imageUiState: StateFlow = _uiState.asStateFlow() + + override fun setImageDirectories(directories: List) { + _uiState.update { it.copy(imageDirectories = directories) } + } + + override fun showPreviousImage() { + with(imageUiState.value) { + selectedImageIndex?.let { index -> + val nextIndex = imageDirectories.getPreviousIndex(index) + _uiState.update { it.copy(selectedImageIndex = nextIndex) } + } + } + updateSelectedImage() + } + + override fun showNextImage() { + with(imageUiState.value) { + selectedImageIndex?.let { index -> + val nextIndex = imageDirectories.getNextIndex(index) + _uiState.update { it.copy(selectedImageIndex = nextIndex) } + } + } + updateSelectedImage() + } + + override fun setSelectedImage(index: Int?) { + _uiState.update { it.copy(selectedImageIndex = index) } + updateSelectedImage() + } + + override fun clearPresentedImage() { + _uiState.update { it.copy(selectedImage = null, selectedImageIndex = null) } + } + + private fun updateSelectedImage() { + with(imageUiState.value) { + selectedImageIndex?.let { index -> + this.imageDirectories.getOrNull(index)?.let { + showPhoto(it) + } + } + } + } + + private fun showPhoto(imageDirectory: ImageDirectory) { + scope?.launch(Dispatchers.Default) { + val retrieveImagesUseCase: RetrieveImageUseCase by inject() + retrieveImagesUseCase(imageDirectory) { newState: State -> + newState.value?.let { imageBitmap -> + _uiState.update { it.copy(selectedImage = imageBitmap) } + } + } + } + } +} diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/Screen.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/Screen.kt new file mode 100644 index 00000000..8d1be466 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/Screen.kt @@ -0,0 +1,7 @@ +package com.kevinschildhorn.fotopresenter.ui.screens.common + +enum class Screen { + LOGIN, + DIRECTORY, + SLIDESHOW, +} diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/ScreenState.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/ScreenState.kt new file mode 100644 index 00000000..e7eafd47 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/ScreenState.kt @@ -0,0 +1,7 @@ +package com.kevinschildhorn.fotopresenter.ui.screens.common + +import com.kevinschildhorn.fotopresenter.ui.UiState + +interface ScreenState { + val state: UiState +} diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/common/ActionSheet.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/ActionSheet.kt similarity index 92% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/common/ActionSheet.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/ActionSheet.kt index b72c0f50..71e7b465 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/common/ActionSheet.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/ActionSheet.kt @@ -1,4 +1,4 @@ -package com.kevinschildhorn.fotopresenter.ui.compose.common +package com.kevinschildhorn.fotopresenter.ui.screens.common.composables import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn @@ -29,12 +29,14 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.kevinschildhorn.atomik.color.base.composeColor import com.kevinschildhorn.fotopresenter.ui.atoms.FotoColors +import com.kevinschildhorn.fotopresenter.ui.screens.common.ActionSheetContext @Composable fun ActionSheet( visible: Boolean, offset: Int, - values: List, + values: List, + onClick: (ActionSheetContext) -> Unit, onDismiss: () -> Unit, ) { val interactionSource = remember { MutableInteractionSource() } @@ -97,10 +99,11 @@ fun ActionSheet( .wrapContentHeight() .padding(start = 10.dp), onClick = { + onClick(it) }, ) { Text( - it, + it.action.title, color = FotoColors.secondaryText.composeColor, textAlign = TextAlign.Start, modifier = Modifier.fillMaxWidth(), diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/common/ErrorView.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/ErrorView.kt similarity index 87% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/common/ErrorView.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/ErrorView.kt index 1649965b..518e304b 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/common/ErrorView.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/ErrorView.kt @@ -1,4 +1,4 @@ -package com.kevinschildhorn.fotopresenter.ui.compose.common +package com.kevinschildhorn.fotopresenter.ui.screens.common.composables import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -10,7 +10,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.kevinschildhorn.atomik.atomic.atoms.textStyle import com.kevinschildhorn.atomik.color.base.composeColor -import com.kevinschildhorn.fotopresenter.ui.atoms.LoginScreenAtoms.errorView +import com.kevinschildhorn.fotopresenter.ui.screens.login.LoginScreenAtoms.errorView @Composable fun ErrorView( diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/common/FormColumn.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/FormColumn.kt similarity index 92% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/common/FormColumn.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/FormColumn.kt index 62720192..02414181 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/common/FormColumn.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/FormColumn.kt @@ -1,4 +1,4 @@ -package com.kevinschildhorn.fotopresenter.ui.compose.common +package com.kevinschildhorn.fotopresenter.ui.screens.common.composables import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/ImagePreviewOverlay.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/ImagePreviewOverlay.kt similarity index 97% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/ImagePreviewOverlay.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/ImagePreviewOverlay.kt index 3c1bf2fb..e6650799 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/ImagePreviewOverlay.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/ImagePreviewOverlay.kt @@ -1,4 +1,4 @@ -package com.kevinschildhorn.fotopresenter.ui.compose +package com.kevinschildhorn.fotopresenter.ui.screens.common.composables import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -23,7 +23,6 @@ import androidx.compose.ui.unit.dp import com.kevinschildhorn.atomik.color.base.composeColor 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 diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/common/LoadingOverlay.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/LoadingOverlay.kt similarity index 92% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/common/LoadingOverlay.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/LoadingOverlay.kt index babc2956..ea9c3940 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/common/LoadingOverlay.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/LoadingOverlay.kt @@ -1,4 +1,4 @@ -package com.kevinschildhorn.fotopresenter.ui.compose.common +package com.kevinschildhorn.fotopresenter.ui.screens.common.composables import androidx.compose.foundation.background import androidx.compose.foundation.layout.fillMaxSize diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/common/Overlay.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/Overlay.kt similarity index 86% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/common/Overlay.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/Overlay.kt index 06c486ba..978d3e8e 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/common/Overlay.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/Overlay.kt @@ -1,4 +1,4 @@ -package com.kevinschildhorn.fotopresenter.ui.compose.common +package com.kevinschildhorn.fotopresenter.ui.screens.common.composables import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/common/PrimaryButton.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/PrimaryButton.kt similarity index 90% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/common/PrimaryButton.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/PrimaryButton.kt index 96ff0768..36629886 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/common/PrimaryButton.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/PrimaryButton.kt @@ -1,4 +1,4 @@ -package com.kevinschildhorn.fotopresenter.ui.compose.common +package com.kevinschildhorn.fotopresenter.ui.screens.common.composables import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.width @@ -11,7 +11,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.kevinschildhorn.atomik.atomic.atoms.textStyle import com.kevinschildhorn.atomik.color.base.composeColor -import com.kevinschildhorn.fotopresenter.ui.atoms.LoginScreenAtoms.primaryButton +import com.kevinschildhorn.fotopresenter.ui.screens.login.LoginScreenAtoms.primaryButton @Composable fun PrimaryButton( diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/common/TitleView.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/TitleView.kt similarity index 79% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/common/TitleView.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/TitleView.kt index 5da6a69a..9afc7b4d 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/common/TitleView.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/TitleView.kt @@ -1,4 +1,4 @@ -package com.kevinschildhorn.fotopresenter.ui.compose.common +package com.kevinschildhorn.fotopresenter.ui.screens.common.composables import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -6,7 +6,7 @@ import androidx.compose.ui.Modifier import com.kevinschildhorn.atomik.atomic.atoms.textStyle import com.kevinschildhorn.atomik.color.base.composeColor import com.kevinschildhorn.fotopresenter.ui.atoms.FotoTypography -import com.kevinschildhorn.fotopresenter.ui.atoms.LoginScreenAtoms.title +import com.kevinschildhorn.fotopresenter.ui.screens.login.LoginScreenAtoms.title @Composable fun TitleView( diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/atoms/DirectoryAtoms.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryAtoms.kt similarity index 83% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/atoms/DirectoryAtoms.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryAtoms.kt index ecf22c8b..5dea1c0f 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/atoms/DirectoryAtoms.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryAtoms.kt @@ -1,9 +1,11 @@ -package com.kevinschildhorn.fotopresenter.ui.atoms +package com.kevinschildhorn.fotopresenter.ui.screens.directory import com.kevinschildhorn.atomik.atomic.atoms.Atom import com.kevinschildhorn.atomik.atomic.atoms.ImageAtom import com.kevinschildhorn.atomik.atomic.atoms.interfaces.SimpleTextAtom import com.kevinschildhorn.atomik.atomic.molecules.BaseMolecule +import com.kevinschildhorn.fotopresenter.ui.atoms.FotoColors +import com.kevinschildhorn.fotopresenter.ui.atoms.FotoTypography import compose.icons.EvaIcons import compose.icons.evaicons.Fill import compose.icons.evaicons.fill.Folder diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/DirectoryScreen.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryScreen.kt similarity index 64% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/DirectoryScreen.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryScreen.kt index 248b7548..3678e363 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/DirectoryScreen.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryScreen.kt @@ -1,31 +1,36 @@ -package com.kevinschildhorn.fotopresenter.ui.compose +package com.kevinschildhorn.fotopresenter.ui.screens.directory import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect 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.ImageSlideshowDetails 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 +import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.ImagePreviewOverlay +import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.PrimaryButton +import com.kevinschildhorn.fotopresenter.ui.screens.directory.composables.DirectoryGrid @Composable fun DirectoryScreen( viewModel: DirectoryViewModel, onLogout: () -> Unit, + onStartSlideshow: (ImageSlideshowDetails) -> Unit, ) { LaunchedEffect(Unit) { viewModel.refreshScreen() } val uiState by viewModel.uiState.collectAsState() + val imageUiState by viewModel.imageUiState.collectAsState() - if (!uiState.loggedIn) - { - onLogout() - } + // Navigation + if (!uiState.loggedIn) onLogout() + uiState.slideshowDetails?.let { + onStartSlideshow(it) + } + + // State uiState.state.asComposable( modifier = Modifier.padding( @@ -33,6 +38,8 @@ fun DirectoryScreen( vertical = Padding.SMALL.dp, ), ) + + // UI PrimaryButton("Logout") { viewModel.logout() } @@ -47,8 +54,11 @@ fun DirectoryScreen( onImageDirectoryPressed = { viewModel.setSelectedImageById(it) }, + onStartSlideshow = { + viewModel.startSlideshow(it) + }, ) - uiState.selectedImage?.let { + imageUiState.selectedImage?.let { ImagePreviewOverlay( it, onDismiss = { diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/state/DirectoryScreenState.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryScreenState.kt similarity index 58% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/state/DirectoryScreenState.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryScreenState.kt index 39d2a3cb..3b70bf53 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/state/DirectoryScreenState.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryScreenState.kt @@ -1,15 +1,17 @@ -package com.kevinschildhorn.fotopresenter.ui.state +package com.kevinschildhorn.fotopresenter.ui.screens.directory import androidx.compose.ui.graphics.ImageBitmap +import com.kevinschildhorn.fotopresenter.data.ImageSlideshowDetails import com.kevinschildhorn.fotopresenter.data.State -import com.kevinschildhorn.fotopresenter.extension.getNextIndex -import com.kevinschildhorn.fotopresenter.extension.getPreviousIndex +import com.kevinschildhorn.fotopresenter.ui.UiState +import com.kevinschildhorn.fotopresenter.ui.screens.common.ActionSheetAction +import com.kevinschildhorn.fotopresenter.ui.screens.common.ActionSheetContext +import com.kevinschildhorn.fotopresenter.ui.screens.common.ScreenState data class DirectoryScreenState( val currentPath: String = "", var directoryGridState: DirectoryGridState = DirectoryGridState(emptyList(), mutableListOf()), - val selectedImageIndex: Int? = null, - val selectedImage: ImageBitmap? = null, + val slideshowDetails: ImageSlideshowDetails? = null, val loggedIn: Boolean = true, override val state: UiState = UiState.IDLE, ) : ScreenState { @@ -35,27 +37,6 @@ data class DirectoryScreenState( } fun getImageIndexFromId(id: Int): Int = directoryGridState.imageStates.indexOfFirst { it.id == id } - - fun getImageStateByIndex(): State? = - selectedImageIndex?.let { index -> - directoryGridState.imageStates.getOrNull(index)?.imageState - } - - fun getNextImageIndex(): Int? { - selectedImageIndex?.let { - return directoryGridState.imageStates.getNextIndex(it) - } ?: run { - return null - } - } - - fun getPreviousImageIndex(): Int? { - selectedImageIndex?.let { - return directoryGridState.imageStates.getPreviousIndex(it) - } ?: run { - return null - } - } } data class DirectoryGridState( @@ -64,22 +45,42 @@ data class DirectoryGridState( ) { val allStates: List get() = folderStates + imageStates + + override fun toString(): String = + """ + Directory Grid State: + Folders: ${folderStates.count()} + ${folderStates.map { it.toString() }.joinToString(", ")} + Images: ${imageStates.count()} + ${imageStates.map { it.toString() }.joinToString(", ")} + """ } data class FolderDirectoryGridCellState( override val name: String, override val id: Int, -) : DirectoryGridCellState +) : DirectoryGridCellState { + override val actionSheetContexts: List + get() = listOf(ActionSheetContext(ActionSheetAction.START_SLIDESHOW, 1)) + + override fun toString(): String = "(F:$name:$id)" +} data class ImageDirectoryGridCellState( val imageState: State, override val name: String, override val id: Int, -) : DirectoryGridCellState +) : DirectoryGridCellState { + override val actionSheetContexts: List + get() = listOf(ActionSheetContext(ActionSheetAction.NONE, 1)) + + override fun toString(): String = "(I:$name:$id)" +} interface DirectoryGridCellState { val name: String val id: Int + val actionSheetContexts: List val isFolderGridCell: Boolean get() = (this is FolderDirectoryGridCellState) diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/DirectoryViewModel.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryViewModel.kt similarity index 68% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/DirectoryViewModel.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryViewModel.kt index 7d62e534..3d9252a0 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/DirectoryViewModel.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryViewModel.kt @@ -1,20 +1,21 @@ -package com.kevinschildhorn.fotopresenter.ui.viewmodel +package com.kevinschildhorn.fotopresenter.ui.screens.directory import co.touchlab.kermit.Logger import com.kevinschildhorn.fotopresenter.data.DirectoryContents +import com.kevinschildhorn.fotopresenter.data.ImageSlideshowDetails import com.kevinschildhorn.fotopresenter.data.State import com.kevinschildhorn.fotopresenter.data.network.NetworkDirectoryDetails import com.kevinschildhorn.fotopresenter.data.network.NetworkHandlerException -import com.kevinschildhorn.fotopresenter.domain.ChangeDirectoryUseCase -import com.kevinschildhorn.fotopresenter.domain.LogoutUseCase import com.kevinschildhorn.fotopresenter.domain.RetrieveDirectoryContentsUseCase -import com.kevinschildhorn.fotopresenter.domain.RetrieveImagesUseCase +import com.kevinschildhorn.fotopresenter.domain.connection.DisconnectFromServerUseCase +import com.kevinschildhorn.fotopresenter.domain.directory.ChangeDirectoryUseCase +import com.kevinschildhorn.fotopresenter.domain.image.RetrieveImageDirectoriesUseCase +import com.kevinschildhorn.fotopresenter.domain.image.RetrieveImageUseCase import com.kevinschildhorn.fotopresenter.extension.addPath -import com.kevinschildhorn.fotopresenter.ui.state.DirectoryGridState -import com.kevinschildhorn.fotopresenter.ui.state.DirectoryScreenState -import com.kevinschildhorn.fotopresenter.ui.state.FolderDirectoryGridCellState -import com.kevinschildhorn.fotopresenter.ui.state.ImageDirectoryGridCellState -import com.kevinschildhorn.fotopresenter.ui.state.UiState +import com.kevinschildhorn.fotopresenter.ui.UiState +import com.kevinschildhorn.fotopresenter.ui.screens.common.DefaultImageViewModel +import com.kevinschildhorn.fotopresenter.ui.screens.common.ImageViewModel +import com.kevinschildhorn.fotopresenter.ui.shared.ViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -26,7 +27,9 @@ import org.koin.core.component.inject class DirectoryViewModel( private val logger: Logger, -) : ViewModel(), KoinComponent { +) : ViewModel(), + ImageViewModel by DefaultImageViewModel(), + KoinComponent { private val _uiState = MutableStateFlow(DirectoryScreenState()) val uiState: StateFlow = _uiState.asStateFlow() @@ -35,55 +38,52 @@ class DirectoryViewModel( private val currentPath: String get() = uiState.value.currentPath + init { + setImageScope(viewModelScope) + } + fun refreshScreen() { updateDirectories() } - fun setLoggedIn() { + //region Connection + + fun setLoggedIn() { _uiState.update { it.copy(loggedIn = true) } } fun logout() { - viewModelScope.launch(Dispatchers.Default) { - val logoutUseCase: LogoutUseCase by inject() + viewModelScope.launch { + logger.i { "Logging Out" } + val logoutUseCase: DisconnectFromServerUseCase by inject() logoutUseCase() + logger.d { "Setting loggedIn state to false" } _uiState.update { it.copy(loggedIn = false) } } } - //region Image - fun showPreviousImage() { - val newIndex = _uiState.value.getPreviousImageIndex() - _uiState.update { it.copy(selectedImageIndex = newIndex) } - updateSelectedImage() - } + //endregion - fun showNextImage() { - val newIndex = _uiState.value.getNextImageIndex() - _uiState.update { it.copy(selectedImageIndex = newIndex) } - updateSelectedImage() - } + //region Image - fun clearPresentedImage() { - _uiState.update { it.copy(selectedImage = null, selectedImageIndex = null) } + fun startSlideshow(directoryId: Int) { + _directoryContentsState.value.folders.find { it.id == directoryId }?.let { + viewModelScope.launch(Dispatchers.Default) { + val retrieveImagesUseCase: RetrieveImageDirectoriesUseCase by inject() + val images = retrieveImagesUseCase(it.details) + _uiState.update { it.copy(slideshowDetails = ImageSlideshowDetails(images)) } + } + } } fun setSelectedImageById(imageId: Int?) { + logger.i { "Set Image with ID: $imageId" } var index: Int? = null imageId?.let { index = _uiState.value.getImageIndexFromId(it) } - _uiState.update { it.copy(selectedImageIndex = index) } - updateSelectedImage() - } - - private fun updateSelectedImage() { - val state = - _uiState.value.getImageStateByIndex()?.let { state -> - _uiState.update { it.copy(selectedImage = state.value) } - } ?: run { - _uiState.update { it.copy(selectedImage = null) } - } + logger.i { "Set Image with Index: $index" } + setSelectedImage(index) } //endregion @@ -105,6 +105,7 @@ class DirectoryViewModel( val newPath = changeDirectoryUseCase(currentPath.addPath(directory.name)) logger.i { "New Path got: $newPath" } _uiState.update { it.copy(currentPath = newPath) } + updateDirectories() } catch (e: NetworkHandlerException) { logger.e(e) { "Error Occurred Getting new path" } @@ -135,24 +136,32 @@ class DirectoryViewModel( _uiState.update { it.copy(state = UiState.LOADING) } viewModelScope.launch(Dispatchers.Default) { val retrieveDirectoryUseCase: RetrieveDirectoryContentsUseCase by inject() + logger.i { "Getting Directory Contents" } val directoryContents = retrieveDirectoryUseCase(currentPath) logger.i { "Got Directory Contents: ${directoryContents.allDirectories.count()}" } _directoryContentsState.update { directoryContents } + + logger.i { "Updating State to Success" } + logger.i { "Setting Directories: $directoryContents" } + setImageDirectories(directoryContents.images) + val gridState = directoryContents.asDirectoryGridState + logger.i { "New Grid State $gridState" } _uiState.update { it.copy( - directoryGridState = directoryContents.asDirectoryGridState, + directoryGridState = gridState, state = UiState.SUCCESS, ) } + logger.i { "Current State ${uiState.value.state}" } updatePhotos() } } private fun updatePhotos() { - _directoryContentsState.value.images.forEach { imageDirectory -> + imageUiState.value.imageDirectories.forEach { imageDirectory -> viewModelScope.launch(Dispatchers.Default) { - val retrieveImagesUseCase: RetrieveImagesUseCase by inject() + val retrieveImagesUseCase: RetrieveImageUseCase by inject() retrieveImagesUseCase(imageDirectory) { newState -> _uiState.update { diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/directory/DirectoryGrid.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/composables/DirectoryGrid.kt similarity index 67% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/directory/DirectoryGrid.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/composables/DirectoryGrid.kt index fd0ad089..9d52b641 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/directory/DirectoryGrid.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/composables/DirectoryGrid.kt @@ -1,4 +1,4 @@ -package com.kevinschildhorn.fotopresenter.ui.compose.directory +package com.kevinschildhorn.fotopresenter.ui.screens.directory.composables import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable @@ -17,10 +17,12 @@ import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex -import com.kevinschildhorn.fotopresenter.ui.compose.common.ActionSheet -import com.kevinschildhorn.fotopresenter.ui.state.DirectoryGridState -import com.kevinschildhorn.fotopresenter.ui.state.FolderDirectoryGridCellState -import com.kevinschildhorn.fotopresenter.ui.state.ImageDirectoryGridCellState +import com.kevinschildhorn.fotopresenter.ui.screens.common.ActionSheetAction +import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.ActionSheet +import com.kevinschildhorn.fotopresenter.ui.screens.directory.DirectoryGridCellState +import com.kevinschildhorn.fotopresenter.ui.screens.directory.DirectoryGridState +import com.kevinschildhorn.fotopresenter.ui.screens.directory.FolderDirectoryGridCellState +import com.kevinschildhorn.fotopresenter.ui.screens.directory.ImageDirectoryGridCellState @OptIn(ExperimentalFoundationApi::class) @Composable @@ -30,9 +32,10 @@ fun DirectoryGrid( modifier: Modifier = Modifier, onFolderPressed: (Int) -> Unit, onImageDirectoryPressed: (Int) -> Unit, + onStartSlideshow: (Int) -> Unit, ) { var actionSheetVisible by remember { mutableStateOf(false) } - var contextMenuPhotoId by rememberSaveable { mutableStateOf(null) } + var contextMenuPhotoState by rememberSaveable { mutableStateOf(null) } val haptics = LocalHapticFeedback.current LazyVerticalGrid( @@ -53,7 +56,7 @@ fun DirectoryGrid( }, onLongClick = { haptics.performHapticFeedback(HapticFeedbackType.LongPress) - contextMenuPhotoId = state.id + contextMenuPhotoState = state actionSheetVisible = true }, onLongClickLabel = "Action Sheet", @@ -75,9 +78,21 @@ fun DirectoryGrid( ActionSheet( visible = actionSheetVisible, offset = 200, - values = listOf("Option A", "Option B"), - ) { - actionSheetVisible = false - contextMenuPhotoId = null - } + values = contextMenuPhotoState?.actionSheetContexts ?: emptyList(), + onClick = { + when (it.action) { + ActionSheetAction.START_SLIDESHOW -> { + onStartSlideshow(contextMenuPhotoState?.id!!) + } + ActionSheetAction.NONE -> { + } + } + actionSheetVisible = false + contextMenuPhotoState = null + }, + onDismiss = { + actionSheetVisible = false + contextMenuPhotoState = null + }, + ) } diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/directory/DirectoryGridCell.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/composables/DirectoryGridCell.kt similarity index 91% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/directory/DirectoryGridCell.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/composables/DirectoryGridCell.kt index 77856eec..cd835e0f 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/directory/DirectoryGridCell.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/composables/DirectoryGridCell.kt @@ -1,4 +1,4 @@ -package com.kevinschildhorn.fotopresenter.ui.compose.directory +package com.kevinschildhorn.fotopresenter.ui.screens.directory.composables import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -15,8 +15,8 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.kevinschildhorn.atomik.atomic.atoms.textStyle import com.kevinschildhorn.atomik.color.base.composeColor -import com.kevinschildhorn.fotopresenter.ui.atoms.DirectoryAtoms import com.kevinschildhorn.fotopresenter.ui.atoms.FotoColors +import com.kevinschildhorn.fotopresenter.ui.screens.directory.DirectoryAtoms @Composable fun DirectoryGridCell( diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/directory/FolderDirectoryGridCell.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/composables/FolderDirectoryGridCell.kt similarity index 85% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/directory/FolderDirectoryGridCell.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/composables/FolderDirectoryGridCell.kt index 189d756d..ca4124ae 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/directory/FolderDirectoryGridCell.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/composables/FolderDirectoryGridCell.kt @@ -1,4 +1,4 @@ -package com.kevinschildhorn.fotopresenter.ui.compose.directory +package com.kevinschildhorn.fotopresenter.ui.screens.directory.composables import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight @@ -11,8 +11,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.kevinschildhorn.atomik.color.base.composeColor -import com.kevinschildhorn.fotopresenter.ui.atoms.DirectoryAtoms -import com.kevinschildhorn.fotopresenter.ui.state.FolderDirectoryGridCellState +import com.kevinschildhorn.fotopresenter.ui.screens.directory.DirectoryAtoms +import com.kevinschildhorn.fotopresenter.ui.screens.directory.FolderDirectoryGridCellState @Composable fun FolderDirectoryGridCell( diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/directory/ImageDirectoryGridCell.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/composables/ImageDirectoryGridCell.kt similarity index 92% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/directory/ImageDirectoryGridCell.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/composables/ImageDirectoryGridCell.kt index 0a82b870..8203644c 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/directory/ImageDirectoryGridCell.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/composables/ImageDirectoryGridCell.kt @@ -1,4 +1,4 @@ -package com.kevinschildhorn.fotopresenter.ui.compose.directory +package com.kevinschildhorn.fotopresenter.ui.screens.directory.composables import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -14,7 +14,7 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import com.kevinschildhorn.atomik.color.base.composeColor import com.kevinschildhorn.fotopresenter.ui.atoms.FotoColors -import com.kevinschildhorn.fotopresenter.ui.state.ImageDirectoryGridCellState +import com.kevinschildhorn.fotopresenter.ui.screens.directory.ImageDirectoryGridCellState import compose.icons.EvaIcons import compose.icons.evaicons.Fill import compose.icons.evaicons.fill.QuestionMark diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/LoginScreen.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/login/LoginScreen.kt similarity index 86% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/LoginScreen.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/login/LoginScreen.kt index 3048fe07..a87daac2 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/LoginScreen.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/login/LoginScreen.kt @@ -1,4 +1,4 @@ -package com.kevinschildhorn.fotopresenter.ui.compose +package com.kevinschildhorn.fotopresenter.ui.screens.login import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -12,11 +12,10 @@ import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign +import com.kevinschildhorn.fotopresenter.ui.UiState import com.kevinschildhorn.fotopresenter.ui.atoms.Padding -import com.kevinschildhorn.fotopresenter.ui.compose.common.TitleView -import com.kevinschildhorn.fotopresenter.ui.compose.login.LoginScreenForm -import com.kevinschildhorn.fotopresenter.ui.state.UiState -import com.kevinschildhorn.fotopresenter.ui.viewmodel.LoginViewModel +import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.TitleView +import com.kevinschildhorn.fotopresenter.ui.screens.login.composables.LoginScreenForm @Composable fun LoginScreen( diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/atoms/LoginScreenAtoms.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/login/LoginScreenAtoms.kt similarity index 89% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/atoms/LoginScreenAtoms.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/login/LoginScreenAtoms.kt index da75ce3a..a3dc66da 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/atoms/LoginScreenAtoms.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/login/LoginScreenAtoms.kt @@ -1,4 +1,4 @@ -package com.kevinschildhorn.fotopresenter.ui.atoms +package com.kevinschildhorn.fotopresenter.ui.screens.login import com.kevinschildhorn.atomik.atomic.atoms.TextViewAtom import com.kevinschildhorn.atomik.atomic.atoms.interfaces.SimpleColorAtom @@ -7,6 +7,9 @@ import com.kevinschildhorn.atomik.atomic.molecules.OutlinedTextFieldMolecule import com.kevinschildhorn.atomik.atomic.molecules.TextButtonMolecule import com.kevinschildhorn.atomik.color.base.AtomikColor import com.kevinschildhorn.atomik.typography.base.AtomikTypography +import com.kevinschildhorn.fotopresenter.ui.atoms.FotoColors +import com.kevinschildhorn.fotopresenter.ui.atoms.FotoTypography +import com.kevinschildhorn.fotopresenter.ui.atoms.Padding object LoginScreenAtoms { fun title( diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/state/LoginScreenState.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/login/LoginScreenState.kt similarity index 81% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/state/LoginScreenState.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/login/LoginScreenState.kt index d3ab2ad5..a88867a0 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/state/LoginScreenState.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/login/LoginScreenState.kt @@ -1,7 +1,9 @@ -package com.kevinschildhorn.fotopresenter.ui.state +package com.kevinschildhorn.fotopresenter.ui.screens.login import com.kevinschildhorn.fotopresenter.data.LoginCredentials -import com.kevinschildhorn.fotopresenter.ui.compose.common.ButtonState +import com.kevinschildhorn.fotopresenter.ui.UiState +import com.kevinschildhorn.fotopresenter.ui.screens.common.ScreenState +import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.ButtonState data class LoginScreenState( val hostname: String = "", diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/LoginViewModel.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/login/LoginViewModel.kt similarity index 88% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/LoginViewModel.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/login/LoginViewModel.kt index 63ca3147..27bc5b75 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/LoginViewModel.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/login/LoginViewModel.kt @@ -1,12 +1,12 @@ -package com.kevinschildhorn.fotopresenter.ui.viewmodel +package com.kevinschildhorn.fotopresenter.ui.screens.login import co.touchlab.kermit.Logger import com.kevinschildhorn.fotopresenter.data.repositories.CredentialsRepository -import com.kevinschildhorn.fotopresenter.domain.AutoConnectUseCase -import com.kevinschildhorn.fotopresenter.domain.ConnectToServerUseCase -import com.kevinschildhorn.fotopresenter.domain.SaveCredentialsUseCase -import com.kevinschildhorn.fotopresenter.ui.state.LoginScreenState -import com.kevinschildhorn.fotopresenter.ui.state.UiState +import com.kevinschildhorn.fotopresenter.domain.connection.AutoConnectUseCase +import com.kevinschildhorn.fotopresenter.domain.connection.ConnectToServerUseCase +import com.kevinschildhorn.fotopresenter.domain.connection.SaveCredentialsUseCase +import com.kevinschildhorn.fotopresenter.ui.UiState +import com.kevinschildhorn.fotopresenter.ui.shared.ViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -88,7 +88,7 @@ class LoginViewModel( } } - fun setLoggedOut() { + fun setLoggedOut() { _uiState.update { it.copy(state = UiState.IDLE) } } diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/login/LoginCheckbox.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/login/composables/LoginCheckbox.kt similarity index 92% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/login/LoginCheckbox.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/login/composables/LoginCheckbox.kt index 6ee1b297..bb008232 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/login/LoginCheckbox.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/login/composables/LoginCheckbox.kt @@ -1,4 +1,4 @@ -package com.kevinschildhorn.fotopresenter.ui.compose.login +package com.kevinschildhorn.fotopresenter.ui.screens.login.composables import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/login/LoginPasswordTextField.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/login/composables/LoginPasswordTextField.kt similarity index 94% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/login/LoginPasswordTextField.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/login/composables/LoginPasswordTextField.kt index 6ead248e..18cdedd4 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/login/LoginPasswordTextField.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/login/composables/LoginPasswordTextField.kt @@ -1,4 +1,4 @@ -package com.kevinschildhorn.fotopresenter.ui.compose.login +package com.kevinschildhorn.fotopresenter.ui.screens.login.composables import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.Icon @@ -16,7 +16,7 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation import com.kevinschildhorn.atomik.atomic.atoms.shape import com.kevinschildhorn.atomik.atomic.atoms.textStyle -import com.kevinschildhorn.fotopresenter.ui.atoms.LoginScreenAtoms +import com.kevinschildhorn.fotopresenter.ui.screens.login.LoginScreenAtoms import compose.icons.EvaIcons import compose.icons.evaicons.Fill import compose.icons.evaicons.fill.Eye diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/login/LoginScreenForm.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/login/composables/LoginScreenForm.kt similarity index 86% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/login/LoginScreenForm.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/login/composables/LoginScreenForm.kt index ee2e5dfe..c0a4ce84 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/login/LoginScreenForm.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/login/composables/LoginScreenForm.kt @@ -1,4 +1,4 @@ -package com.kevinschildhorn.fotopresenter.ui.compose.login +package com.kevinschildhorn.fotopresenter.ui.screens.login.composables import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -9,10 +9,10 @@ import androidx.compose.ui.unit.dp import com.kevinschildhorn.fotopresenter.data.State import com.kevinschildhorn.fotopresenter.extension.required import com.kevinschildhorn.fotopresenter.ui.atoms.Padding -import com.kevinschildhorn.fotopresenter.ui.compose.common.ErrorView -import com.kevinschildhorn.fotopresenter.ui.compose.common.FormColumn -import com.kevinschildhorn.fotopresenter.ui.compose.common.PrimaryButton -import com.kevinschildhorn.fotopresenter.ui.state.LoginScreenState +import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.ErrorView +import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.FormColumn +import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.PrimaryButton +import com.kevinschildhorn.fotopresenter.ui.screens.login.LoginScreenState @Composable fun LoginScreenForm( diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/login/LoginTextField.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/login/composables/LoginTextField.kt similarity index 86% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/login/LoginTextField.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/login/composables/LoginTextField.kt index c61b939d..2f45f716 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/login/LoginTextField.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/login/composables/LoginTextField.kt @@ -1,4 +1,4 @@ -package com.kevinschildhorn.fotopresenter.ui.compose.login +package com.kevinschildhorn.fotopresenter.ui.screens.login.composables import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text @@ -6,7 +6,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.kevinschildhorn.atomik.atomic.atoms.shape import com.kevinschildhorn.atomik.atomic.atoms.textStyle -import com.kevinschildhorn.fotopresenter.ui.atoms.LoginScreenAtoms +import com.kevinschildhorn.fotopresenter.ui.screens.login.LoginScreenAtoms @Composable fun LoginTextField( diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/slideshow/SlideshowScreen.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/slideshow/SlideshowScreen.kt new file mode 100644 index 00000000..221f50af --- /dev/null +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/slideshow/SlideshowScreen.kt @@ -0,0 +1,44 @@ +package com.kevinschildhorn.fotopresenter.ui.screens.slideshow + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material.Button +import androidx.compose.material.Icon +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import compose.icons.EvaIcons +import compose.icons.evaicons.Fill +import compose.icons.evaicons.fill.Close + +@Composable +fun SlideshowScreen( + viewModel: SlideshowViewModel, + onDismiss: () -> Unit, +) { + val uiState by viewModel.uiState.collectAsState() + Column(modifier = Modifier.fillMaxSize()) { + Row( + modifier = Modifier.fillMaxWidth().height(44.dp), + horizontalArrangement = Arrangement.End, + ) { + Button(onClick = onDismiss) { + Icon(EvaIcons.Fill.Close, contentDescription = null) + } + } + uiState.imageState.selectedImage?.let { bitmap -> + Image( + bitmap = bitmap, + contentDescription = null, + modifier = Modifier.fillMaxSize(), + ) + } + } +} diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/slideshow/SlideshowScreenState.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/slideshow/SlideshowScreenState.kt new file mode 100644 index 00000000..df42ae56 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/slideshow/SlideshowScreenState.kt @@ -0,0 +1,9 @@ +package com.kevinschildhorn.fotopresenter.ui.screens.slideshow + +import com.kevinschildhorn.fotopresenter.data.ImageSlideshowDetails +import com.kevinschildhorn.fotopresenter.ui.screens.common.ImageScreenState + +data class SlideshowScreenState( + val imageState: ImageScreenState = ImageScreenState(), + val slideshowDetails: ImageSlideshowDetails = ImageSlideshowDetails(), +) diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/slideshow/SlideshowViewModel.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/slideshow/SlideshowViewModel.kt new file mode 100644 index 00000000..78f3514c --- /dev/null +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/slideshow/SlideshowViewModel.kt @@ -0,0 +1,26 @@ +package com.kevinschildhorn.fotopresenter.ui.screens.slideshow + +import co.touchlab.kermit.Logger +import com.kevinschildhorn.fotopresenter.data.ImageSlideshowDetails +import com.kevinschildhorn.fotopresenter.ui.screens.common.DefaultImageViewModel +import com.kevinschildhorn.fotopresenter.ui.screens.common.ImageViewModel +import com.kevinschildhorn.fotopresenter.ui.shared.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import org.koin.core.component.KoinComponent + +class SlideshowViewModel( + private val logger: Logger, +) : ViewModel(), + ImageViewModel by DefaultImageViewModel(), + KoinComponent { + private val _uiState = MutableStateFlow(SlideshowScreenState()) + val uiState: StateFlow = _uiState.asStateFlow() + + fun setSlideshow(details: ImageSlideshowDetails) { + _uiState.update { it.copy(slideshowDetails = details) } + setImageDirectories(details.directories) + } +} diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/shared/SharedCache.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/shared/SharedCache.kt index c1c17df3..697f19e8 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/shared/SharedCache.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/shared/SharedCache.kt @@ -3,15 +3,37 @@ package com.kevinschildhorn.fotopresenter.ui.shared import androidx.compose.ui.graphics.ImageBitmap import io.github.reactivecircus.cache4k.Cache -object SharedCache { +interface CacheInterface { + fun getImage(id: String): ImageBitmap? + + fun cacheImage( + id: String, + imageBitmap: ImageBitmap, + ) +} + +object SharedCache : CacheInterface { private val imageCache = Cache.Builder().build() - fun getImage(id: String): ImageBitmap? = imageCache.get(id) + override fun getImage(id: String): ImageBitmap? = imageCache.get(id) - fun cacheImage( + override fun cacheImage( id: String, imageBitmap: ImageBitmap, ) { imageCache.put(id, imageBitmap) } } + +class MockSharedCache : CacheInterface { + val contents = mutableMapOf() + + override fun cacheImage( + id: String, + imageBitmap: ImageBitmap, + ) { + contents[id] = imageBitmap + } + + override fun getImage(id: String): ImageBitmap? = contents[id] +} diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/ViewModel.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/shared/ViewModel.kt similarity index 73% rename from shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/ViewModel.kt rename to shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/shared/ViewModel.kt index b7c4eec5..7faf97f7 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/ViewModel.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/shared/ViewModel.kt @@ -1,4 +1,4 @@ -package com.kevinschildhorn.fotopresenter.ui.viewmodel +package com.kevinschildhorn.fotopresenter.ui.shared import kotlinx.coroutines.CoroutineScope diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/state/ScreenState.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/state/ScreenState.kt deleted file mode 100644 index b6a1e458..00000000 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/state/ScreenState.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.kevinschildhorn.fotopresenter.ui.state - -interface ScreenState { - val state: UiState -} diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/PhotoDirectoryViewModel.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/PhotoDirectoryViewModel.kt deleted file mode 100644 index 6cfcca84..00000000 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/PhotoDirectoryViewModel.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.kevinschildhorn.fotopresenter.ui.viewmodel - -import androidx.compose.ui.graphics.ImageBitmap -import com.kevinschildhorn.fotopresenter.data.State -import com.kevinschildhorn.fotopresenter.ui.shared.SharedImage -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch - -class PhotoDirectoryViewModel(private val image: SharedImage?) : ViewModel() { - private val _imageState: MutableStateFlow> = MutableStateFlow(State.IDLE) - val imageState: StateFlow> = _imageState.asStateFlow() - - fun refreshImageBitmap(size: Int) { - var state: State = State.LOADING - _imageState.update { state } - viewModelScope.launch(Dispatchers.Default) { - image?.getImageBitmap(size)?.let { - state = State.SUCCESS(it) - _imageState.update { state } - } ?: run { - state = State.ERROR("No Image Found") - _imageState.update { state } - } - } - } -} diff --git a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/MainCoroutineRule.kt b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/MainCoroutineRule.kt deleted file mode 100644 index 80bfea24..00000000 --- a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/MainCoroutineRule.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.kevinschildhorn.fotopresenter - -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestDispatcher -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain -import org.junit.rules.TestWatcher -import org.junit.runner.Description - -@ExperimentalCoroutinesApi -class MainCoroutineRule(private val dispatcher: TestDispatcher = StandardTestDispatcher()) : - TestWatcher() { - override fun starting(description: Description?) { - super.starting(description) - Dispatchers.setMain(dispatcher) - } - - override fun finished(description: Description?) { - super.finished(description) - Dispatchers.resetMain() - } -} diff --git a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/datasources/DirectoryDataSourceTest.kt b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/datasources/DirectoryDataSourceTest.kt index 8bd00025..3d7ab206 100644 --- a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/datasources/DirectoryDataSourceTest.kt +++ b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/datasources/DirectoryDataSourceTest.kt @@ -77,14 +77,14 @@ class DirectoryDataSourceTest { fun `get Directory Contents Mixed`() = runBlocking { val directories = dataSource.getFolderDirectories("") - assertEquals(1, directories.count()) + assertEquals(2, directories.count()) } @Test fun `get Directory Contents Empty`() = runBlocking { val directories = dataSource.getFolderDirectories("Photos") - assertEquals(0, directories.count()) + assertEquals(1, directories.count()) } //endregion diff --git a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/datasources/ImageCacheDataSourceTest.kt b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/datasources/ImageCacheDataSourceTest.kt new file mode 100644 index 00000000..57ef6dc3 --- /dev/null +++ b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/datasources/ImageCacheDataSourceTest.kt @@ -0,0 +1,26 @@ +package com.kevinschildhorn.fotopresenter.data.datasources + +/* TODO: Requires mocking +/** +Testing [ImageCacheDataSource] + **/ +class ImageCacheDataSourceTest { + private val cache = MockSharedCache() + private val dataSource = ImageCacheDataSource(cache) + + @Test + fun `save Image`() = + runBlocking { + val directoryDetails = MockNetworkDirectoryDetails( + fullPath = "Photos/sample.png", + id = 1, + ) + val bitmap = mockk() + assertTrue(cache.contents.isEmpty()) + dataSource.saveImage(directoryDetails, bitmap) + assertFalse(cache.contents.isEmpty()) + + val result = dataSource.getImage(directoryDetails) + assertEquals(bitmap, result) + } +}*/ diff --git a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/datasources/ImageRemoteDataSourceTest.kt b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/datasources/ImageRemoteDataSourceTest.kt index 8d738651..052b84d1 100644 --- a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/datasources/ImageRemoteDataSourceTest.kt +++ b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/datasources/ImageRemoteDataSourceTest.kt @@ -36,7 +36,7 @@ class ImageRemoteDataSourceTest { runBlocking { val networkDirectory = MockNetworkDirectoryDetails( - fullPath = "Photos/Peeng.png", + fullPath = "Photos/Success.png", id = 1, ) try { diff --git a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/network/MockNetworkHandler.kt b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/network/MockNetworkHandler.kt index 6c59dea2..4088a73c 100644 --- a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/network/MockNetworkHandler.kt +++ b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/network/MockNetworkHandler.kt @@ -13,12 +13,15 @@ object MockNetworkHandler : NetworkHandler { shouldAutoConnect = false, ) + val photoDirectoryId = 5 + private val networkContents = mapOf( "" to listOf( + MockNetworkDirectoryDetails(fullPath = "Photos", id = photoDirectoryId), MockNetworkDirectoryDetails(fullPath = "NewDirectory", id = 1), - MockNetworkDirectoryDetails(fullPath = "Peeng.png", id = 2), + MockNetworkDirectoryDetails(fullPath = "Peeng.png", id = 75), MockNetworkDirectoryDetails(fullPath = "Jaypeg.jpg", id = 3), MockNetworkDirectoryDetails(fullPath = "textFile.txt", id = 4), ), @@ -29,13 +32,29 @@ object MockNetworkHandler : NetworkHandler { ), "Photos" to listOf( - MockNetworkDirectoryDetails(fullPath = "Photos/Peeng.png", id = 2), - MockNetworkDirectoryDetails(fullPath = "Photos/Jaypeg.jpg", id = 3), - MockNetworkDirectoryDetails(fullPath = "Photos/textFile.txt", id = 4), + MockNetworkDirectoryDetails(fullPath = "Photos/Peeng2.png", id = 2), + MockNetworkDirectoryDetails(fullPath = "Photos/Jaypeg2.jpg", id = 3), + MockNetworkDirectoryDetails(fullPath = "Photos/textFile2.txt", id = 4), + MockNetworkDirectoryDetails(fullPath = "Photos/SubPhotos", id = 5), + ), + "Photos/SubPhotos" to + listOf( + MockNetworkDirectoryDetails( + fullPath = "Photos/SubPhotos/Peeng3.png", + id = 2, + ), + MockNetworkDirectoryDetails( + fullPath = "Photos/SubPhotos/Jaypeg3.jpg", + id = 3, + ), + MockNetworkDirectoryDetails( + fullPath = "Photos/SubPhotos/textFile3.txt", + id = 4, + ), ), ) - private val successImageName: String = "Photos/Peeng.png" + private val successImageName: String = "Photos/Success.png" private var connected: Boolean = false override val isConnected: Boolean @@ -61,10 +80,13 @@ object MockNetworkHandler : NetworkHandler { } override suspend fun getDirectoryContents(path: String): List { + print("Getting Directory Contents ${path}\n") + return networkContents[path] ?: emptyList() } override suspend fun openDirectory(directoryName: String): String? { + print("Opening Directory ${directoryName}\n") if (networkContents.containsKey(directoryName)) { return directoryName } @@ -72,6 +94,7 @@ object MockNetworkHandler : NetworkHandler { } override suspend fun openImage(imageName: String): SharedImage? { + print("Opening Image ${imageName}\n") if (imageName == successImageName) { throw Exception("Success") // TODO: This is messy, but SharedImageIs expect } diff --git a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/repositories/CredentialsRepositoryTest.kt b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/repositories/CredentialsRepositoryTest.kt index 92a2f525..b54cddad 100644 --- a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/repositories/CredentialsRepositoryTest.kt +++ b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/repositories/CredentialsRepositoryTest.kt @@ -51,4 +51,22 @@ class CredentialsRepositoryTest : KoinTest { assertEquals(password, credentials.password) assertEquals(shouldAutoConnect, credentials.shouldAutoConnect) } + + @Test + fun `clear auto-connect`() { + repository.saveCredentials( + hostname = "google.com", + username = "secret", + password = "password", + sharedFolder = "Public", + shouldAutoConnect = true, + ) + var credentials = repository.fetchCredentials() + assertEquals(true, credentials.shouldAutoConnect) + + repository.clearAutoConnect() + credentials = repository.fetchCredentials() + + assertEquals(false, credentials.shouldAutoConnect) + } } diff --git a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/repositories/DirectoryRepositoryTest.kt b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/repositories/DirectoryRepositoryTest.kt index 02cd3c4b..d96940fb 100644 --- a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/repositories/DirectoryRepositoryTest.kt +++ b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/repositories/DirectoryRepositoryTest.kt @@ -42,7 +42,7 @@ class DirectoryRepositoryTest : KoinTest { fun `retrieve Directory Contents Success`() = runBlocking { val result = repository.getDirectoryContents("") - assertEquals(1, result.folders.count()) + assertEquals(2, result.folders.count()) assertEquals(2, result.images.count()) } diff --git a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/repositories/ImageRepositoryTest.kt b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/repositories/ImageRepositoryTest.kt index 02859e9d..c44250fd 100644 --- a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/repositories/ImageRepositoryTest.kt +++ b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/repositories/ImageRepositoryTest.kt @@ -47,7 +47,7 @@ class ImageRepositoryTest : KoinTest { val result = repository.getImage( MockNetworkDirectoryDetails( - fullPath = "Photos/Peeng.png", + fullPath = "Photos/Success.png", id = 1, ), ) diff --git a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/AutoConnectUseCaseTest.kt b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/AutoConnectUseCaseTest.kt index e13e7367..4899dadf 100644 --- a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/AutoConnectUseCaseTest.kt +++ b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/AutoConnectUseCaseTest.kt @@ -1,5 +1,6 @@ package com.kevinschildhorn.fotopresenter.domain +import com.kevinschildhorn.fotopresenter.domain.connection.AutoConnectUseCase import com.kevinschildhorn.fotopresenter.testingModule import com.russhwolf.settings.MapSettings import kotlinx.coroutines.runBlocking diff --git a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/ChangeDirectoryUseCaseTest.kt b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/ChangeDirectoryUseCaseTest.kt index 39f4358e..9e6bf1ea 100644 --- a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/ChangeDirectoryUseCaseTest.kt +++ b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/ChangeDirectoryUseCaseTest.kt @@ -3,6 +3,7 @@ package com.kevinschildhorn.fotopresenter.domain import com.kevinschildhorn.fotopresenter.data.network.MockNetworkHandler import com.kevinschildhorn.fotopresenter.data.network.NetworkHandlerError import com.kevinschildhorn.fotopresenter.data.network.NetworkHandlerException +import com.kevinschildhorn.fotopresenter.domain.directory.ChangeDirectoryUseCase import com.kevinschildhorn.fotopresenter.testingModule import kotlinx.coroutines.runBlocking import org.junit.Test diff --git a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/ConnectToServerUseCaseTest.kt b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/ConnectToServerUseCaseTest.kt index 0cd99e6b..967441af 100644 --- a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/ConnectToServerUseCaseTest.kt +++ b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/ConnectToServerUseCaseTest.kt @@ -1,6 +1,7 @@ package com.kevinschildhorn.fotopresenter.domain import com.kevinschildhorn.fotopresenter.data.LoginCredentials +import com.kevinschildhorn.fotopresenter.domain.connection.ConnectToServerUseCase import com.kevinschildhorn.fotopresenter.testingModule import kotlinx.coroutines.runBlocking import org.koin.core.context.startKoin diff --git a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/LogoutUseCaseTest.kt b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/DisconnectFromServerUseCaseTest.kt similarity index 82% rename from shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/LogoutUseCaseTest.kt rename to shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/DisconnectFromServerUseCaseTest.kt index 6320ea3c..f4702255 100644 --- a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/LogoutUseCaseTest.kt +++ b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/DisconnectFromServerUseCaseTest.kt @@ -1,6 +1,7 @@ package com.kevinschildhorn.fotopresenter.domain import com.kevinschildhorn.fotopresenter.data.network.MockNetworkHandler +import com.kevinschildhorn.fotopresenter.domain.connection.DisconnectFromServerUseCase import com.kevinschildhorn.fotopresenter.testingModule import kotlinx.coroutines.runBlocking import org.junit.Test @@ -13,10 +14,10 @@ import kotlin.test.BeforeTest import kotlin.test.assertFalse /** -Testing [LogoutUseCase] +Testing [DisconnectFromServerUseCase] **/ -class LogoutUseCaseTest : KoinTest { - private val useCase: LogoutUseCase by inject() +class DisconnectFromServerUseCaseTest : KoinTest { + private val useCase: DisconnectFromServerUseCase by inject() @BeforeTest fun startTest() { @@ -45,5 +46,4 @@ class LogoutUseCaseTest : KoinTest { useCase() assertFalse(MockNetworkHandler.isConnected, "Failed to Disconnect") } - -} \ No newline at end of file +} diff --git a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/RetrieveDirectoryContentsUseCaseTest.kt b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/RetrieveDirectoryContentsUseCaseTest.kt index 5b0f40aa..0613c0d7 100644 --- a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/RetrieveDirectoryContentsUseCaseTest.kt +++ b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/RetrieveDirectoryContentsUseCaseTest.kt @@ -44,9 +44,9 @@ class RetrieveDirectoryContentsUseCaseTest : KoinTest { val result = useCase("") assertTrue(result.images.first().details.isAnImage) assertTrue(result.folders.first().details.isDirectory) - assertEquals(1, result.folders.count()) + assertEquals(2, result.folders.count()) assertEquals(2, result.images.count()) - assertEquals(3, result.allDirectories.count()) + assertEquals(4, result.allDirectories.count()) } @Test diff --git a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/RetrieveImageDirectoriesUseCaseTest.kt b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/RetrieveImageDirectoriesUseCaseTest.kt new file mode 100644 index 00000000..e41125d9 --- /dev/null +++ b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/RetrieveImageDirectoriesUseCaseTest.kt @@ -0,0 +1,78 @@ +package com.kevinschildhorn.fotopresenter.domain + +import com.kevinschildhorn.fotopresenter.data.network.MockNetworkDirectoryDetails +import com.kevinschildhorn.fotopresenter.data.network.MockNetworkHandler +import com.kevinschildhorn.fotopresenter.data.network.NetworkHandlerError +import com.kevinschildhorn.fotopresenter.data.network.NetworkHandlerException +import com.kevinschildhorn.fotopresenter.domain.image.RetrieveImageDirectoriesUseCase +import com.kevinschildhorn.fotopresenter.testingModule +import kotlinx.coroutines.runBlocking +import org.junit.Test +import org.koin.core.context.startKoin +import org.koin.core.context.stopKoin +import org.koin.test.KoinTest +import org.koin.test.inject +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.assertEquals +import kotlin.test.fail + +/** +Testing [RetrieveImageDirectoriesUseCase] + **/ +class RetrieveImageDirectoriesUseCaseTest : KoinTest { + private val useCase: RetrieveImageDirectoriesUseCase by inject() + + @BeforeTest + fun startTest() = + runBlocking { + startKoin { + modules(testingModule()) + } + MockNetworkHandler.connectSuccessfully() + } + + @AfterTest + fun tearDown() = + runBlocking { + stopKoin() + MockNetworkHandler.disconnect() + } + + @Test + fun `receive directory content success`() = + runBlocking { + val details = MockNetworkDirectoryDetails("", 1) + val result = useCase(details) + assertEquals(6, result.count()) + } + + @Test + fun `receive directory content only directories`() = + runBlocking { + val details = MockNetworkDirectoryDetails("Directories", 1) + val result = useCase(details) + assertEquals(0, result.count()) + } + + @Test + fun `receive directory content failure`() = + runBlocking { + val details = MockNetworkDirectoryDetails("nonExistant", 1) + val result = useCase(details) + assertEquals(0, result.count()) + } + + @Test + fun `receive directory content disconnected`() = + runBlocking { + MockNetworkHandler.disconnect() + try { + val details = MockNetworkDirectoryDetails("Photos", 1) + val result = useCase(details) + fail("Should've thrown") + } catch (e: NetworkHandlerException) { + assertEquals(NetworkHandlerError.NOT_CONNECTED.message, e.message) + } + } +} diff --git a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/SaveCredentialsUseCaseTest.kt b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/SaveCredentialsUseCaseTest.kt index 2a986307..396f6628 100644 --- a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/SaveCredentialsUseCaseTest.kt +++ b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/SaveCredentialsUseCaseTest.kt @@ -3,6 +3,7 @@ package com.kevinschildhorn.fotopresenter.domain import co.touchlab.kermit.Logger import co.touchlab.kermit.LoggerConfig import com.kevinschildhorn.fotopresenter.data.LoginCredentials +import com.kevinschildhorn.fotopresenter.domain.connection.SaveCredentialsUseCase import com.kevinschildhorn.fotopresenter.testingModule import org.koin.core.context.startKoin import org.koin.core.context.stopKoin diff --git a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/DirectoryViewModelTest.kt b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/DirectoryViewModelTest.kt index 5999c779..6ca7e484 100644 --- a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/DirectoryViewModelTest.kt +++ b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/DirectoryViewModelTest.kt @@ -1,56 +1,172 @@ package com.kevinschildhorn.fotopresenter.ui.viewmodel -import com.kevinschildhorn.fotopresenter.MainCoroutineRule +import app.cash.turbine.test import com.kevinschildhorn.fotopresenter.data.network.MockNetworkHandler import com.kevinschildhorn.fotopresenter.testingModule +import com.kevinschildhorn.fotopresenter.ui.UiState +import com.kevinschildhorn.fotopresenter.ui.screens.directory.DirectoryViewModel +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest -import org.junit.Rule +import kotlinx.coroutines.test.setMain import org.junit.Test import org.koin.core.context.startKoin import org.koin.core.context.stopKoin import org.koin.test.KoinTest import org.koin.test.inject import kotlin.test.AfterTest +import kotlin.test.BeforeTest import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertTrue /** Testing [DirectoryViewModel] **/ @OptIn(ExperimentalCoroutinesApi::class) class DirectoryViewModelTest : KoinTest { - @ExperimentalCoroutinesApi - @get:Rule - var mainCoroutineRule = MainCoroutineRule() + private val testDispatcher = StandardTestDispatcher() - private val viewModel: DirectoryViewModel by inject() + @BeforeTest + fun startTest() = + runBlocking { + startKoin { + modules(testingModule()) + } + Dispatchers.setMain(testDispatcher) + MockNetworkHandler.connectSuccessfully() + } @AfterTest - fun tearDown() { - stopKoin() - } -/* + fun tearDown() = + runBlocking { + MockNetworkHandler.disconnect() + stopKoin() + Dispatchers.resetMain() + } + @Test - fun `UI State`() = - runTest { - startKoin { - modules(testingModule()) + fun `Refresh Screen`() = + runTest(testDispatcher) { + val viewModel: DirectoryViewModel by inject() + assertTrue(viewModel.uiState.value.loggedIn) + + viewModel.uiState.test { + viewModel.refreshScreen() + + var state = awaitItem() + assertEquals(UiState.IDLE, state.state) + + state = awaitItem() + assertEquals(UiState.LOADING, state.state) + + state = awaitItem() + assertEquals(UiState.SUCCESS, state.state) + assertEquals("", state.currentPath) + assertTrue(state.loggedIn) + assertEquals(2, state.directoryGridState.imageStates.count()) + assertEquals(2, state.directoryGridState.folderStates.count()) + cancelAndIgnoreRemainingEvents() } + } - MockNetworkHandler.connectSuccessfully() - with(viewModel.uiState.value) { - assertEquals("", currentPath) - assertEquals(0, directoryContents.allDirectories.count()) + @Test + fun logout() = + runTest(testDispatcher) { + val viewModel: DirectoryViewModel by inject() + assertTrue(viewModel.uiState.value.loggedIn) + viewModel.uiState.test { + var state = awaitItem() + viewModel.logout() + state = awaitItem() + assertFalse(state.loggedIn) + cancelAndIgnoreRemainingEvents() } + } - viewModel.refreshScreen() - advanceUntilIdle() + @Test + fun `change Directory`() = + runTest(testDispatcher) { + val viewModel: DirectoryViewModel by inject() + viewModel.uiState.test { + // Refreshing Screen + var state = awaitItem() + viewModel.refreshScreen() + state = awaitItem() + assertEquals(UiState.LOADING, state.state) + state = awaitItem() + assertEquals(UiState.SUCCESS, state.state) + val firstId = state.directoryGridState.folderStates.first().id + assertEquals(MockNetworkHandler.photoDirectoryId, firstId) - with(viewModel.uiState.value) { - // assertEquals("", currentPath) - // assertEquals(2, directoryContents.allDirectories.count()) + // Changing Directory + viewModel.changeDirectory(firstId) + state = awaitItem() + assertEquals(UiState.SUCCESS, state.state) + while (state.currentPath.isEmpty()) { + state = awaitItem() + } + assertEquals("Photos", state.currentPath) + while (state.directoryGridState.folderStates.count() != 1) { + state = awaitItem() + } + assertEquals(2, state.directoryGridState.imageStates.count()) + assertEquals(1, state.directoryGridState.folderStates.count()) + cancelAndIgnoreRemainingEvents() } - MockNetworkHandler.disconnect() - }*/ + } + + @Test + fun `start Slideshow`() = + runTest(testDispatcher) { + val viewModel: DirectoryViewModel by inject() + viewModel.uiState.test { + viewModel.refreshScreen() + var state = awaitItem() + assertEquals(UiState.IDLE, state.state) + state = awaitItem() + assertEquals(UiState.LOADING, state.state) + state = awaitItem() + assertEquals(UiState.SUCCESS, state.state) + + viewModel.startSlideshow(MockNetworkHandler.photoDirectoryId) + while (state.slideshowDetails == null) { + state = awaitItem() + } + assertNotNull(state.slideshowDetails) + while (state.slideshowDetails?.directories!!.count() != 4) { + state = awaitItem() + } + val list = state.slideshowDetails?.directories!! + assertEquals(4, list.size) + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `select Image by ID`() = + runTest(testDispatcher) { + val viewModel: DirectoryViewModel by inject() + viewModel.uiState.test { + viewModel.refreshScreen() + var state = awaitItem() + assertEquals(UiState.IDLE, state.state) + state = awaitItem() + assertEquals(UiState.LOADING, state.state) + state = awaitItem() + assertEquals(UiState.SUCCESS, state.state) + } + viewModel.imageUiState.test { + viewModel.setSelectedImageById(75) + var state = awaitItem() + while (state.selectedImageIndex == null) { + state = awaitItem() + } + assertEquals(0, state.selectedImageIndex) + } + } } diff --git a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/ImageViewModelTest.kt b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/ImageViewModelTest.kt new file mode 100644 index 00000000..5312573a --- /dev/null +++ b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/ImageViewModelTest.kt @@ -0,0 +1,139 @@ +package com.kevinschildhorn.fotopresenter.ui.viewmodel + +import app.cash.turbine.test +import com.kevinschildhorn.fotopresenter.data.ImageDirectory +import com.kevinschildhorn.fotopresenter.data.network.MockNetworkDirectoryDetails +import com.kevinschildhorn.fotopresenter.data.network.MockNetworkHandler +import com.kevinschildhorn.fotopresenter.testingModule +import com.kevinschildhorn.fotopresenter.ui.screens.common.DefaultImageViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.junit.Test +import org.koin.core.context.startKoin +import org.koin.core.context.stopKoin +import org.koin.test.KoinTest +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.assertEquals +import kotlin.test.assertNull + +/** +Testing [ImageViewModel] + **/ +@OptIn(ExperimentalCoroutinesApi::class) +class ImageViewModelTest : KoinTest { + private val testDispatcher = StandardTestDispatcher() + + private val directories = + listOf( + ImageDirectory(MockNetworkDirectoryDetails("Peeng.png", 1)), + ImageDirectory(MockNetworkDirectoryDetails("Jaypeg.jpg", 2)), + ImageDirectory(MockNetworkDirectoryDetails("Photos/Peeng2.png", 3)), + ImageDirectory(MockNetworkDirectoryDetails("Photos/Jaypeg2.jpg", 4)), + ImageDirectory(MockNetworkDirectoryDetails("Photos/SubPhotos/Peeng3.png", 5)), + ImageDirectory(MockNetworkDirectoryDetails("Photos/SubPhotos/Jaypeg3.jpg", 6)), + ) + + @BeforeTest + fun startTest() = + runBlocking { + startKoin { + modules(testingModule()) + } + Dispatchers.setMain(testDispatcher) + MockNetworkHandler.connectSuccessfully() + } + + @AfterTest + fun tearDown() = + runBlocking { + MockNetworkHandler.disconnect() + stopKoin() + Dispatchers.resetMain() + } + + @Test + fun `Set Selected Image`() = + runTest(testDispatcher) { + val viewModel = DefaultImageViewModel() + viewModel.imageUiState.test { + viewModel.setImageDirectories(directories) + var state = awaitItem() + while (state.imageDirectories.isEmpty()) { + state = awaitItem() + } + assertEquals(directories.count(), state.imageDirectories.count()) + } + } + + @Test + fun `Show Previous Image`() = + runTest(testDispatcher) { + val viewModel = DefaultImageViewModel() + viewModel.imageUiState.test { + viewModel.setImageDirectories(directories) + var state = awaitItem() + while (state.imageDirectories.isEmpty()) { + state = awaitItem() + } + assertEquals(directories.count(), state.imageDirectories.count()) + viewModel.setSelectedImage(1) + state = awaitItem() + assertEquals(1, state.selectedImageIndex) + viewModel.showPreviousImage() + state = awaitItem() + assertEquals(0, state.selectedImageIndex) + viewModel.showPreviousImage() + state = awaitItem() + assertEquals(5, state.selectedImageIndex) + } + } + + @Test + fun `Show Next Image`() = + runTest(testDispatcher) { + val viewModel = DefaultImageViewModel() + viewModel.imageUiState.test { + viewModel.setImageDirectories(directories) + var state = awaitItem() + while (state.imageDirectories.isEmpty()) { + state = awaitItem() + } + assertEquals(directories.count(), state.imageDirectories.count()) + viewModel.setSelectedImage(4) + state = awaitItem() + assertEquals(4, state.selectedImageIndex) + viewModel.showNextImage() + state = awaitItem() + assertEquals(5, state.selectedImageIndex) + viewModel.showNextImage() + state = awaitItem() + assertEquals(0, state.selectedImageIndex) + } + } + + @Test + fun `Clear Presented Image`() = + runTest(testDispatcher) { + val viewModel = DefaultImageViewModel() + viewModel.imageUiState.test { + viewModel.setImageDirectories(directories) + var state = awaitItem() + while (state.imageDirectories.isEmpty()) { + state = awaitItem() + } + assertEquals(directories.count(), state.imageDirectories.count()) + viewModel.setSelectedImage(4) + state = awaitItem() + assertEquals(4, state.selectedImageIndex) + viewModel.clearPresentedImage() + state = awaitItem() + assertNull(state.selectedImageIndex) + } + } +} diff --git a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/LoginViewModelTest.kt b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/LoginViewModelTest.kt index a9b69d02..931b3bf9 100644 --- a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/LoginViewModelTest.kt +++ b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/LoginViewModelTest.kt @@ -1,13 +1,12 @@ package com.kevinschildhorn.fotopresenter.ui.viewmodel -import com.kevinschildhorn.fotopresenter.MainCoroutineRule import com.kevinschildhorn.fotopresenter.testingModule -import com.kevinschildhorn.fotopresenter.ui.state.UiState +import com.kevinschildhorn.fotopresenter.ui.UiState +import com.kevinschildhorn.fotopresenter.ui.screens.login.LoginViewModel import com.russhwolf.settings.MapSettings import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest -import org.junit.Rule import org.koin.core.context.startKoin import org.koin.core.context.stopKoin import org.koin.test.KoinTest @@ -23,10 +22,6 @@ Testing [LoginViewModel] **/ @OptIn(ExperimentalCoroutinesApi::class) class LoginViewModelTest : KoinTest { - @ExperimentalCoroutinesApi - @get:Rule - var mainCoroutineRule = MainCoroutineRule() - private val viewModel: LoginViewModel by inject() private val settings = MapSettings( diff --git a/shared/src/desktopMain/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/ViewModel.kt b/shared/src/desktopMain/kotlin/com/kevinschildhorn/fotopresenter/ui/shared/ViewModel.kt similarity index 94% rename from shared/src/desktopMain/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/ViewModel.kt rename to shared/src/desktopMain/kotlin/com/kevinschildhorn/fotopresenter/ui/shared/ViewModel.kt index 9c61fd13..dc6d93d0 100644 --- a/shared/src/desktopMain/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/ViewModel.kt +++ b/shared/src/desktopMain/kotlin/com/kevinschildhorn/fotopresenter/ui/shared/ViewModel.kt @@ -1,4 +1,4 @@ -package com.kevinschildhorn.fotopresenter.ui.viewmodel +package com.kevinschildhorn.fotopresenter.ui.shared import kotlinx.coroutines.MainScope @@ -25,4 +25,4 @@ actual abstract class ViewModel { onCleared() // viewModelScope.cancel() TODO } -} +} \ No newline at end of file