DM Design Tweaks (#3693)

* dm design tweaks

* adding equatable conformance

* topic will now be shown in DMs

* code suggestion

* updated details

* better check

* code improvement
This commit is contained in:
Mauro 2025-01-22 14:32:39 +01:00 committed by GitHub
parent f194285250
commit 48e530fec9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 69 additions and 38 deletions

View File

@ -21,6 +21,7 @@
"a11y_user_menu" = "User menu"; "a11y_user_menu" = "User menu";
"a11y_voice_message_record" = "Record voice message."; "a11y_voice_message_record" = "Record voice message.";
"a11y_voice_message_stop_recording" = "Stop recording"; "a11y_voice_message_stop_recording" = "Stop recording";
"a11y.view_details" = "View details";
"action_accept" = "Accept"; "action_accept" = "Accept";
"action_add_caption" = "Add caption"; "action_add_caption" = "Add caption";
"action_add_to_timeline" = "Add to timeline"; "action_add_to_timeline" = "Add to timeline";
@ -440,6 +441,7 @@
"screen_room_single_knock_request_title" = "%1$@ wants to join this room"; "screen_room_single_knock_request_title" = "%1$@ wants to join this room";
"screen_room_single_knock_request_view_button_title" = "View"; "screen_room_single_knock_request_view_button_title" = "View";
"screen_room_details_pinned_events_row_title" = "Pinned messages"; "screen_room_details_pinned_events_row_title" = "Pinned messages";
"screen_room_details_profile_row_title" = "Profile";
"screen_room_details_requests_to_join_title" = "Requests to join"; "screen_room_details_requests_to_join_title" = "Requests to join";
"screen_room_details_security_and_privacy_title" = "Security & privacy"; "screen_room_details_security_and_privacy_title" = "Security & privacy";
"screen_roomlist_knock_event_sent_description" = "Request to join sent"; "screen_roomlist_knock_event_sent_description" = "Request to join sent";

View File

@ -21,6 +21,7 @@
"a11y_user_menu" = "User menu"; "a11y_user_menu" = "User menu";
"a11y_voice_message_record" = "Record voice message."; "a11y_voice_message_record" = "Record voice message.";
"a11y_voice_message_stop_recording" = "Stop recording"; "a11y_voice_message_stop_recording" = "Stop recording";
"a11y.view_details" = "View details";
"action_accept" = "Accept"; "action_accept" = "Accept";
"action_add_caption" = "Add caption"; "action_add_caption" = "Add caption";
"action_add_to_timeline" = "Add to timeline"; "action_add_to_timeline" = "Add to timeline";
@ -440,6 +441,7 @@
"screen_room_single_knock_request_title" = "%1$@ wants to join this room"; "screen_room_single_knock_request_title" = "%1$@ wants to join this room";
"screen_room_single_knock_request_view_button_title" = "View"; "screen_room_single_knock_request_view_button_title" = "View";
"screen_room_details_pinned_events_row_title" = "Pinned messages"; "screen_room_details_pinned_events_row_title" = "Pinned messages";
"screen_room_details_profile_row_title" = "Profile";
"screen_room_details_requests_to_join_title" = "Requests to join"; "screen_room_details_requests_to_join_title" = "Requests to join";
"screen_room_details_security_and_privacy_title" = "Security & privacy"; "screen_room_details_security_and_privacy_title" = "Security & privacy";
"screen_roomlist_knock_event_sent_description" = "Request to join sent"; "screen_roomlist_knock_event_sent_description" = "Request to join sent";

View File

@ -861,6 +861,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
stateMachine.tryEvent(.presentMediaEventsTimeline) stateMachine.tryEvent(.presentMediaEventsTimeline)
case .presentSecurityAndPrivacyScreen: case .presentSecurityAndPrivacyScreen:
stateMachine.tryEvent(.presentSecurityAndPrivacyScreen) stateMachine.tryEvent(.presentSecurityAndPrivacyScreen)
case .presentRecipientDetails(let userID):
stateMachine.tryEvent(.presentRoomMemberDetails(userID: userID))
} }
} }
.store(in: &cancellables) .store(in: &cancellables)

View File

