mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 19:44:05 +00:00
Invite people in an existing room (#915)
* Handle power levels * Add “invite” button in RoomMembersListScreen * Refactor DI in RoomDetailsScreenCoordinator * Add navigation to “select people” coordinator * Add InviteUsersScreenRoomContext * Add invite user by id api * Updare InviteUsersScreenRoomContext * Support disabled state on FormButtonStyles * Handle membership in UserProfileCell * Fix conflicts * Refactor RoomContext -> RoomType * Refactor UserProfileCell * Add invite action * Setup navigation to user flow coordinator * Add invite logic * Fix test build errors * Address comments * Fix localisations * Update required state * Handle error * Move invite logic in RoomDetailsScreenCoordinator * Add navigation from room’s members list * Fix UTs * Fix missing display name in RoomMemberDetails * Add user indicator * Add feature flag * Add RoomDetailsViewModel UTs * Add room details ui tests * Add InviteUsersViewModelTests UTs * Add UI tests * Update UI tests * Empty commit
This commit is contained in:
@@ -44,7 +44,8 @@ enum FormRowAccessory: View {
|
||||
/// to change the background colour depending on whether the button is currently pressed or not.
|
||||
struct FormButtonStyle: PrimitiveButtonStyle {
|
||||
var iconAlignment: VerticalAlignment = .firstTextBaseline
|
||||
|
||||
/// Disables the button while providing an entry point for UI customizations
|
||||
var isDisabled = false
|
||||
/// An accessory to be added on the trailing side of the row.
|
||||
var accessory: FormRowAccessory?
|
||||
|
||||
@@ -54,12 +55,14 @@ struct FormButtonStyle: PrimitiveButtonStyle {
|
||||
.labelStyle(FormRowLabelStyle(alignment: iconAlignment, role: configuration.role))
|
||||
.frame(maxHeight: .infinity) // Make sure the label fills the cell vertically.
|
||||
}
|
||||
.buttonStyle(Style(accessory: accessory))
|
||||
.buttonStyle(Style(isDisabled: isDisabled, accessory: accessory))
|
||||
.listRowInsets(EdgeInsets()) // Remove insets so the background fills the cell.
|
||||
.disabled(isDisabled)
|
||||
}
|
||||
|
||||
/// Inner style used to set the pressed background colour.
|
||||
struct Style: ButtonStyle {
|
||||
var isDisabled: Bool
|
||||
var accessory: FormRowAccessory?
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
@@ -70,6 +73,7 @@ struct FormButtonStyle: PrimitiveButtonStyle {
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
accessory
|
||||
.opacity(isDisabled ? 0.54 : 1)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.padding(FormRow.insets) // Re-apply the normal insets using padding.
|
||||
@@ -132,7 +136,15 @@ struct FormButtonStyles_Previews: PreviewProvider {
|
||||
}
|
||||
.buttonStyle(FormButtonStyle())
|
||||
Button { } label: {
|
||||
Text("Selection")
|
||||
Text("Selected")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: .selection(isSelected: true)))
|
||||
Button { } label: {
|
||||
Text("Selected (disabled)")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(isDisabled: true, accessory: .selection(isSelected: true)))
|
||||
Button { } label: {
|
||||
Text("Unselected")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: .selection(isSelected: false)))
|
||||
}
|
||||
|
||||
@@ -14,10 +14,12 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import MatrixRustSDK
|
||||
import SwiftUI
|
||||
|
||||
struct UserProfileCell: View {
|
||||
let user: UserProfile
|
||||
let membership: MembershipState?
|
||||
let imageProvider: ImageProviderProtocol?
|
||||
|
||||
var body: some View {
|
||||
@@ -35,25 +37,92 @@ struct UserProfileCell: View {
|
||||
.font(.compound.headingSM)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
|
||||
if user.displayName != nil {
|
||||
Text(user.userID)
|
||||
.font(.compound.bodyMD)
|
||||
.foregroundColor(.element.tertiaryContent)
|
||||
}
|
||||
subtitleContent
|
||||
|
||||
if !user.isVerified {
|
||||
HStack(alignment: .firstTextBaseline, spacing: 4) {
|
||||
Text(Image(systemName: "exclamationmark.circle.fill"))
|
||||
.foregroundColor(.compound.textCriticalPrimary)
|
||||
|
||||
Text(L10n.screenStartChatUnknownProfile)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.font(.compound.bodyXS)
|
||||
.padding(.top, 4)
|
||||
}
|
||||
warningContent
|
||||
}
|
||||
.accessibilityElement(children: .combine)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
@ViewBuilder
|
||||
private var subtitleContent: some View {
|
||||
if let membershipText = membership?.localizedDescription {
|
||||
Text(membershipText)
|
||||
.font(.compound.bodyMD)
|
||||
.foregroundColor(.element.tertiaryContent)
|
||||
} else if user.displayName != nil {
|
||||
Text(user.userID)
|
||||
.font(.compound.bodyMD)
|
||||
.foregroundColor(.element.tertiaryContent)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var warningContent: some View {
|
||||
if !user.isVerified, membership == nil {
|
||||
HStack(alignment: .firstTextBaseline, spacing: 4) {
|
||||
Text(Image(systemName: "exclamationmark.circle.fill"))
|
||||
.foregroundColor(.compound.textCriticalPrimary)
|
||||
|
||||
Text(L10n.screenStartChatUnknownProfile)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.font(.compound.bodyXS)
|
||||
.padding(.top, 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension MembershipState {
|
||||
var localizedDescription: String? {
|
||||
switch self {
|
||||
case .join:
|
||||
return L10n.screenRoomDetailsAlreadyAMember
|
||||
case .invite:
|
||||
return L10n.screenRoomDetailsAlreadyInvited
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct UserProfileCell_Previews: PreviewProvider {
|
||||
static let action: () -> Void = { }
|
||||
|
||||
static var previews: some View {
|
||||
Form {
|
||||
Button(action: action) {
|
||||
UserProfileCell(user: .mockAlice, membership: nil, imageProvider: MockMediaProvider())
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: .selection(isSelected: true)))
|
||||
.previewDisplayName("Selected user")
|
||||
|
||||
Button(action: action) {
|
||||
UserProfileCell(user: .mockBob, membership: nil, imageProvider: MockMediaProvider())
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: .selection(isSelected: false)))
|
||||
.previewDisplayName("Unselected user")
|
||||
|
||||
Button(action: action) {
|
||||
UserProfileCell(user: .mockCharlie, membership: .join, imageProvider: MockMediaProvider())
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(isDisabled: true, accessory: .selection(isSelected: true)))
|
||||
.previewDisplayName("Selected disabled user")
|
||||
|
||||
Button(action: action) {
|
||||
UserProfileCell(user: .init(userID: "@someone:matrix.org"), membership: .join, imageProvider: MockMediaProvider())
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(isDisabled: true, accessory: .selection(isSelected: true)))
|
||||
.previewDisplayName("Unverified joined user")
|
||||
|
||||
Button(action: action) {
|
||||
UserProfileCell(user: .init(userID: "@someone:matrix.org"), membership: nil, imageProvider: MockMediaProvider())
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: .selection(isSelected: false)))
|
||||
.previewDisplayName("Unverified user")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user