Merge pull request #418 from vector-im/feature/fre/create_room_layout_improvements

Create Room - Layouts cleanup
This commit is contained in:
Florian Renaud 2023-05-17 14:38:18 +02:00 committed by GitHub
commit ef684e63fd
20 changed files with 100 additions and 78 deletions

View file

@ -27,5 +27,5 @@ data class CreateRoomConfig(
val topic: String? = null, val topic: String? = null,
val avatarUri: Uri? = null, val avatarUri: Uri? = null,
val invites: ImmutableList<MatrixUser> = persistentListOf(), val invites: ImmutableList<MatrixUser> = persistentListOf(),
val privacy: RoomPrivacy? = null, val privacy: RoomPrivacy = RoomPrivacy.Private,
) )

View file

@ -60,7 +60,7 @@ class CreateRoomDataStore @Inject constructor(
createRoomConfigFlow.tryEmit(createRoomConfigFlow.value.copy(avatarUri = uri)) createRoomConfigFlow.tryEmit(createRoomConfigFlow.value.copy(avatarUri = uri))
} }
fun setPrivacy(privacy: RoomPrivacy?) { fun setPrivacy(privacy: RoomPrivacy) {
createRoomConfigFlow.tryEmit(createRoomConfigFlow.value.copy(privacy = privacy)) createRoomConfigFlow.tryEmit(createRoomConfigFlow.value.copy(privacy = privacy))
} }

View file

@ -54,6 +54,7 @@ fun LabelledTextField(
value = value, value = value,
placeholder = { Text(placeholder) }, placeholder = { Text(placeholder) },
onValueChange = onValueChange, onValueChange = onValueChange,
singleLine = maxLines == 1,
maxLines = maxLines, maxLines = maxLines,
) )
} }

View file

