Merge branch 'mantra' into claude/key-recovery-functionality-00e269
Some checks failed
Material Design conformance / budgets (push) Has been cancelled
Material Design conformance / tests (push) Has been cancelled

This commit is contained in:
Kgothatso Ngako
2026-09-08 09:31:16 +02:00
3 changed files with 19 additions and 264 deletions

View File

@@ -212,6 +212,7 @@
<string name="shared_key">Shared key</string>
<string name="sign">Sign</string>
<string name="sign_in">Sign in</string>
<string name="sign_in_is_not_yet_available_while_mantra_is">Sign in is not yet available while Mantra is in alpha testing.</string>
<string name="sign_in_to_nsec">Sign in to nsec</string>
<string name="sign_in_to_torch_via_nsec_or_remote_signer">Sign in to Mantra via nsec, or remote signer</string>
<string name="sign_in_with_an_npub">Sign in with an npub</string>

View File

@@ -2,273 +2,42 @@ package press.mantra.compose.ui.composable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Create
import androidx.compose.material.icons.filled.NavigateNext
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.LoadingIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.quartz.nip19Bech32.entities.NPub
import com.vitorpamplona.quartz.nip19Bech32.entities.NSec
import press.mantra.compose.ui.theme.spacing
import mantra.composeapp.generated.resources.Res
import org.jetbrains.compose.resources.stringResource
import mantra.composeapp.generated.resources.be_sure_to_keep_this_nsec_safe
import mantra.composeapp.generated.resources.enter_the_nsec_or_npub_read_only_that_you
import mantra.composeapp.generated.resources.next
import mantra.composeapp.generated.resources.nsec_npub_nip_05_static_address
import mantra.composeapp.generated.resources.sign_in
import mantra.composeapp.generated.resources.sign_in_to_nsec
import mantra.composeapp.generated.resources.sign_in_with_an_npub
import mantra.composeapp.generated.resources.something_went_wrong_and_we_were_unable_to
import mantra.composeapp.generated.resources.this_will_give_you_read_only_access_to_the
import mantra.composeapp.generated.resources.this_will_give_you_write_access_to_the
import androidx.compose.material3.SnackbarHost
import mantra.composeapp.generated.resources.sign_in_is_not_yet_available_while_mantra_is
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
import press.mantra.compose.ui.theme.readableContent
import press.mantra.compose.ui.theme.ConformancePreviews
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
// The message is the whole screen while sign in is held back, so it is centred in
// the window rather than given the leading edge the readable column normally keeps.
@Composable
fun SignInToProfileScreen(
initialSignInToProfileUIState: press.mantra.compose.ui.view.state.SignInToProfileUIState = press.mantra.compose.ui.view.state.SignInToProfileUIState.InputPrompt,
nostrRepository: press.mantra.compose.repository.NostrRepository
) {
val signInToProfileViewModel: press.mantra.compose.ui.view.model.SignInToProfileViewModel = viewModel (
factory = press.mantra.compose.ui.view.model.SignInToProfileViewModel.factory(
initialSignInToProfileUIState,
nostrRepository
)
)
// imePadding: this screen has a text field, and without it the software
// keyboard covers whatever is being typed into.
Scaffold(
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
modifier = Modifier.imePadding()) { innerPadding ->
fun SignInToProfileScreen() {
Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding ->
Column(
modifier = Modifier.padding(innerPadding)
modifier = Modifier.fillMaxSize().padding(innerPadding)
.readableContent()
.padding(MaterialTheme.spacing.screenMargin),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Column(
modifier = Modifier.fillMaxWidth().padding(
MaterialTheme.spacing.space125
),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.space250)
) {
when (val signInToProfileUIState = signInToProfileViewModel.signInToProfileUIState.value) {
is press.mantra.compose.ui.view.state.SignInToProfileUIState.InputPrompt -> {
Text(
stringResource(Res.string.sign_in),
style = MaterialTheme.typography.headlineSmall
)
TextField(
modifier = Modifier.fillMaxWidth(),
state = signInToProfileViewModel.signInToProfileFormState.textFieldForm.textFieldState,
isError = signInToProfileViewModel.signInToProfileFormState.textFieldForm.errorMessage.value != null,
supportingText = signInToProfileViewModel.signInToProfileFormState.textFieldForm.errorMessage.value?.let {
{
Text(
text = it,
modifier = Modifier.padding(
bottom = MaterialTheme.spacing.compactPadding
)
)
}
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Done
),
label = {
Text(
text = stringResource(Res.string.nsec_npub_nip_05_static_address),
maxLines = 1,
)
},
placeholder = {
Text(
text = stringResource(Res.string.enter_the_nsec_or_npub_read_only_that_you),
maxLines = 1,
)
},
leadingIcon = {
Icon(
Icons.Default.Create,
contentDescription = "Write note"
)
}
)
Button(
onClick = {
signInToProfileViewModel.signInToProfileUIState.value = signInToProfileViewModel.validateCredential().let { credentialEntity ->
when (credentialEntity) {
is NSec -> {
press.mantra.compose.ui.view.state.SignInToProfileUIState.ConfirmNsecSignIn(
nsec = signInToProfileViewModel.signInToProfileFormState.textFieldForm.textFieldState.text.toString(),
hexKey = credentialEntity.hex
)
}
is NPub -> {
press.mantra.compose.ui.view.state.SignInToProfileUIState.ConfirmNpubSignIn(
npub = signInToProfileViewModel.signInToProfileFormState.textFieldForm.textFieldState.text.toString(),
hexKey = credentialEntity.hex
)
}
else -> {
press.mantra.compose.ui.view.state.SignInToProfileUIState.Error
}
}
}
},
) {
Text(
stringResource(Res.string.next)
)
Icon(
Icons.Default.NavigateNext,
contentDescription = "Next"
)
}
}
is press.mantra.compose.ui.view.state.SignInToProfileUIState.ConfirmNpubSignIn-> {
Spacer(
modifier = Modifier.weight(1f)
)
Text(
text = stringResource(Res.string.sign_in_with_an_npub),
style = MaterialTheme.typography.headlineMedium
)
Text(
text = signInToProfileUIState.npub,
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center
)
Text(
text = signInToProfileUIState.hexKey,
style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Center
)
Spacer(
modifier = Modifier.weight(4f)
)
Text(
text = stringResource(Res.string.this_will_give_you_read_only_access_to_the),
style = MaterialTheme.typography.labelSmall,
textAlign = TextAlign.Center
)
if (signInToProfileViewModel.isActionPending.value) {
LoadingIndicator(
modifier = Modifier
)
} else {
Button(
onClick = {
signInToProfileViewModel.signInToAccount()
}
) {
Text(
text = stringResource(Res.string.sign_in)
)
}
}
}
is press.mantra.compose.ui.view.state.SignInToProfileUIState.ConfirmNsecSignIn-> {
Spacer(
modifier = Modifier.weight(1f)
)
Text(
text = stringResource(Res.string.sign_in_to_nsec),
style = MaterialTheme.typography.headlineMedium
)
Text(
text = stringResource(Res.string.be_sure_to_keep_this_nsec_safe),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyMedium
)
Spacer(
modifier = Modifier.weight(4f)
)
Text(
text = stringResource(Res.string.this_will_give_you_write_access_to_the),
style = MaterialTheme.typography.labelSmall,
textAlign = TextAlign.Center
)
if (signInToProfileViewModel.isActionPending.value) {
LoadingIndicator(
modifier = Modifier
)
} else {
Button(
onClick = {
signInToProfileViewModel.signInToAccount()
}
) {
Text(
text = stringResource(Res.string.sign_in)
)
}
}
}
is press.mantra.compose.ui.view.state.SignInToProfileUIState.Error -> {
Spacer(
modifier = Modifier.weight(1f)
)
Text(
text = stringResource(Res.string.something_went_wrong_and_we_were_unable_to),
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center
)
Spacer(
modifier = Modifier.weight(1f)
)
}
}
}
Text(
text = stringResource(Res.string.sign_in_is_not_yet_available_while_mantra_is),
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center
)
}
}
}
@@ -280,20 +49,7 @@ private fun SignInToProfileScreenPreview() {
Surface(
modifier = Modifier.fillMaxSize()
) {
SignInToProfileScreen(
initialSignInToProfileUIState =
press.mantra.compose.ui.view.state.SignInToProfileUIState.InputPrompt,
// SignInToProfileUIState.Error,
// SignInToProfileUIState.ConfirmNpubSignIn(
// npub = "npub1uh366qnj68a7atvwgwdy4569xd6v5jtferftapqztwjwlzh9553q47pqsx",
// hexKey = "e5e3ad0272d1fbeead8e439a4ad3453374ca4969c8d2be84025ba4ef8ae5a522"
// ),
// SignInToProfileUIState.ConfirmNsecSignIn(
// nsec = "nsecthingsaretobekeptsecret",
// hexKey = "e5e3ad0272d1fbeead8e439a4ad3453374ca4969c8d2be84025ba4ef8ae5a522"
// ),
nostrRepository = press.mantra.compose.repository.NostrRepository.NO_OP_NOSTR_REPOSITORY
)
SignInToProfileScreen()
}
}
}
}

View File

@@ -632,9 +632,7 @@ fun MantraNavHost(
)
}
composable<SignInRoute> {
SignInToProfileScreen(
nostrRepository = databaseNostrRepository
)
SignInToProfileScreen()
}
composable<UnsignedProfileRoute> { backStackEntry ->
backStackEntry.toRoute<UnsignedProfileRoute>()