mirror of
https://github.com/Steffo99/twom.git
synced 2024-11-24 00:54:24 +00:00
Refactor out manageLogin
This commit is contained in:
parent
63ef1a4edd
commit
529d821db1
7 changed files with 239 additions and 234 deletions
|
@ -5,7 +5,7 @@ import android.content.Intent
|
|||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import eu.steffo.twom.composables.login.LoginScaffold
|
||||
import eu.steffo.twom.composables.login.components.LoginScaffold
|
||||
|
||||
|
||||
class LoginActivity : ComponentActivity() {
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
package eu.steffo.twom.composables.errorhandling
|
||||
|
||||
import android.util.Log
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import kotlinx.coroutines.CancellationException
|
||||
|
||||
private const val TAG = "LocalizableError"
|
||||
|
||||
data class LocalizableError(
|
||||
@StringRes val stringResourceId: Int,
|
||||
|
@ -25,3 +30,20 @@ fun LocalizableError?.Display(contents: @Composable (rendered: String) -> Unit)
|
|||
val rendered = this.render() ?: return
|
||||
contents(rendered)
|
||||
}
|
||||
|
||||
suspend fun MutableState<LocalizableError?>.capture(
|
||||
@StringRes error: Int,
|
||||
coroutine: suspend () -> Unit,
|
||||
): Unit? {
|
||||
try {
|
||||
coroutine()
|
||||
} catch (e: CancellationException) {
|
||||
Log.v(TAG, "Cancelled coroutine execution", e)
|
||||
return null
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, "Captured error during coroutine execution", e)
|
||||
this.value = LocalizableError(error, e)
|
||||
return null
|
||||
}
|
||||
return Unit
|
||||
}
|
|
@ -1,231 +0,0 @@
|
|||
package eu.steffo.twom.composables.login
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ProgressIndicatorDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import eu.steffo.twom.R
|
||||
import eu.steffo.twom.composables.errorhandling.Display
|
||||
import eu.steffo.twom.composables.errorhandling.ErrorText
|
||||
import eu.steffo.twom.composables.errorhandling.LocalizableError
|
||||
import eu.steffo.twom.composables.fields.PasswordField
|
||||
import eu.steffo.twom.composables.theme.basePadding
|
||||
import eu.steffo.twom.utils.TwoMGlobals
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
|
||||
import org.matrix.android.sdk.api.auth.login.LoginWizard
|
||||
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
|
||||
import org.matrix.android.sdk.api.failure.MatrixIdFailure
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
||||
|
||||
enum class LoginStep(val step: Int) {
|
||||
NONE(0),
|
||||
SERVICE(1),
|
||||
WELLKNOWN(2),
|
||||
FLOWS(3),
|
||||
WIZARD(4),
|
||||
LOGIN(5),
|
||||
DONE(6),
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
fun LoginForm(
|
||||
modifier: Modifier = Modifier,
|
||||
onLogin: (session: Session) -> Unit = {},
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
var username by rememberSaveable { mutableStateOf("") }
|
||||
var password by rememberSaveable { mutableStateOf("") }
|
||||
|
||||
var loginStep by rememberSaveable { mutableStateOf(LoginStep.NONE) }
|
||||
var error by remember { mutableStateOf<LocalizableError?>(null) }
|
||||
|
||||
suspend fun doLogin() {
|
||||
error = null
|
||||
|
||||
Log.d("Login", "Getting authentication service...")
|
||||
loginStep = LoginStep.SERVICE
|
||||
val auth = TwoMGlobals.matrix.authenticationService()
|
||||
|
||||
Log.d("Login", "Resetting authentication service...")
|
||||
auth.reset()
|
||||
|
||||
Log.d("Login", "Retrieving .well-known data for: $username")
|
||||
loginStep = LoginStep.WELLKNOWN
|
||||
lateinit var wellKnown: WellknownResult
|
||||
try {
|
||||
wellKnown = auth.getWellKnownData(username, null)
|
||||
} catch (e: MatrixIdFailure.InvalidMatrixId) {
|
||||
Log.d(
|
||||
"Login",
|
||||
"User seems to have input an invalid Matrix ID: $username",
|
||||
e
|
||||
)
|
||||
error = LocalizableError(R.string.login_error_username_invalid)
|
||||
return
|
||||
} catch (e: Throwable) {
|
||||
Log.e(
|
||||
"Login",
|
||||
"Something went wrong while retrieving .well-known data for: $username",
|
||||
e
|
||||
)
|
||||
error = LocalizableError(R.string.login_error_wellknown_generic, e)
|
||||
return
|
||||
}
|
||||
if (wellKnown !is WellknownResult.Prompt) {
|
||||
Log.w(
|
||||
"Login",
|
||||
"Data is not .well-known for: $username"
|
||||
)
|
||||
error = LocalizableError(R.string.login_error_wellknown_missing)
|
||||
return
|
||||
}
|
||||
|
||||
Log.d("Login", "Retrieving login flows for: ${wellKnown.homeServerUrl}")
|
||||
loginStep = LoginStep.FLOWS
|
||||
@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
|
||||
lateinit var flows: LoginFlowResult
|
||||
try {
|
||||
@Suppress("UNUSED_VALUE")
|
||||
flows = auth.getLoginFlow(
|
||||
HomeServerConnectionConfig
|
||||
.Builder()
|
||||
.withHomeServerUri(wellKnown.homeServerUrl)
|
||||
.build()
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
Log.e(
|
||||
"Login",
|
||||
"Something went wrong while retrieving login flows for: ${wellKnown.homeServerUrl}",
|
||||
e
|
||||
)
|
||||
error = LocalizableError(R.string.login_error_flows_generic, e)
|
||||
return
|
||||
}
|
||||
|
||||
Log.d("Login", "Creating login wizard...")
|
||||
loginStep = LoginStep.WIZARD
|
||||
lateinit var wizard: LoginWizard
|
||||
try {
|
||||
wizard = auth.getLoginWizard() // Why is this stateful? Aargh.
|
||||
} catch (e: Throwable) {
|
||||
Log.e(
|
||||
"Login",
|
||||
"Something went wrong while setting up the login wizard.",
|
||||
e
|
||||
)
|
||||
error = LocalizableError(R.string.login_error_wizard_generic, e)
|
||||
return
|
||||
}
|
||||
|
||||
Log.d("Login", "Logging in as: $username")
|
||||
loginStep = LoginStep.LOGIN
|
||||
lateinit var session: Session
|
||||
try {
|
||||
session = wizard.login(
|
||||
login = username,
|
||||
password = password,
|
||||
initialDeviceName = "TwoM (Android)",
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
Log.e(
|
||||
"Login",
|
||||
"Something went wrong while logging in as: $username",
|
||||
e
|
||||
)
|
||||
error = LocalizableError(R.string.login_error_login_generic, e)
|
||||
return
|
||||
}
|
||||
|
||||
Log.d(
|
||||
"Login",
|
||||
"Logged in successfully with session id: ${session.sessionId}"
|
||||
)
|
||||
loginStep = LoginStep.DONE
|
||||
|
||||
onLogin(session)
|
||||
}
|
||||
|
||||
Column(modifier) {
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
progress = loginStep.step.toFloat() / LoginStep.DONE.step.toFloat(),
|
||||
color = if (error != null) MaterialTheme.colorScheme.error else ProgressIndicatorDefaults.linearColor
|
||||
)
|
||||
Row(Modifier.basePadding()) {
|
||||
Text(LocalContext.current.getString(R.string.login_text))
|
||||
}
|
||||
Row(Modifier.basePadding()) {
|
||||
TextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
value = username,
|
||||
onValueChange = { username = it },
|
||||
label = {
|
||||
Text(LocalContext.current.getString(R.string.login_username_label))
|
||||
},
|
||||
placeholder = {
|
||||
Text(LocalContext.current.getString(R.string.login_username_placeholder))
|
||||
},
|
||||
supportingText = {
|
||||
Text(LocalContext.current.getString(R.string.login_username_supporting))
|
||||
},
|
||||
)
|
||||
}
|
||||
Row(Modifier.basePadding()) {
|
||||
PasswordField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = password,
|
||||
onValueChange = { password = it },
|
||||
label = {
|
||||
Text(LocalContext.current.getString(R.string.login_password_label))
|
||||
},
|
||||
placeholder = {
|
||||
Text(LocalContext.current.getString(R.string.login_password_placeholder))
|
||||
},
|
||||
supportingText = {
|
||||
Text(LocalContext.current.getString(R.string.login_password_supporting))
|
||||
},
|
||||
)
|
||||
}
|
||||
Row(Modifier.basePadding()) {
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = (username != "" && (loginStep == LoginStep.NONE || error != null)),
|
||||
onClick = {
|
||||
scope.launch { doLogin() }
|
||||
},
|
||||
) {
|
||||
Text(LocalContext.current.getString(R.string.login_complete_text))
|
||||
}
|
||||
}
|
||||
error.Display {
|
||||
Row(Modifier.basePadding()) {
|
||||
ErrorText(
|
||||
text = it
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
package eu.steffo.twom.composables.login.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ProgressIndicatorDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import eu.steffo.twom.R
|
||||
import eu.steffo.twom.composables.errorhandling.Display
|
||||
import eu.steffo.twom.composables.errorhandling.ErrorText
|
||||
import eu.steffo.twom.composables.fields.PasswordField
|
||||
import eu.steffo.twom.composables.login.effects.LoginStep
|
||||
import eu.steffo.twom.composables.login.effects.manageLogin
|
||||
import eu.steffo.twom.composables.theme.basePadding
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
fun LoginForm(
|
||||
modifier: Modifier = Modifier,
|
||||
onLogin: (session: Session) -> Unit = {},
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
var username by rememberSaveable { mutableStateOf("") }
|
||||
var password by rememberSaveable { mutableStateOf("") }
|
||||
|
||||
val manager = manageLogin()
|
||||
|
||||
Column(modifier) {
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
progress = manager.step.ord.toFloat() / LoginStep.DONE.ord.toFloat(),
|
||||
color = if (manager.error != null) {
|
||||
MaterialTheme.colorScheme.error
|
||||
} else {
|
||||
ProgressIndicatorDefaults.linearColor
|
||||
}
|
||||
)
|
||||
Row(Modifier.basePadding()) {
|
||||
Text(LocalContext.current.getString(R.string.login_text))
|
||||
}
|
||||
Row(Modifier.basePadding()) {
|
||||
TextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
value = username,
|
||||
onValueChange = { username = it },
|
||||
label = {
|
||||
Text(LocalContext.current.getString(R.string.login_username_label))
|
||||
},
|
||||
placeholder = {
|
||||
Text(LocalContext.current.getString(R.string.login_username_placeholder))
|
||||
},
|
||||
supportingText = {
|
||||
Text(LocalContext.current.getString(R.string.login_username_supporting))
|
||||
},
|
||||
)
|
||||
}
|
||||
Row(Modifier.basePadding()) {
|
||||
PasswordField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = password,
|
||||
onValueChange = { password = it },
|
||||
label = {
|
||||
Text(LocalContext.current.getString(R.string.login_password_label))
|
||||
},
|
||||
placeholder = {
|
||||
Text(LocalContext.current.getString(R.string.login_password_placeholder))
|
||||
},
|
||||
supportingText = {
|
||||
Text(LocalContext.current.getString(R.string.login_password_supporting))
|
||||
},
|
||||
)
|
||||
}
|
||||
Row(Modifier.basePadding()) {
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = (username != "" && (manager.step == LoginStep.NONE || manager.error != null)),
|
||||
onClick = {
|
||||
scope.launch DoLogin@{
|
||||
val session = manager.login(username, password) ?: return@DoLogin
|
||||
onLogin(session)
|
||||
}
|
||||
},
|
||||
) {
|
||||
Text(LocalContext.current.getString(R.string.login_complete_text))
|
||||
}
|
||||
}
|
||||
manager.error.Display {
|
||||
Row(Modifier.basePadding()) {
|
||||
ErrorText(
|
||||
text = it
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package eu.steffo.twom.composables.login
|
||||
package eu.steffo.twom.composables.login.components
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
|
@ -1,4 +1,4 @@
|
|||
package eu.steffo.twom.composables.login
|
||||
package eu.steffo.twom.composables.login.components
|
||||
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Text
|
|
@ -0,0 +1,101 @@
|
|||
package eu.steffo.twom.composables.login.effects
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import eu.steffo.twom.R
|
||||
import eu.steffo.twom.composables.errorhandling.LocalizableError
|
||||
import eu.steffo.twom.composables.errorhandling.capture
|
||||
import eu.steffo.twom.utils.TwoMGlobals
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
||||
private const val TAG = "manageLogin"
|
||||
|
||||
enum class LoginStep(val ord: Int) {
|
||||
NONE(0),
|
||||
RESET(1),
|
||||
WELLKNOWN(2),
|
||||
FLOWS(3),
|
||||
WIZARD(4),
|
||||
LOGIN(5),
|
||||
DONE(6),
|
||||
}
|
||||
|
||||
data class LoginManager(
|
||||
val step: LoginStep,
|
||||
val error: LocalizableError?,
|
||||
val login: suspend (username: String, password: String) -> Session?,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun manageLogin(): LoginManager {
|
||||
var step by remember { mutableStateOf(LoginStep.NONE) }
|
||||
val error = remember { mutableStateOf<LocalizableError?>(null) }
|
||||
|
||||
suspend fun login(username: String, password: String): Session? {
|
||||
Log.i(TAG, "Starting login process for: $username")
|
||||
step = LoginStep.NONE
|
||||
|
||||
Log.d(TAG, "Resetting authentication service...")
|
||||
step = LoginStep.RESET
|
||||
val auth = TwoMGlobals.matrix.authenticationService()
|
||||
error.capture(R.string.login_error_wellknown_generic) {
|
||||
auth.reset()
|
||||
} ?: return null
|
||||
|
||||
Log.d(TAG, "Retrieving .well-known data for: $username")
|
||||
step = LoginStep.WELLKNOWN
|
||||
lateinit var wellKnown: WellknownResult
|
||||
error.capture(R.string.login_error_wellknown_generic) {
|
||||
wellKnown = auth.getWellKnownData(
|
||||
matrixId = username,
|
||||
homeServerConnectionConfig = null,
|
||||
)
|
||||
} ?: return null
|
||||
if (wellKnown !is WellknownResult.Prompt) {
|
||||
error.value = LocalizableError(R.string.login_error_wellknown_missing)
|
||||
return null
|
||||
}
|
||||
|
||||
Log.d(TAG, "Retrieving login flows for: $username")
|
||||
step = LoginStep.FLOWS
|
||||
error.capture(R.string.login_error_flows_generic) {
|
||||
auth.getLoginFlow(
|
||||
HomeServerConnectionConfig
|
||||
.Builder()
|
||||
.withHomeServerUri((wellKnown as WellknownResult.Prompt).homeServerUrl)
|
||||
.build()
|
||||
)
|
||||
} ?: return null
|
||||
|
||||
Log.d(TAG, "Getting login wizard...")
|
||||
step = LoginStep.WIZARD
|
||||
val wizard = auth.getLoginWizard()
|
||||
|
||||
Log.d(TAG, "Logging in as: $username")
|
||||
step = LoginStep.LOGIN
|
||||
lateinit var session: Session
|
||||
error.capture(R.string.login_error_login_generic) {
|
||||
session = wizard.login(
|
||||
login = username,
|
||||
password = password,
|
||||
initialDeviceName = "TwoM (Android)"
|
||||
)
|
||||
} ?: return null
|
||||
|
||||
Log.i(TAG, "Logged in as: $session")
|
||||
step = LoginStep.DONE
|
||||
return session
|
||||
}
|
||||
|
||||
return LoginManager(
|
||||
step = step,
|
||||
error = error.value,
|
||||
login = ::login,
|
||||
)
|
||||
}
|
Loading…
Reference in a new issue