Skip to content

Commit a1edcc7

Browse files
ComicSASShiftHackZ
andauthored
Connect to localhost server dialog (#213)
* Implemented connect to localhost with ssh-tunneling warning dialog * Updated README.md & strings.xml * Fix Chinese Simplified version * Fixed strings declaration * Updated url validation & strings.xml + refactoring * Fix workflow --------- Co-authored-by: ShiftHackZ <41302477+ShiftHackZ@users.noreply.github.com> Co-authored-by: ShiftHackZ <dmitriy@moroz.cc>
1 parent f834ce5 commit a1edcc7

File tree

16 files changed

+110
-26
lines changed

16 files changed

+110
-26
lines changed

.github/workflows/android_test.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ jobs:
1010
test-feature:
1111
runs-on: ubuntu-latest
1212

13-
needs: build
14-
1513
steps:
1614
- name: Checkout code
1715
uses: actions/checkout@v4.1.0

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ User interface of the app is translated for languages listed in this table:
112112
| Ukrainian | 0.1.0 | `Translated` |
113113
| Turkish | 0.4.1 | `Translated` |
114114
| Russian | 0.5.5 | `Translated` |
115+
| Chinese (Simplified) | 0.6.2 | `Translated` |
115116

116117
Any contributions to the translations are welcome.
117118

app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ android {
1414
namespace 'com.shifthackz.aisdv1.app'
1515
defaultConfig {
1616
applicationId "com.shifthackz.aisdv1.app"
17-
versionName "0.6.1"
18-
versionCode 180
17+
versionName "0.6.2"
18+
versionCode 182
1919

2020
buildConfigField "String", "IMAGE_CDN_URL", "\"https://random.imagecdn.app/\""
2121
buildConfigField "String", "HUGGING_FACE_URL", "\"https://huggingface.co/\""

core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/url/UrlValidator.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ interface UrlValidator {
99
sealed interface Error {
1010
data object Empty : Error
1111
data object BadScheme : Error
12+
data object BadPort : Error
1213
data object Invalid : Error
1314
data object Localhost : Error
1415
}

core/validation/src/main/java/com/shifthackz/aisdv1/core/validation/url/UrlValidatorImpl.kt

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.shifthackz.aisdv1.core.validation.url
33
import android.util.Patterns
44
import android.webkit.URLUtil
55
import com.shifthackz.aisdv1.core.validation.ValidationResult
6+
import java.net.URI
67

78
internal class UrlValidatorImpl : UrlValidator {
89

@@ -19,7 +20,11 @@ internal class UrlValidatorImpl : UrlValidator {
1920
isValid = false,
2021
validationError = UrlValidator.Error.BadScheme,
2122
)
22-
input.contains(LOCALHOST_IPV4) -> ValidationResult(
23+
!isPortValid(input) -> ValidationResult(
24+
isValid = false,
25+
validationError = UrlValidator.Error.BadPort,
26+
)
27+
isLocalhostUrl(input) -> ValidationResult(
2328
isValid = false,
2429
validationError = UrlValidator.Error.Localhost,
2530
)
@@ -34,9 +39,29 @@ internal class UrlValidatorImpl : UrlValidator {
3439
else -> ValidationResult(isValid = true)
3540
}
3641

42+
private fun isPortValid(url: String): Boolean = try {
43+
val uri = URI(url)
44+
val port = uri.port
45+
port in 1..65535 || port == -1
46+
} catch (e: Exception) {
47+
false
48+
}
49+
50+
private fun isLocalhostUrl(url: String): Boolean = try {
51+
val uri = URI(url)
52+
val host = uri.host
53+
host.equals(LOCALHOST_ALIAS, true)
54+
|| host.equals(LOCALHOST_IPV4, true)
55+
|| host.equals(LOCALHOST_IPV6, true)
56+
} catch (e: Exception) {
57+
false
58+
}
59+
3760
companion object {
3861
private const val SCHEME_HTTPS = "https://"
3962
private const val SCHEME_HTTP = "http://"
63+
private const val LOCALHOST_ALIAS = "localhost"
4064
private const val LOCALHOST_IPV4 = "127.0.0.1"
65+
private const val LOCALHOST_IPV6 = "[::1]"
4166
}
4267
}

presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ModalRenderer.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ fun ModalRenderer(
192192
onDismissRequest = dismiss,
193193
)
194194

195-
Modal.ExportInProgress -> ProgressDialog(
195+
Modal.ExportInProgress -> ProgressDialog(
196196
titleResId = R.string.exporting_progress_title,
197197
subTitleResId = R.string.exporting_progress_sub_title,
198198
canDismiss = false,
@@ -242,5 +242,14 @@ fun ModalRenderer(
242242
onDismissRequest = dismiss,
243243
onResult = { processIntent(ImageToImageIntent.UpdateImage(it)) }
244244
)
245+
246+
Modal.ConnectLocalHost -> DecisionInteractiveDialog(
247+
title = R.string.interaction_warning_title.asUiText(),
248+
text = R.string.interaction_warning_localhost_sub_title.asUiText(),
249+
confirmActionResId = R.string.action_connect,
250+
dismissActionResId = R.string.cancel,
251+
onConfirmAction = { processIntent(ServerSetupIntent.ConnectToLocalHost) },
252+
onDismissRequest = dismiss,
253+
)
245254
}
246255
}

presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/Modal.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ sealed interface Modal {
2222

2323
data object ExportInProgress : Modal
2424

25+
data object ConnectLocalHost : Modal
26+
2527

2628
@Immutable
2729
data class SelectSdModel(val models: List<String>, val selected: String) : Modal
@@ -64,13 +66,13 @@ sealed interface Modal {
6466
sealed interface Image : Modal {
6567

6668
@Immutable
67-
data class Single(val result: AiGenerationResult, val autoSaveEnabled: Boolean): Image
69+
data class Single(val result: AiGenerationResult, val autoSaveEnabled: Boolean) : Image
6870

6971
@Immutable
70-
data class Batch(val results: List<AiGenerationResult>, val autoSaveEnabled: Boolean): Image
72+
data class Batch(val results: List<AiGenerationResult>, val autoSaveEnabled: Boolean) : Image
7173

7274
@Immutable
73-
data class Crop(val bitmap: Bitmap): Image
75+
data class Crop(val bitmap: Bitmap) : Image
7476

7577
companion object {
7678
fun create(list: List<AiGenerationResult>, autoSaveEnabled: Boolean): Image =

presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupIntent.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ sealed interface ServerSetupIntent : MviIntent {
4646

4747
data object LaunchManageStoragePermission : ServerSetupIntent
4848

49+
data object ConnectToLocalHost : ServerSetupIntent
50+
4951
sealed class LaunchUrl : ServerSetupIntent, KoinComponent {
5052

5153
protected val linksProvider: LinksProvider by inject()

presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/ServerSetupViewModel.kt

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ class ServerSetupViewModel(
129129
it.copy(step = ServerSetupState.Step.CONFIGURE)
130130
}
131131

132-
ServerSetupState.Step.CONFIGURE -> connectToServer()
132+
ServerSetupState.Step.CONFIGURE -> validateAndConnectToServer()
133133
}
134134

135135
is ServerSetupIntent.UpdateAuthType -> updateState {
@@ -200,10 +200,16 @@ class ServerSetupViewModel(
200200
is ServerSetupIntent.UpdateStabilityAiApiKey -> updateState {
201201
it.copy(stabilityAiApiKey = intent.key)
202202
}
203+
204+
ServerSetupIntent.ConnectToLocalHost -> connectToServer()
203205
}
204206

205-
private fun connectToServer() {
207+
private fun validateAndConnectToServer() {
206208
if (!validate()) return
209+
connectToServer()
210+
}
211+
212+
private fun connectToServer() {
207213
emitEffect(ServerSetupEffect.HideKeyboard)
208214
!when (currentState.mode) {
209215
ServerSource.HORDE -> connectToHorde()
@@ -212,8 +218,10 @@ class ServerSetupViewModel(
212218
ServerSource.HUGGING_FACE -> connectToHuggingFace()
213219
ServerSource.OPEN_AI -> connectToOpenAi()
214220
ServerSource.STABILITY_AI -> connectToStabilityAi()
215-
}.doOnSubscribe { setScreenModal(Modal.Communicating(canCancel = false)) }
216-
.subscribeOnMainThread(schedulersProvider).subscribeBy(::errorLog) { result ->
221+
}
222+
.doOnSubscribe { setScreenModal(Modal.Communicating(canCancel = false)) }
223+
.subscribeOnMainThread(schedulersProvider)
224+
.subscribeBy(::errorLog) { result ->
217225
result.fold(
218226
onSuccess = { onSetupComplete() },
219227
onFailure = { t ->
@@ -230,8 +238,8 @@ class ServerSetupViewModel(
230238
else {
231239
val serverUrlValidation = urlValidator(currentState.serverUrl)
232240
var isValid = serverUrlValidation.isValid
233-
updateState {
234-
var newState = it.copy(
241+
updateState { state ->
242+
var newState = state.copy(
235243
serverUrlValidationError = serverUrlValidation.mapToUi()
236244
)
237245
if (currentState.authType == ServerSetupState.AuthType.HTTP_BASIC) {
@@ -243,6 +251,12 @@ class ServerSetupViewModel(
243251
)
244252
isValid = isValid && loginValidation.isValid && passwordValidation.isValid
245253
}
254+
if (serverUrlValidation.validationError is UrlValidator.Error.Localhost
255+
&& newState.loginValidationError == null
256+
&& newState.passwordValidationError == null
257+
) {
258+
newState = newState.copy(screenModal = Modal.ConnectLocalHost)
259+
}
246260
newState
247261
}
248262
isValid

presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/forms/Automatic1111Form.kt

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,12 @@ fun Automatic1111Form(
6666
supportingText = state.serverUrlValidationError
6767
?.takeIf { !state.demoMode }
6868
?.let { { Text(it.asString(), color = MaterialTheme.colorScheme.error) } },
69+
maxLines = 1,
6970
)
7071
if (!state.demoMode) {
7172
DropdownTextField(
7273
modifier = fieldModifier,
73-
label = "Authorization".asUiText(),
74+
label = R.string.auth_title.asUiText(),
7475
items = ServerSetupState.AuthType.entries,
7576
value = state.authType,
7677
onItemSelected = {
@@ -85,7 +86,6 @@ fun Automatic1111Form(
8586
)
8687
when (state.authType) {
8788
ServerSetupState.AuthType.HTTP_BASIC -> {
88-
8989
TextField(
9090
modifier = fieldModifier,
9191
value = state.login,
@@ -97,6 +97,7 @@ fun Automatic1111Form(
9797
supportingText = state.loginValidationError?.let {
9898
{ Text(it.asString(), color = MaterialTheme.colorScheme.error) }
9999
},
100+
maxLines = 1,
100101
)
101102
TextField(
102103
modifier = fieldModifier,
@@ -107,8 +108,11 @@ fun Automatic1111Form(
107108
label = { Text(stringResource(id = R.string.hint_password)) },
108109
isError = state.passwordValidationError != null,
109110
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
110-
visualTransformation = if (state.passwordVisible) VisualTransformation.None
111-
else PasswordVisualTransformation(),
111+
visualTransformation = if (state.passwordVisible) {
112+
VisualTransformation.None
113+
} else {
114+
PasswordVisualTransformation()
115+
},
112116
supportingText = state.passwordValidationError?.let {
113117
{ Text(it.asString(), color = MaterialTheme.colorScheme.error) }
114118
},
@@ -126,7 +130,8 @@ fun Automatic1111Form(
126130
},
127131
content = { Icon(image, description) },
128132
)
129-
}
133+
},
134+
maxLines = 1,
130135
)
131136
}
132137
else -> Unit
@@ -171,4 +176,4 @@ fun Automatic1111Form(
171176
style = MaterialTheme.typography.bodyMedium,
172177
)
173178
}
174-
}
179+
}

presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/setup/mappers/ServerSetupValidationUrlMapper.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ fun ValidationResult<UrlValidator.Error>.mapToUi(): UiText? {
1010
if (this.isValid) return null
1111
return when (validationError as UrlValidator.Error) {
1212
UrlValidator.Error.BadScheme -> R.string.error_invalid_scheme
13+
UrlValidator.Error.BadPort -> R.string.error_invalid_port
1314
UrlValidator.Error.Empty -> R.string.error_empty_url
1415
UrlValidator.Error.Invalid -> R.string.error_invalid_url
15-
UrlValidator.Error.Localhost -> R.string.error_localhost_url
16-
}.asUiText()
16+
UrlValidator.Error.Localhost -> null
17+
}?.asUiText()
1718
}

presentation/src/main/res/values-ru/strings.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
<string name="hint_in_paint_area">Область Inpaint</string>
9090
<string name="hint_autodetect">Авто</string>
9191

92+
<string name="auth_title">Авторизация</string>
9293
<string name="auth_anonymous">Анонимно</string>
9394
<string name="auth_http_basic">HTTP Базовая</string>
9495

@@ -208,6 +209,9 @@
208209
<string name="interaction_export_sub_title">Эта функция экспортирует все изображения галереи в архив *.zip. Этот процесс может длиться долго, если у вас много изображений. Хотите продолжить?</string>
209210
<string name="interaction_cache_sub_title">Это приведет к сбросу настроек программы и удалению всех созданных изображений. Вы хотите продолжить?</string>
210211

212+
<string name="interaction_warning_title">Предупреждение</string>
213+
<string name="interaction_warning_localhost_sub_title">Вы пытаетесь подключиться к локальному серверу localhost (127.0.0.1).\n\nЭто может не сработать, если на вашем Android-устройстве не настроено туннелирование ssh или другой механизм переадресации портов.</string>
214+
211215
<string name="interaction_delete_generation_title">Удалить изображение</string>
212216
<string name="interaction_delete_generation_sub_title">Вы уверены, что хотите окончательно удалить это изображение?</string>
213217

@@ -230,6 +234,7 @@
230234
<string name="error_empty_field">Поле не может быть пустым</string>
231235
<string name="error_invalid">Неверные данные</string>
232236
<string name="error_invalid_scheme">URL должно начинаться с <b>http://</b> или <b>https://</b></string>
237+
<string name="error_invalid_port">Порт должен быть в диапазоне от 0 до 65535</string>
233238
<string name="error_invalid_url">Недействительный URL-адрес сервера</string>
234239
<string name="error_localhost_url">Вы не можете использовать localhost (127.0.0.1) URL-адрес для подключения к серверу.</string>
235240
<string name="error_min_size">Минимальный размер %1$s</string>

presentation/src/main/res/values-tr/strings.xml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,14 @@
8989
<string name="hint_in_paint_area">Inpaint alan</string>
9090
<string name="hint_autodetect">Otomatik olarak algıla</string>
9191

92+
<string name="auth_title">Yetki</string>
9293
<string name="auth_anonymous">Anonim</string>
9394
<string name="auth_http_basic">HTTP Temel</string>
9495

9596
<string name="hint_server_setup_title">Lütfen Stable Diffusion WebUI(AUTOMATIC1111) URL adresinizi yazın.</string>
9697
<string name="hint_valid_urls">Bazı sunucu örnekleri:\n• http://192.168.0.2:7860\n• http://alanadiniz.com:7860\n• https://alanadiniz.com</string>
9798
<string name="hint_demo_mode">This mode allows you to test the application behavior, even if you don\'t have Stable Diffusion WebUI server.\n\nIn demo mode app ignores user prompt, does not use AI server, and returns some mock images.</string>
98-
<string name="hint_args_warning" tools:ignore="TypographyDashes">Before connecting ensure that:\n• you are running AUTOMATIC1111 WebUI with flags --api --listen\n• your firewall is not blocking 7860 port\n• phone is on the same WiFi with your PC</string>
99+
<string name="hint_args_warning" tools:ignore="TypographyDashes,Typos">Before connecting ensure that:\n• you are running AUTOMATIC1111 WebUI with flags --api --listen\n• your firewall is not blocking 7860 port\n• phone is on the same WiFi with your PC</string>
99100

100101
<string name="hint_server_horde_title">Horde AI bulutuna bağlanın</string>
101102
<string name="hint_server_horde_sub_title">Horde AI, Görüntü oluşturma çalışanları ve metin oluşturma çalışanlarından oluşan kitle kaynaklı dağıtılmış bir kümedir.</string>
@@ -128,7 +129,7 @@
128129
<string name="home_tab_settings">Ayarlar</string>
129130

130131
<string name="gallery_tab_image">Resim</string>
131-
<string name="gallery_tab_original">Orjinal</string>
132+
<string name="gallery_tab_original">Orijinal</string>
132133
<string name="gallery_tab_info">Bilgi</string>
133134

134135
<string name="title_text_to_image">Yazıdan Resime</string>
@@ -208,6 +209,9 @@
208209
<string name="interaction_export_sub_title">Bu işlem bütün galerideki resimleri tek bir .zip arşivi dosyası olarak dışa aktaracaktır. Galerinizin boyutuna göre bu işlem uzun bir zaman alabailir. Devam etmek istiyor musunuz?</string>
209210
<string name="interaction_cache_sub_title">Bu işlem bütün uygulama ayarlarını ve oluşturulan resimleri silecektir. Devam etmek istiyor musunuz?</string>
210211

212+
<string name="interaction_warning_title">Uyarı</string>
213+
<string name="interaction_warning_localhost_sub_title">Localhost (127.0.0.1) sunucusuna bağlanmaya çalışıyorsunuz.\n\nAndroid cihazınızda ssh tüneli veya başka bir bağlantı noktası yönlendirme mekanizması kurulu olmadığı sürece çalışmayabilir.</string>
214+
211215
<string name="interaction_delete_generation_title">Resmi sil</string>
212216
<string name="interaction_delete_generation_sub_title">Kalıcı olarak bu resmi silmek istediğinize emin misiniz?</string>
213217

@@ -230,6 +234,7 @@
230234
<string name="error_empty_field">Alan boş olmamalıdır</string>
231235
<string name="error_invalid">Geçerli olmayan bilgi girişi</string>
232236
<string name="error_invalid_scheme">Sunucu adresi <b>http://</b> veya <b>https://</b> ile başlamalıdır.</string>
237+
<string name="error_invalid_port">Port 0 ile 65535 aralığında olmalıdır</string>
233238
<string name="error_invalid_url">Geçersiz sunucu adresi.</string>
234239
<string name="error_localhost_url">Sunucuya bağlanmak için localhost (127.0.0.1) URL adresi kullanamazsınız.</string>
235240
<string name="error_min_size">Asgari boyut %1$s</string>

presentation/src/main/res/values-uk/strings.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
<string name="hint_in_paint_area">Область Inpaint</string>
9090
<string name="hint_autodetect">Авто</string>
9191

92+
<string name="auth_title">Авторизація</string>
9293
<string name="auth_anonymous">Анонімна</string>
9394
<string name="auth_http_basic">HTTP Базова</string>
9495

@@ -208,6 +209,9 @@
208209
<string name="interaction_export_sub_title">Ця функція експортує всі зображення галереї у архів *.zip. Цей процес може тривати довго, якщо у вас багато зображень. Бажаєте продовжити?</string>
209210
<string name="interaction_cache_sub_title">Це призведе до скидання налаштувань програми та видалення всіх створених зображень. Ви бажаєте продовжити?</string>
210211

212+
<string name="interaction_warning_title">Попередження</string>
213+
<string name="interaction_warning_localhost_sub_title">Ви намагаєтеся підключитися до локального сервера localhost (127.0.0.1).\n\nЦе може не працювати, якщо на вашому пристрої Android не налаштовано SSH-тунелювання або інший механізм переадресації портів.</string>
214+
211215
<string name="interaction_delete_generation_title">Видалити зображення</string>
212216
<string name="interaction_delete_generation_sub_title">Ви впевнені, що хочете остаточно видалити це зображення?</string>
213217

@@ -230,6 +234,7 @@
230234
<string name="error_empty_field">Поле не може бути пустим</string>
231235
<string name="error_invalid">Неправильні дані</string>
232236
<string name="error_invalid_scheme">URL має починатися з <b>http://</b> або <b>https://</b></string>
237+
<string name="error_invalid_port">Порт має бути в діапазоні від 0 до 65535</string>
233238
<string name="error_invalid_url">Недійсна URL-адреса сервера</string>
234239
<string name="error_localhost_url">Ви не можете використовувати localhost (127.0.0.1) URL-адресу для підключення до сервера.</string>
235240
<string name="error_min_size">Мінімальний розмір %1$s</string>

0 commit comments

Comments
 (0)