diff --git a/core/localization/src/main/res/values/strings.xml b/core/localization/src/main/res/values/strings.xml
index 8263cc8f8..c767358e6 100755
--- a/core/localization/src/main/res/values/strings.xml
+++ b/core/localization/src/main/res/values/strings.xml
@@ -186,6 +186,7 @@
Textual Inversion
Inversion
Edit tag
+ Select source
You have %1$s photos saved in Download/SDAI
Created
diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/DownloadableModelRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/DownloadableModelRepositoryImpl.kt
index 6f84189c3..0e46f816c 100644
--- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/DownloadableModelRepositoryImpl.kt
+++ b/data/src/main/java/com/shifthackz/aisdv1/data/repository/DownloadableModelRepositoryImpl.kt
@@ -13,11 +13,7 @@ internal class DownloadableModelRepositoryImpl(
private val buildInfoProvider: BuildInfoProvider,
) : DownloadableModelRepository {
- override fun download(id: String) = localDataSource
- .getById(id)
- .flatMapObservable { model ->
- remoteDataSource.download(id, model.sources.firstOrNull() ?: "")
- }
+ override fun download(id: String, url: String) = remoteDataSource.download(id, url)
override fun delete(id: String) = localDataSource.delete(id)
diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/DownloadableModelRepositoryImplTest.kt b/data/src/test/java/com/shifthackz/aisdv1/data/repository/DownloadableModelRepositoryImplTest.kt
index 379bc3c59..b246a019f 100644
--- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/DownloadableModelRepositoryImplTest.kt
+++ b/data/src/test/java/com/shifthackz/aisdv1/data/repository/DownloadableModelRepositoryImplTest.kt
@@ -219,21 +219,6 @@ class DownloadableModelRepositoryImplTest {
.assertNotComplete()
}
- @Test
- fun `given attempt to download model, local data source has no such model, expected error value`() {
- every {
- stubLocalDataSource.getById(any())
- } returns Single.error(stubException)
-
- repository
- .download("5598")
- .test()
- .assertNoValues()
- .assertError(stubException)
- .await()
- .assertNotComplete()
- }
-
@Test
fun `given attempt to download model, local data source has such model, download succeeds, expected unknown, downloading, complete values`() {
every {
@@ -241,7 +226,7 @@ class DownloadableModelRepositoryImplTest {
} returns Single.just(mockLocalAiModel)
val stubObserver = repository
- .download("5598")
+ .download("5598", "https://moroz.cc/stub.zip")
.test()
stubDownloadState.onNext(DownloadState.Unknown)
@@ -276,7 +261,7 @@ class DownloadableModelRepositoryImplTest {
} returns Single.just(mockLocalAiModel)
val stubObserver = repository
- .download("5598")
+ .download("5598", "https://moroz.cc/stub.zip")
.test()
stubDownloadState.onNext(DownloadState.Unknown)
@@ -309,7 +294,7 @@ class DownloadableModelRepositoryImplTest {
} returns Observable.error(stubException)
repository
- .download("5598")
+ .download("5598", "https://moroz.cc/stub.zip")
.test()
.assertError(stubException)
.assertNoValues()
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 0dda2ed46..3a5f59eb0 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
@@ -38,6 +38,8 @@ import com.shifthackz.aisdv1.domain.usecase.downloadable.DownloadModelUseCase
import com.shifthackz.aisdv1.domain.usecase.downloadable.DownloadModelUseCaseImpl
import com.shifthackz.aisdv1.domain.usecase.downloadable.GetLocalMediaPipeModelsUseCase
import com.shifthackz.aisdv1.domain.usecase.downloadable.GetLocalMediaPipeModelsUseCaseImpl
+import com.shifthackz.aisdv1.domain.usecase.downloadable.GetLocalModelUseCase
+import com.shifthackz.aisdv1.domain.usecase.downloadable.GetLocalModelUseCaseImpl
import com.shifthackz.aisdv1.domain.usecase.downloadable.GetLocalOnnxModelsUseCase
import com.shifthackz.aisdv1.domain.usecase.downloadable.GetLocalOnnxModelsUseCaseImpl
import com.shifthackz.aisdv1.domain.usecase.downloadable.ObserveLocalOnnxModelsUseCase
@@ -185,6 +187,7 @@ internal val useCasesModule = module {
factoryOf(::FetchAndGetStabilityAiEnginesUseCaseImpl) bind FetchAndGetStabilityAiEnginesUseCase::class
factoryOf(::FetchAndGetSupportersUseCaseImpl) bind FetchAndGetSupportersUseCase::class
factoryOf(::SendReportUseCaseImpl) bind SendReportUseCase::class
+ factoryOf(::GetLocalModelUseCaseImpl) bind GetLocalModelUseCase::class
}
internal val interActorsModule = module {
diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/DownloadableModelRepository.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/DownloadableModelRepository.kt
index 79efed665..9d124726e 100644
--- a/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/DownloadableModelRepository.kt
+++ b/domain/src/main/java/com/shifthackz/aisdv1/domain/repository/DownloadableModelRepository.kt
@@ -8,7 +8,7 @@ import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.core.Single
interface DownloadableModelRepository {
- fun download(id: String): Observable
+ fun download(id: String, url: String): Observable
fun delete(id: String): Completable
fun getAllOnnx(): Single>
fun getAllMediaPipe(): Single>
diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DownloadModelUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DownloadModelUseCase.kt
index 331fa2438..a956f3c6c 100644
--- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DownloadModelUseCase.kt
+++ b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DownloadModelUseCase.kt
@@ -4,5 +4,5 @@ import com.shifthackz.aisdv1.domain.entity.DownloadState
import io.reactivex.rxjava3.core.Observable
interface DownloadModelUseCase {
- operator fun invoke(id: String): Observable
+ operator fun invoke(id: String, url: String): Observable
}
diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DownloadModelUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DownloadModelUseCaseImpl.kt
index 6c5cf2925..d9d326332 100644
--- a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DownloadModelUseCaseImpl.kt
+++ b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DownloadModelUseCaseImpl.kt
@@ -6,5 +6,5 @@ internal class DownloadModelUseCaseImpl(
private val downloadableModelRepository: DownloadableModelRepository,
) : DownloadModelUseCase {
- override fun invoke(id: String) = downloadableModelRepository.download(id)
+ override fun invoke(id: String, url: String) = downloadableModelRepository.download(id, url)
}
diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalModelUseCase.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalModelUseCase.kt
new file mode 100644
index 000000000..4bf4b43d3
--- /dev/null
+++ b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalModelUseCase.kt
@@ -0,0 +1,8 @@
+package com.shifthackz.aisdv1.domain.usecase.downloadable
+
+import com.shifthackz.aisdv1.domain.entity.LocalAiModel
+import io.reactivex.rxjava3.core.Single
+
+interface GetLocalModelUseCase {
+ operator fun invoke(id: String): Single
+}
diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalModelUseCaseImpl.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalModelUseCaseImpl.kt
new file mode 100644
index 000000000..3b6c6188f
--- /dev/null
+++ b/domain/src/main/java/com/shifthackz/aisdv1/domain/usecase/downloadable/GetLocalModelUseCaseImpl.kt
@@ -0,0 +1,12 @@
+package com.shifthackz.aisdv1.domain.usecase.downloadable
+
+import com.shifthackz.aisdv1.domain.datasource.DownloadableModelDataSource
+import com.shifthackz.aisdv1.domain.entity.LocalAiModel
+import io.reactivex.rxjava3.core.Single
+
+internal class GetLocalModelUseCaseImpl(
+ private val localDataSource: DownloadableModelDataSource.Local,
+) : GetLocalModelUseCase {
+
+ override fun invoke(id: String): Single = localDataSource.getById(id)
+}
diff --git a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DownloadModelUseCaseImplTest.kt b/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DownloadModelUseCaseImplTest.kt
index 669200f4a..f5c463a75 100644
--- a/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DownloadModelUseCaseImplTest.kt
+++ b/domain/src/test/java/com/shifthackz/aisdv1/domain/usecase/downloadable/DownloadModelUseCaseImplTest.kt
@@ -23,13 +23,13 @@ class DownloadModelUseCaseImplTest {
@Before
fun initialize() {
- whenever(stubRepository.download(any()))
+ whenever(stubRepository.download(any(), any()))
.thenReturn(stubDownloadStatus)
}
@Test
fun `given download running, then finishes successfully, expected final state is Complete`() {
- val stubObserver = useCase("5598").test()
+ val stubObserver = useCase("5598", "https://moroz.cc/stub.zip").test()
stubDownloadStatus.onNext(DownloadState.Unknown)
@@ -58,7 +58,7 @@ class DownloadModelUseCaseImplTest {
@Test
fun `given download running, then fails, expected final state is Error`() {
- val stubObserver = useCase("5598").test()
+ val stubObserver = useCase("5598", "https://moroz.cc/stub.zip").test()
stubDownloadStatus.onNext(DownloadState.Unknown)
@@ -87,7 +87,7 @@ class DownloadModelUseCaseImplTest {
@Test
fun `given download running, then fails, then user restarts download, then completes, expected state Error on 1st try, final state is Complete`() {
- val stubObserver = useCase("5598").test()
+ val stubObserver = useCase("5598", "https://moroz.cc/stub.zip").test()
stubDownloadStatus.onNext(DownloadState.Unknown)
@@ -140,10 +140,10 @@ class DownloadModelUseCaseImplTest {
@Test
fun `given observable terminated with unexpected error, expected error value`() {
- whenever(stubRepository.download(any()))
+ whenever(stubRepository.download(any(), any()))
.thenReturn(Observable.error(stubTerminateException))
- useCase("5598")
+ useCase("5598", "https://moroz.cc/stub.zip")
.test()
.assertError(stubTerminateException)
.await()
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index c9b510532..184c54499 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,6 +1,6 @@
[versions]
-versionName = "0.6.7"
-versionCode = "188"
+versionName = "0.6.8"
+versionCode = "190"
targetSdk = "34"
compileSdk = "35"
minSdk = "24"
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 5833ac9e3..054bdb337 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
@@ -1,6 +1,7 @@
package com.shifthackz.aisdv1.presentation.di
import com.shifthackz.aisdv1.presentation.activity.AiStableDiffusionViewModel
+import com.shifthackz.aisdv1.presentation.modal.download.DownloadDialogViewModel
import com.shifthackz.aisdv1.presentation.modal.embedding.EmbeddingViewModel
import com.shifthackz.aisdv1.presentation.modal.extras.ExtrasViewModel
import com.shifthackz.aisdv1.presentation.modal.history.InputHistoryViewModel
@@ -53,6 +54,7 @@ val viewModelModule = module {
viewModelOf(::DonateViewModel)
viewModelOf(::BackgroundWorkViewModel)
viewModelOf(::LoggerViewModel)
+ viewModelOf(::DownloadDialogViewModel)
viewModel { parameters ->
OnBoardingViewModel(
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ModalRenderer.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ModalRenderer.kt
index eaabe5d90..a25ea9bbd 100644
--- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ModalRenderer.kt
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ModalRenderer.kt
@@ -23,6 +23,7 @@ import com.shifthackz.aisdv1.presentation.core.GenerationFormUpdateEvent
import com.shifthackz.aisdv1.presentation.core.GenerationMviIntent
import com.shifthackz.aisdv1.presentation.core.ImageToImageIntent
import com.shifthackz.aisdv1.presentation.modal.crop.CropImageModal
+import com.shifthackz.aisdv1.presentation.modal.download.DownloadDialog
import com.shifthackz.aisdv1.presentation.modal.embedding.EmbeddingScreen
import com.shifthackz.aisdv1.presentation.modal.extras.ExtrasScreen
import com.shifthackz.aisdv1.presentation.modal.grid.GridBottomSheet
@@ -367,5 +368,14 @@ fun ModalRenderer(
}
)
}
+
+ is Modal.SelectDownloadSource -> DownloadDialog(
+ modelId = screenModal.modelId,
+ onDismissRequest = dismiss,
+ onDownloadSourceSelected = { url ->
+ processIntent(ServerSetupIntent.LocalModel.DownloadConfirm(screenModal.modelId, url))
+ dismiss()
+ }
+ )
}
}
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialog.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialog.kt
new file mode 100644
index 000000000..31477a1fd
--- /dev/null
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialog.kt
@@ -0,0 +1,215 @@
+package com.shifthackz.aisdv1.presentation.modal.download
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material.icons.filled.Download
+import androidx.compose.material.icons.filled.Link
+import androidx.compose.material3.Button
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
+import com.shifthackz.aisdv1.presentation.R
+import com.shifthackz.android.core.mvi.MviComponent
+import org.koin.androidx.compose.koinViewModel
+import com.shifthackz.aisdv1.core.localization.R as LocalizationR
+
+private const val GITHUB_WEB_RESOURCE = "github.com"
+private const val SDAI_WEB_RESOURCE = "share.moroz.cc"
+
+@Composable
+fun DownloadDialog(
+ modifier: Modifier = Modifier,
+ modelId: String,
+ onDismissRequest: () -> Unit,
+ onDownloadSourceSelected: (url: String) -> Unit,
+) {
+ MviComponent(
+ viewModel = koinViewModel().apply {
+ processIntent(DownloadDialogIntent.LoadModelData(modelId))
+ },
+ processEffect = { effect ->
+ when (effect) {
+ DownloadDialogEffect.Close -> onDismissRequest()
+ is DownloadDialogEffect.StartDownload -> onDownloadSourceSelected(effect.url)
+ }
+ }
+ ) { state, processIntent ->
+ ScreenContent(
+ modifier = modifier,
+ state = state,
+ processIntent = processIntent,
+ )
+ }
+}
+
+@Composable
+@Preview
+private fun ScreenContent(
+ modifier: Modifier = Modifier,
+ state: DownloadDialogState = DownloadDialogState(),
+ processIntent: (DownloadDialogIntent) -> Unit = {},
+) {
+ Dialog(
+ onDismissRequest = {},
+ properties = DialogProperties(
+ dismissOnClickOutside = false,
+ dismissOnBackPress = false,
+ ),
+ ) {
+ Surface(
+ modifier = modifier.fillMaxHeight(0.38f),
+ shape = RoundedCornerShape(16.dp),
+ color = MaterialTheme.colorScheme.background,
+ ) {
+ Scaffold(
+ topBar = {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Spacer(modifier = Modifier.weight(1f))
+ Spacer(modifier = Modifier.width(40.dp))
+ Text(
+ text = stringResource(LocalizationR.string.title_select_download_source),
+ style = MaterialTheme.typography.bodyLarge,
+ textAlign = TextAlign.Center,
+ fontWeight = FontWeight.Bold,
+ )
+ Spacer(modifier = Modifier.weight(1f))
+ IconButton(
+ onClick = { processIntent(DownloadDialogIntent.Close) },
+ ) {
+ Icon(
+ imageVector = Icons.Default.Close,
+ contentDescription = null,
+ )
+ }
+ }
+ },
+ bottomBar = {
+ Box(
+ modifier = Modifier.fillMaxWidth(),
+ contentAlignment = Alignment.Center,
+ ) {
+ Button(
+ modifier = Modifier
+ .padding(bottom = 12.dp)
+ .fillMaxWidth(0.65f),
+ onClick = { processIntent(DownloadDialogIntent.StartDownload) },
+ ) {
+ Icon(
+ modifier = Modifier.padding(end = 8.dp),
+ imageVector = Icons.Default.Download,
+ contentDescription = null,
+ )
+ Text(
+ text = stringResource(LocalizationR.string.download),
+ color = LocalContentColor.current,
+ )
+ }
+ }
+ },
+ ) { paddingValues ->
+ LazyColumn(
+ modifier = Modifier.padding(paddingValues),
+ ) {
+ items(
+ count = state.sources.size,
+ key = { index -> state.sources[index] }
+ ) { index ->
+ val (url, selected) = state.sources[index]
+ Row(
+ modifier = Modifier
+ .padding(vertical = 8.dp, horizontal = 16.dp)
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(16.dp))
+ .background(color = MaterialTheme.colorScheme.surfaceTint.copy(alpha = 0.8f))
+ .defaultMinSize(minHeight = 50.dp)
+ .border(
+ width = 2.dp,
+ shape = RoundedCornerShape(16.dp),
+ color = if (selected) {
+ MaterialTheme.colorScheme.primary
+ } else {
+ Color.Transparent
+ },
+ )
+ .clickable { processIntent(DownloadDialogIntent.SelectSource(url)) },
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ val webResource = remember {
+ runCatching {
+ url.split("://")[1].split("/").first()
+ }.getOrElse { "" }
+ }
+
+ val iconModifier = Modifier
+ .size(42.dp)
+ .padding(horizontal = 8.dp)
+
+ when (webResource) {
+ GITHUB_WEB_RESOURCE -> Icon(
+ modifier = iconModifier,
+ painter = painterResource(R.drawable.ic_github),
+ contentDescription = null,
+ )
+
+ SDAI_WEB_RESOURCE -> Image(
+ modifier = iconModifier,
+ painter = painterResource(R.drawable.ic_sdai_logo),
+ contentDescription = null,
+ )
+
+ else -> Icon(
+ modifier = iconModifier,
+ imageVector = Icons.Default.Link,
+ contentDescription = null,
+ )
+ }
+
+ Text(
+ text = webResource,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialogEffect.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialogEffect.kt
new file mode 100644
index 000000000..d672f7832
--- /dev/null
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialogEffect.kt
@@ -0,0 +1,10 @@
+package com.shifthackz.aisdv1.presentation.modal.download
+
+import com.shifthackz.android.core.mvi.MviEffect
+
+sealed interface DownloadDialogEffect : MviEffect {
+
+ data object Close : DownloadDialogEffect
+
+ data class StartDownload(val url: String) : DownloadDialogEffect
+}
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialogIntent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialogIntent.kt
new file mode 100644
index 000000000..6940e12b5
--- /dev/null
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialogIntent.kt
@@ -0,0 +1,14 @@
+package com.shifthackz.aisdv1.presentation.modal.download
+
+import com.shifthackz.android.core.mvi.MviIntent
+
+sealed interface DownloadDialogIntent : MviIntent {
+
+ data class LoadModelData(val id: String): DownloadDialogIntent
+
+ data class SelectSource(val url: String) : DownloadDialogIntent
+
+ data object Close : DownloadDialogIntent
+
+ data object StartDownload : DownloadDialogIntent
+}
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialogState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialogState.kt
new file mode 100644
index 000000000..70d3788ad
--- /dev/null
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialogState.kt
@@ -0,0 +1,13 @@
+package com.shifthackz.aisdv1.presentation.modal.download
+
+import androidx.compose.runtime.Immutable
+import com.shifthackz.android.core.mvi.MviState
+
+@Immutable
+data class DownloadDialogState(
+ val sources: List> = emptyList(),
+) : MviState {
+
+ val selectedUrl: String
+ get() = sources.find { (_, selected) -> selected }?.first ?: ""
+}
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialogViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialogViewModel.kt
new file mode 100644
index 000000000..2d648714c
--- /dev/null
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/download/DownloadDialogViewModel.kt
@@ -0,0 +1,42 @@
+package com.shifthackz.aisdv1.presentation.modal.download
+
+import com.shifthackz.aisdv1.core.common.log.errorLog
+import com.shifthackz.aisdv1.core.common.schedulers.DispatchersProvider
+import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider
+import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread
+import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel
+import com.shifthackz.aisdv1.domain.usecase.downloadable.GetLocalModelUseCase
+import io.reactivex.rxjava3.kotlin.subscribeBy
+
+class DownloadDialogViewModel(
+ private val getLocalModelUseCase: GetLocalModelUseCase,
+ private val schedulersProvider: SchedulersProvider,
+ dispatchersProvider: DispatchersProvider,
+) : MviRxViewModel() {
+
+ override val initialState = DownloadDialogState()
+
+ override val effectDispatcher = dispatchersProvider.immediate
+
+ override fun processIntent(intent: DownloadDialogIntent) {
+ when (intent) {
+ is DownloadDialogIntent.LoadModelData -> !getLocalModelUseCase(intent.id)
+ .subscribeOnMainThread(schedulersProvider)
+ .subscribeBy(::errorLog) { model ->
+ updateState {
+ it.copy(sources = model.sources.mapIndexed { i, url -> url to (i == 0) })
+ }
+ }
+
+ is DownloadDialogIntent.SelectSource -> updateState {
+ it.copy(sources = it.sources.map { (url, _) -> url to (url == intent.url) })
+ }
+
+ DownloadDialogIntent.StartDownload -> emitEffect(
+ DownloadDialogEffect.StartDownload(currentState.selectedUrl)
+ )
+
+ DownloadDialogIntent.Close -> emitEffect(DownloadDialogEffect.Close)
+ }
+ }
+}
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/Modal.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/Modal.kt
index de4349d80..98983733b 100644
--- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/Modal.kt
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/Modal.kt
@@ -123,4 +123,6 @@ sealed interface Modal {
data class LDScheduler(val scheduler: SchedulersToken) : Modal
data class GalleryGrid(val grid: Grid) : Modal
+
+ data class SelectDownloadSource(val modelId: String): Modal
}
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupIntent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupIntent.kt
index 425f9ef65..9735a3583 100644
--- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupIntent.kt
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupIntent.kt
@@ -95,10 +95,10 @@ sealed interface ServerSetupIntent : MviIntent {
sealed interface LocalModel : ServerSetupIntent {
- val model: ServerSetupState.LocalModel
+ data class ClickReduce(val model: ServerSetupState.LocalModel) : LocalModel
- data class ClickReduce(override val model: ServerSetupState.LocalModel) : LocalModel
+ data class DownloadConfirm(val modelId: String, val url: String): LocalModel
- data class DeleteConfirm(override val model: ServerSetupState.LocalModel) : LocalModel
+ data class DeleteConfirm(val model: ServerSetupState.LocalModel) : LocalModel
}
}
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupViewModel.kt
index d054a301d..0e5601955 100644
--- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupViewModel.kt
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupViewModel.kt
@@ -226,6 +226,10 @@ class ServerSetupViewModel(
is ServerSetupIntent.SelectLocalModelPath -> updateState { state ->
state.withLocalCustomModelPath(intent.value)
}
+
+ is ServerSetupIntent.LocalModel.DownloadConfirm -> with(intent) {
+ download(modelId, url)
+ }
}
private fun validateAndConnectToServer() {
@@ -296,6 +300,7 @@ class ServerSetupViewModel(
}
validation.isValid
}
+
else -> {
currentState.localMediaPipeModels.find { it.selected && it.downloaded } != null
}
@@ -441,46 +446,51 @@ class ServerSetupViewModel(
it.copy(screenModal = Modal.DeleteLocalModelConfirm(localModel()))
}
// User requested new download operation
- else -> {
- updateState { state ->
- state.withUpdatedLocalModel(
- localModel().copy(downloadState = DownloadState.Downloading()),
- )
- }
- !downloadModelUseCase(localModel().id)
- .distinctUntilChanged()
- .doOnSubscribe { wakeLockInterActor.acquireWakelockUseCase() }
- .doFinally { wakeLockInterActor.releaseWakeLockUseCase() }
- .subscribeOnMainThread(schedulersProvider)
- .subscribeBy(
- onError = { t ->
- errorLog(t)
- val message = t.localizedMessage ?: "Error"
- updateState { state ->
- state.withUpdatedLocalModel(
- localModel().copy(
- downloadState = DownloadState.Error(t),
- ),
- )
- }
- setScreenModal(Modal.Error(message.asUiText()))
- },
- onNext = { downloadState ->
- updateState { state ->
- state.withUpdatedLocalModel(
- localModel().copy(
- downloadState = downloadState,
- downloaded = downloadState is DownloadState.Complete
- ),
- )
- }
- },
- )
- .also { downloadDisposables.add(localModel().id to it) }
- }
+ else -> setScreenModal(Modal.SelectDownloadSource(localModel().id))
}
}
+ private fun download(modelId: String, url: String) {
+ val localModel =
+ currentState.localModels.firstOrNull { it.id == modelId } ?: return
+
+ updateState { state ->
+ state.withUpdatedLocalModel(
+ localModel.copy(downloadState = DownloadState.Downloading()),
+ )
+ }
+ !downloadModelUseCase(localModel.id, url)
+ .distinctUntilChanged()
+ .doOnSubscribe { wakeLockInterActor.acquireWakelockUseCase() }
+ .doFinally { wakeLockInterActor.releaseWakeLockUseCase() }
+ .subscribeOnMainThread(schedulersProvider)
+ .subscribeBy(
+ onError = { t ->
+ errorLog(t)
+ val message = t.localizedMessage ?: "Error"
+ updateState { state ->
+ state.withUpdatedLocalModel(
+ localModel.copy(
+ downloadState = DownloadState.Error(t),
+ ),
+ )
+ }
+ setScreenModal(Modal.Error(message.asUiText()))
+ },
+ onNext = { downloadState ->
+ updateState { state ->
+ state.withUpdatedLocalModel(
+ localModel.copy(
+ downloadState = downloadState,
+ downloaded = downloadState is DownloadState.Complete
+ ),
+ )
+ }
+ },
+ )
+ .also { downloadDisposables.add(localModel.id to it) }
+ }
+
private fun setScreenModal(value: Modal) = updateState {
it.copy(screenModal = value)
}
diff --git a/presentation/src/main/res/drawable/ic_github.xml b/presentation/src/main/res/drawable/ic_github.xml
new file mode 100644
index 000000000..3a1abe804
--- /dev/null
+++ b/presentation/src/main/res/drawable/ic_github.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupViewModelTest.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupViewModelTest.kt
index c9be862b4..8713466db 100644
--- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupViewModelTest.kt
+++ b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupViewModelTest.kt
@@ -148,7 +148,7 @@ class ServerSetupViewModelTest : CoreViewModelTest() {
@Test
fun `given received LocalModel ClickReduce intent, model not downloaded, expected UI state is Downloading, wakeLocks called`() {
every {
- stubDownloadModelUseCase(any())
+ stubDownloadModelUseCase(any(), any())
} returns Observable.just(DownloadState.Downloading(22))
every {
@@ -180,7 +180,7 @@ class ServerSetupViewModelTest : CoreViewModelTest() {
stubWakeLockInterActor.releaseWakeLockUseCase()
}
verify {
- stubDownloadModelUseCase("1")
+ stubDownloadModelUseCase("1", "https://example.com/1.html")
}
}