@ -1896,6 +1896,8 @@ internal enum L10n {
internal static var screenRoomDetailsNotificationTitle: String { return L10n.tr("Localizable", "screen_room_details_notification_title") } internal static var screenRoomDetailsNotificationTitle: String { return L10n.tr("Localizable", "screen_room_details_notification_title") }
/// Pinned messages /// Pinned messages
internal static var screenRoomDetailsPinnedEventsRowTitle: String { return L10n.tr("Localizable", "screen_room_details_pinned_events_row_title") } internal static var screenRoomDetailsPinnedEventsRowTitle: String { return L10n.tr("Localizable", "screen_room_details_pinned_events_row_title") }
/// Profile
internal static var screenRoomDetailsProfileRowTitle: String { return L10n.tr("Localizable", "screen_room_details_profile_row_title") }
/// Requests to join /// Requests to join
internal static var screenRoomDetailsRequestsToJoinTitle: String { return L10n.tr("Localizable", "screen_room_details_requests_to_join_title") } internal static var screenRoomDetailsRequestsToJoinTitle: String { return L10n.tr("Localizable", "screen_room_details_requests_to_join_title") }
/// Roles and permissions /// Roles and permissions
@ -2781,6 +2783,11 @@ internal enum L10n {
/// Check UnifiedPush /// Check UnifiedPush
internal static var troubleshootNotificationsTestUnifiedPushTitle: String { return L10n.tr("Localizable", "troubleshoot_notifications_test_unified_push_title") } internal static var troubleshootNotificationsTestUnifiedPushTitle: String { return L10n.tr("Localizable", "troubleshoot_notifications_test_unified_push_title") }
internal enum A11y {
/// View details
internal static var viewDetails: String { return L10n.tr("Localizable", "a11y.view_details") }
}
internal enum Common { internal enum Common {
/// Copied to clipboard /// Copied to clipboard
internal static var copiedToClipboard: String { return L10n.tr("Localizable", "common.copied_to_clipboard") } internal static var copiedToClipboard: String { return L10n.tr("Localizable", "common.copied_to_clipboard") }

View File

@ -31,6 +31,7 @@ struct JoinedRoomProxyMockConfiguration {
var timelineStartReached = false var timelineStartReached = false
var members: [RoomMemberProxyMock] = .allMembers var members: [RoomMemberProxyMock] = .allMembers
var heroes: [RoomMemberProxyMock] = []
var knockRequestsState: KnockRequestsState = .loaded([]) var knockRequestsState: KnockRequestsState = .loaded([])
var ownUserID = RoomMemberProxyMock.mockMe.userID var ownUserID = RoomMemberProxyMock.mockMe.userID
var inviter: RoomMemberProxyProtocol? var inviter: RoomMemberProxyProtocol?
@ -159,7 +160,7 @@ extension RoomInfo {
isIgnored: $0.isIgnored, isIgnored: $0.isIgnored,
suggestedRoleForPowerLevel: $0.role, suggestedRoleForPowerLevel: $0.role,
membershipChangeReason: $0.membershipChangeReason) }, membershipChangeReason: $0.membershipChangeReason) },
heroes: [], heroes: configuration.heroes.map(RoomHero.init),
activeMembersCount: UInt64(configuration.members.filter { $0.membership == .join || $0.membership == .invite }.count), activeMembersCount: UInt64(configuration.members.filter { $0.membership == .join || $0.membership == .invite }.count),
invitedMembersCount: UInt64(configuration.members.filter { $0.membership == .invite }.count), invitedMembersCount: UInt64(configuration.members.filter { $0.membership == .invite }.count),
joinedMembersCount: UInt64(configuration.members.filter { $0.membership == .join }.count), joinedMembersCount: UInt64(configuration.members.filter { $0.membership == .join }.count),
@ -178,3 +179,11 @@ extension RoomInfo {
historyVisibility: .shared) historyVisibility: .shared)
} }
} }
private extension RoomHero {
init(from memberProxy: RoomMemberProxyMock) {
self.init(userId: memberProxy.userID,
displayName: memberProxy.displayName,
avatarUrl: memberProxy.avatarURL?.absoluteString)
}
}

View File

