Repository: airbnb/lottie-ios
File: Sources/Public/Animation/LottieView.swift (Swift)

Code before:
  ///  - If the `currentTime` is `nil`, no changes will be made and any existing animations
  ///    will continue playing uninterrupted.
  public func currentTime(_ currentTime: TimeInterval?) -> Self {
    configure { view in
      if
        let currentTime = currentTime,
        view.currentTime != currentTime
      {
        view.currentTime = currentTime
      }
    }
  }

  /// Returns a new instance of this view, which will invoke the provided `loadAnimation` closure
  /// whenever the `binding` value is updated.
  ///
  /// - Note: This function requires a valid `loadAnimation` closure provided during view initialization,
  ///         otherwise `reloadAnimationTrigger` will have no effect.
  /// - Parameters:
  ///   - binding: The binding that triggers the reloading when its value changes.
  ///   - showPlaceholder: When `true`, the current animation will be removed before invoking `loadAnimation`,
  ///     displaying the `Placeholder` until the new animation loads.
  ///     When `false`, the previous animation remains visible while the new one loads.
  public func reloadAnimationTrigger<Value: Equatable>(_ binding: Binding<Value>, showPlaceholder: Bool = true) -> Self {
    var copy = self
    copy.reloadAnimationTrigger = binding.map { $0 as any Equatable }
    copy.showPlaceholderWhileReloading = showPlaceholder
    return copy
  }

  /// Returns a view that updates the given binding each frame with the animation's `realtimeAnimationProgress`.
  /// The `LottieView` is wrapped in a `TimelineView` with the `.animation` schedule.
  ///  - This is a one-way binding. Its value is updated but never read.
  ///  - If provided, the binding will be updated each frame with the `realtimeAnimationProgress`
  ///    of the underlying `LottieAnimationView`. This is potentially expensive since it triggers
  ///    a state update every frame.
  ///  - If the binding is `nil`, the `TimelineView` will be paused and no updates will occur to the binding.
  @available(iOS 15.0, tvOS 15.0, macOS 12.0, *)
  public func getRealtimeAnimationProgress(_ realtimeAnimationProgress: Binding<AnimationProgressTime>?) -> some View {
    TimelineView(.animation(paused: realtimeAnimationProgress == nil)) { _ in
      configure { view in
        if let realtimeAnimationProgress = realtimeAnimationProgress {
          DispatchQueue.main.async {
            realtimeAnimationProgress.wrappedValue = view.realtimeAnimationProgress
          }
        }
      }
    }
  }

  /// Returns a view that updates the given binding each frame with the animation's `realtimeAnimationProgress`.

Review comment (bug):
It seems we cant use `any Equatable` due to backwards compatibility requirements (CI is failing), so I think we need to switch back to requiring `Hashable` so we can use `AnyHashable` here. Fortunately most `Equatable` types are also `Hashable`.

Code after:
      if
        let currentFrame = currentFrame,
        view.currentFrame != currentFrame
      {
        view.currentFrame = currentFrame
      }
    }
  }

  /// Returns a copy of this view updated to display the given time value.
  ///  - If the `currentTime` value is provided, the `currentTime` of the
  ///    underlying `LottieAnimationView` is updated. This will pause any existing animations.
  ///  - If the `currentTime` is `nil`, no changes will be made and any existing animations
  ///    will continue playing uninterrupted.
  public func currentTime(_ currentTime: TimeInterval?) -> Self {
    configure { view in
      if
        let currentTime = currentTime,
        view.currentTime != currentTime
      {
        view.currentTime = currentTime
      }
    }
  }

  /// Returns a new instance of this view, which will invoke the provided `loadAnimation` closure
  /// whenever the `binding` value is updated.
  ///
  /// - Note: This function requires a valid `loadAnimation` closure provided during view initialization,
  ///         otherwise `reloadAnimationTrigger` will have no effect.
  /// - Parameters:
  ///   - binding: The binding that triggers the reloading when its value changes.
  ///   - showPlaceholder: When `true`, the current animation will be removed before invoking `loadAnimation`,
  ///     displaying the `Placeholder` until the new animation loads.
  ///     When `false`, the previous animation remains visible while the new one loads.
  public func reloadAnimationTrigger<Value: Equatable>(_ value: Value, showPlaceholder: Bool = true) -> Self {
    var copy = self
    copy.reloadAnimationTrigger = AnyEquatable(value)
    copy.showPlaceholderWhileReloading = showPlaceholder
    return copy
  }

  /// Returns a view that updates the given binding each frame with the animation's `realtimeAnimationProgress`.
  /// The `LottieView` is wrapped in a `TimelineView` with the `.animation` schedule.
  ///  - This is a one-way binding. Its value is updated but never read.
  ///  - If provided, the binding will be updated each frame with the `realtimeAnimationProgress`
  ///    of the underlying `LottieAnimationView`. This is potentially expensive since it triggers
  ///    a state update every frame.
  ///  - If the binding is `nil`, the `TimelineView` will be paused and no updates will occur to the binding.
  @available(iOS 15.0, tvOS 15.0, macOS 12.0, *)
  public func getRealtimeAnimationProgress(_ realtimeAnimationProgress: Binding<AnimationProgressTime>?) -> some View {