@ -23,7 +23,7 @@ import io.element.android.libraries.matrix.api.user.MatrixUser
sealed interface ConfigureRoomEvents { sealed interface ConfigureRoomEvents {
data class RoomNameChanged(val name: String) : ConfigureRoomEvents data class RoomNameChanged(val name: String) : ConfigureRoomEvents
data class TopicChanged(val topic: String) : ConfigureRoomEvents data class TopicChanged(val topic: String) : ConfigureRoomEvents
data class RoomPrivacyChanged(val privacy: RoomPrivacy?) : ConfigureRoomEvents data class RoomPrivacyChanged(val privacy: RoomPrivacy) : ConfigureRoomEvents
data class RemoveFromSelection(val matrixUser: MatrixUser) : ConfigureRoomEvents data class RemoveFromSelection(val matrixUser: MatrixUser) : ConfigureRoomEvents
data class CreateRoom(val config: CreateRoomConfig) : ConfigureRoomEvents data class CreateRoom(val config: CreateRoomConfig) : ConfigureRoomEvents
data class HandleAvatarAction(val action: AvatarAction) : ConfigureRoomEvents data class HandleAvatarAction(val action: AvatarAction) : ConfigureRoomEvents

View file

@ -56,11 +56,6 @@ class ConfigureRoomPresenter @Inject constructor(
@Composable @Composable
override fun present(): ConfigureRoomState { override fun present(): ConfigureRoomState {
val createRoomConfig = dataStore.getCreateRoomConfig().collectAsState(CreateRoomConfig()) val createRoomConfig = dataStore.getCreateRoomConfig().collectAsState(CreateRoomConfig())
val isCreateButtonEnabled by remember(createRoomConfig.value.roomName, createRoomConfig.value.privacy) {
derivedStateOf {
createRoomConfig.value.roomName.isNullOrEmpty().not() && createRoomConfig.value.privacy != null
}
}
val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker( val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker(
onResult = { uri -> if (uri != null) dataStore.setAvatarUri(uri = uri, cached = true) }, onResult = { uri -> if (uri != null) dataStore.setAvatarUri(uri = uri, cached = true) },
@ -108,7 +103,6 @@ class ConfigureRoomPresenter @Inject constructor(
return ConfigureRoomState( return ConfigureRoomState(
config = createRoomConfig.value, config = createRoomConfig.value,
isCreateButtonEnabled = isCreateButtonEnabled,
avatarActions = avatarActions, avatarActions = avatarActions,
createRoomAction = createRoomAction.value, createRoomAction = createRoomAction.value,
eventSink = ::handleEvents, eventSink = ::handleEvents,

View file

@ -24,8 +24,9 @@ import kotlinx.collections.immutable.ImmutableList
data class ConfigureRoomState( data class ConfigureRoomState(
val config: CreateRoomConfig, val config: CreateRoomConfig,
val isCreateButtonEnabled: Boolean,
val avatarActions: ImmutableList<AvatarAction>, val avatarActions: ImmutableList<AvatarAction>,
val createRoomAction: Async<RoomId>, val createRoomAction: Async<RoomId>,
val eventSink: (ConfigureRoomEvents) -> Unit val eventSink: (ConfigureRoomEvents) -> Unit
) ) {
val isCreateButtonEnabled: Boolean = config.roomName.isNullOrEmpty().not()
}

View file

@ -31,16 +31,14 @@ open class ConfigureRoomStateProvider : PreviewParameterProvider<ConfigureRoomSt
roomName = "Room 101", roomName = "Room 101",
topic = "Room topic for this room when the text goes onto multiple lines and is really long, there shouldnt be more than 3 lines", topic = "Room topic for this room when the text goes onto multiple lines and is really long, there shouldnt be more than 3 lines",
invites = aListOfSelectedUsers(), invites = aListOfSelectedUsers(),
privacy = RoomPrivacy.Private, privacy = RoomPrivacy.Public,
), ),
isCreateButtonEnabled = true,
), ),
) )
} }
fun aConfigureRoomState() = ConfigureRoomState( fun aConfigureRoomState() = ConfigureRoomState(
config = CreateRoomConfig(), config = CreateRoomConfig(),
isCreateButtonEnabled = false,
avatarActions = persistentListOf(), avatarActions = persistentListOf(),
createRoomAction = Async.Uninitialized, createRoomAction = Async.Uninitialized,
eventSink = { }, eventSink = { },

View file

@ -17,12 +17,13 @@
package io.element.android.features.createroom.impl.configureroom package io.element.android.features.createroom.impl.configureroom
import android.net.Uri import android.net.Uri
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.selection.selectableGroup import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ModalBottomSheetValue import androidx.compose.material.ModalBottomSheetValue
@ -33,6 +34,9 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@ -68,6 +72,7 @@ fun ConfigureRoomView(
onRoomCreated: (RoomId) -> Unit = {}, onRoomCreated: (RoomId) -> Unit = {},
) { ) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val focusManager = LocalFocusManager.current
val itemActionsBottomSheetState = rememberModalBottomSheetState( val itemActionsBottomSheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden, initialValue = ModalBottomSheetValue.Hidden,
) )
@ -79,50 +84,67 @@ fun ConfigureRoomView(
} }
fun onAvatarClicked() { fun onAvatarClicked() {
focusManager.clearFocus()
coroutineScope.launch { coroutineScope.launch {
itemActionsBottomSheetState.show() itemActionsBottomSheetState.show()
} }
} }
Scaffold( Scaffold(
modifier = modifier, modifier = modifier.clearFocusOnTap(focusManager),
topBar = { topBar = {
ConfigureRoomToolbar( ConfigureRoomToolbar(
isNextActionEnabled = state.isCreateButtonEnabled, isNextActionEnabled = state.isCreateButtonEnabled,
onBackPressed = onBackPressed, onBackPressed = onBackPressed,
onNextPressed = { onNextPressed = {
focusManager.clearFocus()
state.eventSink(ConfigureRoomEvents.CreateRoom(state.config)) state.eventSink(ConfigureRoomEvents.CreateRoom(state.config))
}, },
) )
} }
) { padding -> ) { padding ->
Column( LazyColumn(
modifier = Modifier.padding(padding), modifier = Modifier.padding(padding),
verticalArrangement = Arrangement.spacedBy(24.dp), verticalArrangement = Arrangement.spacedBy(24.dp),
) { ) {
RoomNameWithAvatar( item {
modifier = Modifier.padding(horizontal = 16.dp), RoomNameWithAvatar(
avatarUri = state.config.avatarUri, modifier = Modifier.padding(horizontal = 16.dp),
roomName = state.config.roomName.orEmpty(), avatarUri = state.config.avatarUri,
onAvatarClick = ::onAvatarClicked, roomName = state.config.roomName.orEmpty(),
onRoomNameChanged = { state.eventSink(ConfigureRoomEvents.RoomNameChanged(it)) }, onAvatarClick = ::onAvatarClicked,
) onRoomNameChanged = { state.eventSink(ConfigureRoomEvents.RoomNameChanged(it)) },
RoomTopic( )
modifier = Modifier.padding(horizontal = 16.dp), }
topic = state.config.topic.orEmpty(), item {
onTopicChanged = { state.eventSink(ConfigureRoomEvents.TopicChanged(it)) }, RoomTopic(
) modifier = Modifier.padding(horizontal = 16.dp),
SelectedUsersList( topic = state.config.topic.orEmpty(),
contentPadding = PaddingValues(horizontal = 24.dp), onTopicChanged = { state.eventSink(ConfigureRoomEvents.TopicChanged(it)) },
selectedUsers = state.config.invites, )
onUserRemoved = { state.eventSink(ConfigureRoomEvents.RemoveFromSelection(it)) }, }
) if (state.config.invites.isNotEmpty()) {
Spacer(Modifier.weight(1f)) item {
RoomPrivacyOptions( SelectedUsersList(
modifier = Modifier.padding(bottom = 40.dp), contentPadding = PaddingValues(horizontal = 24.dp),
selected = state.config.privacy, selectedUsers = state.config.invites,
onOptionSelected = { state.eventSink(ConfigureRoomEvents.RoomPrivacyChanged(it.privacy)) }, onUserRemoved = {
) focusManager.clearFocus()
state.eventSink(ConfigureRoomEvents.RemoveFromSelection(it))
},
)
}
}
item {
RoomPrivacyOptions(
modifier = Modifier.padding(bottom = 40.dp),
selected = state.config.privacy,
onOptionSelected = {
focusManager.clearFocus()
state.eventSink(ConfigureRoomEvents.RoomPrivacyChanged(it.privacy))
},
)
}
} }
} }
@ -259,3 +281,11 @@ private fun ContentToPreview(state: ConfigureRoomState) {
state = state, state = state,
) )
} }
private fun Modifier.clearFocusOnTap(focusManager: FocusManager): Modifier =
pointerInput(Unit) {
detectTapGestures(onTap = {
focusManager.clearFocus()
})
}

