mirror of
https://github.com/Steffo99/twom.git
synced 2025-02-16 16:23:57 +00:00
Start some kind of massive refactor
This commit is contained in:
parent
59932ab67c
commit
5befff9864
34 changed files with 602 additions and 671 deletions
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -31,7 +31,7 @@
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".main.MainActivity"
|
android:name=".activities.MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:theme="@style/Theme.TwoM">
|
android:theme="@style/Theme.TwoM">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
@ -42,15 +42,15 @@
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".login.LoginActivity"
|
android:name=".activities.LoginActivity"
|
||||||
android:theme="@style/Theme.TwoM" />
|
android:theme="@style/Theme.TwoM" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".room.RoomActivity"
|
android:name=".activities.RoomActivity"
|
||||||
android:theme="@style/Theme.TwoM" />
|
android:theme="@style/Theme.TwoM" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".create.CreateActivity"
|
android:name=".activities.CreateRoomActivity"
|
||||||
android:theme="@style/Theme.TwoM" />
|
android:theme="@style/Theme.TwoM" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
|
@ -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<Unit, CreateRoomActivityResult?>() {
|
||||||
|
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() }
|
||||||
|
}
|
||||||
|
}
|
25
app/src/main/java/eu/steffo/twom/activities/LoginActivity.kt
Normal file
25
app/src/main/java/eu/steffo/twom/activities/LoginActivity.kt
Normal file
|
@ -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<Unit, Unit>() {
|
||||||
|
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() }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package eu.steffo.twom.main
|
package eu.steffo.twom.activities
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
@ -11,10 +11,8 @@ import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.core.net.toFile
|
import androidx.core.net.toFile
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import eu.steffo.twom.create.CreateActivity
|
import eu.steffo.twom.composables.main.MainScaffold
|
||||||
import eu.steffo.twom.login.LoginActivity
|
|
||||||
import eu.steffo.twom.matrix.TwoMMatrix
|
import eu.steffo.twom.matrix.TwoMMatrix
|
||||||
import eu.steffo.twom.room.RoomActivity
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||||
|
@ -77,6 +75,12 @@ class MainActivity : ComponentActivity() {
|
||||||
currentSession.open()
|
currentSession.open()
|
||||||
currentSession.syncService().startSync(true)
|
currentSession.syncService().startSync(true)
|
||||||
currentSession.addListener(OpenSessionListener(this::resetContent))
|
currentSession.addListener(OpenSessionListener(this::resetContent))
|
||||||
|
|
||||||
|
Log.d(
|
||||||
|
"Main",
|
||||||
|
"Opened session, recomposing..."
|
||||||
|
)
|
||||||
|
resetContent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,42 +99,8 @@ class MainActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onClickLogin() {
|
private fun destroySession() {
|
||||||
Log.d("Main", "Clicked login, launching login activity...")
|
|
||||||
val intent = Intent(applicationContext, LoginActivity::class.java)
|
|
||||||
loginLauncher.launch(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
private fun onClickRoom(roomId: String) {
|
||||||
|
@ -146,7 +116,7 @@ class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
private fun onClickCreate() {
|
private fun onClickCreate() {
|
||||||
Log.d("Main", "Clicked the New button, launching create activity...")
|
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)
|
createLauncher.launch(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,10 +124,10 @@ class MainActivity : ComponentActivity() {
|
||||||
Log.d("Main", "Received result from create activity: $result")
|
Log.d("Main", "Received result from create activity: $result")
|
||||||
if (result.resultCode == RESULT_OK) {
|
if (result.resultCode == RESULT_OK) {
|
||||||
val intent: Intent = result.data!!
|
val intent: Intent = result.data!!
|
||||||
val name = intent.getStringExtra(CreateActivity.NAME_EXTRA)
|
val name = intent.getStringExtra(CreateRoomActivity.NAME_EXTRA)
|
||||||
val description = intent.getStringExtra(CreateActivity.DESCRIPTION_EXTRA)
|
val description = intent.getStringExtra(CreateRoomActivity.DESCRIPTION_EXTRA)
|
||||||
@Suppress("DEPRECATION") val avatarUri =
|
@Suppress("DEPRECATION") val avatarUri =
|
||||||
intent.getParcelableExtra<Uri>(CreateActivity.AVATAR_EXTRA)
|
intent.getParcelableExtra<Uri>(CreateRoomActivity.AVATAR_EXTRA)
|
||||||
|
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
Log.w("Main", "Result from create activity did not have `name` extra set")
|
Log.w("Main", "Result from create activity did not have `name` extra set")
|
||||||
|
@ -265,12 +235,32 @@ class MainActivity : ComponentActivity() {
|
||||||
private fun resetContent() {
|
private fun resetContent() {
|
||||||
Log.d("Main", "Recomposing...")
|
Log.d("Main", "Recomposing...")
|
||||||
setContent {
|
setContent {
|
||||||
MatrixActivityScaffold(
|
// TODO: Check this with a clearer mind
|
||||||
onClickLogin = this::onClickLogin,
|
MainScaffold(
|
||||||
onClickLogout = this::onClickLogout,
|
|
||||||
onClickRoom = this::onClickRoom,
|
|
||||||
onClickCreate = this::onClickCreate,
|
|
||||||
session = session,
|
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!")
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.result.contract.ActivityResultContract
|
||||||
import eu.steffo.twom.matrix.TwoMMatrix
|
import eu.steffo.twom.matrix.TwoMMatrix
|
||||||
|
import eu.steffo.twom.room.RoomActivityScaffold
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
|
||||||
class RoomActivity : ComponentActivity() {
|
class RoomActivity : ComponentActivity() {
|
||||||
|
@ -12,6 +16,16 @@ class RoomActivity : ComponentActivity() {
|
||||||
const val ROOM_ID_EXTRA = "roomId"
|
const val ROOM_ID_EXTRA = "roomId"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Contract : ActivityResultContract<String, Unit>() {
|
||||||
|
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 lateinit var session: Session
|
||||||
|
|
||||||
private fun fetchLastSession() {
|
private fun fetchLastSession() {
|
|
@ -1,4 +1,4 @@
|
||||||
package eu.steffo.twom.create
|
package eu.steffo.twom.composables.avatar
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
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.platform.LocalContext
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import eu.steffo.twom.matrix.avatar.AvatarFromImageBitmap
|
import eu.steffo.twom.matrix.avatar.AvatarFromImageBitmap
|
||||||
|
import eu.steffo.twom.utils.BitmapUtilities
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview(widthDp = 40, heightDp = 40)
|
@Preview(widthDp = 40, heightDp = 40)
|
||||||
fun AvatarSelector(
|
fun AvatarPicker(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onSelectAvatar: (bitmap: Bitmap) -> Unit = {},
|
fallbackText: String = "?",
|
||||||
|
onPick: (bitmap: Bitmap) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val resolver = context.contentResolver
|
val resolver = context.contentResolver
|
||||||
|
@ -32,13 +34,13 @@ fun AvatarSelector(
|
||||||
rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) ImageSelect@{
|
rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) ImageSelect@{
|
||||||
it ?: return@ImageSelect
|
it ?: return@ImageSelect
|
||||||
|
|
||||||
val rawBitmap = ImageHandler.getRawBitmap(resolver, it) ?: return@ImageSelect
|
val rawBitmap = BitmapUtilities.getRawBitmap(resolver, it) ?: return@ImageSelect
|
||||||
val orientation = ImageHandler.getOrientation(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
|
selection = correctedBitmap
|
||||||
onSelectAvatar(correctedBitmap)
|
onPick(correctedBitmap)
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
|
@ -49,6 +51,7 @@ fun AvatarSelector(
|
||||||
) {
|
) {
|
||||||
AvatarFromImageBitmap(
|
AvatarFromImageBitmap(
|
||||||
bitmap = selection?.asImageBitmap(),
|
bitmap = selection?.asImageBitmap(),
|
||||||
|
fallbackText = fallbackText,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package eu.steffo.twom.create
|
package eu.steffo.twom.composables.createroom
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.compose.foundation.layout.Column
|
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.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.steffo.twom.R
|
import eu.steffo.twom.R
|
||||||
|
import eu.steffo.twom.composables.avatar.AvatarPicker
|
||||||
import eu.steffo.twom.theme.TwoMPadding
|
import eu.steffo.twom.theme.TwoMPadding
|
||||||
|
import eu.steffo.twom.utils.BitmapUtilities
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview(showBackground = true)
|
||||||
fun CreateActivityContent(
|
fun CreateRoomForm(
|
||||||
modifier: Modifier = Modifier,
|
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 name by rememberSaveable { mutableStateOf("") }
|
||||||
var description by rememberSaveable { mutableStateOf("") }
|
var description by rememberSaveable { mutableStateOf("") }
|
||||||
var avatarUri by rememberSaveable { mutableStateOf<Uri?>(null) }
|
var avatarUri by rememberSaveable { mutableStateOf<Uri?>(null) }
|
||||||
|
|
||||||
// val avatarBitmap = if(avatarUri != null) BitmapFactory.decodeFile(avatarUri.toString()).asImageBitmap() else null
|
|
||||||
|
|
||||||
Column(modifier) {
|
Column(modifier) {
|
||||||
Row(TwoMPadding.base) {
|
Row(TwoMPadding.base) {
|
||||||
val avatarContentDescription = stringResource(R.string.create_avatar_label)
|
val avatarContentDescription = stringResource(R.string.create_avatar_label)
|
||||||
AvatarSelector(
|
AvatarPicker(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(60.dp)
|
.size(60.dp)
|
||||||
.clip(MaterialTheme.shapes.medium)
|
.clip(MaterialTheme.shapes.medium)
|
||||||
.semantics {
|
.semantics {
|
||||||
this.contentDescription = avatarContentDescription
|
this.contentDescription = avatarContentDescription
|
||||||
},
|
},
|
||||||
onSelectAvatar = SelectAvatar@{
|
onPick = {
|
||||||
val cache = ImageHandler.bitmapToCache("createAvatar", it)
|
val cache = BitmapUtilities.bitmapToCache("createAvatar", it)
|
||||||
avatarUri = Uri.fromFile(cache)
|
avatarUri = Uri.fromFile(cache)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -86,7 +85,7 @@ fun CreateActivityContent(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
onClick = {
|
onClick = {
|
||||||
onClickCreate(name, description, avatarUri)
|
onSubmit(name, description, avatarUri)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.create_complete_text))
|
Text(stringResource(R.string.create_complete_text))
|
|
@ -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)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
@ -12,24 +8,17 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import eu.steffo.twom.R
|
import eu.steffo.twom.R
|
||||||
|
import eu.steffo.twom.composables.navigation.BackIconButton
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
fun CreateActivityTopBar(
|
fun CreateActivityTopBar(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClickBack: () -> Unit = {},
|
|
||||||
) {
|
) {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
navigationIcon = {
|
navigationIcon = { BackIconButton() },
|
||||||
IconButton(onClick = onClickBack) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.ArrowBack,
|
|
||||||
contentDescription = LocalContext.current.getString(R.string.back)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
title = { Text(LocalContext.current.getString(R.string.create_title)) }
|
title = { Text(LocalContext.current.getString(R.string.create_title)) }
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
|
@ -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.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
231
app/src/main/java/eu/steffo/twom/composables/login/LoginForm.kt
Normal file
231
app/src/main/java/eu/steffo/twom/composables/login/LoginForm.kt
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.foundation.layout.padding
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import eu.steffo.twom.theme.TwoMTheme
|
import eu.steffo.twom.theme.TwoMTheme
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
@ -11,21 +15,26 @@ import org.matrix.android.sdk.api.session.Session
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
fun LoginActivityScaffold(
|
fun LoginScaffold(
|
||||||
onBack: () -> Unit = {},
|
|
||||||
onLogin: (session: Session) -> Unit = {},
|
onLogin: (session: Session) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val activity = context as Activity
|
||||||
|
|
||||||
|
fun submitActivity() {
|
||||||
|
activity.setResult(ComponentActivity.RESULT_OK, Intent())
|
||||||
|
activity.finish()
|
||||||
|
}
|
||||||
|
|
||||||
TwoMTheme {
|
TwoMTheme {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
LoginActivityTopBar(
|
LoginActivityTopBar()
|
||||||
onBack = onBack,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
content = {
|
content = {
|
||||||
LoginActivityContent(
|
LoginForm(
|
||||||
modifier = Modifier.padding(it),
|
modifier = Modifier.padding(it),
|
||||||
onLogin = onLogin
|
onLogin = { submitActivity() }
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
|
@ -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.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
@ -12,24 +8,17 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import eu.steffo.twom.R
|
import eu.steffo.twom.R
|
||||||
|
import eu.steffo.twom.composables.navigation.BackIconButton
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
fun LoginActivityTopBar(
|
fun LoginActivityTopBar(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onBack: () -> Unit = {},
|
|
||||||
) {
|
) {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
navigationIcon = {
|
navigationIcon = { BackIconButton() },
|
||||||
IconButton(onClick = onBack) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.ArrowBack,
|
|
||||||
contentDescription = LocalContext.current.getString(R.string.back)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
title = { Text(LocalContext.current.getString(R.string.login_title)) }
|
title = { Text(LocalContext.current.getString(R.string.login_title)) }
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -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.foundation.layout.Box
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.AccountCircle
|
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.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import eu.steffo.twom.R
|
import eu.steffo.twom.R
|
||||||
|
import eu.steffo.twom.activities.LoginActivity
|
||||||
import eu.steffo.twom.matrix.LocalSession
|
import eu.steffo.twom.matrix.LocalSession
|
||||||
import eu.steffo.twom.matrix.avatar.AvatarFromUserId
|
import eu.steffo.twom.matrix.avatar.AvatarFromUserId
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview(showBackground = true)
|
@Preview(showBackground = true)
|
||||||
fun MainActivityAccountIconButton(
|
fun AccountIconButton(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClickLogin: () -> Unit = {},
|
processLogin: () -> Unit = {},
|
||||||
onClickLogout: () -> Unit = {},
|
processLogout: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val session = LocalSession.current
|
val session = LocalSession.current
|
||||||
var expanded by remember { mutableStateOf(false) }
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val loginLauncher =
|
||||||
|
rememberLauncherForActivityResult(LoginActivity.Contract()) { processLogin() }
|
||||||
|
|
||||||
Box(modifier) {
|
Box(modifier) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { expanded = true },
|
onClick = { expanded = true },
|
||||||
|
@ -58,7 +64,7 @@ fun MainActivityAccountIconButton(
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
expanded = false
|
expanded = false
|
||||||
onClickLogin()
|
loginLauncher.launch()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -68,7 +74,7 @@ fun MainActivityAccountIconButton(
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
expanded = false
|
expanded = false
|
||||||
onClickLogout()
|
processLogout()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -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))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
|
@ -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.foundation.layout.Column
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.steffo.twom.R
|
import eu.steffo.twom.R
|
||||||
|
import eu.steffo.twom.main.RoomListItem
|
||||||
import eu.steffo.twom.matrix.LocalSession
|
import eu.steffo.twom.matrix.LocalSession
|
||||||
import eu.steffo.twom.matrix.TwoMMatrix
|
import eu.steffo.twom.matrix.TwoMMatrix
|
||||||
|
import eu.steffo.twom.theme.ErrorText
|
||||||
import eu.steffo.twom.theme.TwoMPadding
|
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.model.Membership
|
||||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MainActivityRoomList(
|
fun MainContentLoggedIn(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClickRoom: (roomId: String) -> Unit = {},
|
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
val session = LocalSession.current
|
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 {
|
roomSummaryQueryParams {
|
||||||
this.memberships = listOf(Membership.JOIN)
|
this.memberships = listOf(Membership.JOIN)
|
||||||
this.includeType = listOf(TwoMMatrix.ROOM_TYPE)
|
this.includeType = listOf(TwoMMatrix.ROOM_TYPE)
|
||||||
|
@ -44,25 +45,7 @@ fun MainActivityRoomList(
|
||||||
text = stringResource(R.string.main_roomlist_empty_text)
|
text = stringResource(R.string.main_roomlist_empty_text)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
roomSummaries!!.forEach {
|
roomSummaries!!.forEach { RoomListItem(it) }
|
||||||
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`!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
@ -12,7 +12,7 @@ import eu.steffo.twom.theme.TwoMPadding
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview(showBackground = true)
|
@Preview(showBackground = true)
|
||||||
fun MainActivityNotLoggedIn(
|
fun MainContentNotLoggedIn(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClickLogin: () -> Unit = {},
|
onClickLogin: () -> Unit = {},
|
||||||
) {
|
) {
|
|
@ -1,4 +1,4 @@
|
||||||
package eu.steffo.twom.main
|
package eu.steffo.twom.composables.main
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
@ -12,32 +12,29 @@ import org.matrix.android.sdk.api.session.Session
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
fun MatrixActivityScaffold(
|
fun MainScaffold(
|
||||||
onClickLogin: () -> Unit = {},
|
processLogin: () -> Unit = {},
|
||||||
onClickLogout: () -> Unit = {},
|
processLogout: () -> Unit = {},
|
||||||
onClickRoom: (roomId: String) -> Unit = {},
|
|
||||||
onClickCreate: () -> Unit = {},
|
|
||||||
session: Session? = null,
|
session: Session? = null,
|
||||||
) {
|
) {
|
||||||
TwoMTheme {
|
TwoMTheme {
|
||||||
CompositionLocalProvider(LocalSession provides session) {
|
CompositionLocalProvider(LocalSession provides session) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
MainActivityTopBar(
|
MainTopBar(
|
||||||
onClickLogin = onClickLogin,
|
processLogin = processLogin,
|
||||||
onClickLogout = onClickLogout,
|
processLogout = processLogout,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
MainActivityCreateFAB(
|
CreateRoomFAB()
|
||||||
onClickCreate = onClickCreate,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
content = {
|
content = {
|
||||||
MainActivityContent(
|
if (session == null) {
|
||||||
modifier = Modifier.padding(it),
|
MainContentNotLoggedIn(Modifier.padding(it))
|
||||||
onClickRoom = onClickRoom,
|
} else {
|
||||||
)
|
MainContentLoggedIn(Modifier.padding(it))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package eu.steffo.twom.main
|
package eu.steffo.twom.composables.main
|
||||||
|
|
||||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
@ -13,10 +13,10 @@ import eu.steffo.twom.R
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
fun MainActivityTopBar(
|
fun MainTopBar(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClickLogin: () -> Unit = {},
|
processLogin: () -> Unit = {},
|
||||||
onClickLogout: () -> Unit = {},
|
processLogout: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
CenterAlignedTopAppBar(
|
CenterAlignedTopAppBar(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
@ -24,9 +24,9 @@ fun MainActivityTopBar(
|
||||||
Text(LocalContext.current.getString(R.string.app_name))
|
Text(LocalContext.current.getString(R.string.app_name))
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
MainActivityAccountIconButton(
|
AccountIconButton(
|
||||||
onClickLogin = onClickLogin,
|
processLogin = processLogin,
|
||||||
onClickLogout = onClickLogout,
|
processLogout = processLogout,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
|
@ -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()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<Int?>(null) }
|
|
||||||
var errorMessageString by rememberSaveable { mutableStateOf<String?>(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"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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),
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,7 @@
|
||||||
package eu.steffo.twom.main
|
package eu.steffo.twom.main
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
@ -12,6 +14,8 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
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.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
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.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.steffo.twom.R
|
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.matrix.avatar.AvatarFromURL
|
||||||
|
import eu.steffo.twom.theme.ErrorText
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
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
|
@Composable
|
||||||
fun RoomListItem(
|
fun RoomListItem(
|
||||||
roomSummary: RoomSummary,
|
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) }
|
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(
|
ListItem(
|
||||||
modifier = Modifier.combinedClickable(
|
modifier = Modifier.combinedClickable(
|
||||||
onClick = { onClickRoom(roomSummary.roomId) },
|
onClick = { openRoom() },
|
||||||
onLongClick = { expanded = true }
|
onLongClick = { expanded = true }
|
||||||
),
|
),
|
||||||
headlineContent = {
|
headlineContent = {
|
||||||
|
@ -72,7 +109,7 @@ fun RoomListItem(
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
expanded = false
|
expanded = false
|
||||||
onLeaveRoom(roomSummary.roomId)
|
scope.launch { leaveRoom() }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ fun RoomActivityContent(
|
||||||
|
|
||||||
val session = LocalSession.current
|
val session = LocalSession.current
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
ErrorText(stringResource(R.string.room_error_session_missing))
|
ErrorText(stringResource(R.string.error_session_missing))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package eu.steffo.twom.create
|
package eu.steffo.twom.utils
|
||||||
|
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
@ -8,7 +8,7 @@ import android.net.Uri
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterface
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class ImageHandler {
|
class BitmapUtilities {
|
||||||
companion object {
|
companion object {
|
||||||
fun getOrientation(contentResolver: ContentResolver, uri: Uri): Int? {
|
fun getOrientation(contentResolver: ContentResolver, uri: Uri): Int? {
|
||||||
contentResolver.openInputStream(uri).use {
|
contentResolver.openInputStream(uri).use {
|
|
@ -55,7 +55,7 @@
|
||||||
<string name="room_rsvp_unknown_response">Hasn\'t answered yet</string>
|
<string name="room_rsvp_unknown_response">Hasn\'t answered yet</string>
|
||||||
<string name="room_rsvp_unknown_placeholder">Leave a comment…</string>
|
<string name="room_rsvp_unknown_placeholder">Leave a comment…</string>
|
||||||
<string name="room_update_label">Update</string>
|
<string name="room_update_label">Update</string>
|
||||||
<string name="room_error_session_missing">The Matrix session context has not been initialized.</string>
|
<string name="error_session_missing">The Matrix session context has not been initialized.</string>
|
||||||
<string name="room_error_room_notfound">Could not find the requested Matrix room.</string>
|
<string name="room_error_room_notfound">Could not find the requested Matrix room.</string>
|
||||||
<string name="room_error_room_missing">The Matrix room context has not been initialized.</string>
|
<string name="room_error_room_missing">The Matrix room context has not been initialized.</string>
|
||||||
<string name="room_rsvp_unknown_label">No answer</string>
|
<string name="room_rsvp_unknown_label">No answer</string>
|
||||||
|
@ -69,4 +69,9 @@
|
||||||
<string name="room_rsvp_invited_label">Not opened</string>
|
<string name="room_rsvp_invited_label">Not opened</string>
|
||||||
<string name="room_rsvp_invited_response">Hasn\'t opened the invite yet</string>
|
<string name="room_rsvp_invited_response">Hasn\'t opened the invite yet</string>
|
||||||
<string name="main_room_leave_label">Leave party</string>
|
<string name="main_room_leave_label">Leave party</string>
|
||||||
|
<string name="login_error_wellknown_generic">Something went wrong while retrieving .well-known data: %1$s</string>
|
||||||
|
<string name="login_error_flows_generic">Something went wrong while retrieving login flows: %1$s</string>
|
||||||
|
<string name="login_error_wizard_generic">Something went wrong while setting up the login wizard: %1$s</string>
|
||||||
|
<string name="login_error_login_generic">Something went wrong while logging in: %1$s</string>
|
||||||
|
<string name="main_error_leave_generic">Something went wrong while leaving the room: %1$s</string>
|
||||||
</resources>
|
</resources>
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue