UI update for room details (#2831)

This commit is contained in:
Mauro
2024-05-10 17:58:35 +02:00
committed by GitHub
parent 446854e1c3
commit ae06226cbc
49 changed files with 282 additions and 113 deletions
@@ -710,6 +710,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
stateMachine.tryEvent(.presentPollsHistory)
case .presentRolesAndPermissionsScreen:
stateMachine.tryEvent(.presentRolesAndPermissionsScreen)
case .presentCall:
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
}
}
.store(in: &cancellables)
+9 -1
View File
@@ -1219,6 +1219,8 @@ internal enum L10n {
internal static var screenQrCodeLoginDeviceCodeSubtitle: String { return L10n.tr("Localizable", "screen_qr_code_login_device_code_subtitle") }
/// Enter the number below on your other device
internal static var screenQrCodeLoginDeviceCodeTitle: String { return L10n.tr("Localizable", "screen_qr_code_login_device_code_title") }
/// Ready to scan
internal static var screenQrCodeLoginInitialStateButtonTitle: String { return L10n.tr("Localizable", "screen_qr_code_login_initial_state_button_title") }
/// Open %1$@ on a desktop device
internal static func screenQrCodeLoginInitialStateItem1(_ p1: Any) -> String {
return L10n.tr("Localizable", "screen_qr_code_login_initial_state_item_1", String(describing: p1))
@@ -1231,7 +1233,7 @@ internal enum L10n {
}
/// “Link new device”
internal static var screenQrCodeLoginInitialStateItem3Action: String { return L10n.tr("Localizable", "screen_qr_code_login_initial_state_item_3_action") }
/// Follow the instructions shown
/// Scan the QR code with this device
internal static var screenQrCodeLoginInitialStateItem4: String { return L10n.tr("Localizable", "screen_qr_code_login_initial_state_item_4") }
/// Open %1$@ on another device to get the QR code
internal static func screenQrCodeLoginInitialStateTitle(_ p1: Any) -> String {
@@ -1409,6 +1411,12 @@ internal enum L10n {
internal static var screenRoomDetailsAlreadyAMember: String { return L10n.tr("Localizable", "screen_room_details_already_a_member") }
/// Already invited
internal static var screenRoomDetailsAlreadyInvited: String { return L10n.tr("Localizable", "screen_room_details_already_invited") }
/// Encrypted
internal static var screenRoomDetailsBadgeEncrypted: String { return L10n.tr("Localizable", "screen_room_details_badge_encrypted") }
/// Not encrypted
internal static var screenRoomDetailsBadgeNotEncrypted: String { return L10n.tr("Localizable", "screen_room_details_badge_not_encrypted") }
/// Public room
internal static var screenRoomDetailsBadgePublic: String { return L10n.tr("Localizable", "screen_room_details_badge_public") }
/// Edit Room
internal static var screenRoomDetailsEditRoomTitle: String { return L10n.tr("Localizable", "screen_room_details_edit_room_title") }
/// There was an unknown error and the information couldn't be changed.
@@ -17,10 +17,16 @@
import SwiftUI
struct AvatarHeaderView<Footer: View>: View {
private enum Badge: Hashable {
case encrypted(Bool)
case `public`
}
let id: String
let name: String?
let subtitle: String?
let avatarURL: URL?
private let badges: [Badge]
let avatarSize: AvatarSize
let imageProvider: ImageProviderProtocol?
@@ -41,6 +47,13 @@ struct AvatarHeaderView<Footer: View>: View {
self.imageProvider = imageProvider
self.onAvatarTap = onAvatarTap
self.footer = footer
var badges = [Badge]()
badges.append(.encrypted(room.isEncrypted))
if room.isPublic {
badges.append(.public)
}
self.badges = badges
}
init(member: RoomMemberDetails,
@@ -57,6 +70,7 @@ struct AvatarHeaderView<Footer: View>: View {
self.imageProvider = imageProvider
self.onAvatarTap = onAvatarTap
self.footer = footer
badges = []
}
init(user: UserProfileProxy,
@@ -73,6 +87,30 @@ struct AvatarHeaderView<Footer: View>: View {
self.imageProvider = imageProvider
self.onAvatarTap = onAvatarTap
self.footer = footer
badges = []
}
private var badgesStack: some View {
HStack(spacing: 8) {
ForEach(badges, id: \.self) { badge in
switch badge {
case .encrypted(let isEncrypted):
if isEncrypted {
BadgeLabel(title: L10n.screenRoomDetailsBadgeEncrypted,
icon: \.lockSolid,
isHighlighted: true)
} else {
BadgeLabel(title: L10n.screenRoomDetailsBadgeNotEncrypted,
icon: \.lockOff,
isHighlighted: false)
}
case .public:
BadgeLabel(title: L10n.screenRoomDetailsBadgePublic,
icon: \.public,
isHighlighted: false)
}
}
}
}
var body: some View {
@@ -102,6 +140,10 @@ struct AvatarHeaderView<Footer: View>: View {
.textSelection(.enabled)
}
if !badges.isEmpty {
badgesStack
}
footer()
}
.frame(maxWidth: .infinity, alignment: .center)
@@ -119,7 +161,9 @@ struct AvatarHeaderView_Previews: PreviewProvider, TestablePreview {
AvatarHeaderView(room: .init(id: "@test:matrix.org",
name: "Test Room",
avatarURL: URL.picturesDirectory,
canonicalAlias: "#test:matrix.org"),
canonicalAlias: "#test:matrix.org",
isEncrypted: true,
isPublic: true),
avatarSize: .room(on: .details),
imageProvider: MockMediaProvider()) {
HStack(spacing: 32) {
@@ -0,0 +1,75 @@
//
// Copyright 2024 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.
//
import Compound
import SwiftUI
struct BadgeLabel: View {
let title: String
let icon: KeyPath<CompoundIcons, Image>
let isHighlighted: Bool
var body: some View {
Label(title,
icon: icon,
iconSize: .xSmall,
relativeTo: .compound.bodySM)
.labelStyle(BadgeLabelStyle(isHighlighted: isHighlighted))
}
}
private struct BadgeLabelStyle: LabelStyle {
let isHighlighted: Bool
var titleColor: Color {
isHighlighted ? .compound._badgeTextSuccess : .compound._badgeTextSubtle
}
var iconColor: Color {
isHighlighted ? .compound.iconSuccessPrimary : .compound.iconSecondary
}
var backgroundColor: Color {
isHighlighted ? .compound._bgBadgeSuccess : .compound.bgSubtlePrimary
}
func makeBody(configuration: Configuration) -> some View {
HStack(spacing: 4) {
configuration.icon
.foregroundStyle(iconColor)
configuration.title
.foregroundStyle(titleColor)
}
.font(.compound.bodySM)
.padding(.leading, 8)
.padding(.trailing, 12)
.padding(.vertical, 4)
.background(Capsule().fill(backgroundColor))
}
}
struct BadgeLabel_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
VStack(spacing: 10) {
BadgeLabel(title: "Encrypted",
icon: \.lockSolid,
isHighlighted: true)
BadgeLabel(title: "Not encrypted",
icon: \.lockSolid,
isHighlighted: false)
}
}
}
@@ -56,12 +56,7 @@ struct JoinRoomScreen: View {
}
if let memberCount = context.viewState.roomDetails?.memberCount {
Label("\(memberCount)", icon: \.userProfile, iconSize: .small, relativeTo: .compound.bodySMSemibold)
.font(.compound.bodySMSemibold)
.foregroundStyle(.compound.textSecondary)
.padding(4)
.background(Capsule().fill(.compound.bgSubtleSecondary))
.labelStyle(.custom(spacing: 4))
BadgeLabel(title: "\(memberCount)", icon: \.userProfile, isHighlighted: false)
}
if let topic = context.viewState.roomDetails?.topic {
@@ -35,6 +35,7 @@ enum RoomDetailsScreenCoordinatorAction {
case presentInviteUsersScreen
case presentPollsHistory
case presentRolesAndPermissionsScreen
case presentCall
}
final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
@@ -79,6 +80,8 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
actionsSubject.send(.presentPollsHistory)
case .requestRolesAndPermissionsPresentation:
actionsSubject.send(.presentRolesAndPermissionsScreen)
case .startCall:
actionsSubject.send(.presentCall)
}
}
.store(in: &cancellables)
@@ -29,6 +29,7 @@ enum RoomDetailsScreenViewModelAction {
case requestEditDetailsPresentation
case requestPollsHistoryPresentation
case requestRolesAndPermissionsPresentation
case startCall
}
// MARK: View
@@ -50,6 +51,7 @@ struct RoomDetailsScreenViewState: BindableState {
var canEditRoomAvatar = false
var canEditRolesOrPermissions = false
var notificationSettingsState: RoomDetailsNotificationSettingsState = .loading
var canJoinCall = false
var canEdit: Bool {
!isDirect && (canEditRoomName || canEditRoomTopic || canEditRoomAvatar)
@@ -65,6 +67,12 @@ struct RoomDetailsScreenViewState: BindableState {
var shortcuts: [RoomDetailsScreenViewShortcut] {
var shortcuts: [RoomDetailsScreenViewShortcut] = [.mute]
if !ProcessInfo.processInfo.isiOSAppOnMac, canJoinCall {
shortcuts.append(.call)
}
if dmRecipient == nil, canInviteUsers {
shortcuts.append(.invite)
}
if let permalink = dmRecipient?.permalink {
shortcuts.append(.share(link: permalink))
} else if let permalink {
@@ -184,11 +192,14 @@ enum RoomDetailsScreenViewAction {
case processTapPolls
case toggleFavourite(isFavourite: Bool)
case processTapRolesAndPermissions
case processTapCall
}
enum RoomDetailsScreenViewShortcut {
case share(link: URL)
case mute
case call
case invite
}
extension RoomDetailsScreenViewShortcut: Hashable { }
@@ -63,6 +63,13 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
bindings: .init()),
imageProvider: mediaProvider)
Task {
let userID = roomProxy.ownUserID
if case let .success(permission) = await roomProxy.canUserJoinCall(userID: userID) {
state.canJoinCall = permission
}
}
Task {
if case let .success(permalinkURL) = await roomProxy.matrixToPermalink() {
state.permalink = permalinkURL
@@ -126,6 +133,8 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
Task { await toggleFavourite(isFavourite) }
case .processTapRolesAndPermissions:
actionsSubject.send(.requestRolesAndPermissionsPresentation)
case .processTapCall:
actionsSubject.send(.startCall)
}
}
@@ -107,6 +107,20 @@ struct RoomDetailsScreen: View {
CompoundIcon(\.shareIos)
}
.buttonStyle(FormActionButtonStyle(title: L10n.actionShare))
case .call:
Button {
context.send(viewAction: .processTapCall)
} label: {
CompoundIcon(\.videoCall)
}
.buttonStyle(FormActionButtonStyle(title: L10n.actionCall))
case .invite:
Button {
context.send(viewAction: .processTapInvite)
} label: {
CompoundIcon(\.userAdd)
}
.buttonStyle(FormActionButtonStyle(title: L10n.actionInvite))
}
}
}
@@ -152,15 +166,6 @@ struct RoomDetailsScreen: View {
context.send(viewAction: .processTapPeople)
})
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.people)
if context.viewState.canInviteUsers {
ListRow(label: .default(title: L10n.screenRoomDetailsInvitePeopleTitle,
icon: \.userAdd),
kind: .navigationLink {
context.send(viewAction: .processTapInvite)
})
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.invite)
}
}
ListRow(label: .default(title: L10n.screenPollsHistoryTitle,
icon: \.polls),
@@ -21,4 +21,6 @@ struct RoomDetails {
let name: String?
let avatarURL: URL?
let canonicalAlias: String?
let isEncrypted: Bool
let isPublic: Bool
}
@@ -141,7 +141,9 @@ extension RoomProxyProtocol {
RoomDetails(id: id,
name: name,
avatarURL: avatarURL,
canonicalAlias: canonicalAlias)
canonicalAlias: canonicalAlias,
isEncrypted: isEncrypted,
isPublic: isPublic)
}
// Avoids to duplicate the same logic around in the app