Merge branch 'develop' of https://github.com/vector-im/element-x-android into dla/feature/custom_room_notification_settings_list

This commit is contained in:
David Langley 2023-10-18 22:07:14 +01:00
commit af4a1d3fd6
1981 changed files with 15504 additions and 5063 deletions

View file

@ -17,7 +17,7 @@
package io.element.android.features.roomdetails.impl
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.leaveroom.api.LeaveRoomState
import io.element.android.features.leaveroom.api.aLeaveRoomState
import io.element.android.features.roomdetails.impl.members.details.aRoomMemberDetailsState
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.RoomMember
@ -80,7 +80,7 @@ fun aRoomDetailsState() = RoomDetailsState(
canShowNotificationSettings = true,
roomType = RoomDetailsType.Room,
roomMemberDetailsState = null,
leaveRoomState = LeaveRoomState(),
leaveRoomState = aLeaveRoomState(),
roomNotificationSettings = RoomNotificationSettings(mode = RoomNotificationMode.MUTE, isDefault = false),
eventSink = {}
)

View file

@ -33,12 +33,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.outlined.Add
import androidx.compose.material.icons.outlined.Lock
import androidx.compose.material.icons.outlined.Notifications
import androidx.compose.material.icons.outlined.NotificationsOff
import androidx.compose.material.icons.outlined.Person
import androidx.compose.material.icons.outlined.PersonAddAlt
import androidx.compose.material.icons.outlined.Share
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
@ -48,9 +43,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
@ -69,7 +62,7 @@ import io.element.android.libraries.designsystem.components.preferences.Preferen
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.LargeHeightPreview
import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight
import io.element.android.libraries.designsystem.theme.components.DropdownMenu
import io.element.android.libraries.designsystem.theme.components.DropdownMenuItem
import io.element.android.libraries.designsystem.theme.components.Icon
@ -77,6 +70,7 @@ import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.designsystem.utils.CommonDrawables
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.theme.ElementTheme
@ -153,7 +147,8 @@ fun RoomDetailsView(
if (state.canShowNotificationSettings && state.roomNotificationSettings != null) {
NotificationSection(
isDefaultMode = state.roomNotificationSettings.isDefault,
openRoomNotificationSettings = openRoomNotificationSettings)
openRoomNotificationSettings = openRoomNotificationSettings
)
}
if (state.roomType is RoomDetailsType.Room) {
@ -188,7 +183,7 @@ fun RoomDetailsView(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun RoomDetailsTopBar(
private fun RoomDetailsTopBar(
goBack: () -> Unit,
onActionClicked: (RoomDetailsAction) -> Unit,
showEdit: Boolean,
@ -225,27 +220,39 @@ internal fun RoomDetailsTopBar(
}
@Composable
internal fun MainActionsSection(state: RoomDetailsState, onShareRoom: () -> Unit, modifier: Modifier = Modifier) {
private fun MainActionsSection(state: RoomDetailsState, onShareRoom: () -> Unit, modifier: Modifier = Modifier) {
Row(modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
val roomNotificationSettings = state.roomNotificationSettings
if (state.canShowNotificationSettings && roomNotificationSettings != null) {
if (roomNotificationSettings.mode == RoomNotificationMode.MUTE) {
MainActionButton(title = stringResource(CommonStrings.common_unmute), icon = Icons.Outlined.NotificationsOff, onClick = {
state.eventSink(RoomDetailsEvent.UnmuteNotification)
})
MainActionButton(
title = stringResource(CommonStrings.common_unmute),
iconResourceId = CommonDrawables.ic_compound_notifications_off,
onClick = {
state.eventSink(RoomDetailsEvent.UnmuteNotification)
},
)
} else {
MainActionButton(title = stringResource(CommonStrings.common_mute), icon = Icons.Outlined.Notifications, onClick = {
state.eventSink(RoomDetailsEvent.MuteNotification)
})
MainActionButton(
title = stringResource(CommonStrings.common_mute),
iconResourceId = CommonDrawables.ic_compound_notifications,
onClick = {
state.eventSink(RoomDetailsEvent.MuteNotification)
},
)
}
}
Spacer(modifier = Modifier.width(20.dp))
MainActionButton(title = stringResource(R.string.screen_room_details_share_room_title), icon = Icons.Outlined.Share, onClick = onShareRoom)
MainActionButton(
title = stringResource(R.string.screen_room_details_share_room_title),
iconResourceId = CommonDrawables.ic_compound_share_android,
onClick = onShareRoom
)
}
}
@Composable
internal fun RoomHeaderSection(
private fun RoomHeaderSection(
avatarUrl: String?,
roomId: String,
roomName: String,
@ -282,7 +289,7 @@ internal fun RoomHeaderSection(
}
@Composable
internal fun TopicSection(
private fun TopicSection(
roomTopic: RoomTopicState,
onActionClicked: (RoomDetailsAction) -> Unit,
modifier: Modifier = Modifier
@ -308,7 +315,7 @@ internal fun TopicSection(
}
@Composable
internal fun NotificationSection(
private fun NotificationSection(
isDefaultMode: Boolean,
openRoomNotificationSettings: () -> Unit,
modifier: Modifier = Modifier
@ -322,21 +329,21 @@ internal fun NotificationSection(
PreferenceText(
title = stringResource(R.string.screen_room_details_notification_title),
subtitle = subtitle,
icon = Icons.Outlined.Notifications,
iconResourceId = CommonDrawables.ic_compound_notifications,
onClick = openRoomNotificationSettings,
)
}
}
@Composable
internal fun MembersSection(
private fun MembersSection(
memberCount: Long,
openRoomMemberList: () -> Unit,
modifier: Modifier = Modifier,
) {
PreferenceCategory(modifier = modifier) {
PreferenceText(
title = stringResource(R.string.screen_room_details_people_title),
title = stringResource(CommonStrings.common_people),
icon = Icons.Outlined.Person,
currentValue = memberCount.toString(),
onClick = openRoomMemberList,
@ -345,48 +352,48 @@ internal fun MembersSection(
}
@Composable
internal fun InviteSection(
private fun InviteSection(
invitePeople: () -> Unit,
modifier: Modifier = Modifier,
) {
PreferenceCategory(modifier = modifier) {
PreferenceText(
title = stringResource(R.string.screen_room_details_invite_people_title),
icon = Icons.Outlined.PersonAddAlt,
iconResourceId = CommonDrawables.ic_compound_user_add,
onClick = invitePeople,
)
}
}
@Composable
internal fun SecuritySection(modifier: Modifier = Modifier) {
private fun SecuritySection(modifier: Modifier = Modifier) {
PreferenceCategory(title = stringResource(R.string.screen_room_details_security_title), modifier = modifier) {
PreferenceText(
title = stringResource(R.string.screen_room_details_encryption_enabled_title),
subtitle = stringResource(R.string.screen_room_details_encryption_enabled_subtitle),
icon = Icons.Outlined.Lock,
iconResourceId = CommonDrawables.ic_compound_lock,
)
}
}
@Composable
internal fun OtherActionsSection(onLeaveRoom: () -> Unit, modifier: Modifier = Modifier) {
private fun OtherActionsSection(onLeaveRoom: () -> Unit, modifier: Modifier = Modifier) {
PreferenceCategory(showDivider = false, modifier = modifier) {
PreferenceText(
title = stringResource(R.string.screen_room_details_leave_room_title),
icon = ImageVector.vectorResource(R.drawable.ic_door_open),
iconResourceId = CommonDrawables.ic_compound_leave,
tintColor = MaterialTheme.colorScheme.error,
onClick = onLeaveRoom,
)
}
}
@LargeHeightPreview
@PreviewWithLargeHeight
@Composable
internal fun RoomDetailsPreview(@PreviewParameter(RoomDetailsStateProvider::class) state: RoomDetailsState) =
ElementPreviewLight { ContentToPreview(state) }
@LargeHeightPreview
@PreviewWithLargeHeight
@Composable
internal fun RoomDetailsDarkPreview(@PreviewParameter(RoomDetailsStateProvider::class) state: RoomDetailsState) =
ElementPreviewDark { ContentToPreview(state) }

View file

@ -0,0 +1,77 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.roomdetails.impl.blockuser
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import io.element.android.features.roomdetails.impl.R
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsEvents
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
@Composable
fun BlockUserDialogs(state: RoomMemberDetailsState) {
when (state.displayConfirmationDialog) {
null -> Unit
RoomMemberDetailsState.ConfirmationDialog.Block -> {
BlockConfirmationDialog(
onBlockAction = {
state.eventSink(
RoomMemberDetailsEvents.BlockUser(
needsConfirmation = false
)
)
},
onDismiss = { state.eventSink(RoomMemberDetailsEvents.ClearConfirmationDialog) }
)
}
RoomMemberDetailsState.ConfirmationDialog.Unblock -> {
UnblockConfirmationDialog(
onUnblockAction = {
state.eventSink(
RoomMemberDetailsEvents.UnblockUser(
needsConfirmation = false
)
)
},
onDismiss = { state.eventSink(RoomMemberDetailsEvents.ClearConfirmationDialog) }
)
}
}
}
@Composable
private fun BlockConfirmationDialog(onBlockAction: () -> Unit, onDismiss: () -> Unit) {
ConfirmationDialog(
title = stringResource(R.string.screen_dm_details_block_user),
content = stringResource(R.string.screen_dm_details_block_alert_description),
submitText = stringResource(R.string.screen_dm_details_block_alert_action),
onSubmitClicked = onBlockAction,
onDismiss = onDismiss
)
}
@Composable
private fun UnblockConfirmationDialog(onUnblockAction: () -> Unit, onDismiss: () -> Unit) {
ConfirmationDialog(
title = stringResource(R.string.screen_dm_details_unblock_user),
content = stringResource(R.string.screen_dm_details_unblock_alert_description),
submitText = stringResource(R.string.screen_dm_details_unblock_alert_action),
onSubmitClicked = onUnblockAction,
onDismiss = onDismiss
)
}

View file

@ -27,7 +27,6 @@ import io.element.android.features.roomdetails.impl.members.details.RoomMemberDe
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
import io.element.android.libraries.designsystem.components.dialogs.RetryDialog
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
@ -85,44 +84,3 @@ private fun PreferenceBlockUser(
)
}
}
@Composable
internal fun BlockUserDialogs(state: RoomMemberDetailsState) {
when (state.displayConfirmationDialog) {
null -> Unit
RoomMemberDetailsState.ConfirmationDialog.Block -> {
BlockConfirmationDialog(
onBlockAction = { state.eventSink(RoomMemberDetailsEvents.BlockUser(needsConfirmation = false)) },
onDismiss = { state.eventSink(RoomMemberDetailsEvents.ClearConfirmationDialog) }
)
}
RoomMemberDetailsState.ConfirmationDialog.Unblock -> {
UnblockConfirmationDialog(
onUnblockAction = { state.eventSink(RoomMemberDetailsEvents.UnblockUser(needsConfirmation = false)) },
onDismiss = { state.eventSink(RoomMemberDetailsEvents.ClearConfirmationDialog) }
)
}
}
}
@Composable
internal fun BlockConfirmationDialog(onBlockAction: () -> Unit, onDismiss: () -> Unit) {
ConfirmationDialog(
title = stringResource(R.string.screen_dm_details_block_user),
content = stringResource(R.string.screen_dm_details_block_alert_description),
submitText = stringResource(R.string.screen_dm_details_block_alert_action),
onSubmitClicked = onBlockAction,
onDismiss = onDismiss
)
}
@Composable
internal fun UnblockConfirmationDialog(onUnblockAction: () -> Unit, onDismiss: () -> Unit) {
ConfirmationDialog(
title = stringResource(R.string.screen_dm_details_unblock_user),
content = stringResource(R.string.screen_dm_details_unblock_alert_description),
submitText = stringResource(R.string.screen_dm_details_unblock_alert_action),
onSubmitClicked = onUnblockAction,
onDismiss = onDismiss
)
}

View file

@ -39,6 +39,8 @@ import io.element.android.libraries.matrix.api.room.powerlevels.canSendState
import io.element.android.libraries.matrix.ui.media.AvatarAction
import io.element.android.libraries.mediapickers.api.PickerProvider
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
import io.element.android.libraries.permissions.api.PermissionsEvents
import io.element.android.libraries.permissions.api.PermissionsPresenter
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@ -49,10 +51,15 @@ class RoomDetailsEditPresenter @Inject constructor(
private val room: MatrixRoom,
private val mediaPickerProvider: PickerProvider,
private val mediaPreProcessor: MediaPreProcessor,
permissionsPresenterFactory: PermissionsPresenter.Factory,
) : Presenter<RoomDetailsEditState> {
private val cameraPermissionPresenter = permissionsPresenterFactory.create(android.Manifest.permission.CAMERA)
private var pendingPermissionRequest = false
@Composable
override fun present(): RoomDetailsEditState {
val cameraPermissionState = cameraPermissionPresenter.present()
val roomSyncUpdateFlow = room.syncUpdateFlow.collectAsState()
// Since there is no way to obtain the new avatar uri after uploading a new avatar,
@ -92,6 +99,13 @@ class RoomDetailsEditPresenter @Inject constructor(
onResult = { uri -> if (uri != null) roomAvatarUri = uri }
)
LaunchedEffect(cameraPermissionState.permissionGranted) {
if (cameraPermissionState.permissionGranted && pendingPermissionRequest) {
pendingPermissionRequest = false
cameraPhotoPicker.launch()
}
}
val avatarActions by remember(roomAvatarUri) {
derivedStateOf {
listOfNotNull(
@ -110,7 +124,12 @@ class RoomDetailsEditPresenter @Inject constructor(
is RoomDetailsEditEvents.HandleAvatarAction -> {
when (event.action) {
AvatarAction.ChoosePhoto -> galleryImagePicker.launch()
AvatarAction.TakePhoto -> cameraPhotoPicker.launch()
AvatarAction.TakePhoto -> if (cameraPermissionState.permissionGranted) {
cameraPhotoPicker.launch()
} else {
pendingPermissionRequest = true
cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions)
}
AvatarAction.Remove -> roomAvatarUri = null
}
}
@ -132,6 +151,7 @@ class RoomDetailsEditPresenter @Inject constructor(
avatarActions = avatarActions,
saveButtonEnabled = saveButtonEnabled,
saveAction = saveAction.value,
cameraPermissionState = cameraPermissionState,
eventSink = ::handleEvents,
)
}

View file

@ -17,8 +17,9 @@
package io.element.android.features.roomdetails.impl.edit
import android.net.Uri
import io.element.android.libraries.matrix.ui.media.AvatarAction
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.ui.media.AvatarAction
import io.element.android.libraries.permissions.api.PermissionsState
import kotlinx.collections.immutable.ImmutableList
data class RoomDetailsEditState(
@ -32,5 +33,6 @@ data class RoomDetailsEditState(
val avatarActions: ImmutableList<AvatarAction>,
val saveButtonEnabled: Boolean,
val saveAction: Async<Unit>,
val cameraPermissionState: PermissionsState,
val eventSink: (RoomDetailsEditEvents) -> Unit
)

View file

@ -19,6 +19,7 @@ package io.element.android.features.roomdetails.impl.edit
import android.net.Uri
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.permissions.api.aPermissionsState
import kotlinx.collections.immutable.persistentListOf
open class RoomDetailsEditStateProvider : PreviewParameterProvider<RoomDetailsEditState> {
@ -45,5 +46,6 @@ fun aRoomDetailsEditState() = RoomDetailsEditState(
avatarActions = persistentListOf(),
saveButtonEnabled = true,
saveAction = Async.Uninitialized,
cameraPermissionState = aPermissionsState(showDialog = false),
eventSink = {}
)

View file

@ -53,7 +53,7 @@ import io.element.android.libraries.designsystem.components.ProgressDialog
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
import io.element.android.libraries.designsystem.preview.DayNightPreviews
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
import io.element.android.libraries.designsystem.theme.components.Scaffold
@ -62,6 +62,7 @@ import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.ui.components.AvatarActionBottomSheet
import io.element.android.libraries.matrix.ui.components.EditableAvatarView
import io.element.android.libraries.permissions.api.PermissionsView
import io.element.android.libraries.theme.ElementTheme
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.coroutines.launch
@ -193,6 +194,10 @@ fun RoomDetailsEditView(
else -> Unit
}
PermissionsView(
state = state.cameraPermissionState,
)
}
@Composable
@ -228,7 +233,7 @@ private fun Modifier.clearFocusOnTap(focusManager: FocusManager): Modifier =
})
}
@DayNightPreviews
@PreviewsDayNight
@Composable
internal fun RoomDetailsEditViewPreview(@PreviewParameter(RoomDetailsEditStateProvider::class) state: RoomDetailsEditState) = ElementPreview {
RoomDetailsEditView(

View file

@ -19,15 +19,14 @@ package io.element.android.features.roomdetails.impl.invite
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
data class RoomInviteMembersState(
val canInvite: Boolean = false,
val searchQuery: String = "",
val searchResults: SearchBarResultState<ImmutableList<InvitableUser>> = SearchBarResultState.NotSearching(),
val selectedUsers: ImmutableList<MatrixUser> = persistentListOf(),
val isSearchActive: Boolean = false,
val eventSink: (RoomInviteMembersEvents) -> Unit = {},
val canInvite: Boolean,
val searchQuery: String,
val searchResults: SearchBarResultState<ImmutableList<InvitableUser>>,
val selectedUsers: ImmutableList<MatrixUser>,
val isSearchActive: Boolean,
val eventSink: (RoomInviteMembersEvents) -> Unit,
)
data class InvitableUser(

View file

@ -18,20 +18,22 @@ package io.element.android.features.roomdetails.impl.invite
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.components.aMatrixUser
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
internal class RoomInviteMembersStateProvider : PreviewParameterProvider<RoomInviteMembersState> {
override val values: Sequence<RoomInviteMembersState>
get() = sequenceOf(
RoomInviteMembersState(),
RoomInviteMembersState(canInvite = true, selectedUsers = aMatrixUserList().toImmutableList()),
RoomInviteMembersState(isSearchActive = true, searchQuery = "some query"),
RoomInviteMembersState(isSearchActive = true, searchQuery = "some query", selectedUsers = aMatrixUserList().toImmutableList()),
RoomInviteMembersState(isSearchActive = true, searchQuery = "some query", searchResults = SearchBarResultState.NoResults()),
RoomInviteMembersState(
aRoomInviteMembersState(),
aRoomInviteMembersState(canInvite = true, selectedUsers = aMatrixUserList().toImmutableList()),
aRoomInviteMembersState(isSearchActive = true, searchQuery = "some query"),
aRoomInviteMembersState(isSearchActive = true, searchQuery = "some query", selectedUsers = aMatrixUserList().toImmutableList()),
aRoomInviteMembersState(isSearchActive = true, searchQuery = "some query", searchResults = SearchBarResultState.NoResults()),
aRoomInviteMembersState(
isSearchActive = true,
canInvite = true,
searchQuery = "some query",
@ -48,7 +50,7 @@ internal class RoomInviteMembersStateProvider : PreviewParameterProvider<RoomInv
)
)
),
RoomInviteMembersState(
aRoomInviteMembersState(
isSearchActive = true,
canInvite = true,
searchQuery = "@alice:server.org",
@ -64,3 +66,20 @@ internal class RoomInviteMembersStateProvider : PreviewParameterProvider<RoomInv
),
)
}
private fun aRoomInviteMembersState(
canInvite: Boolean = false,
searchQuery: String = "",
searchResults: SearchBarResultState<ImmutableList<InvitableUser>> = SearchBarResultState.NotSearching(),
selectedUsers: ImmutableList<MatrixUser> = persistentListOf(),
isSearchActive: Boolean = false,
): RoomInviteMembersState {
return RoomInviteMembersState(
canInvite = canInvite,
searchQuery = searchQuery,
searchResults = searchResults,
selectedUsers = selectedUsers,
isSearchActive = isSearchActive,
eventSink = {},
)
}

View file

@ -34,7 +34,7 @@ import androidx.compose.ui.unit.dp
import io.element.android.features.roomdetails.impl.R
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.preview.DayNightPreviews
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
@ -111,7 +111,7 @@ fun RoomInviteMembersView(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RoomInviteMembersTopBar(
private fun RoomInviteMembersTopBar(
canSend: Boolean,
modifier: Modifier = Modifier,
onBackPressed: () -> Unit = {},
@ -216,7 +216,7 @@ private fun RoomInviteMembersSearchBar(
)
}
@DayNightPreviews
@PreviewsDayNight
@Composable
internal fun RoomInviteMembersPreview(@PreviewParameter(RoomInviteMembersStateProvider::class) state: RoomInviteMembersState) = ElementPreview {
RoomInviteMembersView(state)

View file

@ -42,7 +42,7 @@ import io.element.android.features.roomdetails.impl.R
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.preview.DayNightPreviews
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
@ -203,7 +203,7 @@ private fun RoomMemberListTopBar(
modifier = modifier,
title = {
Text(
text = stringResource(R.string.screen_room_details_people_title),
text = stringResource(CommonStrings.common_people),
style = ElementTheme.typography.aliasScreenTitle,
)
},
@ -249,7 +249,7 @@ private fun RoomMemberSearchBar(
)
}
@DayNightPreviews
@PreviewsDayNight
@Composable
internal fun RoomMemberListPreview(@PreviewParameter(RoomMemberListStateProvider::class) state: RoomMemberListState) = ElementPreview {
RoomMemberListView(

View file

@ -23,7 +23,7 @@ data class RoomMemberDetailsState(
val userName: String?,
val avatarUrl: String?,
val isBlocked: Async<Boolean>,
val displayConfirmationDialog: ConfirmationDialog? = null,
val displayConfirmationDialog: ConfirmationDialog?,
val isCurrentUser: Boolean,
val eventSink: (RoomMemberDetailsEvents) -> Unit
) {

View file

@ -37,6 +37,7 @@ fun aRoomMemberDetailsState() = RoomMemberDetailsState(
userName = "Daniel",
avatarUrl = null,
isBlocked = Async.Success(false),
displayConfirmationDialog = null,
isCurrentUser = false,
eventSink = {},
)

View file

@ -16,49 +16,27 @@
package io.element.android.features.roomdetails.impl.members.details
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ChatBubbleOutline
import androidx.compose.material.icons.outlined.Share
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.features.roomdetails.impl.blockuser.BlockUserDialogs
import io.element.android.features.roomdetails.impl.blockuser.BlockUserSection
import io.element.android.libraries.designsystem.components.avatar.Avatar
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.button.MainActionButton
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.LargeHeightPreview
import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight
import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.theme.ElementTheme
import io.element.android.libraries.ui.strings.CommonStrings
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@Composable
@ -103,45 +81,9 @@ fun RoomMemberDetailsView(
}
}
/*
@Composable
internal fun RoomMemberHeaderSection(
avatarUrl: String?,
userId: String,
userName: String?,
modifier: Modifier = Modifier
) {
Column(modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
Box(modifier = Modifier.size(70.dp)) {
Avatar(
avatarData = AvatarData(userId, userName, avatarUrl, AvatarSize.UserHeader),
modifier = Modifier.fillMaxSize()
)
}
Spacer(modifier = Modifier.height(24.dp))
if (userName != null) {
Text(text = userName, style = ElementTheme.typography.fontHeadingLgBold)
Spacer(modifier = Modifier.height(6.dp))
}
Text(
text = userId,
style = ElementTheme.typography.fontBodyLgRegular,
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
textAlign = TextAlign.Center,
)
Spacer(Modifier.height(40.dp))
}
}
@Composable
internal fun RoomMemberMainActionsSection(onShareUser: () -> Unit, modifier: Modifier = Modifier) {
Row(modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
MainActionButton(title = stringResource(CommonStrings.action_share), icon = Icons.Outlined.Share, onClick = onShareUser)
}
}
@Composable
internal fun SendMessageSection(onSendMessage: () -> Unit, modifier: Modifier = Modifier) {
private fun SendMessageSection(onSendMessage: () -> Unit, modifier: Modifier = Modifier) {
PreferenceCategory(modifier = modifier) {
PreferenceText(
title = stringResource(CommonStrings.action_send_message),
@ -150,13 +92,14 @@ internal fun SendMessageSection(onSendMessage: () -> Unit, modifier: Modifier =
)
}
}
*/
@LargeHeightPreview
@PreviewWithLargeHeight
@Composable
internal fun RoomMemberDetailsViewLightPreview(@PreviewParameter(RoomMemberDetailsStateProvider::class) state: RoomMemberDetailsState) =
ElementPreviewLight { ContentToPreview(state) }
@LargeHeightPreview
@PreviewWithLargeHeight
@Composable
internal fun RoomMemberDetailsViewDarkPreview(@PreviewParameter(RoomMemberDetailsStateProvider::class) state: RoomMemberDetailsState) =
ElementPreviewDark { ContentToPreview(state) }

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.roomdetails.impl.members.details
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.components.avatar.Avatar
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.theme.ElementTheme
@Composable
fun RoomMemberHeaderSection(
avatarUrl: String?,
userId: String,
userName: String?,
modifier: Modifier = Modifier
) {
Column(modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
Box(modifier = Modifier.size(70.dp)) {
Avatar(
avatarData = AvatarData(userId, userName, avatarUrl, AvatarSize.UserHeader),
modifier = Modifier.fillMaxSize()
)
}
Spacer(modifier = Modifier.height(24.dp))
if (userName != null) {
Text(text = userName, style = ElementTheme.typography.fontHeadingLgBold)
Spacer(modifier = Modifier.height(6.dp))
}
Text(
text = userId,
style = ElementTheme.typography.fontBodyLgRegular,
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
textAlign = TextAlign.Center,
)
Spacer(Modifier.height(40.dp))
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.roomdetails.impl.members.details
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import io.element.android.libraries.designsystem.components.button.MainActionButton
import io.element.android.libraries.designsystem.utils.CommonDrawables
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun RoomMemberMainActionsSection(onShareUser: () -> Unit, modifier: Modifier = Modifier) {
Row(modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
MainActionButton(
title = stringResource(CommonStrings.action_share),
iconResourceId = CommonDrawables.ic_compound_share_android,
onClick = onShareUser
)
}
}

View file

@ -27,7 +27,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.DayNightPreviews
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.theme.components.RadioButton
import io.element.android.libraries.designsystem.theme.components.Text
@ -77,7 +77,7 @@ fun RoomNotificationSettingsOption(
}
}
@DayNightPreviews
@PreviewsDayNight
@Composable
internal fun RoomPrivacyOptionPreview() = ElementPreview {
Column {

View file

@ -37,7 +37,7 @@ import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
import io.element.android.libraries.designsystem.preview.DayNightPreviews
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
import io.element.android.libraries.designsystem.theme.components.Scaffold
@ -130,7 +130,7 @@ fun RoomNotificationSettingsView(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RoomNotificationSettingsTopBar(
private fun RoomNotificationSettingsTopBar(
modifier: Modifier = Modifier,
onBackPressed: () -> Unit = {},
) {
@ -175,7 +175,7 @@ fun ShowChangeNotificationSettingError(state: RoomNotificationSettingsState, eve
)
}
@DayNightPreviews
@PreviewsDayNight
@Composable
internal fun RoomNotificationSettingsPreview(
@PreviewParameter(RoomNotificationSettingsStateProvider::class) state: RoomNotificationSettingsState

View file

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M440,520Q457,520 468.5,508.5Q480,497 480,480Q480,463 468.5,451.5Q457,440 440,440Q423,440 411.5,451.5Q400,463 400,480Q400,497 411.5,508.5Q423,520 440,520ZM280,840L280,760L520,720Q520,720 520,720Q520,720 520,720L520,275Q520,260 511,248Q502,236 488,234L280,200L280,120L500,156Q544,164 572,197Q600,230 600,274L600,718Q600,747 581,769.5Q562,792 533,797L280,840ZM280,760L680,760L680,200Q680,200 680,200Q680,200 680,200L280,200Q280,200 280,200Q280,200 280,200L280,760ZM160,840Q143,840 131.5,828.5Q120,817 120,800Q120,783 131.5,771.5Q143,760 160,760L200,760L200,200Q200,166 223.5,143Q247,120 280,120L680,120Q714,120 737,143Q760,166 760,200L760,760L800,760Q817,760 828.5,771.5Q840,783 840,800Q840,817 828.5,828.5Q817,840 800,840L160,840Z"/>
</vector>

View file

@ -45,7 +45,6 @@
<string name="screen_dm_details_unblock_alert_description">"Znovu uvidíte všechny zprávy od nich."</string>
<string name="screen_dm_details_unblock_user">"Odblokovat uživatele"</string>
<string name="screen_room_details_leave_room_title">"Opustit místnost"</string>
<string name="screen_room_details_people_title">"Lidé"</string>
<string name="screen_room_details_security_title">"Zabezpečení"</string>
<string name="screen_room_details_topic_title">"Téma"</string>
</resources>

View file

@ -44,7 +44,6 @@
<string name="screen_dm_details_unblock_alert_description">"Du kannst dann wieder alle Nachrichten von ihnen sehen."</string>
<string name="screen_dm_details_unblock_user">"Benutzer entsperren"</string>
<string name="screen_room_details_leave_room_title">"Raum verlassen"</string>
<string name="screen_room_details_people_title">"Personen"</string>
<string name="screen_room_details_security_title">"Sicherheit"</string>
<string name="screen_room_details_topic_title">"Thema"</string>
</resources>

View file

@ -15,7 +15,6 @@
<string name="screen_dm_details_unblock_alert_description">"Al desbloquear al usuario, podrás volver a ver todos sus mensajes."</string>
<string name="screen_dm_details_unblock_user">"Desbloquear usuario"</string>
<string name="screen_room_details_leave_room_title">"Salir de la sala"</string>
<string name="screen_room_details_people_title">"Personas"</string>
<string name="screen_room_details_security_title">"Seguridad"</string>
<string name="screen_room_details_topic_title">"Tema"</string>
</resources>

View file

@ -44,7 +44,6 @@
<string name="screen_dm_details_unblock_alert_description">"Vous pourrez à nouveau voir tous ses messages."</string>
<string name="screen_dm_details_unblock_user">"Débloquer lutilisateur"</string>
<string name="screen_room_details_leave_room_title">"Quitter le salon"</string>
<string name="screen_room_details_people_title">"Personnes"</string>
<string name="screen_room_details_security_title">"Sécurité"</string>
<string name="screen_room_details_topic_title">"Sujet"</string>
</resources>

View file

@ -15,7 +15,6 @@
<string name="screen_dm_details_unblock_alert_description">"Dopo aver sbloccato l\'utente, potrai vedere nuovamente tutti i suoi messaggi."</string>
<string name="screen_dm_details_unblock_user">"Sblocca utente"</string>
<string name="screen_room_details_leave_room_title">"Esci dalla stanza"</string>
<string name="screen_room_details_people_title">"Persone"</string>
<string name="screen_room_details_security_title">"Sicurezza"</string>
<string name="screen_room_details_topic_title">"Oggetto"</string>
</resources>

View file

@ -44,7 +44,6 @@
<string name="screen_dm_details_unblock_alert_description">"La deblocarea utilizatorului, veți putea vedea din nou toate mesajele de la acesta."</string>
<string name="screen_dm_details_unblock_user">"Deblocați utilizatorul"</string>
<string name="screen_room_details_leave_room_title">"Părăsiți camera"</string>
<string name="screen_room_details_people_title">"Persoane"</string>
<string name="screen_room_details_security_title">"Securitate"</string>
<string name="screen_room_details_topic_title">"Subiect"</string>
</resources>

View file

@ -45,7 +45,6 @@
<string name="screen_dm_details_unblock_alert_description">"Вы снова сможете увидеть все сообщения."</string>
<string name="screen_dm_details_unblock_user">"Разблокировать пользователя"</string>
<string name="screen_room_details_leave_room_title">"Покинуть комнату"</string>
<string name="screen_room_details_people_title">"Пользователи"</string>
<string name="screen_room_details_security_title">"Безопасность"</string>
<string name="screen_room_details_topic_title">"Тема"</string>
</resources>

View file

@ -45,7 +45,6 @@
<string name="screen_dm_details_unblock_alert_description">"Všetky správy od nich budete môcť opäť vidieť."</string>
<string name="screen_dm_details_unblock_user">"Odblokovať používateľa"</string>
<string name="screen_room_details_leave_room_title">"Opustiť miestnosť"</string>
<string name="screen_room_details_people_title">"Ľudia"</string>
<string name="screen_room_details_security_title">"Bezpečnosť"</string>
<string name="screen_room_details_topic_title">"Téma"</string>
</resources>

View file

@ -9,6 +9,9 @@
<string name="screen_room_details_edit_room_title">"編輯聊天室"</string>
<string name="screen_room_details_edition_error_title">"無法更新聊天室"</string>
<string name="screen_room_details_encryption_enabled_title">"訊息已加密"</string>
<string name="screen_room_details_error_loading_notification_settings">"載入通知設定時發生錯誤。"</string>
<string name="screen_room_details_error_muting">"無法關閉聊天室通知,請再試一次。"</string>
<string name="screen_room_details_error_unmuting">"無法開啟聊天室通知,請再試一次。"</string>
<string name="screen_room_details_invite_people_title">"邀請夥伴"</string>
<string name="screen_room_details_notification_mode_custom">"自訂"</string>
<string name="screen_room_details_notification_mode_default">"預設"</string>
@ -23,12 +26,12 @@
<string name="screen_room_notification_settings_error_restoring_default">"無法重設為預設模式,請再試一次。"</string>
<string name="screen_room_notification_settings_error_setting_mode">"無法設定模式,請再試一次。"</string>
<string name="screen_room_notification_settings_mode_all_messages">"所有訊息"</string>
<string name="screen_room_notification_settings_mode_mentions_and_keywords">"限提及與關鍵字"</string>
<string name="screen_room_notification_settings_mode_mentions_and_keywords">"限提及與關鍵字"</string>
<string name="screen_dm_details_block_alert_action">"封鎖"</string>
<string name="screen_dm_details_block_user">"封鎖使用者"</string>
<string name="screen_dm_details_unblock_alert_action">"解除封鎖"</string>
<string name="screen_dm_details_unblock_user">"解除封鎖使用者"</string>
<string name="screen_room_details_leave_room_title">"離開聊天室"</string>
<string name="screen_room_details_people_title">"夥伴"</string>
<string name="screen_room_details_security_title">"安全性"</string>
<string name="screen_room_details_topic_title">"主題"</string>
</resources>

View file

@ -44,7 +44,6 @@
<string name="screen_dm_details_unblock_alert_description">"You\'ll be able to see all messages from them again."</string>
<string name="screen_dm_details_unblock_user">"Unblock user"</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_security_title">"Security"</string>
<string name="screen_room_details_topic_title">"Topic"</string>
</resources>

View file

@ -22,7 +22,7 @@ import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.features.leaveroom.api.LeaveRoomEvent
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
import io.element.android.features.leaveroom.fake.LeaveRoomPresenterFake
import io.element.android.features.leaveroom.fake.FakeLeaveRoomPresenter
import io.element.android.features.roomdetails.impl.RoomDetailsEvent
import io.element.android.features.roomdetails.impl.RoomDetailsPresenter
import io.element.android.features.roomdetails.impl.RoomDetailsType
@ -59,9 +59,10 @@ class RoomDetailsPresenterTests {
@get:Rule
val warmUpRule = WarmUpRule()
private fun aRoomDetailsPresenter(
private fun createRoomDetailsPresenter(
room: MatrixRoom,
leaveRoomPresenter: LeaveRoomPresenter = LeaveRoomPresenterFake(),
leaveRoomPresenter: LeaveRoomPresenter = FakeLeaveRoomPresenter(),
dispatchers: CoroutineDispatchers,
notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService()
): RoomDetailsPresenter {
@ -88,7 +89,7 @@ class RoomDetailsPresenterTests {
@Test
fun `present - initial state is created from room info`() = runTest {
val room = aMatrixRoom()
val presenter = aRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -107,7 +108,7 @@ class RoomDetailsPresenterTests {
@Test
fun `present - initial state with no room name`() = runTest {
val room = aMatrixRoom(name = null)
val presenter = aRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -129,7 +130,7 @@ class RoomDetailsPresenterTests {
val roomMembers = listOf(myRoomMember, otherRoomMember)
givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers))
}
val presenter = aRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -145,7 +146,7 @@ class RoomDetailsPresenterTests {
val room = aMatrixRoom().apply {
givenCanInviteResult(Result.success(true))
}
val presenter = aRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -163,7 +164,7 @@ class RoomDetailsPresenterTests {
val room = aMatrixRoom().apply {
givenCanInviteResult(Result.success(false))
}
val presenter = aRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -178,7 +179,7 @@ class RoomDetailsPresenterTests {
val room = aMatrixRoom().apply {
givenCanInviteResult(Result.failure(Throwable("Whoops")))
}
val presenter = aRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -196,7 +197,7 @@ class RoomDetailsPresenterTests {
givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.failure(Throwable("Whelp")))
givenCanInviteResult(Result.success(false))
}
val presenter = aRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -225,7 +226,7 @@ class RoomDetailsPresenterTests {
givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(true))
givenCanInviteResult(Result.success(false))
}
val presenter = aRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -254,7 +255,7 @@ class RoomDetailsPresenterTests {
givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true))
}
val presenter = aRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -275,7 +276,7 @@ class RoomDetailsPresenterTests {
givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(true))
givenCanInviteResult(Result.success(false))
}
val presenter = aRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -296,7 +297,7 @@ class RoomDetailsPresenterTests {
givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(false))
givenCanInviteResult(Result.success(false))
}
val presenter = aRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -314,7 +315,7 @@ class RoomDetailsPresenterTests {
givenCanInviteResult(Result.success(false))
}
val presenter = aRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -332,7 +333,7 @@ class RoomDetailsPresenterTests {
givenCanInviteResult(Result.success(false))
}
val presenter = aRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -348,9 +349,9 @@ class RoomDetailsPresenterTests {
@Test
fun `present - leave room event is passed on to leave room presenter`() = runTest {
val leaveRoomPresenter = LeaveRoomPresenterFake()
val leaveRoomPresenter = FakeLeaveRoomPresenter()
val room = aMatrixRoom()
val presenter = aRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers())
val presenter = createRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers())
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -364,10 +365,10 @@ class RoomDetailsPresenterTests {
@Test
fun `present - notification mode changes`() = runTest {
val leaveRoomPresenter = LeaveRoomPresenterFake()
val leaveRoomPresenter = FakeLeaveRoomPresenter()
val notificationSettingsService = FakeNotificationSettingsService()
val room = aMatrixRoom(notificationSettingsService = notificationSettingsService)
val presenter = aRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers(), notificationSettingsService)
val presenter = createRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers(), notificationSettingsService)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -383,10 +384,10 @@ class RoomDetailsPresenterTests {
@Test
fun `present - mute room notifications`() = runTest {
val leaveRoomPresenter = LeaveRoomPresenterFake()
val leaveRoomPresenter = FakeLeaveRoomPresenter()
val notificationSettingsService = FakeNotificationSettingsService(initialRoomMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
val room = aMatrixRoom(notificationSettingsService = notificationSettingsService)
val presenter = aRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers(), notificationSettingsService)
val presenter = createRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers(), notificationSettingsService)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -401,13 +402,13 @@ class RoomDetailsPresenterTests {
@Test
fun `present - unmute room notifications`() = runTest {
val leaveRoomPresenter = LeaveRoomPresenterFake()
val leaveRoomPresenter = FakeLeaveRoomPresenter()
val notificationSettingsService = FakeNotificationSettingsService(
initialRoomMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
initialEncryptedGroupDefaultMode = RoomNotificationMode.ALL_MESSAGES
)
val room = aMatrixRoom(notificationSettingsService = notificationSettingsService)
val presenter = aRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers(), notificationSettingsService)
val presenter = createRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers(), notificationSettingsService)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {

View file

@ -32,6 +32,9 @@ import io.element.android.libraries.matrix.ui.media.AvatarAction
import io.element.android.libraries.mediapickers.test.FakePickerProvider
import io.element.android.libraries.mediaupload.api.MediaUploadInfo
import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
import io.element.android.libraries.permissions.api.PermissionsPresenter
import io.element.android.libraries.permissions.test.FakePermissionsPresenter
import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory
import io.element.android.tests.testutils.WarmUpRule
import io.mockk.every
import io.mockk.mockk
@ -74,18 +77,22 @@ class RoomDetailsEditPresenterTest {
unmockkAll()
}
private fun aRoomDetailsEditPresenter(room: MatrixRoom): RoomDetailsEditPresenter {
private fun createRoomDetailsEditPresenter(
room: MatrixRoom,
permissionsPresenter: PermissionsPresenter = FakePermissionsPresenter(),
): RoomDetailsEditPresenter {
return RoomDetailsEditPresenter(
room = room,
mediaPickerProvider = fakePickerProvider,
mediaPreProcessor = fakeMediaPreProcessor,
permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter),
)
}
@Test
fun `present - initial state is created from room info`() = runTest {
val room = aMatrixRoom(avatarUrl = AN_AVATAR_URL)
val presenter = aRoomDetailsEditPresenter(room)
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -112,7 +119,7 @@ class RoomDetailsEditPresenterTest {
givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(false))
givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.failure(Throwable("Oops")))
}
val presenter = aRoomDetailsEditPresenter(room)
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -138,7 +145,7 @@ class RoomDetailsEditPresenterTest {
givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(true))
givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.failure(Throwable("Oops")))
}
val presenter = aRoomDetailsEditPresenter(room)
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -164,7 +171,7 @@ class RoomDetailsEditPresenterTest {
givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.failure(Throwable("Oops")))
givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true))
}
val presenter = aRoomDetailsEditPresenter(room)
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -186,7 +193,7 @@ class RoomDetailsEditPresenterTest {
@Test
fun `present - updates state in response to changes`() = runTest {
val room = aMatrixRoom(topic = "My topic", name = "Name", avatarUrl = AN_AVATAR_URL)
val presenter = aRoomDetailsEditPresenter(room)
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -232,7 +239,7 @@ class RoomDetailsEditPresenterTest {
fakePickerProvider.givenResult(anotherAvatarUri)
val presenter = aRoomDetailsEditPresenter(room)
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -252,19 +259,31 @@ class RoomDetailsEditPresenterTest {
val room = aMatrixRoom(topic = "My topic", name = "Name", avatarUrl = AN_AVATAR_URL)
fakePickerProvider.givenResult(anotherAvatarUri)
val presenter = aRoomDetailsEditPresenter(room)
val fakePermissionsPresenter = FakePermissionsPresenter()
val presenter = createRoomDetailsEditPresenter(
room = room,
permissionsPresenter = fakePermissionsPresenter,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.roomAvatarUrl).isEqualTo(roomAvatarUri)
assertThat(initialState.cameraPermissionState.permissionGranted).isFalse()
initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.TakePhoto))
awaitItem().apply {
assertThat(roomAvatarUrl).isEqualTo(anotherAvatarUri)
}
val stateWithAskingPermission = awaitItem()
assertThat(stateWithAskingPermission.cameraPermissionState.showDialog).isTrue()
fakePermissionsPresenter.setPermissionGranted()
val stateWithPermission = awaitItem()
assertThat(stateWithPermission.cameraPermissionState.permissionGranted).isTrue()
val stateWithNewAvatar = awaitItem()
assertThat(stateWithNewAvatar.roomAvatarUrl).isEqualTo(anotherAvatarUri)
// Do it again, no permission is requested
fakePickerProvider.givenResult(roomAvatarUri)
stateWithNewAvatar.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.TakePhoto))
val stateWithNewAvatar2 = awaitItem()
assertThat(stateWithNewAvatar2.roomAvatarUrl).isEqualTo(roomAvatarUri)
}
}
@ -274,7 +293,7 @@ class RoomDetailsEditPresenterTest {
fakePickerProvider.givenResult(roomAvatarUri)
val presenter = aRoomDetailsEditPresenter(room)
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -326,7 +345,7 @@ class RoomDetailsEditPresenterTest {
fakePickerProvider.givenResult(roomAvatarUri)
val presenter = aRoomDetailsEditPresenter(room)
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -376,7 +395,7 @@ class RoomDetailsEditPresenterTest {
fun `present - save changes room details if different`() = runTest {
val room = aMatrixRoom(topic = "My topic", name = "Name", avatarUrl = AN_AVATAR_URL)
val presenter = aRoomDetailsEditPresenter(room)
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -401,7 +420,7 @@ class RoomDetailsEditPresenterTest {
fun `present - save doesn't change room details if they're the same trimmed`() = runTest {
val room = aMatrixRoom(topic = "My topic", name = "Name", avatarUrl = AN_AVATAR_URL)
val presenter = aRoomDetailsEditPresenter(room)
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -425,7 +444,7 @@ class RoomDetailsEditPresenterTest {
fun `present - save doesn't change topic if it was unset and is now blank`() = runTest {
val room = aMatrixRoom(topic = null, name = "Name", avatarUrl = AN_AVATAR_URL)
val presenter = aRoomDetailsEditPresenter(room)
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -448,7 +467,7 @@ class RoomDetailsEditPresenterTest {
fun `present - save doesn't change name if it's now empty`() = runTest {
val room = aMatrixRoom(topic = "My topic", name = "Name", avatarUrl = AN_AVATAR_URL)
val presenter = aRoomDetailsEditPresenter(room)
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -473,7 +492,7 @@ class RoomDetailsEditPresenterTest {
givenPickerReturnsFile()
val presenter = aRoomDetailsEditPresenter(room)
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -498,7 +517,7 @@ class RoomDetailsEditPresenterTest {
fakePickerProvider.givenResult(anotherAvatarUri)
fakeMediaPreProcessor.givenResult(Result.failure(Throwable("Oh no")))
val presenter = aRoomDetailsEditPresenter(room)
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -564,7 +583,7 @@ class RoomDetailsEditPresenterTest {
givenSetTopicResult(Result.failure(Throwable("!")))
}
val presenter = aRoomDetailsEditPresenter(room)
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -583,7 +602,7 @@ class RoomDetailsEditPresenterTest {
}
private suspend fun saveAndAssertFailure(room: MatrixRoom, event: RoomDetailsEditEvents) {
val presenter = aRoomDetailsEditPresenter(room)
val presenter = createRoomDetailsEditPresenter(room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()