@ -37,7 +37,14 @@ struct AvatarHeaderView<Footer: View>: View {
@ViewBuilder footer: @escaping () -> Footer) { @ViewBuilder footer: @escaping () -> Footer) {
avatarInfo = .room(room.avatar) avatarInfo = .room(room.avatar)
title = room.name ?? room.id title = room.name ?? room.id
subtitle = room.canonicalAlias
if let roomAlias = room.canonicalAlias {
subtitle = roomAlias
} else if room.isDirect, case let .heroes(heroes) = room.avatar, heroes.count == 1 {
subtitle = heroes[0].userID
} else {
subtitle = nil
}
self.avatarSize = avatarSize self.avatarSize = avatarSize
self.mediaProvider = mediaProvider self.mediaProvider = mediaProvider
@ -193,7 +200,8 @@ struct AvatarHeaderView_Previews: PreviewProvider, TestablePreview {
avatarURL: .mockMXCAvatar), avatarURL: .mockMXCAvatar),
canonicalAlias: "#test:matrix.org", canonicalAlias: "#test:matrix.org",
isEncrypted: true, isEncrypted: true,
isPublic: true), isPublic: true,
isDirect: false),
avatarSize: .room(on: .details), avatarSize: .room(on: .details),
mediaProvider: MediaProviderMock(configuration: .init())) { mediaProvider: MediaProviderMock(configuration: .init())) {
HStack(spacing: 32) { HStack(spacing: 32) {

View File

@ -22,6 +22,7 @@ struct RoomDetailsScreenCoordinatorParameters {
enum RoomDetailsScreenCoordinatorAction { enum RoomDetailsScreenCoordinatorAction {
case leftRoom case leftRoom
case presentRoomMembersList case presentRoomMembersList
case presentRecipientDetails(userID: String)
case presentRoomDetailsEditScreen case presentRoomDetailsEditScreen
case presentNotificationSettingsScreen case presentNotificationSettingsScreen
case presentInviteUsersScreen case presentInviteUsersScreen
@ -88,6 +89,8 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
actionsSubject.send(.presentKnockingRequestsListScreen) actionsSubject.send(.presentKnockingRequestsListScreen)
case .displaySecurityAndPrivacy: case .displaySecurityAndPrivacy:
actionsSubject.send(.presentSecurityAndPrivacyScreen) actionsSubject.send(.presentSecurityAndPrivacyScreen)
case .requestRecipientDetailsPresentation(let userID):
actionsSubject.send(.presentRecipientDetails(userID: userID))
} }
} }
.store(in: &cancellables) .store(in: &cancellables)

View File

@ -12,9 +12,10 @@ import SwiftUI
// MARK: View model // MARK: View model
enum RoomDetailsScreenViewModelAction { enum RoomDetailsScreenViewModelAction: Equatable {
case requestNotificationSettingsPresentation case requestNotificationSettingsPresentation
case requestMemberDetailsPresentation case requestMemberDetailsPresentation
case requestRecipientDetailsPresentation(userID: String)
case requestInvitePeoplePresentation case requestInvitePeoplePresentation
case leftRoom case leftRoom
case requestEditDetailsPresentation case requestEditDetailsPresentation
@ -68,7 +69,7 @@ struct RoomDetailsScreenViewState: BindableState {
} }
var hasTopicSection: Bool { var hasTopicSection: Bool {
topic != nil || (canEdit && canEditRoomTopic) topic != nil || canEditRoomTopic
} }
var bindings: RoomDetailsScreenViewStateBindings var bindings: RoomDetailsScreenViewStateBindings
@ -198,6 +199,7 @@ enum RoomDetailsScreenViewAction {
case ignoreConfirmed case ignoreConfirmed
case unignoreConfirmed case unignoreConfirmed
case processTapNotifications case processTapNotifications
case processTapRecipientProfile
case processToggleMuteNotifications case processToggleMuteNotifications
case displayAvatar(URL) case displayAvatar(URL)
case processTapPolls case processTapPolls

View File

@ -170,6 +170,11 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
actionsSubject.send(.displayKnockingRequests) actionsSubject.send(.displayKnockingRequests)
case .processTapSecurityAndPrivacy: case .processTapSecurityAndPrivacy:
actionsSubject.send(.displaySecurityAndPrivacy) actionsSubject.send(.displaySecurityAndPrivacy)
case .processTapRecipientProfile:
guard let userID = dmRecipient?.userID else {
return
}
actionsSubject.send(.requestRecipientDetailsPresentation(userID: userID))
} }
} }

