๐Ÿ“ฆ airbnb / lottie-ios

๐Ÿ“„ ReducedMotionOption.swift ยท 119 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119// Created by Cal Stephens on 7/14/23.
// Copyright ยฉ 2023 Airbnb Inc. All rights reserved.

#if canImport(UIKit)
import UIKit
#elseif canImport(AppKit)
import AppKit
#endif

// MARK: - ReducedMotionOption

/// Options for controlling animation behavior in response to user / system "reduced motion" configuration
public enum ReducedMotionOption {
  /// Always use the specific given `ReducedMotionMode` value.
  case specific(ReducedMotionMode)

  /// Dynamically check the given `ReducedMotionOptionProvider` each time an animation begins.
  ///  - Includes a Hashable `dataID` to support `ReducedMotionOption`'s `Hashable` requirement,
  ///    which is required due to `LottieConfiguration`'s existing `Hashable` requirement.
  case dynamic(ReducedMotionOptionProvider, dataID: AnyHashable)
}

extension ReducedMotionOption {
  /// The standard behavior where Lottie animations play normally with no overrides.
  /// By default this mode is used when the system "reduced motion" option is disabled.
  public static var standardMotion: ReducedMotionOption {
    .specific(.standardMotion)
  }

  /// Lottie animations with a "reduced motion" marker will play that marker instead of any other animations.
  /// By default this mode is used when the system "reduced motion" option is enabled.
  ///  - Valid marker names include "reduced motion", "reducedMotion", "reduced_motion" (case insensitive).
  public static var reducedMotion: ReducedMotionOption {
    .specific(.reducedMotion)
  }

  /// A `ReducedMotionOptionProvider` that returns `.reducedMotion` when
  /// the system `UIAccessibility.isReduceMotionEnabled` option is `true`.
  /// This is the default option of `LottieConfiguration`.
  public static var systemReducedMotionToggle: ReducedMotionOption {
    .dynamic(SystemReducedMotionOptionProvider(), dataID: ObjectIdentifier(SystemReducedMotionOptionProvider.self))
  }
}

extension ReducedMotionOption {
  /// The current `ReducedMotionMode` based on the currently selected option.
  public var currentReducedMotionMode: ReducedMotionMode {
    switch self {
    case .specific(let specificMode):
      specificMode
    case .dynamic(let optionProvider, _):
      optionProvider.currentReducedMotionMode
    }
  }
}

// MARK: Hashable

extension ReducedMotionOption: Hashable {
  public static func ==(_ lhs: ReducedMotionOption, _ rhs: ReducedMotionOption) -> Bool {
    switch (lhs, rhs) {
    case (.specific(let lhsMode), .specific(let rhsMode)):
      lhsMode == rhsMode
    case (.dynamic(_, let lhsDataID), .dynamic(_, dataID: let rhsDataID)):
      lhsDataID == rhsDataID
    case (.dynamic, .specific), (.specific, .dynamic):
      false
    }
  }

  public func hash(into hasher: inout Hasher) {
    switch self {
    case .specific(let mode):
      hasher.combine(mode)
    case .dynamic(_, let dataID):
      hasher.combine(dataID)
    }
  }
}

// MARK: - ReducedMotionMode

public enum ReducedMotionMode: Hashable {
  /// The default behavior where Lottie animations play normally with no overrides
  /// By default this mode is used when the system "reduced motion" option is disabled.
  case standardMotion

  /// Lottie animations with a "reduced motion" marker will play that marker instead of any other animations.
  /// By default this mode is used when the system "reduced motion" option is enabled.
  case reducedMotion
}

// MARK: - ReducedMotionOptionProvider

/// A type that returns a dynamic `ReducedMotionMode` which is checked when playing a Lottie animation.
public protocol ReducedMotionOptionProvider {
  var currentReducedMotionMode: ReducedMotionMode { get }
}

// MARK: - SystemReducedMotionOptionProvider

/// A `ReducedMotionOptionProvider` that returns `.reducedMotion` when
/// the system `UIAccessibility.isReduceMotionEnabled` option is `true`.
public struct SystemReducedMotionOptionProvider: ReducedMotionOptionProvider {
  public init() { }

  public var currentReducedMotionMode: ReducedMotionMode {
    #if canImport(UIKit)
    if UIAccessibility.isReduceMotionEnabled {
      return .reducedMotion
    } else {
      return .standardMotion
    }
    #else
    return .standardMotion
    #endif
  }
}