mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 16:24:11 +00:00
Design: Forms, Login and Members screen. (#608)
* Rename Settings styles to Form styles. * Removed redundant FormPickerRow. * Use the button style everywhere and fix icon shapes.
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
/// A view to be added on the trailing edge of a form row.
|
||||
enum FormRowAccessory: View {
|
||||
case navigationLink
|
||||
|
||||
var body: some View {
|
||||
switch self {
|
||||
case .navigationLink:
|
||||
return Image(systemName: "chevron.forward")
|
||||
.font(.element.subheadlineBold)
|
||||
.foregroundColor(.element.quaternaryContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Default button styling for form rows.
|
||||
///
|
||||
/// The primitive style is needed to set the list row insets to `0`. The inner style is then needed
|
||||
/// to change the background colour depending on whether the button is currently pressed or not.
|
||||
struct FormButtonStyle: PrimitiveButtonStyle {
|
||||
/// An accessory to be added on the trailing side of the row.
|
||||
var accessory: FormRowAccessory?
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
Button(action: configuration.trigger) {
|
||||
configuration.label
|
||||
.frame(maxHeight: .infinity) // Make sure the label fills the cell vertically.
|
||||
}
|
||||
.buttonStyle(Style(accessory: accessory))
|
||||
.listRowInsets(EdgeInsets()) // Remove insets so the background fills the cell.
|
||||
}
|
||||
|
||||
/// Inner style used to set the pressed background colour.
|
||||
struct Style: ButtonStyle {
|
||||
var accessory: FormRowAccessory?
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
HStack {
|
||||
configuration.label
|
||||
.labelStyle(FormRowLabelStyle())
|
||||
.foregroundColor(.element.primaryContent)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
accessory
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.padding(FormRow.insets) // Re-apply the normal insets using padding.
|
||||
.background(configuration.isPressed ? Color.element.quinaryContent : .clear)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Small squared action button style for settings screens
|
||||
struct FormActionButtonStyle: ButtonStyle {
|
||||
let title: String
|
||||
|
||||
@ScaledMetric private var menuIconSize = 54.0
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
VStack {
|
||||
configuration.label
|
||||
.buttonStyle(.plain)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
.frame(width: menuIconSize, height: menuIconSize)
|
||||
.background {
|
||||
RoundedRectangle(cornerRadius: 16)
|
||||
.fill(configuration.isPressed ? Color.element.quinaryContent : .element.formRowBackground)
|
||||
}
|
||||
|
||||
Text(title)
|
||||
.foregroundColor(.element.secondaryContent)
|
||||
.font(.element.subheadline)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FormButtonStyles_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Form {
|
||||
Section {
|
||||
Button { } label: {
|
||||
Label("Hello world", systemImage: "globe")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle())
|
||||
|
||||
Button { } label: {
|
||||
Label("Show something", systemImage: "rectangle.portrait")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: .navigationLink))
|
||||
|
||||
ShareLink(item: "test")
|
||||
.buttonStyle(FormButtonStyle())
|
||||
}
|
||||
.formSectionStyle()
|
||||
|
||||
Section {
|
||||
Button { } label: {
|
||||
Text("Hello world")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle())
|
||||
}
|
||||
.formSectionStyle()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
enum FormRow {
|
||||
/// Element specific insets that are used for all our Form rows.
|
||||
static let insets = EdgeInsets(top: 7, leading: 16, bottom: 7, trailing: 16)
|
||||
}
|
||||
+21
-2
@@ -16,7 +16,7 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsRowLabelStyle: LabelStyle {
|
||||
struct FormRowLabelStyle: LabelStyle {
|
||||
@ScaledMetric private var menuIconSize = 30.0
|
||||
|
||||
var alignment: VerticalAlignment = .firstTextBaseline
|
||||
@@ -26,12 +26,31 @@ struct SettingsRowLabelStyle: LabelStyle {
|
||||
configuration.icon
|
||||
.foregroundColor(.element.secondaryContent)
|
||||
.padding(4)
|
||||
.frame(width: menuIconSize, height: menuIconSize)
|
||||
.background(Color.element.formBackground)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
.frame(width: menuIconSize, height: menuIconSize)
|
||||
configuration.title
|
||||
.font(.element.body)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FormRowLabelStyle_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Label("Person", systemImage: "person")
|
||||
.labelStyle(FormRowLabelStyle())
|
||||
|
||||
Label("Help", systemImage: "questionmark.circle")
|
||||
.labelStyle(FormRowLabelStyle())
|
||||
|
||||
Label("Camera", systemImage: "camera")
|
||||
.labelStyle(FormRowLabelStyle())
|
||||
|
||||
Label("Help", systemImage: "questionmark")
|
||||
.labelStyle(FormRowLabelStyle())
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
+15
-1
@@ -16,7 +16,7 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
/// Style for section header
|
||||
/// Style for form section headers
|
||||
struct FormSectionHeaderStyle: ViewModifier {
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
@@ -25,9 +25,23 @@ struct FormSectionHeaderStyle: ViewModifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Standard style for form sections
|
||||
struct FormSectionStyle: ViewModifier {
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(FormRow.insets)
|
||||
.listRowBackground(Color.element.formRowBackground)
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
/// Applies the `FormSectionHeaderStyle` modifier to the view
|
||||
func formSectionHeader() -> some View {
|
||||
modifier(FormSectionHeaderStyle())
|
||||
}
|
||||
|
||||
func formSectionStyle() -> some View {
|
||||
modifier(FormSectionStyle())
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
/// Small squared action button style for settings screens
|
||||
struct SettingsActionButtonStyle: ButtonStyle {
|
||||
let title: String
|
||||
|
||||
@ScaledMetric private var menuIconSize = 54.0
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
VStack {
|
||||
configuration.label
|
||||
.buttonStyle(.plain)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
.frame(width: menuIconSize, height: menuIconSize)
|
||||
.background {
|
||||
RoundedRectangle(cornerRadius: 16)
|
||||
.fill(Color.element.formRowBackground.opacity(configuration.isPressed ? 0.5 : 1))
|
||||
}
|
||||
|
||||
Text(title)
|
||||
.foregroundColor(.element.secondaryContent)
|
||||
.font(.element.subheadline)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
/// Default row that can be reused for settings screens
|
||||
struct SettingsDefaultRow: View {
|
||||
// MARK: Public
|
||||
|
||||
let title: String
|
||||
let image: Image
|
||||
let action: () -> Void
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let listRowInsets = EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)
|
||||
|
||||
// MARK: Views
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
HStack(spacing: 16) {
|
||||
Label(title: {
|
||||
Text(title)
|
||||
}, icon: {
|
||||
image
|
||||
})
|
||||
.labelStyle(SettingsRowLabelStyle())
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "chevron.forward")
|
||||
.foregroundColor(.element.tertiaryContent)
|
||||
}
|
||||
}
|
||||
.listRowInsets(listRowInsets)
|
||||
.listRowSeparator(.hidden)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
/// Picker row that can be reused for settings screens
|
||||
struct SettingsPickerRow<SelectionValue: Hashable, Content: View>: View {
|
||||
// MARK: Public
|
||||
|
||||
let title: String
|
||||
let image: Image
|
||||
@Binding var selection: SelectionValue
|
||||
let content: () -> Content
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@ScaledMetric private var menuIconSize = 30.0
|
||||
private let listRowInsets = EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)
|
||||
|
||||
// MARK: Views
|
||||
|
||||
var body: some View {
|
||||
Picker(selection: $selection, content: content) {
|
||||
HStack(spacing: 16) {
|
||||
image
|
||||
.foregroundColor(.element.systemGray)
|
||||
.padding(4)
|
||||
.background(Color.element.systemGray6)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
.frame(width: menuIconSize, height: menuIconSize)
|
||||
|
||||
Text(title)
|
||||
.font(.element.body)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
}
|
||||
}
|
||||
.listRowInsets(listRowInsets)
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
}
|
||||
@@ -92,12 +92,12 @@ final class LoginCoordinator: CoordinatorProtocol {
|
||||
static let loadingIndicatorIdentifier = "LoginCoordinatorLoading"
|
||||
|
||||
private func startLoading(isInteractionBlocking: Bool) {
|
||||
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,
|
||||
type: .modal,
|
||||
title: ElementL10n.loading,
|
||||
persistent: true))
|
||||
|
||||
if !isInteractionBlocking {
|
||||
if isInteractionBlocking {
|
||||
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,
|
||||
type: .modal,
|
||||
title: ElementL10n.loading,
|
||||
persistent: true))
|
||||
} else {
|
||||
viewModel.update(isLoading: true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,10 @@ struct LoginScreen: View {
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.bottom, 8)
|
||||
|
||||
TextField(ElementL10n.loginSigninUsernameHint, text: $context.username)
|
||||
TextField(ElementL10n.loginSigninUsernameHint,
|
||||
text: $context.username,
|
||||
// Prompt colour fixes a flicker that occurs before the text field style introspects the field.
|
||||
prompt: Text(ElementL10n.loginSigninUsernameHint).foregroundColor(.element.tertiaryContent))
|
||||
.focused($isUsernameFocused)
|
||||
.textFieldStyle(.elementInput(accessibilityIdentifier: A11yIdentifiers.loginScreen.emailUsername))
|
||||
.disableAutocorrection(true)
|
||||
@@ -86,7 +89,10 @@ struct LoginScreen: View {
|
||||
.onSubmit { isPasswordFocused = true }
|
||||
.padding(.bottom, 20)
|
||||
|
||||
SecureField(ElementL10n.loginSignupPasswordHint, text: $context.password)
|
||||
SecureField(ElementL10n.loginSignupPasswordHint,
|
||||
text: $context.password,
|
||||
// Prompt colour fixes a flicker that occurs before the text field style introspects the field.
|
||||
prompt: Text(ElementL10n.loginSignupPasswordHint).foregroundColor(.element.tertiaryContent))
|
||||
.focused($isPasswordFocused)
|
||||
.textFieldStyle(.elementInput(accessibilityIdentifier: A11yIdentifiers.loginScreen.password))
|
||||
.textContentType(.password)
|
||||
@@ -133,6 +139,8 @@ struct LoginScreen: View {
|
||||
private func submit() {
|
||||
guard context.viewState.canSubmit else { return }
|
||||
context.send(viewAction: .next)
|
||||
isUsernameFocused = false
|
||||
isPasswordFocused = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -96,11 +96,15 @@ struct BugReportScreen: View {
|
||||
private var sendLogsToggle: some View {
|
||||
VStack(spacing: 8) {
|
||||
Toggle(ElementL10n.bugReportScreenIncludeLogs, isOn: $context.sendingLogsEnabled)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
.tint(Color.element.brand)
|
||||
.accessibilityIdentifier(A11yIdentifiers.bugReportScreen.sendLogs)
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 11)
|
||||
.background(RoundedRectangle(cornerRadius: 14).fill(Color.element.formRowBackground))
|
||||
.padding(.vertical, 6.5)
|
||||
.background {
|
||||
RoundedRectangle(cornerRadius: 14)
|
||||
.fill(Color.element.formRowBackground)
|
||||
}
|
||||
|
||||
Text(ElementL10n.bugReportScreenLogsDescription)
|
||||
.font(.element.caption1)
|
||||
@@ -117,13 +121,13 @@ struct BugReportScreen: View {
|
||||
photoLibrary: .shared()) {
|
||||
HStack(spacing: 16) {
|
||||
Label(context.viewState.screenshot == nil ? ElementL10n.bugReportScreenAttachScreenshot : ElementL10n.bugReportScreenEditScreenshot, systemImage: "camera")
|
||||
.labelStyle(SettingsRowLabelStyle())
|
||||
.labelStyle(FormRowLabelStyle())
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 11)
|
||||
.background(RoundedRectangle(cornerRadius: 14).fill(Color.element.formRowBackground))
|
||||
.buttonStyle(FormButtonStyle())
|
||||
.background(Color.element.formRowBackground)
|
||||
.cornerRadius(14)
|
||||
.accessibilityIdentifier(A11yIdentifiers.bugReportScreen.attachScreenshot)
|
||||
if let screenshot = context.viewState.screenshot {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
@@ -173,11 +177,14 @@ struct BugReport_Previews: PreviewProvider {
|
||||
isModallyPresented: false)
|
||||
|
||||
static var previews: some View {
|
||||
Group {
|
||||
NavigationStack {
|
||||
BugReportScreen(context: BugReportViewModel(bugReportService: MockBugReportService(),
|
||||
screenshot: nil,
|
||||
isModallyPresented: false).context)
|
||||
.previewDisplayName("Without Screenshot")
|
||||
}
|
||||
|
||||
NavigationStack {
|
||||
BugReportScreen(context: BugReportViewModel(bugReportService: MockBugReportService(),
|
||||
screenshot: Asset.Images.appLogo.image,
|
||||
isModallyPresented: false).context)
|
||||
|
||||
+4
-1
@@ -29,8 +29,9 @@ struct DeveloperOptionsScreenScreen: View {
|
||||
Text("🥳")
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.buttonStyle(FormButtonStyle())
|
||||
}
|
||||
.listRowBackground(Color.element.formRowBackground)
|
||||
.formSectionStyle()
|
||||
}
|
||||
.overlay(effectsView)
|
||||
.scrollContentBackground(.hidden)
|
||||
@@ -43,6 +44,8 @@ struct DeveloperOptionsScreenScreen: View {
|
||||
private var effectsView: some View {
|
||||
if showConfetti {
|
||||
EffectsView(effect: .confetti)
|
||||
.ignoresSafeArea()
|
||||
.allowsHitTesting(false)
|
||||
.task { await removeConfettiAfterDelay() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ struct HomeScreen: View {
|
||||
}
|
||||
}
|
||||
.shimmer()
|
||||
.disabled(context.viewState.roomListMode == .skeletons)
|
||||
.disabled(true)
|
||||
} else {
|
||||
LazyVStack(spacing: 0) {
|
||||
ForEach(context.viewState.visibleRooms) { room in
|
||||
@@ -79,6 +79,7 @@ struct HomeScreen: View {
|
||||
}
|
||||
}
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
.scrollDisabled(context.viewState.roomListMode == .skeletons)
|
||||
.animation(.elementDefault, value: context.viewState.showSessionVerificationBanner)
|
||||
.animation(.elementDefault, value: context.viewState.roomListMode)
|
||||
.alert(item: $context.alertInfo) { $0.alert }
|
||||
|
||||
@@ -17,25 +17,18 @@
|
||||
import SwiftUI
|
||||
|
||||
struct RoomDetailsScreen: View {
|
||||
private let listRowInsets = EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)
|
||||
|
||||
@ObservedObject var context: RoomDetailsViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
headerSection
|
||||
|
||||
if let topic = context.viewState.topic {
|
||||
topicSection(with: topic)
|
||||
.listRowBackground(Color.element.formRowBackground)
|
||||
}
|
||||
|
||||
|
||||
topicSection
|
||||
|
||||
aboutSection
|
||||
.listRowBackground(Color.element.formRowBackground)
|
||||
|
||||
|
||||
if context.viewState.isEncrypted {
|
||||
securitySection
|
||||
.listRowBackground(Color.element.formRowBackground)
|
||||
}
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
@@ -71,12 +64,12 @@ struct RoomDetailsScreen: View {
|
||||
Button { context.send(viewAction: .copyRoomLink) } label: {
|
||||
Image(systemName: "link")
|
||||
}
|
||||
.buttonStyle(SettingsActionButtonStyle(title: ElementL10n.roomDetailsCopyLink))
|
||||
.buttonStyle(FormActionButtonStyle(title: ElementL10n.roomDetailsCopyLink))
|
||||
|
||||
ShareLink(item: permalink) {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
}
|
||||
.buttonStyle(SettingsActionButtonStyle(title: ElementL10n.inviteUsersToRoomActionInvite.capitalized))
|
||||
.buttonStyle(FormActionButtonStyle(title: ElementL10n.inviteUsersToRoomActionInvite.capitalized))
|
||||
}
|
||||
.padding(.top, 32)
|
||||
}
|
||||
@@ -85,14 +78,18 @@ struct RoomDetailsScreen: View {
|
||||
.listRowBackground(Color.clear)
|
||||
}
|
||||
|
||||
private func topicSection(with topic: String) -> some View {
|
||||
Section {
|
||||
Text(topic)
|
||||
.foregroundColor(.element.secondaryContent)
|
||||
.font(.element.footnote)
|
||||
} header: {
|
||||
Text(ElementL10n.roomSettingsTopic)
|
||||
.formSectionHeader()
|
||||
@ViewBuilder
|
||||
private var topicSection: some View {
|
||||
if let topic = context.viewState.topic {
|
||||
Section {
|
||||
Text(topic)
|
||||
.foregroundColor(.element.secondaryContent)
|
||||
.font(.element.footnote)
|
||||
} header: {
|
||||
Text(ElementL10n.roomSettingsTopic)
|
||||
.formSectionHeader()
|
||||
}
|
||||
.formSectionStyle()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,24 +98,19 @@ struct RoomDetailsScreen: View {
|
||||
Button {
|
||||
context.send(viewAction: .processTapPeople)
|
||||
} label: {
|
||||
HStack {
|
||||
Label(ElementL10n.bottomActionPeople, systemImage: "person")
|
||||
.labelStyle(SettingsRowLabelStyle())
|
||||
|
||||
Spacer()
|
||||
|
||||
LabeledContent {
|
||||
if context.viewState.isLoadingMembers {
|
||||
ProgressView()
|
||||
} else {
|
||||
Text(String(context.viewState.members.count))
|
||||
.foregroundColor(.element.secondaryContent)
|
||||
.foregroundColor(.element.tertiaryContent)
|
||||
.font(.element.body)
|
||||
Image(systemName: "chevron.forward")
|
||||
.foregroundColor(.element.quaternaryContent)
|
||||
}
|
||||
} label: {
|
||||
Label(ElementL10n.bottomActionPeople, systemImage: "person")
|
||||
}
|
||||
}
|
||||
.listRowInsets(listRowInsets)
|
||||
.buttonStyle(FormButtonStyle(accessory: context.viewState.isLoadingMembers ? nil : .navigationLink))
|
||||
.foregroundColor(.element.primaryContent)
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.people)
|
||||
.disabled(context.viewState.isLoadingMembers)
|
||||
@@ -126,33 +118,34 @@ struct RoomDetailsScreen: View {
|
||||
Text(ElementL10n.roomDetailsAboutSectionTitle)
|
||||
.formSectionHeader()
|
||||
}
|
||||
.formSectionStyle()
|
||||
}
|
||||
|
||||
private var securitySection: some View {
|
||||
Section {
|
||||
HStack(alignment: .top) {
|
||||
Label(title: {
|
||||
Label {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(ElementL10n.encryptionEnabled)
|
||||
Text(ElementL10n.encryptionEnabledTileDescription)
|
||||
.foregroundColor(.element.secondaryContent)
|
||||
.font(.element.footnote)
|
||||
}
|
||||
}, icon: {
|
||||
} icon: {
|
||||
Image(systemName: "lock.shield")
|
||||
})
|
||||
.labelStyle(SettingsRowLabelStyle(alignment: .top))
|
||||
}
|
||||
.labelStyle(FormRowLabelStyle(alignment: .top))
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "checkmark")
|
||||
.foregroundColor(.element.quaternaryContent)
|
||||
}
|
||||
.padding(.horizontal, -3)
|
||||
} header: {
|
||||
Text(ElementL10n.roomProfileSectionSecurity)
|
||||
.formSectionHeader()
|
||||
}
|
||||
.formSectionStyle()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,8 +39,9 @@ struct RoomMemberDetailsScreen: View {
|
||||
}
|
||||
}
|
||||
.searchable(text: $context.searchQuery, placement: .navigationBarDrawer(displayMode: .always))
|
||||
.alert(item: $context.alertInfo) { $0.alert }
|
||||
.background(Color.element.background.ignoresSafeArea())
|
||||
.navigationTitle(ElementL10n.bottomActionPeople)
|
||||
.alert(item: $context.alertInfo) { $0.alert }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,30 +21,23 @@ struct SettingsScreen: View {
|
||||
|
||||
@ScaledMetric private var avatarSize = AvatarSize.user(on: .settings).value
|
||||
|
||||
private let listRowInsets = EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)
|
||||
|
||||
@ObservedObject var context: SettingsScreenViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
userSection
|
||||
.listRowBackground(Color.element.formRowBackground)
|
||||
|
||||
if context.viewState.showSessionVerificationSection {
|
||||
sessionVerificationSection
|
||||
.listRowBackground(Color.element.formRowBackground)
|
||||
}
|
||||
|
||||
simplifiedSection
|
||||
.listRowBackground(Color.element.formRowBackground)
|
||||
|
||||
if context.viewState.showDeveloperOptions {
|
||||
developerOptionsSection
|
||||
.listRowBackground(Color.element.formRowBackground)
|
||||
}
|
||||
|
||||
signOutSection
|
||||
.listRowBackground(Color.element.formRowBackground)
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.element.formBackground.ignoresSafeArea())
|
||||
@@ -69,6 +62,7 @@ struct SettingsScreen: View {
|
||||
contentID: context.viewState.userID,
|
||||
avatarSize: .user(on: .settings),
|
||||
imageProvider: context.imageProvider)
|
||||
.accessibilityHidden(true)
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(context.viewState.userDisplayName ?? "")
|
||||
@@ -78,62 +72,66 @@ struct SettingsScreen: View {
|
||||
.font(.element.subheadline)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
}
|
||||
.accessibilityElement(children: .combine)
|
||||
}
|
||||
.listRowInsets(listRowInsets)
|
||||
}
|
||||
.formSectionStyle()
|
||||
}
|
||||
|
||||
private var sessionVerificationSection: some View {
|
||||
Section {
|
||||
SettingsDefaultRow(title: ElementL10n.settingsSessionVerification,
|
||||
image: Image(systemName: "checkmark.shield")) {
|
||||
context.send(viewAction: .sessionVerification)
|
||||
Button { context.send(viewAction: .sessionVerification) } label: {
|
||||
Label(ElementL10n.settingsSessionVerification, systemImage: "checkmark.shield")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle())
|
||||
}
|
||||
.formSectionStyle()
|
||||
}
|
||||
|
||||
private var developerOptionsSection: some View {
|
||||
Section {
|
||||
SettingsDefaultRow(title: ElementL10n.settingsDeveloperOptions,
|
||||
image: Image(systemName: "hammer.circle")) {
|
||||
context.send(viewAction: .developerOptions)
|
||||
Button { context.send(viewAction: .developerOptions) } label: {
|
||||
Label(ElementL10n.settingsDeveloperOptions, systemImage: "hammer.circle")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: .navigationLink))
|
||||
.accessibilityIdentifier("sessionVerificationButton")
|
||||
}
|
||||
.formSectionStyle()
|
||||
}
|
||||
|
||||
private var simplifiedSection: some View {
|
||||
Section {
|
||||
SettingsPickerRow(title: ElementL10n.settingsTimelineStyle,
|
||||
image: Image(systemName: "rectangle.grid.1x2"),
|
||||
selection: $context.timelineStyle) {
|
||||
Picker(selection: $context.timelineStyle) {
|
||||
ForEach(TimelineStyle.allCases, id: \.self) { style in
|
||||
Text(style.name)
|
||||
.tag(style)
|
||||
}
|
||||
} label: {
|
||||
Label(ElementL10n.settingsTimelineStyle, systemImage: "rectangle.grid.1x2")
|
||||
.labelStyle(FormRowLabelStyle())
|
||||
}
|
||||
.accessibilityIdentifier("timelineStylePicker")
|
||||
.onChange(of: context.timelineStyle) { _ in
|
||||
context.send(viewAction: .changedTimelineStyle)
|
||||
}
|
||||
|
||||
SettingsDefaultRow(title: ElementL10n.sendBugReport,
|
||||
image: Image(systemName: "questionmark.circle")) {
|
||||
context.send(viewAction: .reportBug)
|
||||
Button { context.send(viewAction: .reportBug) } label: {
|
||||
Label(ElementL10n.sendBugReport, systemImage: "questionmark.circle")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: .navigationLink))
|
||||
.accessibilityIdentifier("reportBugButton")
|
||||
}
|
||||
.formSectionStyle()
|
||||
}
|
||||
|
||||
private var signOutSection: some View {
|
||||
Section {
|
||||
SettingsDefaultRow(title: ElementL10n.actionSignOut,
|
||||
image: Image(systemName: "rectangle.portrait.and.arrow.right")) {
|
||||
showingLogoutConfirmation = true
|
||||
Button { showingLogoutConfirmation = true } label: {
|
||||
Label(ElementL10n.actionSignOut, systemImage: "rectangle.portrait.and.arrow.right")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle())
|
||||
.accessibilityIdentifier("logoutButton")
|
||||
.alert(ElementL10n.actionSignOut,
|
||||
isPresented: $showingLogoutConfirmation) {
|
||||
.alert(ElementL10n.actionSignOut, isPresented: $showingLogoutConfirmation) {
|
||||
Button(ElementL10n.actionSignOut,
|
||||
role: .destructive,
|
||||
action: logout)
|
||||
@@ -155,6 +153,7 @@ struct SettingsScreen: View {
|
||||
}
|
||||
.padding(.top, 24)
|
||||
}
|
||||
.formSectionStyle()
|
||||
}
|
||||
|
||||
private var doneButton: some View {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:840363fb8bda7290d75aad9dd2bfee3583299aca1682a18d9348c17cdff495de
|
||||
size 115274
|
||||
oid sha256:358aedfda97e567f398fc13908ba07135bd5d85c7b1000cc81ff3000b81d75d7
|
||||
size 115132
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f542e446145a1ce909d977120e5d32cf172d6adb7bfd54ebb7f7b0428bb94f1d
|
||||
size 115114
|
||||
oid sha256:446e2ec1437988533e82ab70b91b57cabf1d3e43be62bdc39014325b2ebc2f26
|
||||
size 115963
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7a370935dd82dde19d887038b076f155273e0dfaa3af6c9fa33bcfb97b825eb6
|
||||
size 169111
|
||||
oid sha256:e8872122c06b940ed87c5f191b5a13c61b9fe9e05aa0e16013d2e21932d3cf13
|
||||
size 168982
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:dc4a8c344dd80070277035e811b84b31ae9050d4f11bb22e507e0cf402cc5e56
|
||||
size 171658
|
||||
oid sha256:844522141b319587c5179dc67202e97fdb9dba1e1fc8ce8358787b0dd38e63ce
|
||||
size 171543
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e80577c2f9a41da888e211e5948169c7b8449f6da79403f2787f98e4f9532819
|
||||
size 122129
|
||||
oid sha256:95af9a204ad87a1aa1aa722623627395e81c8414023e28a67a40b92689e077aa
|
||||
size 121979
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9c1a4f312dabc8e0f7ca1f4555cc0fb9212845c40d54b104aaa8e6df0a1b1a1e
|
||||
size 90593
|
||||
oid sha256:bf58b16186bdaf165064a675a75a33a66a3ddd626c715e29bfd534844e209c1d
|
||||
size 90332
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0677000cc125bb1c3aaaf02b0236939085c4ad2d1f34e653f2b81dfaafb98ab7
|
||||
size 115608
|
||||
oid sha256:023baadc85550005385b8fd770a55bc2f082d52514837385855184758dbcdcf5
|
||||
size 116212
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:929b6e0abf913cc4598dedd2faef6845b73697d163480dab3339f88d90e43970
|
||||
size 101082
|
||||
oid sha256:50f9b885cf0c41930c1137c1e6ffb0517d0eca6e6d67a32f6cca41be3896ed67
|
||||
size 109633
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:52c8c00c02ba4da3ea07981704182f02e9e3981befd6f7c7ade1b51f7ce3187b
|
||||
size 151905
|
||||
oid sha256:316c358d1c29ba6543cef3fe17a496120f58fc8c1f67f44eedfe6cbb0c842cf7
|
||||
size 151665
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a28aa63ec2b888633e8898bb7c8d91ae784da1569d3faa45f29a209d6dea5354
|
||||
size 151771
|
||||
oid sha256:3a620a0f0aa381737f5c290aedd2a49316c0280701d2fd44402b3eccef00c7c5
|
||||
size 154154
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:add279f6f7f8f9d15941d2b9b3c1046165bb7f5df30d964a50763a1fbbc59c05
|
||||
size 165889
|
||||
oid sha256:5587816cfd6aeaf9d6e7eccf45174292c3f32d7ac7ea0a81553103c50aba079c
|
||||
size 167343
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:31791083395412c78e885160659df1ba7ba1a0cc2bc927fdc7e61b09f1cb6f6c
|
||||
size 169888
|
||||
oid sha256:4942a438cf706b6c69ba033c09d43b266940ecd1a39a155edb8e65cdd39939a6
|
||||
size 171248
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:219bdb41bad2e50060ffb9b9052626ac77763a4e80e3f0a7e90e0f8a881f6f71
|
||||
size 158742
|
||||
oid sha256:2362b200a07dedfc74adcfcec55d20a8c6605b4bcc5ee3a726030c93d7a1728d
|
||||
size 158371
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:08ec2c9755ee86d41b9825cfb4ad031e480ac8aacc3591e1a145e27344c831ee
|
||||
size 106500
|
||||
oid sha256:fbcfc6944b4c32583168276b6d64f68618dde1de251c772156918aa7b6644594
|
||||
size 108453
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:feb2d3126893d206fe486e22b0277f8cdf3e69fc1e748c1386f0f733189595b6
|
||||
size 142603
|
||||
oid sha256:e2d8a909c8ea5d9877ac951bd56334481f924b9a8ce1a495f7352164f1815d2a
|
||||
size 145566
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f39c68000391860a8260c6faeb29954456d3f10f8cacef51ed1f5850ec7b85e1
|
||||
size 126031
|
||||
oid sha256:61f029120bf78b803acd6634c016daad77a70d49803e0a628a2621a6a0d2fe9b
|
||||
size 133059
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e61714b5faf17d54f5e1053fb6d4062dae065caa59987fb6a35a66652dabc317
|
||||
size 114745
|
||||
oid sha256:1f28e2dcc9e240a607968f6d701d4ca7e8770e49edd3a6ede7e6c7fbb27a62ab
|
||||
size 114594
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e66ec0e654b0c209b675142bd4acec0220b65d088822c35c14521ab57cec0b46
|
||||
size 114579
|
||||
oid sha256:8ba414a888dc1d6d28e5fb27a949493ab2a6dcc1e73216e6765bc5f4bd59bc45
|
||||
size 115342
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e2771a646e6581abab1c86c79ae463c817cc61f8eef45e23dbccf856fa69286c
|
||||
size 168402
|
||||
oid sha256:e070d58f45db27aeb9fec478c5a1651fc03d6e6bf43abca1618093e06123e642
|
||||
size 168248
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:11073c8e77147d476713c92ae2209dde2593bd1e52f8444790eaca040d978f07
|
||||
size 171000
|
||||
oid sha256:44ad27e09e5b13ecb849a54fdfb38bd27c0a4869a9537178aa2c09ce343e0cd9
|
||||
size 170880
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bb1983b49b4ebd61e4717dcd8a500307839d3e4a96d24afb4e37f236c083da10
|
||||
size 121992
|
||||
oid sha256:c157347a9e3a790911d247451103146cbee0e341c798e1cc2d8b5938baaa806e
|
||||
size 121841
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:999309a2b2227b3b5bc2c471fc60b7de0ff060bbffc60e18493651d31ccc2e2e
|
||||
size 86938
|
||||
oid sha256:5781e2acf57989a2091b6bcc1287a76966ab6b5ecea800b9e096338b31b60704
|
||||
size 87190
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9892c8a4cb57e9d678204a0995f048cc9810c03bece188ee69504f0b81478e23
|
||||
size 112193
|
||||
oid sha256:8da611272e84a28cc3a02b3ffdda36703a6ad8c9927ad9bd5bc412b95018bb16
|
||||
size 112731
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b84b32cd357de819895541fb6ed10c562649e9f3596108151cd93e4d815977d5
|
||||
size 101777
|
||||
oid sha256:30ff9c1d2ba944923c4ffa52b258263b03c57243c226b6916220fb532ff56111
|
||||
size 110398
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9e910c1fb0e2e449b999f08c0e38f0fc1228506e903d22d53057687a3e1c6125
|
||||
size 148744
|
||||
oid sha256:64769e5b69982d2d83e2bbdfffb83f1d73079f077c8ee1c5b68163fc659d53f4
|
||||
size 150686
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:dc190d5cda1f47929fb14fec12ff81a330d66e8ce4bca7846d896bc598410918
|
||||
size 150772
|
||||
oid sha256:be4d36138ecb1fdb0ce374d92827354851e53943e6a1bbdf62f4bf87ba755930
|
||||
size 153234
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b0668c40ede3181a2ff1091e4572908ed61549ca0fca4aee29b2f7ce4b5323bb
|
||||
size 164888
|
||||
oid sha256:71f01217f1d90a9a181047d51e373fbdd5411efa51578c9dcd156c573da7646c
|
||||
size 166342
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5a5ee287e5f6d416e57105c6418f8c5346153af63027a7fb9f1443b8fe944a39
|
||||
size 168860
|
||||
oid sha256:72b1f8b6fca4375729f62d47c5b903f07419449cbbcbe9f77318b6ef9195a4a6
|
||||
size 170255
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8e01515371cf24c0c527c2728d1a15090ff7162faa22b7d2dacfad15818b6aee
|
||||
size 158582
|
||||
oid sha256:3385b4006707085ccb922e443e5e84ee0b1ff4d9cee90fcaf8e369dcd37566b2
|
||||
size 158211
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0065cc98273a41f0782f6f556187f8f3519db3063e0dc147edcdd37cad38dc6f
|
||||
size 103310
|
||||
oid sha256:7bedda0ec007954c232d31713a9e6eb6adae2b561d2a856dede57e68c62dae68
|
||||
size 104960
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f9a7db7bb77d8d87d185e8ed22b4f12be4a066678b0fa0f95200eb55111c0ab9
|
||||
size 139278
|
||||
oid sha256:59a7327e8b127222fcf1cef19094af0cc10446d5e5d6de09cf4c0765ffa3c83e
|
||||
size 142239
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:63a0ed106d7a0cbe662657f8e55e9a8b3635e847d3c48e00fc8bb1bedb7f675d
|
||||
size 123587
|
||||
oid sha256:8298b57d94db8a9776db4051364722b82b04421bdfa920bccb0bbb2cb912fd2a
|
||||
size 133085
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Fix the background colour of the room members screen in dark mode.
|
||||
@@ -0,0 +1 @@
|
||||
Make sure forms have pressed states, remove incorrect disclosure indicators, stop login screen placeholders from flickering and don't block the loging screen when parsing a username.
|
||||
@@ -0,0 +1 @@
|
||||
Rename SettingsRow… to Form…Style and use these everywhere (sparingly on the Bug Report Screen which isn't a real form).
|
||||
Reference in New Issue
Block a user