Merge pull request #94 from Radiokot/fix/ui_issues

Fix some UI issues
This commit is contained in:
Vitor Pamplona 2023-02-07 15:59:26 -05:00 committed by GitHub
commit 9faf6a2fbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 184 additions and 194 deletions

View File

@ -5,32 +5,20 @@ import android.util.LruCache
import android.util.Patterns import android.util.Patterns
import androidx.compose.animation.animateContentSize import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.Button import androidx.compose.material.*
import androidx.compose.material.ButtonDefaults import androidx.compose.runtime.*
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.os.ConfigurationCompat import androidx.core.os.ConfigurationCompat
import androidx.navigation.NavController import androidx.navigation.NavController
@ -48,12 +36,12 @@ import com.vitorpamplona.amethyst.model.toByteArray
import com.vitorpamplona.amethyst.model.toNote import com.vitorpamplona.amethyst.model.toNote
import com.vitorpamplona.amethyst.service.Nip19 import com.vitorpamplona.amethyst.service.Nip19
import com.vitorpamplona.amethyst.ui.note.toShortenHex import com.vitorpamplona.amethyst.ui.note.toShortenHex
import nostr.postr.toNpub
import java.net.MalformedURLException import java.net.MalformedURLException
import java.net.URISyntaxException import java.net.URISyntaxException
import java.net.URL import java.net.URL
import java.util.Locale import java.util.*
import java.util.regex.Pattern import java.util.regex.Pattern
import nostr.postr.toNpub
val imageExtension = Pattern.compile("(.*/)*.+\\.(png|jpg|gif|bmp|jpeg|webp|svg)$") val imageExtension = Pattern.compile("(.*/)*.+\\.(png|jpg|gif|bmp|jpeg|webp|svg)$")
val videoExtension = Pattern.compile("(.*/)*.+\\.(mp4|avi|wmv|mpg|amv|webm)$") val videoExtension = Pattern.compile("(.*/)*.+\\.(mp4|avi|wmv|mpg|amv|webm)$")
@ -195,25 +183,35 @@ fun RichTextViewer(content: String, canPreview: Boolean, tags: List<List<String>
if (source != null && target != null) { if (source != null && target != null) {
if (source != target) { if (source != target) {
FlowRow() { val clickableTextStyle = SpanStyle(color = MaterialTheme.colors.primary.copy(alpha = 0.52f))
Text(
text = "Auto-translated from ", val annotatedTranslationString= buildAnnotatedString {
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), append("Auto-translated from ")
)
ClickableText( withStyle(clickableTextStyle) {
text = AnnotatedString(Locale(source).displayName), pushStringAnnotation("showOriginal", true.toString())
onClick = { showOriginal = true }, append(Locale(source).displayName)
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary.copy(alpha = 0.52f)) }
)
Text( append(" to ")
text = " to ",
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), withStyle(clickableTextStyle) {
) pushStringAnnotation("showOriginal", false.toString())
ClickableText( append(Locale(target).displayName)
text = AnnotatedString(Locale(target).displayName), }
onClick = { showOriginal = false }, }
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary.copy(alpha = 0.52f))
) ClickableText(
text = annotatedTranslationString,
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)),
overflow = TextOverflow.Visible,
maxLines = 3
) { spanOffset ->
annotatedTranslationString.getStringAnnotations(spanOffset, spanOffset)
.firstOrNull()
?.also { span ->
showOriginal = span.item.toBoolean()
}
} }
} }
} }

View File

