From 395709cc28ff533df605ad0560c28c2dfa429351 Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Sat, 20 Jan 2024 21:43:32 +0100 Subject: [PATCH] Allow changes to the room configuration --- .../twom/activities/ConfigureRoomActivity.kt | 74 ++++++++--- .../eu/steffo/twom/activities/MainActivity.kt | 6 +- .../twom/composables/avatar/AvatarPicker.kt | 23 ++-- .../configureroom/ConfigureRoomForm.kt | 39 ++++-- .../configureroom/ConfigureRoomScaffold.kt | 31 ++--- .../configureroom/ConfigureRoomTopBar.kt | 13 +- .../twom/composables/main/CreateRoomFAB.kt | 7 +- .../twom/composables/main/MainScaffold.kt | 6 +- .../composables/viewroom/RoomIconButton.kt | 117 +++++++++++++++--- .../composables/viewroom/ViewRoomTopBar.kt | 16 +-- app/src/main/res/values/strings.xml | 1 + 11 files changed, 231 insertions(+), 102 deletions(-) diff --git a/app/src/main/java/eu/steffo/twom/activities/ConfigureRoomActivity.kt b/app/src/main/java/eu/steffo/twom/activities/ConfigureRoomActivity.kt index 57a2e41..76a2244 100644 --- a/app/src/main/java/eu/steffo/twom/activities/ConfigureRoomActivity.kt +++ b/app/src/main/java/eu/steffo/twom/activities/ConfigureRoomActivity.kt @@ -17,32 +17,68 @@ class ConfigureRoomActivity : ComponentActivity() { const val AVATAR_EXTRA = "avatar" } - data class Result( + data class Configuration( val name: String, val description: String, val avatarUri: Uri?, - ) + ) { + fun toIntent(): Intent { + val intent = Intent() + intent.putExtra(NAME_EXTRA, this.name) + intent.putExtra(DESCRIPTION_EXTRA, this.description) + if (this.avatarUri != null) { + intent.putExtra(AVATAR_EXTRA, this.avatarUri.toString()) + } + return intent + } - class Contract : ActivityResultContract() { + fun toIntent(context: Context, klass: Class): Intent { + val intent = Intent(context, klass) + intent.putExtra(NAME_EXTRA, this.name) + intent.putExtra(DESCRIPTION_EXTRA, this.description) + if (this.avatarUri != null) { + intent.putExtra(AVATAR_EXTRA, this.avatarUri.toString()) + } + return intent + } + + companion object { + fun fromIntent(intent: Intent): Configuration? { + val name = intent.getStringExtra(NAME_EXTRA) ?: return null + val description = intent.getStringExtra(DESCRIPTION_EXTRA) ?: return null + val avatarString = intent.getStringExtra(AVATAR_EXTRA) + val avatarUri = if (avatarString != null) Uri.parse(avatarString) else null + + return Configuration( + name = name, + description = description, + avatarUri = avatarUri, + ) + } + } + } + + class CreateContract : ActivityResultContract() { override fun createIntent(context: Context, input: Unit): Intent { return Intent(context, ConfigureRoomActivity::class.java) } - override fun parseResult(resultCode: Int, intent: Intent?): Result? { + override fun parseResult(resultCode: Int, intent: Intent?): Configuration? { return when (resultCode) { - RESULT_OK -> { - intent!! - val name = intent.getStringExtra(NAME_EXTRA)!! - val description = intent.getStringExtra(DESCRIPTION_EXTRA)!! - val avatar = intent.getStringExtra(AVATAR_EXTRA) + RESULT_OK -> Configuration.fromIntent(intent!!) + else -> null + } + } + } - Result( - name = name, - description = description, - avatarUri = if (avatar != null) Uri.parse(avatar) else null, - ) - } + class EditContract : ActivityResultContract() { + override fun createIntent(context: Context, input: Configuration): Intent { + return input.toIntent(context, ConfigureRoomActivity::class.java) + } + override fun parseResult(resultCode: Int, intent: Intent?): Configuration? { + return when (resultCode) { + RESULT_OK -> Configuration.fromIntent(intent!!) else -> null } } @@ -51,6 +87,12 @@ class ConfigureRoomActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContent { ConfigureRoomScaffold() } + val configuration = Configuration.fromIntent(intent) + + setContent { + ConfigureRoomScaffold( + initialConfiguration = configuration, + ) + } } } \ No newline at end of file diff --git a/app/src/main/java/eu/steffo/twom/activities/MainActivity.kt b/app/src/main/java/eu/steffo/twom/activities/MainActivity.kt index d37a353..5a40228 100644 --- a/app/src/main/java/eu/steffo/twom/activities/MainActivity.kt +++ b/app/src/main/java/eu/steffo/twom/activities/MainActivity.kt @@ -100,8 +100,12 @@ class MainActivity : ComponentActivity() { Log.d("Main", "Done logging out!") }, - processCreate = { name, description, avatarUri -> + processCreate = { lifecycleScope.launch { + val name = it.name + val description = it.description + val avatarUri = it.avatarUri + val currentSession = session val createRoomParams = CreateRoomParams() diff --git a/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarPicker.kt b/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarPicker.kt index 7a1545a..51be5dd 100644 --- a/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarPicker.kt +++ b/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarPicker.kt @@ -4,32 +4,29 @@ import android.graphics.Bitmap import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.clickable +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import eu.steffo.twom.utils.BitmapUtilities +@OptIn(ExperimentalFoundationApi::class) @Composable @Preview(widthDp = 40, heightDp = 40) fun AvatarPicker( modifier: Modifier = Modifier, fallbackText: String = "?", - onPick: (bitmap: Bitmap) -> Unit = {}, + value: Bitmap? = null, + onPick: (bitmap: Bitmap?) -> Unit = {}, alpha: Float = 1.0f, ) { val context = LocalContext.current val resolver = context.contentResolver - var selection by remember { mutableStateOf(null) } - val launcher = rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) ImageSelect@{ it ?: return@ImageSelect @@ -39,18 +36,18 @@ fun AvatarPicker( val correctedBitmap = BitmapUtilities.squareAndOrient(rawBitmap, orientation) - selection = correctedBitmap onPick(correctedBitmap) } Box( modifier = modifier - .clickable { - launcher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) - } + .combinedClickable( + onClick = { launcher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) }, + onLongClick = { onPick(null) }, + ) ) { AvatarImage( - bitmap = selection?.asImageBitmap(), + bitmap = value?.asImageBitmap(), fallbackText = fallbackText, alpha = alpha, ) diff --git a/app/src/main/java/eu/steffo/twom/composables/configureroom/ConfigureRoomForm.kt b/app/src/main/java/eu/steffo/twom/composables/configureroom/ConfigureRoomForm.kt index 219fede..8264973 100644 --- a/app/src/main/java/eu/steffo/twom/composables/configureroom/ConfigureRoomForm.kt +++ b/app/src/main/java/eu/steffo/twom/composables/configureroom/ConfigureRoomForm.kt @@ -1,6 +1,9 @@ package eu.steffo.twom.composables.configureroom +import android.app.Activity +import android.graphics.Bitmap import android.net.Uri +import androidx.activity.ComponentActivity import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth @@ -14,16 +17,19 @@ 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.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription 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.activities.ConfigureRoomActivity import eu.steffo.twom.composables.avatar.AvatarPicker import eu.steffo.twom.composables.theme.basePadding import eu.steffo.twom.utils.BitmapUtilities @@ -32,11 +38,16 @@ import eu.steffo.twom.utils.BitmapUtilities @Preview(showBackground = true) fun ConfigureRoomForm( modifier: Modifier = Modifier, - onSubmit: (name: String, description: String, avatarUri: String?) -> Unit = { _, _, _ -> }, + initialConfiguration: ConfigureRoomActivity.Configuration? = null, + onSubmit: (ConfigureRoomActivity.Configuration) -> Unit = {}, ) { - var name by rememberSaveable { mutableStateOf("") } - var description by rememberSaveable { mutableStateOf("") } - var avatarUri by rememberSaveable { mutableStateOf(null) } + var name by rememberSaveable { mutableStateOf(initialConfiguration?.name ?: "") } + var description by rememberSaveable { mutableStateOf(initialConfiguration?.description ?: "") } + // TODO: How to load the original avatar from the URL? + var avatarBitmap by remember { mutableStateOf(null) } + + val context = LocalContext.current + val activity = context as Activity Column(modifier) { Row(Modifier.basePadding()) { @@ -48,10 +59,8 @@ fun ConfigureRoomForm( .semantics { this.contentDescription = avatarContentDescription }, - onPick = { - val cache = BitmapUtilities.bitmapToCache("createAvatar", it) - avatarUri = Uri.fromFile(cache) - }, + value = avatarBitmap, + onPick = { avatarBitmap = it }, ) TextField( modifier = Modifier @@ -85,7 +94,19 @@ fun ConfigureRoomForm( modifier = Modifier .fillMaxWidth(), onClick = { - onSubmit(name, description, avatarUri.toString()) + val result = ConfigureRoomActivity.Configuration( + name = name, + description = description, + avatarUri = if (avatarBitmap != null) { + Uri.fromFile( + BitmapUtilities.bitmapToCache("createAvatar", avatarBitmap!!) + ) + } else { + null + }, + ) + activity.setResult(ComponentActivity.RESULT_OK, result.toIntent()) + activity.finish() }, ) { Text(stringResource(R.string.create_complete_text)) diff --git a/app/src/main/java/eu/steffo/twom/composables/configureroom/ConfigureRoomScaffold.kt b/app/src/main/java/eu/steffo/twom/composables/configureroom/ConfigureRoomScaffold.kt index 50eb6b4..064f418 100644 --- a/app/src/main/java/eu/steffo/twom/composables/configureroom/ConfigureRoomScaffold.kt +++ b/app/src/main/java/eu/steffo/twom/composables/configureroom/ConfigureRoomScaffold.kt @@ -1,46 +1,29 @@ package eu.steffo.twom.composables.configureroom -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.activities.ConfigureRoomActivity import eu.steffo.twom.composables.theme.TwoMTheme @Composable @Preview -fun ConfigureRoomScaffold() { - val context = LocalContext.current - val activity = context as Activity - - fun submitActivity(name: String, description: String, avatarUri: String?) { - val resultIntent = Intent() - resultIntent.putExtra(ConfigureRoomActivity.NAME_EXTRA, name) - resultIntent.putExtra(ConfigureRoomActivity.DESCRIPTION_EXTRA, description) - // Kotlin cannot use nullable types in Java interop generics - if (avatarUri != null) { - resultIntent.putExtra(ConfigureRoomActivity.AVATAR_EXTRA, avatarUri) - } - activity.setResult(ComponentActivity.RESULT_OK, resultIntent) - activity.finish() - } - +fun ConfigureRoomScaffold( + initialConfiguration: ConfigureRoomActivity.Configuration? = null, +) { TwoMTheme { Scaffold( topBar = { - ConfigureActivityTopBar() + ConfigureActivityTopBar( + initialName = initialConfiguration?.name, + ) }, content = { ConfigureRoomForm( modifier = Modifier.padding(it), - onSubmit = { name: String, description: String, avatarUri: String? -> - submitActivity(name, description, avatarUri) - } + initialConfiguration = initialConfiguration, ) } ) diff --git a/app/src/main/java/eu/steffo/twom/composables/configureroom/ConfigureRoomTopBar.kt b/app/src/main/java/eu/steffo/twom/composables/configureroom/ConfigureRoomTopBar.kt index ee01c93..46eac96 100644 --- a/app/src/main/java/eu/steffo/twom/composables/configureroom/ConfigureRoomTopBar.kt +++ b/app/src/main/java/eu/steffo/twom/composables/configureroom/ConfigureRoomTopBar.kt @@ -5,7 +5,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import eu.steffo.twom.R import eu.steffo.twom.composables.navigation.BackIconButton @@ -15,10 +15,19 @@ import eu.steffo.twom.composables.navigation.BackIconButton @Preview fun ConfigureActivityTopBar( modifier: Modifier = Modifier, + initialName: String? = null, ) { TopAppBar( modifier = modifier, navigationIcon = { BackIconButton() }, - title = { Text(LocalContext.current.getString(R.string.create_title)) } + title = { + Text( + text = if (initialName == null) { + stringResource(R.string.create_title) + } else { + stringResource(R.string.edit_title, initialName) + } + ) + } ) } \ No newline at end of file diff --git a/app/src/main/java/eu/steffo/twom/composables/main/CreateRoomFAB.kt b/app/src/main/java/eu/steffo/twom/composables/main/CreateRoomFAB.kt index ca7a7bd..6a00579 100644 --- a/app/src/main/java/eu/steffo/twom/composables/main/CreateRoomFAB.kt +++ b/app/src/main/java/eu/steffo/twom/composables/main/CreateRoomFAB.kt @@ -1,6 +1,5 @@ package eu.steffo.twom.composables.main -import android.net.Uri import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.launch import androidx.compose.material.icons.Icons @@ -19,12 +18,12 @@ import eu.steffo.twom.activities.ConfigureRoomActivity @Preview fun CreateRoomFAB( modifier: Modifier = Modifier, - onCreateParamsSelected: (name: String, description: String, avatarUri: Uri?) -> Unit = { _, _, _ -> }, + onCreateConfigured: (configuration: ConfigureRoomActivity.Configuration) -> Unit = {}, ) { val launcher = - rememberLauncherForActivityResult(ConfigureRoomActivity.Contract()) { + rememberLauncherForActivityResult(ConfigureRoomActivity.CreateContract()) { if (it != null) { - onCreateParamsSelected(it.name, it.description, it.avatarUri) + onCreateConfigured(it) } } diff --git a/app/src/main/java/eu/steffo/twom/composables/main/MainScaffold.kt b/app/src/main/java/eu/steffo/twom/composables/main/MainScaffold.kt index 0952867..6aecb04 100644 --- a/app/src/main/java/eu/steffo/twom/composables/main/MainScaffold.kt +++ b/app/src/main/java/eu/steffo/twom/composables/main/MainScaffold.kt @@ -1,12 +1,12 @@ package eu.steffo.twom.composables.main -import android.net.Uri import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview +import eu.steffo.twom.activities.ConfigureRoomActivity import eu.steffo.twom.composables.matrix.LocalSession import eu.steffo.twom.composables.theme.TwoMTheme import org.matrix.android.sdk.api.session.Session @@ -16,7 +16,7 @@ import org.matrix.android.sdk.api.session.Session fun MainScaffold( processLogin: () -> Unit = {}, processLogout: () -> Unit = {}, - processCreate: (name: String, description: String, avatarUri: Uri?) -> Unit = { _, _, _ -> }, + processCreate: (it: ConfigureRoomActivity.Configuration) -> Unit = {}, session: Session? = null, ) { TwoMTheme { @@ -31,7 +31,7 @@ fun MainScaffold( floatingActionButton = { if (session != null) { CreateRoomFAB( - onCreateParamsSelected = processCreate, + onCreateConfigured = processCreate, ) } }, diff --git a/app/src/main/java/eu/steffo/twom/composables/viewroom/RoomIconButton.kt b/app/src/main/java/eu/steffo/twom/composables/viewroom/RoomIconButton.kt index 0fd21ce..ff79bc6 100644 --- a/app/src/main/java/eu/steffo/twom/composables/viewroom/RoomIconButton.kt +++ b/app/src/main/java/eu/steffo/twom/composables/viewroom/RoomIconButton.kt @@ -1,6 +1,10 @@ package eu.steffo.twom.composables.viewroom +import android.net.Uri +import android.util.Log +import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.foundation.layout.Box +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.IconButton @@ -9,27 +13,115 @@ 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.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.core.net.toFile import eu.steffo.twom.R +import eu.steffo.twom.activities.ConfigureRoomActivity import eu.steffo.twom.composables.avatar.AvatarURL +import eu.steffo.twom.composables.errorhandling.ErrorIconButton +import eu.steffo.twom.composables.matrix.LocalSession +import kotlinx.coroutines.launch +import kotlin.jvm.optionals.getOrNull @Composable fun RoomIconButton( modifier: Modifier = Modifier, - avatarUrl: String? = null, canEdit: Boolean = true, ) { var expanded by remember { mutableStateOf(false) } + val session = LocalSession.current + if (session == null) { + ErrorIconButton( + message = stringResource(R.string.error_session_missing) + ) + return + } + + val roomSummaryRequest = LocalRoomSummary.current + if (roomSummaryRequest == null) { + CircularProgressIndicator() + return + } + + val roomSummary = roomSummaryRequest.getOrNull() + if (roomSummary == null) { + ErrorIconButton( + message = stringResource(R.string.room_error_roomsummary_notfound) + ) + return + } + + val roomRequest = LocalRoom.current + if (roomRequest == null) { + CircularProgressIndicator() + return + } + + val room = roomRequest.getOrNull() + if (room == null) { + ErrorIconButton( + message = stringResource(R.string.room_error_room_notfound) + ) + return + } + + val scope = rememberCoroutineScope() + + val launcher = + rememberLauncherForActivityResult( + ConfigureRoomActivity.EditContract() + ) { + if (it != null) { + scope.launch { + + val name = it.name + Log.d("RoomIconButton", "Updating room name to `$name`") + room.stateService().updateName(it.name) + + val description = it.description + Log.d("RoomIconButton", "Updating room description to `$description`") + room.stateService().updateTopic(it.description) + + val avatarUri = it.avatarUri + val avatarFile = avatarUri?.toFile() + when (avatarFile?.isFile) { + false -> { + Log.e( + "RoomIconButton", + "Avatar has been deleted from cache before room could possibly be updated, ignoring..." + ) + } + + true -> { + Log.d( + "RoomIconButton", + "Avatar seems to exist at: $avatarUri" + ) + room.stateService().updateAvatar(avatarUri, avatarFile.name) + } + + null -> { + Log.d( + "RoomIconButton", + "Avatar was not set, ignoring..." + ) + } + } + } + } + } + Box(modifier) { IconButton( onClick = { expanded = true }, ) { AvatarURL( - url = avatarUrl, + url = roomSummary.avatarUrl, contentDescription = LocalContext.current.getString(R.string.room_options_label), ) } @@ -37,28 +129,23 @@ fun RoomIconButton( expanded = expanded, onDismissRequest = { expanded = false }, ) { - DropdownMenuItem( - text = { - Text(stringResource(R.string.room_options_zoom_text)) - }, - onClick = { - // TODO - expanded = false - } - ) - if (canEdit) { DropdownMenuItem( text = { Text(stringResource(R.string.room_options_edit_text)) }, onClick = { - // TODO expanded = false + launcher.launch( + ConfigureRoomActivity.Configuration( + name = roomSummary.name, + description = roomSummary.topic, + avatarUri = Uri.parse(roomSummary.avatarUrl), + ) + ) } ) } } } - -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/steffo/twom/composables/viewroom/ViewRoomTopBar.kt b/app/src/main/java/eu/steffo/twom/composables/viewroom/ViewRoomTopBar.kt index 0fa7ed0..99aef88 100644 --- a/app/src/main/java/eu/steffo/twom/composables/viewroom/ViewRoomTopBar.kt +++ b/app/src/main/java/eu/steffo/twom/composables/viewroom/ViewRoomTopBar.kt @@ -1,16 +1,12 @@ package eu.steffo.twom.composables.viewroom -import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar 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 -import eu.steffo.twom.composables.errorhandling.ErrorIconButton import eu.steffo.twom.composables.errorhandling.ErrorText import eu.steffo.twom.composables.errorhandling.LoadingText import eu.steffo.twom.composables.navigation.BackIconButton @@ -47,17 +43,7 @@ fun ViewRoomTopBar( } }, actions = { - if (roomSummaryRequest == null) { - CircularProgressIndicator() - } else if (roomSummary == null) { - ErrorIconButton( - message = stringResource(R.string.room_error_roomsummary_notfound) - ) - } else { - RoomIconButton( - avatarUrl = roomSummary.avatarUrl, - ) - } + RoomIconButton() }, ) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0f1ad54..5881d5a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -84,4 +84,5 @@ Your response has been updated, but something went wrong while attempting to remove your previous one: %1$s You have been removed from the room. Something went wrong while sending the invite: %1$s + Editing %1$s \ No newline at end of file