diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/AiGenerationResultMappers.kt b/data/src/main/java/com/shifthackz/aisdv1/data/mappers/AiGenerationResultMappers.kt index b76ff056..b1f6bf17 100644 --- a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/AiGenerationResultMappers.kt +++ b/data/src/main/java/com/shifthackz/aisdv1/data/mappers/AiGenerationResultMappers.kt @@ -26,6 +26,7 @@ fun AiGenerationResult.mapDomainToEntity(): GenerationResultEntity = with(this) subSeed = subSeed, subSeedStrength = subSeedStrength, denoisingStrength = denoisingStrength, + hidden = hidden, ) } //endregion @@ -53,6 +54,7 @@ fun GenerationResultEntity.mapEntityToDomain(): AiGenerationResult = with(this) subSeed = subSeed, subSeedStrength = subSeedStrength, denoisingStrength = denoisingStrength, + hidden = hidden, ) } //endregion diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/ImageToImagePayloadMappers.kt b/data/src/main/java/com/shifthackz/aisdv1/data/mappers/ImageToImagePayloadMappers.kt index 467ca8a3..2863d629 100644 --- a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/ImageToImagePayloadMappers.kt +++ b/data/src/main/java/com/shifthackz/aisdv1/data/mappers/ImageToImagePayloadMappers.kt @@ -142,6 +142,7 @@ fun Pair.mapToAiGenResult(): AiGenera subSeed = if (payload.subSeed.trim().isNotEmpty()) payload.subSeed else mapSubSeedFromRemote(response.info), subSeedStrength = payload.subSeedStrength, + hidden = false, ) } @@ -165,6 +166,7 @@ fun Pair.mapCloudToAiGenResult(): AiGenerationResul seed = payload.seed, subSeed = payload.subSeed, subSeedStrength = payload.subSeedStrength, + hidden = false, ) } //endregion diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/TextToImagePayloadMappers.kt b/data/src/main/java/com/shifthackz/aisdv1/data/mappers/TextToImagePayloadMappers.kt index 36c76b21..5c4589bb 100755 --- a/data/src/main/java/com/shifthackz/aisdv1/data/mappers/TextToImagePayloadMappers.kt +++ b/data/src/main/java/com/shifthackz/aisdv1/data/mappers/TextToImagePayloadMappers.kt @@ -143,6 +143,7 @@ fun Pair.mapToAiGenResult(): AiGenerat subSeed = if (payload.subSeed.trim().isNotEmpty()) payload.subSeed else mapSubSeedFromRemote(response.info), subSeedStrength = payload.subSeedStrength, + hidden = false, ) } @@ -166,6 +167,7 @@ fun Pair.mapCloudToAiGenResult(): AiGenerationResult seed = payload.seed, subSeed = payload.subSeed, subSeedStrength = payload.subSeedStrength, + hidden = false, ) } @@ -189,6 +191,7 @@ fun Pair.mapLocalDiffusionToAiGenResult(): AiGenerat seed = payload.seed, subSeed = payload.subSeed, subSeedStrength = payload.subSeedStrength, + hidden = false, ) } //endregion diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/preference/PreferenceManagerImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/preference/PreferenceManagerImpl.kt index 1c5ee337..56191be3 100644 --- a/data/src/main/java/com/shifthackz/aisdv1/data/preference/PreferenceManagerImpl.kt +++ b/data/src/main/java/com/shifthackz/aisdv1/data/preference/PreferenceManagerImpl.kt @@ -107,7 +107,7 @@ class PreferenceManagerImpl( override var formPromptTaggedInput: Boolean by preferences.delegates.boolean( key = KEY_FORM_PROMPT_TAGGED_INPUT, - default = true, + default = false, onChanged = ::onPreferencesChanged, ) @@ -274,7 +274,7 @@ class PreferenceManagerImpl( const val KEY_AI_AUTO_SAVE = "key_ai_auto_save" const val KEY_SAVE_TO_MEDIA_STORE = "key_save_to_media_store" const val KEY_FORM_ALWAYS_SHOW_ADVANCED_OPTIONS = "key_always_show_advanced_options" - const val KEY_FORM_PROMPT_TAGGED_INPUT = "key_prompt_tagged_input" + const val KEY_FORM_PROMPT_TAGGED_INPUT = "key_prompt_tagged_input_kb" const val KEY_SERVER_SOURCE = "key_server_source" const val KEY_SD_MODEL = "key_sd_model" const val KEY_HORDE_API_KEY = "key_horde_api_key" diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/GenerationResultRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/GenerationResultRepositoryImpl.kt index b6de9b8a..c4e8614a 100644 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/GenerationResultRepositoryImpl.kt +++ b/data/src/main/java/com/shifthackz/aisdv1/data/repository/GenerationResultRepositoryImpl.kt @@ -39,4 +39,11 @@ internal class GenerationResultRepositoryImpl( override fun deleteByIdList(idList: List) = localDataSource.deleteByIdList(idList) override fun deleteAll() = localDataSource.deleteAll() + + override fun toggleVisibility(id: Long): Single = localDataSource + .queryById(id) + .map { it.copy(hidden = !it.hidden) } + .flatMap(localDataSource::insert) + .flatMap { localDataSource.queryById(id) } + .map(AiGenerationResult::hidden) } diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/AiGenerationResultMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/AiGenerationResultMocks.kt index 354198f9..f36301b7 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/AiGenerationResultMocks.kt +++ b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/AiGenerationResultMocks.kt @@ -21,6 +21,7 @@ val mockAiGenerationResult = AiGenerationResult( subSeed = "1504", subSeedStrength = 5598f, denoisingStrength = 1504f, + hidden = false, ) val mockAiGenerationResults = listOf(mockAiGenerationResult) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/GenerationResultEntityMocks.kt b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/GenerationResultEntityMocks.kt index e1ad84b4..fd8fa9e4 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/mocks/GenerationResultEntityMocks.kt +++ b/data/src/test/java/com/shifthackz/aisdv1/data/mocks/GenerationResultEntityMocks.kt @@ -22,6 +22,7 @@ val mockGenerationResultEntity = GenerationResultEntity( subSeed = "1504", subSeedStrength = 5598f, denoisingStrength = 1504f, + hidden = false, ) val mockGenerationResultEntities = listOf(mockGenerationResultEntity) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/GenerationResultRepositoryImplTest.kt b/data/src/test/java/com/shifthackz/aisdv1/data/repository/GenerationResultRepositoryImplTest.kt index 401503b8..a95b5d49 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/GenerationResultRepositoryImplTest.kt +++ b/data/src/test/java/com/shifthackz/aisdv1/data/repository/GenerationResultRepositoryImplTest.kt @@ -299,4 +299,40 @@ class GenerationResultRepositoryImplTest { .await() .assertNotComplete() } + + @Test + fun `given attempt to toggle image visibility, process succeeds, expected boolean value`() { + every { + stubLocalDataSource.queryById(any()) + } returns Single.just(mockAiGenerationResult) + + every { + stubLocalDataSource.insert(any()) + } returns Single.just(5598L) + + repository + .toggleVisibility(5598L) + .test() + .await() + .assertComplete() + } + + @Test + fun `given attempt to toggle image visibility, error occurs, expected boolean value`() { + every { + stubLocalDataSource.queryById(any()) + } returns Single.just(mockAiGenerationResult) + + every { + stubLocalDataSource.insert(any()) + } returns Single.error(stubException) + + repository + .toggleVisibility(5598L) + .test() + .assertError(stubException) + .assertNoValues() + .await() + .assertNotComplete() + } } diff --git a/demo/src/main/java/com/shifthackz/aisdv1/demo/ImageToImageDemoImpl.kt b/demo/src/main/java/com/shifthackz/aisdv1/demo/ImageToImageDemoImpl.kt index 37a15f5a..e46a05ed 100644 --- a/demo/src/main/java/com/shifthackz/aisdv1/demo/ImageToImageDemoImpl.kt +++ b/demo/src/main/java/com/shifthackz/aisdv1/demo/ImageToImageDemoImpl.kt @@ -29,6 +29,7 @@ internal class ImageToImageDemoImpl( subSeed = timeProvider.currentTimeMillis().toString(), subSeedStrength = 0f, denoisingStrength = 0f, + hidden = false, ) override fun getDemoBase64(payload: ImageToImagePayload) = execute(payload) diff --git a/demo/src/main/java/com/shifthackz/aisdv1/demo/TextToImageDemoImpl.kt b/demo/src/main/java/com/shifthackz/aisdv1/demo/TextToImageDemoImpl.kt index ee183fee..68a8f309 100644 --- a/demo/src/main/java/com/shifthackz/aisdv1/demo/TextToImageDemoImpl.kt +++ b/demo/src/main/java/com/shifthackz/aisdv1/demo/TextToImageDemoImpl.kt @@ -29,6 +29,7 @@ internal class TextToImageDemoImpl( subSeed = timeProvider.currentTimeMillis().toString(), subSeedStrength = 0f, denoisingStrength = 0f, + hidden = false, ) override fun getDemoBase64(payload: TextToImagePayload) = execute(payload) diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/di/DomainModule.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/di/DomainModule.kt index f3bdb8e5..0dda2ed4 100755 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/di/DomainModule.kt +++ b/domain/src/main/java/com/shifthackz/aisdv1/domain/di/DomainModule.kt @@ -54,6 +54,8 @@ import com.shifthackz.aisdv1.domain.usecase.gallery.GetGalleryItemsUseCase import com.shifthackz.aisdv1.domain.usecase.gallery.GetGalleryItemsUseCaseImpl import com.shifthackz.aisdv1.domain.usecase.gallery.GetMediaStoreInfoUseCase import com.shifthackz.aisdv1.domain.usecase.gallery.GetMediaStoreInfoUseCaseImpl +import com.shifthackz.aisdv1.domain.usecase.gallery.ToggleImageVisibilityUseCase +import com.shifthackz.aisdv1.domain.usecase.gallery.ToggleImageVisibilityUseCaseImpl import com.shifthackz.aisdv1.domain.usecase.generation.GetGenerationResultPagedUseCase import com.shifthackz.aisdv1.domain.usecase.generation.GetGenerationResultPagedUseCaseImpl import com.shifthackz.aisdv1.domain.usecase.generation.GetGenerationResultUseCase @@ -157,6 +159,7 @@ internal val useCasesModule = module { factoryOf(::ObserveSeverConnectivityUseCaseImpl) bind ObserveSeverConnectivityUseCase::class factoryOf(::ObserveHordeProcessStatusUseCaseImpl) bind ObserveHordeProcessStatusUseCase::class factoryOf(::GetMediaStoreInfoUseCaseImpl) bind GetMediaStoreInfoUseCase::class + factoryOf(::ToggleImageVisibilityUseCaseImpl) bind ToggleImageVisibilityUseCase::class factoryOf(::GetRandomImageUseCaseImpl) bind GetRandomImageUseCase::class factoryOf(::SaveLastResultToCacheUseCaseImpl) bind SaveLastResultToCacheUseCase::class factoryOf(::GetLastResultFromCacheUseCaseImpl) bind GetLastResultFromCacheUseCase::class diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/AiGenerationResult.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/AiGenerationResult.kt index a541bd73..1c8b6a24 100755 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/AiGenerationResult.kt +++ b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/AiGenerationResult.kt @@ -20,6 +20,7 @@ data class AiGenerationResult( val subSeed: String, val subSeedStrength: Float, val denoisingStrength: Float, + val hidden: Boolean, ) { enum class Type(val key: String) { TEXT_TO_IMAGE("txt2img"), diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/GenerationResultRepository.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/GenerationResultRepository.kt index d469da63..de3a1aec 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/GenerationResultRepository.kt +++ b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/GenerationResultRepository.kt @@ -24,4 +24,6 @@ interface GenerationResultRepository { fun deleteByIdList(idList: List): Completable fun deleteAll(): Completable + + fun toggleVisibility(id: Long): Single } diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/debug/DebugInsertBadBase64UseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/debug/DebugInsertBadBase64UseCaseImpl.kt index 3962aca8..738fc23a 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/debug/DebugInsertBadBase64UseCaseImpl.kt +++ b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/debug/DebugInsertBadBase64UseCaseImpl.kt @@ -32,6 +32,7 @@ class DebugInsertBadBase64UseCaseImpl( subSeed = "", subSeedStrength = 0f, denoisingStrength = 0f, + hidden = false, ) } } diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/ToggleImageVisibilityUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/ToggleImageVisibilityUseCase.kt new file mode 100644 index 00000000..fe8f5410 --- /dev/null +++ b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/ToggleImageVisibilityUseCase.kt @@ -0,0 +1,7 @@ +package com.shifthackz.aisdv1.domain.usecase.gallery + +import io.reactivex.rxjava3.core.Single + +interface ToggleImageVisibilityUseCase { + operator fun invoke(id: Long): Single +} diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/ToggleImageVisibilityUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/ToggleImageVisibilityUseCaseImpl.kt new file mode 100644 index 00000000..f7310f9f --- /dev/null +++ b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/gallery/ToggleImageVisibilityUseCaseImpl.kt @@ -0,0 +1,10 @@ +package com.shifthackz.aisdv1.domain.usecase.gallery + +import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository + +internal class ToggleImageVisibilityUseCaseImpl( + private val repository: GenerationResultRepository, +) : ToggleImageVisibilityUseCase { + + override fun invoke(id: Long) = repository.toggleVisibility(id) +} diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/AiGenerationResultMocks.kt b/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/AiGenerationResultMocks.kt index da9d09d9..8d51f0ae 100644 --- a/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/AiGenerationResultMocks.kt +++ b/domain/src/test/java/com/shifthackz/aisdv1/domain/mocks/AiGenerationResultMocks.kt @@ -21,6 +21,7 @@ val mockAiGenerationResult = AiGenerationResult( subSeed = "1504", subSeedStrength = 5598f, denoisingStrength = 1504f, + hidden = false, ) val mockAiGenerationResults = listOf(mockAiGenerationResult) diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/gallery/ToggleImageVisibilityUseCaseImplTest.kt b/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/gallery/ToggleImageVisibilityUseCaseImplTest.kt new file mode 100644 index 00000000..33880cd4 --- /dev/null +++ b/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/gallery/ToggleImageVisibilityUseCaseImplTest.kt @@ -0,0 +1,42 @@ +package com.shifthackz.aisdv1.domain.usecase.gallery + +import com.nhaarman.mockitokotlin2.any +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever +import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository +import io.reactivex.rxjava3.core.Single +import org.junit.Test + +class ToggleImageVisibilityUseCaseImplTest { + + private val stubRepository = mock() + + private val useCase = ToggleImageVisibilityUseCaseImpl(stubRepository) + + @Test + fun `given repository returned value, expected valid boolean value`() { + whenever(stubRepository.toggleVisibility(any())) + .thenReturn(Single.just(true)) + + useCase(5598L) + .test() + .await() + .assertValue(true) + .assertComplete() + } + + @Test + fun `given repository thrown exception, expected error value`() { + val stubException = Throwable("Error communicating with MediaStore.") + + whenever(stubRepository.toggleVisibility(any())) + .thenReturn(Single.error(stubException)) + + useCase(5598L) + .test() + .await() + .assertError(stubException) + .assertNoValues() + .assertNotComplete() + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/di/ViewModelModule.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/di/ViewModelModule.kt index d0d88d68..0a8cebce 100755 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/di/ViewModelModule.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/di/ViewModelModule.kt @@ -97,6 +97,7 @@ val viewModelModule = module { getGenerationResultUseCase = get(), getLastResultFromCacheUseCase = get(), deleteGalleryItemUseCase = get(), + toggleImageVisibilityUseCase = get(), galleryDetailBitmapExporter = get(), base64ToBitmapConverter = get(), schedulersProvider = get(), diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailIntent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailIntent.kt index 419a444a..a7e03168 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailIntent.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailIntent.kt @@ -22,6 +22,8 @@ sealed interface GalleryDetailIntent : MviIntent { Request, Confirm } + data object ToggleVisibility : GalleryDetailIntent + data object Report : GalleryDetailIntent data object DismissDialog : GalleryDetailIntent diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailMocks.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailMocks.kt index 5bd77389..96f47051 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailMocks.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailMocks.kt @@ -27,4 +27,5 @@ val mockGalleryDetailTxt2Img = GalleryDetailState.Content( subSeed = "0001".asUiText(), subSeedStrength = "".asUiText(), denoisingStrength = "".asUiText(), + hidden = false, ) diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailScreen.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailScreen.kt index c3cc1eeb..80156fe7 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailScreen.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailScreen.kt @@ -23,6 +23,8 @@ import androidx.compose.material.icons.filled.ContentCopy import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Report import androidx.compose.material.icons.filled.Share +import androidx.compose.material.icons.filled.Visibility +import androidx.compose.material.icons.filled.VisibilityOff import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -235,6 +237,18 @@ private fun GalleryDetailNavigationBar( tint = LocalContentColor.current, ) } + IconButton( + onClick = { processIntent(GalleryDetailIntent.ToggleVisibility) }, + ) { + Icon( + imageVector = if (state.hidden) { + Icons.Default.VisibilityOff + } else { + Icons.Default.Visibility + }, + contentDescription = "Toggle visibility", + ) + } IconButton( onClick = { processIntent(GalleryDetailIntent.Export.Params) }, ) { @@ -297,6 +311,7 @@ private fun GalleryDetailContentState( GalleryDetailState.Tab.IMAGE -> ZoomableImage( modifier = Modifier.fillMaxSize(), source = ZoomableImageSource.Bmp(state.bitmap), + hideImage = state.hidden, ) GalleryDetailState.Tab.ORIGINAL -> state.inputBitmap?.let { bmp -> diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailState.kt index e917e7e2..fbf161f3 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailState.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/gallery/detail/GalleryDetailState.kt @@ -48,6 +48,7 @@ sealed interface GalleryDetailState : MviState { val subSeed: UiText, val subSeedStrength: UiText, val denoisingStrength: UiText, + val hidden: Boolean, ) : GalleryDetailState fun withTab(tab: Tab): GalleryDetailState = when (this) { @@ -60,6 +61,11 @@ sealed interface GalleryDetailState : MviState { is Loading -> copy(screenModal = dialog) } + fun withHiddenState(value: Boolean) = when (this) { + is Content -> copy(hidden = value) + is Loading -> this + } + enum class Tab( @StringRes val label: Int, @DrawableRes val iconRes: Int, @@ -101,5 +107,6 @@ fun Triple