Create & Join rooms UI/UX tweaks (#1005)

* Fix people icon size

* Disable dismiss when loading in StartChatScreen

* Reduce debounce on search

* Add padding to invite button

* Add show/hide bottom bar behaviour

* Fix bg color in StartChatScreen form

* Update localisations

* Update search users results to 10

* Fix form background in InviteUsersScreen

* Fix invites list bg color

* Add mxid in InvitesScreenCell

* Refactor InvitesScreenCell design

* Fix separator spacing

* Add more design fixes

* Refine InvitesScreenCell layout

* Refactor UserProfileCell design

* Refactor FormRowAccessory

* Update localisations

* Fix camera size and security section padding

* Change create button style

* Refine InviteUsersScreenSelectedItem

* Refine CreateRoomScreen

* Refine UserProfileCell padding

* Amend preview name

* delete test code

* Add separator in security section

* Fix spacing in InviteUsersScreenSelectedItem

* Revert confirmationAction in CreateRoomScreen

* Refine HomeScreenInvitesButton

* Add dismiss keyboard in InviteUsersScreen

* Refactor FormRowAccessory

* Remove isDisabled from FormButtonStyles

* Update localisations

* Cleanup RoomDetailsEditScreen

* UI fixes on horizontal user carousel

* Refactor InviteUsersScreenSelectedItem

* Try fix flaky tests

* Fix typo

* Update snapshot tests

* Fix flaky UTs

* Fix HomeScreen ui tests

* Fix scroll on selection

* Refactor RoomFlowCoordinatorTests
This commit is contained in:
Alfonso Grillo
2023-06-06 10:03:38 +02:00
committed by GitHub
parent fedd401a56
commit 4e926c3334
87 changed files with 484 additions and 315 deletions
@@ -30,7 +30,7 @@ extension Publisher where Output == String, Failure == Never {
/// Clearing the text publishes the update immediately.
func debounceAndRemoveDuplicates() -> AnyPublisher<String, Never> {
map { query in
let milliseconds = query.isEmpty ? 0 : 500
let milliseconds = query.isEmpty ? 0 : 250
return Just(query).delay(for: .milliseconds(milliseconds), scheduler: DispatchQueue.main)
}
.switchToLatest()
@@ -0,0 +1,28 @@
//
// Copyright 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.
//
import SwiftUI
extension Section where Parent == Color, Content == EmptyView, Footer == EmptyView {
// An empty section whose purpose is to keep Form's background color when there is no content into it.
static var empty: some View {
Section {
EmptyView()
} header: {
Color.clear
}
}
}
+38 -4
View File
@@ -18,6 +18,11 @@ import Combine
import UIKit
class ScrollViewAdapter: NSObject, UIScrollViewDelegate {
enum ScrollDirection {
case up
case down
}
var scrollView: UIScrollView? {
didSet {
oldValue?.delegate = nil
@@ -25,13 +30,28 @@ class ScrollViewAdapter: NSObject, UIScrollViewDelegate {
}
}
let didScroll = PassthroughSubject<Void, Never>()
let isScrolling = CurrentValueSubject<Bool, Never>(false)
private let didScrollSubject = PassthroughSubject<Void, Never>()
var didScroll: AnyPublisher<Void, Never> {
didScrollSubject.eraseToAnyPublisher()
}
private let isScrollingSubject = CurrentValueSubject<Bool, Never>(false)
var isScrolling: CurrentValuePublisher<Bool, Never> {
.init(isScrollingSubject)
}
private let scrollDirectionSubject: PassthroughSubject<ScrollDirection, Never> = .init()
var scrollDirection: AnyPublisher<ScrollDirection, Never> {
scrollDirectionSubject
.removeDuplicates()
.eraseToAnyPublisher()
}
// MARK: - UIScrollViewDelegate
func scrollViewDidScroll(_ scrollView: UIScrollView) {
didScroll.send(())
didScrollSubject.send(())
updateScrollDirection(scrollView)
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
@@ -57,6 +77,20 @@ class ScrollViewAdapter: NSObject, UIScrollViewDelegate {
// MARK: - Private
private func updateDidScroll(_ scrollView: UIScrollView) {
isScrolling.send(scrollView.isDragging || scrollView.isDecelerating)
isScrollingSubject.send(scrollView.isDragging || scrollView.isDecelerating)
}
private func updateScrollDirection(_ scrollView: UIScrollView) {
let velocity = scrollView.panGestureRecognizer.velocity(in: nil)
if velocity.y > Constant.scrollDirectionThreshold {
scrollDirectionSubject.send(.down)
} else if velocity.y < -Constant.scrollDirectionThreshold {
scrollDirectionSubject.send(.up)
}
}
private enum Constant {
static let scrollDirectionThreshold: CGFloat = 200
}
}
@@ -17,13 +17,31 @@
import SwiftUI
/// A view to be added on the trailing edge of a form row.
enum FormRowAccessory: View {
case navigationLink
case progressView
case selection(isSelected: Bool)
struct FormRowAccessory: View {
@Environment(\.isEnabled) private var isEnabled
enum Kind {
case navigationLink
case progressView
case selection(isSelected: Bool)
}
let kind: Kind
static var navigationLink: Self {
.init(kind: .navigationLink)
}
static var progressView: Self {
.init(kind: .progressView)
}
static func selection(isSelected: Bool) -> Self {
.init(kind: .selection(isSelected: isSelected))
}
var body: some View {
switch self {
switch kind {
case .navigationLink:
Image(systemName: "chevron.forward")
.font(.compound.bodyMD.bold())
@@ -33,9 +51,13 @@ enum FormRowAccessory: View {
case .selection(let isSelected):
Image(systemName: isSelected ? "checkmark.circle.fill" : "circle")
.font(.compound.bodyLG)
.foregroundColor(isSelected ? .element.primaryContent : .element.tertiaryContent)
.foregroundColor(isSelected && isEnabled ? .element.primaryContent : .element.tertiaryContent)
}
}
private init(kind: Kind) {
self.kind = kind
}
}
/// Default button styling for form rows.
@@ -44,8 +66,6 @@ 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?
@@ -55,14 +75,12 @@ struct FormButtonStyle: PrimitiveButtonStyle {
.labelStyle(FormRowLabelStyle(alignment: iconAlignment, role: configuration.role))
.frame(maxHeight: .infinity) // Make sure the label fills the cell vertically.
}
.buttonStyle(Style(isDisabled: isDisabled, accessory: accessory))
.buttonStyle(Style(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
private struct Style: ButtonStyle {
var accessory: FormRowAccessory?
func makeBody(configuration: Configuration) -> some View {
@@ -73,7 +91,6 @@ 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.
@@ -135,14 +152,18 @@ struct FormButtonStyles_Previews: PreviewProvider {
Text("Hello world")
}
.buttonStyle(FormButtonStyle())
Button { } label: {
Text("Selected")
}
.buttonStyle(FormButtonStyle(accessory: .selection(isSelected: true)))
Button { } label: {
Text("Selected (disabled)")
}
.buttonStyle(FormButtonStyle(isDisabled: true, accessory: .selection(isSelected: true)))
.buttonStyle(FormButtonStyle(accessory: .selection(isSelected: true)))
.disabled(true)
Button { } label: {
Text("Unselected")
}
@@ -29,10 +29,9 @@ struct UserProfileCell: View {
contentID: user.userID,
avatarSize: .user(on: .startChat),
imageProvider: imageProvider)
.padding(.vertical, 10)
.accessibilityHidden(true)
VStack(alignment: .leading, spacing: 4) {
VStack(alignment: .leading, spacing: 0) {
Text(user.displayName ?? user.userID)
.font(.compound.headingSM)
.foregroundColor(.element.primaryContent)
@@ -40,6 +39,7 @@ struct UserProfileCell: View {
subtitleContent
warningContent
.padding(.top, 2)
}
.accessibilityElement(children: .combine)
}
@@ -63,15 +63,14 @@ struct UserProfileCell: View {
@ViewBuilder
private var warningContent: some View {
if !user.isVerified, membership == nil {
HStack(alignment: .firstTextBaseline, spacing: 4) {
HStack(alignment: .firstTextBaseline, spacing: 5) {
Text(Image(systemName: "exclamationmark.circle.fill"))
.foregroundColor(.compound.textCriticalPrimary)
Text(L10n.commonInviteUnknownProfile)
.foregroundColor(.secondary)
.foregroundColor(.element.tertiaryContent)
}
.font(.compound.bodyXS)
.padding(.top, 4)
}
}
}
@@ -109,13 +108,15 @@ struct UserProfileCell_Previews: PreviewProvider {
Button(action: action) {
UserProfileCell(user: .mockCharlie, membership: .join, imageProvider: MockMediaProvider())
}
.buttonStyle(FormButtonStyle(isDisabled: true, accessory: .selection(isSelected: true)))
.disabled(true)
.buttonStyle(FormButtonStyle(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)))
.disabled(true)
.buttonStyle(FormButtonStyle(accessory: .selection(isSelected: true)))
.previewDisplayName("Unverified joined user")
Button(action: action) {