Skip to content

Commit

Permalink
Add StyledText and InlineTextButton SwiftUI views.
Browse files Browse the repository at this point in the history
Replace AnalyticsPromptTermsText with InlineTextButton.
  • Loading branch information
pixlwave committed Jan 31, 2022
1 parent 0f28260 commit a3a1678
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 127 deletions.
39 changes: 19 additions & 20 deletions RiotSwiftUI/Modules/AnalyticsPrompt/AnalyticsPromptModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,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 {
Expand All @@ -70,11 +65,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:
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(let termsString), .upgrade(let termsString):
return termsString
case .newUser:
return VectorL10n.analyticsPromptTermsLinkNewUser
case .upgrade:
return VectorL10n.analyticsPromptTermsLinkUpgrade
}
}

Expand All @@ -99,15 +106,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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Original file line number Diff line number Diff line change
Expand Up @@ -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.")

Expand All @@ -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
}
}
13 changes: 6 additions & 7 deletions RiotSwiftUI/Modules/AnalyticsPrompt/View/AnalyticsPrompt.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

Expand All @@ -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)
Expand Down Expand Up @@ -125,6 +123,7 @@ struct AnalyticsPrompt: View {
.padding(.bottom, geometry.safeAreaInsets.bottom > 0 ? 0 : 16)
}
.background(theme.colors.background.ignoresSafeArea())
.accentColor(theme.colors.accent)
}
}
}
Expand Down

This file was deleted.

89 changes: 89 additions & 0 deletions RiotSwiftUI/Modules/Common/Util/InlineTextButton.swift
Original file line number Diff line number Diff line change
@@ -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 {

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())
}

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()
}
}
Loading

0 comments on commit a3a1678

Please sign in to comment.