mirror of
https://github.com/Steffo99/twom.git
synced 2024-11-21 23:54:26 +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>
|
||||
|
||||
<activity
|
||||
android:name=".main.MainActivity"
|
||||
android:name=".activities.MainActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.TwoM">
|
||||
<intent-filter>
|
||||
|
@ -42,15 +42,15 @@
|
|||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".login.LoginActivity"
|
||||
android:name=".activities.LoginActivity"
|
||||
android:theme="@style/Theme.TwoM" />
|
||||
|
||||
<activity
|
||||
android:name=".room.RoomActivity"
|
||||
android:name=".activities.RoomActivity"
|
||||
android:theme="@style/Theme.TwoM" />
|
||||
|
||||
<activity
|
||||
android:name=".create.CreateActivity"
|
||||
android:name=".activities.CreateRoomActivity"
|
||||
android:theme="@style/Theme.TwoM" />
|
||||
|
||||
</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.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<Uri>(CreateActivity.AVATAR_EXTRA)
|
||||
intent.getParcelableExtra<Uri>(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!")
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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<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 fun fetchLastSession() {
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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<Uri?>(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))
|
|
@ -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.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)) }
|
||||
)
|
||||
}
|
|
@ -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.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.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() }
|
||||
)
|
||||
},
|
||||
)
|
|
@ -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)) }
|
||||
)
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
)
|
||||
}
|
|
@ -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.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) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 = {},
|
||||
) {
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
|
@ -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,
|
||||
)
|
||||
},
|
||||
)
|
|
@ -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
|
||||
|
||||
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() }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
|
@ -55,7 +55,7 @@
|
|||
<string name="room_rsvp_unknown_response">Hasn\'t answered yet</string>
|
||||
<string name="room_rsvp_unknown_placeholder">Leave a comment…</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_missing">The Matrix room context has not been initialized.</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_response">Hasn\'t opened the invite yet</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>
|
|
@ -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…
Reference in a new issue