1
Fork 0
mirror of https://github.com/Steffo99/twom.git synced 2024-11-21 23:54:26 +00:00

Progress. Probably.

This commit is contained in:
Steffo 2023-11-22 05:45:14 +01:00
parent 1204669c88
commit cdad623e96
Signed by: steffo
GPG key ID: 2A24051445686895
21 changed files with 128 additions and 441 deletions

View file

@ -31,7 +31,7 @@
</provider>
<activity
android:name=".MainActivity"
android:name=".matrix.MatrixActivity"
android:exported="true"
android:theme="@android:style/Theme.NoTitleBar">
<intent-filter>
@ -42,11 +42,11 @@
</activity>
<activity
android:name=".ui.login.LoginActivity"
android:name=".login.LoginActivity"
android:theme="@android:style/Theme.NoTitleBar" />
<activity
android:name=".ui.homeserver.SelectHomeserverActivity"
android:name=".homeserver.SelectHomeserverActivity"
android:theme="@android:style/Theme.NoTitleBar" />
</application>

View file

@ -0,0 +1,15 @@
package eu.steffo.twom.login
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
class LoginActivity : ComponentActivity() {
override fun onStart() {
super.onStart()
setContent {
LoginActivityScaffold()
}
}
}

View file

@ -1,9 +1,11 @@
package eu.steffo.twom.ui.login
package eu.steffo.twom.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.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
@ -17,7 +19,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import eu.steffo.twom.R
import eu.steffo.twom.matrix.TwoMMatrix
import eu.steffo.twom.ui.BASE_PADDING
import eu.steffo.twom.theme.TwoMPadding
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
@ -26,8 +28,6 @@ import org.matrix.android.sdk.api.session.Session
@Preview(showBackground = true)
fun LoginActivityControl(
modifier: Modifier = Modifier,
selectedHomeserver: String? = null,
onSelectHomeserver: () -> Unit = {},
onLogin: (session: Session) -> Unit = {},
) {
val scope = rememberCoroutineScope()
@ -38,19 +38,20 @@ fun LoginActivityControl(
var loggingIn by rememberSaveable { mutableStateOf(false) }
Column(modifier) {
Row(BASE_PADDING) {
if (loggingIn) {
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth(),
)
} else {
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth(),
progress = 0.0f,
)
}
Row(TwoMPadding.base) {
Text(LocalContext.current.getString(R.string.login_text))
}
Row(BASE_PADDING) {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = onSelectHomeserver,
enabled = true,
) {
Text(LocalContext.current.getString(R.string.login_selecthomeserver_text))
}
}
Row(BASE_PADDING) {
Row(TwoMPadding.base) {
TextField(
modifier = Modifier.fillMaxWidth(),
value = username,
@ -64,18 +65,9 @@ fun LoginActivityControl(
supportingText = {
Text(LocalContext.current.getString(R.string.login_username_supporting))
},
prefix = {
Text("@")
},
suffix = {
// TODO: Properly perform the login process
val localpart = selectedHomeserver?.replace(Regex("^https?://"), "")
Text(":$localpart")
},
enabled = (selectedHomeserver != null),
)
}
Row(BASE_PADDING) {
Row(TwoMPadding.base) {
PasswordField(
modifier = Modifier.fillMaxWidth(),
value = password,
@ -89,26 +81,48 @@ fun LoginActivityControl(
supportingText = {
Text(LocalContext.current.getString(R.string.login_password_supporting))
},
enabled = (selectedHomeserver != null),
)
}
Row(BASE_PADDING) {
Row(TwoMPadding.base) {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
val wizard = TwoMMatrix.matrix!!.authenticationService().getLoginWizard()
scope.launch Login@{
Log.d(this::class.qualifiedName, "Launching login wizard...")
val wizard = TwoMMatrix.matrix.authenticationService().getLoginWizard()
scope.launch {
val session = wizard.login(
login = "@$username:$selectedHomeserver",
password = password,
initialDeviceName = "Garasauto", // TODO
deviceId = "Garasauto", // TODO
// TODO: Which exceptions can this spawn?
Log.d(this::class.qualifiedName, "Trying to login as: $username")
loggingIn = true
lateinit var session: Session
// TODO: Why does this not catch the exception?
try {
session = wizard.login(
login = username,
password = password,
initialDeviceName = "Garasauto", // TODO
deviceId = "Garasauto", // TODO
)
} catch (e: RuntimeException) {
Log.e(
this::class.qualifiedName,
"Something went wrong while logging in as: $username",
e
)
return@Login
} finally {
loggingIn = false
}
Log.d(
this::class.qualifiedName,
"Logged in with session id: ${session.sessionId}"
)
TwoMMatrix.session = session
onLogin(session)
}
},
enabled = (username != "" && TwoMMatrix.matrix != null),
enabled = (username != ""),
) {
Text(LocalContext.current.getString(R.string.login_complete_text))
}

View file

@ -1,4 +1,4 @@
package eu.steffo.twom.ui.login
package eu.steffo.twom.login
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
@ -14,7 +14,7 @@ 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.ui.theme.TwoMTheme
import eu.steffo.twom.theme.TwoMTheme
import org.matrix.android.sdk.api.session.Session
@ -43,11 +43,7 @@ fun LoginActivityScaffold(
)
}
) {
LoginActivityControl(
modifier = Modifier.padding(it),
selectedHomeserver = selectedHomeserver,
onSelectHomeserver = onSelectHomeserver,
)
LoginActivityControl(Modifier.padding(it))
}
}
}

View file

@ -1,7 +1,6 @@
package eu.steffo.twom.ui.login
package eu.steffo.twom.login
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
@ -10,7 +9,6 @@ import androidx.compose.material.icons.filled.FavoriteBorder
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldColors
import androidx.compose.material3.TextFieldDefaults

View file

@ -1,4 +1,4 @@
package eu.steffo.twom
package eu.steffo.twom.matrix
import android.content.Intent
import android.os.Bundle
@ -17,24 +17,24 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import eu.steffo.twom.matrix.TwoMMatrix
import eu.steffo.twom.ui.login.LoginActivity
import eu.steffo.twom.ui.theme.TwoMTheme
import eu.steffo.twom.R
import eu.steffo.twom.login.LoginActivity
import eu.steffo.twom.theme.TwoMTheme
@OptIn(ExperimentalMaterial3Api::class)
class MainActivity : ComponentActivity() {
class MatrixActivity : ComponentActivity() {
private lateinit var loginLauncher: ActivityResultLauncher<Intent>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
actionBar?.hide()
TwoMMatrix.initMatrix(applicationContext)
TwoMMatrix.tryInitSessionFromStorage()
TwoMMatrix.ensureMatrix(applicationContext)
loginLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
Log.i("Garasauto", "Garaso")
loginLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
Log.d(this::class.qualifiedName, "LoginActivity has returned a result.")
}
}
@ -45,8 +45,10 @@ class MainActivity : ComponentActivity() {
TwoMTheme {
Scaffold(
topBar = {
CenterAlignedTopAppBar (
title = { Text(LocalContext.current.getString(R.string.app_name)) }
CenterAlignedTopAppBar(
title = {
Text(LocalContext.current.getString(R.string.app_name))
}
)
}
) {

View file

@ -1,3 +0,0 @@
package eu.steffo.twom.matrix
class MatrixAlreadyInitializedError : Exception()

View file

@ -1,3 +0,0 @@
package eu.steffo.twom.matrix
class SessionAlreadyInitializedError : Exception()

View file

@ -1,3 +0,0 @@
package eu.steffo.twom.matrix
class SessionNotInitializedError : Exception()

View file

@ -1,58 +1,41 @@
package eu.steffo.twom.matrix
import TwoMRoomDisplayNameFallbackProvider
import android.content.Context
import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.session.Session
/**
* Object containing the global state of the application.
*/
object TwoMMatrix {
var matrix: Matrix? = null
private set
/**
* The global [Matrix] object of the application.
*
* Most activities will expect this to be available.
*/
lateinit var matrix: Matrix
fun initMatrix(context: Context) {
if(matrix != null) {
throw MatrixAlreadyInitializedError()
}
matrix = Matrix(
context = context.applicationContext,
matrixConfiguration = MatrixConfiguration(
applicationFlavor = "TwoM",
roomDisplayNameFallbackProvider = TwoMRoomDisplayNameFallbackProvider(context.applicationContext)
private fun isMatrixInitialized(): Boolean {
return this::matrix.isInitialized
}
/**
* Make sure the [matrix] object is available, constructing it if it isn't initialized.
*
* Uses the passed [Context] to access the [application context][Context.getApplicationContext], which is required by the SDK provided by the Matrix Foundation.
*/
fun ensureMatrix(context: Context): Matrix? {
if (!isMatrixInitialized()) {
matrix = Matrix(
context = context.applicationContext,
matrixConfiguration = MatrixConfiguration(
applicationFlavor = "TwoM",
roomDisplayNameFallbackProvider = TwoMRoomDisplayNameFallbackProvider(context.applicationContext)
)
)
)
}
var session: Session? = null
set(value) {
if (field != null) {
closeSession()
}
field = value
if (field != null) {
openSession()
}
return matrix
}
fun tryInitSessionFromStorage() {
val lastSession = matrix?.authenticationService()?.getLastAuthenticatedSession()
if(lastSession != null) {
session = lastSession
}
}
// TODO: Does this throw an error if the session is already open?
private fun openSession() {
val currentSession = session ?: throw SessionNotInitializedError()
currentSession.open()
currentSession.syncService().startSync(true)
}
// TODO: Does this throw an error if the session is already closed?
private fun closeSession() {
val currentSession = session ?: throw SessionNotInitializedError()
currentSession.close()
currentSession.syncService().stopSync()
return null
}
}

View file

@ -1,3 +1,5 @@
package eu.steffo.twom.matrix
import android.content.Context
import eu.steffo.twom.R
import org.matrix.android.sdk.api.provider.RoomDisplayNameFallbackProvider

View file

@ -1,8 +1,10 @@
package eu.steffo.twom.ui
package eu.steffo.twom.theme
import androidx.compose.foundation.layout.padding
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
val BASE_PADDING = Modifier.padding(all = 10.dp)
object TwoMPadding {
val base = Modifier.padding(all = 10.dp)
}

View file

@ -1,8 +1,9 @@
package eu.steffo.twom.ui.theme
package eu.steffo.twom.theme
import android.app.Activity
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Typography
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.runtime.Composable
@ -34,9 +35,11 @@ fun TwoMTheme(
}
}
val typography = Typography()
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
typography = typography,
content = content
)
}
}

View file

@ -1,30 +0,0 @@
package eu.steffo.twom.ui.homeserver
import android.content.Intent
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
class SelectHomeserverActivity : ComponentActivity() {
companion object {
const val HOMESERVER_EXTRA_KEY = "homeserver"
}
override fun onStart() {
super.onStart()
setContent {
SelectHomeserverScaffold(
onBack = {
setResult(RESULT_CANCELED)
finish()
},
onComplete = {
val result = Intent()
result.putExtra(HOMESERVER_EXTRA_KEY, it)
setResult(RESULT_OK, result)
finish()
}
)
}
}
}

View file

@ -1,101 +0,0 @@
package eu.steffo.twom.ui.homeserver
import android.net.Uri
import android.webkit.URLUtil
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
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 androidx.compose.ui.unit.dp
import eu.steffo.twom.R
import eu.steffo.twom.matrix.TwoMMatrix
import eu.steffo.twom.ui.BASE_PADDING
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
@Composable
@Preview(showBackground = true)
fun SelectHomeserverControl(
modifier: Modifier = Modifier,
onComplete: (homeserver: String) -> Unit = {},
) {
val scope = rememberCoroutineScope()
var homeserver by rememberSaveable { mutableStateOf("") }
var state by rememberSaveable { mutableStateOf(SelectHomeserverFieldState.Empty) }
Column(modifier) {
Row(BASE_PADDING) {
Text(LocalContext.current.getString(R.string.selecthomeserver_text))
}
Row(BASE_PADDING) {
SelectHomeserverField(
modifier = Modifier.fillMaxWidth(),
value = homeserver,
onValueChange = OnValueChange@{
homeserver = it
state = SelectHomeserverFieldState.Empty
if (homeserver == "") {
return@OnValueChange
}
if (!(URLUtil.isHttpUrl(homeserver) || URLUtil.isHttpsUrl(homeserver))) {
state = SelectHomeserverFieldState.URLInvalid
return@OnValueChange
}
val uri = Uri.parse(homeserver)
scope.launch ValidateFlows@{
state = SelectHomeserverFieldState.Waiting
delay(500L)
if (homeserver != it) return@ValidateFlows
val authenticationService = TwoMMatrix.matrix!!.authenticationService()
state = SelectHomeserverFieldState.Validating
try {
authenticationService.getLoginFlow(
HomeServerConnectionConfig(
homeServerUri = uri,
)
)
} catch (e: Throwable) {
state = SelectHomeserverFieldState.FlowInvalid
return@ValidateFlows
}
state = SelectHomeserverFieldState.Valid
}
},
enabled = (TwoMMatrix.matrix != null),
state = state,
)
}
Row(BASE_PADDING) {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
onComplete(homeserver)
},
enabled = (state == SelectHomeserverFieldState.Valid),
) {
Text(LocalContext.current.getString(R.string.selecthomeserver_complete_text))
}
}
}
}

View file

@ -1,70 +0,0 @@
package eu.steffo.twom.ui.homeserver
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Create
import androidx.compose.material.icons.filled.Send
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import eu.steffo.twom.R
@Composable
@Preview
fun SelectHomeserverField(
modifier: Modifier = Modifier,
value: String = "",
onValueChange: (String) -> Unit = {},
enabled: Boolean = true,
state: SelectHomeserverFieldState = SelectHomeserverFieldState.Empty,
) {
TextField(
modifier = modifier,
value = value,
onValueChange = onValueChange,
enabled = enabled,
singleLine = true,
label = {
Text(LocalContext.current.getString(R.string.selecthomeserver_input_label))
},
placeholder = {
Text(LocalContext.current.getString(R.string.selecthomeserver_input_placeholder))
},
trailingIcon = {
Icon(
when(state) {
SelectHomeserverFieldState.Empty -> Icons.Default.Create
SelectHomeserverFieldState.Waiting -> Icons.Default.Create
SelectHomeserverFieldState.Validating -> Icons.Default.Send
SelectHomeserverFieldState.URLInvalid -> Icons.Default.Close
SelectHomeserverFieldState.FlowInvalid -> Icons.Default.Close
SelectHomeserverFieldState.Valid -> Icons.Default.Check
},
null
)
},
supportingText = {
Text(
when(state) {
SelectHomeserverFieldState.Empty -> LocalContext.current.getString(R.string.selecthomeserver_input_supporting_empty)
SelectHomeserverFieldState.Waiting -> LocalContext.current.getString(R.string.selecthomeserver_input_supporting_waiting)
SelectHomeserverFieldState.Validating -> LocalContext.current.getString(R.string.selecthomeserver_input_supporting_validating)
SelectHomeserverFieldState.URLInvalid -> LocalContext.current.getString(R.string.selecthomeserver_input_supporting_urlinvalid)
SelectHomeserverFieldState.FlowInvalid -> LocalContext.current.getString(R.string.selecthomeserver_input_supporting_flowinvalid)
SelectHomeserverFieldState.Valid -> LocalContext.current.getString(R.string.selecthomeserver_input_supporting_valid)
}
)
},
isError = when(state) {
SelectHomeserverFieldState.URLInvalid -> true
SelectHomeserverFieldState.FlowInvalid -> true
else -> false
}
)
}

View file

@ -1,10 +0,0 @@
package eu.steffo.twom.ui.homeserver
enum class SelectHomeserverFieldState {
Empty,
Waiting,
Validating,
URLInvalid,
FlowInvalid,
Valid,
}

View file

@ -1,51 +0,0 @@
package eu.steffo.twom.ui.homeserver
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
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.ui.theme.TwoMTheme
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@Preview
fun SelectHomeserverScaffold(
onBack: () -> Unit = {},
onComplete: (homeserver: String) -> Unit = {},
) {
TwoMTheme {
Scaffold(
topBar = {
TopAppBar (
navigationIcon = {
IconButton(onClick = onBack) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = LocalContext.current.getString(R.string.back)
)
}
},
title = {
Text(LocalContext.current.getString(R.string.homeserver_title))
},
)
}
) {
SelectHomeserverControl(
modifier = Modifier.padding(it),
onComplete = onComplete
)
}
}
}

View file

@ -1,50 +0,0 @@
package eu.steffo.twom.ui.login
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import eu.steffo.twom.ui.homeserver.SelectHomeserverActivity
class LoginActivity : ComponentActivity() {
private lateinit var homeserverLauncher: ActivityResultLauncher<Intent>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
homeserverLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val selectedHomeserver =
it.data?.getStringExtra(SelectHomeserverActivity.HOMESERVER_EXTRA_KEY)
Log.d("LoginActivity", "Selected homeserver: $selectedHomeserver")
setContent {
LoginActivityScaffold(
selectedHomeserver = selectedHomeserver,
onSelectHomeserver = {
homeserverLauncher.launch(
Intent(
applicationContext,
SelectHomeserverActivity::class.java
)
)
},
)
}
}
}
override fun onStart() {
super.onStart()
setContent {
LoginActivityScaffold(
onSelectHomeserver = {
homeserverLauncher.launch(Intent(applicationContext, SelectHomeserverActivity::class.java))
}
)
}
}
}

