From fbc866fc9f0cc91917d3c51ee2400c788d451ea3 Mon Sep 17 00:00:00 2001 From: Kevin Schildhorn Date: Mon, 18 Dec 2023 11:27:20 -0500 Subject: [PATCH 1/2] Adding Slideshow Details --- .../ui/compose/PlaylistPreviews.kt | 5 ++- .../com/kevinschildhorn/fotopresenter/Koin.kt | 4 +- .../fotopresenter/data/PlaylistDetails.kt | 22 +++++++++++ .../data/datasources/PlaylistDataSource.kt | 33 ++++++++++------- .../data/network/NetworkDirectoryDetails.kt | 2 +- .../data/network/NetworkHandler.kt | 3 ++ .../data/repositories/PlaylistRepository.kt | 5 ++- .../ui/screens/common/ActionSheetContext.kt | 1 + .../ui/screens/common/ImageScreenState.kt | 5 ++- .../ui/screens/common/ImageViewModel.kt | 2 +- .../ui/screens/directory/DirectoryScreen.kt | 37 +++++++++++++++++-- .../screens/directory/DirectoryScreenState.kt | 14 ++++--- .../screens/directory/DirectoryViewModel.kt | 23 ++++++++++-- .../ui/screens/playlist/PlaylistScreen.kt | 12 ++++-- .../screens/playlist/PlaylistScreenState.kt | 3 +- .../ui/screens/playlist/PlaylistViewModel.kt | 27 ++++++++++---- .../playlist/composables/PlaylistColumn.kt | 3 +- .../playlist/composables/PlaylistOverlay.kt | 3 +- .../fotopresenter/ImageDirectory.sq | 5 +++ .../datasources/ImageRemoteDataSourceTest.kt | 8 ++-- .../datasources/PlaylistDataSourceTest.kt | 8 ++-- .../data/network/MockNetworkHandler.kt | 28 +++++++------- .../data/repositories/ImageRepositoryTest.kt | 8 ++-- .../RetrieveImageDirectoriesUseCaseTest.kt | 10 ++--- .../ui/viewmodel/ImageViewModelTest.kt | 14 +++---- .../fotopresenter/data/network/SMBJHandler.kt | 11 ++++++ 26 files changed, 211 insertions(+), 85 deletions(-) create mode 100644 shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/PlaylistDetails.kt diff --git a/shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/PlaylistPreviews.kt b/shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/PlaylistPreviews.kt index 57eb78cc..7f497b4e 100644 --- a/shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/PlaylistPreviews.kt +++ b/shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/PlaylistPreviews.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import com.kevinschildhorn.fotopresenter.Playlist +import com.kevinschildhorn.fotopresenter.data.PlaylistDetails import com.kevinschildhorn.fotopresenter.ui.screens.playlist.composables.PlaylistColumn import com.kevinschildhorn.fotopresenter.ui.screens.playlist.composables.PlaylistOverlay import com.kevinschildhorn.fotopresenter.ui.screens.playlist.composables.PlaylistScreenCreateRow @@ -36,7 +37,7 @@ fun PlaylistRowsPreview() { @Preview fun PlaylistColumnPreview() { PlaylistColumn( - listOf(Playlist(1,"Playlist 1"), Playlist(2, "Playlist 2")), + listOf(PlaylistDetails(1,"Playlist 1"), PlaylistDetails(2, "Playlist 2")), {}, {}, {}, @@ -57,7 +58,7 @@ fun PlaylistOverlayPreview() { Text("Toggle") } PlaylistOverlay( - listOf(Playlist(1,"Playlist 1"), Playlist(2, "Playlist 2")), + listOf(PlaylistDetails(1,"Playlist 1"), PlaylistDetails(2, "Playlist 2")), {}, {}, {}, diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/Koin.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/Koin.kt index 911d7a0d..93f43fa1 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/Koin.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/Koin.kt @@ -41,7 +41,7 @@ val commonModule = single { ImageRemoteDataSource(get()) } single { ImageRepository(get()) } single { ImageCacheDataSource(get()) } - single { PlaylistDataSource(get()) } + single { PlaylistDataSource(get(), baseLogger.withTag("PlaylistDataSource")) } single { PlaylistRepository(get()) } // Domain @@ -62,7 +62,7 @@ val commonModule = // UI single { LoginViewModel(baseLogger.withTag("LoginViewModel"), get()) } - single { DirectoryViewModel(baseLogger.withTag("DirectoryViewModel")) } + single { DirectoryViewModel(get(),baseLogger.withTag("DirectoryViewModel")) } single { SlideshowViewModel(baseLogger.withTag("SlideshowViewModel")) } single { PlaylistViewModel(get(),baseLogger.withTag("PlaylistViewModel")) } } diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/PlaylistDetails.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/PlaylistDetails.kt new file mode 100644 index 00000000..69e9a96e --- /dev/null +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/PlaylistDetails.kt @@ -0,0 +1,22 @@ +package com.kevinschildhorn.fotopresenter.data + +import com.kevinschildhorn.fotopresenter.PlaylistImage +import com.kevinschildhorn.fotopresenter.data.network.DefaultNetworkDirectoryDetails + +data class PlaylistDetails( + val id: Long, + val name: String, + val images: List = emptyList(), +){ + val asImageSlideshowDetails:ImageSlideshowDetails + get() = ImageSlideshowDetails( + directories = images.map { + ImageDirectory( + DefaultNetworkDirectoryDetails( + id = it.directory_id.toInt(), + fullPath = it.directory_path + ) + ) + } + ) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/datasources/PlaylistDataSource.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/datasources/PlaylistDataSource.kt index b27b1023..0e2d82ab 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/datasources/PlaylistDataSource.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/datasources/PlaylistDataSource.kt @@ -1,24 +1,27 @@ package com.kevinschildhorn.fotopresenter.data.datasources import app.cash.sqldelight.db.SqlDriver +import co.touchlab.kermit.Logger import com.kevinschildhorn.fotopresenter.Playlist import com.kevinschildhorn.fotopresenter.PlaylistDatabase import com.kevinschildhorn.fotopresenter.PlaylistImage import com.kevinschildhorn.fotopresenter.data.ImageDirectory +import com.kevinschildhorn.fotopresenter.data.PlaylistDetails import org.koin.core.component.KoinComponent class PlaylistDataSource( driver: SqlDriver, + private val logger: Logger? = null, ) : KoinComponent { private val database = PlaylistDatabase(driver) fun createPlaylist(name: String, directories: List = emptyList()): Playlist? { return try { - //logger?.i { "Creating Playlist $name with images: ${directories.count()}" } + logger?.i { "Creating Playlist $name with images: ${directories.count()}" } database.playlistQueries.insertPlaylist(name) val playlist = database.playlistQueries.selectPlaylistByName(name).executeAsOne() - //logger?.i { "Playlist Created, now adding images" } + logger?.i { "Playlist Created, now adding images" } directories.forEach { insertPlaylistImage(playlistId = playlist.id, directory = it) @@ -29,30 +32,34 @@ class PlaylistDataSource( } } - fun getAllPlaylists(): List { + fun getAllPlaylists(): List { return try { - database.playlistQueries.selectAllPlaylists().executeAsList() + database.playlistQueries.selectAllPlaylists().executeAsList().map { + val images = + database.imageDirectoryQueries.selectPlaylistImages(it.id).executeAsList() + PlaylistDetails(it.id,it.name, images) + } } catch (e: Exception) { emptyList() } - } - fun getPlaylistByName(name: String): Playlist? { + fun getPlaylistByName(name: String): PlaylistDetails? { return try { - //logger?.i { "Selecting playlist by name $name" } + logger?.i { "Selecting playlist by name $name" } val playList: Playlist = database.playlistQueries.selectPlaylistByName(name).executeAsOne() - //logger?.i { "Retrieved Playlist!" } - playList + val images = + database.imageDirectoryQueries.selectPlaylistImages(playList.id).executeAsList() + logger?.i { "Retrieved Playlist!" } + PlaylistDetails(playList.id, playList.name, images) } catch (e: Exception) { null } } fun insertPlaylistImage(playlistId: Long, directory: ImageDirectory): PlaylistImage? { - //logger?.i { "Inserting Playlist Image ${directory.name}" } - + logger?.i { "Inserting Playlist Image ${directory.name}" } database.imageDirectoryQueries.insertPlaylistImage( playlist_id = playlistId, directory_path = directory.details.fullPath, @@ -63,11 +70,11 @@ class PlaylistDataSource( fun getPlaylistImage(playlistId: Long, directoryPath: String): PlaylistImage? { return try { - //logger?.i { "Selecting Playlist Image $playlistId" } + logger?.i { "Selecting Playlist Image $playlistId" } val image: PlaylistImage = database.imageDirectoryQueries.selectPlaylistImage(playlistId, directoryPath) .executeAsOne() - //logger?.i { "Selecting Playlist Image" } + logger?.i { "Selecting Playlist Image" } image } catch (e: Exception) { null diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/network/NetworkDirectoryDetails.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/network/NetworkDirectoryDetails.kt index 45796551..4c93da17 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/network/NetworkDirectoryDetails.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/network/NetworkDirectoryDetails.kt @@ -23,7 +23,7 @@ interface NetworkDirectoryDetails { fileExtension == "bmp" } -class MockNetworkDirectoryDetails( +class DefaultNetworkDirectoryDetails( override val fullPath: String, override val id: Int, ) : NetworkDirectoryDetails diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/network/NetworkHandler.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/network/NetworkHandler.kt index df58dda5..eaaf2095 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/network/NetworkHandler.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/network/NetworkHandler.kt @@ -10,6 +10,8 @@ interface NetworkHandler { suspend fun disconnect() + suspend fun getDirectoryDetails(path: String): NetworkDirectoryDetails? + suspend fun getDirectoryContents(path: String): List suspend fun openDirectory(path: String): String? @@ -25,4 +27,5 @@ class NetworkHandlerException : Exception { enum class NetworkHandlerError(val message: String) { NOT_CONNECTED("The Network Handler is not Connected"), DIRECTORY_NOT_FOUND("The Directory you selected was not found"), + FILE_NOT_FOUND("The File you selected was not found"), } diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/repositories/PlaylistRepository.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/repositories/PlaylistRepository.kt index 63a50b3d..f2d549ea 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/repositories/PlaylistRepository.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/data/repositories/PlaylistRepository.kt @@ -3,6 +3,7 @@ package com.kevinschildhorn.fotopresenter.data.repositories import com.kevinschildhorn.fotopresenter.Playlist import com.kevinschildhorn.fotopresenter.PlaylistImage import com.kevinschildhorn.fotopresenter.data.ImageDirectory +import com.kevinschildhorn.fotopresenter.data.PlaylistDetails import com.kevinschildhorn.fotopresenter.data.datasources.PlaylistDataSource class PlaylistRepository( @@ -12,10 +13,10 @@ class PlaylistRepository( fun createPlaylist(name: String, directories: List = emptyList()): Playlist? = playlistDataSource.createPlaylist(name, directories) - fun getAllPlaylists(): List = + fun getAllPlaylists(): List = playlistDataSource.getAllPlaylists() - fun getPlaylistByName(name: String): Playlist? = + fun getPlaylistByName(name: String): PlaylistDetails? = playlistDataSource.getPlaylistByName(name) fun insertPlaylistImage(playlistId: Long, directory: ImageDirectory): PlaylistImage? = 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 index 3e625d36..5d42ccbf 100644 --- 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 @@ -2,6 +2,7 @@ package com.kevinschildhorn.fotopresenter.ui.screens.common enum class ActionSheetAction(val title: String) { START_SLIDESHOW("Start A Slideshow"), + ADD_STATIC_LOCATION("Add to a Playlist"), NONE("Nothing"), // TEMP } 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 index 2ccf8f4c..1a8fcbf9 100644 --- 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 @@ -7,4 +7,7 @@ data class ImageScreenState( val imageDirectories: List = emptyList(), val selectedImageIndex: Int? = null, val selectedImage: ImageBitmap? = null, -) +){ + val selectedImageDirectory: ImageDirectory? + get() = imageDirectories.getOrNull(index) +} 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 index 5f25322f..613ca77b 100644 --- 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 @@ -84,7 +84,7 @@ class DefaultImageViewModel(private val logger: Logger? = null) : ImageViewModel with(imageUiState.value) { selectedImageIndex?.let { index -> logger?.d { "Selected Image Index found. getting Image Directory" } - this.imageDirectories.getOrNull(index)?.let { + this.selectedImageDirectory?.let { logger?.d { "Image Directory found, showing photo" } showPhoto(it) } ?: run { diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryScreen.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryScreen.kt index 8d744447..c1333653 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryScreen.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryScreen.kt @@ -37,6 +37,9 @@ import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.PrimaryTe import com.kevinschildhorn.fotopresenter.ui.screens.directory.composables.DirectoryGrid import com.kevinschildhorn.fotopresenter.ui.screens.directory.composables.DirectoryNavigationBar import com.kevinschildhorn.fotopresenter.ui.screens.directory.composables.NavigationRailOverlay +import com.kevinschildhorn.fotopresenter.ui.screens.playlist.PlaylistDialog +import com.kevinschildhorn.fotopresenter.ui.screens.playlist.PlaylistScreen +import com.kevinschildhorn.fotopresenter.ui.screens.playlist.composables.PlaylistOverlay import compose.icons.EvaIcons import compose.icons.evaicons.Fill import compose.icons.evaicons.fill.LogOut @@ -47,6 +50,7 @@ enum class DirectoryOverlay { IMAGE, NAV_RAIL, LOGOUT_CONFIRMATION, + PLAYLIST, NONE, } @@ -71,7 +75,7 @@ fun DirectoryScreen( onStartSlideshow(it) } - // UI + //region UI Column { TextButton( modifier = Modifier.size(55.dp), @@ -117,6 +121,11 @@ fun DirectoryScreen( }, ) } + //endregion + + // Overlays + + //region ActionSheet ActionSheet( visible = overlayVisible == DirectoryOverlay.ACTION_SHEET, offset = 200, @@ -125,12 +134,14 @@ fun DirectoryScreen( when (it.action) { ActionSheetAction.START_SLIDESHOW -> { viewModel.startSlideshow(contextMenuPhotoState?.id!!) + overlayVisible = DirectoryOverlay.NONE } - + ActionSheetAction.ADD_STATIC_LOCATION -> + overlayVisible = DirectoryOverlay.PLAYLIST ActionSheetAction.NONE -> { + overlayVisible = DirectoryOverlay.NONE } } - overlayVisible = DirectoryOverlay.NONE contextMenuPhotoState = null }, onDismiss = { @@ -138,6 +149,9 @@ fun DirectoryScreen( contextMenuPhotoState = null }, ) + //endregion + + //region Selected Image imageUiState.selectedImage?.let { ImagePreviewOverlay( it, @@ -154,10 +168,15 @@ fun DirectoryScreen( }, ) } + //endregion + + //region Loading if (uiState.state is UiState.LOADING) { LoadingOverlay() } + //endregion + //region NavigationRail NavigationRailOverlay( visible = overlayVisible == DirectoryOverlay.NAV_RAIL, onDismiss = { @@ -168,6 +187,9 @@ fun DirectoryScreen( }, onPlaylists = onShowPlaylists, ) + //endregion + + //region Logout if (overlayVisible == DirectoryOverlay.LOGOUT_CONFIRMATION) { ConfirmationDialog( "Log Out", @@ -181,4 +203,13 @@ fun DirectoryScreen( }, ) } + //endregion + + //region Playlist + if(overlayVisible == DirectoryOverlay.PLAYLIST) { + PlaylistScreen(viewModel) { + viewModel.addSelectedImageToPlaylist(it) + } + } + //endregion } diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryScreenState.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryScreenState.kt index 24beaf24..635d0d66 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryScreenState.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryScreenState.kt @@ -30,13 +30,14 @@ data class DirectoryScreenState( } return this.copy( directoryGridState = - directoryGridState.copy( - imageStates = list, - ), + directoryGridState.copy( + imageStates = list, + ), ) } - fun getImageIndexFromId(id: Int): Int = directoryGridState.imageStates.indexOfFirst { it.id == id } + fun getImageIndexFromId(id: Int): Int = + directoryGridState.imageStates.indexOfFirst { it.id == id } val currentPathList: List get() = currentPath.split("\\").filter { it.isNotEmpty() } @@ -75,7 +76,10 @@ data class ImageDirectoryGridCellState( override val id: Int, ) : DirectoryGridCellState { override val actionSheetContexts: List - get() = listOf(ActionSheetContext(ActionSheetAction.NONE, 1)) + get() = listOf( + ActionSheetContext(ActionSheetAction.NONE, 1), + ActionSheetContext(ActionSheetAction.ADD_STATIC_LOCATION, 1) + ) override fun toString(): String = "(I:$name:$id)" } diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryViewModel.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryViewModel.kt index 665f850a..252604d0 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryViewModel.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryViewModel.kt @@ -1,11 +1,14 @@ package com.kevinschildhorn.fotopresenter.ui.screens.directory import co.touchlab.kermit.Logger +import com.kevinschildhorn.fotopresenter.Playlist import com.kevinschildhorn.fotopresenter.data.DirectoryContents import com.kevinschildhorn.fotopresenter.data.ImageSlideshowDetails +import com.kevinschildhorn.fotopresenter.data.PlaylistDetails import com.kevinschildhorn.fotopresenter.data.State import com.kevinschildhorn.fotopresenter.data.network.NetworkDirectoryDetails import com.kevinschildhorn.fotopresenter.data.network.NetworkHandlerException +import com.kevinschildhorn.fotopresenter.data.repositories.PlaylistRepository import com.kevinschildhorn.fotopresenter.domain.RetrieveDirectoryContentsUseCase import com.kevinschildhorn.fotopresenter.domain.connection.DisconnectFromServerUseCase import com.kevinschildhorn.fotopresenter.domain.directory.ChangeDirectoryUseCase @@ -16,6 +19,7 @@ import com.kevinschildhorn.fotopresenter.extension.navigateBackToPathAtIndex 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.screens.playlist.PlaylistViewModel import com.kevinschildhorn.fotopresenter.ui.shared.ViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow @@ -27,10 +31,12 @@ import org.koin.core.component.KoinComponent import org.koin.core.component.inject class DirectoryViewModel( + private val playlistRepository: PlaylistRepository, private val logger: Logger, -) : ViewModel(), +) : PlaylistViewModel(playlistRepository, logger), ImageViewModel by DefaultImageViewModel(logger), KoinComponent { + private val _uiState = MutableStateFlow(DirectoryScreenState()) val uiState: StateFlow = _uiState.asStateFlow() @@ -106,8 +112,8 @@ class DirectoryViewModel( changeDirectoryToPath(currentPath.addPath(it.details.name)) } } - - private fun changeDirectoryToPath(path:String){ + + private fun changeDirectoryToPath(path: String) { logger.i { "Changing directory to path $path" } viewModelScope.launch(Dispatchers.Default) { @@ -201,4 +207,15 @@ class DirectoryViewModel( ) //endregion + + //region Playlist + + fun addSelectedImageToPlaylist(playlist: PlaylistDetails) { + imageUiState.value.selectedImageDirectory?.let{ + addToPlaylist(it, playlist) + } + } + + //endregion + } diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/PlaylistScreen.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/PlaylistScreen.kt index 12cc70ad..814aa647 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/PlaylistScreen.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/PlaylistScreen.kt @@ -8,6 +8,8 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import com.kevinschildhorn.fotopresenter.Playlist +import com.kevinschildhorn.fotopresenter.data.PlaylistDetails import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.ConfirmationDialog import com.kevinschildhorn.fotopresenter.ui.screens.directory.DirectoryOverlay import com.kevinschildhorn.fotopresenter.ui.screens.playlist.composables.PlaylistOverlay @@ -22,9 +24,9 @@ enum class PlaylistDialog{ @Composable fun PlaylistScreen( viewModel: PlaylistViewModel, - onLoginSuccess: () -> Unit, + onPlaylistSelected: (PlaylistDetails) -> Unit, ) { - val uiState by viewModel.uiState.collectAsState() + val uiState by viewModel.playlistState.collectAsState() var dialogOpen by remember { mutableStateOf(PlaylistDialog.NONE) } LaunchedEffect(Unit) { @@ -33,8 +35,10 @@ fun PlaylistScreen( PlaylistOverlay( uiState.playlists, - onClick = { - + onClick = { id -> + viewModel.getPlaylist(id)?.let { + onPlaylistSelected(it) + } }, onDelete = { dialogOpen = PlaylistDialog.DELETE viewModel.setSelectedPlaylist(it) diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/PlaylistScreenState.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/PlaylistScreenState.kt index 89a7a16e..c3b158a3 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/PlaylistScreenState.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/PlaylistScreenState.kt @@ -1,11 +1,12 @@ package com.kevinschildhorn.fotopresenter.ui.screens.playlist import com.kevinschildhorn.fotopresenter.Playlist +import com.kevinschildhorn.fotopresenter.data.PlaylistDetails import com.kevinschildhorn.fotopresenter.ui.UiState import com.kevinschildhorn.fotopresenter.ui.screens.common.ScreenState data class PlaylistScreenState( - val playlists: List = emptyList(), + val playlists: List = emptyList(), val selectedId: Long? = null, override val state: UiState = UiState.IDLE, ) : ScreenState \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/PlaylistViewModel.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/PlaylistViewModel.kt index 06acb56f..c22c1a22 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/PlaylistViewModel.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/PlaylistViewModel.kt @@ -1,6 +1,9 @@ package com.kevinschildhorn.fotopresenter.ui.screens.playlist import co.touchlab.kermit.Logger +import com.kevinschildhorn.fotopresenter.Playlist +import com.kevinschildhorn.fotopresenter.data.ImageDirectory +import com.kevinschildhorn.fotopresenter.data.PlaylistDetails import com.kevinschildhorn.fotopresenter.data.repositories.PlaylistRepository import com.kevinschildhorn.fotopresenter.ui.shared.ViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -9,37 +12,47 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import org.koin.core.component.KoinComponent -class PlaylistViewModel( +open class PlaylistViewModel( private val playlistRepository: PlaylistRepository, private val logger: Logger, ) : ViewModel(), KoinComponent { private val _uiState = MutableStateFlow(PlaylistScreenState()) - val uiState: StateFlow = _uiState.asStateFlow() + val playlistState: StateFlow = _uiState.asStateFlow() init { refreshPlaylists() } - fun refreshPlaylists(){ + fun refreshPlaylists() { val allPlaylists = playlistRepository.getAllPlaylists() _uiState.update { it.copy(playlists = allPlaylists) } } - fun setSelectedPlaylist(id:Long){ + fun setSelectedPlaylist(id: Long) { _uiState.update { it.copy(selectedId = id) } } - fun clearSelectedPlaylist(){ + fun getPlaylist(id: Long): PlaylistDetails? = + _uiState.value.playlists.find { it.id == id } + + fun clearSelectedPlaylist() { _uiState.update { it.copy(selectedId = null) } } - fun createPlaylist(name: String){ + fun createPlaylist(name: String) { playlistRepository.createPlaylist(name) refreshPlaylists() } - fun deletePlaylist(){ + fun addToPlaylist(imageDirectory: ImageDirectory, playlist: PlaylistDetails) { + playlistRepository.insertPlaylistImage( + playlistId = playlist.id, + directory = imageDirectory + ) + } + + fun deletePlaylist() { _uiState.value.selectedId?.let { playlistRepository.deletePlaylist(it) } diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/composables/PlaylistColumn.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/composables/PlaylistColumn.kt index 34e72a33..830cde16 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/composables/PlaylistColumn.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/composables/PlaylistColumn.kt @@ -21,13 +21,14 @@ import androidx.compose.ui.unit.dp import com.kevinschildhorn.atomik.atomic.atoms.textStyle import com.kevinschildhorn.atomik.color.base.composeColor import com.kevinschildhorn.fotopresenter.Playlist +import com.kevinschildhorn.fotopresenter.data.PlaylistDetails import com.kevinschildhorn.fotopresenter.ui.atoms.FotoColors import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.AtomikText import com.kevinschildhorn.fotopresenter.ui.screens.playlist.PlaylistScreenAtoms @Composable fun PlaylistColumn( - options: List = emptyList(), + options: List = emptyList(), onCreate: () -> Unit, onClick: (Long) -> Unit, onEdit: (Long) -> Unit, diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/composables/PlaylistOverlay.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/composables/PlaylistOverlay.kt index 00aded45..790b7619 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/composables/PlaylistOverlay.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/composables/PlaylistOverlay.kt @@ -2,11 +2,12 @@ package com.kevinschildhorn.fotopresenter.ui.screens.playlist.composables import androidx.compose.runtime.Composable import com.kevinschildhorn.fotopresenter.Playlist +import com.kevinschildhorn.fotopresenter.data.PlaylistDetails import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.Overlay @Composable fun PlaylistOverlay( - options: List = emptyList(), + options: List = emptyList(), onCreate: () -> Unit, onClick: (Long) -> Unit, onEdit: (Long) -> Unit, diff --git a/shared/src/commonMain/sqldelight/com/kevinschildhorn/fotopresenter/ImageDirectory.sq b/shared/src/commonMain/sqldelight/com/kevinschildhorn/fotopresenter/ImageDirectory.sq index e00317d5..0f70792c 100644 --- a/shared/src/commonMain/sqldelight/com/kevinschildhorn/fotopresenter/ImageDirectory.sq +++ b/shared/src/commonMain/sqldelight/com/kevinschildhorn/fotopresenter/ImageDirectory.sq @@ -9,6 +9,11 @@ insertPlaylistImage: INSERT INTO PlaylistImage(playlist_id, directory_path, directory_id) VALUES (?, ?, ?); +selectPlaylistImages: +SELECT * +FROM PlaylistImage +WHERE playlist_id = ?; + selectPlaylistImage: SELECT * FROM PlaylistImage 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 052b84d1..4f7acf8f 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 @@ -1,6 +1,6 @@ package com.kevinschildhorn.fotopresenter.data.datasources -import com.kevinschildhorn.fotopresenter.data.network.MockNetworkDirectoryDetails +import com.kevinschildhorn.fotopresenter.data.network.DefaultNetworkDirectoryDetails import com.kevinschildhorn.fotopresenter.data.network.MockNetworkHandler import com.kevinschildhorn.fotopresenter.data.network.NetworkHandlerError import com.kevinschildhorn.fotopresenter.data.network.NetworkHandlerException @@ -35,7 +35,7 @@ class ImageRemoteDataSourceTest { fun `get Image Success`() = runBlocking { val networkDirectory = - MockNetworkDirectoryDetails( + DefaultNetworkDirectoryDetails( fullPath = "Photos/Success.png", id = 1, ) @@ -50,7 +50,7 @@ class ImageRemoteDataSourceTest { fun `get Image Failure`() = runBlocking { val networkDirectory = - MockNetworkDirectoryDetails( + DefaultNetworkDirectoryDetails( fullPath = "Photos/nonExistant.png", id = 1, ) @@ -63,7 +63,7 @@ class ImageRemoteDataSourceTest { runBlocking { networkHandler.disconnect() try { - val image = dataSource.getImage(MockNetworkDirectoryDetails("", 1)) + val image = dataSource.getImage(DefaultNetworkDirectoryDetails("", 1)) fail("Should have thrown exception") } catch (e: NetworkHandlerException) { assertEquals(e.message, NetworkHandlerError.NOT_CONNECTED.message) diff --git a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/datasources/PlaylistDataSourceTest.kt b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/datasources/PlaylistDataSourceTest.kt index cff7320a..32ea9e2d 100644 --- a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/datasources/PlaylistDataSourceTest.kt +++ b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/data/datasources/PlaylistDataSourceTest.kt @@ -4,7 +4,7 @@ import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver import com.kevinschildhorn.fotopresenter.PlaylistDatabase import com.kevinschildhorn.fotopresenter.data.ImageDirectory -import com.kevinschildhorn.fotopresenter.data.network.MockNetworkDirectoryDetails +import com.kevinschildhorn.fotopresenter.data.network.DefaultNetworkDirectoryDetails import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull @@ -18,7 +18,7 @@ Testing [PlaylistDataSource] class PlaylistDataSourceTest { private val imageDirectory = ImageDirectory( - MockNetworkDirectoryDetails( + DefaultNetworkDirectoryDetails( fullPath = "Image1.png", id = 1, ) @@ -26,13 +26,13 @@ class PlaylistDataSourceTest { private val imageDirectoryList: List = listOf( imageDirectory, ImageDirectory( - MockNetworkDirectoryDetails( + DefaultNetworkDirectoryDetails( fullPath = "Photos/Image2.png", id = 2, ) ), ImageDirectory( - MockNetworkDirectoryDetails( + DefaultNetworkDirectoryDetails( fullPath = "Image3.png", id = 3, ) 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 4088a73c..b7b0deec 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 @@ -19,35 +19,35 @@ object MockNetworkHandler : NetworkHandler { mapOf( "" to listOf( - MockNetworkDirectoryDetails(fullPath = "Photos", id = photoDirectoryId), - MockNetworkDirectoryDetails(fullPath = "NewDirectory", id = 1), - MockNetworkDirectoryDetails(fullPath = "Peeng.png", id = 75), - MockNetworkDirectoryDetails(fullPath = "Jaypeg.jpg", id = 3), - MockNetworkDirectoryDetails(fullPath = "textFile.txt", id = 4), + DefaultNetworkDirectoryDetails(fullPath = "Photos", id = photoDirectoryId), + DefaultNetworkDirectoryDetails(fullPath = "NewDirectory", id = 1), + DefaultNetworkDirectoryDetails(fullPath = "Peeng.png", id = 75), + DefaultNetworkDirectoryDetails(fullPath = "Jaypeg.jpg", id = 3), + DefaultNetworkDirectoryDetails(fullPath = "textFile.txt", id = 4), ), "Directories" to listOf( - MockNetworkDirectoryDetails(fullPath = "Directories/NewDirectory", id = 1), - MockNetworkDirectoryDetails(fullPath = "Directories/NewDirectory2", id = 2), + DefaultNetworkDirectoryDetails(fullPath = "Directories/NewDirectory", id = 1), + DefaultNetworkDirectoryDetails(fullPath = "Directories/NewDirectory2", id = 2), ), "Photos" to listOf( - 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), + DefaultNetworkDirectoryDetails(fullPath = "Photos/Peeng2.png", id = 2), + DefaultNetworkDirectoryDetails(fullPath = "Photos/Jaypeg2.jpg", id = 3), + DefaultNetworkDirectoryDetails(fullPath = "Photos/textFile2.txt", id = 4), + DefaultNetworkDirectoryDetails(fullPath = "Photos/SubPhotos", id = 5), ), "Photos/SubPhotos" to listOf( - MockNetworkDirectoryDetails( + DefaultNetworkDirectoryDetails( fullPath = "Photos/SubPhotos/Peeng3.png", id = 2, ), - MockNetworkDirectoryDetails( + DefaultNetworkDirectoryDetails( fullPath = "Photos/SubPhotos/Jaypeg3.jpg", id = 3, ), - MockNetworkDirectoryDetails( + DefaultNetworkDirectoryDetails( fullPath = "Photos/SubPhotos/textFile3.txt", id = 4, ), 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 c44250fd..2babcf5b 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 @@ -1,6 +1,6 @@ package com.kevinschildhorn.fotopresenter.data.repositories -import com.kevinschildhorn.fotopresenter.data.network.MockNetworkDirectoryDetails +import com.kevinschildhorn.fotopresenter.data.network.DefaultNetworkDirectoryDetails import com.kevinschildhorn.fotopresenter.data.network.MockNetworkHandler import com.kevinschildhorn.fotopresenter.data.network.NetworkHandlerError import com.kevinschildhorn.fotopresenter.data.network.NetworkHandlerException @@ -46,7 +46,7 @@ class ImageRepositoryTest : KoinTest { try { val result = repository.getImage( - MockNetworkDirectoryDetails( + DefaultNetworkDirectoryDetails( fullPath = "Photos/Success.png", id = 1, ), @@ -61,7 +61,7 @@ class ImageRepositoryTest : KoinTest { runBlocking { val result = repository.getImage( - MockNetworkDirectoryDetails( + DefaultNetworkDirectoryDetails( fullPath = "Photos/nonExistant.png", id = 1, ), @@ -76,7 +76,7 @@ class ImageRepositoryTest : KoinTest { try { val result = repository.getImage( - MockNetworkDirectoryDetails( + DefaultNetworkDirectoryDetails( fullPath = "Photos/nonExistant.png", id = 1, ), diff --git a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/RetrieveImageDirectoriesUseCaseTest.kt b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/RetrieveImageDirectoriesUseCaseTest.kt index e41125d9..e7857649 100644 --- a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/RetrieveImageDirectoriesUseCaseTest.kt +++ b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/domain/RetrieveImageDirectoriesUseCaseTest.kt @@ -1,6 +1,6 @@ package com.kevinschildhorn.fotopresenter.domain -import com.kevinschildhorn.fotopresenter.data.network.MockNetworkDirectoryDetails +import com.kevinschildhorn.fotopresenter.data.network.DefaultNetworkDirectoryDetails import com.kevinschildhorn.fotopresenter.data.network.MockNetworkHandler import com.kevinschildhorn.fotopresenter.data.network.NetworkHandlerError import com.kevinschildhorn.fotopresenter.data.network.NetworkHandlerException @@ -42,7 +42,7 @@ class RetrieveImageDirectoriesUseCaseTest : KoinTest { @Test fun `receive directory content success`() = runBlocking { - val details = MockNetworkDirectoryDetails("", 1) + val details = DefaultNetworkDirectoryDetails("", 1) val result = useCase(details) assertEquals(6, result.count()) } @@ -50,7 +50,7 @@ class RetrieveImageDirectoriesUseCaseTest : KoinTest { @Test fun `receive directory content only directories`() = runBlocking { - val details = MockNetworkDirectoryDetails("Directories", 1) + val details = DefaultNetworkDirectoryDetails("Directories", 1) val result = useCase(details) assertEquals(0, result.count()) } @@ -58,7 +58,7 @@ class RetrieveImageDirectoriesUseCaseTest : KoinTest { @Test fun `receive directory content failure`() = runBlocking { - val details = MockNetworkDirectoryDetails("nonExistant", 1) + val details = DefaultNetworkDirectoryDetails("nonExistant", 1) val result = useCase(details) assertEquals(0, result.count()) } @@ -68,7 +68,7 @@ class RetrieveImageDirectoriesUseCaseTest : KoinTest { runBlocking { MockNetworkHandler.disconnect() try { - val details = MockNetworkDirectoryDetails("Photos", 1) + val details = DefaultNetworkDirectoryDetails("Photos", 1) val result = useCase(details) fail("Should've thrown") } catch (e: NetworkHandlerException) { 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 index 5312573a..9955aa21 100644 --- a/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/ImageViewModelTest.kt +++ b/shared/src/commonTest/kotlin/com/kevinschildhorn/fotopresenter/ui/viewmodel/ImageViewModelTest.kt @@ -2,7 +2,7 @@ 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.DefaultNetworkDirectoryDetails import com.kevinschildhorn.fotopresenter.data.network.MockNetworkHandler import com.kevinschildhorn.fotopresenter.testingModule import com.kevinschildhorn.fotopresenter.ui.screens.common.DefaultImageViewModel @@ -31,12 +31,12 @@ class ImageViewModelTest : KoinTest { 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)), + ImageDirectory(DefaultNetworkDirectoryDetails("Peeng.png", 1)), + ImageDirectory(DefaultNetworkDirectoryDetails("Jaypeg.jpg", 2)), + ImageDirectory(DefaultNetworkDirectoryDetails("Photos/Peeng2.png", 3)), + ImageDirectory(DefaultNetworkDirectoryDetails("Photos/Jaypeg2.jpg", 4)), + ImageDirectory(DefaultNetworkDirectoryDetails("Photos/SubPhotos/Peeng3.png", 5)), + ImageDirectory(DefaultNetworkDirectoryDetails("Photos/SubPhotos/Jaypeg3.jpg", 6)), ) @BeforeTest diff --git a/shared/src/jvmMain/kotlin/com/kevinschildhorn/fotopresenter/data/network/SMBJHandler.kt b/shared/src/jvmMain/kotlin/com/kevinschildhorn/fotopresenter/data/network/SMBJHandler.kt index ca4fc64e..8f57f9c9 100644 --- a/shared/src/jvmMain/kotlin/com/kevinschildhorn/fotopresenter/data/network/SMBJHandler.kt +++ b/shared/src/jvmMain/kotlin/com/kevinschildhorn/fotopresenter/data/network/SMBJHandler.kt @@ -49,6 +49,17 @@ object SMBJHandler : NetworkHandler { return true } + override suspend fun getDirectoryDetails(path: String): NetworkDirectoryDetails? { + share?.getFileInformation(path)?.let { + return DefaultNetworkDirectoryDetails( + id = it.internalInformation.indexNumber.toInt(), + fullPath = path, + ) + } ?: run { + return null + } + } + override suspend fun getDirectoryContents(path: String): List { return share?.list(path)?.map { SMBJNetworkDirectoryDetails( From cff8f8a49f7c379d9c46c445268f8c260abd0574 Mon Sep 17 00:00:00 2001 From: Kevin Schildhorn Date: Mon, 18 Dec 2023 14:16:12 -0500 Subject: [PATCH 2/2] Adding playlist --- .../ui/compose/PlaylistPreviews.kt | 8 ++- .../ui/screens/common/ImageScreenState.kt | 7 ++- .../ui/screens/common/ImageViewModel.kt | 15 ++--- .../screens/common/composables/FotoDIalog.kt | 8 ++- .../ui/screens/directory/DirectoryScreen.kt | 13 ++-- .../screens/directory/DirectoryViewModel.kt | 12 ++-- .../ui/screens/playlist/PlaylistScreen.kt | 60 ++++++++++++------- .../screens/playlist/PlaylistScreenState.kt | 9 ++- .../ui/screens/playlist/PlaylistViewModel.kt | 7 ++- .../playlist/composables/PlaylistColumn.kt | 23 +++---- .../playlist/composables/PlaylistOverlay.kt | 13 +++- .../composables/PlaylistScreenPlaylistRow.kt | 12 ++++ .../playlist/composables/TextListDialog.kt | 24 ++++++++ 13 files changed, 150 insertions(+), 61 deletions(-) create mode 100644 shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/composables/TextListDialog.kt diff --git a/shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/PlaylistPreviews.kt b/shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/PlaylistPreviews.kt index 7f497b4e..413393e8 100644 --- a/shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/PlaylistPreviews.kt +++ b/shared/src/androidMain/kotlin/com/kevinschildhorn/fotopresenter/ui/compose/PlaylistPreviews.kt @@ -27,8 +27,8 @@ import com.kevinschildhorn.fotopresenter.ui.screens.playlist.composables.Playlis @Preview fun PlaylistRowsPreview() { Column(modifier = Modifier.fillMaxSize()) { - PlaylistScreenPlaylistRow("Playlist 1", onClick = {}, onDelete = {}, onEdit = {}) - PlaylistScreenPlaylistRow("Playlist 2", onClick = {}, onDelete = {}, onEdit = {}) + PlaylistScreenPlaylistRow("Playlist 1", {},{}, {}, {}) + PlaylistScreenPlaylistRow("Playlist 2", {}, {}, {}, {}) PlaylistScreenCreateRow(onClick = {}) } } @@ -41,6 +41,7 @@ fun PlaylistColumnPreview() { {}, {}, {}, + {}, {} ) } @@ -62,7 +63,8 @@ fun PlaylistOverlayPreview() { {}, {}, {}, - {} + {}, + {}, ) } 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 index 1a8fcbf9..fbe2814d 100644 --- 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 @@ -7,7 +7,10 @@ data class ImageScreenState( val imageDirectories: List = emptyList(), val selectedImageIndex: Int? = null, val selectedImage: ImageBitmap? = null, -){ +) { val selectedImageDirectory: ImageDirectory? - get() = imageDirectories.getOrNull(index) + get() = + selectedImageIndex?.let { + imageDirectories.getOrNull(it) + } } 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 index 613ca77b..76436936 100644 --- 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 @@ -70,6 +70,7 @@ class DefaultImageViewModel(private val logger: Logger? = null) : ImageViewModel } override fun setSelectedImage(index: Int?) { + logger?.i { "Setting selected image to $index" } _uiState.update { it.copy(selectedImageIndex = index) } updateSelectedImage() } @@ -80,16 +81,12 @@ class DefaultImageViewModel(private val logger: Logger? = null) : ImageViewModel private fun updateSelectedImage() { logger?.i { "Updating Selected Index" } - with(imageUiState.value) { - selectedImageIndex?.let { index -> - logger?.d { "Selected Image Index found. getting Image Directory" } - this.selectedImageDirectory?.let { - logger?.d { "Image Directory found, showing photo" } - showPhoto(it) - } ?: run { - logger?.w { "Image Directory NOT found for index: $index in count ${imageDirectories.count()}" } - } + selectedImageDirectory?.let { + logger?.d { "Image Directory found, showing photo" } + showPhoto(it) + } ?: run { + logger?.w { "Image Directory NOT found" } } } } diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/FotoDIalog.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/FotoDIalog.kt index ee51ddf8..fc5f198d 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/FotoDIalog.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/common/composables/FotoDIalog.kt @@ -30,7 +30,7 @@ import com.kevinschildhorn.fotopresenter.ui.screens.common.CommonAtoms fun FotoDialog( dialogTitle: String, onDismissRequest: () -> Unit, - onConfirmation: () -> Unit, + onConfirmation: (() -> Unit)? = null, content: @Composable ColumnScope.() -> Unit, ) { Dialog(onDismissRequest = { onDismissRequest() }) { @@ -68,8 +68,10 @@ fun FotoDialog( ) { AtomikText("Cancel", atom = CommonAtoms.dialogButton) } - PrimaryTextButton("Confirm") { - onConfirmation() + onConfirmation?.let { + PrimaryTextButton("Confirm") { + it() + } } } } diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryScreen.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryScreen.kt index c1333653..fa37de54 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryScreen.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryScreen.kt @@ -135,14 +135,17 @@ fun DirectoryScreen( ActionSheetAction.START_SLIDESHOW -> { viewModel.startSlideshow(contextMenuPhotoState?.id!!) overlayVisible = DirectoryOverlay.NONE + contextMenuPhotoState = null } + ActionSheetAction.ADD_STATIC_LOCATION -> overlayVisible = DirectoryOverlay.PLAYLIST + ActionSheetAction.NONE -> { overlayVisible = DirectoryOverlay.NONE + contextMenuPhotoState = null } } - contextMenuPhotoState = null }, onDismiss = { overlayVisible = DirectoryOverlay.NONE @@ -206,9 +209,11 @@ fun DirectoryScreen( //endregion //region Playlist - if(overlayVisible == DirectoryOverlay.PLAYLIST) { - PlaylistScreen(viewModel) { - viewModel.addSelectedImageToPlaylist(it) + if (overlayVisible == DirectoryOverlay.PLAYLIST) { + PlaylistScreen(viewModel) { playlist -> + viewModel.addToPlaylist(contextMenuPhotoState, playlist) + overlayVisible = DirectoryOverlay.NONE + contextMenuPhotoState = null } } //endregion diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryViewModel.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryViewModel.kt index 252604d0..20ba554a 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryViewModel.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/directory/DirectoryViewModel.kt @@ -3,6 +3,7 @@ package com.kevinschildhorn.fotopresenter.ui.screens.directory import co.touchlab.kermit.Logger import com.kevinschildhorn.fotopresenter.Playlist import com.kevinschildhorn.fotopresenter.data.DirectoryContents +import com.kevinschildhorn.fotopresenter.data.ImageDirectory import com.kevinschildhorn.fotopresenter.data.ImageSlideshowDetails import com.kevinschildhorn.fotopresenter.data.PlaylistDetails import com.kevinschildhorn.fotopresenter.data.State @@ -210,12 +211,15 @@ class DirectoryViewModel( //region Playlist - fun addSelectedImageToPlaylist(playlist: PlaylistDetails) { - imageUiState.value.selectedImageDirectory?.let{ - addToPlaylist(it, playlist) + fun addToPlaylist(state: DirectoryGridCellState?, playlist: PlaylistDetails) { + logger.i { "Inserting Playlist Image ${playlist.id} as ${state}" } + + imageUiState.value.imageDirectories.find { it.id == state?.id }?.let { imageDirectory -> + addToPlaylist(imageDirectory, playlist) + } ?: run { + logger.w { "Could not find image directory" } } } - //endregion } diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/PlaylistScreen.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/PlaylistScreen.kt index 814aa647..09292bf6 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/PlaylistScreen.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/PlaylistScreen.kt @@ -14,13 +14,16 @@ import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.Confirmat import com.kevinschildhorn.fotopresenter.ui.screens.directory.DirectoryOverlay import com.kevinschildhorn.fotopresenter.ui.screens.playlist.composables.PlaylistOverlay import com.kevinschildhorn.fotopresenter.ui.screens.playlist.composables.TextEntryDialog +import com.kevinschildhorn.fotopresenter.ui.screens.playlist.composables.TextListDialog -enum class PlaylistDialog{ +enum class PlaylistDialog { NONE, CREATE, DELETE, + DETAILS, EDIT } + @Composable fun PlaylistScreen( viewModel: PlaylistViewModel, @@ -39,6 +42,9 @@ fun PlaylistScreen( viewModel.getPlaylist(id)?.let { onPlaylistSelected(it) } + }, onDetails = { + dialogOpen = PlaylistDialog.DETAILS + viewModel.setSelectedPlaylist(it) }, onDelete = { dialogOpen = PlaylistDialog.DELETE viewModel.setSelectedPlaylist(it) @@ -50,27 +56,39 @@ fun PlaylistScreen( } ) - if (dialogOpen == PlaylistDialog.CREATE) { - TextEntryDialog({ - dialogOpen = PlaylistDialog.NONE - }, { - viewModel.createPlaylist(it) - dialogOpen = PlaylistDialog.NONE - }) - } - if (dialogOpen == PlaylistDialog.DELETE) { - ConfirmationDialog( - "Delete Playlist", - "Are you sure you want to delete this playlist?", - onDismissRequest = { + when(dialogOpen){ + PlaylistDialog.CREATE -> { + TextEntryDialog({ dialogOpen = PlaylistDialog.NONE - viewModel.clearSelectedPlaylist() - }, - onConfirmation = { - viewModel.deletePlaylist() + }, { + viewModel.createPlaylist(it) dialogOpen = PlaylistDialog.NONE - viewModel.clearSelectedPlaylist() - }, - ) + }) + } + PlaylistDialog.DELETE -> { + ConfirmationDialog( + "Delete Playlist", + "Are you sure you want to delete this playlist?", + onDismissRequest = { + dialogOpen = PlaylistDialog.NONE + viewModel.clearSelectedPlaylist() + }, + onConfirmation = { + viewModel.deletePlaylist() + dialogOpen = PlaylistDialog.NONE + viewModel.clearSelectedPlaylist() + }, + ) + } + PlaylistDialog.DETAILS -> { + uiState.selectedPlaylist?.let { + TextListDialog(it.images.map { it.directory_path }){ + dialogOpen = PlaylistDialog.NONE + } + } + } + else -> { + + } } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/PlaylistScreenState.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/PlaylistScreenState.kt index c3b158a3..4359864c 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/PlaylistScreenState.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/PlaylistScreenState.kt @@ -9,4 +9,11 @@ data class PlaylistScreenState( val playlists: List = emptyList(), val selectedId: Long? = null, override val state: UiState = UiState.IDLE, -) : ScreenState \ No newline at end of file +) : ScreenState { + + val selectedPlaylist: PlaylistDetails? + get() = + selectedId?.let { id -> + playlists.find { it.id == id } + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/PlaylistViewModel.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/PlaylistViewModel.kt index c22c1a22..acaf723f 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/PlaylistViewModel.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/PlaylistViewModel.kt @@ -46,10 +46,15 @@ open class PlaylistViewModel( } fun addToPlaylist(imageDirectory: ImageDirectory, playlist: PlaylistDetails) { + logger.i { "Inserting Playlist Image ${playlist.id} as ${imageDirectory.name}" } playlistRepository.insertPlaylistImage( playlistId = playlist.id, directory = imageDirectory - ) + )?.let { + logger.i { "Successfully inserted playlist image" } + } ?: run { + logger.w { "Failed to insert playlist image" } + } } fun deletePlaylist() { diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/composables/PlaylistColumn.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/composables/PlaylistColumn.kt index 830cde16..4b3ceeeb 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/composables/PlaylistColumn.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/composables/PlaylistColumn.kt @@ -1,28 +1,21 @@ package com.kevinschildhorn.fotopresenter.ui.screens.playlist.composables import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import com.kevinschildhorn.fotopresenter.ui.atoms.Padding import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Divider -import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import com.kevinschildhorn.atomik.atomic.atoms.textStyle import com.kevinschildhorn.atomik.color.base.composeColor -import com.kevinschildhorn.fotopresenter.Playlist import com.kevinschildhorn.fotopresenter.data.PlaylistDetails import com.kevinschildhorn.fotopresenter.ui.atoms.FotoColors +import com.kevinschildhorn.fotopresenter.ui.atoms.Padding import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.AtomikText import com.kevinschildhorn.fotopresenter.ui.screens.playlist.PlaylistScreenAtoms @@ -31,6 +24,7 @@ fun PlaylistColumn( options: List = emptyList(), onCreate: () -> Unit, onClick: (Long) -> Unit, + onDetails: (Long) -> Unit, onEdit: (Long) -> Unit, onDelete: (Long) -> Unit, ) { @@ -54,6 +48,9 @@ fun PlaylistColumn( onClick = { onClick(it.id) }, + onDetails = { + onDetails(it.id) + }, onEdit = { onEdit(it.id) }, @@ -61,7 +58,11 @@ fun PlaylistColumn( onDelete(it.id) } ) - Divider(startIndent = 0.dp, thickness = 1.dp, color = FotoColors.secondaryText.composeColor) + Divider( + startIndent = 0.dp, + thickness = 1.dp, + color = FotoColors.secondaryText.composeColor + ) } } PlaylistScreenCreateRow(onClick = onCreate) diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/composables/PlaylistOverlay.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/composables/PlaylistOverlay.kt index 790b7619..7b061cf6 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/composables/PlaylistOverlay.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/composables/PlaylistOverlay.kt @@ -1,6 +1,7 @@ package com.kevinschildhorn.fotopresenter.ui.screens.playlist.composables import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import com.kevinschildhorn.fotopresenter.Playlist import com.kevinschildhorn.fotopresenter.data.PlaylistDetails import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.Overlay @@ -10,6 +11,7 @@ fun PlaylistOverlay( options: List = emptyList(), onCreate: () -> Unit, onClick: (Long) -> Unit, + onDetails: (Long) -> Unit, onEdit: (Long) -> Unit, onDelete: (Long) -> Unit, ) { @@ -17,7 +19,14 @@ fun PlaylistOverlay( 5f, visible = true, onDismiss = {}, - ) { - PlaylistColumn(options, onCreate, onClick, onEdit, onDelete) + ) { + PlaylistColumn( + options, + onCreate = onCreate, + onClick = onClick, + onDetails = onDetails, + onEdit = onEdit, + onDelete = onDelete, + ) } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/composables/PlaylistScreenPlaylistRow.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/composables/PlaylistScreenPlaylistRow.kt index 83aff0dd..2e4967c8 100644 --- a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/composables/PlaylistScreenPlaylistRow.kt +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/composables/PlaylistScreenPlaylistRow.kt @@ -23,12 +23,14 @@ import com.kevinschildhorn.fotopresenter.ui.screens.playlist.PlaylistScreenAtoms import compose.icons.EvaIcons import compose.icons.evaicons.Outline import compose.icons.evaicons.outline.Edit +import compose.icons.evaicons.outline.Info import compose.icons.evaicons.outline.Trash @Composable fun PlaylistScreenPlaylistRow( title: String, onClick: () -> Unit, + onDetails: () -> Unit, onEdit: () -> Unit, onDelete: () -> Unit, ) { @@ -46,6 +48,16 @@ fun PlaylistScreenPlaylistRow( Spacer(Modifier.fillMaxWidth()) } Row(modifier = Modifier.fillMaxHeight()) { + TextButton( + modifier = Modifier.width(44.dp), + onClick = onDetails + ) { + AtomikIcon( + EvaIcons.Outline.Info, + atom, + contentDescription = "Details", + ) + } TextButton( modifier = Modifier.width(44.dp), onClick = onEdit diff --git a/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/composables/TextListDialog.kt b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/composables/TextListDialog.kt new file mode 100644 index 00000000..104a9eab --- /dev/null +++ b/shared/src/commonMain/kotlin/com/kevinschildhorn/fotopresenter/ui/screens/playlist/composables/TextListDialog.kt @@ -0,0 +1,24 @@ +package com.kevinschildhorn.fotopresenter.ui.screens.playlist.composables + +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import com.kevinschildhorn.fotopresenter.ui.screens.common.composables.FotoDialog + +@Composable +fun TextListDialog( + list: List, + onDismissRequest: () -> Unit, +) { + FotoDialog( + dialogTitle = "Playlist Items", + onDismissRequest = onDismissRequest, + ) { + LazyColumn { + items(list){ + Text(it) + } + } + } +} \ No newline at end of file