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 <[email protected]> Co-authored-by: ShiftHackZ <[email protected]>
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/[email protected]

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+
}

0 commit comments

Comments
 (0)