View file

@ -1,6 +0,0 @@
package eu.steffo.twom.ui.theme
import androidx.compose.material3.Typography
// Set of Material typography styles to start with
val Typography = Typography()

View file

@ -12,7 +12,6 @@
<string name="room_name_fallback_members_more">%1$s, %2$s, %3$s and %4$d others\' party</string>
<string name="room_name_fallback_invite">Party invite</string>
<string name="selecthomeserver_complete_text">Continue</string>
<string name="selecthomeserver_text">Please select the homeserver you would like to log into.</string>
<string name="selecthomeserver_input_supporting_flowinvalid">The homeserver URL you provided does not point to a Matrix homeserver.</string>
<string name="selecthomeserver_input_supporting_validating">Checking if the URL points to a Matrix homeserver…</string>
<string name="selecthomeserver_input_supporting_waiting">Waiting for you to stop writing…</string>
@ -22,13 +21,13 @@
<string name="back">Go back</string>
<string name="login_text">To use TwoM, you need to log into a Matrix homeserver. Currently, TwoM supports only username and password authentication.</string>
<string name="login_complete_text">Log in</string>
<string name="login_selecthomeserver_text">Select homeserver</string>
<string name="login_username_placeholder">steffo</string>
<string name="login_username_supporting">The Matrix ID to login as.</string>
<string name="login_username_placeholder">\@steffo:candy.steffo.eu</string>
<string name="login_username_supporting">The Matrix ID to login as, including the leading \"@\" and the trailing \":localpart\".</string>
<string name="login_password_placeholder">p4ssw0rd!</string>
<string name="login_password_supporting">The password of the Matrix account.</string>
<string name="login_username_label">Username</string>
<string name="login_password_label">Password</string>
<string name="password_show">Show password</string>
<string name="password_hide">Hide password</string>
<string name="selecthomeserver_text">Please enter the localpart of your Matrix homeserver.</string>
</resources>