Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class AppPreferences(
val sshUsername = stringPreference("sshUsername", "")
val publicKey = stringPreference("publicKey", "")
val privateKey = stringPreference("privateKey", "")
val passphrase = stringPreference("passphrase", "")

val appAuthToken = stringPreference("appAuthToken", "")

Expand All @@ -85,6 +86,7 @@ class AppPreferences(
username = this.sshUsername.get(),
publicKey = this.publicKey.get(),
privateKey = this.privateKey.get(),
passphrase = this.passphrase.get().ifEmpty { null }
)
}
}
Expand All @@ -96,6 +98,7 @@ class AppPreferences(
sshUsername.update(cred.username)
publicKey.update(cred.publicKey)
privateKey.update(cred.privateKey)
passphrase.update(cred.passphrase ?: "")
}

is Cred.UserPassPlainText -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,20 @@ fun SetupPage(
@Composable
fun SetupLine(
text: String,
content: @Composable ColumnScope.() -> Unit
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
content: @Composable ColumnScope.() -> Unit,
) {

Column(
modifier = Modifier
.padding(bottom = 18.dp)
.padding(bottom = 18.dp),
) {
Text(text = text)
content()
Column(
horizontalAlignment = horizontalAlignment
) {
content()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ sealed interface RemoteDestination : Parcelable {

@Parcelize
data object Cloning : RemoteDestination
// @Parcelize
// data class LoadKeysFromDevice(
// val provider: Provider?,
// val url: String
// ) : RemoteDestination

@Parcelize
data class LoadKeysFromDevice(
val url: String
) : RemoteDestination

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ sealed class Cred : Parcelable {
val username: String = "git",
val publicKey: String,
val privateKey: String,
val passphrase: String?,
) : Cred() {
override fun toString(): String {
return "Ssh(username=$username, publicKey=$publicKey, privateKeyLen=${privateKey.length})"
return "Ssh(username=$username, publicKey=$publicKey, privateKeyLen=${privateKey.length}, passphraseLen=${passphrase?.length})"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ fun GenerateNewSshKeysScreen(
cred = Cred.Ssh(
publicKey = publicKey.value,
privateKey = privateKey.value,
passphrase = null
),
onSuccess = onSuccess
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,88 +1,196 @@
package io.github.wiiznokes.gitnote.ui.screen.setup.remote

import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
import io.github.wiiznokes.gitnote.R
import io.github.wiiznokes.gitnote.ui.component.AppPage
import io.github.wiiznokes.gitnote.ui.component.SetupButton
import io.github.wiiznokes.gitnote.ui.component.SetupLine
import io.github.wiiznokes.gitnote.ui.component.SetupPage
import io.github.wiiznokes.gitnote.ui.model.Cred
import io.github.wiiznokes.gitnote.ui.model.StorageConfiguration
import io.github.wiiznokes.gitnote.ui.viewmodel.InitState
import io.github.wiiznokes.gitnote.ui.viewmodel.SetupViewModelI
import io.github.wiiznokes.gitnote.ui.viewmodel.SetupViewModelMock


private fun isKeyCorrect(key: String): Boolean {
return true
}


@Composable
fun LoadKeysFromDeviceScreen(
onBackClick: () -> Unit,
onNext: () -> Unit,
cloneState: InitState,
storageConfig: StorageConfiguration,
url: String,
vm: SetupViewModelI,
onClone: () -> Unit,
onSuccess: () -> Unit,

) {
AppPage(
title = "",
title = stringResource(R.string.custom_ssh_keys),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
onBackClick = onBackClick,
onBackClickEnabled = !cloneState.isLoading()
) {

val publicKey = rememberSaveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(TextFieldValue())
}
SetupPage {
val publicKey = rememberSaveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(TextFieldValue())
}

val privateKey = rememberSaveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(TextFieldValue())
}
val privateKey = rememberSaveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(TextFieldValue())
}

val privateKeyPassword = rememberSaveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(TextFieldValue())
}

SetupLine(
text = stringResource(R.string.public_key),
horizontalAlignment = Alignment.CenterHorizontally
) {
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = publicKey.value,
onValueChange = {
publicKey.value = it
},
label = {
Text(text = stringResource(R.string.public_key))
},
isError = !isKeyCorrect(publicKey.value.text),
)

LoadFileButton(
onTextLoaded = {
publicKey.value = publicKey.value.copy(text = it)
}
)
}

val privateKeyPassword = rememberSaveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(TextFieldValue())
SetupLine(
text = stringResource(R.string.private_key),
horizontalAlignment = Alignment.CenterHorizontally
) {
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = privateKey.value,
onValueChange = {
privateKey.value = it
},
label = {
Text(text = stringResource(R.string.private_key))
},
isError = !isKeyCorrect(privateKey.value.text),
)

LoadFileButton(
onTextLoaded = {
privateKey.value = privateKey.value.copy(text = it)
}
)
}

SetupLine(
text = stringResource(R.string.private_key_password)
) {
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = privateKeyPassword.value,
onValueChange = {
privateKeyPassword.value = it
},
label = {
Text(text = stringResource(R.string.private_key_password))
},
)
}

SetupLine(
text = stringResource(R.string.try_cloning)
) {
SetupButton(
text = stringResource(R.string.clone_repo),
enabled = isKeyCorrect(publicKey.value.text) && isKeyCorrect(privateKey.value.text),
onClick = {
vm.cloneRepo(
storageConfig = storageConfig,
remoteUrl = url,
cred = Cred.Ssh(
publicKey = publicKey.value.text,
privateKey = privateKey.value.text,
passphrase = privateKeyPassword.value.text.ifEmpty { null }
),
onSuccess = onSuccess
)

onClone()
},
)
}
}
}
}

