Benoit Marty
2 years ago
12 changed files with 398 additions and 117 deletions
@ -1,8 +0,0 @@ |
|||||||
package io.element.android.x.features.login |
|
||||||
|
|
||||||
sealed interface LoginActions { |
|
||||||
data class SetHomeserver(val homeserver: String) : LoginActions |
|
||||||
data class SetLogin(val login: String) : LoginActions |
|
||||||
data class SetPassword(val password: String) : LoginActions |
|
||||||
object Submit : LoginActions |
|
||||||
} |
|
@ -1,17 +1,14 @@ |
|||||||
package io.element.android.x.features.login |
package io.element.android.x.features.login |
||||||
|
|
||||||
import com.airbnb.mvrx.Async |
import com.airbnb.mvrx.Async |
||||||
|
import com.airbnb.mvrx.Loading |
||||||
import com.airbnb.mvrx.MavericksState |
import com.airbnb.mvrx.MavericksState |
||||||
import com.airbnb.mvrx.Uninitialized |
import com.airbnb.mvrx.Uninitialized |
||||||
|
|
||||||
data class LoginViewState( |
data class LoginViewState( |
||||||
val homeserver: String = "matrix.org", |
|
||||||
val login: String = "", |
val login: String = "", |
||||||
val password: String = "", |
val password: String = "", |
||||||
val isLoggedIn: Async<Unit> = Uninitialized, |
val isLoggedIn: Async<Unit> = Uninitialized, |
||||||
) : MavericksState { |
) : MavericksState { |
||||||
|
val submitEnabled = login.isNotEmpty() && password.isNotEmpty() && isLoggedIn !is Loading |
||||||
val submitEnabled = homeserver.isNotEmpty() && login.isNotEmpty() && password.isNotEmpty() |
|
||||||
|
|
||||||
|
|
||||||
} |
} |
||||||
|
@ -0,0 +1,149 @@ |
|||||||
|
@file:OptIn(ExperimentalMaterial3Api::class) |
||||||
|
|
||||||
|
package io.element.android.x.features.login.changeserver |
||||||
|
|
||||||
|
import androidx.compose.foundation.background |
||||||
|
import androidx.compose.foundation.layout.* |
||||||
|
import androidx.compose.foundation.rememberScrollState |
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape |
||||||
|
import androidx.compose.foundation.text.KeyboardOptions |
||||||
|
import androidx.compose.foundation.verticalScroll |
||||||
|
import androidx.compose.material3.* |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.runtime.getValue |
||||||
|
import androidx.compose.ui.Alignment |
||||||
|
import androidx.compose.ui.Modifier |
||||||
|
import androidx.compose.ui.text.font.FontWeight |
||||||
|
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.tooling.preview.Preview |
||||||
|
import androidx.compose.ui.unit.dp |
||||||
|
import androidx.compose.ui.unit.sp |
||||||
|
import com.airbnb.mvrx.Fail |
||||||
|
import com.airbnb.mvrx.Loading |
||||||
|
import com.airbnb.mvrx.Success |
||||||
|
import com.airbnb.mvrx.compose.collectAsState |
||||||
|
import com.airbnb.mvrx.compose.mavericksViewModel |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun ChangeServerScreen( |
||||||
|
viewModel: ChangeServerViewModel = mavericksViewModel(), |
||||||
|
onChangeServerSuccess: () -> Unit = { } |
||||||
|
) { |
||||||
|
val state: ChangeServerViewState by viewModel.collectAsState() |
||||||
|
ChangeServerContent( |
||||||
|
state = state, |
||||||
|
onChangeServer = viewModel::setServer, |
||||||
|
onChangeServerSubmit = viewModel::setServerSubmit, |
||||||
|
onChangeServerSuccess = onChangeServerSuccess |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Composable |
||||||
|
fun ChangeServerContent( |
||||||
|
state: ChangeServerViewState, |
||||||
|
onChangeServer: (String) -> Unit = {}, |
||||||
|
onChangeServerSubmit: () -> Unit = {}, |
||||||
|
onChangeServerSuccess: () -> Unit = {}, |
||||||
|
) { |
||||||
|
Surface(color = MaterialTheme.colorScheme.background) { |
||||||
|
Box(modifier = Modifier.fillMaxSize()) { |
||||||
|
val scrollState = rememberScrollState() |
||||||
|
Column( |
||||||
|
modifier = Modifier |
||||||
|
.verticalScroll( |
||||||
|
state = scrollState, |
||||||
|
) |
||||||
|
.padding(horizontal = 16.dp), |
||||||
|
) |
||||||
|
{ |
||||||
|
val isError = state.changeServerAction is Fail |
||||||
|
Text( |
||||||
|
modifier = Modifier |
||||||
|
.padding(top = 99.dp) |
||||||
|
.size(width = 81.dp, height = 73.dp) |
||||||
|
.align(Alignment.CenterHorizontally) |
||||||
|
.background( |
||||||
|
color = MaterialTheme.colorScheme.surfaceVariant, |
||||||
|
shape = RoundedCornerShape(32.dp) |
||||||
|
), |
||||||
|
text = "\uDBC2\uDEAC", |
||||||
|
fontSize = 34.sp, |
||||||
|
textAlign = TextAlign.Center, |
||||||
|
) |
||||||
|
Text( |
||||||
|
text = "Your server", |
||||||
|
modifier = Modifier |
||||||
|
.fillMaxWidth() |
||||||
|
.defaultMinSize(minHeight = 56.dp) |
||||||
|
.align(Alignment.CenterHorizontally) |
||||||
|
.padding(top = 38.dp), |
||||||
|
textAlign = TextAlign.Center, |
||||||
|
fontWeight = FontWeight.Bold, |
||||||
|
fontSize = 24.sp, |
||||||
|
) |
||||||
|
Text( |
||||||
|
text = "A server is a home for all your data.\n" + |
||||||
|
"You choose your server and it’s easy to make one.", // TODO "Learn more.", |
||||||
|
modifier = Modifier |
||||||
|
.fillMaxWidth() |
||||||
|
.align(Alignment.CenterHorizontally) |
||||||
|
.padding(top = 16.dp), |
||||||
|
textAlign = TextAlign.Center, |
||||||
|
fontSize = 16.sp, |
||||||
|
color = MaterialTheme.colorScheme.secondary |
||||||
|
) |
||||||
|
OutlinedTextField( |
||||||
|
value = state.homeserver, |
||||||
|
modifier = Modifier |
||||||
|
.fillMaxWidth() |
||||||
|
.padding(top = 200.dp), |
||||||
|
onValueChange = onChangeServer, |
||||||
|
label = { |
||||||
|
Text(text = "Server") |
||||||
|
}, |
||||||
|
isError = isError, |
||||||
|
keyboardOptions = KeyboardOptions( |
||||||
|
keyboardType = KeyboardType.Password, |
||||||
|
imeAction = ImeAction.Send, |
||||||
|
), |
||||||
|
) |
||||||
|
if (isError) { |
||||||
|
Text( |
||||||
|
text = (state.changeServerAction as? Fail)?.toString().orEmpty(), |
||||||
|
color = MaterialTheme.colorScheme.error, |
||||||
|
style = MaterialTheme.typography.bodySmall, |
||||||
|
modifier = Modifier.padding(start = 16.dp) |
||||||
|
) |
||||||
|
} |
||||||
|
Button( |
||||||
|
onClick = onChangeServerSubmit, |
||||||
|
enabled = state.submitEnabled, |
||||||
|
modifier = Modifier |
||||||
|
.fillMaxWidth() |
||||||
|
.padding(top = 44.dp) |
||||||
|
) { |
||||||
|
Text(text = "Continue") |
||||||
|
} |
||||||
|
if (state.changeServerAction is Success) { |
||||||
|
onChangeServerSuccess() |
||||||
|
} |
||||||
|
} |
||||||
|
if (state.changeServerAction is Loading) { |
||||||
|
CircularProgressIndicator( |
||||||
|
modifier = Modifier.align(Alignment.Center) |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
@Preview |
||||||
|
private fun ChangeServerContentPreview() { |
||||||
|
ChangeServerContent( |
||||||
|
state = ChangeServerViewState(homeserver = "matrix.org"), |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,32 @@ |
|||||||
|
package io.element.android.x.features.login.changeserver |
||||||
|
|
||||||
|
import com.airbnb.mvrx.Loading |
||||||
|
import com.airbnb.mvrx.MavericksViewModel |
||||||
|
import io.element.android.x.matrix.MatrixInstance |
||||||
|
import kotlinx.coroutines.launch |
||||||
|
|
||||||
|
class ChangeServerViewModel(initialState: ChangeServerViewState) : |
||||||
|
MavericksViewModel<ChangeServerViewState>(initialState) { |
||||||
|
|
||||||
|
private val matrix = MatrixInstance.getInstance() |
||||||
|
|
||||||
|
fun setServer(server: String) { |
||||||
|
setState { |
||||||
|
copy(homeserver = server) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun setServerSubmit() = withState { state -> |
||||||
|
setState { |
||||||
|
copy(changeServerAction = Loading()) |
||||||
|
} |
||||||
|
|
||||||
|
viewModelScope.launch { |
||||||
|
suspend { |
||||||
|
matrix.setHomeserver(state.homeserver) |
||||||
|
}.execute { it -> |
||||||
|
copy(changeServerAction = it) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
package io.element.android.x.features.login.changeserver |
||||||
|
|
||||||
|
import com.airbnb.mvrx.Async |
||||||
|
import com.airbnb.mvrx.Loading |
||||||
|
import com.airbnb.mvrx.MavericksState |
||||||
|
import com.airbnb.mvrx.Uninitialized |
||||||
|
|
||||||
|
data class ChangeServerViewState( |
||||||
|
val homeserver: String = "matrix.org", |
||||||
|
val changeServerAction: Async<Unit> = Uninitialized, |
||||||
|
) : MavericksState { |
||||||
|
val submitEnabled = homeserver.isNotEmpty() && changeServerAction !is Loading |
||||||
|
} |
@ -1,5 +0,0 @@ |
|||||||
package io.element.android.x.features.onboarding |
|
||||||
|
|
||||||
sealed interface OnBoardingActions { |
|
||||||
data class GoToPage(val page: Int) : OnBoardingActions |
|
||||||
} |
|
@ -0,0 +1,26 @@ |
|||||||
|
package io.element.android.x.core.compose |
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi |
||||||
|
import androidx.compose.foundation.layout.WindowInsets |
||||||
|
import androidx.compose.foundation.layout.isImeVisible |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.runtime.State |
||||||
|
import androidx.compose.runtime.rememberUpdatedState |
||||||
|
import androidx.compose.ui.platform.LocalLifecycleOwner |
||||||
|
import androidx.lifecycle.Lifecycle |
||||||
|
|
||||||
|
/** |
||||||
|
* Inspired from https://stackoverflow.com/questions/68847559/how-can-i-detect-keyboard-opening-and-closing-in-jetpack-compose |
||||||
|
*/ |
||||||
|
enum class Keyboard { |
||||||
|
Opened, Closed |
||||||
|
} |
||||||
|
|
||||||
|
// Note: it does not work as expected... |
||||||
|
@OptIn(ExperimentalLayoutApi::class) |
||||||
|
@Composable |
||||||
|
fun keyboardAsState(): State<Keyboard> { |
||||||
|
val lifecycle = LocalLifecycleOwner.current.lifecycle |
||||||
|
val isResumed = lifecycle.currentState == Lifecycle.State.RESUMED |
||||||
|
return rememberUpdatedState(if (WindowInsets.isImeVisible && isResumed) Keyboard.Opened else Keyboard.Closed) |
||||||
|
} |
Loading…
Reference in new issue