View File

@ -15,13 +15,7 @@ struct RoomDetailsScreen: View {
var body: some View { var body: some View {
Form { Form {
if let recipient = context.viewState.dmRecipient, roomHeaderSection
let accountOwner = context.viewState.accountOwner {
dmHeaderSection(accountOwner: accountOwner,
recipient: recipient)
} else {
normalRoomHeaderSection
}
topicSection topicSection
@ -66,7 +60,7 @@ struct RoomDetailsScreen: View {
// MARK: - Private // MARK: - Private
private var normalRoomHeaderSection: some View { private var roomHeaderSection: some View {
AvatarHeaderView(room: context.viewState.details, AvatarHeaderView(room: context.viewState.details,
avatarSize: .room(on: .details), avatarSize: .room(on: .details),
mediaProvider: context.mediaProvider) { url in mediaProvider: context.mediaProvider) { url in
@ -79,19 +73,6 @@ struct RoomDetailsScreen: View {
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.avatar) .accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.avatar)
} }
private func dmHeaderSection(accountOwner: RoomMemberDetails, recipient: RoomMemberDetails) -> some View {
AvatarHeaderView(accountOwner: accountOwner,
dmRecipient: recipient,
mediaProvider: context.mediaProvider) { url in
context.send(viewAction: .displayAvatar(url))
} footer: {
if !context.viewState.shortcuts.isEmpty {
headerSectionShortcuts
}
}
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.dmAvatar)
}
@ViewBuilder @ViewBuilder
private var headerSectionShortcuts: some View { private var headerSectionShortcuts: some View {
HStack(spacing: 8) { HStack(spacing: 8) {
@ -204,6 +185,14 @@ struct RoomDetailsScreen: View {
context.send(viewAction: .processTapSecurityAndPrivacy) context.send(viewAction: .processTapSecurityAndPrivacy)
}) })
} }
if context.viewState.dmRecipient != nil {
ListRow(label: .default(title: L10n.screenRoomDetailsProfileRowTitle,
icon: \.userProfile),
kind: .navigationLink {
context.send(viewAction: .processTapRecipientProfile)
})
}
} }
} }
@ -369,12 +358,12 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
] ]
let roomProxy = JoinedRoomProxyMock(.init(id: "dm_room_id", let roomProxy = JoinedRoomProxyMock(.init(id: "dm_room_id",
name: "DM Room", name: "Dan",
topic: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", topic: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
isDirect: true, isDirect: true,
isEncrypted: true, isEncrypted: true,
canonicalAlias: "#alias:domain.com", members: members,
members: members)) heroes: [.mockDan]))
let notificationSettingsProxy = NotificationSettingsProxyMock(with: .init()) let notificationSettingsProxy = NotificationSettingsProxyMock(with: .init())
return RoomDetailsScreenViewModel(roomProxy: roomProxy, return RoomDetailsScreenViewModel(roomProxy: roomProxy,

View File

@ -14,4 +14,5 @@ struct RoomDetails {
let canonicalAlias: String? let canonicalAlias: String?
let isEncrypted: Bool let isEncrypted: Bool
let isPublic: Bool let isPublic: Bool
let isDirect: Bool
} }

View File

@ -182,7 +182,8 @@ extension JoinedRoomProxyProtocol {
avatar: infoPublisher.value.avatar, avatar: infoPublisher.value.avatar,
canonicalAlias: infoPublisher.value.canonicalAlias, canonicalAlias: infoPublisher.value.canonicalAlias,
isEncrypted: isEncrypted, isEncrypted: isEncrypted,
isPublic: infoPublisher.value.isPublic) isPublic: infoPublisher.value.isPublic,
isDirect: infoPublisher.value.isDirect)
} }
var isDirectOneToOneRoom: Bool { var isDirectOneToOneRoom: Bool {