diff --git a/app/src/androidTest/java/eu/steffo/twom/ExampleInstrumentedTest.kt b/app/src/androidTest/java/eu/steffo/twom/ExampleInstrumentedTest.kt
deleted file mode 100644
index c19be2f..0000000
--- a/app/src/androidTest/java/eu/steffo/twom/ExampleInstrumentedTest.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package eu.steffo.twom
-
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.ext.junit.runners.AndroidJUnit4
-
-import org.junit.Test
-import org.junit.runner.RunWith
-
-import org.junit.Assert.*
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * See [testing documentation](http://d.android.com/tools/testing).
- */
-@RunWith(AndroidJUnit4::class)
-class ExampleInstrumentedTest {
- @Test
- fun useAppContext() {
- // Context of the app under test.
- val appContext = InstrumentationRegistry.getInstrumentation().targetContext
- assertEquals("eu.steffo.twom", appContext.packageName)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 847bd61..b85341f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -31,7 +31,7 @@
@@ -42,15 +42,15 @@
diff --git a/app/src/main/java/eu/steffo/twom/activities/CreateRoomActivity.kt b/app/src/main/java/eu/steffo/twom/activities/CreateRoomActivity.kt
new file mode 100644
index 0000000..89d1eaa
--- /dev/null
+++ b/app/src/main/java/eu/steffo/twom/activities/CreateRoomActivity.kt
@@ -0,0 +1,49 @@
+package eu.steffo.twom.activities
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.result.contract.ActivityResultContract
+import eu.steffo.twom.composables.createroom.CreateRoomScaffold
+
+
+data class CreateRoomActivityResult(
+ val name: String,
+ val description: String,
+ val avatarUri: String?,
+)
+
+
+class CreateRoomActivity : ComponentActivity() {
+ companion object {
+ const val NAME_EXTRA = "name"
+ const val DESCRIPTION_EXTRA = "description"
+ const val AVATAR_EXTRA = "avatar"
+ }
+
+ class Contract : ActivityResultContract() {
+ override fun createIntent(context: Context, input: Unit): Intent {
+ return Intent(context, CreateRoomActivity::class.java)
+ }
+
+ override fun parseResult(resultCode: Int, intent: Intent?): CreateRoomActivityResult? {
+ return when (resultCode) {
+ RESULT_OK -> CreateRoomActivityResult(
+ name = intent!!.getStringExtra(NAME_EXTRA)!!,
+ description = intent.getStringExtra(DESCRIPTION_EXTRA)!!,
+ avatarUri = intent.getStringExtra(AVATAR_EXTRA),
+ )
+
+ else -> null
+ }
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContent { CreateRoomScaffold() }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/steffo/twom/activities/LoginActivity.kt b/app/src/main/java/eu/steffo/twom/activities/LoginActivity.kt
new file mode 100644
index 0000000..73926c0
--- /dev/null
+++ b/app/src/main/java/eu/steffo/twom/activities/LoginActivity.kt
@@ -0,0 +1,25 @@
+package eu.steffo.twom.activities
+
+import android.content.Context
+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
+
+
+class LoginActivity : ComponentActivity() {
+ class Contract : ActivityResultContract() {
+ override fun createIntent(context: Context, input: Unit): Intent {
+ return Intent(context, LoginActivity::class.java)
+ }
+
+ override fun parseResult(resultCode: Int, intent: Intent?) {}
+ }
+
+ override fun onStart() {
+ super.onStart()
+
+ setContent { LoginScaffold() }
+ }
+}
diff --git a/app/src/main/java/eu/steffo/twom/main/MainActivity.kt b/app/src/main/java/eu/steffo/twom/activities/MainActivity.kt
similarity index 82%
rename from app/src/main/java/eu/steffo/twom/main/MainActivity.kt
rename to app/src/main/java/eu/steffo/twom/activities/MainActivity.kt
index b713322..5357450 100644
--- a/app/src/main/java/eu/steffo/twom/main/MainActivity.kt
+++ b/app/src/main/java/eu/steffo/twom/activities/MainActivity.kt
@@ -1,4 +1,4 @@
-package eu.steffo.twom.main
+package eu.steffo.twom.activities
import android.content.Intent
import android.net.Uri
@@ -11,10 +11,8 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.net.toFile
import androidx.lifecycle.lifecycleScope
-import eu.steffo.twom.create.CreateActivity
-import eu.steffo.twom.login.LoginActivity
+import eu.steffo.twom.composables.main.MainScaffold
import eu.steffo.twom.matrix.TwoMMatrix
-import eu.steffo.twom.room.RoomActivity
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
@@ -77,6 +75,12 @@ class MainActivity : ComponentActivity() {
currentSession.open()
currentSession.syncService().startSync(true)
currentSession.addListener(OpenSessionListener(this::resetContent))
+
+ Log.d(
+ "Main",
+ "Opened session, recomposing..."
+ )
+ resetContent()
}
}
@@ -95,42 +99,8 @@ class MainActivity : ComponentActivity() {
}
}
- private fun onClickLogin() {
- Log.d("Main", "Clicked login, launching login activity...")
- val intent = Intent(applicationContext, LoginActivity::class.java)
- loginLauncher.launch(intent)
- }
+ private fun destroySession() {
- private fun onLogin(result: ActivityResult) {
- Log.d("Main", "Received result from login activity: $result")
- when (result.resultCode) {
- RESULT_OK -> {
- Log.d(
- "Main",
- "Login activity returned a successful result, trying to get session..."
- )
- fetchLastSession()
- openSession()
- }
- else -> {
- Log.d("Main", "Login activity was cancelled.")
- }
- }
- }
-
- private fun onClickLogout() {
- lifecycleScope.launch {
- val signedOutSession = session!!
-
- Log.d("Main", "Clicked logout, recomposing...")
- session = null
- resetContent()
-
- Log.d("Main", "Done recomposing, now signing out...")
- signedOutSession.signOutService().signOut(true)
-
- Log.d("Main", "Done logging out!")
- }
}
private fun onClickRoom(roomId: String) {
@@ -146,7 +116,7 @@ class MainActivity : ComponentActivity() {
private fun onClickCreate() {
Log.d("Main", "Clicked the New button, launching create activity...")
- val intent = Intent(applicationContext, CreateActivity::class.java)
+ val intent = Intent(applicationContext, CreateRoomActivity::class.java)
createLauncher.launch(intent)
}
@@ -154,10 +124,10 @@ class MainActivity : ComponentActivity() {
Log.d("Main", "Received result from create activity: $result")
if (result.resultCode == RESULT_OK) {
val intent: Intent = result.data!!
- val name = intent.getStringExtra(CreateActivity.NAME_EXTRA)
- val description = intent.getStringExtra(CreateActivity.DESCRIPTION_EXTRA)
+ val name = intent.getStringExtra(CreateRoomActivity.NAME_EXTRA)
+ val description = intent.getStringExtra(CreateRoomActivity.DESCRIPTION_EXTRA)
@Suppress("DEPRECATION") val avatarUri =
- intent.getParcelableExtra(CreateActivity.AVATAR_EXTRA)
+ intent.getParcelableExtra(CreateRoomActivity.AVATAR_EXTRA)
if (name == null) {
Log.w("Main", "Result from create activity did not have `name` extra set")
@@ -265,12 +235,32 @@ class MainActivity : ComponentActivity() {
private fun resetContent() {
Log.d("Main", "Recomposing...")
setContent {
- MatrixActivityScaffold(
- onClickLogin = this::onClickLogin,
- onClickLogout = this::onClickLogout,
- onClickRoom = this::onClickRoom,
- onClickCreate = this::onClickCreate,
+ // TODO: Check this with a clearer mind
+ MainScaffold(
session = session,
+ processLogin = {
+ Log.d(
+ "Main",
+ "Login activity returned a successful result, trying to get session..."
+ )
+ fetchLastSession()
+ openSession()
+
+ },
+ processLogout = {
+ val signedOutSession = session!!
+
+ Log.d("Main", "Clicked logout, recomposing...")
+ session = null
+ resetContent()
+
+ Log.d("Main", "Done recomposing, now signing out...")
+ lifecycleScope.launch {
+ signedOutSession.signOutService().signOut(true)
+ }
+
+ Log.d("Main", "Done logging out!")
+ },
)
}
}
diff --git a/app/src/main/java/eu/steffo/twom/room/RoomActivity.kt b/app/src/main/java/eu/steffo/twom/activities/RoomActivity.kt
similarity index 70%
rename from app/src/main/java/eu/steffo/twom/room/RoomActivity.kt
rename to app/src/main/java/eu/steffo/twom/activities/RoomActivity.kt
index cbedcd3..6ecfdb8 100644
--- a/app/src/main/java/eu/steffo/twom/room/RoomActivity.kt
+++ b/app/src/main/java/eu/steffo/twom/activities/RoomActivity.kt
@@ -1,10 +1,14 @@
-package eu.steffo.twom.room
+package eu.steffo.twom.activities
+import android.content.Context
+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.contract.ActivityResultContract
import eu.steffo.twom.matrix.TwoMMatrix
+import eu.steffo.twom.room.RoomActivityScaffold
import org.matrix.android.sdk.api.session.Session
class RoomActivity : ComponentActivity() {
@@ -12,6 +16,16 @@ class RoomActivity : ComponentActivity() {
const val ROOM_ID_EXTRA = "roomId"
}
+ class Contract : ActivityResultContract() {
+ override fun createIntent(context: Context, input: String): Intent {
+ val intent = Intent(context, RoomActivity::class.java)
+ intent.putExtra(ROOM_ID_EXTRA, input)
+ return intent
+ }
+
+ override fun parseResult(resultCode: Int, intent: Intent?) {}
+ }
+
private lateinit var session: Session
private fun fetchLastSession() {
diff --git a/app/src/main/java/eu/steffo/twom/create/AvatarSelector.kt b/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarPicker.kt
similarity index 73%
rename from app/src/main/java/eu/steffo/twom/create/AvatarSelector.kt
rename to app/src/main/java/eu/steffo/twom/composables/avatar/AvatarPicker.kt
index 2bfb5f4..8f6d829 100644
--- a/app/src/main/java/eu/steffo/twom/create/AvatarSelector.kt
+++ b/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarPicker.kt
@@ -1,4 +1,4 @@
-package eu.steffo.twom.create
+package eu.steffo.twom.composables.avatar
import android.graphics.Bitmap
import androidx.activity.compose.rememberLauncherForActivityResult
@@ -16,12 +16,14 @@ import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import eu.steffo.twom.matrix.avatar.AvatarFromImageBitmap
+import eu.steffo.twom.utils.BitmapUtilities
@Composable
@Preview(widthDp = 40, heightDp = 40)
-fun AvatarSelector(
+fun AvatarPicker(
modifier: Modifier = Modifier,
- onSelectAvatar: (bitmap: Bitmap) -> Unit = {},
+ fallbackText: String = "?",
+ onPick: (bitmap: Bitmap) -> Unit = {},
) {
val context = LocalContext.current
val resolver = context.contentResolver
@@ -32,13 +34,13 @@ fun AvatarSelector(
rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) ImageSelect@{
it ?: return@ImageSelect
- val rawBitmap = ImageHandler.getRawBitmap(resolver, it) ?: return@ImageSelect
- val orientation = ImageHandler.getOrientation(resolver, it) ?: return@ImageSelect
+ val rawBitmap = BitmapUtilities.getRawBitmap(resolver, it) ?: return@ImageSelect
+ val orientation = BitmapUtilities.getOrientation(resolver, it) ?: return@ImageSelect
- val correctedBitmap = ImageHandler.squareAndOrient(rawBitmap, orientation)
+ val correctedBitmap = BitmapUtilities.squareAndOrient(rawBitmap, orientation)
selection = correctedBitmap
- onSelectAvatar(correctedBitmap)
+ onPick(correctedBitmap)
}
Box(
@@ -49,6 +51,7 @@ fun AvatarSelector(
) {
AvatarFromImageBitmap(
bitmap = selection?.asImageBitmap(),
+ fallbackText = fallbackText,
)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/eu/steffo/twom/create/CreateActivityContent.kt b/app/src/main/java/eu/steffo/twom/composables/createroom/CreateRoomForm.kt
similarity index 85%
rename from app/src/main/java/eu/steffo/twom/create/CreateActivityContent.kt
rename to app/src/main/java/eu/steffo/twom/composables/createroom/CreateRoomForm.kt
index f4992cd..a40bb0a 100644
--- a/app/src/main/java/eu/steffo/twom/create/CreateActivityContent.kt
+++ b/app/src/main/java/eu/steffo/twom/composables/createroom/CreateRoomForm.kt
@@ -1,4 +1,4 @@
-package eu.steffo.twom.create
+package eu.steffo.twom.composables.createroom
import android.net.Uri
import androidx.compose.foundation.layout.Column
@@ -24,33 +24,32 @@ import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import eu.steffo.twom.R
+import eu.steffo.twom.composables.avatar.AvatarPicker
import eu.steffo.twom.theme.TwoMPadding
+import eu.steffo.twom.utils.BitmapUtilities
@Composable
-@Preview
-fun CreateActivityContent(
+@Preview(showBackground = true)
+fun CreateRoomForm(
modifier: Modifier = Modifier,
- onClickCreate: (name: String, description: String, avatarUri: Uri?) -> Unit = { _, _, _ -> },
+ onSubmit: (name: String, description: String, avatarUri: Uri?) -> Unit = { _, _, _ -> },
) {
-
var name by rememberSaveable { mutableStateOf("") }
var description by rememberSaveable { mutableStateOf("") }
var avatarUri by rememberSaveable { mutableStateOf(null) }
- // val avatarBitmap = if(avatarUri != null) BitmapFactory.decodeFile(avatarUri.toString()).asImageBitmap() else null
-
Column(modifier) {
Row(TwoMPadding.base) {
val avatarContentDescription = stringResource(R.string.create_avatar_label)
- AvatarSelector(
+ AvatarPicker(
modifier = Modifier
.size(60.dp)
.clip(MaterialTheme.shapes.medium)
.semantics {
this.contentDescription = avatarContentDescription
},
- onSelectAvatar = SelectAvatar@{
- val cache = ImageHandler.bitmapToCache("createAvatar", it)
+ onPick = {
+ val cache = BitmapUtilities.bitmapToCache("createAvatar", it)
avatarUri = Uri.fromFile(cache)
},
)
@@ -86,7 +85,7 @@ fun CreateActivityContent(
modifier = Modifier
.fillMaxWidth(),
onClick = {
- onClickCreate(name, description, avatarUri)
+ onSubmit(name, description, avatarUri)
},
) {
Text(stringResource(R.string.create_complete_text))
diff --git a/app/src/main/java/eu/steffo/twom/composables/createroom/CreateRoomScaffold.kt b/app/src/main/java/eu/steffo/twom/composables/createroom/CreateRoomScaffold.kt
new file mode 100644
index 0000000..ce969f8
--- /dev/null
+++ b/app/src/main/java/eu/steffo/twom/composables/createroom/CreateRoomScaffold.kt
@@ -0,0 +1,49 @@
+package eu.steffo.twom.composables.createroom
+
+import android.app.Activity
+import android.content.Intent
+import android.net.Uri
+import androidx.activity.ComponentActivity
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Scaffold
+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.activities.CreateRoomActivity
+import eu.steffo.twom.theme.TwoMTheme
+
+@Composable
+@Preview
+fun CreateRoomScaffold() {
+ val context = LocalContext.current
+ val activity = context as Activity
+
+ fun submitActivity(name: String, description: String, avatarUri: Uri?) {
+ val resultIntent = Intent()
+ resultIntent.putExtra(CreateRoomActivity.NAME_EXTRA, name)
+ resultIntent.putExtra(CreateRoomActivity.DESCRIPTION_EXTRA, description)
+ // Kotlin cannot use nullable types in Java interop generics
+ if (avatarUri != null) {
+ resultIntent.putExtra(CreateRoomActivity.AVATAR_EXTRA, avatarUri)
+ }
+ activity.setResult(ComponentActivity.RESULT_OK, resultIntent)
+ activity.finish()
+ }
+
+ TwoMTheme {
+ Scaffold(
+ topBar = {
+ CreateActivityTopBar()
+ },
+ content = {
+ CreateRoomForm(
+ modifier = Modifier.padding(it),
+ onSubmit = { name: String, description: String, avatarUri: Uri? ->
+ submitActivity(name, description, avatarUri)
+ }
+ )
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/steffo/twom/create/CreateActivityTopBar.kt b/app/src/main/java/eu/steffo/twom/composables/createroom/CreateRoomTopBar.kt
similarity index 53%
rename from app/src/main/java/eu/steffo/twom/create/CreateActivityTopBar.kt
rename to app/src/main/java/eu/steffo/twom/composables/createroom/CreateRoomTopBar.kt
index 3c20d12..d078ffd 100644
--- a/app/src/main/java/eu/steffo/twom/create/CreateActivityTopBar.kt
+++ b/app/src/main/java/eu/steffo/twom/composables/createroom/CreateRoomTopBar.kt
@@ -1,10 +1,6 @@
-package eu.steffo.twom.create
+package eu.steffo.twom.composables.createroom
-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.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
@@ -12,24 +8,17 @@ 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.navigation.BackIconButton
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@Preview
fun CreateActivityTopBar(
modifier: Modifier = Modifier,
- onClickBack: () -> Unit = {},
) {
TopAppBar(
modifier = modifier,
- navigationIcon = {
- IconButton(onClick = onClickBack) {
- Icon(
- imageVector = Icons.Filled.ArrowBack,
- contentDescription = LocalContext.current.getString(R.string.back)
- )
- }
- },
+ navigationIcon = { BackIconButton() },
title = { Text(LocalContext.current.getString(R.string.create_title)) }
)
}
\ No newline at end of file
diff --git a/app/src/main/java/eu/steffo/twom/composables/errorhandling/ErrorText.kt b/app/src/main/java/eu/steffo/twom/composables/errorhandling/ErrorText.kt
new file mode 100644
index 0000000..452134b
--- /dev/null
+++ b/app/src/main/java/eu/steffo/twom/composables/errorhandling/ErrorText.kt
@@ -0,0 +1,19 @@
+package eu.steffo.twom.composables.errorhandling
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+
+
+@Composable
+fun ErrorText(
+ text: String,
+ modifier: Modifier = Modifier,
+) {
+ Text(
+ modifier = modifier,
+ text = text,
+ color = MaterialTheme.colorScheme.error,
+ )
+}
diff --git a/app/src/main/java/eu/steffo/twom/login/PasswordField.kt b/app/src/main/java/eu/steffo/twom/composables/fields/PasswordField.kt
similarity index 98%
rename from app/src/main/java/eu/steffo/twom/login/PasswordField.kt
rename to app/src/main/java/eu/steffo/twom/composables/fields/PasswordField.kt
index c6e3c4f..fe61548 100644
--- a/app/src/main/java/eu/steffo/twom/login/PasswordField.kt
+++ b/app/src/main/java/eu/steffo/twom/composables/fields/PasswordField.kt
@@ -1,4 +1,4 @@
-package eu.steffo.twom.login
+package eu.steffo.twom.composables.fields
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.text.KeyboardActions
diff --git a/app/src/main/java/eu/steffo/twom/composables/login/LoginForm.kt b/app/src/main/java/eu/steffo/twom/composables/login/LoginForm.kt
new file mode 100644
index 0000000..fcb4f6b
--- /dev/null
+++ b/app/src/main/java/eu/steffo/twom/composables/login/LoginForm.kt
@@ -0,0 +1,231 @@
+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.ErrorText
+import eu.steffo.twom.composables.errorhandling.LocalizableError
+import eu.steffo.twom.composables.fields.PasswordField
+import eu.steffo.twom.matrix.TwoMMatrix
+import eu.steffo.twom.theme.TwoMPadding
+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),
+}
+
+
+// TODO: Localize error messages
+@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) }
+ val error by remember { mutableStateOf(LocalizableError()) }
+
+ suspend fun doLogin() {
+ error.clear()
+
+ Log.d("Login", "Getting authentication service...")
+ loginStep = LoginStep.SERVICE
+ val auth = TwoMMatrix.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.set(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.set(R.string.login_error_wellknown_generic, e)
+ return
+ }
+ if (wellKnown !is WellknownResult.Prompt) {
+ Log.w(
+ "Login",
+ "Data is not .well-known for: $username"
+ )
+ error.set(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.set(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) {
+ // TODO: It sure would be nice to know which exceptions can be thrown here.
+ Log.e(
+ "Login",
+ "Something went wrong while setting up the login wizard.",
+ e
+ )
+ error.set(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.set(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.occurred()) MaterialTheme.colorScheme.error else ProgressIndicatorDefaults.linearColor
+ )
+ Row(TwoMPadding.base) {
+ Text(LocalContext.current.getString(R.string.login_text))
+ }
+ Row(TwoMPadding.base) {
+ 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(TwoMPadding.base) {
+ PasswordField(
+ modifier = Modifier.fillMaxWidth(),
+ singleLine = true,
+ 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(TwoMPadding.base) {
+ Button(
+ modifier = Modifier.fillMaxWidth(),
+ enabled = (username != "" && (loginStep == LoginStep.NONE || error.occurred())),
+ onClick = {
+ scope.launch { doLogin() }
+ },
+ ) {
+ Text(LocalContext.current.getString(R.string.login_complete_text))
+ }
+ }
+ error.Show {
+ Row(TwoMPadding.base) {
+ ErrorText(it)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/steffo/twom/login/LoginActivityScaffold.kt b/app/src/main/java/eu/steffo/twom/composables/login/LoginScaffold.kt
similarity index 52%
rename from app/src/main/java/eu/steffo/twom/login/LoginActivityScaffold.kt
rename to app/src/main/java/eu/steffo/twom/composables/login/LoginScaffold.kt
index 98c99c4..19f4bb8 100644
--- a/app/src/main/java/eu/steffo/twom/login/LoginActivityScaffold.kt
+++ b/app/src/main/java/eu/steffo/twom/composables/login/LoginScaffold.kt
@@ -1,9 +1,13 @@
-package eu.steffo.twom.login
+package eu.steffo.twom.composables.login
+import android.app.Activity
+import android.content.Intent
+import androidx.activity.ComponentActivity
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
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.theme.TwoMTheme
import org.matrix.android.sdk.api.session.Session
@@ -11,21 +15,26 @@ import org.matrix.android.sdk.api.session.Session
@Composable
@Preview
-fun LoginActivityScaffold(
- onBack: () -> Unit = {},
+fun LoginScaffold(
onLogin: (session: Session) -> Unit = {},
) {
+ val context = LocalContext.current
+ val activity = context as Activity
+
+ fun submitActivity() {
+ activity.setResult(ComponentActivity.RESULT_OK, Intent())
+ activity.finish()
+ }
+
TwoMTheme {
Scaffold(
topBar = {
- LoginActivityTopBar(
- onBack = onBack,
- )
+ LoginActivityTopBar()
},
content = {
- LoginActivityContent(
+ LoginForm(
modifier = Modifier.padding(it),
- onLogin = onLogin
+ onLogin = { submitActivity() }
)
},
)
diff --git a/app/src/main/java/eu/steffo/twom/login/LoginActivityTopBar.kt b/app/src/main/java/eu/steffo/twom/composables/login/LoginTopBar.kt
similarity index 53%
rename from app/src/main/java/eu/steffo/twom/login/LoginActivityTopBar.kt
rename to app/src/main/java/eu/steffo/twom/composables/login/LoginTopBar.kt
index 38eb797..67636ec 100644
--- a/app/src/main/java/eu/steffo/twom/login/LoginActivityTopBar.kt
+++ b/app/src/main/java/eu/steffo/twom/composables/login/LoginTopBar.kt
@@ -1,10 +1,6 @@
-package eu.steffo.twom.login
+package eu.steffo.twom.composables.login
-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.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
@@ -12,24 +8,17 @@ 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.navigation.BackIconButton
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@Preview
fun LoginActivityTopBar(
modifier: Modifier = Modifier,
- onBack: () -> Unit = {},
) {
TopAppBar(
modifier = modifier,
- navigationIcon = {
- IconButton(onClick = onBack) {
- Icon(
- imageVector = Icons.Filled.ArrowBack,
- contentDescription = LocalContext.current.getString(R.string.back)
- )
- }
- },
+ navigationIcon = { BackIconButton() },
title = { Text(LocalContext.current.getString(R.string.login_title)) }
)
}
\ No newline at end of file
diff --git a/app/src/main/java/eu/steffo/twom/main/MainActivityAccountIconButton.kt b/app/src/main/java/eu/steffo/twom/composables/main/AccountIconButton.kt
similarity index 83%
rename from app/src/main/java/eu/steffo/twom/main/MainActivityAccountIconButton.kt
rename to app/src/main/java/eu/steffo/twom/composables/main/AccountIconButton.kt
index 4cfdef9..68cd56f 100644
--- a/app/src/main/java/eu/steffo/twom/main/MainActivityAccountIconButton.kt
+++ b/app/src/main/java/eu/steffo/twom/composables/main/AccountIconButton.kt
@@ -1,5 +1,7 @@
-package eu.steffo.twom.main
+package eu.steffo.twom.composables.main
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.launch
import androidx.compose.foundation.layout.Box
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
@@ -18,19 +20,23 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import eu.steffo.twom.R
+import eu.steffo.twom.activities.LoginActivity
import eu.steffo.twom.matrix.LocalSession
import eu.steffo.twom.matrix.avatar.AvatarFromUserId
@Composable
@Preview(showBackground = true)
-fun MainActivityAccountIconButton(
+fun AccountIconButton(
modifier: Modifier = Modifier,
- onClickLogin: () -> Unit = {},
- onClickLogout: () -> Unit = {},
+ processLogin: () -> Unit = {},
+ processLogout: () -> Unit = {},
) {
val session = LocalSession.current
var expanded by remember { mutableStateOf(false) }
+ val loginLauncher =
+ rememberLauncherForActivityResult(LoginActivity.Contract()) { processLogin() }
+
Box(modifier) {
IconButton(
onClick = { expanded = true },
@@ -58,7 +64,7 @@ fun MainActivityAccountIconButton(
},
onClick = {
expanded = false
- onClickLogin()
+ loginLauncher.launch()
}
)
} else {
@@ -68,7 +74,7 @@ fun MainActivityAccountIconButton(
},
onClick = {
expanded = false
- onClickLogout()
+ processLogout()
}
)
}
diff --git a/app/src/main/java/eu/steffo/twom/composables/main/CreateRoomFAB.kt b/app/src/main/java/eu/steffo/twom/composables/main/CreateRoomFAB.kt
new file mode 100644
index 0000000..e3c2213
--- /dev/null
+++ b/app/src/main/java/eu/steffo/twom/composables/main/CreateRoomFAB.kt
@@ -0,0 +1,33 @@
+package eu.steffo.twom.composables.main
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material3.ExtendedFloatingActionButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import eu.steffo.twom.R
+
+@Composable
+@Preview
+fun CreateRoomFAB(
+ modifier: Modifier = Modifier,
+ onClick: () -> Unit = {},
+) {
+ ExtendedFloatingActionButton(
+ modifier = modifier,
+ onClick = onClick,
+ icon = {
+ Icon(
+ Icons.Filled.Add,
+ contentDescription = null
+ )
+ },
+ text = {
+ Text(stringResource(R.string.main_efab_create_text))
+ }
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/steffo/twom/main/MainActivityRoomList.kt b/app/src/main/java/eu/steffo/twom/composables/main/MainContentLoggedIn.kt
similarity index 51%
rename from app/src/main/java/eu/steffo/twom/main/MainActivityRoomList.kt
rename to app/src/main/java/eu/steffo/twom/composables/main/MainContentLoggedIn.kt
index 1fba34c..bb2ee84 100644
--- a/app/src/main/java/eu/steffo/twom/main/MainActivityRoomList.kt
+++ b/app/src/main/java/eu/steffo/twom/composables/main/MainContentLoggedIn.kt
@@ -1,31 +1,32 @@
-package eu.steffo.twom.main
+package eu.steffo.twom.composables.main
-import android.util.Log
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
-import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import eu.steffo.twom.R
+import eu.steffo.twom.main.RoomListItem
import eu.steffo.twom.matrix.LocalSession
import eu.steffo.twom.matrix.TwoMMatrix
+import eu.steffo.twom.theme.ErrorText
import eu.steffo.twom.theme.TwoMPadding
-import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
@Composable
-fun MainActivityRoomList(
+fun MainContentLoggedIn(
modifier: Modifier = Modifier,
- onClickRoom: (roomId: String) -> Unit = {},
) {
- val scope = rememberCoroutineScope()
-
val session = LocalSession.current
- val roomSummaries by session!!.roomService().getRoomSummariesLive(
+ if (session == null) {
+ ErrorText(stringResource(R.string.error_session_missing))
+ return
+ }
+
+ val roomSummaries by session.roomService().getRoomSummariesLive(
roomSummaryQueryParams {
this.memberships = listOf(Membership.JOIN)
this.includeType = listOf(TwoMMatrix.ROOM_TYPE)
@@ -44,25 +45,7 @@ fun MainActivityRoomList(
text = stringResource(R.string.main_roomlist_empty_text)
)
} else {
- roomSummaries!!.forEach {
- RoomListItem(
- roomSummary = it,
- onClickRoom = onClickRoom,
- onLeaveRoom = {
- scope.launch LeaveRoom@{
- Log.i("Main", "Leaving room `$it`...")
- try {
- session!!.roomService().leaveRoom(it, "Decided to leave the room")
- } catch (error: Throwable) {
- Log.e("Main", "Failed to leave room `$it`: $error")
- // TODO: Display an error somewhere
- return@LeaveRoom
- }
- Log.d("Main", "Successfully left room `$it`!")
- }
- }
- )
- }
+ roomSummaries!!.forEach { RoomListItem(it) }
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/eu/steffo/twom/main/MainActivityNotLoggedIn.kt b/app/src/main/java/eu/steffo/twom/composables/main/MainContentNotLoggedIn.kt
similarity index 91%
rename from app/src/main/java/eu/steffo/twom/main/MainActivityNotLoggedIn.kt
rename to app/src/main/java/eu/steffo/twom/composables/main/MainContentNotLoggedIn.kt
index 25dc2b4..966dc0b 100644
--- a/app/src/main/java/eu/steffo/twom/main/MainActivityNotLoggedIn.kt
+++ b/app/src/main/java/eu/steffo/twom/composables/main/MainContentNotLoggedIn.kt
@@ -1,4 +1,4 @@
-package eu.steffo.twom.main
+package eu.steffo.twom.composables.main
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -12,7 +12,7 @@ import eu.steffo.twom.theme.TwoMPadding
@Composable
@Preview(showBackground = true)
-fun MainActivityNotLoggedIn(
+fun MainContentNotLoggedIn(
modifier: Modifier = Modifier,
onClickLogin: () -> Unit = {},
) {
diff --git a/app/src/main/java/eu/steffo/twom/main/MainActivityScaffold.kt b/app/src/main/java/eu/steffo/twom/composables/main/MainScaffold.kt
similarity index 54%
rename from app/src/main/java/eu/steffo/twom/main/MainActivityScaffold.kt
rename to app/src/main/java/eu/steffo/twom/composables/main/MainScaffold.kt
index ba4f14b..8acc0bd 100644
--- a/app/src/main/java/eu/steffo/twom/main/MainActivityScaffold.kt
+++ b/app/src/main/java/eu/steffo/twom/composables/main/MainScaffold.kt
@@ -1,4 +1,4 @@
-package eu.steffo.twom.main
+package eu.steffo.twom.composables.main
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
@@ -12,32 +12,29 @@ import org.matrix.android.sdk.api.session.Session
@Composable
@Preview
-fun MatrixActivityScaffold(
- onClickLogin: () -> Unit = {},
- onClickLogout: () -> Unit = {},
- onClickRoom: (roomId: String) -> Unit = {},
- onClickCreate: () -> Unit = {},
+fun MainScaffold(
+ processLogin: () -> Unit = {},
+ processLogout: () -> Unit = {},
session: Session? = null,
) {
TwoMTheme {
CompositionLocalProvider(LocalSession provides session) {
Scaffold(
topBar = {
- MainActivityTopBar(
- onClickLogin = onClickLogin,
- onClickLogout = onClickLogout,
+ MainTopBar(
+ processLogin = processLogin,
+ processLogout = processLogout,
)
},
floatingActionButton = {
- MainActivityCreateFAB(
- onClickCreate = onClickCreate,
- )
+ CreateRoomFAB()
},
content = {
- MainActivityContent(
- modifier = Modifier.padding(it),
- onClickRoom = onClickRoom,
- )
+ if (session == null) {
+ MainContentNotLoggedIn(Modifier.padding(it))
+ } else {
+ MainContentLoggedIn(Modifier.padding(it))
+ }
}
)
}
diff --git a/app/src/main/java/eu/steffo/twom/main/MainActivityTopBar.kt b/app/src/main/java/eu/steffo/twom/composables/main/MainTopBar.kt
similarity index 72%
rename from app/src/main/java/eu/steffo/twom/main/MainActivityTopBar.kt
rename to app/src/main/java/eu/steffo/twom/composables/main/MainTopBar.kt
index 4053102..f6f4973 100644
--- a/app/src/main/java/eu/steffo/twom/main/MainActivityTopBar.kt
+++ b/app/src/main/java/eu/steffo/twom/composables/main/MainTopBar.kt
@@ -1,4 +1,4 @@
-package eu.steffo.twom.main
+package eu.steffo.twom.composables.main
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -13,10 +13,10 @@ import eu.steffo.twom.R
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@Preview
-fun MainActivityTopBar(
+fun MainTopBar(
modifier: Modifier = Modifier,
- onClickLogin: () -> Unit = {},
- onClickLogout: () -> Unit = {},
+ processLogin: () -> Unit = {},
+ processLogout: () -> Unit = {},
) {
CenterAlignedTopAppBar(
modifier = modifier,
@@ -24,9 +24,9 @@ fun MainActivityTopBar(
Text(LocalContext.current.getString(R.string.app_name))
},
actions = {
- MainActivityAccountIconButton(
- onClickLogin = onClickLogin,
- onClickLogout = onClickLogout,
+ AccountIconButton(
+ processLogin = processLogin,
+ processLogout = processLogout,
)
},
)
diff --git a/app/src/main/java/eu/steffo/twom/create/CreateActivity.kt b/app/src/main/java/eu/steffo/twom/create/CreateActivity.kt
deleted file mode 100644
index 6b83e20..0000000
--- a/app/src/main/java/eu/steffo/twom/create/CreateActivity.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-package eu.steffo.twom.create
-
-import android.content.Intent
-import android.net.Uri
-import android.os.Bundle
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-
-class CreateActivity : ComponentActivity() {
- companion object {
- const val NAME_EXTRA = "name"
- const val DESCRIPTION_EXTRA = "description"
- const val AVATAR_EXTRA = "avatar"
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- setContent {
- CreateActivityScaffold(
- onClickBack = {
- setResult(RESULT_CANCELED)
- finish()
- },
- onClickCreate = { name: String, description: String, avatarUri: Uri? ->
- val resultIntent = Intent()
- resultIntent.putExtra(NAME_EXTRA, name)
- resultIntent.putExtra(DESCRIPTION_EXTRA, description)
- // Kotlin cannot use nullable types in Java interop generics
- if (avatarUri != null) {
- resultIntent.putExtra(AVATAR_EXTRA, avatarUri)
- }
- setResult(RESULT_OK, resultIntent)
- finish()
- },
- )
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/eu/steffo/twom/create/CreateActivityScaffold.kt b/app/src/main/java/eu/steffo/twom/create/CreateActivityScaffold.kt
deleted file mode 100644
index 10b4169..0000000
--- a/app/src/main/java/eu/steffo/twom/create/CreateActivityScaffold.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-package eu.steffo.twom.create
-
-import android.net.Uri
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.Scaffold
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.Preview
-import eu.steffo.twom.theme.TwoMTheme
-
-@Composable
-@Preview
-fun CreateActivityScaffold(
- onClickBack: () -> Unit = {},
- onClickCreate: (name: String, description: String, avatarUri: Uri?) -> Unit = { _, _, _ -> },
-) {
- TwoMTheme {
- Scaffold(
- topBar = {
- CreateActivityTopBar(
- onClickBack = onClickBack,
- )
- },
- content = {
- CreateActivityContent(
- modifier = Modifier.padding(it),
- onClickCreate = onClickCreate,
- )
- }
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/eu/steffo/twom/login/ErrorText.kt b/app/src/main/java/eu/steffo/twom/login/ErrorText.kt
deleted file mode 100644
index 9381d27..0000000
--- a/app/src/main/java/eu/steffo/twom/login/ErrorText.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-package eu.steffo.twom.login
-
-import androidx.compose.material3.LocalTextStyle
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.font.FontStyle
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.text.style.TextDecoration
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.TextUnit
-
-@Composable
-@Preview(showBackground = true)
-fun ErrorText(
- modifier: Modifier = Modifier,
- text: String = "",
- fontSize: TextUnit = TextUnit.Unspecified,
- fontStyle: FontStyle? = null,
- fontWeight: FontWeight? = null,
- fontFamily: FontFamily? = null,
- letterSpacing: TextUnit = TextUnit.Unspecified,
- textDecoration: TextDecoration? = null,
- textAlign: TextAlign? = null,
- lineHeight: TextUnit = TextUnit.Unspecified,
- overflow: TextOverflow = TextOverflow.Clip,
- softWrap: Boolean = true,
- maxLines: Int = Int.MAX_VALUE,
- minLines: Int = 1,
- onTextLayout: (TextLayoutResult) -> Unit = {},
- style: TextStyle = LocalTextStyle.current
-) {
- Text(
- modifier = modifier,
- text = text,
- color = MaterialTheme.colorScheme.error,
- fontSize = fontSize,
- fontStyle = fontStyle,
- fontWeight = fontWeight,
- fontFamily = fontFamily,
- letterSpacing = letterSpacing,
- textDecoration = textDecoration,
- textAlign = textAlign,
- lineHeight = lineHeight,
- overflow = overflow,
- softWrap = softWrap,
- maxLines = maxLines,
- minLines = minLines,
- onTextLayout = onTextLayout,
- style = style,
- )
-}
\ No newline at end of file
diff --git a/app/src/main/java/eu/steffo/twom/login/LoginActivity.kt b/app/src/main/java/eu/steffo/twom/login/LoginActivity.kt
deleted file mode 100644
index faf97f8..0000000
--- a/app/src/main/java/eu/steffo/twom/login/LoginActivity.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package eu.steffo.twom.login
-
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-
-
-class LoginActivity : ComponentActivity() {
- override fun onStart() {
- super.onStart()
-
- setContent {
- LoginActivityScaffold(
- onBack = {
- setResult(RESULT_CANCELED)
- finish()
- },
- onLogin = {
- setResult(RESULT_OK)
- finish()
- },
- )
- }
- }
-}
diff --git a/app/src/main/java/eu/steffo/twom/login/LoginActivityContent.kt b/app/src/main/java/eu/steffo/twom/login/LoginActivityContent.kt
deleted file mode 100644
index 900d04c..0000000
--- a/app/src/main/java/eu/steffo/twom/login/LoginActivityContent.kt
+++ /dev/null
@@ -1,226 +0,0 @@
-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.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.res.stringResource
-import androidx.compose.ui.tooling.preview.Preview
-import eu.steffo.twom.R
-import eu.steffo.twom.matrix.TwoMMatrix
-import eu.steffo.twom.theme.TwoMPadding
-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
-
-
-// TODO: Localize error messages
-@Composable
-@Preview(showBackground = true)
-fun LoginActivityContent(
- 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 errorMessageId by rememberSaveable { mutableStateOf(null) }
- var errorMessageString by rememberSaveable { mutableStateOf(null) }
-
- Column(modifier) {
- LinearProgressIndicator(
- modifier = Modifier.fillMaxWidth(),
- progress = loginStep.step.toFloat() / LoginStep.DONE.step.toFloat(),
- color = if (errorMessageId != null || errorMessageString != null) MaterialTheme.colorScheme.error else ProgressIndicatorDefaults.linearColor
- )
- Row(TwoMPadding.base) {
- Text(LocalContext.current.getString(R.string.login_text))
- }
- Row(TwoMPadding.base) {
- 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(TwoMPadding.base) {
- PasswordField(
- modifier = Modifier.fillMaxWidth(),
- singleLine = true,
- 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(TwoMPadding.base) {
- Button(
- modifier = Modifier.fillMaxWidth(),
- onClick = {
- scope.launch Login@{
- errorMessageId = null
- errorMessageString = null
-
- Log.d("Login", "Getting authentication service...")
- loginStep = LoginStep.SERVICE
- val auth = TwoMMatrix.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
- )
- errorMessageId = R.string.login_error_username_invalid
- return@Login
- } catch (e: Throwable) {
- // TODO: It sure would be nice to know which exceptions can be thrown here.
- Log.e(
- "Login",
- "Something went wrong while retrieving .well-known data for: $username",
- e
- )
- errorMessageString = e.toString()
- return@Login
- }
- if (wellKnown !is WellknownResult.Prompt) {
- Log.w(
- "Login",
- "Data is not .well-known for: $username"
- )
- errorMessageId = R.string.login_error_wellknown_missing
- return@Login
- }
-
- Log.d("Login", "Retrieving login flows for: ${wellKnown.homeServerUrl}")
- loginStep = LoginStep.FLOWS
- lateinit var flows: LoginFlowResult
- try {
- flows = auth.getLoginFlow(
- HomeServerConnectionConfig
- .Builder()
- .withHomeServerUri(wellKnown.homeServerUrl)
- .build()
- )
- } catch (e: Throwable) {
- // TODO: It sure would be nice to know which exceptions can be thrown here.
- Log.e(
- "Login",
- "Something went wrong while retrieving login flows for: ${wellKnown.homeServerUrl}",
- e
- )
- errorMessageString = e.message
- return@Login
- }
-
- 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) {
- // TODO: It sure would be nice to know which exceptions can be thrown here.
- Log.e(
- "Login",
- "Something went wrong while creating the login wizard.",
- e
- )
- errorMessageString = e.message
- return@Login
- }
-
- 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
- )
- errorMessageString = e.message
- return@Login
- }
-
- Log.d(
- "Login",
- "Logged in successfully with session id: ${session.sessionId}"
- )
- loginStep = LoginStep.DONE
-
- onLogin(session)
- }
- },
- enabled = (username != "" && (loginStep == LoginStep.NONE || errorMessageId != null)),
- ) {
- Text(LocalContext.current.getString(R.string.login_complete_text))
- }
- }
- if (errorMessageId != null) {
- Row(TwoMPadding.base) {
- // FIXME: Can this cause an error?
- ErrorText(
- text = if (errorMessageString != null) {
- errorMessageString!!
- } else if (errorMessageId != null) {
- stringResource(errorMessageId!!)
- } else {
- "Unknown error"
- }
- )
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/eu/steffo/twom/login/LoginStep.kt b/app/src/main/java/eu/steffo/twom/login/LoginStep.kt
deleted file mode 100644
index dbf9139..0000000
--- a/app/src/main/java/eu/steffo/twom/login/LoginStep.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package eu.steffo.twom.login
-
-enum class LoginStep(val step: Int) {
- NONE(0),
- SERVICE(1),
- WELLKNOWN(2),
- FLOWS(3),
- WIZARD(4),
- LOGIN(5),
- DONE(6),
-}
\ No newline at end of file
diff --git a/app/src/main/java/eu/steffo/twom/main/MainActivityContent.kt b/app/src/main/java/eu/steffo/twom/main/MainActivityContent.kt
deleted file mode 100644
index 3e1260e..0000000
--- a/app/src/main/java/eu/steffo/twom/main/MainActivityContent.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package eu.steffo.twom.main
-
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import eu.steffo.twom.matrix.LocalSession
-
-@Composable
-fun MainActivityContent(
- modifier: Modifier = Modifier,
- onClickRoom: (roomId: String) -> Unit = {},
-) {
- val session = LocalSession.current
-
- if (session == null) {
- MainActivityNotLoggedIn(
- modifier = modifier,
- )
- } else {
- MainActivityRoomList(
- modifier = modifier,
- onClickRoom = onClickRoom,
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/eu/steffo/twom/main/MainActivityCreateFAB.kt b/app/src/main/java/eu/steffo/twom/main/MainActivityCreateFAB.kt
deleted file mode 100644
index fc45876..0000000
--- a/app/src/main/java/eu/steffo/twom/main/MainActivityCreateFAB.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-package eu.steffo.twom.main
-
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Add
-import androidx.compose.material3.ExtendedFloatingActionButton
-import androidx.compose.material3.Icon
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalView
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.tooling.preview.Preview
-import eu.steffo.twom.R
-import eu.steffo.twom.matrix.LocalSession
-
-@Composable
-@Preview
-fun MainActivityCreateFAB(
- modifier: Modifier = Modifier,
- onClickCreate: () -> Unit = {},
-) {
- val session = LocalSession.current
-
- val shouldDisplay = (session != null || LocalView.current.isInEditMode)
-
- if (shouldDisplay) {
- ExtendedFloatingActionButton(
- modifier = modifier,
- onClick = onClickCreate,
- icon = {
- Icon(
- Icons.Filled.Add,
- contentDescription = null
- )
- },
- text = {
- Text(stringResource(R.string.main_efab_create_text))
- }
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/eu/steffo/twom/main/RoomListItem.kt b/app/src/main/java/eu/steffo/twom/main/RoomListItem.kt
index 57fa17b..79584e2 100644
--- a/app/src/main/java/eu/steffo/twom/main/RoomListItem.kt
+++ b/app/src/main/java/eu/steffo/twom/main/RoomListItem.kt
@@ -1,5 +1,7 @@
package eu.steffo.twom.main
+import android.util.Log
+import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
@@ -12,6 +14,8 @@ import androidx.compose.material3.Text
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
@@ -19,7 +23,12 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import eu.steffo.twom.R
+import eu.steffo.twom.activities.RoomActivity
+import eu.steffo.twom.composables.errorhandling.LocalizableError
+import eu.steffo.twom.matrix.LocalSession
import eu.steffo.twom.matrix.avatar.AvatarFromURL
+import eu.steffo.twom.theme.ErrorText
+import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.room.model.RoomSummary
@@ -27,14 +36,42 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
@Composable
fun RoomListItem(
roomSummary: RoomSummary,
- onClickRoom: (roomId: String) -> Unit = {},
- onLeaveRoom: (roomId: String) -> Unit = {},
) {
+ val roomId = roomSummary.roomId
+
+ val scope = rememberCoroutineScope()
+
+ val session = LocalSession.current
+ if (session == null) {
+ ErrorText(stringResource(R.string.error_session_missing))
+ return
+ }
+
var expanded by rememberSaveable { mutableStateOf(false) }
+ val error by remember { mutableStateOf(LocalizableError()) }
+
+ val roomActivityLauncher = rememberLauncherForActivityResult(RoomActivity.Contract()) {}
+
+ fun openRoom() {
+ Log.i("Main", "Opening room `$roomId`...")
+ roomActivityLauncher.launch(roomId)
+ }
+
+ suspend fun leaveRoom() {
+ Log.i("Main", "Leaving room `$roomId`...")
+ try {
+ session.roomService().leaveRoom(roomId, "Decided to leave the room")
+ } catch (e: Throwable) {
+ Log.e("Main", "Failed to leave room `$roomId`: $error")
+ error.set(R.string.main_error_leave_generic, e)
+ return
+ }
+ Log.d("Main", "Successfully left room `$roomId`!")
+ }
ListItem(
modifier = Modifier.combinedClickable(
- onClick = { onClickRoom(roomSummary.roomId) },
+ onClick = { openRoom() },
onLongClick = { expanded = true }
),
headlineContent = {
@@ -72,7 +109,7 @@ fun RoomListItem(
},
onClick = {
expanded = false
- onLeaveRoom(roomSummary.roomId)
+ scope.launch { leaveRoom() }
}
)
}
diff --git a/app/src/main/java/eu/steffo/twom/room/RoomActivityContent.kt b/app/src/main/java/eu/steffo/twom/room/RoomActivityContent.kt
index 99b7493..f5ee28d 100644
--- a/app/src/main/java/eu/steffo/twom/room/RoomActivityContent.kt
+++ b/app/src/main/java/eu/steffo/twom/room/RoomActivityContent.kt
@@ -34,7 +34,7 @@ fun RoomActivityContent(
val session = LocalSession.current
if (session == null) {
- ErrorText(stringResource(R.string.room_error_session_missing))
+ ErrorText(stringResource(R.string.error_session_missing))
return
}
diff --git a/app/src/main/java/eu/steffo/twom/create/ImageHandler.kt b/app/src/main/java/eu/steffo/twom/utils/BitmapUtilities.kt
similarity index 98%
rename from app/src/main/java/eu/steffo/twom/create/ImageHandler.kt
rename to app/src/main/java/eu/steffo/twom/utils/BitmapUtilities.kt
index a3fb606..50a0b9e 100644
--- a/app/src/main/java/eu/steffo/twom/create/ImageHandler.kt
+++ b/app/src/main/java/eu/steffo/twom/utils/BitmapUtilities.kt
@@ -1,4 +1,4 @@
-package eu.steffo.twom.create
+package eu.steffo.twom.utils
import android.content.ContentResolver
import android.graphics.Bitmap
@@ -8,7 +8,7 @@ import android.net.Uri
import androidx.exifinterface.media.ExifInterface
import java.io.File
-class ImageHandler {
+class BitmapUtilities {
companion object {
fun getOrientation(contentResolver: ContentResolver, uri: Uri): Int? {
contentResolver.openInputStream(uri).use {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ab214e0..6ccae6c 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -55,7 +55,7 @@
Hasn\'t answered yet
Leave a comment…
Update
- The Matrix session context has not been initialized.
+ The Matrix session context has not been initialized.
Could not find the requested Matrix room.
The Matrix room context has not been initialized.
No answer
@@ -69,4 +69,9 @@
Not opened
Hasn\'t opened the invite yet
Leave party
+ Something went wrong while retrieving .well-known data: %1$s
+ Something went wrong while retrieving login flows: %1$s
+ Something went wrong while setting up the login wizard: %1$s
+ Something went wrong while logging in: %1$s
+ Something went wrong while leaving the room: %1$s
\ No newline at end of file
diff --git a/app/src/test/java/eu/steffo/twom/ExampleUnitTest.kt b/app/src/test/java/eu/steffo/twom/ExampleUnitTest.kt
deleted file mode 100644
index cf9f700..0000000
--- a/app/src/test/java/eu/steffo/twom/ExampleUnitTest.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package eu.steffo.twom
-
-import org.junit.Test
-
-import org.junit.Assert.*
-
-/**
- * Example local unit test, which will execute on the development machine (host).
- *
- * See [testing documentation](http://d.android.com/tools/testing).
- */
-class ExampleUnitTest {
- @Test
- fun addition_isCorrect() {
- assertEquals(4, 2 + 2)
- }
-}
\ No newline at end of file