OutlinedTextField(
value = publicKey.value,
onValueChange = {
publicKey.value = it
},
label = {
Text(text = "Public key")
},
singleLine = true,
isError = !isKeyCorrect(publicKey.value.text),
)
@Composable
private fun LoadFileButton(
onTextLoaded: (String) -> Unit
) {
val context = LocalContext.current

OutlinedTextField(
value = privateKey.value,
onValueChange = {
privateKey.value = it
},
label = {
Text(text = "Private key")
},
singleLine = true,
isError = !isKeyCorrect(privateKey.value.text),
)
val filePickerLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.OpenDocument()
) { uri ->
if (uri != null) {
val content = context.contentResolver.openInputStream(uri)
?.bufferedReader()
.use { it?.readText().orEmpty() }

OutlinedTextField(
value = privateKeyPassword.value,
onValueChange = {
privateKeyPassword.value = it
},
label = {
Text(text = "Private key password")
},
singleLine = true,
)
onTextLoaded(content)
}
}

Button(
onClick = {
onNext()
},
enabled = isKeyCorrect(publicKey.value.text) && isKeyCorrect(privateKey.value.text)
) {
Text(text = "Next")
Button(
onClick = {
filePickerLauncher.launch(arrayOf("text/*", "application/*"))
}
) {
Text(
text = stringResource(R.string.load_from_file)
)
}
}

@Preview
@Composable
private fun LoadKeysFromDeviceScreenPreview() {
LoadKeysFromDeviceScreen(
onBackClick = {},
cloneState = InitState.Idle,
vm = SetupViewModelMock(),
storageConfig = StorageConfiguration.App,
onSuccess = {},
onClone = {},
url = "url",
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,13 @@ fun RemoteScreen(
)
)
},
onCustom = {
navController.navigate(
RemoteDestination.LoadKeysFromDevice(
url = remoteDestination.url
)
)
}
)

is GenerateNewKeys -> GenerateNewSshKeysScreen(
Expand All @@ -145,6 +152,16 @@ fun RemoteScreen(
onClone = { navController.navigate(RemoteDestination.Cloning) }
)

is RemoteDestination.LoadKeysFromDevice -> LoadKeysFromDeviceScreen(
onBackClick = { navController.pop() },
cloneState = initState,
storageConfig = storageConfig,
url = remoteDestination.url,
vm = vm,
onSuccess = onInitSuccess,
onClone = { navController.navigate(RemoteDestination.Cloning) }
)

is RemoteDestination.Credentials -> CredentialsScreen(
onBackClick = { navController.pop() },
storageConfig = storageConfig,
Expand Down
Loading
Loading