mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-30 05:06:58 +02:00
Merge branch 'gil/SP1_space_creation' into gil/5231_SP3-1_Update_room_settings_for_Spaces
# Conflicts: # Podfile.lock
This commit is contained in:
@@ -42,18 +42,13 @@ struct AnalyticsPromptViewState: BindableState {
|
||||
/// A collection of strings for the UI that need to be created in
|
||||
/// the coordinator or mocked in the RiotSwiftUI target.
|
||||
protocol AnalyticsPromptStringsProtocol {
|
||||
var appDisplayName: String { get }
|
||||
|
||||
var point1: NSAttributedString { get }
|
||||
var point2: NSAttributedString { get }
|
||||
|
||||
var termsNewUser: NSAttributedString { get }
|
||||
var termsUpgrade: NSAttributedString { get }
|
||||
}
|
||||
|
||||
enum AnalyticsPromptType {
|
||||
case newUser(termsString: NSAttributedString)
|
||||
case upgrade(termsString: NSAttributedString)
|
||||
case newUser
|
||||
case upgrade
|
||||
}
|
||||
|
||||
extension AnalyticsPromptType {
|
||||
@@ -67,11 +62,23 @@ extension AnalyticsPromptType {
|
||||
}
|
||||
}
|
||||
|
||||
/// The terms string that should be displayed.
|
||||
var termsStrings: NSAttributedString {
|
||||
/// The main part of the terms string that should be displayed.
|
||||
var mainTermsString: String {
|
||||
switch self {
|
||||
case .newUser(let termsString), .upgrade(let termsString):
|
||||
return termsString
|
||||
case .newUser:
|
||||
return VectorL10n.analyticsPromptTermsNewUser("%@")
|
||||
case .upgrade:
|
||||
return VectorL10n.analyticsPromptTermsUpgrade("%@")
|
||||
}
|
||||
}
|
||||
|
||||
/// The tappable part of the terms string that should be displayed.
|
||||
var termsLinkString: String {
|
||||
switch self {
|
||||
case .newUser:
|
||||
return VectorL10n.analyticsPromptTermsLinkNewUser
|
||||
case .upgrade:
|
||||
return VectorL10n.analyticsPromptTermsLinkUpgrade
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,15 +103,7 @@ extension AnalyticsPromptType {
|
||||
}
|
||||
}
|
||||
|
||||
extension AnalyticsPromptType: CaseIterable {
|
||||
static var allCases: [AnalyticsPromptType] {
|
||||
let strings = MockAnalyticsPromptStrings()
|
||||
return [
|
||||
.newUser(termsString: strings.termsNewUser),
|
||||
.upgrade(termsString: strings.termsUpgrade)
|
||||
]
|
||||
}
|
||||
}
|
||||
extension AnalyticsPromptType: CaseIterable { }
|
||||
|
||||
extension AnalyticsPromptType: Identifiable {
|
||||
var id: String {
|
||||
|
||||
@@ -52,9 +52,9 @@ final class AnalyticsPromptCoordinator: Coordinator, Presentable {
|
||||
let promptType: AnalyticsPromptType
|
||||
|
||||
if Analytics.shared.promptShouldDisplayUpgradeMessage {
|
||||
promptType = .upgrade(termsString: strings.termsUpgrade)
|
||||
promptType = .upgrade
|
||||
} else {
|
||||
promptType = .newUser(termsString: strings.termsNewUser)
|
||||
promptType = .newUser
|
||||
}
|
||||
|
||||
let viewModel = AnalyticsPromptViewModel(promptType: promptType, strings: strings, termsURL: BuildSettings.analyticsTermsURL)
|
||||
|
||||
@@ -18,16 +18,7 @@ import Foundation
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
struct AnalyticsPromptStrings: AnalyticsPromptStringsProtocol {
|
||||
let appDisplayName = AppInfo.current.displayName
|
||||
|
||||
let point1 = HTMLFormatter().formatHTML(VectorL10n.analyticsPromptPoint1, withAllowedTags: ["b", "p"], fontSize: UIFont.systemFontSize)
|
||||
let point2 = HTMLFormatter().formatHTML(VectorL10n.analyticsPromptPoint2, withAllowedTags: ["b", "p"], fontSize: UIFont.systemFontSize)
|
||||
|
||||
let termsNewUser = HTMLFormatter().format(VectorL10n.analyticsPromptTermsNewUser("%@"),
|
||||
with: VectorL10n.analyticsPromptTermsLinkNewUser,
|
||||
using: BuildSettings.analyticsTermsURL)
|
||||
let termsUpgrade = HTMLFormatter().format(VectorL10n.analyticsPromptTermsUpgrade("%@"),
|
||||
with: VectorL10n.analyticsPromptTermsLinkUpgrade,
|
||||
using: BuildSettings.analyticsTermsURL)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,14 +17,9 @@
|
||||
import UIKit
|
||||
|
||||
struct MockAnalyticsPromptStrings: AnalyticsPromptStringsProtocol {
|
||||
var appDisplayName = "Element"
|
||||
|
||||
let point1: NSAttributedString
|
||||
let point2: NSAttributedString
|
||||
|
||||
let termsNewUser: NSAttributedString
|
||||
let termsUpgrade: NSAttributedString
|
||||
|
||||
let shortString = NSAttributedString(string: "This is a short string.")
|
||||
let longString = NSAttributedString(string: "This is a very long string that will be used to test the layout over multiple lines of text to ensure everything is correct.")
|
||||
|
||||
@@ -38,15 +33,5 @@ struct MockAnalyticsPromptStrings: AnalyticsPromptStringsProtocol {
|
||||
point2.append(NSAttributedString(string: "don't", attributes: [.font: UIFont.boldSystemFont(ofSize: UIFont.systemFontSize)]))
|
||||
point2.append(NSAttributedString(string: " share information with third parties"))
|
||||
self.point2 = point2
|
||||
|
||||
let termsNewUser = NSMutableAttributedString(string: "You can read all our terms ")
|
||||
termsNewUser.append(NSAttributedString(string: "here", attributes: [.link: URL(string: "https://element.io/cookie-policy")!]))
|
||||
termsNewUser.append(NSAttributedString(string: "."))
|
||||
self.termsNewUser = termsNewUser
|
||||
|
||||
let termsUpgrade = NSMutableAttributedString(string: "Read all our terms ")
|
||||
termsUpgrade.append(NSAttributedString(string: "here", attributes: [.link: URL(string: "https://element.io/cookie-policy")!]))
|
||||
termsUpgrade.append(NSAttributedString(string: ". Is that OK?"))
|
||||
self.termsUpgrade = termsUpgrade
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,12 +42,10 @@ struct AnalyticsPrompt: View {
|
||||
VStack {
|
||||
Text("\(viewModel.viewState.promptType.message)\n")
|
||||
|
||||
AnalyticsPromptTermsText(attributedString: viewModel.viewState.promptType.termsStrings)
|
||||
.accessibilityLabel(Text(viewModel.viewState.promptType.termsStrings.string))
|
||||
.accessibilityValue(Text(VectorL10n.accessibilityButtonLabel))
|
||||
.onTapGesture {
|
||||
viewModel.send(viewAction: .openTermsURL)
|
||||
}
|
||||
InlineTextButton(viewModel.viewState.promptType.mainTermsString,
|
||||
tappableText: viewModel.viewState.promptType.termsLinkString) {
|
||||
viewModel.send(viewAction: .openTermsURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +69,7 @@ struct AnalyticsPrompt: View {
|
||||
Image(uiImage: Asset.Images.analyticsLogo.image)
|
||||
.padding(.bottom, 25)
|
||||
|
||||
Text(VectorL10n.analyticsPromptTitle(viewModel.viewState.strings.appDisplayName))
|
||||
Text(VectorL10n.analyticsPromptTitle(AppInfo.current.displayName))
|
||||
.font(theme.fonts.title2B)
|
||||
.foregroundColor(theme.colors.primaryContent)
|
||||
.padding(.bottom, 2)
|
||||
@@ -125,6 +123,7 @@ struct AnalyticsPrompt: View {
|
||||
.padding(.bottom, geometry.safeAreaInsets.bottom > 0 ? 0 : 16)
|
||||
}
|
||||
.background(theme.colors.background.ignoresSafeArea())
|
||||
.accentColor(theme.colors.accent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
//
|
||||
// Copyright 2021 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
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
/// The last line of text in the description with highlighting on the link string.
|
||||
struct AnalyticsPromptTermsText: View {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@Environment(\.theme) private var theme
|
||||
|
||||
/// A string with a link attribute.
|
||||
private struct StringComponent {
|
||||
let string: String
|
||||
let isLink: Bool
|
||||
}
|
||||
|
||||
/// Internal representation of the string as composable parts.
|
||||
private let components: [StringComponent]
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(attributedString: NSAttributedString) {
|
||||
var components = [StringComponent]()
|
||||
let range = NSRange(location: 0, length: attributedString.length)
|
||||
let string = attributedString.string as NSString
|
||||
|
||||
attributedString.enumerateAttributes(in: range, options: []) { attributes, range, stop in
|
||||
let isLink = attributes.keys.contains(.link)
|
||||
components.append(StringComponent(string: string.substring(with: range), isLink: isLink))
|
||||
}
|
||||
|
||||
self.components = components
|
||||
}
|
||||
|
||||
// MARK: - Views
|
||||
|
||||
var body: some View {
|
||||
components.reduce(Text("")) {
|
||||
$0 + Text($1.string).foregroundColor($1.isLink ? theme.colors.accent : nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
@available(iOS 14.0, *)
|
||||
struct AnalyticsPromptTermsText_Previews: PreviewProvider {
|
||||
|
||||
static let strings = MockAnalyticsPromptStrings()
|
||||
|
||||
static var previews: some View {
|
||||
VStack(spacing: 8) {
|
||||
AnalyticsPromptTermsText(attributedString: strings.termsNewUser)
|
||||
AnalyticsPromptTermsText(attributedString: strings.termsUpgrade)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import Foundation
|
||||
@available(iOS 14.0, *)
|
||||
enum MockAppScreens {
|
||||
static let appScreens: [MockScreenState.Type] = [
|
||||
MockOnboardingUseCaseSelectionScreenState.self,
|
||||
MockOnboardingSplashScreenScreenState.self,
|
||||
MockLocationSharingScreenState.self,
|
||||
MockAnalyticsPromptScreenState.self,
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// Copyright 2021 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
|
||||
|
||||
@available(iOS, introduced: 14.0, deprecated: 15.0, message: "Use Text with an AttributedString instead that includes a link and handle the tap by adding an OpenURLAction to the environment.")
|
||||
/// A `Button`, that fakes having a tappable string inside of a regular string.
|
||||
struct InlineTextButton: View {
|
||||
|
||||
private struct StringComponent {
|
||||
let string: Substring
|
||||
let isTinted: Bool
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
/// The individual components of the string.
|
||||
private let components: [StringComponent]
|
||||
private let action: () -> Void
|
||||
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
/// Creates a new `InlineTextButton`.
|
||||
/// - Parameters:
|
||||
/// - mainText: The main text that shouldn't appear tappable. This must contain a single `%@` placeholder somewhere within.
|
||||
/// - tappableText: The tappable text that will be substituted into the `%@` placeholder.
|
||||
/// - action: The action to perform when tapping the button.
|
||||
internal init(_ mainText: String, tappableText: String, action: @escaping () -> Void) {
|
||||
guard let range = mainText.range(of: "%@") else {
|
||||
self.components = [StringComponent(string: Substring(mainText), isTinted: false)]
|
||||
self.action = action
|
||||
return
|
||||
}
|
||||
|
||||
let firstComponent = StringComponent(string: mainText[..<range.lowerBound], isTinted: false)
|
||||
let middleComponent = StringComponent(string: Substring(tappableText), isTinted: true)
|
||||
let lastComponent = StringComponent(string: mainText[range.upperBound...], isTinted: false)
|
||||
|
||||
self.components = [firstComponent, middleComponent, lastComponent]
|
||||
self.action = action
|
||||
}
|
||||
|
||||
// MARK: - Views
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
EmptyView()
|
||||
}
|
||||
.buttonStyle(Style(components: components))
|
||||
.accessibilityLabel(components.map { $0.string }.joined())
|
||||
}
|
||||
|
||||
private struct Style: ButtonStyle {
|
||||
let components: [StringComponent]
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
components.reduce(Text("")) { lastValue, component in
|
||||
lastValue + Text(component.string)
|
||||
.foregroundColor(component.isTinted ? .accentColor.opacity(configuration.isPressed ? 0.2 : 1) : nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
struct Previews_InlineButtonText_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
InlineTextButton("Hello there this is a sentence. %@.",
|
||||
tappableText: "And this is a button",
|
||||
action: { })
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
//
|
||||
// Copyright 2021 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
|
||||
import DesignKit
|
||||
|
||||
@available(iOS, introduced: 14.0, deprecated: 15.0, message: "Use Text with an AttributedString instead.")
|
||||
/// A `Text` view that renders attributed strings with their `.font` and `.foregroundColor` attributes.
|
||||
/// This view is a workaround for iOS 13/14 not supporting `AttributedString`.
|
||||
struct StyledText: View {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@Environment(\.theme) private var theme
|
||||
|
||||
/// A string with a bold property.
|
||||
private struct StringComponent {
|
||||
let string: String
|
||||
var font: Font? = nil
|
||||
var color: Color? = nil
|
||||
}
|
||||
|
||||
/// Internal representation of the string as composable parts.
|
||||
private let components: [StringComponent]
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
/// Creates a `StyledText` using the supplied attributed string.
|
||||
/// - Parameter attributedString: The attributed string to display.
|
||||
init(_ attributedString: NSAttributedString) {
|
||||
var components = [StringComponent]()
|
||||
let range = NSRange(location: 0, length: attributedString.length)
|
||||
let string = attributedString.string as NSString
|
||||
|
||||
attributedString.enumerateAttributes(in: range, options: []) { attributes, range, stop in
|
||||
let font = attributes[.font] as? UIFont
|
||||
let color = attributes[.foregroundColor] as? UIColor
|
||||
|
||||
let component = StringComponent(
|
||||
string: string.substring(with: range),
|
||||
font: font.map { Font($0) },
|
||||
color: color.map { Color($0) }
|
||||
)
|
||||
|
||||
components.append(component)
|
||||
}
|
||||
|
||||
self.components = components
|
||||
}
|
||||
|
||||
/// Creates a `StyledText` using a plain string.
|
||||
/// - Parameter string: The plain string to display
|
||||
init(_ string: String) {
|
||||
self.components = [StringComponent(string: string, font: nil)]
|
||||
}
|
||||
|
||||
// MARK: - Views
|
||||
|
||||
var body: some View {
|
||||
components.reduce(Text("")) { lastValue, component in
|
||||
lastValue + Text(component.string)
|
||||
.font(component.font)
|
||||
.foregroundColor(component.color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
struct StyledText_Previews: PreviewProvider {
|
||||
static func prettyText() -> NSAttributedString {
|
||||
let string = NSMutableAttributedString(string: "T", attributes: [
|
||||
.font: UIFont.boldSystemFont(ofSize: 12),
|
||||
.foregroundColor: UIColor.red
|
||||
])
|
||||
string.append(NSAttributedString(string: "e", attributes: [
|
||||
.font: UIFont.boldSystemFont(ofSize: 14),
|
||||
.foregroundColor: UIColor.orange
|
||||
]))
|
||||
string.append(NSAttributedString(string: "s", attributes: [
|
||||
.font: UIFont.boldSystemFont(ofSize: 13),
|
||||
.foregroundColor: UIColor.yellow
|
||||
]))
|
||||
string.append(NSAttributedString(string: "t", attributes: [
|
||||
.font: UIFont.boldSystemFont(ofSize: 15),
|
||||
.foregroundColor: UIColor.green
|
||||
]))
|
||||
string.append(NSAttributedString(string: "i", attributes: [
|
||||
.font: UIFont.boldSystemFont(ofSize: 11),
|
||||
.foregroundColor: UIColor.cyan
|
||||
]))
|
||||
string.append(NSAttributedString(string: "n", attributes: [
|
||||
.font: UIFont.boldSystemFont(ofSize: 16),
|
||||
.foregroundColor: UIColor.blue
|
||||
]))
|
||||
string.append(NSAttributedString(string: "g", attributes: [
|
||||
.font: UIFont.boldSystemFont(ofSize: 14),
|
||||
.foregroundColor: UIColor.purple
|
||||
]))
|
||||
return string
|
||||
}
|
||||
|
||||
static var previews: some View {
|
||||
VStack(spacing: 8) {
|
||||
StyledText("Hello, World!")
|
||||
StyledText(NSAttributedString(string: "Testing",
|
||||
attributes: [.font: UIFont.boldSystemFont(ofSize: 64)]))
|
||||
StyledText(prettyText())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// Copyright 2021 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
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
struct OnboardingButtonStyle: ButtonStyle {
|
||||
@Environment(\.theme) private var theme
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.stroke(configuration.isPressed ? theme.colors.accent : theme.colors.quinaryContent, lineWidth: configuration.isPressed ? 2 : 1.5)
|
||||
)
|
||||
.contentShape(RoundedRectangle(cornerRadius: 8))
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,7 @@ struct OnboardingSplashScreenPage: View {
|
||||
.scaledToFit()
|
||||
.frame(maxWidth: 300)
|
||||
.padding(20)
|
||||
.accessibilityHidden(true)
|
||||
|
||||
VStack(spacing: 8) {
|
||||
OnboardingSplashScreenTitleText(content.title)
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// Copyright 2021 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
|
||||
|
||||
final class OnboardingUseCaseSelectionCoordinator: Coordinator, Presentable {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let onboardingUseCaseHostingController: UIViewController
|
||||
private var onboardingUseCaseViewModel: OnboardingUseCaseViewModelProtocol
|
||||
|
||||
// MARK: Public
|
||||
|
||||
// Must be used only internally
|
||||
var childCoordinators: [Coordinator] = []
|
||||
var completion: ((OnboardingUseCaseViewModelResult) -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
init() {
|
||||
let viewModel = OnboardingUseCaseViewModel()
|
||||
let view = OnboardingUseCaseSelectionScreen(viewModel: viewModel.context)
|
||||
onboardingUseCaseViewModel = viewModel
|
||||
|
||||
let hostingController = VectorHostingController(rootView: view)
|
||||
hostingController.vc_removeBackTitle()
|
||||
hostingController.enableNavigationBarScrollEdgesAppearance = true
|
||||
onboardingUseCaseHostingController = hostingController
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
func start() {
|
||||
MXLog.debug("[OnboardingUseCaseSelectionCoordinator] did start.")
|
||||
onboardingUseCaseViewModel.completion = { [weak self] result in
|
||||
MXLog.debug("[OnboardingUseCaseSelectionCoordinator] OnboardingUseCaseViewModel did complete with result: \(result).")
|
||||
guard let self = self else { return }
|
||||
self.completion?(result)
|
||||
}
|
||||
}
|
||||
|
||||
func toPresentable() -> UIViewController {
|
||||
return self.onboardingUseCaseHostingController
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// Copyright 2021 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 Foundation
|
||||
import SwiftUI
|
||||
|
||||
/// Using an enum for the screen allows you define the different state cases with
|
||||
/// the relevant associated data for each case.
|
||||
@available(iOS 14.0, *)
|
||||
enum MockOnboardingUseCaseSelectionScreenState: MockScreenState, CaseIterable {
|
||||
// A case for each state you want to represent
|
||||
// with specific, minimal associated data that will allow you
|
||||
// mock that screen.
|
||||
case `default`
|
||||
|
||||
/// The associated screen
|
||||
var screenType: Any.Type {
|
||||
OnboardingUseCaseSelectionScreen.self
|
||||
}
|
||||
|
||||
/// A list of screen state definitions
|
||||
static var allCases: [MockOnboardingUseCaseSelectionScreenState] {
|
||||
// Each of the presence statuses
|
||||
[.default]
|
||||
}
|
||||
|
||||
/// Generate the view struct for the screen state.
|
||||
var screenView: ([Any], AnyView) {
|
||||
let viewModel = OnboardingUseCaseViewModel()
|
||||
|
||||
// can simulate service and viewModel actions here if needs be.
|
||||
|
||||
return (
|
||||
[self, viewModel],
|
||||
AnyView(OnboardingUseCaseSelectionScreen(viewModel: viewModel.context)
|
||||
.addDependency(MockAvatarService.example))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// Copyright 2021 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 Foundation
|
||||
|
||||
// MARK: - Coordinator
|
||||
|
||||
// MARK: View model
|
||||
|
||||
enum OnboardingUseCaseStateAction {
|
||||
case viewAction(OnboardingUseCaseViewAction)
|
||||
}
|
||||
|
||||
enum OnboardingUseCaseViewModelResult {
|
||||
case personalMessaging
|
||||
case workMessaging
|
||||
case communityMessaging
|
||||
case skipped
|
||||
case customServer
|
||||
}
|
||||
|
||||
// MARK: View
|
||||
|
||||
struct OnboardingUseCaseViewState: BindableState { }
|
||||
|
||||
enum OnboardingUseCaseViewAction {
|
||||
case answer(OnboardingUseCaseViewModelResult)
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// Copyright 2021 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
|
||||
|
||||
@available(iOS 14, *)
|
||||
typealias OnboardingUseCaseViewModelType = StateStoreViewModel<OnboardingUseCaseViewState,
|
||||
OnboardingUseCaseStateAction,
|
||||
OnboardingUseCaseViewAction>
|
||||
@available(iOS 14, *)
|
||||
class OnboardingUseCaseViewModel: OnboardingUseCaseViewModelType, OnboardingUseCaseViewModelProtocol {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var completion: ((OnboardingUseCaseViewModelResult) -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init() {
|
||||
super.init(initialViewState: OnboardingUseCaseViewState())
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: OnboardingUseCaseViewAction) {
|
||||
switch viewAction {
|
||||
case .answer(let result):
|
||||
completion?(result)
|
||||
}
|
||||
}
|
||||
|
||||
override class func reducer(state: inout OnboardingUseCaseViewState, action: OnboardingUseCaseStateAction) {
|
||||
// There is no mutable state to reduce :)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// Copyright 2021 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 Foundation
|
||||
|
||||
protocol OnboardingUseCaseViewModelProtocol {
|
||||
|
||||
var completion: ((OnboardingUseCaseViewModelResult) -> Void)? { get set }
|
||||
@available(iOS 14, *)
|
||||
var context: OnboardingUseCaseViewModelType.Context { get }
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// Copyright 2021 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 XCTest
|
||||
import RiotSwiftUI
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
class OnboardingUseCaseUITests: MockScreenTest {
|
||||
// The view has no parameters or changing state to test.
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// Copyright 2021 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 XCTest
|
||||
|
||||
@testable import RiotSwiftUI
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
class OnboardingUseCaseViewModelTests: XCTestCase {
|
||||
// The view model has nothing to test.
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// Copyright 2021 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
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
/// A button used for the Use Case selection.
|
||||
struct OnboardingUseCaseButton: View {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@Environment(\.theme) private var theme
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/// The button's title.
|
||||
let title: String
|
||||
/// The button's image.
|
||||
let image: ImageAsset
|
||||
|
||||
/// The button's action when tapped.
|
||||
let action: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
HStack(spacing: 16) {
|
||||
Image(image.name)
|
||||
Text(title)
|
||||
.font(theme.fonts.bodySB)
|
||||
.foregroundColor(theme.colors.primaryContent)
|
||||
}
|
||||
.padding(16)
|
||||
}
|
||||
.buttonStyle(OnboardingButtonStyle())
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
struct Previews_OnboardingUseCaseButton_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
OnboardingUseCaseButton(title: VectorL10n.onboardingUseCaseWorkMessaging,
|
||||
image: Asset.Images.onboardingUseCaseWork,
|
||||
action: { })
|
||||
.padding(16)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
//
|
||||
// Copyright 2021 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
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
/// The screen shown to a new user to select their use case for the app.
|
||||
struct OnboardingUseCaseSelectionScreen: View {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@Environment(\.theme) private var theme
|
||||
|
||||
// MARK: Public
|
||||
|
||||
@ObservedObject var viewModel: OnboardingUseCaseViewModel.Context
|
||||
|
||||
/// The screen's title and instructions.
|
||||
var titleContent: some View {
|
||||
VStack(spacing: 8) {
|
||||
Image(Asset.Images.onboardingUseCaseIcon.name)
|
||||
.padding(.bottom, 8)
|
||||
.accessibilityHidden(true)
|
||||
|
||||
Text(VectorL10n.onboardingUseCaseTitle)
|
||||
.font(theme.fonts.title2B)
|
||||
.foregroundColor(theme.colors.primaryContent)
|
||||
|
||||
Text(VectorL10n.onboardingUseCaseMessage)
|
||||
.font(theme.fonts.body)
|
||||
.foregroundColor(theme.colors.secondaryContent)
|
||||
}
|
||||
}
|
||||
|
||||
/// The buttons used to select a use case for the app.
|
||||
var useCaseButtons: some View {
|
||||
VStack(spacing: 8) {
|
||||
OnboardingUseCaseButton(title: VectorL10n.onboardingUseCasePersonalMessaging,
|
||||
image: theme.isDark ? Asset.Images.onboardingUseCasePersonalDark : Asset.Images.onboardingUseCasePersonal) {
|
||||
viewModel.send(viewAction: .answer(.personalMessaging))
|
||||
}
|
||||
|
||||
OnboardingUseCaseButton(title: VectorL10n.onboardingUseCaseWorkMessaging,
|
||||
image: theme.isDark ? Asset.Images.onboardingUseCaseWorkDark : Asset.Images.onboardingUseCaseWork) {
|
||||
viewModel.send(viewAction: .answer(.workMessaging))
|
||||
}
|
||||
|
||||
OnboardingUseCaseButton(title: VectorL10n.onboardingUseCaseCommunityMessaging,
|
||||
image: theme.isDark ? Asset.Images.onboardingUseCaseCommunityDark : Asset.Images.onboardingUseCaseCommunity) {
|
||||
viewModel.send(viewAction: .answer(.communityMessaging))
|
||||
}
|
||||
|
||||
InlineTextButton(VectorL10n.onboardingUseCaseNotSureYet("%@"),
|
||||
tappableText: VectorL10n.onboardingUseCaseSkipButton) {
|
||||
viewModel.send(viewAction: .answer(.skipped))
|
||||
}
|
||||
.font(theme.fonts.subheadline)
|
||||
.foregroundColor(theme.colors.tertiaryContent)
|
||||
.padding(.top, 8)
|
||||
}
|
||||
}
|
||||
|
||||
/// A footer showing a button to connect to a server.
|
||||
var serverFooter: some View {
|
||||
VStack(spacing: 14) {
|
||||
Text(VectorL10n.onboardingUseCaseExistingServerMessage)
|
||||
.font(theme.fonts.subheadline)
|
||||
.foregroundColor(theme.colors.tertiaryContent)
|
||||
|
||||
Button { viewModel.send(viewAction: .answer(.customServer)) } label: {
|
||||
Text(VectorL10n.onboardingUseCaseExistingServerButton)
|
||||
.font(theme.fonts.body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
VStack {
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
titleContent
|
||||
.padding(.bottom, 36)
|
||||
|
||||
useCaseButtons
|
||||
}
|
||||
.frame(maxWidth: OnboardingConstants.maxContentWidth,
|
||||
maxHeight: OnboardingConstants.maxContentHeight)
|
||||
.padding(16)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
serverFooter
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.bottom, geometry.safeAreaInsets.bottom > 0 ? 20 : 36)
|
||||
}
|
||||
}
|
||||
.background(theme.colors.background.ignoresSafeArea())
|
||||
.accentColor(theme.colors.accent)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
struct OnboardingUseCase_Previews: PreviewProvider {
|
||||
static let stateRenderer = MockOnboardingUseCaseSelectionScreenState.stateRenderer
|
||||
static var previews: some View {
|
||||
NavigationView {
|
||||
stateRenderer.screenGroup()
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -97,7 +97,8 @@ final class LocationSharingCoordinator: Coordinator, Presentable {
|
||||
static func shareLocationActivityController(_ location: CLLocationCoordinate2D) -> UIActivityViewController {
|
||||
return UIActivityViewController(activityItems: [ShareToMapsAppActivity.urlForMapsAppType(.apple, location: location)],
|
||||
applicationActivities: [ShareToMapsAppActivity(type: .apple, location: location),
|
||||
ShareToMapsAppActivity(type: .google, location: location)])
|
||||
ShareToMapsAppActivity(type: .google, location: location),
|
||||
ShareToMapsAppActivity(type: .osm, location: location)])
|
||||
}
|
||||
|
||||
// MARK: - Presentable
|
||||
|
||||
@@ -24,6 +24,7 @@ class ShareToMapsAppActivity: UIActivity {
|
||||
enum MapsAppType {
|
||||
case apple
|
||||
case google
|
||||
case osm
|
||||
}
|
||||
|
||||
private let type: MapsAppType
|
||||
@@ -44,6 +45,8 @@ class ShareToMapsAppActivity: UIActivity {
|
||||
return URL(string: "https://maps.apple.com?ll=\(location.latitude),\(location.longitude)&q=Pin")!
|
||||
case .google:
|
||||
return URL(string: "https://www.google.com/maps/search/?api=1&query=\(location.latitude),\(location.longitude)")!
|
||||
case .osm:
|
||||
return URL(string: "https://www.openstreetmap.org/?mlat=\(location.latitude)&mlon=\(location.longitude)")!
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +56,8 @@ class ShareToMapsAppActivity: UIActivity {
|
||||
return VectorL10n.locationSharingOpenAppleMaps
|
||||
case .google:
|
||||
return VectorL10n.locationSharingOpenGoogleMaps
|
||||
case .osm:
|
||||
return VectorL10n.locationSharingOpenOpenStreetMaps
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,66 +32,74 @@ struct LocationSharingView: View {
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
LocationSharingMapView(tileServerMapURL: context.viewState.mapStyleURL,
|
||||
avatarData: context.viewState.avatarData,
|
||||
location: context.viewState.location,
|
||||
errorSubject: context.viewState.errorSubject,
|
||||
userLocation: $context.userLocation)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button(VectorL10n.cancel, action: {
|
||||
context.send(viewAction: .cancel)
|
||||
})
|
||||
}
|
||||
ToolbarItem(placement: .principal) {
|
||||
Text(VectorL10n.locationSharingTitle)
|
||||
.font(.headline)
|
||||
.foregroundColor(theme.colors.primaryContent)
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
if context.viewState.location != nil {
|
||||
Button {
|
||||
context.send(viewAction: .share)
|
||||
} label: {
|
||||
Image(uiImage: Asset.Images.locationShareIcon.image)
|
||||
.accessibilityIdentifier("LocationSharingView.shareButton")
|
||||
}
|
||||
.disabled(!context.viewState.shareButtonEnabled)
|
||||
} else {
|
||||
Button(VectorL10n.locationSharingShareAction, action: {
|
||||
context.send(viewAction: .share)
|
||||
})
|
||||
.disabled(!context.viewState.shareButtonEnabled)
|
||||
ZStack(alignment: .bottom) {
|
||||
LocationSharingMapView(tileServerMapURL: context.viewState.mapStyleURL,
|
||||
avatarData: context.viewState.avatarData,
|
||||
location: context.viewState.location,
|
||||
errorSubject: context.viewState.errorSubject,
|
||||
userLocation: $context.userLocation)
|
||||
.ignoresSafeArea()
|
||||
|
||||
HStack {
|
||||
Link("© MapTiler", destination: URL(string: "https://www.maptiler.com/copyright/")!)
|
||||
Link("© OpenStreetMap contributors", destination: URL(string: "https://www.openstreetmap.org/copyright")!)
|
||||
}
|
||||
.font(theme.fonts.caption1)
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button(VectorL10n.cancel, action: {
|
||||
context.send(viewAction: .cancel)
|
||||
})
|
||||
}
|
||||
ToolbarItem(placement: .principal) {
|
||||
Text(VectorL10n.locationSharingTitle)
|
||||
.font(.headline)
|
||||
.foregroundColor(theme.colors.primaryContent)
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
if context.viewState.location != nil {
|
||||
Button {
|
||||
context.send(viewAction: .share)
|
||||
} label: {
|
||||
Image(uiImage: Asset.Images.locationShareIcon.image)
|
||||
.accessibilityIdentifier("LocationSharingView.shareButton")
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.ignoresSafeArea()
|
||||
.alert(item: $context.alertInfo) { info in
|
||||
if let secondaryButton = info.secondaryButton {
|
||||
return Alert(title: Text(info.title),
|
||||
message: subtitleTextForAlertInfo(info),
|
||||
primaryButton: .default(Text(info.primaryButton.title)) {
|
||||
info.primaryButton.action?()
|
||||
},
|
||||
secondaryButton: .default(Text(secondaryButton.title)) {
|
||||
secondaryButton.action?()
|
||||
})
|
||||
.disabled(!context.viewState.shareButtonEnabled)
|
||||
} else {
|
||||
return Alert(title: Text(info.title),
|
||||
message: subtitleTextForAlertInfo(info),
|
||||
dismissButton: .default(Text(info.primaryButton.title)) {
|
||||
info.primaryButton.action?()
|
||||
})
|
||||
Button(VectorL10n.locationSharingShareAction, action: {
|
||||
context.send(viewAction: .share)
|
||||
})
|
||||
.disabled(!context.viewState.shareButtonEnabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.introspectNavigationController { navigationController in
|
||||
ThemeService.shared().theme.applyStyle(onNavigationBar: navigationController.navigationBar)
|
||||
}
|
||||
.alert(item: $context.alertInfo) { info in
|
||||
if let secondaryButton = info.secondaryButton {
|
||||
return Alert(title: Text(info.title),
|
||||
message: subtitleTextForAlertInfo(info),
|
||||
primaryButton: .default(Text(info.primaryButton.title)) {
|
||||
info.primaryButton.action?()
|
||||
},
|
||||
secondaryButton: .default(Text(secondaryButton.title)) {
|
||||
secondaryButton.action?()
|
||||
})
|
||||
} else {
|
||||
return Alert(title: Text(info.title),
|
||||
message: subtitleTextForAlertInfo(info),
|
||||
dismissButton: .default(Text(info.primaryButton.title)) {
|
||||
info.primaryButton.action?()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
.accentColor(theme.colors.accent)
|
||||
.activityIndicator(show: context.viewState.showLoadingIndicator)
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
.introspectNavigationController { navigationController in
|
||||
ThemeService.shared().theme.applyStyle(onNavigationBar: navigationController.navigationBar)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
|
||||
@@ -146,15 +146,17 @@ final class PollEditFormCoordinator: Coordinator, Presentable {
|
||||
}
|
||||
|
||||
private static func pollDetailsTypeToKindKey(_ type: EditFormPollType) -> String {
|
||||
let mapping = [EditFormPollType.disclosed : kMXMessageContentKeyExtensiblePollKindDisclosed,
|
||||
EditFormPollType.undisclosed : kMXMessageContentKeyExtensiblePollKindUndisclosed]
|
||||
let mapping = [EditFormPollType.disclosed : kMXMessageContentKeyExtensiblePollKindDisclosedMSC3381,
|
||||
EditFormPollType.undisclosed : kMXMessageContentKeyExtensiblePollKindUndisclosedMSC3381]
|
||||
|
||||
return mapping[type] ?? kMXMessageContentKeyExtensiblePollKindDisclosed
|
||||
return mapping[type] ?? kMXMessageContentKeyExtensiblePollKindDisclosedMSC3381
|
||||
}
|
||||
|
||||
private static func pollKindKeyToDetailsType(_ key: String) -> EditFormPollType {
|
||||
let mapping = [kMXMessageContentKeyExtensiblePollKindDisclosed : EditFormPollType.disclosed,
|
||||
kMXMessageContentKeyExtensiblePollKindUndisclosed : EditFormPollType.undisclosed]
|
||||
kMXMessageContentKeyExtensiblePollKindDisclosedMSC3381 : EditFormPollType.disclosed,
|
||||
kMXMessageContentKeyExtensiblePollKindUndisclosed : EditFormPollType.undisclosed,
|
||||
kMXMessageContentKeyExtensiblePollKindUndisclosedMSC3381 : EditFormPollType.undisclosed]
|
||||
|
||||
return mapping[key] ?? EditFormPollType.disclosed
|
||||
}
|
||||
|
||||
@@ -35,8 +35,7 @@ struct PollEditForm: View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 32.0) {
|
||||
|
||||
// Intentionally disabled until platform parity.
|
||||
// PollEditFormTypePicker(selectedType: $viewModel.type)
|
||||
PollEditFormTypePicker(selectedType: $viewModel.type)
|
||||
|
||||
VStack(alignment: .leading, spacing: 16.0) {
|
||||
Text(VectorL10n.pollEditFormPollQuestionOrTopic)
|
||||
@@ -116,14 +115,14 @@ struct PollEditForm: View {
|
||||
}
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.introspectNavigationController { navigationController in
|
||||
ThemeService.shared().theme.applyStyle(onNavigationBar: navigationController.navigationBar)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.accentColor(theme.colors.accent)
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
.introspectNavigationController { navigationController in
|
||||
ThemeService.shared().theme.applyStyle(onNavigationBar: navigationController.navigationBar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -16,6 +16,7 @@
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
import MatrixSDK
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
class RoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol {
|
||||
@@ -25,7 +26,7 @@ class RoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol {
|
||||
// MARK: Private
|
||||
|
||||
private let roomId: String
|
||||
private let session:MXSession
|
||||
private let session: MXSession
|
||||
private var replacementRoom: MXRoom?
|
||||
private var didBuildSpaceGraphObserver: Any?
|
||||
private var accessItems: [RoomAccessTypeChooserAccessItem] = []
|
||||
@@ -66,7 +67,7 @@ class RoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol {
|
||||
self.roomId = roomId
|
||||
self.session = session
|
||||
self.currentRoomId = roomId
|
||||
restrictedVersionOverride = session.homeserverCapabilities.versionOverrideForFeature(.restricted)
|
||||
restrictedVersionOverride = session.homeserverCapabilitiesService.versionOverrideForFeature(.restricted)
|
||||
|
||||
roomUpgradeRequiredSubject = CurrentValueSubject(false)
|
||||
waitingMessageSubject = CurrentValueSubject(nil)
|
||||
@@ -193,7 +194,7 @@ class RoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol {
|
||||
room.state { [weak self] state in
|
||||
guard let self = self else { return }
|
||||
|
||||
if let roomVersion = state?.stateEvents(with: .roomCreate)?.last?.wireContent["room_version"] as? String, let homeserverCapabilitiesService = self.session.homeserverCapabilities {
|
||||
if let roomVersion = state?.stateEvents(with: .roomCreate)?.last?.wireContent["room_version"] as? String, let homeserverCapabilitiesService = self.session.homeserverCapabilitiesService {
|
||||
self.roomUpgradeRequired = self.restrictedVersionOverride != nil && !homeserverCapabilitiesService.isFeatureSupported(.restricted, by: roomVersion)
|
||||
}
|
||||
|
||||
|
||||
-17
@@ -30,23 +30,6 @@ class RoomAccessTypeChooserUITests: MockScreenTest {
|
||||
|
||||
func verifyRoomAccessTypeChooserScreen() throws {
|
||||
guard let screenState = screenState as? MockRoomAccessTypeChooserScreenState else { fatalError("no screen") }
|
||||
switch screenState {
|
||||
case .noRooms:
|
||||
verifyRoomAccessTypeChooserNoRooms()
|
||||
case .rooms:
|
||||
verifyRoomAccessTypeChooserRooms()
|
||||
}
|
||||
}
|
||||
|
||||
func verifyRoomAccessTypeChooserNoRooms() {
|
||||
let errorMessage = app.staticTexts["errorMessage"]
|
||||
XCTAssert(errorMessage.exists)
|
||||
XCTAssert(errorMessage.label == "No Rooms")
|
||||
}
|
||||
|
||||
func verifyRoomAccessTypeChooserRooms() {
|
||||
let displayNameCount = app.buttons.matching(identifier:"roomNameText").count
|
||||
XCTAssertEqual(displayNameCount, 3)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -96,8 +96,7 @@ final class TimelinePollCoordinator: Coordinator, Presentable, PollAggregatorDel
|
||||
}
|
||||
|
||||
func canEditPoll() -> Bool {
|
||||
return false // Intentionally disabled until platform parity.
|
||||
// return pollAggregator.poll.isClosed == false && pollAggregator.poll.totalAnswerCount == 0
|
||||
return pollAggregator.poll.isClosed == false && pollAggregator.poll.totalAnswerCount == 0
|
||||
}
|
||||
|
||||
func endPoll() {
|
||||
|
||||
Reference in New Issue
Block a user