Skip to content

Gallery UI/UX improvements #233

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.shifthackz.aisdv1.core.extensions

import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.StartOffset
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.debugInspectorInfo

fun Modifier.shake(
enabled: Boolean,
animationDurationMillis: Int = 167,
animationStartOffset: Int = 0,
) = this.composed(
factory = {
val infiniteTransition = rememberInfiniteTransition(label = "shake")
val scaleInfinite by infiniteTransition.animateFloat(
initialValue = 1f,
targetValue = .99f,
animationSpec = infiniteRepeatable(
animation = tween(animationDurationMillis, easing = LinearEasing),
repeatMode = RepeatMode.Reverse,
initialStartOffset = StartOffset(animationStartOffset),
),
label = "shake",
)
val rotation by infiniteTransition.animateFloat(
initialValue = -1.5f,
targetValue = 1.5f,
animationSpec = infiniteRepeatable(
animation = tween(animationDurationMillis, easing = LinearEasing),
repeatMode = RepeatMode.Reverse,
initialStartOffset = StartOffset(animationStartOffset),
),
label = "shake",
)

Modifier.graphicsLayer {
scaleX = if (enabled) scaleInfinite else 1f
scaleY = if (enabled) scaleInfinite else 1f
rotationZ = if (enabled) rotation else 0f
}
},
inspectorInfo = debugInspectorInfo {
name = "shake"
properties["enabled"] = enabled
},
)
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@ internal class GenerationResultLocalDataSource(
.queryById(id)
.map(GenerationResultEntity::mapEntityToDomain)

override fun queryByIdList(idList: List<Long>) = dao
.queryByIdList(idList)
.map(List<GenerationResultEntity>::mapEntityToDomain)

override fun deleteById(id: Long) = dao.deleteById(id)

override fun deleteByIdList(idList: List<Long>) = dao.deleteByIdList(idList)

override fun deleteAll() = dao.deleteAll()
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,15 @@ internal class GenerationResultRepositoryImpl(

override fun getById(id: Long) = localDataSource.queryById(id)

override fun getByIds(idList: List<Long>) = localDataSource.queryByIdList(idList)

override fun insert(result: AiGenerationResult) = localDataSource
.insert(result)
.flatMap { id -> exportToMediaStore(result).andThen(Single.just(id)) }

override fun deleteById(id: Long) = localDataSource.deleteById(id)

override fun deleteByIdList(idList: List<Long>) = localDataSource.deleteByIdList(idList)

override fun deleteAll() = localDataSource.deleteAll()
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ sealed interface GenerationResultDataSource {
fun queryAll(): Single<List<AiGenerationResult>>
fun queryPage(limit: Int, offset: Int): Single<List<AiGenerationResult>>
fun queryById(id: Long): Single<AiGenerationResult>
fun queryByIdList(idList: List<Long>): Single<List<AiGenerationResult>>
fun deleteById(id: Long): Completable
fun deleteByIdList(idList: List<Long>): Completable
fun deleteAll(): Completable
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,16 @@ import com.shifthackz.aisdv1.domain.usecase.downloadable.GetLocalAiModelsUseCase
import com.shifthackz.aisdv1.domain.usecase.downloadable.GetLocalAiModelsUseCaseImpl
import com.shifthackz.aisdv1.domain.usecase.downloadable.ObserveLocalAiModelsUseCase
import com.shifthackz.aisdv1.domain.usecase.downloadable.ObserveLocalAiModelsUseCaseImpl
import com.shifthackz.aisdv1.domain.usecase.gallery.DeleteAllGalleryUseCase
import com.shifthackz.aisdv1.domain.usecase.gallery.DeleteAllGalleryUseCaseImpl
import com.shifthackz.aisdv1.domain.usecase.gallery.DeleteGalleryItemUseCase
import com.shifthackz.aisdv1.domain.usecase.gallery.DeleteGalleryItemUseCaseImpl
import com.shifthackz.aisdv1.domain.usecase.gallery.DeleteGalleryItemsUseCase
import com.shifthackz.aisdv1.domain.usecase.gallery.DeleteGalleryItemsUseCaseImpl
import com.shifthackz.aisdv1.domain.usecase.gallery.GetAllGalleryUseCase
import com.shifthackz.aisdv1.domain.usecase.gallery.GetAllGalleryUseCaseImpl
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.generation.GetGenerationResultPagedUseCase
Expand Down Expand Up @@ -123,8 +129,11 @@ internal val useCasesModule = module {
factoryOf(::SelectStableDiffusionModelUseCaseImpl) bind SelectStableDiffusionModelUseCase::class
factoryOf(::GetGenerationResultPagedUseCaseImpl) bind GetGenerationResultPagedUseCase::class
factoryOf(::GetAllGalleryUseCaseImpl) bind GetAllGalleryUseCase::class
factoryOf(::GetGalleryItemsUseCaseImpl) bind GetGalleryItemsUseCase::class
factoryOf(::GetGenerationResultUseCaseImpl) bind GetGenerationResultUseCase::class
factoryOf(::DeleteGalleryItemUseCaseImpl) bind DeleteGalleryItemUseCase::class
factoryOf(::DeleteGalleryItemsUseCaseImpl) bind DeleteGalleryItemsUseCase::class
factoryOf(::DeleteAllGalleryUseCaseImpl) bind DeleteAllGalleryUseCase::class
factoryOf(::GetStableDiffusionSamplersUseCaseImpl) bind GetStableDiffusionSamplersUseCase::class
factoryOf(::FetchAndGetLorasUseCaseImpl) bind FetchAndGetLorasUseCase::class
factoryOf(::FetchAndGetHyperNetworksUseCaseImpl) bind FetchAndGetHyperNetworksUseCase::class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ interface GenerationResultRepository {

fun getById(id: Long): Single<AiGenerationResult>

fun getByIds(idList: List<Long>): Single<List<AiGenerationResult>>

fun insert(result: AiGenerationResult): Single<Long>

fun deleteById(id: Long): Completable

fun deleteByIdList(idList: List<Long>): Completable

fun deleteAll(): Completable
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.shifthackz.aisdv1.domain.usecase.gallery

import io.reactivex.rxjava3.core.Completable

interface DeleteAllGalleryUseCase {
operator fun invoke(): Completable
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.shifthackz.aisdv1.domain.usecase.gallery

import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository
import io.reactivex.rxjava3.core.Completable

internal class DeleteAllGalleryUseCaseImpl(
private val generationResultRepository: GenerationResultRepository,
) : DeleteAllGalleryUseCase {

override fun invoke(): Completable = generationResultRepository.deleteAll()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.shifthackz.aisdv1.domain.usecase.gallery

import io.reactivex.rxjava3.core.Completable

interface DeleteGalleryItemsUseCase {
operator fun invoke(ids: List<Long>): Completable
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.shifthackz.aisdv1.domain.usecase.gallery

import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository
import io.reactivex.rxjava3.core.Completable

internal class DeleteGalleryItemsUseCaseImpl(
private val generationResultRepository: GenerationResultRepository,
) : DeleteGalleryItemsUseCase {

override fun invoke(ids: List<Long>): Completable = generationResultRepository.deleteByIdList(ids)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.shifthackz.aisdv1.domain.usecase.gallery

import com.shifthackz.aisdv1.domain.entity.AiGenerationResult
import io.reactivex.rxjava3.core.Single

interface GetGalleryItemsUseCase {
operator fun invoke(ids: List<Long>): Single<List<AiGenerationResult>>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.shifthackz.aisdv1.domain.usecase.gallery

import com.shifthackz.aisdv1.domain.repository.GenerationResultRepository

internal class GetGalleryItemsUseCaseImpl(
private val generationResultRepository: GenerationResultRepository,
) : GetGalleryItemsUseCase {

override fun invoke(ids: List<Long>) = generationResultRepository.getByIds(ids)
}
Original file line number Diff line number Diff line change
Expand Up @@ -175,20 +175,48 @@ fun ModalRenderer(
)
}

Modal.DeleteImageConfirm -> DecisionInteractiveDialog(
title = R.string.interaction_delete_generation_title.asUiText(),
text = R.string.interaction_delete_generation_sub_title.asUiText(),
is Modal.DeleteImageConfirm -> DecisionInteractiveDialog(
title = when {
screenModal.isAll -> R.string.interaction_delete_all_title
screenModal.isMultiple -> R.string.interaction_delete_selection_title
else -> R.string.interaction_delete_generation_title
}.asUiText(),
text = when {
screenModal.isAll -> R.string.interaction_delete_all_sub_title
screenModal.isMultiple -> R.string.interaction_delete_selection_sub_title
else -> R.string.interaction_delete_generation_sub_title
}.asUiText(),
confirmActionResId = R.string.yes,
dismissActionResId = R.string.no,
onConfirmAction = { processIntent(GalleryDetailIntent.Delete.Confirm) },
onConfirmAction = {
val intent = if (screenModal.isAll) {
GalleryIntent.Delete.All.Confirm
} else if (screenModal.isMultiple) {
GalleryIntent.Delete.Selection.Confirm
} else {
GalleryDetailIntent.Delete.Confirm
}
processIntent(intent)
},
onDismissRequest = dismiss,
)

Modal.ConfirmExport -> DecisionInteractiveDialog(
is Modal.ConfirmExport -> DecisionInteractiveDialog(
title = R.string.interaction_export_title.asUiText(),
text = R.string.interaction_export_sub_title.asUiText(),
text = if (screenModal.exportAll) {
R.string.interaction_export_sub_title
} else {
R.string.interaction_export_sub_title_selection
}.asUiText(),
confirmActionResId = R.string.action_export,
onConfirmAction = { processIntent(GalleryIntent.Export.Confirm) },
onConfirmAction = {
val intent = if (screenModal.exportAll) {
GalleryIntent.Export.All.Confirm
} else {
GalleryIntent.Export.Selection.Confirm
}
processIntent(intent)
},
onDismissRequest = dismiss,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ sealed interface Modal {

data object ClearAppCache : Modal

data object DeleteImageConfirm : Modal
data class DeleteImageConfirm(
val isAll: Boolean,
val isMultiple: Boolean,
) : Modal

data object ConfirmExport : Modal
data class ConfirmExport(val exportAll: Boolean) : Modal

data object ExportInProgress : Modal

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ class GalleryDetailViewModel(
emitEffect(GalleryDetailEffect.ShareClipBoard(intent.content.toString()))
}

GalleryDetailIntent.Delete.Request -> setActiveModal(Modal.DeleteImageConfirm)
GalleryDetailIntent.Delete.Request -> setActiveModal(
Modal.DeleteImageConfirm(false, isMultiple = false)
)

GalleryDetailIntent.Delete.Confirm -> {
setActiveModal(Modal.None)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import com.shifthackz.android.core.mvi.MviEffect
import java.io.File

sealed interface GalleryEffect : MviEffect {

data object Refresh : GalleryEffect

data class Share(val zipFile: File) : GalleryEffect

data class OpenUri(val uri: Uri) : GalleryEffect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,33 @@ import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter.Input
import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter.Output
import com.shifthackz.aisdv1.domain.entity.AiGenerationResult
import com.shifthackz.aisdv1.domain.usecase.gallery.GetAllGalleryUseCase
import com.shifthackz.aisdv1.domain.usecase.gallery.GetGalleryItemsUseCase
import com.shifthackz.aisdv1.presentation.utils.FileSavableExporter
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.core.Single
import java.io.File

class GalleryExporter(
override val fileProviderDescriptor: FileProviderDescriptor,
private val getGalleryItemsUseCase: GetGalleryItemsUseCase,
private val getAllGalleryUseCase: GetAllGalleryUseCase,
private val base64ToBitmapConverter: Base64ToBitmapConverter,
private val schedulersProvider: SchedulersProvider,
) : FileSavableExporter.BmpToFile, FileSavableExporter.FilesToZip {

operator fun invoke(): Single<File> = getAllGalleryUseCase()
.subscribeOn(schedulersProvider.io)
.flatMapObservable { Observable.fromIterable(it) }
.map { aiDomain -> aiDomain to Input(aiDomain.image) }
.flatMapSingle { (aiDomain, input) ->
base64ToBitmapConverter(input).map { out -> aiDomain to out }
}
.flatMapSingle(::saveBitmapToFileImpl)
.toList()
.flatMap(::saveFilesToZip)
operator fun invoke(ids: List<Long>? = null): Single<File> {
val chain = ids?.let(getGalleryItemsUseCase::invoke) ?: getAllGalleryUseCase()
return chain
.subscribeOn(schedulersProvider.io)
.flatMapObservable { Observable.fromIterable(it) }
.map { aiDomain -> aiDomain to Input(aiDomain.image) }
.flatMapSingle { (aiDomain, input) ->
base64ToBitmapConverter(input).map { out -> aiDomain to out }
}
.flatMapSingle(::saveBitmapToFileImpl)
.toList()
.flatMap(::saveFilesToZip)
}

private fun saveBitmapToFileImpl(data: Pair<AiGenerationResult, Output>) =
saveBitmapToFile(data.first.hashCode().toString(), data.second.bitmap)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,30 @@ import com.shifthackz.android.core.mvi.MviIntent

sealed interface GalleryIntent : MviIntent {

enum class Export : GalleryIntent {
Request, Confirm;
sealed interface Export : GalleryIntent {

enum class All : Export {
Request, Confirm;
}

enum class Selection : Export {
Request, Confirm;
}
}

sealed interface Delete : GalleryIntent {

enum class All : Export {
Request, Confirm;
}

enum class Selection : Delete {
Request, Confirm;
}
}

enum class Dropdown : GalleryIntent {
Toggle, Show, Close;
}

data object DismissDialog : GalleryIntent
Expand All @@ -17,4 +39,10 @@ sealed interface GalleryIntent : MviIntent {
data class OpenMediaStoreFolder(val uri: Uri) : GalleryIntent

data class Drawer(val intent: DrawerIntent) : GalleryIntent

data class ChangeSelectionMode(val flag: Boolean) : GalleryIntent

data object UnselectAll : GalleryIntent

data class ToggleItemSelection(val id: Long) : GalleryIntent
}
Loading
Loading