View file

@ -101,7 +101,7 @@ fun CreateRoomRootView(
when (state.startDmAction) { when (state.startDmAction) {
is Async.Loading -> { is Async.Loading -> {
ProgressDialog(text = stringResource(id = StringR.string.common_creating_room)) ProgressDialog(text = stringResource(id = StringR.string.common_starting_chat))
} }
is Async.Failure -> { is Async.Failure -> {

View file

@ -97,7 +97,7 @@ class ConfigureRoomPresenterTests {
assertThat(initialState.config.topic).isNull() assertThat(initialState.config.topic).isNull()
assertThat(initialState.config.invites).isEmpty() assertThat(initialState.config.invites).isEmpty()
assertThat(initialState.config.avatarUri).isNull() assertThat(initialState.config.avatarUri).isNull()
assertThat(initialState.config.privacy).isNull() assertThat(initialState.config.privacy).isEqualTo(RoomPrivacy.Private)
} }
} }
@ -115,13 +115,6 @@ class ConfigureRoomPresenterTests {
var newState: ConfigureRoomState = awaitItem() var newState: ConfigureRoomState = awaitItem()
config = config.copy(roomName = A_ROOM_NAME) config = config.copy(roomName = A_ROOM_NAME)
assertThat(newState.config).isEqualTo(config) assertThat(newState.config).isEqualTo(config)
assertThat(newState.isCreateButtonEnabled).isFalse()
// Select privacy
newState.eventSink(ConfigureRoomEvents.RoomPrivacyChanged(RoomPrivacy.Private))
newState = awaitItem()
config = config.copy(privacy = RoomPrivacy.Private)
assertThat(newState.config).isEqualTo(config)
assertThat(newState.isCreateButtonEnabled).isTrue() assertThat(newState.isCreateButtonEnabled).isTrue()
// Clear room name // Clear room name

View file

@ -1,19 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="screen_room_member_list_header_title"> <plurals name="screen_room_member_list_header_title">
<item quantity="one">"1 person"</item> <item quantity="one">"1 person"</item>
<item quantity="other">"%1$d people"</item> <item quantity="other">"%1$d people"</item>
</plurals> </plurals>
<string name="screen_room_details_encryption_enabled_subtitle">"Messages are secured with locks. Only you and the recipients have the unique keys to unlock them."</string> <string name="screen_room_details_already_a_member">"Already a member"</string>
<string name="screen_room_details_encryption_enabled_title">"Message encryption enabled"</string> <string name="screen_room_details_already_invited">"Already invited"</string>
<string name="screen_room_details_share_room_title">"Share room"</string> <string name="screen_room_details_encryption_enabled_subtitle">"Messages are secured with locks. Only you and the recipients have the unique keys to unlock them."</string>
<string name="screen_room_member_list_pending_header_title">"Pending"</string> <string name="screen_room_details_encryption_enabled_title">"Message encryption enabled"</string>
<string name="screen_dm_details_block_alert_action">"Block"</string> <string name="screen_room_details_share_room_title">"Share room"</string>
<string name="screen_dm_details_block_alert_description">"Blocked users will not be able to send you messages and all message by them will be hidden. You can reverse this action anytime."</string> <string name="screen_room_member_list_pending_header_title">"Pending"</string>
<string name="screen_dm_details_block_user">"Block user"</string> <string name="screen_dm_details_block_alert_action">"Block"</string>
<string name="screen_dm_details_unblock_alert_action">"Unblock"</string> <string name="screen_dm_details_block_alert_description">"Blocked users will not be able to send you messages and all message by them will be hidden. You can reverse this action anytime."</string>
<string name="screen_dm_details_unblock_alert_description">"On unblocking the user, you will be able to see all messages by them again."</string> <string name="screen_dm_details_block_user">"Block user"</string>
<string name="screen_dm_details_unblock_user">"Unblock user"</string> <string name="screen_dm_details_unblock_alert_action">"Unblock"</string>
<string name="screen_dm_details_unblock_alert_description">"On unblocking the user, you will be able to see all messages by them again."</string>
<string name="screen_dm_details_unblock_user">"Unblock user"</string>
<string name="screen_room_details_invite_people_title">"Invite people"</string> <string name="screen_room_details_invite_people_title">"Invite people"</string>
<string name="screen_room_details_leave_room_title">"Leave room"</string> <string name="screen_room_details_leave_room_title">"Leave room"</string>
<string name="screen_room_details_people_title">"People"</string> <string name="screen_room_details_people_title">"People"</string>

View file

@ -73,7 +73,7 @@ fun SearchBar(
@Preview(group = PreviewGroup.Search) @Preview(group = PreviewGroup.Search)
@Composable @Composable
internal fun DockedSearchBarPreview() = ElementThemedPreview { ContentToPreview() } internal fun SearchBarPreview() = ElementThemedPreview { ContentToPreview() }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable

View file

@ -63,6 +63,7 @@
<string name="common_developer_options">"Developer options"</string> <string name="common_developer_options">"Developer options"</string>
<string name="common_edited_suffix">"(edited)"</string> <string name="common_edited_suffix">"(edited)"</string>
<string name="common_editing">"Editing"</string> <string name="common_editing">"Editing"</string>
<string name="common_emote">"* %1$s %2$s"</string>
<string name="common_encryption_enabled">"Encryption enabled"</string> <string name="common_encryption_enabled">"Encryption enabled"</string>
<string name="common_error">"Error"</string> <string name="common_error">"Error"</string>
<string name="common_file">"File"</string> <string name="common_file">"File"</string>
@ -84,12 +85,14 @@
<string name="common_report_a_bug">"Report a bug"</string> <string name="common_report_a_bug">"Report a bug"</string>
<string name="common_report_submitted">"Report submitted"</string> <string name="common_report_submitted">"Report submitted"</string>
<string name="common_search_for_someone">"Search for someone"</string> <string name="common_search_for_someone">"Search for someone"</string>
<string name="common_search_results">"Search results"</string>
<string name="common_security">"Security"</string> <string name="common_security">"Security"</string>
<string name="common_select_your_server">"Select your server"</string> <string name="common_select_your_server">"Select your server"</string>
<string name="common_sending">"Sending…"</string> <string name="common_sending">"Sending…"</string>
<string name="common_server_not_supported">"Server not supported"</string> <string name="common_server_not_supported">"Server not supported"</string>
<string name="common_server_url">"Server URL"</string> <string name="common_server_url">"Server URL"</string>
<string name="common_settings">"Settings"</string> <string name="common_settings">"Settings"</string>
<string name="common_starting_chat">"Starting chat…"</string>
<string name="common_sticker">"Sticker"</string> <string name="common_sticker">"Sticker"</string>
<string name="common_success">"Success"</string> <string name="common_success">"Success"</string>
<string name="common_suggestions">"Suggestions"</string> <string name="common_suggestions">"Suggestions"</string>

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:24ad0b5edc27ddddca573b37f82579715d880793c0dc880248d2b572dd6d6a40 oid sha256:a9da3afa82783ded1e686a36eb969b8e097de853e9c6dadea24884f7975c0720
size 64379 size 63729

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:b0c9d4eaaaf6a691f43d25e0855ee056b69fa090ad8c53b26732c8f2532e2138 oid sha256:e2c200874d6fa8f4fe07da69a7879eb264fb8941c1ab9654162cb76586c52936
size 103384 size 103326

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:4023481d2a045d7e0d824db81b28ee47c4bcdc568d98537b3573d554e0b76e32 oid sha256:e48bb979a1d328871157847862723b8342cd8c2704750045bbf24592e143a5e0
size 58445 size 57874

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:8f953d0df467ad4cd1ae24291e9593945b945712506f093bdcae3da975f8ca91 oid sha256:4320fd1900a120a737ba2a56e6c1f55cf671eb0e8c1791dc605994d333feb0c2
size 96800 size 96888

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:8f29da5d5aeb65659b065b7bd6afe276f83e020545a027780d2391308d1a4076 oid sha256:56c23bd0880524cc56b713c93c5358e9f3771291c20f7a149bad00465f3f987a
size 20750 size 20648

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:ae3e8c4e952b97628d026dfe78781aef894d6c2e742ac6ae1f1a2c0170df159e oid sha256:f12591531cfdce24378003dda48940c20d8c9cf7bfac73ac1e7713e925bfac4d
size 20382 size 20214