@ -228,7 +228,7 @@ private fun ProfileHeader(
contentPadding = PaddingValues(0.dp) contentPadding = PaddingValues(0.dp)
) { ) {
Icon( Icon(
tint = if (isSystemInDarkTheme()) Color.White else Color.Black, tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
imageVector = Icons.Default.MoreVert, imageVector = Icons.Default.MoreVert,
contentDescription = "More Options", contentDescription = "More Options",
) )

View File

@ -1,37 +1,18 @@
package com.vitorpamplona.amethyst.ui.screen package com.vitorpamplona.amethyst.ui.screen
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.Button import androidx.compose.foundation.verticalScroll
import androidx.compose.material.ButtonDefaults import androidx.compose.material.*
import androidx.compose.material.Checkbox
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Visibility import androidx.compose.material.icons.outlined.Visibility
import androidx.compose.material.icons.outlined.VisibilityOff import androidx.compose.material.icons.outlined.VisibilityOff
import androidx.compose.runtime.Composable import androidx.compose.runtime.*
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@ -40,16 +21,13 @@ import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.*
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.components.sendMail
@Composable @Composable
fun LoginPage(accountViewModel: AccountStateViewModel) { fun LoginPage(accountViewModel: AccountStateViewModel) {
@ -59,12 +37,144 @@ fun LoginPage(accountViewModel: AccountStateViewModel) {
var termsAcceptanceIsRequired by remember { mutableStateOf("") } var termsAcceptanceIsRequired by remember { mutableStateOf("") }
val uri = LocalUriHandler.current val uri = LocalUriHandler.current
Box(modifier = Modifier.fillMaxSize()) { Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.SpaceBetween
) {
// The first child is glued to the top.
// Hence we have nothing at the top, an empty box is used.
Box(modifier = Modifier.height(0.dp))
// The second child, this column, is centered vertically.
Column(
modifier = Modifier
.padding(20.dp)
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Image(
painterResource(id = R.drawable.amethyst),
contentDescription = "App Logo",
modifier = Modifier.size(200.dp),
contentScale = ContentScale.Inside
)
Spacer(modifier = Modifier.height(40.dp))
var showPassword by remember {
mutableStateOf(false)
}
OutlinedTextField(
value = key.value,
onValueChange = { key.value = it },
keyboardOptions = KeyboardOptions(
autoCorrect = false,
keyboardType = KeyboardType.Ascii,
imeAction = ImeAction.Go
),
placeholder = {
Text(
text = "nsec / npub / hex private key",
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
},
trailingIcon = {
IconButton(onClick = { showPassword = !showPassword }) {
Icon(
imageVector = if (showPassword) Icons.Outlined.VisibilityOff else Icons.Outlined.Visibility,
contentDescription = if (showPassword) "Show Password" else "Hide Password"
)
}
},
visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(),
keyboardActions = KeyboardActions(
onGo = {
try {
accountViewModel.login(key.value.text)
} catch (e: Exception) {
errorMessage = "Invalid key"
}
}
)
)
if (errorMessage.isNotBlank()) {
Text(
text = errorMessage,
color = MaterialTheme.colors.error,
style = MaterialTheme.typography.caption
)
}
Spacer(modifier = Modifier.height(20.dp))
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(
checked = acceptedTerms.value,
onCheckedChange = { acceptedTerms.value = it }
)
Text(text = "I accept the ")
ClickableText(
text = AnnotatedString("terms of use"),
onClick = { runCatching { uri.openUri("https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md") } },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary),
)
}
if (termsAcceptanceIsRequired.isNotBlank()) {
Text(
text = termsAcceptanceIsRequired,
color = MaterialTheme.colors.error,
style = MaterialTheme.typography.caption
)
}
Spacer(modifier = Modifier.height(20.dp))
Box(modifier = Modifier.padding(40.dp, 0.dp, 40.dp, 0.dp)) {
Button(
onClick = {
if (!acceptedTerms.value) {
termsAcceptanceIsRequired = "Acceptance of terms is required"
}
if (key.value.text.isBlank()) {
errorMessage = "Key is required"
}
if (acceptedTerms.value && key.value.text.isNotBlank()) {
try {
accountViewModel.login(key.value.text)
} catch (e: Exception) {
errorMessage = "Invalid key"
}
}
},
shape = RoundedCornerShape(35.dp),
modifier = Modifier
.fillMaxWidth()
.height(50.dp),
colors = ButtonDefaults
.buttonColors(
backgroundColor = if (acceptedTerms.value) MaterialTheme.colors.primary else Color.Gray
)
) {
Text(text = "Login")
}
}
}
// The last child is glued to the bottom.
ClickableText( ClickableText(
text = AnnotatedString("Generate a new key"), text = AnnotatedString("Generate a new key"),
modifier = Modifier modifier = Modifier
.align(Alignment.BottomCenter) .padding(20.dp)
.padding(20.dp), .fillMaxWidth(),
onClick = { onClick = {
if (acceptedTerms.value) { if (acceptedTerms.value) {
accountViewModel.newKey() accountViewModel.newKey()
@ -75,127 +185,9 @@ fun LoginPage(accountViewModel: AccountStateViewModel) {
style = TextStyle( style = TextStyle(
fontSize = 14.sp, fontSize = 14.sp,
textDecoration = TextDecoration.Underline, textDecoration = TextDecoration.Underline,
color = MaterialTheme.colors.primary color = MaterialTheme.colors.primary,
textAlign = TextAlign.Center
) )
) )
} }
Column(
modifier = Modifier.padding(20.dp).fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Image(
painterResource(id = R.drawable.amethyst),
contentDescription = "App Logo",
modifier = Modifier.size(200.dp),
contentScale = ContentScale.Inside
)
Spacer(modifier = Modifier.height(40.dp))
var showPassword by remember {
mutableStateOf(false)
}
OutlinedTextField(
value = key.value,
onValueChange = { key.value = it },
keyboardOptions = KeyboardOptions(
autoCorrect = false,
keyboardType = KeyboardType.Ascii,
imeAction = ImeAction.Go
),
placeholder = {
Text(
text = "nsec / npub / hex private key",
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
},
trailingIcon = {
IconButton(onClick = { showPassword = !showPassword }) {
Icon(
imageVector = if (showPassword) Icons.Outlined.VisibilityOff else Icons.Outlined.Visibility,
contentDescription = if (showPassword) "Show Password" else "Hide Password"
)
}
},
visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(),
keyboardActions = KeyboardActions(
onGo = {
try {
accountViewModel.login(key.value.text)
} catch (e: Exception) {
errorMessage = "Invalid key"
}
}
)
)
if (errorMessage.isNotBlank()) {
Text(
text = errorMessage,
color = MaterialTheme.colors.error,
style = MaterialTheme.typography.caption
)
}
Spacer(modifier = Modifier.height(20.dp))
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(
checked = acceptedTerms.value,
onCheckedChange = { acceptedTerms.value = it }
)
Text(text = "I accept the ")
ClickableText(
text = AnnotatedString("terms of use"),
onClick = { runCatching { uri.openUri("https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md") } },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary),
)
}
if (termsAcceptanceIsRequired.isNotBlank()) {
Text(
text = termsAcceptanceIsRequired,
color = MaterialTheme.colors.error,
style = MaterialTheme.typography.caption
)
}
Spacer(modifier = Modifier.height(20.dp))
Box(modifier = Modifier.padding(40.dp, 0.dp, 40.dp, 0.dp)) {
Button(
onClick = {
if (!acceptedTerms.value) {
termsAcceptanceIsRequired = "Acceptance of terms is required"
}
if (key.value.text.isBlank()) {
errorMessage = "Key is required"
}
if (acceptedTerms.value && key.value.text.isNotBlank()) {
try {
accountViewModel.login(key.value.text)
} catch (e: Exception) {
errorMessage = "Invalid key"
}
}
},
shape = RoundedCornerShape(35.dp),
modifier = Modifier
.fillMaxWidth()
.height(50.dp),
colors = ButtonDefaults
.buttonColors(
backgroundColor = if (acceptedTerms.value) MaterialTheme.colors.primary else Color.Gray
)
) {
Text(text = "Login")
}
}
}
} }