diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/AppExtensions.kt b/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/AppExtensions.kt
index f32dc5aa..db9aaad0 100644
--- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/AppExtensions.kt
+++ b/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/AppExtensions.kt
@@ -7,7 +7,7 @@ import android.content.Intent
import android.net.Uri
import android.provider.Settings
import android.widget.Toast
-
+import androidx.annotation.StringRes
fun Context.isAppInForeground(): Boolean {
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
@@ -18,6 +18,10 @@ fun Context.isAppInForeground(): Boolean {
}
}
+fun Context.showToast(@StringRes resId: Int) {
+ resources.getString(resId).let(::showToast)
+}
+
fun Context.showToast(text: String) {
Toast.makeText(this, text, Toast.LENGTH_LONG).show()
}
diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/SchedulersProvider.kt b/core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/SchedulersProvider.kt
index f904069f..2e7bea78 100755
--- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/SchedulersProvider.kt
+++ b/core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/SchedulersProvider.kt
@@ -1,6 +1,7 @@
package com.shifthackz.aisdv1.core.common.schedulers
import io.reactivex.rxjava3.core.Scheduler
+import io.reactivex.rxjava3.schedulers.Schedulers
import java.util.concurrent.Executor
interface SchedulersProvider {
@@ -8,4 +9,11 @@ interface SchedulersProvider {
val ui: Scheduler
val computation: Scheduler
val singleThread: Executor
+
+ fun byToken(token: SchedulersToken): Scheduler = when (token) {
+ SchedulersToken.MAIN_THREAD -> ui
+ SchedulersToken.IO_THREAD -> io
+ SchedulersToken.COMPUTATION -> computation
+ SchedulersToken.SINGLE_THREAD -> Schedulers.from(singleThread)
+ }
}
diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/SchedulersToken.kt b/core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/SchedulersToken.kt
new file mode 100644
index 00000000..c674d7c5
--- /dev/null
+++ b/core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/SchedulersToken.kt
@@ -0,0 +1,8 @@
+package com.shifthackz.aisdv1.core.common.schedulers
+
+enum class SchedulersToken(val type: String) {
+ MAIN_THREAD("Main thread"),
+ IO_THREAD("IO thread"),
+ COMPUTATION("Computation"),
+ SINGLE_THREAD("Single thread"),
+}
diff --git a/core/localization/src/main/res/values-ru/strings.xml b/core/localization/src/main/res/values-ru/strings.xml
index ecf330b6..726aaa23 100644
--- a/core/localization/src/main/res/values-ru/strings.xml
+++ b/core/localization/src/main/res/values-ru/strings.xml
@@ -16,6 +16,8 @@
Применить
Закрыть
Далее
+ ✅ Успешно
+ ❌ Ошибка
Два
Три
@@ -153,7 +155,8 @@
Конфигурация
Выберите SD ML модель
Очистить кэш приложения
- Дебаг Меню
+ Режим разработчика
+ Логи
ЛоРА
Инверсия текста
Инверсия
@@ -298,8 +301,19 @@
Чтобы использовать локальную пользовательскую модель, поместите ее в локальную папку в памяти телефона: Download/SDAi/model
Окончательная структура папок должна быть такой:
+ Отладка
QA тест-кейсы
+ Work Manager API
+ Режим разработчика разблокирован!
+ Посмотреть логи
+ Очистить все логи
+ Перезапустить последнюю txt2img задачу
+ Перезапустить последнюю img2img задачу
+ Отменить все задачи
+ Разрешить прерывание генерации
+ Поток
Внести битый Base64 в БД
+ Лог файл пуст.
Здесь пусто…
Добавьте что-нибуть на %1$s сервере в: \n\n%2$s
@@ -330,4 +344,9 @@
Отсутствует разрешение.
Разрешите права на %1$s в настройках.
+
+ UI поток
+ I/O Поток
+ Вычислительный
+ Однопоточность
diff --git a/core/localization/src/main/res/values-tr/strings.xml b/core/localization/src/main/res/values-tr/strings.xml
index b65fb353..1c4a97ae 100644
--- a/core/localization/src/main/res/values-tr/strings.xml
+++ b/core/localization/src/main/res/values-tr/strings.xml
@@ -16,6 +16,8 @@
Uygula
Kapalı
Sonraki
+ ✅ Başarılı
+ ❌ Başarısızlık
İki
Üç
@@ -153,7 +155,8 @@
Sunucu Kurulumu
SD Modeli seçin
Uygulama önbelleğini temizle
- Hata Ayıklama Menüsü
+ Geliştirici modu
+ Günlükler
LoRA
Metin İnversiyon
İnversiyon
@@ -298,8 +301,19 @@
Yerel özel modeli kullanmak için telefonunuzun depolama alanındaki yerel klasöre yerleştirin: Download/SDAi/model
Son klasör yapısı şu şekilde olmalıdır::
+ Hata ayıklama
+ Work Manager API
QA işlemleri
+ Geliştirici modu kilidi açıldı!
+ Günlükleri görüntüle
+ Tüm günlükleri temizle
+ Son txt2img görevini yeniden başlat
+ Son img2img görevini yeniden başlat
+ Tüm çalışanları iptal et
+ Oluşturmayı kesmeye izin ver
+ İşlem zamanlayıcısı
Kötü Base64\'ü DB\'ye yerleştirin
+ Herhangi bir günlük bulunamadı.
Burada hiçbir şey…
%1$s sunucusuna biraz içerik ekleyin: \n\n%2$s
@@ -330,4 +344,9 @@
Eksik izinler.
Lütfen uygulama ayarlarında %1$s iznini verin.
+
+ Ana İş Parçacığı
+ G/Ç İş Parçacığı
+ Hesaplama
+ Tek İş Parçacığı
diff --git a/core/localization/src/main/res/values-uk/strings.xml b/core/localization/src/main/res/values-uk/strings.xml
index 8435199d..8b901cd4 100644
--- a/core/localization/src/main/res/values-uk/strings.xml
+++ b/core/localization/src/main/res/values-uk/strings.xml
@@ -16,6 +16,8 @@
Застосувати
Закрити
Далі
+ ✅ Успіх
+ ❌ Помилка
Два
Три
@@ -153,7 +155,8 @@
Конфігурація
Оберіть SD ML модель
Очистити кеш додатку
- Дебаг Меню
+ Меню розробника
+ Логи
ЛоРА
Інверсія тексту
Інверсія
@@ -298,8 +301,19 @@
Щоб використовувати локальну спеціальну модель, помістіть її в локальну папку в пам’яті телефону: Download/SDAi/model
Остаточна структура папок має бути такою:
+ Відладка
+ Work Manager API
QA тест-кейси
+ Режим розробника розблоковано!
+ Дивитися логи
+ Видалити всі логи
+ Перезапуск останнього txt2img завдання
+ Перезапуск останнього img2img завдання
+ Скасувати всі завдання
+ Дозволити переривання генерації
+ Потік
Внести битий Base64 в БД
+ Лог файл пустий.
Тут порожньо…
Додайте щось на %1$s сервері до: \n\n%2$s
@@ -330,4 +344,9 @@
Додаток не має дозволів.
Довольте права на %1$s в налаштуваннях.
+
+ UI Потік
+ I/O Потік
+ Обчислення
+ Один потік
diff --git a/core/localization/src/main/res/values-zh/strings.xml b/core/localization/src/main/res/values-zh/strings.xml
index 078471ee..0849958f 100644
--- a/core/localization/src/main/res/values-zh/strings.xml
+++ b/core/localization/src/main/res/values-zh/strings.xml
@@ -21,6 +21,8 @@
应用
关闭
下一步
+ ✅ 成功
+ ❌ 失败
二
三
@@ -191,7 +193,8 @@
配置
选择SD ML模型
清除应用缓存
- 调试菜单
+ 开发者模式
+ 日志
LoRA
超网络
H-Net
@@ -360,8 +363,19 @@
最终的文件夹结构应该是:
+ 调试
+ 工作管理器 API
QA操作
+ 开发者模式已解锁!
+ 查看日志
+ 清除所有日志
+ 重新启动最后一个 txt2img 任务
+ 重新启动最后一个 img2img 任务
+ 取消所有工作程序
+ 允许中断生成
+ 进程调度程序
在数据库中插入错误的Base64
+ 未找到日志。
这里什么都没有…
@@ -396,4 +410,9 @@
缺少权限。
请在应用程序设置中允许 %1$s 权限。
+
+ 主线程
+ I/O 线程
+ 计算
+ 单线程
diff --git a/core/localization/src/main/res/values/strings.xml b/core/localization/src/main/res/values/strings.xml
index b9f9e542..bd1b8e31 100755
--- a/core/localization/src/main/res/values/strings.xml
+++ b/core/localization/src/main/res/values/strings.xml
@@ -20,6 +20,8 @@
Apply
Close
Next
+ ✅ Success
+ ❌ Failure
Two
Three
@@ -171,7 +173,8 @@
Configuration
Select SD ML Model
Clear app cache
- Debug Menu
+ Developer mode
+ Logs
LoRA
Hypernetworks
H-Net
@@ -320,8 +323,20 @@
To use local custom model, place it to local folder in your phone storage: Download/SDAi/model
The final folder structure should be:
+ Debugging
+ Local Diffusion
+ Work Manager API
QA actions
+ Developer mode unlocked!
+ View logs
+ Clear all logs
+ Restart last txt2img task
+ Restart last img2img task
+ Cancel all workers
+ Allow to interrupt generation
+ Process scheduler
Insert bad Base64 in DB
+ No logs found.
Nothing here…
Add some content on %1$s server to: \n\n%2$s
@@ -353,4 +368,9 @@
Missing permissions.
Please allow %1$s permission in application settings.
+
+ Main Thread
+ I/O Thread
+ Computation
+ Single Thread
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 224449af..1a41e3a5 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
@@ -3,6 +3,7 @@ package com.shifthackz.aisdv1.data.preference
import android.content.SharedPreferences
import com.shifthackz.aisdv1.core.common.extensions.fixUrlSlashes
import com.shifthackz.aisdv1.core.common.extensions.shouldUseNewMediaStore
+import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken
import com.shifthackz.aisdv1.domain.entity.ColorToken
import com.shifthackz.aisdv1.domain.entity.DarkThemeToken
import com.shifthackz.aisdv1.domain.entity.FeatureTag
@@ -51,6 +52,29 @@ class PreferenceManagerImpl(
.apply()
.also { onPreferencesChanged() }
+ override var developerMode: Boolean
+ get() = preferences.getBoolean(KEY_DEVELOPER_MODE, false)
+ set(value) = preferences.edit()
+ .putBoolean(KEY_DEVELOPER_MODE, value)
+ .apply()
+ .also { onPreferencesChanged() }
+
+ override var localDiffusionAllowCancel: Boolean
+ get() = preferences.getBoolean(KEY_ALLOW_LOCAL_DIFFUSION_CANCEL, false)
+ set(value) = preferences.edit()
+ .putBoolean(KEY_ALLOW_LOCAL_DIFFUSION_CANCEL, value)
+ .apply()
+ .also { onPreferencesChanged() }
+
+ override var localDiffusionSchedulerThread: SchedulersToken
+ get() = preferences
+ .getInt(KEY_LOCAL_DIFFUSION_SCHEDULER_THREAD, SchedulersToken.COMPUTATION.ordinal)
+ .let { SchedulersToken.entries[it] }
+ set(value) = preferences.edit()
+ .putInt(KEY_LOCAL_DIFFUSION_SCHEDULER_THREAD, value.ordinal)
+ .apply()
+ .also { onPreferencesChanged() }
+
override var monitorConnectivity: Boolean
get() = if (!source.featureTags.contains(FeatureTag.OwnServer)) false
else preferences.getBoolean(KEY_MONITOR_CONNECTIVITY, true)
@@ -232,6 +256,9 @@ class PreferenceManagerImpl(
serverUrl = automatic1111ServerUrl,
sdModel = sdModel,
demoMode = demoMode,
+ developerMode = developerMode,
+ localDiffusionAllowCancel = localDiffusionAllowCancel,
+ localDiffusionSchedulerThread = localDiffusionSchedulerThread,
monitorConnectivity = monitorConnectivity,
backgroundGeneration = backgroundGeneration,
autoSaveAiResults = autoSaveAiResults,
@@ -257,6 +284,9 @@ class PreferenceManagerImpl(
const val KEY_SWARM_SERVER_URL = "key_swarm_server_url"
const val KEY_SWARM_MODEL = "key_swarm_model"
const val KEY_DEMO_MODE = "key_demo_mode"
+ const val KEY_DEVELOPER_MODE = "key_developer_mode"
+ const val KEY_ALLOW_LOCAL_DIFFUSION_CANCEL = "key_allow_local_diffusion_cancel"
+ const val KEY_LOCAL_DIFFUSION_SCHEDULER_THREAD = "key_local_diffusion_scheduler_thread"
const val KEY_MONITOR_CONNECTIVITY = "key_monitor_connectivity"
const val KEY_AI_AUTO_SAVE = "key_ai_auto_save"
const val KEY_SAVE_TO_MEDIA_STORE = "key_save_to_media_store"
diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/LocalDiffusionGenerationRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/LocalDiffusionGenerationRepositoryImpl.kt
index 36856cbe..0254b3d8 100644
--- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/LocalDiffusionGenerationRepositoryImpl.kt
+++ b/data/src/main/java/com/shifthackz/aisdv1/data/repository/LocalDiffusionGenerationRepositoryImpl.kt
@@ -19,8 +19,8 @@ internal class LocalDiffusionGenerationRepositoryImpl(
mediaStoreGateway: MediaStoreGateway,
base64ToBitmapConverter: Base64ToBitmapConverter,
localDataSource: GenerationResultDataSource.Local,
- preferenceManager: PreferenceManager,
backgroundWorkObserver: BackgroundWorkObserver,
+ private val preferenceManager: PreferenceManager,
private val localDiffusion: LocalDiffusion,
private val downloadableLocalDataSource: DownloadableModelDataSource.Local,
private val bitmapToBase64Converter: BitmapToBase64Converter,
@@ -46,7 +46,7 @@ internal class LocalDiffusionGenerationRepositoryImpl(
private fun generate(payload: TextToImagePayload) = localDiffusion
.process(payload)
- .subscribeOn(schedulersProvider.computation)
+ .subscribeOn(schedulersProvider.byToken(preferenceManager.localDiffusionSchedulerThread))
.map(BitmapToBase64Converter::Input)
.flatMap(bitmapToBase64Converter::invoke)
.map(BitmapToBase64Converter.Output::base64ImageString)
diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/LocalDiffusionGenerationRepositoryImplTest.kt b/data/src/test/java/com/shifthackz/aisdv1/data/repository/LocalDiffusionGenerationRepositoryImplTest.kt
index c92ecaaf..75df885f 100644
--- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/LocalDiffusionGenerationRepositoryImplTest.kt
+++ b/data/src/test/java/com/shifthackz/aisdv1/data/repository/LocalDiffusionGenerationRepositoryImplTest.kt
@@ -2,6 +2,7 @@ package com.shifthackz.aisdv1.data.repository
import android.graphics.Bitmap
import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider
+import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken
import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter
import com.shifthackz.aisdv1.core.imageprocessing.BitmapToBase64Converter
import com.shifthackz.aisdv1.data.mocks.mockLocalAiModel
@@ -61,6 +62,10 @@ class LocalDiffusionGenerationRepositoryImplTest {
@Before
fun initialize() {
+ every {
+ stubPreferenceManager::localDiffusionSchedulerThread.get()
+ } returns SchedulersToken.COMPUTATION
+
every {
stubBackgroundWorkObserver.hasActiveTasks()
} returns false
diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Settings.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Settings.kt
index 723e5dac..c4da9e46 100644
--- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Settings.kt
+++ b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Settings.kt
@@ -1,9 +1,14 @@
package com.shifthackz.aisdv1.domain.entity
+import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken
+
data class Settings(
val serverUrl: String = "",
val sdModel: String = "",
val demoMode: Boolean = false,
+ val developerMode: Boolean = false,
+ val localDiffusionAllowCancel: Boolean = false,
+ val localDiffusionSchedulerThread: SchedulersToken = SchedulersToken.COMPUTATION,
val monitorConnectivity: Boolean = false,
val backgroundGeneration: Boolean = false,
val autoSaveAiResults: Boolean = false,
diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/feature/work/BackgroundTaskManager.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/feature/work/BackgroundTaskManager.kt
index ecf2f0cc..78199398 100644
--- a/domain/src/main/java/com/shifthackz/aisdv1/domain/feature/work/BackgroundTaskManager.kt
+++ b/domain/src/main/java/com/shifthackz/aisdv1/domain/feature/work/BackgroundTaskManager.kt
@@ -6,4 +6,7 @@ import com.shifthackz.aisdv1.domain.entity.TextToImagePayload
interface BackgroundTaskManager {
fun scheduleTextToImageTask(payload: TextToImagePayload)
fun scheduleImageToImageTask(payload: ImageToImagePayload)
+ fun retryLastTextToImageTask(): Result
+ fun retryLastImageToImageTask(): Result
+ fun cancelAll(): Result
}
diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/preference/PreferenceManager.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/preference/PreferenceManager.kt
index bc52c431..2ea33eef 100644
--- a/domain/src/main/java/com/shifthackz/aisdv1/domain/preference/PreferenceManager.kt
+++ b/domain/src/main/java/com/shifthackz/aisdv1/domain/preference/PreferenceManager.kt
@@ -1,5 +1,6 @@
package com.shifthackz.aisdv1.domain.preference
+import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken
import com.shifthackz.aisdv1.domain.entity.Grid
import com.shifthackz.aisdv1.domain.entity.ServerSource
import com.shifthackz.aisdv1.domain.entity.Settings
@@ -10,6 +11,9 @@ interface PreferenceManager {
var swarmUiServerUrl: String
var swarmUiModel: String
var demoMode: Boolean
+ var developerMode: Boolean
+ var localDiffusionAllowCancel: Boolean
+ var localDiffusionSchedulerThread: SchedulersToken
var monitorConnectivity: Boolean
var autoSaveAiResults: Boolean
var saveToMediaStore: Boolean
diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionContract.kt b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionContract.kt
index c1dbf677..a59a7afc 100644
--- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionContract.kt
+++ b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionContract.kt
@@ -3,6 +3,10 @@
package com.shifthackz.aisdv1.feature.diffusion
internal object LocalDiffusionContract {
+ //region LOGGING
+ const val TAG = "LocalDiffusion"
+ //endregion
+
//region MODELS PATHS
const val UNET_MODEL = "unet/model.ort"
const val VAE_MODEL = "vae_decoder/model.ort"
diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionImpl.kt b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionImpl.kt
index b2698c84..56b3f39d 100644
--- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionImpl.kt
+++ b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionImpl.kt
@@ -2,8 +2,11 @@ package com.shifthackz.aisdv1.feature.diffusion
import ai.onnxruntime.OnnxTensor
import android.graphics.Bitmap
+import com.shifthackz.aisdv1.core.common.log.debugLog
+import com.shifthackz.aisdv1.core.common.log.errorLog
import com.shifthackz.aisdv1.domain.entity.TextToImagePayload
import com.shifthackz.aisdv1.domain.feature.diffusion.LocalDiffusion
+import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.TAG
import com.shifthackz.aisdv1.feature.diffusion.ai.tokenizer.LocalDiffusionTextTokenizer
import com.shifthackz.aisdv1.feature.diffusion.ai.unet.UNet
import com.shifthackz.aisdv1.feature.diffusion.environment.OrtEnvironmentProvider
@@ -21,15 +24,25 @@ internal class LocalDiffusionImpl(
override fun process(payload: TextToImagePayload): Single = Single.create { emitter ->
try {
+ emitter.setCancellable {
+ debugLog(TAG, "{$TAG} Received cancelable signal.")
+ interruptGeneration()
+ }
uNet.setCallback(object : UNet.Callback {
override fun onStep(maxStep: Int, step: Int) {
+ debugLog(TAG, "Received step update: ${maxStep}/${step}")
statusSubject.onNext(LocalDiffusion.Status(step, maxStep))
}
override fun onBuildImage(status: Int, bitmap: Bitmap?) {
- if (!emitter.isDisposed) {
- bitmap?.let(emitter::onSuccess) ?: emitter.onError(Throwable("Bitmap is null"))
- }
+ bitmap
+ ?.let(emitter::onSuccess)
+ ?.also { debugLog("{$TAG} Bitmap built successfully!") }
+ ?: run {
+ val t = Throwable("Bitmap is null")
+ errorLog(t, "{$TAG} Bitmap is null.")
+ emitter.onError(t)
+ }
}
})
@@ -71,15 +84,23 @@ internal class LocalDiffusionImpl(
height = payload.height,
)
} catch (e: Exception) {
- if (!emitter.isDisposed) emitter.onError(e)
+ errorLog(e, "{$TAG} Caught exception while Local Diffusion process.")
+ interruptGeneration()
+ emitter.onError(e)
}
}
// ToDo review method of LocalDiffusion cancellation, now next generation crashes using this approach
override fun interrupt() = Completable.fromAction {
- tokenizer.close()
- uNet.close()
+ interruptGeneration()
}
override fun observeStatus() = statusSubject
+
+ private fun interruptGeneration() {
+ debugLog("{$TAG} Trying to interrupt generation.")
+ tokenizer.close()
+ uNet.close()
+ debugLog("{$TAG} Generation interrupt successful!")
+ }
}
diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/tokenizer/EnglishTextTokenizer.kt b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/tokenizer/EnglishTextTokenizer.kt
index 9cd3c615..e539317b 100644
--- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/tokenizer/EnglishTextTokenizer.kt
+++ b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/tokenizer/EnglishTextTokenizer.kt
@@ -8,11 +8,13 @@ import android.text.TextUtils
import android.util.JsonReader
import android.util.Pair
import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor
+import com.shifthackz.aisdv1.core.common.log.debugLog
import com.shifthackz.aisdv1.core.common.log.errorLog
import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract
import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.KEY_INPUT_IDS
import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.ORT
import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.ORT_KEY_MODEL_FORMAT
+import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.TAG
import com.shifthackz.aisdv1.feature.diffusion.ai.extensions.halfCorner
import com.shifthackz.aisdv1.feature.diffusion.ai.extensions.toArrays
import com.shifthackz.aisdv1.feature.diffusion.environment.LocalModelIdProvider
@@ -44,23 +46,32 @@ internal class EnglishTextTokenizer(
override val maxLength = 77
override fun initialize() {
- if (session != null) return
+ if (session != null) {
+ debugLog("{$TAG} {TOKENIZER} {initialize} Session already initialized, skipping...")
+ return
+ }
val options = OrtSession.SessionOptions()
options.addConfigEntry(ORT_KEY_MODEL_FORMAT, ORT)
session = ortEnvironmentProvider.get().createSession(
"${modelPathPrefix(fileProviderDescriptor, localModelIdProvider)}/${LocalDiffusionContract.TOKENIZER_MODEL}",
options
)
+ debugLog("{$TAG} {TOKENIZER} {initialize} Session created successfully!")
if (!isInitMap) {
encoder.putAll(loadEncoder())
decoder.putAll(loadDecoder(encoder))
bpeRanks.putAll(loadBpeRanks())
}
isInitMap = true
+ debugLog("{$TAG} {TOKENIZER} {initialize} Tokenizer map initialized successfully!")
}
override fun decode(ids: IntArray?): String {
- if (ids == null) return ""
+ debugLog("{$TAG} {TOKENIZER} {decode} Trying to decode ${ids?.size ?: "null"} int array...")
+ if (ids == null) {
+ debugLog("{$TAG} {TOKENIZER} {decode} Input ids array is null, skipping.")
+ return ""
+ }
val stringBuilder = StringBuilder()
for (value in ids) {
if (decoder.containsKey(value)) stringBuilder.append(decoder[value])
@@ -74,10 +85,13 @@ internal class EnglishTextTokenizer(
}
val ints = IntArray(result.size)
for (i in result.indices) ints[i] = result[i]
- return String(ints, 0, ints.size)
+ val resultString = String(ints, 0, ints.size)
+ debugLog("{$TAG} {TOKENIZER} {decode} Decode was successful!")
+ return resultString
}
override fun encode(text: String?): IntArray {
+ debugLog("{$TAG} {TOKENIZER} {encode} Trying to encode ${text ?: "null"}...")
var input = text
input = input.toString().lowercase(Locale.getDefault()).halfCorner()
val stringList: MutableList = ArrayList()
@@ -113,11 +127,16 @@ internal class EnglishTextTokenizer(
Arrays.fill(copy, 49407)
System.arraycopy(ids, 0, copy, 0, if (ids.size < copy.size) ids.size else copy.size)
copy[copy.size - 1] = 49407
+ debugLog("{$TAG} {TOKENIZER} {encode} Encode was successful!")
return copy
}
override fun tensor(ids: IntArray?): OnnxTensor? {
- if (ids == null) return null
+ debugLog("{$TAG} {TOKENIZER} {tensor} Trying to tensor ${ids?.size ?: "null"} int array...")
+ if (ids == null) {
+ debugLog("{$TAG} {TOKENIZER} {tensor} Input ids array is null, skipping.")
+ return null
+ }
val inputIds = OnnxTensor.createTensor(
ortEnvironmentProvider.get(),
IntBuffer.wrap(ids),
@@ -128,14 +147,18 @@ internal class EnglishTextTokenizer(
val result = session!!.run(input)
val lastHiddenState = result[0].value
result.close()
- return OnnxTensor.createTensor(ortEnvironmentProvider.get(), lastHiddenState)
+ val tensor = OnnxTensor.createTensor(ortEnvironmentProvider.get(), lastHiddenState)
+ debugLog("{$TAG} {TOKENIZER} {tensor} Tensor formation was successful!")
+ return tensor
}
override fun createUnconditionalInput(text: String?): IntArray = encode(text)
override fun close() {
+ debugLog("{$TAG} {TOKENIZER} {close} Closing session...")
session?.close()
session = null
+ debugLog("{$TAG} {TOKENIZER} {close} Session closed successfully!")
}
private fun bpe(token: String): List {
diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/unet/UNet.kt b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/unet/UNet.kt
index ba8f13b7..96d427f2 100644
--- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/unet/UNet.kt
+++ b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/unet/UNet.kt
@@ -9,6 +9,7 @@ import ai.onnxruntime.providers.NNAPIFlags
import android.graphics.Bitmap
import android.util.Pair
import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor
+import com.shifthackz.aisdv1.core.common.log.debugLog
import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract
import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.KEY_ENCODER_HIDDEN_STATES
import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.KEY_LATENT_SAMPLE
@@ -16,6 +17,7 @@ import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.KEY_SAMPLE
import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.KEY_TIME_STEP
import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.ORT
import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.ORT_KEY_MODEL_FORMAT
+import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.TAG
import com.shifthackz.aisdv1.feature.diffusion.ai.extensions.duplicate
import com.shifthackz.aisdv1.feature.diffusion.ai.extensions.getSizes
import com.shifthackz.aisdv1.feature.diffusion.ai.extensions.multipleTensorsByFloat
@@ -155,9 +157,17 @@ internal class UNet(
width: Int,
height: Int,
) {
+ debugLog("{$TAG} {uNet} {inference} Trying to start inference:")
+ debugLog("{$TAG} {uNet} {inference} - seed: $seedNum")
+ debugLog("{$TAG} {uNet} {inference} - numInferenceSteps: $numInferenceSteps")
+ debugLog("{$TAG} {uNet} {inference} - textEmbeddings: $textEmbeddings")
+ debugLog("{$TAG} {uNet} {inference} - guidanceScale: $guidanceScale")
+ debugLog("{$TAG} {uNet} {inference} - batchSize: $batchSize")
+ debugLog("{$TAG} {uNet} {inference} - size: ${width}x${height}")
this.width = width
this.height = height
val localDiffusionScheduler = EulerAncestralDiscreteLocalDiffusionScheduler()
+ debugLog("{$TAG} {uNet} {inference} Initialized scheduler: $localDiffusionScheduler")
val timeSteps: IntArray = localDiffusionScheduler.setTimeSteps(numInferenceSteps)
val seed = if (seedNum <= 0) random.nextLong() else seedNum
var latents: LocalDiffusionTensor<*> = generateLatentSample(
@@ -167,13 +177,19 @@ internal class UNet(
seed,
localDiffusionScheduler.initNoiseSigma.toFloat()
)
+ debugLog("{$TAG} {uNet} {inference} Got latents: ${latents.hashCode()}")
val shape = longArrayOf(2, 4, (height / 8).toLong(), (width / 8).toLong())
+ debugLog("{$TAG} {uNet} {inference} Got shape: $shape")
+ debugLog("{$TAG} {uNet} {inference} Starting steps processing! Total : ${timeSteps.size}")
for (i in timeSteps.indices) {
var latentModelInput: LocalDiffusionTensor<*> = duplicate(
latents.tensor.floatBuffer.array(),
shape,
)
latentModelInput = localDiffusionScheduler.scaleModelInput(latentModelInput, i)
+ debugLog("{$TAG} {uNet} {inference} {Step_$i} ------------------")
+ debugLog("{$TAG} {uNet} {inference} {Step_$i} Latent model input: $latentModelInput")
+ debugLog("{$TAG} {uNet} {inference} {Step_$i} Notifying callback about step.")
callback?.onStep(timeSteps.size, i)
val input = createUNetModelInput(
textEmbeddings,
@@ -184,8 +200,11 @@ internal class UNet(
longArrayOf(1)
)
)
+ debugLog("{$TAG} {uNet} {inference} {Step_$i} Got uNet model input: $input")
val result = session!!.run(input)
+ debugLog("{$TAG} {uNet} {inference} {Step_$i} Got result from uNet session: $result")
val dataSet = result[0].value as Array3D
+ debugLog("{$TAG} {uNet} {inference} {Step_$i} Trying to close ORT session in: $result")
result.close()
val splitTensors: Pair, Array3D> =
splitTensor(
@@ -194,7 +213,13 @@ internal class UNet(
)
val noisePrediction = splitTensors.first
val noisePredictionText = splitTensors.second
+ debugLog("{$TAG} {uNet} {inference} {Step_$i} Got split tensors with prediction:")
+ debugLog("{$TAG} {uNet} {inference} {Step_$i} - splitTensors: $splitTensors")
+ debugLog("{$TAG} {uNet} {inference} {Step_$i} - noisePrediction: $noisePrediction")
+ debugLog("{$TAG} {uNet} {inference} {Step_$i} - noisePredictionText: $noisePredictionText")
+ debugLog("{$TAG} {uNet} {inference} {Step_$i} Trying to preform guidance...")
performGuidance(noisePrediction, noisePredictionText, guidanceScale)
+ debugLog("{$TAG} {uNet} {inference} {Step_$i} Guidance performed successfully!")
latents = localDiffusionScheduler.step(
LocalDiffusionTensor(
OnnxTensor.createTensor(
@@ -207,16 +232,22 @@ internal class UNet(
i,
latents,
)
+ debugLog("{$TAG} {uNet} {inference} {Step_$i} Finalized latents: $latents")
+ debugLog("{$TAG} {uNet} {inference} {Step_$i} ------------------")
}
callback?.also { clb ->
+ debugLog("{$TAG} {uNet} {inference} Finalization / Flushing image...")
callback?.onStep(timeSteps.size, timeSteps.size)
val bitmap = decode(latents)
+ debugLog("{$TAG} {uNet} {inference} Finalization / Decoded bitmap: ${bitmap.hashCode()}")
clb.onBuildImage(0, bitmap)
+ debugLog("{$TAG} {uNet} {inference} Finalization / Notifying callback and closing session.")
close()
}
}
fun decode(latents: LocalDiffusionTensor<*>): Bitmap {
+ debugLog("{$TAG} {uNet} {decode} Trying to decode latents: ${latents.hashCode()}")
val tensor: LocalDiffusionTensor<*> = multipleTensorsByFloat(
latents.tensor.floatBuffer.array(),
1.0f / 0.18215f,
@@ -225,21 +256,26 @@ internal class UNet(
val decoderInput: MutableMap = HashMap()
decoderInput[KEY_LATENT_SAMPLE] = tensor.tensor
val value: Any = decoder!!.decode(decoderInput.toMap())
- return decoder!!.convertToImage(
+ val bitmap = decoder!!.convertToImage(
value as Array3D,
width,
height,
)
+ debugLog("{$TAG} {uNet} {decode} Bitmap generated successfully: ${bitmap.hashCode()}")
+ return bitmap
}
fun close() {
+ debugLog("{$TAG} {uNet} {close} Closing session...")
session?.close()
decoder?.close()
session = null
decoder = null
+ debugLog("{$TAG} {uNet} {close} Session closed successfully!")
}
fun setCallback(callback: Callback?) {
+ debugLog("{$TAG} {uNet} Setting new result callback ${callback.hashCode()}")
this.callback = callback
}
diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/BackgroundTaskManagerImpl.kt b/feature/work/src/main/java/com/shifthackz/aisdv1/work/BackgroundTaskManagerImpl.kt
index 175a62d3..c1444ec9 100644
--- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/BackgroundTaskManagerImpl.kt
+++ b/feature/work/src/main/java/com/shifthackz/aisdv1/work/BackgroundTaskManagerImpl.kt
@@ -25,6 +25,33 @@ internal class BackgroundTaskManagerImpl : BackgroundTaskManager {
runWork(payload.toByteArray(), Constants.FILE_IMAGE_TO_IMAGE)
}
+ override fun retryLastTextToImageTask(): Result {
+ try {
+ val bytes = readPayload(Constants.FILE_TEXT_TO_IMAGE)
+ ?: return Result.failure(Throwable("Payload is null."))
+ runWork(bytes, Constants.FILE_TEXT_TO_IMAGE)
+ return Result.success(Unit)
+ } catch (e: Exception) {
+ return Result.failure(e)
+ }
+ }
+
+ override fun retryLastImageToImageTask(): Result {
+ try {
+ val bytes = readPayload(Constants.FILE_IMAGE_TO_IMAGE)
+ ?: return Result.failure(Throwable("Payload is null."))
+ runWork(bytes, Constants.FILE_IMAGE_TO_IMAGE)
+ return Result.success(Unit)
+ } catch (e: Exception) {
+ return Result.failure(e)
+ }
+ }
+
+ override fun cancelAll(): Result = runCatching {
+ val workManager: WorkManagerProvider by inject(WorkManagerProvider::class.java)
+ workManager().cancelAllWork()
+ }
+
private inline fun runWork(bytes: ByteArray, fileName: String) {
val workManager: WorkManagerProvider by inject(WorkManagerProvider::class.java)
val workRequest = OneTimeWorkRequestBuilder()
@@ -33,7 +60,7 @@ internal class BackgroundTaskManagerImpl : BackgroundTaskManager {
.build()
writePayload(bytes, fileName)
- workManager().cancelAllWork()
+ workManager().cancelUniqueWork(Constants.TAG_GENERATION)
workManager().enqueueUniqueWork(
Constants.TAG_GENERATION,
ExistingWorkPolicy.REPLACE,
@@ -41,6 +68,16 @@ internal class BackgroundTaskManagerImpl : BackgroundTaskManager {
)
}
+ private fun readPayload(fileName: String): ByteArray? {
+ val fileProviderDescriptor: FileProviderDescriptor by inject(FileProviderDescriptor::class.java)
+ val cacheDirectory = File(fileProviderDescriptor.workCacheDirPath)
+ if (!cacheDirectory.exists()) {
+ return null
+ }
+ val outFile = File(cacheDirectory, fileName)
+ return outFile.readBytes()
+ }
+
private fun writePayload(bytes: ByteArray, fileName: String) {
val fileProviderDescriptor: FileProviderDescriptor by inject(FileProviderDescriptor::class.java)
val cacheDirectory = File(fileProviderDescriptor.workCacheDirPath)
diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/core/CoreGenerationWorker.kt b/feature/work/src/main/java/com/shifthackz/aisdv1/work/core/CoreGenerationWorker.kt
index 1dd69c12..d3076dee 100644
--- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/core/CoreGenerationWorker.kt
+++ b/feature/work/src/main/java/com/shifthackz/aisdv1/work/core/CoreGenerationWorker.kt
@@ -13,6 +13,7 @@ import com.shifthackz.aisdv1.domain.entity.AiGenerationResult
import com.shifthackz.aisdv1.domain.entity.ServerSource
import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver
import com.shifthackz.aisdv1.domain.preference.PreferenceManager
+import com.shifthackz.aisdv1.domain.usecase.generation.InterruptGenerationUseCase
import com.shifthackz.aisdv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase
import com.shifthackz.aisdv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase
import io.reactivex.rxjava3.disposables.CompositeDisposable
@@ -24,10 +25,11 @@ internal abstract class CoreGenerationWorker(
workerParameters: WorkerParameters,
pushNotificationManager: PushNotificationManager,
activityIntentProvider: ActivityIntentProvider,
- preferenceManager: PreferenceManager,
+ private val preferenceManager: PreferenceManager,
private val backgroundWorkObserver: BackgroundWorkObserver,
private val observeHordeProcessStatusUseCase: ObserveHordeProcessStatusUseCase,
private val observeLocalDiffusionProcessStatusUseCase: ObserveLocalDiffusionProcessStatusUseCase,
+ private val interruptGenerationUseCase: InterruptGenerationUseCase,
) : NotificationWorker(
context = context,
workerParameters = workerParameters,
@@ -41,6 +43,11 @@ internal abstract class CoreGenerationWorker(
override fun onStopped() {
super.onStopped()
+ runCatching {
+ interruptGenerationUseCase()
+ .onErrorComplete()
+ .blockingAwait()
+ }
compositeDisposable.clear()
backgroundWorkObserver.postCancelSignal()
}
@@ -86,7 +93,7 @@ internal abstract class CoreGenerationWorker(
body = subTitle,
silent = true,
progress = status.current to status.total,
- canCancel = false,
+ canCancel = preferenceManager.localDiffusionAllowCancel,
)
}
}
@@ -118,7 +125,6 @@ internal abstract class CoreGenerationWorker(
}
protected fun handleError(t: Throwable) {
- errorLog(t)
backgroundWorkObserver.postFailedSignal(t)
val title = applicationContext.getString(LocalizationR.string.notification_fail_title)
val subTitle = applicationContext.getString(LocalizationR.string.notification_fail_sub_title)
diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/di/SdaiWorkerFactory.kt b/feature/work/src/main/java/com/shifthackz/aisdv1/work/di/SdaiWorkerFactory.kt
index 8d5dc6ce..68d419d1 100644
--- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/di/SdaiWorkerFactory.kt
+++ b/feature/work/src/main/java/com/shifthackz/aisdv1/work/di/SdaiWorkerFactory.kt
@@ -10,6 +10,7 @@ import com.shifthackz.aisdv1.core.notification.PushNotificationManager
import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver
import com.shifthackz.aisdv1.domain.preference.PreferenceManager
import com.shifthackz.aisdv1.domain.usecase.generation.ImageToImageUseCase
+import com.shifthackz.aisdv1.domain.usecase.generation.InterruptGenerationUseCase
import com.shifthackz.aisdv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase
import com.shifthackz.aisdv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase
import com.shifthackz.aisdv1.domain.usecase.generation.TextToImageUseCase
@@ -24,6 +25,7 @@ class SdaiWorkerFactory(
private val imageToImageUseCase: ImageToImageUseCase,
private val observeHordeProcessStatusUseCase: ObserveHordeProcessStatusUseCase,
private val observeLocalDiffusionProcessStatusUseCase: ObserveLocalDiffusionProcessStatusUseCase,
+ private val interruptGenerationUseCase: InterruptGenerationUseCase,
private val fileProviderDescriptor: FileProviderDescriptor,
private val activityIntentProvider: ActivityIntentProvider,
) : WorkerFactory() {
@@ -44,6 +46,7 @@ class SdaiWorkerFactory(
textToImageUseCase = textToImageUseCase,
observeHordeProcessStatusUseCase = observeHordeProcessStatusUseCase,
observeLocalDiffusionProcessStatusUseCase = observeLocalDiffusionProcessStatusUseCase,
+ interruptGenerationUseCase = interruptGenerationUseCase,
fileProviderDescriptor = fileProviderDescriptor,
)
@@ -57,6 +60,7 @@ class SdaiWorkerFactory(
imageToImageUseCase = imageToImageUseCase,
observeHordeProcessStatusUseCase = observeHordeProcessStatusUseCase,
observeLocalDiffusionProcessStatusUseCase = observeLocalDiffusionProcessStatusUseCase,
+ interruptGenerationUseCase = interruptGenerationUseCase,
fileProviderDescriptor = fileProviderDescriptor,
)
diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/ImageToImageTask.kt b/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/ImageToImageTask.kt
index 7dedd982..c6b69bf8 100644
--- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/ImageToImageTask.kt
+++ b/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/ImageToImageTask.kt
@@ -8,6 +8,7 @@ import com.shifthackz.aisdv1.core.notification.PushNotificationManager
import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver
import com.shifthackz.aisdv1.domain.preference.PreferenceManager
import com.shifthackz.aisdv1.domain.usecase.generation.ImageToImageUseCase
+import com.shifthackz.aisdv1.domain.usecase.generation.InterruptGenerationUseCase
import com.shifthackz.aisdv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase
import com.shifthackz.aisdv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase
import com.shifthackz.aisdv1.work.Constants
@@ -26,6 +27,7 @@ internal class ImageToImageTask(
preferenceManager: PreferenceManager,
observeHordeProcessStatusUseCase: ObserveHordeProcessStatusUseCase,
observeLocalDiffusionProcessStatusUseCase: ObserveLocalDiffusionProcessStatusUseCase,
+ interruptGenerationUseCase: InterruptGenerationUseCase,
private val backgroundWorkObserver: BackgroundWorkObserver,
private val imageToImageUseCase: ImageToImageUseCase,
private val fileProviderDescriptor: FileProviderDescriptor,
@@ -38,6 +40,7 @@ internal class ImageToImageTask(
backgroundWorkObserver = backgroundWorkObserver,
observeHordeProcessStatusUseCase = observeHordeProcessStatusUseCase,
observeLocalDiffusionProcessStatusUseCase = observeLocalDiffusionProcessStatusUseCase,
+ interruptGenerationUseCase = interruptGenerationUseCase,
) {
override val notificationId = NOTIFICATION_IMAGE_TO_IMAGE_FOREGROUND
diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/TextToImageTask.kt b/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/TextToImageTask.kt
index b392fd68..f9e68631 100644
--- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/TextToImageTask.kt
+++ b/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/TextToImageTask.kt
@@ -4,9 +4,12 @@ import android.content.Context
import androidx.work.WorkerParameters
import com.shifthackz.aisdv1.core.common.appbuild.ActivityIntentProvider
import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor
+import com.shifthackz.aisdv1.core.common.log.debugLog
+import com.shifthackz.aisdv1.core.common.log.errorLog
import com.shifthackz.aisdv1.core.notification.PushNotificationManager
import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver
import com.shifthackz.aisdv1.domain.preference.PreferenceManager
+import com.shifthackz.aisdv1.domain.usecase.generation.InterruptGenerationUseCase
import com.shifthackz.aisdv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase
import com.shifthackz.aisdv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase
import com.shifthackz.aisdv1.domain.usecase.generation.TextToImageUseCase
@@ -25,6 +28,7 @@ internal class TextToImageTask(
activityIntentProvider: ActivityIntentProvider,
observeHordeProcessStatusUseCase: ObserveHordeProcessStatusUseCase,
observeLocalDiffusionProcessStatusUseCase: ObserveLocalDiffusionProcessStatusUseCase,
+ interruptGenerationUseCase: InterruptGenerationUseCase,
private val preferenceManager: PreferenceManager,
private val backgroundWorkObserver: BackgroundWorkObserver,
private val textToImageUseCase: TextToImageUseCase,
@@ -38,6 +42,7 @@ internal class TextToImageTask(
backgroundWorkObserver = backgroundWorkObserver,
observeHordeProcessStatusUseCase = observeHordeProcessStatusUseCase,
observeLocalDiffusionProcessStatusUseCase = observeLocalDiffusionProcessStatusUseCase,
+ interruptGenerationUseCase = interruptGenerationUseCase,
) {
override val notificationId: Int = NOTIFICATION_TEXT_TO_IMAGE_FOREGROUND
@@ -54,6 +59,7 @@ internal class TextToImageTask(
handleError(Throwable("Background process count > 0"))
compositeDisposable.clear()
preferenceManager.backgroundProcessCount = 0
+ debugLog("Background process count > 0! Skipping task.")
return Single.just(Result.failure())
}
@@ -61,13 +67,16 @@ internal class TextToImageTask(
handleStart()
backgroundWorkObserver.refreshStatus()
backgroundWorkObserver.dismissResult()
+ debugLog("Starting TextToImageTask!")
return try {
val file = File(fileProviderDescriptor.workCacheDirPath, Constants.FILE_TEXT_TO_IMAGE)
if (!file.exists()) {
preferenceManager.backgroundProcessCount--
- handleError(Throwable("File is null."))
+ val t = Throwable("File is null.")
+ handleError(t)
compositeDisposable.clear()
+ errorLog(t, "Payload file does not exist.")
return Single.just(Result.failure())
}
@@ -76,8 +85,10 @@ internal class TextToImageTask(
if (payload == null) {
preferenceManager.backgroundProcessCount--
- handleError(Throwable("Payload is null."))
+ val t = Throwable("Payload is null.")
+ handleError(t)
compositeDisposable.clear()
+ errorLog(t, "Payload was failed to read/parse.")
return Single.just(Result.failure())
}
@@ -89,11 +100,13 @@ internal class TextToImageTask(
.map { result ->
preferenceManager.backgroundProcessCount--
handleSuccess(result)
+ debugLog("Generation finished successfully!")
Result.success()
}
.onErrorReturn { t ->
preferenceManager.backgroundProcessCount--
handleError(t)
+ errorLog(t, "Caught exception from TextToImageUseCase!")
Result.failure()
}
.doFinally { compositeDisposable.clear() }
@@ -101,6 +114,7 @@ internal class TextToImageTask(
preferenceManager.backgroundProcessCount--
handleError(e)
compositeDisposable.clear()
+ errorLog(e, "Caught exception from TextToImageTask worker!")
Single.just(Result.failure())
}
}
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 cbaa637f..a9a1aac4 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
@@ -14,6 +14,7 @@ import com.shifthackz.aisdv1.presentation.screen.home.HomeNavigationViewModel
import com.shifthackz.aisdv1.presentation.screen.img2img.ImageToImageViewModel
import com.shifthackz.aisdv1.presentation.screen.inpaint.InPaintViewModel
import com.shifthackz.aisdv1.presentation.screen.loader.ConfigurationLoaderViewModel
+import com.shifthackz.aisdv1.presentation.screen.logger.LoggerViewModel
import com.shifthackz.aisdv1.presentation.screen.settings.SettingsViewModel
import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupLaunchSource
import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupViewModel
@@ -50,6 +51,7 @@ val viewModelModule = module {
viewModelOf(::WebUiViewModel)
viewModelOf(::DonateViewModel)
viewModelOf(::BackgroundWorkViewModel)
+ viewModelOf(::LoggerViewModel)
viewModel { parameters ->
val launchSource = ServerSetupLaunchSource.fromKey(parameters.get())
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 5cfdcd6b..860d3fd0 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
@@ -27,8 +27,10 @@ import com.shifthackz.aisdv1.presentation.modal.extras.ExtrasScreen
import com.shifthackz.aisdv1.presentation.modal.grid.GridBottomSheet
import com.shifthackz.aisdv1.presentation.modal.history.InputHistoryScreen
import com.shifthackz.aisdv1.presentation.modal.language.LanguageBottomSheet
+import com.shifthackz.aisdv1.presentation.modal.ldscheduler.LDSchedulerBottomSheet
import com.shifthackz.aisdv1.presentation.modal.tag.EditTagDialog
import com.shifthackz.aisdv1.presentation.model.Modal
+import com.shifthackz.aisdv1.presentation.screen.debug.DebugMenuIntent
import com.shifthackz.aisdv1.presentation.screen.gallery.detail.GalleryDetailIntent
import com.shifthackz.aisdv1.presentation.screen.gallery.list.GalleryIntent
import com.shifthackz.aisdv1.presentation.screen.inpaint.InPaintIntent
@@ -57,6 +59,7 @@ fun ModalRenderer(
processIntent(GalleryIntent.DismissDialog)
processIntent(GalleryDetailIntent.DismissDialog)
processIntent(InPaintIntent.ScreenModal.Dismiss)
+ processIntent(DebugMenuIntent.DismissModal)
}
val context = LocalContext.current
when (screenModal) {
@@ -93,6 +96,13 @@ fun ModalRenderer(
titleResId = LocalizationR.string.communicating_local_title,
canDismiss = false,
step = screenModal.pair,
+ content = screenModal.canCancel.takeIf { it }?.let {
+ {
+ ProgressDialogCancelButton {
+ processIntent(GenerationMviIntent.Cancel.Generation)
+ }
+ }
+ },
)
is Modal.Image.Single -> GenerationImageResultDialog(
@@ -332,5 +342,18 @@ fun ModalRenderer(
}
)
}
+
+ is Modal.LDScheduler -> ModalBottomSheet(
+ onDismissRequest = dismiss,
+ shape = RectangleShape,
+ ) {
+ LDSchedulerBottomSheet(
+ currentScheduler = screenModal.scheduler,
+ onSelected = {
+ processIntent(DebugMenuIntent.LocalDiffusionScheduler.Confirm(it))
+ dismiss()
+ }
+ )
+ }
}
}
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ldscheduler/LDSchedulerBottomSheer.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ldscheduler/LDSchedulerBottomSheer.kt
new file mode 100644
index 00000000..8d5b49eb
--- /dev/null
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ldscheduler/LDSchedulerBottomSheer.kt
@@ -0,0 +1,44 @@
+package com.shifthackz.aisdv1.presentation.modal.ldscheduler
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.navigationBarsPadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Construction
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken
+import com.shifthackz.aisdv1.presentation.screen.debug.mapToUi
+import com.shifthackz.aisdv1.presentation.widget.item.SettingsItem
+
+@Composable
+@Preview
+fun LDSchedulerBottomSheet(
+ modifier: Modifier = Modifier,
+ currentScheduler: SchedulersToken = SchedulersToken.COMPUTATION,
+ onSelected: (SchedulersToken) -> Unit = {},
+) {
+ Column(
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp)
+ .navigationBarsPadding()
+ .padding(bottom = 16.dp),
+ ) {
+ SchedulersToken.entries.forEach { scheduler ->
+ SettingsItem(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(bottom = 8.dp),
+ selected = scheduler == currentScheduler,
+ text = scheduler.mapToUi(),
+ showChevron = false,
+ onClick = { onSelected(scheduler) },
+ startIcon = Icons.Default.Construction,
+ )
+ }
+ }
+}
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 3951ed0c..0e53eb82 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
@@ -2,6 +2,7 @@ package com.shifthackz.aisdv1.presentation.model
import android.graphics.Bitmap
import androidx.compose.runtime.Immutable
+import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken
import com.shifthackz.aisdv1.core.model.UiText
import com.shifthackz.aisdv1.domain.entity.AiGenerationResult
import com.shifthackz.aisdv1.domain.entity.Grid
@@ -37,7 +38,10 @@ sealed interface Modal {
data class SelectSdModel(val models: List, val selected: String) : Modal
@Immutable
- data class Generating(val status: LocalDiffusion.Status? = null) : Modal {
+ data class Generating(
+ val canCancel: Boolean = false,
+ val status: LocalDiffusion.Status? = null,
+ ) : Modal {
val pair: Pair?
get() = status?.let { (current, total) -> current to total }
}
@@ -104,5 +108,7 @@ sealed interface Modal {
data object Language : Modal
+ data class LDScheduler(val scheduler: SchedulersToken) : Modal
+
data class GalleryGrid(val grid: Grid) : Modal
}
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/DrawerNavGraph.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/DrawerNavGraph.kt
index c0f92edc..b5f0bfbe 100644
--- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/DrawerNavGraph.kt
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/DrawerNavGraph.kt
@@ -1,6 +1,7 @@
package com.shifthackz.aisdv1.presentation.navigation.graph
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.DeveloperMode
import androidx.compose.material.icons.filled.SettingsEthernet
import androidx.compose.material.icons.filled.Web
import com.shifthackz.aisdv1.core.model.UiText
@@ -23,6 +24,9 @@ fun mainDrawerNavItems(settings: Settings? = null): List = buildList {
}
add(settingsTab())
add(configuration())
+ settings?.developerMode?.takeIf { it }?.let {
+ add(developerMode())
+ }
}
private fun webUi(source: ServerSource) = NavItem(
@@ -45,3 +49,11 @@ private fun configuration() = NavItem(
vector = Icons.Default.SettingsEthernet,
),
)
+
+private fun developerMode() = NavItem(
+ name = LocalizationR.string.title_debug_menu.asUiText(),
+ route = Constants.ROUTE_DEBUG,
+ icon = NavItem.Icon.Vector(
+ vector = Icons.Default.DeveloperMode,
+ )
+)
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/MainNavGraph.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/MainNavGraph.kt
index a654de47..ef8c49a4 100644
--- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/MainNavGraph.kt
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/MainNavGraph.kt
@@ -10,6 +10,7 @@ import com.shifthackz.aisdv1.presentation.screen.donate.DonateScreen
import com.shifthackz.aisdv1.presentation.screen.gallery.detail.GalleryDetailScreen
import com.shifthackz.aisdv1.presentation.screen.inpaint.InPaintScreen
import com.shifthackz.aisdv1.presentation.screen.loader.ConfigurationLoaderScreen
+import com.shifthackz.aisdv1.presentation.screen.logger.LoggerScreen
import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupLaunchSource
import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupScreen
import com.shifthackz.aisdv1.presentation.screen.splash.SplashScreen
@@ -65,6 +66,13 @@ fun NavGraphBuilder.mainNavGraph() {
route = Constants.ROUTE_DEBUG
}
)
+ addDestination(
+ ComposeNavigator.Destination(provider[ComposeNavigator::class]) {
+ LoggerScreen()
+ }.apply {
+ route = Constants.ROUTE_LOGGER
+ }
+ )
addDestination(
ComposeNavigator.Destination(provider[ComposeNavigator::class]) {
InPaintScreen()
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouter.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouter.kt
index f0b70dd9..7f64a566 100644
--- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouter.kt
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouter.kt
@@ -21,4 +21,6 @@ interface MainRouter : Router {
fun navigateToDonate()
fun navigateToDebugMenu()
+
+ fun navigateToLogger()
}
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterImpl.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterImpl.kt
index 873874a2..24a8c52c 100644
--- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterImpl.kt
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterImpl.kt
@@ -56,8 +56,10 @@ internal class MainRouterImpl(
}
override fun navigateToDebugMenu() {
- if (debugMenuAccessor()) {
- effectSubject.onNext(NavigationEffect.Navigate.Route(Constants.ROUTE_DEBUG))
- }
+ effectSubject.onNext(NavigationEffect.Navigate.Route(Constants.ROUTE_DEBUG))
+ }
+
+ override fun navigateToLogger() {
+ effectSubject.onNext(NavigationEffect.Navigate.Route(Constants.ROUTE_LOGGER))
}
}
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuAccessor.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuAccessor.kt
index 5681de85..b080c44b 100644
--- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuAccessor.kt
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuAccessor.kt
@@ -1,19 +1,23 @@
package com.shifthackz.aisdv1.presentation.screen.debug
-import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider
+import com.shifthackz.aisdv1.domain.preference.PreferenceManager
import com.shifthackz.aisdv1.presentation.utils.Constants
-class DebugMenuAccessor(private val buildInfoProvider: BuildInfoProvider) {
+class DebugMenuAccessor(
+ private val preferenceManager: PreferenceManager,
+) {
private var tapCount = 0;
operator fun invoke(): Boolean {
- if (buildInfoProvider.isDebug) {
- tapCount++
- if (tapCount >= Constants.DEBUG_MENU_ACCESS_TAPS) {
- tapCount = 0;
- return true
- }
+ if (preferenceManager.developerMode) {
+ return true
+ }
+ tapCount++
+ if (tapCount >= Constants.DEBUG_MENU_ACCESS_TAPS) {
+ tapCount = 0
+ preferenceManager.developerMode = true
+ return true
}
return false
}
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuEffect.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuEffect.kt
new file mode 100644
index 00000000..132c6522
--- /dev/null
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuEffect.kt
@@ -0,0 +1,8 @@
+package com.shifthackz.aisdv1.presentation.screen.debug
+
+import com.shifthackz.aisdv1.core.model.UiText
+import com.shifthackz.android.core.mvi.MviEffect
+
+sealed interface DebugMenuEffect : MviEffect {
+ data class Message(val message: UiText) : DebugMenuEffect
+}
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuIntent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuIntent.kt
index 22c7e1f3..dae80520 100644
--- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuIntent.kt
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuIntent.kt
@@ -1,8 +1,30 @@
package com.shifthackz.aisdv1.presentation.screen.debug
+import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken
import com.shifthackz.android.core.mvi.MviIntent
-enum class DebugMenuIntent : MviIntent {
- NavigateBack,
- InsertBadBase64;
+sealed interface DebugMenuIntent : MviIntent {
+
+ data object NavigateBack : DebugMenuIntent
+
+ data object ViewLogs : DebugMenuIntent
+
+ data object ClearLogs : DebugMenuIntent
+
+ data object AllowLocalDiffusionCancel : DebugMenuIntent
+
+ data object InsertBadBase64 : DebugMenuIntent
+
+ sealed interface LocalDiffusionScheduler : DebugMenuIntent {
+
+ data class Confirm(val token: SchedulersToken) : DebugMenuIntent
+
+ data object Request : DebugMenuIntent
+ }
+
+ enum class WorkManager : DebugMenuIntent {
+ CancelAll, RestartTxt2Img, RestartImg2Img;
+ }
+
+ data object DismissModal : DebugMenuIntent
}
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuMappers.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuMappers.kt
new file mode 100644
index 00000000..3d2c705f
--- /dev/null
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuMappers.kt
@@ -0,0 +1,13 @@
+package com.shifthackz.aisdv1.presentation.screen.debug
+
+import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken
+import com.shifthackz.aisdv1.core.model.UiText
+import com.shifthackz.aisdv1.core.model.asUiText
+import com.shifthackz.aisdv1.core.localization.R as LocalizationR
+
+fun SchedulersToken.mapToUi(): UiText = when (this) {
+ SchedulersToken.MAIN_THREAD -> LocalizationR.string.scheduler_main
+ SchedulersToken.IO_THREAD -> LocalizationR.string.scheduler_io
+ SchedulersToken.COMPUTATION -> LocalizationR.string.scheduler_computation
+ SchedulersToken.SINGLE_THREAD -> LocalizationR.string.scheduler_single_thread
+}.asUiText()
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuScreen.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuScreen.kt
index 06cded3f..24776091 100644
--- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuScreen.kt
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuScreen.kt
@@ -9,7 +9,13 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.TextSnippet
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
+import androidx.compose.material.icons.filled.Cancel
+import androidx.compose.material.icons.filled.CancelScheduleSend
+import androidx.compose.material.icons.filled.CleaningServices
+import androidx.compose.material.icons.filled.Construction
+import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.SettingsEthernet
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -17,25 +23,39 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import com.shifthackz.aisdv1.core.common.extensions.showToast
import com.shifthackz.aisdv1.core.model.asUiText
import com.shifthackz.aisdv1.core.ui.MviComponent
+import com.shifthackz.aisdv1.presentation.modal.ModalRenderer
+import com.shifthackz.aisdv1.presentation.widget.item.SettingsHeader
import com.shifthackz.aisdv1.presentation.widget.item.SettingsItem
import org.koin.androidx.compose.koinViewModel
import com.shifthackz.aisdv1.core.localization.R as LocalizationR
@Composable
fun DebugMenuScreen() {
+ val context = LocalContext.current
MviComponent(
viewModel = koinViewModel(),
- ) { _, intentHandler ->
+ processEffect = { effect ->
+ when (effect) {
+ is DebugMenuEffect.Message -> context.showToast(
+ effect.message.asString(context)
+ )
+ }
+ }
+ ) { state, intentHandler ->
ScreenContent(
modifier = Modifier.fillMaxSize(),
+ state = state,
processIntent = intentHandler,
)
}
@@ -44,6 +64,7 @@ fun DebugMenuScreen() {
@Composable
private fun ScreenContent(
modifier: Modifier = Modifier,
+ state: DebugMenuState = DebugMenuState(),
processIntent: (DebugMenuIntent) -> Unit = {},
) {
Scaffold(
@@ -81,10 +102,74 @@ private fun ScreenContent(
.fillMaxWidth()
.padding(bottom = 8.dp)
- Text(
+ SettingsHeader(
modifier = headerModifier,
- text = stringResource(id = LocalizationR.string.debug_section_qa),
- style = MaterialTheme.typography.headlineSmall,
+ text = LocalizationR.string.debug_section_main.asUiText(),
+ )
+ SettingsItem(
+ modifier = itemModifier,
+ startIcon = Icons.AutoMirrored.Filled.TextSnippet,
+ text = LocalizationR.string.debug_action_logger.asUiText(),
+ onClick = { processIntent(DebugMenuIntent.ViewLogs) },
+ )
+ SettingsItem(
+ modifier = itemModifier,
+ startIcon = Icons.Default.CleaningServices,
+ text = LocalizationR.string.debug_action_logger_clear.asUiText(),
+ onClick = { processIntent(DebugMenuIntent.ClearLogs) },
+ )
+
+ SettingsHeader(
+ modifier = headerModifier,
+ text = LocalizationR.string.debug_section_work_manager.asUiText(),
+ )
+ SettingsItem(
+ modifier = itemModifier,
+ startIcon = Icons.Default.Refresh,
+ text = LocalizationR.string.debug_action_work_restart_txt2img.asUiText(),
+ onClick = { processIntent(DebugMenuIntent.WorkManager.RestartTxt2Img) },
+ )
+ SettingsItem(
+ modifier = itemModifier,
+ startIcon = Icons.Default.Refresh,
+ text = LocalizationR.string.debug_action_work_restart_img2img.asUiText(),
+ onClick = { processIntent(DebugMenuIntent.WorkManager.RestartImg2Img) },
+ )
+ SettingsItem(
+ modifier = itemModifier,
+ startIcon = Icons.Default.Cancel,
+ text = LocalizationR.string.debug_action_work_cancel_all.asUiText(),
+ onClick = { processIntent(DebugMenuIntent.WorkManager.CancelAll) },
+ )
+
+ SettingsHeader(
+ modifier = headerModifier,
+ text = LocalizationR.string.debug_section_ld.asUiText(),
+ )
+ SettingsItem(
+ modifier = itemModifier,
+ startIcon = Icons.Default.CancelScheduleSend,
+ text = LocalizationR.string.debug_action_ld_allow_cancel.asUiText(),
+ onClick = { processIntent(DebugMenuIntent.AllowLocalDiffusionCancel) },
+ endValueContent = {
+ Switch(
+ modifier = Modifier.padding(horizontal = 8.dp),
+ checked = state.localDiffusionAllowCancel,
+ onCheckedChange = { processIntent(DebugMenuIntent.AllowLocalDiffusionCancel) },
+ )
+ }
+ )
+ SettingsItem(
+ modifier = itemModifier,
+ startIcon = Icons.Default.Construction,
+ text = LocalizationR.string.debug_action_ld_scheduler.asUiText(),
+ onClick = { processIntent(DebugMenuIntent.LocalDiffusionScheduler.Request) },
+ endValueText = state.localDiffusionSchedulerThread.mapToUi(),
+ )
+
+ SettingsHeader(
+ modifier = headerModifier,
+ text = LocalizationR.string.debug_section_qa.asUiText(),
)
SettingsItem(
modifier = itemModifier,
@@ -93,6 +178,9 @@ private fun ScreenContent(
onClick = { processIntent(DebugMenuIntent.InsertBadBase64) },
)
}
+ ModalRenderer(screenModal = state.screenModal) {
+ (it as? DebugMenuIntent)?.let(processIntent::invoke)
+ }
}
}
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuState.kt
new file mode 100644
index 00000000..20fa36e4
--- /dev/null
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuState.kt
@@ -0,0 +1,11 @@
+package com.shifthackz.aisdv1.presentation.screen.debug
+
+import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken
+import com.shifthackz.aisdv1.presentation.model.Modal
+import com.shifthackz.android.core.mvi.MviState
+
+data class DebugMenuState(
+ val screenModal: Modal = Modal.None,
+ val localDiffusionAllowCancel: Boolean = false,
+ val localDiffusionSchedulerThread: SchedulersToken = SchedulersToken.COMPUTATION,
+) : MviState
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModel.kt
index fbcbacd3..9336acaf 100644
--- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModel.kt
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModel.kt
@@ -1,29 +1,105 @@
package com.shifthackz.aisdv1.presentation.screen.debug
+import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor
+import com.shifthackz.aisdv1.core.common.log.FileLoggingTree
import com.shifthackz.aisdv1.core.common.log.errorLog
import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider
import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread
+import com.shifthackz.aisdv1.core.model.asUiText
import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel
+import com.shifthackz.aisdv1.domain.feature.work.BackgroundTaskManager
+import com.shifthackz.aisdv1.domain.preference.PreferenceManager
import com.shifthackz.aisdv1.domain.usecase.debug.DebugInsertBadBase64UseCase
+import com.shifthackz.aisdv1.presentation.model.Modal
import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter
-import com.shifthackz.android.core.mvi.EmptyEffect
-import com.shifthackz.android.core.mvi.EmptyState
import io.reactivex.rxjava3.kotlin.subscribeBy
+import com.shifthackz.aisdv1.core.localization.R as LocalizationR
class DebugMenuViewModel(
+ private val preferenceManager: PreferenceManager,
+ private val fileProviderDescriptor: FileProviderDescriptor,
private val debugInsertBadBase64UseCase: DebugInsertBadBase64UseCase,
private val schedulersProvider: SchedulersProvider,
private val mainRouter: MainRouter,
-) : MviRxViewModel() {
+ private val backgroundTaskManager: BackgroundTaskManager,
+) : MviRxViewModel() {
- override val initialState = EmptyState
+ override val initialState = DebugMenuState()
+
+ init {
+ !preferenceManager
+ .observe()
+ .subscribeOnMainThread(schedulersProvider)
+ .subscribeBy(::errorLog) { settings ->
+ updateState { state ->
+ state.copy(
+ localDiffusionAllowCancel = settings.localDiffusionAllowCancel,
+ localDiffusionSchedulerThread = settings.localDiffusionSchedulerThread,
+ )
+ }
+ }
+ }
override fun processIntent(intent: DebugMenuIntent) {
when (intent) {
DebugMenuIntent.NavigateBack -> mainRouter.navigateBack()
+
DebugMenuIntent.InsertBadBase64 -> !debugInsertBadBase64UseCase()
.subscribeOnMainThread(schedulersProvider)
- .subscribeBy(::errorLog)
+ .subscribeBy(::onError, ::onSuccess)
+
+ DebugMenuIntent.ClearLogs -> {
+ try {
+ FileLoggingTree.clearLog(fileProviderDescriptor)
+ onSuccess()
+ } catch (e: Exception) {
+ onError(e)
+ }
+ }
+
+ DebugMenuIntent.ViewLogs -> mainRouter.navigateToLogger()
+
+ DebugMenuIntent.AllowLocalDiffusionCancel -> {
+ preferenceManager.localDiffusionAllowCancel = !currentState.localDiffusionAllowCancel
+ }
+
+ DebugMenuIntent.LocalDiffusionScheduler.Request -> updateState {
+ it.copy(screenModal = Modal.LDScheduler(it.localDiffusionSchedulerThread))
+ }
+
+ is DebugMenuIntent.LocalDiffusionScheduler.Confirm -> {
+ preferenceManager.localDiffusionSchedulerThread = intent.token
+ }
+
+ DebugMenuIntent.DismissModal -> updateState {
+ it.copy(screenModal = Modal.None)
+ }
+
+ DebugMenuIntent.WorkManager.CancelAll -> backgroundTaskManager
+ .cancelAll()
+ .handleState()
+
+ DebugMenuIntent.WorkManager.RestartTxt2Img -> backgroundTaskManager
+ .retryLastTextToImageTask()
+ .handleState()
+
+ DebugMenuIntent.WorkManager.RestartImg2Img -> backgroundTaskManager
+ .retryLastImageToImageTask()
+ .handleState()
}
}
+
+ private fun Result.handleState() = this.fold(
+ onSuccess = { onSuccess() },
+ onFailure = ::onError,
+ )
+
+ private fun onSuccess() {
+ emitEffect(DebugMenuEffect.Message(LocalizationR.string.success.asUiText()))
+ }
+
+ private fun onError(t: Throwable) {
+ errorLog(t)
+ emitEffect(DebugMenuEffect.Message(LocalizationR.string.failure.asUiText()))
+ }
}
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerIntent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerIntent.kt
new file mode 100644
index 00000000..a1fc441c
--- /dev/null
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerIntent.kt
@@ -0,0 +1,10 @@
+package com.shifthackz.aisdv1.presentation.screen.logger
+
+import com.shifthackz.android.core.mvi.MviIntent
+
+sealed interface LoggerIntent : MviIntent {
+
+ data object ReadLogs : LoggerIntent
+
+ data object NavigateBack : LoggerIntent
+}
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerScreen.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerScreen.kt
new file mode 100644
index 00000000..def1e935
--- /dev/null
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerScreen.kt
@@ -0,0 +1,202 @@
+@file:OptIn(ExperimentalMaterial3Api::class)
+
+package com.shifthackz.aisdv1.presentation.screen.logger
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.ArrowBack
+import androidx.compose.material.icons.filled.ArrowDownward
+import androidx.compose.material.icons.filled.ArrowUpward
+import androidx.compose.material.icons.filled.Refresh
+import androidx.compose.material3.CenterAlignedTopAppBar
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.shifthackz.aisdv1.core.ui.MviComponent
+import kotlinx.coroutines.launch
+import org.koin.androidx.compose.koinViewModel
+import com.shifthackz.aisdv1.core.localization.R as LocalizationR
+
+@Composable
+fun LoggerScreen() {
+ MviComponent(
+ viewModel = koinViewModel(),
+ ) { state, processIntent ->
+ LoggerScreenContent(
+ state = state,
+ processIntent = processIntent,
+ )
+ }
+}
+
+@Composable
+@Preview
+private fun LoggerScreenContent(
+ state: LoggerState = LoggerState(),
+ processIntent: (LoggerIntent) -> Unit = {},
+) {
+ val scrollState = rememberScrollState()
+ val scope = rememberCoroutineScope()
+ Scaffold(
+ topBar = {
+ CenterAlignedTopAppBar(
+ navigationIcon = {
+ IconButton(
+ onClick = { processIntent(LoggerIntent.NavigateBack) },
+ content = {
+ Icon(
+ Icons.AutoMirrored.Outlined.ArrowBack,
+ contentDescription = "Back button",
+ )
+ },
+ )
+ },
+ title = {
+ Text(
+ text = stringResource(id = LocalizationR.string.title_debug_logger),
+ style = MaterialTheme.typography.headlineMedium,
+ )
+ },
+ actions = {
+ AnimatedVisibility(
+ visible = !state.loading,
+ enter = fadeIn(),
+ exit = fadeOut(),
+ ) {
+ IconButton(
+ onClick = {
+ processIntent(LoggerIntent.ReadLogs)
+ },
+ content = {
+ Icon(
+ Icons.Default.Refresh,
+ contentDescription = "Refresh",
+ )
+ },
+ )
+ }
+ }
+ )
+ },
+ bottomBar = {
+ AnimatedVisibility(
+ visible = !state.loading && state.text.isNotBlank(),
+ enter = fadeIn(),
+ exit = fadeOut(),
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End),
+ ) {
+ IconButton(
+ onClick = {
+ scope.launch {
+ scrollState.animateScrollTo(0)
+ }
+ },
+ content = {
+ Icon(
+ Icons.Default.ArrowUpward,
+ contentDescription = "Down",
+ )
+ },
+ )
+ IconButton(
+ onClick = {
+ scope.launch {
+ scrollState.animateScrollTo(scrollState.maxValue)
+ }
+ },
+ content = {
+ Icon(
+ Icons.Default.ArrowDownward,
+ contentDescription = "Down",
+ )
+ },
+ )
+ }
+ }
+
+ }
+ ) { paddingValues ->
+ val text = if (!state.loading) state.text else ""
+ val scrollStateHorizontal = rememberScrollState()
+ if (!state.loading && state.text.isBlank()) {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(paddingValues),
+ contentAlignment = Alignment.Center,
+ ) {
+ Text(
+ text = stringResource(id = LocalizationR.string.debug_logger_empty),
+ textAlign = TextAlign.Center,
+ )
+ }
+ }
+ Column(
+ modifier = Modifier
+ .padding(paddingValues)
+ .fillMaxSize()
+ .verticalScroll(scrollState),
+ ) {
+ AnimatedVisibility(
+ visible = state.loading,
+ enter = fadeIn(),
+ exit = fadeOut(),
+ ) {
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ contentAlignment = Alignment.Center,
+ ) {
+ CircularProgressIndicator(
+ modifier = Modifier
+ .size(60.dp)
+ .aspectRatio(1f),
+ )
+ }
+ }
+ Text(
+ modifier = Modifier.horizontalScroll(scrollStateHorizontal),
+ text = text,
+ fontFamily = FontFamily.Monospace,
+ fontSize = 11.sp,
+ lineHeight = 12.sp,
+ )
+ }
+ LaunchedEffect(state.text) {
+ if (!state.loading) {
+ scrollState.scrollTo(scrollState.maxValue)
+ }
+ }
+ }
+}
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerState.kt
new file mode 100644
index 00000000..0571411a
--- /dev/null
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerState.kt
@@ -0,0 +1,10 @@
+package com.shifthackz.aisdv1.presentation.screen.logger
+
+import androidx.compose.runtime.Immutable
+import com.shifthackz.android.core.mvi.MviState
+
+@Immutable
+data class LoggerState(
+ val loading: Boolean = true,
+ val text: String = "",
+) : MviState
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerViewModel.kt
new file mode 100644
index 00000000..21e503f3
--- /dev/null
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerViewModel.kt
@@ -0,0 +1,47 @@
+package com.shifthackz.aisdv1.presentation.screen.logger
+
+import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor
+import com.shifthackz.aisdv1.core.common.log.FileLoggingTree
+import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel
+import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter
+import com.shifthackz.android.core.mvi.EmptyEffect
+import java.io.File
+
+class LoggerViewModel(
+ private val fileProviderDescriptor: FileProviderDescriptor,
+ private val mainRouter: MainRouter,
+) : MviRxViewModel() {
+
+ override val initialState = LoggerState()
+
+ init {
+ readLogs()
+ }
+
+ override fun processIntent(intent: LoggerIntent) {
+ when (intent) {
+ LoggerIntent.ReadLogs -> readLogs()
+ LoggerIntent.NavigateBack -> mainRouter.navigateBack()
+ }
+ }
+
+ private fun readLogs() {
+ updateState { it.copy(loading = true, text = "") }
+ try {
+ val logFile = File(
+ fileProviderDescriptor.logsCacheDirPath +
+ "/" +
+ FileLoggingTree.LOGGER_FILENAME
+ )
+ val content = logFile.readText()
+ updateState {
+ it.copy(
+ loading = false,
+ text = content,
+ )
+ }
+ } catch (e: Exception) {
+ updateState { it.copy(loading = false) }
+ }
+ }
+}
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsEffect.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsEffect.kt
index 664c0cf5..f43aeca4 100644
--- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsEffect.kt
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsEffect.kt
@@ -11,5 +11,7 @@ sealed interface SettingsEffect : MviEffect {
data object ShareLogFile : SettingsEffect
+ data object DeveloperModeUnlocked : SettingsEffect
+
data class OpenUrl(val url: String) : SettingsEffect
}
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsIntent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsIntent.kt
index ff8b0fbc..be85a4fb 100644
--- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsIntent.kt
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsIntent.kt
@@ -13,6 +13,8 @@ sealed interface SettingsIntent : MviIntent {
data object NavigateConfiguration : SettingsIntent
+ data object NavigateDeveloperMode : SettingsIntent
+
sealed interface SdModel : SettingsIntent {
data object OpenChooser : SdModel
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsScreen.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsScreen.kt
index de5bac26..f4757741 100644
--- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsScreen.kt
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsScreen.kt
@@ -26,6 +26,7 @@ import androidx.compose.material.icons.filled.Code
import androidx.compose.material.icons.filled.ColorLens
import androidx.compose.material.icons.filled.DarkMode
import androidx.compose.material.icons.filled.DeleteForever
+import androidx.compose.material.icons.filled.DeveloperMode
import androidx.compose.material.icons.filled.DynamicForm
import androidx.compose.material.icons.filled.Folder
import androidx.compose.material.icons.filled.FormatColorFill
@@ -60,6 +61,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.shifthackz.aisdv1.core.common.extensions.openUrl
+import com.shifthackz.aisdv1.core.common.extensions.showToast
import com.shifthackz.aisdv1.core.common.math.roundTo
import com.shifthackz.aisdv1.core.model.UiText
import com.shifthackz.aisdv1.core.model.asUiText
@@ -115,6 +117,9 @@ fun SettingsScreen() {
}
SettingsEffect.ShareLogFile -> ReportProblemEmailComposer().invoke(context)
is SettingsEffect.OpenUrl -> context.openUrl(effect.url)
+ SettingsEffect.DeveloperModeUnlocked -> context.showToast(
+ LocalizationR.string.debug_action_unlock,
+ )
}
},
applySystemUiColors = false,
@@ -297,6 +302,15 @@ private fun ContentSettingsState(
style = MaterialTheme.typography.labelMedium,
)
}
+ AnimatedVisibility(visible = !state.loading && state.developerMode) {
+ SettingsItem(
+ modifier = itemModifier,
+ loading = state.loading,
+ startIcon = Icons.Default.DeveloperMode,
+ text = LocalizationR.string.title_debug_menu.asUiText(),
+ onClick = { processIntent(SettingsIntent.NavigateDeveloperMode) },
+ )
+ }
//endregion
//region APP SETTINGS
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsState.kt
index e8bb7490..432ec831 100644
--- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsState.kt
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsState.kt
@@ -29,6 +29,7 @@ data class SettingsState(
val colorToken: ColorToken = ColorToken.MAUVE,
val darkThemeToken: DarkThemeToken = DarkThemeToken.FRAPPE,
val galleryGrid: Grid = Grid.Fixed2,
+ val developerMode: Boolean = false,
val appVersion: String = "",
) : MviState {
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsViewModel.kt
index d6bd101b..ff9c9cd7 100644
--- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsViewModel.kt
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsViewModel.kt
@@ -19,6 +19,7 @@ import com.shifthackz.aisdv1.domain.usecase.stabilityai.ObserveStabilityAiCredit
import com.shifthackz.aisdv1.presentation.model.Modal
import com.shifthackz.aisdv1.presentation.navigation.router.drawer.DrawerRouter
import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter
+import com.shifthackz.aisdv1.presentation.screen.debug.DebugMenuAccessor
import com.shifthackz.aisdv1.presentation.screen.drawer.DrawerIntent
import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupLaunchSource
import io.reactivex.rxjava3.core.Flowable
@@ -32,6 +33,7 @@ class SettingsViewModel(
private val clearAppCacheUseCase: ClearAppCacheUseCase,
private val schedulersProvider: SchedulersProvider,
private val preferenceManager: PreferenceManager,
+ private val debugMenuAccessor: DebugMenuAccessor,
private val buildInfoProvider: BuildInfoProvider,
private val mainRouter: MainRouter,
private val drawerRouter: DrawerRouter,
@@ -80,6 +82,7 @@ class SettingsViewModel(
colorToken = ColorToken.parse(settings.designColorToken),
darkThemeToken = DarkThemeToken.parse(settings.designDarkThemeToken),
galleryGrid = settings.galleryGrid,
+ developerMode = settings.developerMode,
appVersion = version,
)
}
@@ -89,7 +92,9 @@ class SettingsViewModel(
override fun processIntent(intent: SettingsIntent) {
when (intent) {
- SettingsIntent.Action.AppVersion -> mainRouter.navigateToDebugMenu()
+ SettingsIntent.Action.AppVersion -> if (debugMenuAccessor()) {
+ emitEffect(SettingsEffect.DeveloperModeUnlocked)
+ }
SettingsIntent.Action.ClearAppCache.Request -> updateState {
it.copy(screenModal = Modal.ClearAppCache)
@@ -107,6 +112,8 @@ class SettingsViewModel(
ServerSetupLaunchSource.SETTINGS
)
+ SettingsIntent.NavigateDeveloperMode -> mainRouter.navigateToDebugMenu()
+
SettingsIntent.SdModel.OpenChooser -> updateState {
it.copy(
screenModal = Modal.SelectSdModel(
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/txt2img/TextToImageViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/txt2img/TextToImageViewModel.kt
index dbf31dad..6f98575f 100755
--- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/txt2img/TextToImageViewModel.kt
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/txt2img/TextToImageViewModel.kt
@@ -66,7 +66,7 @@ class TextToImageViewModel(
private val progressModal: Modal
get() {
if (currentState.mode == ServerSource.LOCAL) {
- return Modal.Generating()
+ return Modal.Generating(canCancel = preferenceManager.localDiffusionAllowCancel)
}
return Modal.Communicating()
}
@@ -126,14 +126,14 @@ class TextToImageViewModel(
}
override fun onReceivedHordeStatus(status: HordeProcessStatus) {
- if (currentState.screenModal is Modal.Communicating) {
- setActiveModal(Modal.Communicating(hordeProcessStatus = status))
- }
+ (currentState.screenModal as? Modal.Communicating)
+ ?.copy(hordeProcessStatus = status)
+ ?.let(::setActiveModal)
}
override fun onReceivedLocalDiffusionStatus(status: LocalDiffusion.Status) {
- if (currentState.screenModal is Modal.Generating) {
- setActiveModal(Modal.Generating(status))
- }
+ (currentState.screenModal as? Modal.Generating)
+ ?.copy(status = status)
+ ?.let(::setActiveModal)
}
}
diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/Constants.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/Constants.kt
index 43f101f0..ad1e0095 100644
--- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/Constants.kt
+++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/Constants.kt
@@ -20,6 +20,7 @@ object Constants {
const val ROUTE_GALLERY_DETAIL_FULL = "$ROUTE_GALLERY_DETAIL/{$PARAM_ITEM_ID}"
const val ROUTE_SETTINGS = "settings"
const val ROUTE_DEBUG = "debug"
+ const val ROUTE_LOGGER = "logger"
const val ROUTE_IN_PAINT = "in_paint"
const val ROUTE_DONATE = "donate"
diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterImplTest.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterImplTest.kt
index 64527033..ecec9c5c 100644
--- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterImplTest.kt
+++ b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterImplTest.kt
@@ -1,21 +1,26 @@
package com.shifthackz.aisdv1.presentation.navigation.router.main
-import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider
+import com.shifthackz.aisdv1.domain.preference.PreferenceManager
import com.shifthackz.aisdv1.presentation.navigation.NavigationEffect
import com.shifthackz.aisdv1.presentation.screen.debug.DebugMenuAccessor
import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupLaunchSource
import com.shifthackz.aisdv1.presentation.utils.Constants
-import io.mockk.every
import io.mockk.mockk
+import org.junit.Before
import org.junit.Test
class MainRouterImplTest {
- private val stubBuildInfoProvider = mockk()
- private val stubDebugMenuAccessor = DebugMenuAccessor(stubBuildInfoProvider)
+ private val stubPreferenceManager = mockk()
+ private val stubDebugMenuAccessor = DebugMenuAccessor(stubPreferenceManager)
private val router = MainRouterImpl(stubDebugMenuAccessor)
+ @Before
+ fun initialize() {
+
+ }
+
@Test
fun `given user navigates back, expected router emits Back event`() {
router
@@ -105,63 +110,14 @@ class MainRouterImplTest {
)
}
- @Test
- fun `given user tapped hidden menu 6 times, build is debuggable, expected router emits no events`() {
- every {
- stubBuildInfoProvider.isDebug
- } returns true
-
- val stubObserver = router.observe().test()
-
- repeat(6) { router.navigateToDebugMenu() }
-
- stubObserver
- .assertNoErrors()
- .assertNoValues()
- }
-
@Test
fun `given user tapped hidden menu 7 times, build is debuggable, expected router emits Route event with ROUTE_DEBUG route`() {
- every {
- stubBuildInfoProvider.isDebug
- } returns true
-
val stubObserver = router.observe().test()
- repeat(7) { router.navigateToDebugMenu() }
+ router.navigateToDebugMenu()
stubObserver
.assertNoErrors()
.assertValueAt(0, NavigationEffect.Navigate.Route(Constants.ROUTE_DEBUG))
}
-
- @Test
- fun `given user tapped hidden menu 6 times, build is NOT debuggable, expected router emits no events`() {
- every {
- stubBuildInfoProvider.isDebug
- } returns false
-
- val stubObserver = router.observe().test()
-
- repeat(6) { router.navigateToDebugMenu() }
-
- stubObserver
- .assertNoErrors()
- .assertNoValues()
- }
-
- @Test
- fun `given user tapped hidden menu 7 times, build is NOT debuggable, expected router emits no events`() {
- every {
- stubBuildInfoProvider.isDebug
- } returns false
-
- val stubObserver = router.observe().test()
-
- repeat(7) { router.navigateToDebugMenu() }
-
- stubObserver
- .assertNoErrors()
- .assertNoValues()
- }
}
diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModelTest.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModelTest.kt
index e501b0f3..4175f057 100644
--- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModelTest.kt
+++ b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModelTest.kt
@@ -1,5 +1,9 @@
package com.shifthackz.aisdv1.presentation.screen.debug
+import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor
+import com.shifthackz.aisdv1.domain.entity.Settings
+import com.shifthackz.aisdv1.domain.feature.work.BackgroundTaskManager
+import com.shifthackz.aisdv1.domain.preference.PreferenceManager
import com.shifthackz.aisdv1.domain.usecase.debug.DebugInsertBadBase64UseCase
import com.shifthackz.aisdv1.presentation.core.CoreViewModelTest
import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter
@@ -8,19 +12,36 @@ import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import io.reactivex.rxjava3.core.Completable
+import io.reactivex.rxjava3.core.Flowable
+import org.junit.Before
import org.junit.Test
class DebugMenuViewModelTest : CoreViewModelTest() {
+ private val stubFileProviderDescriptor = mockk()
private val stubDebugInsertBadBase64UseCase = mockk()
private val stubMainRouter = mockk()
+ private val stubPreferenceManager = mockk()
+ private val stubBackgroundTaskManager = mockk()
override fun initializeViewModel() = DebugMenuViewModel(
+ preferenceManager = stubPreferenceManager,
+ fileProviderDescriptor = stubFileProviderDescriptor,
debugInsertBadBase64UseCase = stubDebugInsertBadBase64UseCase,
schedulersProvider = stubSchedulersProvider,
mainRouter = stubMainRouter,
+ backgroundTaskManager = stubBackgroundTaskManager,
)
+ @Before
+ override fun initialize() {
+ super.initialize()
+
+ every {
+ stubPreferenceManager.observe()
+ } returns Flowable.just(Settings())
+ }
+
@Test
fun `given received NavigateBack intent, expected router navigateBack() method called`() {
every {
diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateViewModelTest.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateViewModelTest.kt
index 7859cc9a..218cd875 100644
--- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateViewModelTest.kt
+++ b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateViewModelTest.kt
@@ -9,8 +9,6 @@ import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import io.reactivex.rxjava3.core.Single
-import kotlinx.coroutines.flow.firstOrNull
-import kotlinx.coroutines.test.runTest
import org.junit.Assert
import org.junit.Test
@@ -32,14 +30,12 @@ class DonateViewModelTest : CoreViewModelTest() {
stubFetchAndGetSupportersUseCase()
} returns Single.just(mockSupporters)
- runTest {
- val expected = DonateState(
- loading = false,
- supporters = mockSupporters,
- )
- val actual = viewModel.state.value
- Assert.assertEquals(expected, actual)
- }
+ val expected = DonateState(
+ loading = false,
+ supporters = mockSupporters,
+ )
+ val actual = viewModel.state.value
+ Assert.assertEquals(expected, actual)
}
@Test
@@ -48,14 +44,13 @@ class DonateViewModelTest : CoreViewModelTest() {
stubFetchAndGetSupportersUseCase()
} returns Single.error(stubException)
- runTest {
- val expected = DonateState(
- loading = false,
- supporters = emptyList(),
- )
- val actual = viewModel.state.value
- Assert.assertEquals(expected, actual)
- }
+ val expected = DonateState(
+ loading = false,
+ supporters = emptyList(),
+ )
+ val actual = viewModel.state.value
+ Assert.assertEquals(expected, actual)
+
}
@Test
@@ -74,24 +69,4 @@ class DonateViewModelTest : CoreViewModelTest() {
stubMainRouter.navigateBack()
}
}
-
- @Test
- fun `given received LaunchUrl intent, expected OpenUrl effect delivered to effect collector`() {
- every {
- stubFetchAndGetSupportersUseCase()
- } returns Single.just(mockSupporters)
-
- val intent = mockk()
- every {
- intent::url.get()
- } returns "https://5598.is.my.favourite.com"
-
- viewModel.processIntent(intent)
-
- runTest {
- val expected = DonateEffect.OpenUrl("https://5598.is.my.favourite.com")
- val actual = viewModel.effect.firstOrNull()
- Assert.assertEquals(expected, actual)
- }
- }
}
diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerViewModelTest.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerViewModelTest.kt
new file mode 100644
index 00000000..2840ba85
--- /dev/null
+++ b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerViewModelTest.kt
@@ -0,0 +1,56 @@
+package com.shifthackz.aisdv1.presentation.screen.logger
+
+import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor
+import com.shifthackz.aisdv1.presentation.core.CoreViewModelTest
+import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.verify
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+
+class LoggerViewModelTest : CoreViewModelTest() {
+
+ private val stubFileProviderDescriptor = mockk()
+ private val stubMainRouter = mockk()
+
+ override fun initializeViewModel() = LoggerViewModel(
+ fileProviderDescriptor = stubFileProviderDescriptor,
+ mainRouter = stubMainRouter,
+ )
+
+ @Before
+ override fun initialize() {
+ super.initialize()
+ every {
+ stubFileProviderDescriptor.logsCacheDirPath
+ } returns "/tmp/local"
+ }
+
+ @Test
+ fun `initialize, read logs, expected loaded state`() {
+ runTest {
+ val expected = LoggerState(
+ loading = false,
+ text = ""
+ )
+ val actual = viewModel.state.value
+ Assert.assertEquals(expected, actual)
+ }
+ }
+
+ @Test
+ fun `given received NavigateBack intent, expected router navigateBack() called`() {
+ every {
+ stubMainRouter.navigateBack()
+ } returns Unit
+
+ viewModel.processIntent(LoggerIntent.NavigateBack)
+
+ verify {
+ stubMainRouter.navigateBack()
+ }
+ }
+}
diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsViewModelTest.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsViewModelTest.kt
index 2b93515e..5aa56c54 100644
--- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsViewModelTest.kt
+++ b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsViewModelTest.kt
@@ -16,6 +16,7 @@ import com.shifthackz.aisdv1.presentation.mocks.mockStableDiffusionModels
import com.shifthackz.aisdv1.presentation.model.Modal
import com.shifthackz.aisdv1.presentation.navigation.router.drawer.DrawerRouter
import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter
+import com.shifthackz.aisdv1.presentation.screen.debug.DebugMenuAccessor
import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupLaunchSource
import com.shifthackz.aisdv1.presentation.stub.stubSchedulersProvider
import io.mockk.every
@@ -46,6 +47,7 @@ class SettingsViewModelTest : CoreViewModelTest() {
private val stubBuildInfoProvider = mockk()
private val stubMainRouter = mockk()
private val stubDrawerRouter = mockk()
+ private val stubDebugMenuAccessor = mockk()
override fun initializeViewModel() = SettingsViewModel(
getStableDiffusionModelsUseCase = stubGetStableDiffusionModelsUseCase,
@@ -57,6 +59,7 @@ class SettingsViewModelTest : CoreViewModelTest() {
buildInfoProvider = stubBuildInfoProvider,
mainRouter = stubMainRouter,
drawerRouter = stubDrawerRouter,
+ debugMenuAccessor = stubDebugMenuAccessor,
)
@Before
@@ -90,15 +93,17 @@ class SettingsViewModelTest : CoreViewModelTest() {
}
@Test
- fun `given received Action AppVersion intent, expected router navigateToDebugMenu() method called`() {
+ fun `given received Action AppVersion intent, expected DeveloperModeUnlocked effect delivered to effect collector`() {
every {
- stubMainRouter.navigateToDebugMenu()
- } returns Unit
+ stubDebugMenuAccessor.invoke()
+ } returns true
viewModel.processIntent(SettingsIntent.Action.AppVersion)
- verify {
- stubMainRouter.navigateToDebugMenu()
+ runTest {
+ viewModel.effect.test {
+ Assert.assertEquals(SettingsEffect.DeveloperModeUnlocked, awaitItem())
+ }
}
}