Skip to content

Commit

Permalink
Merge branch 'main' into IOS-9244-ButtonLink-accesibility
Browse files Browse the repository at this point in the history
  • Loading branch information
alexanegon committed Sep 22, 2023
2 parents a618efd + 9bfe335 commit e518459
Show file tree
Hide file tree
Showing 228 changed files with 300 additions and 78 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ BRAND_FILES:= $(Movistar) $(Blau) $(O2) $(Vivo) $(VivoNew) # List of all the bra

# Xcode
ifneq ($(origin GITHUB_ACTION),undefined)
export DEVELOPER_DIR=/Applications/Xcode-14.3.app/Contents/Developer
export DEVELOPER_DIR=/Applications/Xcode-14.3.1.app/Contents/Developer
endif

# TokenGenerator func to be used in all token generators.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import SwiftUI
struct FeedbackCatalogView: View {
@State var title: String = "Title of the feedback"
@State var message: String = "Insert here the message that will be included in the feedback."
@State var slot: Bool = false

@State var selectedStyleIndex = 0
@State var styles: [FeedbackStyle] = [
Expand All @@ -27,6 +28,7 @@ struct FeedbackCatalogView: View {
List {
section("Title") { TextField("Title", text: $title) }
section("Message") { TextField("Message", text: $message) }
section("Slot") { Toggle("Show slot", isOn: $slot) }

section("Style") { stylePicker }

Expand All @@ -38,18 +40,46 @@ struct FeedbackCatalogView: View {
}
}
.sheet(isPresented: $presentingFeedback) {
Feedback(
style: styles[selectedStyleIndex],
title: title,
message: message,
primaryButton: { Button("Primary", action: {}) },
secondaryButton: { Button("Secondary", action: {}) }
)
.titleAccessibilityLabel("Title")
.imageAccessibilityIdentifier("Image")
if slot {
FeedBackSlot
} else {
FeedBack
}
}
}

@ViewBuilder
var FeedBack: some View {
Feedback(
style: styles[selectedStyleIndex],
title: title,
message: message,
primaryButton: { Button("Primary", action: {}) },
secondaryButton: { Button("Secondary", action: {}) }
)
.titleAccessibilityLabel("Title")
.imageAccessibilityIdentifier("Image")
}

@ViewBuilder
var FeedBackSlot: some View {
Feedback(
style: styles[selectedStyleIndex],
title: title,
message: message,
contentView: { text },
primaryButton: { Button("Primary", action: {}) },
secondaryButton: { Button("Secondary", action: {}) }
)
.titleAccessibilityLabel("Title")
.imageAccessibilityIdentifier("Image")
}

@ViewBuilder
var text: some View {
Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas vitae suscipit purus. Nullam quis venenatis lorem. Curabitur laoreet sem sed eros rutrum dictum. Vivamus fermentum vestibulum lacus non euismod. Vestibulum imperdiet sem et neque convallis tempus. Curabitur at lectus enim. Donec vehicula, tortor in pulvinar ornare, nisl justo accumsan ipsum, et sodales magna arcu vel odio. Sed tincidunt ante ligula, sed venenatis eros rutrum ac. Aenean fringilla elit mollis venenatis tempor. Aliquam facilisis, erat quis congue faucibus, enim erat pulvinar justo, ac mollis erat nulla ut dolor. Etiam rhoncus nulla mi, non pretium eros lobortis nec. Ut vulputate ex eu nibh laoreet, in luctus tortor elementum. Ut tristique lectus vel arcu suscipit, sit amet consequat enim porta. Suspendisse vulputate placerat lorem a luctus. Sed suscipit lacus vehicula sapien malesuada semper. Mauris urna orci, maximus non eleifend blandit, accumsan id mi.\nInteger a hendrerit sapien, nec gravida diam. Donec semper vehicula eros ut pharetra. In non convallis tellus, sed ultrices nulla. Cras a arcu neque. Quisque sit amet arcu congue, molestie risus non, facilisis felis. Vivamus et accumsan ipsum, et condimentum quam. Suspendisse eleifend velit turpis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nulla rutrum mauris vel felis porttitor, ut luctus turpis mollis. Suspendisse a nulla ultricies, malesuada augue quis, varius mauris. Morbi eget lacinia orci. Phasellus vel varius nisi.")
}

@ViewBuilder
var stylePicker: some View {
picker($selectedStyleIndex, options: styles)
Expand Down
142 changes: 114 additions & 28 deletions Sources/Mistica/Components/Feedback/FeedbackView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,40 @@ import UIKit

public class FeedbackView: UIView {
private enum Constants {
static let animationDelay: TimeInterval = 0.2
static let animationDuration: TimeInterval = 0.8
static let animationCurveControlPoint1 = CGPoint(x: 0.215, y: 0.61)
static let animationCurveControlPoint2 = CGPoint(x: 0.355, y: 1)
static let iconSize: CGFloat = 48
enum Animation {
enum Animator {
static let duration: TimeInterval = 0.8
static let curveControlPoint1 = CGPoint(x: 0.215, y: 0.61)
static let curveControlPoint2 = CGPoint(x: 0.355, y: 1)
}

enum Delay {
static let initial: TimeInterval = 0.2
static let large: TimeInterval = 0.6
static let small: TimeInterval = 0.3
}

enum Opacity {
static let initial = CGFloat.zero
static let final = CGFloat(1)
}

enum Offset {
static let initial = CGFloat(20)
static let final = CGFloat.zero
}
}
}

private lazy var animator = UIViewPropertyAnimator(
duration: Constants.animationDuration,
controlPoint1: Constants.animationCurveControlPoint1,
controlPoint2: Constants.animationCurveControlPoint2
)
private var animators = [UIViewPropertyAnimator]()
private var animator: UIViewPropertyAnimator {
UIViewPropertyAnimator(
duration: Constants.Animation.Animator.duration,
controlPoint1: Constants.Animation.Animator.curveControlPoint1,
controlPoint2: Constants.Animation.Animator.curveControlPoint2
)
}

// Setup properties
private let style: FeedbackStyle
Expand All @@ -36,6 +59,10 @@ public class FeedbackView: UIView {
let icon = UIImageView()
icon.contentMode = .scaleAspectFit
icon.tintColor = .brand
NSLayoutConstraint.activate([
icon.widthAnchor.constraint(equalToConstant: Constants.iconSize),
icon.heightAnchor.constraint(equalToConstant: Constants.iconSize)
])
icon.isAccessibilityElement = true
icon.accessibilityIdentifier = DefaultIdentifiers.Feedback.asset
return icon
Expand All @@ -47,6 +74,10 @@ public class FeedbackView: UIView {
animation.contentMode = .scaleAspectFit
animation.loopMode = .playOnce
animation.isUserInteractionEnabled = false
NSLayoutConstraint.activate([
animation.widthAnchor.constraint(equalToConstant: Constants.iconSize),
animation.heightAnchor.constraint(equalToConstant: Constants.iconSize)
])
animation.isAccessibilityElement = true
animation.accessibilityIdentifier = DefaultIdentifiers.Feedback.asset
return animation
Expand Down Expand Up @@ -194,7 +225,8 @@ public class FeedbackView: UIView {
scrollStackView.translatesAutoresizingMaskIntoConstraints = false
scrollStackView.stackView.spacing = 24
scrollStackView.stackView.alignment = .leading
scrollStackView.stackView.layoutMargins = UIEdgeInsets(top: 64, left: 24, bottom: 16, right: 24)
scrollStackView.stackView.layoutMargins = UIEdgeInsets(top: 64, left: 16, bottom: 16, right: 16)
scrollStackView.stackView.preservesSuperviewLayoutMargins = false
return scrollStackView
}()

Expand Down Expand Up @@ -239,11 +271,10 @@ public extension FeedbackView {
func startAnimation() {
guard style.shouldAnimate, !animationFired else { return }
animationFired = true
animator.startAnimation(afterDelay: Constants.animationDelay)
triggerHapticFeedback()

if UIView.areAnimationsEnabled {
animatedIcon.play()
startAnimations()
} else {
animatedIcon.stop()
animatedIcon.currentProgress = 1
Expand All @@ -257,6 +288,7 @@ private extension FeedbackView {
setupContent()
setupBackground()
prepareAnimation()
prepareHapticFeedback()
}

func setupContent() {
Expand Down Expand Up @@ -290,32 +322,86 @@ private extension FeedbackView {
scrollStackView.stackView.insertArrangedSubview(icon, at: 0)
case .animation(let animation):
animatedIcon.animation = animation
let width = animation.bounds.width
let height = animation.bounds.height
NSLayoutConstraint.activate([
animatedIcon.widthAnchor.constraint(equalToConstant: width),
animatedIcon.heightAnchor.constraint(equalToConstant: height)
])
// To center animations that are not 1:1 squares
let leftMargin = (width - height) / 2
animatedIcon.transform = CGAffineTransform(translationX: -leftMargin, y: 0)
scrollStackView.stackView.insertArrangedSubview(animatedIcon, at: 0)
}
}

func prepareAnimation() {
guard style.shouldAnimate else { return }
// Initial state
func prepare(view: UIView) {
view.alpha = Constants.Animation.Opacity.initial
view.transform = CGAffineTransform(
translationX: 0,
y: Constants.Animation.Offset.initial
)
}
// Final state
func animation(for view: UIView) -> () -> Void {
{
view.alpha = Constants.Animation.Opacity.final
view.transform = CGAffineTransform(
translationX: 0,
y: Constants.Animation.Offset.final
)
}
}

guard UIView.areAnimationsEnabled, style.shouldAnimate else { return }
animationFired = false
contentContainerStackView.alpha = 0
contentContainerStackView.transform = CGAffineTransform(translationX: 0, y: 20)
animator.addAnimations {
self.contentContainerStackView.alpha = 1
self.contentContainerStackView.transform = CGAffineTransform(translationX: 0, y: 0)

// Views that should animate
var views = [UIView]()
if title.isEmpty == false {
views.append(titleLabel)
}
if let subtitle, subtitle.isEmpty == false {
views.append(subtitleLabel)
}
if let errorReference, errorReference.isEmpty == false {
views.append(errorReferenceLabel)
}
if let extraContent {
views.append(extraContent)
}

// Prepare
views.forEach(prepare(view:))
// Generate animators
animators = views.map(animation).map { animation in
let animator = animator
animator.addAnimations(animation)
return animator
}
}

func startAnimations() {
// Start the initial
DispatchQueue.main.asyncAfter(deadline: .now() + Constants.Animation.Delay.initial) { [weak self] in
self?.animatedIcon.play()

// Animate views
guard let animators = self?.animators, animators.isEmpty == false else { return }
DispatchQueue.main.asyncAfter(deadline: .now() + Constants.Animation.Delay.large) {
self?.animate(remaining: animators)
}
}
}

func animate(remaining animators: [UIViewPropertyAnimator]) {
var animators = animators

let animator = animators.removeFirst()
animator.startAnimation()

// Animate other views
guard animators.isEmpty == false else { return }
DispatchQueue.main.asyncAfter(deadline: .now() + Constants.Animation.Delay.small) { [weak self] in
self?.animate(remaining: animators)
}
prepareHapticFeedback()
}

func prepareHapticFeedback() {
guard UIView.areAnimationsEnabled else { return }
feedbackGenerator?.prepare()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,12 @@ public extension FeedbackViewController {
view.addSubview(withDefaultConstraints: feedbackView)
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
feedbackView.startAnimation()
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if configuration.backButton == .none {
navigationController?.interactivePopGestureRecognizer?.isEnabled = false
}
feedbackView.startAnimation()
}

override func viewWillDisappear(_ animated: Bool) {
Expand Down
Loading

0 comments on commit e518459

Please sign in to comment.