Story mute button animation

This commit is contained in:
Ali 2023-07-28 16:47:50 +04:00
parent 1cee5070f8
commit a257155ab4
6 changed files with 80 additions and 31 deletions

View File

@ -28,6 +28,10 @@ private final class LottieDirectContent: LottieComponent.Content {
self.path = path self.path = path
} }
override var frameRange: Range<Double> {
return 0.0 ..< 1.0
}
override func isEqual(to other: LottieComponent.Content) -> Bool { override func isEqual(to other: LottieComponent.Content) -> Bool {
guard let other = other as? LottieDirectContent else { guard let other = other as? LottieDirectContent else {
return false return false

View File

@ -12,6 +12,10 @@ public final class LottieComponent: Component {
public typealias EnvironmentType = Empty public typealias EnvironmentType = Empty
open class Content: Equatable { open class Content: Equatable {
open var frameRange: Range<Double> {
preconditionFailure()
}
public init() { public init() {
} }
@ -34,8 +38,14 @@ public final class LottieComponent: Component {
public final class AppBundleContent: Content { public final class AppBundleContent: Content {
public let name: String public let name: String
public init(name: String) { private let frameRangeValue: Range<Double>
override public var frameRange: Range<Double> {
return self.frameRangeValue
}
public init(name: String, frameRange: Range<Double> = 0.0 ..< 1.0) {
self.name = name self.name = name
self.frameRangeValue = frameRange
} }
override public func isEqual(to other: Content) -> Bool { override public func isEqual(to other: Content) -> Bool {
@ -45,6 +55,9 @@ public final class LottieComponent: Component {
if self.name != other.name { if self.name != other.name {
return false return false
} }
if self.frameRangeValue != other.frameRangeValue {
return false
}
return true return true
} }
@ -59,9 +72,10 @@ public final class LottieComponent: Component {
} }
} }
public enum StartingPosition { public enum StartingPosition: Equatable {
case begin case begin
case end case end
case fraction(Double)
} }
public let content: Content public let content: Content
@ -104,6 +118,7 @@ public final class LottieComponent: Component {
private var scheduledPlayOnce: Bool = false private var scheduledPlayOnce: Bool = false
private var playOnceCompletion: (() -> Void)? private var playOnceCompletion: (() -> Void)?
private var animationInstance: LottieInstance? private var animationInstance: LottieInstance?
private var animationFrameRange: Range<Int>?
private var currentDisplaySize: CGSize? private var currentDisplaySize: CGSize?
private var currentContentDisposable: Disposable? private var currentContentDisposable: Disposable?
@ -171,7 +186,7 @@ public final class LottieComponent: Component {
public func playOnce(delay: Double = 0.0, completion: (() -> Void)? = nil) { public func playOnce(delay: Double = 0.0, completion: (() -> Void)? = nil) {
self.playOnceCompletion = completion self.playOnceCompletion = completion
guard let _ = self.animationInstance else { guard let _ = self.animationInstance, let animationFrameRange = self.animationFrameRange else {
self.scheduledPlayOnce = true self.scheduledPlayOnce = true
return return
} }
@ -182,8 +197,8 @@ public final class LottieComponent: Component {
self.scheduledPlayOnce = false self.scheduledPlayOnce = false
if self.currentFrame != 0 { if self.currentFrame != animationFrameRange.lowerBound {
self.currentFrame = 0 self.currentFrame = animationFrameRange.lowerBound
self.updateImage() self.updateImage()
} }
@ -218,24 +233,35 @@ public final class LottieComponent: Component {
} }
} }
private func loadAnimation(data: Data, cacheKey: String?, startingPosition: StartingPosition) { private func loadAnimation(data: Data, cacheKey: String?, startingPosition: StartingPosition, frameRange: Range<Double>) {
self.animationInstance = LottieInstance(data: data, fitzModifier: .none, colorReplacements: nil, cacheKey: cacheKey ?? "") self.animationInstance = LottieInstance(data: data, fitzModifier: .none, colorReplacements: nil, cacheKey: cacheKey ?? "")
if let animationInstance = self.animationInstance {
self.animationFrameRange = Int(floor(frameRange.lowerBound * Double(animationInstance.frameCount))) ..< Int(floor(frameRange.upperBound * Double(animationInstance.frameCount)))
} else {
self.animationFrameRange = nil
}
if let _ = self.animationInstance, let animationFrameRange = self.animationFrameRange {
switch startingPosition {
case .begin:
self.currentFrame = animationFrameRange.lowerBound
case .end:
self.currentFrame = Int(max(animationFrameRange.lowerBound, animationFrameRange.upperBound - 1))
case let .fraction(fraction):
self.currentFrame = animationFrameRange.lowerBound + Int(floor(Double(animationFrameRange.upperBound - animationFrameRange.lowerBound) * fraction))
}
}
if self.scheduledPlayOnce { if self.scheduledPlayOnce {
self.scheduledPlayOnce = false self.scheduledPlayOnce = false
self.playOnce() self.playOnce()
} else if let animationInstance = self.animationInstance { } else {
switch startingPosition {
case .begin:
self.currentFrame = 0
case .end:
self.currentFrame = Int(animationInstance.frameCount - 1)
}
self.updateImage() self.updateImage()
} }
} }
private func advanceIfNeeded() { private func advanceIfNeeded() {
guard let animationInstance = self.animationInstance else { guard let animationInstance = self.animationInstance, let animationFrameRange = self.animationFrameRange else {
return return
} }
guard let currentFrameStartTime = self.currentFrameStartTime else { guard let currentFrameStartTime = self.currentFrameStartTime else {
@ -258,8 +284,8 @@ public final class LottieComponent: Component {
advanceFrameCount = 4 advanceFrameCount = 4
} }
self.currentFrame += advanceFrameCount self.currentFrame += advanceFrameCount
if self.currentFrame >= Int(animationInstance.frameCount) - 1 { if self.currentFrame >= animationFrameRange.upperBound - 1 {
self.currentFrame = Int(animationInstance.frameCount) - 1 self.currentFrame = animationFrameRange.upperBound - 1
self.updateImage() self.updateImage()
self.displayLink?.invalidate() self.displayLink?.invalidate()
self.displayLink = nil self.displayLink = nil
@ -276,14 +302,17 @@ public final class LottieComponent: Component {
} }
private func updateImage() { private func updateImage() {
guard let animationInstance = self.animationInstance, let currentDisplaySize = self.currentDisplaySize else { guard let animationInstance = self.animationInstance, let animationFrameRange = self.animationFrameRange, let currentDisplaySize = self.currentDisplaySize else {
return return
} }
guard let context = DrawingContext(size: currentDisplaySize, scale: 1.0, opaque: false, clear: true) else { guard let context = DrawingContext(size: currentDisplaySize, scale: 1.0, opaque: false, clear: true) else {
return return
} }
animationInstance.renderFrame(with: Int32(self.currentFrame % Int(animationInstance.frameCount)), into: context.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(currentDisplaySize.width), height: Int32(currentDisplaySize.height), bytesPerRow: Int32(context.bytesPerRow)) var effectiveFrameIndex = self.currentFrame
effectiveFrameIndex = max(animationFrameRange.lowerBound, min(animationFrameRange.upperBound, effectiveFrameIndex))
animationInstance.renderFrame(with: Int32(effectiveFrameIndex), into: context.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(currentDisplaySize.width), height: Int32(currentDisplaySize.height), bytesPerRow: Int32(context.bytesPerRow))
var image = context.generateImage() var image = context.generateImage()
if let _ = self.component?.color { if let _ = self.component?.color {
@ -316,12 +345,13 @@ public final class LottieComponent: Component {
if previousComponent?.content != component.content { if previousComponent?.content != component.content {
self.currentContentDisposable?.dispose() self.currentContentDisposable?.dispose()
let content = component.content let content = component.content
let frameRange = content.frameRange
self.currentContentDisposable = component.content.load { [weak self, weak content] data, cacheKey in self.currentContentDisposable = component.content.load { [weak self, weak content] data, cacheKey in
Queue.mainQueue().async { Queue.mainQueue().async {
guard let self, let component = self.component, component.content == content else { guard let self, let component = self.component, component.content == content else {
return return
} }
self.loadAnimation(data: data, cacheKey: cacheKey, startingPosition: component.startingPosition) self.loadAnimation(data: data, cacheKey: cacheKey, startingPosition: component.startingPosition, frameRange: frameRange)
} }
} }
} else if redrawImage { } else if redrawImage {

View File

@ -9,6 +9,10 @@ public extension LottieComponent {
final class EmojiContent: LottieComponent.Content { final class EmojiContent: LottieComponent.Content {
private let context: AccountContext private let context: AccountContext
private let fileId: Int64 private let fileId: Int64
override public var frameRange: Range<Double> {
return 0.0 ..< 1.0
}
public init( public init(
context: AccountContext, context: AccountContext,

View File

@ -47,6 +47,10 @@ public final class PlainButtonComponent: Component {
private let contentContainer = UIView() private let contentContainer = UIView()
private let content = ComponentView<Empty>() private let content = ComponentView<Empty>()
public var contentView: UIView? {
return self.content.view
}
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)

View File

@ -78,6 +78,7 @@ swift_library(
"//submodules/WebPBinding", "//submodules/WebPBinding",
"//submodules/Utils/RangeSet", "//submodules/Utils/RangeSet",
"//submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen", "//submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen",
"//submodules/TelegramUI/Components/LottieComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -35,6 +35,7 @@ import AttachmentUI
import StickerPackPreviewUI import StickerPackPreviewUI
import TextNodeWithEntities import TextNodeWithEntities
import TelegramStringFormatting import TelegramStringFormatting
import LottieComponent
public final class StoryAvailableReactions: Equatable { public final class StoryAvailableReactions: Equatable {
let reactionItems: [ReactionItem] let reactionItems: [ReactionItem]
@ -372,6 +373,7 @@ public final class StoryItemSetContainerComponent: Component {
var leftInfoItem: InfoItem? var leftInfoItem: InfoItem?
let moreButton = ComponentView<Empty>() let moreButton = ComponentView<Empty>()
var currentSoundButtonState: Bool?
let soundButton = ComponentView<Empty>() let soundButton = ComponentView<Empty>()
var privacyIcon: ComponentView<Empty>? var privacyIcon: ComponentView<Empty>?
@ -2427,21 +2429,18 @@ public final class StoryItemSetContainerComponent: Component {
} }
} }
let soundImage: String let soundButtonState = isSilentVideo || component.isAudioMuted
if isSilentVideo || component.isAudioMuted {
soundImage = "Stories/SoundOff"
} else {
soundImage = "Stories/SoundOn"
}
//TODO:anim_storymute
let soundButtonSize = self.soundButton.update( let soundButtonSize = self.soundButton.update(
transition: transition, transition: transition,
component: AnyComponent(PlainButtonComponent( component: AnyComponent(PlainButtonComponent(
content: AnyComponent(BundleIconComponent( content: AnyComponent(LottieComponent(
name: soundImage, content: LottieComponent.AppBundleContent(
tintColor: .white, name: "anim_storymute",
maxSize: nil frameRange: soundButtonState ? (0.0 ..< 0.5) : (0.5 ..< 1.0)
),
color: .white,
startingPosition: .end,
size: CGSize(width: 30.0, height: 30.0)
)), )),
effectAlignment: .center, effectAlignment: .center,
minSize: CGSize(width: 33.0, height: 64.0), minSize: CGSize(width: 33.0, height: 64.0),
@ -2481,6 +2480,13 @@ public final class StoryItemSetContainerComponent: Component {
if isVideo { if isVideo {
headerRightOffset -= soundButtonSize.width + 13.0 headerRightOffset -= soundButtonSize.width + 13.0
} }
if let currentSoundButtonState = self.currentSoundButtonState, currentSoundButtonState != soundButtonState {
if let lottieView = (soundButtonView as? PlainButtonComponent.View)?.contentView as? LottieComponent.View {
lottieView.playOnce()
}
}
self.currentSoundButtonState = soundButtonState
} }
let storyPrivacyIcon: StoryPrivacyIconComponent.Privacy? let storyPrivacyIcon: StoryPrivacyIconComponent.Privacy?