mirror of https://github.com/readrops/Readrops.git
Add initial UI of AccountCredentialsScreen
This commit is contained in:
parent
2c105f596a
commit
cc7b874ef5
|
@ -1,6 +1,7 @@
|
|||
package com.readrops.app.compose
|
||||
|
||||
import com.readrops.app.compose.account.AccountScreenModel
|
||||
import com.readrops.app.compose.account.credentials.AccountCredentialsScreenModel
|
||||
import com.readrops.app.compose.account.selection.AccountSelectionViewModel
|
||||
import com.readrops.app.compose.feeds.FeedScreenModel
|
||||
import com.readrops.app.compose.item.ItemScreenModel
|
||||
|
@ -9,6 +10,7 @@ import com.readrops.app.compose.repositories.GetFoldersWithFeeds
|
|||
import com.readrops.app.compose.repositories.LocalRSSRepository
|
||||
import com.readrops.app.compose.timelime.TimelineScreenModel
|
||||
import com.readrops.db.entities.account.Account
|
||||
import com.readrops.db.entities.account.AccountType
|
||||
import org.koin.dsl.module
|
||||
|
||||
val composeAppModule = module {
|
||||
|
@ -25,6 +27,8 @@ val composeAppModule = module {
|
|||
ItemScreenModel(get(), itemId)
|
||||
}
|
||||
|
||||
factory { (accountType: AccountType) -> AccountCredentialsScreenModel(accountType) }
|
||||
|
||||
single { GetFoldersWithFeeds(get()) }
|
||||
|
||||
// repositories
|
||||
|
|
|
@ -172,7 +172,7 @@ object AccountTab : Tab {
|
|||
onDismiss = { screenModel.closeDialog() },
|
||||
onValidate = { accountType ->
|
||||
screenModel.closeDialog()
|
||||
navigator.push(AccountCredentialsScreen(accountType, state.account))
|
||||
navigator.push(AccountCredentialsScreen(accountType))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,41 +1,173 @@
|
|||
package com.readrops.app.compose.account.credentials
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
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.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import cafe.adriel.voyager.koin.getScreenModel
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import com.readrops.app.compose.home.HomeScreen
|
||||
import com.readrops.app.compose.R
|
||||
import com.readrops.app.compose.util.components.AndroidScreen
|
||||
import com.readrops.db.entities.account.Account
|
||||
import com.readrops.app.compose.util.theme.ShortSpacer
|
||||
import com.readrops.app.compose.util.theme.VeryLargeSpacer
|
||||
import com.readrops.app.compose.util.theme.spacing
|
||||
import com.readrops.db.entities.account.AccountType
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
class AccountCredentialsScreen(
|
||||
private val accountType: AccountType,
|
||||
private val account: Account? = null,
|
||||
private val accountType: AccountType
|
||||
) : AndroidScreen() {
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val screenModel =
|
||||
getScreenModel<AccountCredentialsScreenModel>(parameters = { parametersOf(accountType) })
|
||||
|
||||
Column {
|
||||
Text(
|
||||
text = "AccountCredentialsScreen"
|
||||
)
|
||||
val state by screenModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
Button(onClick = { navigator.replaceAll(HomeScreen()) }) {
|
||||
Text(
|
||||
text = "skip"
|
||||
Box(
|
||||
modifier = Modifier.imePadding()
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.fillMaxSize()
|
||||
.padding(MaterialTheme.spacing.largeSpacing)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = accountType.iconRes),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(48.dp)
|
||||
)
|
||||
|
||||
ShortSpacer()
|
||||
|
||||
Text(
|
||||
text = stringResource(id = accountType.typeName),
|
||||
style = MaterialTheme.typography.headlineMedium
|
||||
)
|
||||
|
||||
VeryLargeSpacer()
|
||||
|
||||
OutlinedTextField(
|
||||
value = state.name,
|
||||
onValueChange = { screenModel.onEvent(Event.NameEvent(it)) },
|
||||
label = { Text(text = stringResource(id = R.string.account_name)) },
|
||||
singleLine = true,
|
||||
isError = state.isNameError,
|
||||
supportingText = { Text(text = state.nameError?.errorText().orEmpty()) },
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
ShortSpacer()
|
||||
|
||||
OutlinedTextField(
|
||||
value = state.url,
|
||||
onValueChange = { screenModel.onEvent(Event.URLEvent(it)) },
|
||||
label = { Text(text = stringResource(id = R.string.account_url)) },
|
||||
singleLine = true,
|
||||
isError = state.isUrlError,
|
||||
supportingText = { Text(text = state.urlError?.errorText().orEmpty()) },
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
ShortSpacer()
|
||||
|
||||
OutlinedTextField(
|
||||
value = state.login,
|
||||
onValueChange = { screenModel.onEvent(Event.LoginEvent(it)) },
|
||||
label = { Text(text = stringResource(id = R.string.login)) },
|
||||
singleLine = true,
|
||||
isError = state.isLoginError,
|
||||
supportingText = { Text(text = state.passwordError?.errorText().orEmpty()) },
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
ShortSpacer()
|
||||
|
||||
OutlinedTextField(
|
||||
value = state.password,
|
||||
onValueChange = { screenModel.onEvent(Event.PasswordEvent(it)) },
|
||||
label = { Text(text = stringResource(id = R.string.password)) },
|
||||
trailingIcon = {
|
||||
IconButton(
|
||||
onClick = { screenModel.setPasswordVisibility(!state.isPasswordVisible) }
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(
|
||||
id = if (state.isPasswordVisible)
|
||||
R.drawable.ic_visible_off
|
||||
else R.drawable.ic_visible
|
||||
),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
},
|
||||
singleLine = true,
|
||||
visualTransformation = if (state.isPasswordVisible)
|
||||
VisualTransformation.None
|
||||
else
|
||||
PasswordVisualTransformation(),
|
||||
isError = state.isPasswordError,
|
||||
supportingText = { Text(text = state.passwordError?.errorText().orEmpty()) },
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Password,
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
ShortSpacer()
|
||||
|
||||
Button(
|
||||
onClick = { screenModel.login() },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
if (state.isLoginStarted) {
|
||||
CircularProgressIndicator(
|
||||
color = MaterialTheme.colorScheme.onPrimary,
|
||||
strokeWidth = 2.dp,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
} else {
|
||||
Text(text = stringResource(id = R.string.validate))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
package com.readrops.app.compose.account.credentials
|
||||
|
||||
import android.util.Patterns
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import com.readrops.app.compose.util.components.TextFieldError
|
||||
import com.readrops.db.entities.account.Account
|
||||
import com.readrops.db.entities.account.AccountType
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
class AccountCredentialsScreenModel(
|
||||
private val accountType: AccountType
|
||||
) : StateScreenModel<AccountCredentialsState>(AccountCredentialsState(name = accountType.name)) {
|
||||
|
||||
fun onEvent(event: Event): Unit = with(mutableState) {
|
||||
when (event) {
|
||||
is Event.LoginEvent -> update { it.copy(login = event.value, loginError = null) }
|
||||
is Event.NameEvent -> update { it.copy(name = event.value, nameError = null) }
|
||||
is Event.PasswordEvent -> update { it.copy(password = event.value, passwordError = null) }
|
||||
is Event.URLEvent -> update { it.copy(url = event.value, urlError = null) }
|
||||
}
|
||||
}
|
||||
|
||||
fun setPasswordVisibility(isVisible: Boolean) {
|
||||
mutableState.update { it.copy(isPasswordVisible = isVisible) }
|
||||
}
|
||||
|
||||
fun login() {
|
||||
if (validateFields()) {
|
||||
mutableState.update { it.copy(isLoginStarted = true) }
|
||||
|
||||
with(state.value) {
|
||||
val account = Account(
|
||||
url = url,
|
||||
accountName = name,
|
||||
login = login,
|
||||
password = password,
|
||||
accountType = accountType
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateFields(): Boolean = with(mutableState.value) {
|
||||
var validate = true
|
||||
|
||||
if (url.isEmpty()) {
|
||||
mutableState.update { it.copy(urlError = TextFieldError.EmptyField) }
|
||||
validate = false
|
||||
}
|
||||
|
||||
if (name.isEmpty()) {
|
||||
mutableState.update { it.copy(nameError = TextFieldError.EmptyField) }
|
||||
validate = false
|
||||
}
|
||||
|
||||
if (login.isEmpty()) {
|
||||
mutableState.update { it.copy(loginError = TextFieldError.EmptyField) }
|
||||
validate = false
|
||||
}
|
||||
|
||||
if (password.isEmpty()) {
|
||||
mutableState.update { it.copy(passwordError = TextFieldError.EmptyField) }
|
||||
validate = false
|
||||
}
|
||||
|
||||
if (url.isNotEmpty() && !Patterns.WEB_URL.matcher(url).matches()) {
|
||||
mutableState.update { it.copy(urlError = TextFieldError.BadUrl) }
|
||||
validate = false
|
||||
}
|
||||
|
||||
return validate
|
||||
}
|
||||
}
|
||||
|
||||
data class AccountCredentialsState(
|
||||
val url: String = "https://",
|
||||
val urlError: TextFieldError? = null,
|
||||
val name: String = "",
|
||||
val nameError: TextFieldError? = null,
|
||||
val login: String = "",
|
||||
val loginError: TextFieldError? = null,
|
||||
val password: String = "",
|
||||
val passwordError: TextFieldError? = null,
|
||||
val isPasswordVisible: Boolean = false,
|
||||
val isLoginStarted: Boolean = false
|
||||
) {
|
||||
val isUrlError = urlError != null
|
||||
|
||||
val isNameError = nameError != null
|
||||
|
||||
val isLoginError = loginError != null
|
||||
|
||||
val isPasswordError = passwordError != null
|
||||
}
|
||||
|
||||
sealed class Event(val value: String) {
|
||||
class URLEvent(value: String) : Event(value)
|
||||
class NameEvent(value: String) : Event(value)
|
||||
class LoginEvent(value: String) : Event(value)
|
||||
class PasswordEvent(value: String) : Event(value)
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
|
||||
|
||||
</vector>
|
|
@ -0,0 +1,5 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,7c2.76,0 5,2.24 5,5 0,0.65 -0.13,1.26 -0.36,1.83l2.92,2.92c1.51,-1.26 2.7,-2.89 3.43,-4.75 -1.73,-4.39 -6,-7.5 -11,-7.5 -1.4,0 -2.74,0.25 -3.98,0.7l2.16,2.16C10.74,7.13 11.35,7 12,7zM2,4.27l2.28,2.28 0.46,0.46C3.08,8.3 1.78,10.02 1,12c1.73,4.39 6,7.5 11,7.5 1.55,0 3.03,-0.3 4.38,-0.84l0.42,0.42L19.73,22 21,20.73 3.27,3 2,4.27zM7.53,9.8l1.55,1.55c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.66 1.34,3 3,3 0.22,0 0.44,-0.03 0.65,-0.08l1.55,1.55c-0.67,0.33 -1.41,0.53 -2.2,0.53 -2.76,0 -5,-2.24 -5,-5 0,-0.79 0.2,-1.53 0.53,-2.2zM11.84,9.02l3.15,3.15 0.02,-0.16c0,-1.66 -1.34,-3 -3,-3l-0.17,0.01z"/>
|
||||
|
||||
</vector>
|
Loading…
Reference in New Issue