Various improvements

This commit is contained in:
Ilya Laktyushin 2023-12-17 21:22:01 +04:00
parent 3314a18165
commit 7f0aff80f0
10 changed files with 79 additions and 27 deletions

View File

@ -10759,3 +10759,5 @@ Sorry for the inconvenience.";
"Story.MessageReposted.Channel" = "Message reposted to **%@**."; "Story.MessageReposted.Channel" = "Message reposted to **%@**.";
"Story.Views.Commented" = " • commented"; "Story.Views.Commented" = " • commented";
"Share.RepostToStory" = "Repost\nto Story";

View File

@ -261,6 +261,9 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke
} }
} }
public var forceSynchronous = false
public init(useMetalCache: Bool = false) { public init(useMetalCache: Bool = false) {
self.queue = sharedQueue self.queue = sharedQueue
self.eventsNode = AnimatedStickerNodeDisplayEvents() self.eventsNode = AnimatedStickerNodeDisplayEvents()
@ -366,7 +369,7 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke
strongSelf.play(firstFrame: true) strongSelf.play(firstFrame: true)
} }
} }
self.disposable.set((source.directDataPath(attemptSynchronously: false) self.disposable.set((source.directDataPath(attemptSynchronously: self.forceSynchronous)
|> filter { $0 != nil } |> filter { $0 != nil }
|> deliverOnMainQueue).startStrict(next: { path in |> deliverOnMainQueue).startStrict(next: { path in
f(path!) f(path!)
@ -698,7 +701,8 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke
let frameSourceHolder = self.frameSource let frameSourceHolder = self.frameSource
let timerHolder = self.timer let timerHolder = self.timer
let useMetalCache = self.useMetalCache let useMetalCache = self.useMetalCache
self.queue.async { [weak self] in
let action = { [weak self] in
var maybeFrameSource: AnimatedStickerFrameSource? = frameSourceHolder.with { $0 }?.syncWith { $0 }.value var maybeFrameSource: AnimatedStickerFrameSource? = frameSourceHolder.with { $0 }?.syncWith { $0 }.value
if case .timestamp = position { if case .timestamp = position {
} else { } else {
@ -795,6 +799,11 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke
frameQueue.generateFramesIfNeeded() frameQueue.generateFramesIfNeeded()
} }
} }
if self.forceSynchronous {
action()
} else {
self.queue.async(action)
}
} }
public func playIfNeeded() -> Bool { public func playIfNeeded() -> Bool {

View File

@ -170,11 +170,11 @@ public final class SelectablePeerNode: ASDisplayNode {
) )
} }
public func setupStoryRepost(accountPeerId: EnginePeer.Id, postbox: Postbox, network: Network, theme: PresentationTheme, strings: PresentationStrings, synchronousLoad: Bool) { public func setupStoryRepost(accountPeerId: EnginePeer.Id, postbox: Postbox, network: Network, theme: PresentationTheme, strings: PresentationStrings, synchronousLoad: Bool, isMessage: Bool) {
self.peer = nil self.peer = nil
self.textNode.maximumNumberOfLines = 2 self.textNode.maximumNumberOfLines = 2
self.textNode.attributedText = NSAttributedString(string: strings.Share_RepostStory, font: textFont, textColor: self.theme.textColor, paragraphAlignment: .center) self.textNode.attributedText = NSAttributedString(string: isMessage ? strings.Share_RepostToStory : strings.Share_RepostStory, font: textFont, textColor: self.theme.textColor, paragraphAlignment: .center)
self.avatarNode.setPeer(accountPeerId: accountPeerId, postbox: postbox, network: network, contentSettings: ContentSettings.default, theme: theme, peer: nil, overrideImage: .repostIcon, emptyColor: self.theme.avatarPlaceholderColor, clipStyle: .round, synchronousLoad: synchronousLoad) self.avatarNode.setPeer(accountPeerId: accountPeerId, postbox: postbox, network: network, contentSettings: ContentSettings.default, theme: theme, peer: nil, overrideImage: .repostIcon, emptyColor: self.theme.avatarPlaceholderColor, clipStyle: .round, synchronousLoad: synchronousLoad)
self.avatarNode.playRepostAnimation() self.avatarNode.playRepostAnimation()

View File

@ -95,7 +95,7 @@ final class ShareControllerGridSectionNode: ASDisplayNode {
final class ShareControllerPeerGridItem: GridItem { final class ShareControllerPeerGridItem: GridItem {
enum ShareItem: Equatable { enum ShareItem: Equatable {
case peer(peer: EngineRenderedPeer, presence: EnginePeer.Presence?, topicId: Int64?, threadData: MessageHistoryThreadData?) case peer(peer: EngineRenderedPeer, presence: EnginePeer.Presence?, topicId: Int64?, threadData: MessageHistoryThreadData?)
case story case story(isMessage: Bool)
var peerId: EnginePeer.Id? { var peerId: EnginePeer.Id? {
if case let .peer(peer, _, _, _) = self { if case let .peer(peer, _, _, _) = self {
@ -246,14 +246,15 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
self.placeholderNode = nil self.placeholderNode = nil
shimmerNode.removeFromSupernode() shimmerNode.removeFromSupernode()
} }
} else if let item, case .story = item { } else if let item, case let .story(isMessage) = item {
self.peerNode.setupStoryRepost( self.peerNode.setupStoryRepost(
accountPeerId: context.accountPeerId, accountPeerId: context.accountPeerId,
postbox: context.stateManager.postbox, postbox: context.stateManager.postbox,
network: context.stateManager.network, network: context.stateManager.network,
theme: theme, theme: theme,
strings: strings, strings: strings,
synchronousLoad: synchronousLoad synchronousLoad: synchronousLoad,
isMessage: isMessage
) )
} else { } else {
let shimmerNode: ShimmerEffectNode let shimmerNode: ShimmerEffectNode

View File

@ -170,7 +170,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
var index: Int32 = 0 var index: Int32 = 0
if canShareStory { if canShareStory {
entries.append(SharePeerEntry(index: index, item: .story, theme: theme, strings: strings)) entries.append(SharePeerEntry(index: index, item: .story(isMessage: false), theme: theme, strings: strings))
index += 1 index += 1
} }

View File

@ -61,7 +61,7 @@ private func monthAtIndex(_ index: Int, strings: PresentationStrings) -> String
} }
} }
public func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Message, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, strings: PresentationStrings, format: MessageTimestampStatusFormat = .regular, associatedData: ChatMessageItemAssociatedData) -> String { public func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Message, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, strings: PresentationStrings, format: MessageTimestampStatusFormat = .regular, associatedData: ChatMessageItemAssociatedData, ignoreAuthor: Bool = false) -> String {
if let adAttribute = message.adAttribute { if let adAttribute = message.adAttribute {
switch adAttribute.messageType { switch adAttribute.messageType {
case .sponsored: case .sponsored:
@ -155,6 +155,9 @@ public func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Mess
if let subject = associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info { if let subject = associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info {
authorTitle = nil authorTitle = nil
} }
if ignoreAuthor {
authorTitle = nil
}
if case .minimal = format { if case .minimal = format {

View File

@ -625,6 +625,10 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode,
if let strongSelf = self { if let strongSelf = self {
if strongSelf.item == nil { if strongSelf.item == nil {
strongSelf.animationNode.autoplay = true strongSelf.animationNode.autoplay = true
if let animationNode = strongSelf.animationNode as? DefaultAnimatedStickerNodeImpl, item.presentationData.isPreview {
animationNode.displaysAsynchronously = false
animationNode.forceSynchronous = true
}
strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: animationName), width: 384, height: 384, playbackMode: .still(.start), mode: .direct(cachePathPrefix: nil)) strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: animationName), width: 384, height: 384, playbackMode: .still(.start), mode: .direct(cachePathPrefix: nil))
} }
strongSelf.item = item strongSelf.item = item

View File

@ -268,6 +268,9 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
instantVideoBackgroundImage = nil instantVideoBackgroundImage = nil
ignoreHeaders = true ignoreHeaders = true
} }
if item.presentationData.isPreview {
ignoreHeaders = true
}
if item.presentationData.theme != currentItem?.presentationData.theme { if item.presentationData.theme != currentItem?.presentationData.theme {
updatedInstantVideoBackgroundImage = instantVideoBackgroundImage updatedInstantVideoBackgroundImage = instantVideoBackgroundImage
@ -529,13 +532,17 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
} else { } else {
dateFormat = .regular dateFormat = .regular
} }
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData) let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData, ignoreAuthor: item.presentationData.isPreview)
let maxDateAndStatusWidth: CGFloat let maxDateAndStatusWidth: CGFloat
if case .bubble = statusDisplayType { if case .bubble = statusDisplayType {
maxDateAndStatusWidth = width maxDateAndStatusWidth = width
} else { } else {
maxDateAndStatusWidth = width - videoFrame.midX - 85.0 if item.presentationData.isPreview {
maxDateAndStatusWidth = width - videoFrame.midX - 65.0
} else {
maxDateAndStatusWidth = width - videoFrame.midX - 85.0
}
} }
var isReplyThread = false var isReplyThread = false
@ -546,8 +553,8 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
let statusSuggestedWidthAndContinue = makeDateAndStatusLayout(ChatMessageDateAndStatusNode.Arguments( let statusSuggestedWidthAndContinue = makeDateAndStatusLayout(ChatMessageDateAndStatusNode.Arguments(
context: item.context, context: item.context,
presentationData: item.presentationData, presentationData: item.presentationData,
edited: edited && !sentViaBot, edited: edited && !sentViaBot && !item.presentationData.isPreview,
impressionCount: viewCount, impressionCount: item.presentationData.isPreview ? nil : viewCount,
dateText: dateText, dateText: dateText,
type: statusType, type: statusType,
layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil),
@ -576,6 +583,8 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
contentSize.height += dateAndStatusSize.height + 2.0 contentSize.height += dateAndStatusSize.height + 2.0
contentSize.width = max(contentSize.width, dateAndStatusSize.width) contentSize.width = max(contentSize.width, dateAndStatusSize.width)
dateAndStatusOverflow = true dateAndStatusOverflow = true
} else if item.presentationData.isPreview {
contentSize.width += 44.0
} }
let result = ChatMessageInstantVideoItemLayoutResult(contentSize: contentSize, overflowLeft: 0.0, overflowRight: dateAndStatusOverflow ? 0.0 : (max(0.0, floorToScreenPixels(videoFrame.midX) + 55.0 + dateAndStatusSize.width - videoFrame.width))) let result = ChatMessageInstantVideoItemLayoutResult(contentSize: contentSize, overflowLeft: 0.0, overflowRight: dateAndStatusOverflow ? 0.0 : (max(0.0, floorToScreenPixels(videoFrame.midX) + 55.0 + dateAndStatusSize.width - videoFrame.width)))
@ -673,7 +682,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
let durationTextColor: UIColor let durationTextColor: UIColor
switch statusDisplayType { switch statusDisplayType {
case .free: case .free:
let serviceColor = serviceMessageColorComponents(theme: theme.theme, wallpaper: theme.wallpaper) let serviceColor = serviceMessageColorComponents(theme: theme.theme, wallpaper: theme.wallpaper)
durationTextColor = serviceColor.primaryText durationTextColor = serviceColor.primaryText
durationBlurColor = (selectDateFillStaticColor(theme: theme.theme, wallpaper: theme.wallpaper), item.controllerInteraction.enableFullTranslucency && dateFillNeedsBlur(theme: theme.theme, wallpaper: theme.wallpaper)) durationBlurColor = (selectDateFillStaticColor(theme: theme.theme, wallpaper: theme.wallpaper), item.controllerInteraction.enableFullTranslucency && dateFillNeedsBlur(theme: theme.theme, wallpaper: theme.wallpaper))
case .bubble: case .bubble:
@ -692,9 +701,9 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
previousVideoNode = strongSelf.videoNode previousVideoNode = strongSelf.videoNode
if let durationBlurColor = durationBlurColor { if let durationBlurColor = durationBlurColor {
if let durationBackgroundNode = strongSelf.durationBackgroundNode { if let durationBackgroundNode = strongSelf.durationBackgroundNode {
durationBackgroundNode.updateColor(color: durationBlurColor.0, enableBlur: durationBlurColor.1, transition: .immediate) durationBackgroundNode.updateColor(color: durationBlurColor.0, enableBlur: durationBlurColor.1 && !item.presentationData.isPreview, transition: .immediate)
} else { } else {
let durationBackgroundNode = NavigationBackgroundNode(color: durationBlurColor.0, enableBlur: durationBlurColor.1) let durationBackgroundNode = NavigationBackgroundNode(color: durationBlurColor.0, enableBlur: durationBlurColor.1 && !item.presentationData.isPreview)
strongSelf.durationBackgroundNode = durationBackgroundNode strongSelf.durationBackgroundNode = durationBackgroundNode
strongSelf.addSubnode(durationBackgroundNode) strongSelf.addSubnode(durationBackgroundNode)
} }
@ -756,6 +765,13 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
strongSelf.insertSubnode(strongSelf.secretVideoPlaceholderBackground, belowSubnode: videoNode) strongSelf.insertSubnode(strongSelf.secretVideoPlaceholderBackground, belowSubnode: videoNode)
strongSelf.insertSubnode(strongSelf.secretVideoPlaceholder, belowSubnode: videoNode) strongSelf.insertSubnode(strongSelf.secretVideoPlaceholder, belowSubnode: videoNode)
} }
} else if item.presentationData.isPreview {
let updatedSecretPlaceholderSignal = chatMessageVideo(postbox: item.context.account.postbox, userLocation: .peer(item.message.id.peerId), videoReference: .message(message: MessageReference(item.message), media: telegramFile), synchronousLoad: true)
strongSelf.secretVideoPlaceholder.displaysAsynchronously = false
strongSelf.secretVideoPlaceholder.setSignal(updatedSecretPlaceholderSignal, attemptSynchronously: true)
if strongSelf.secretVideoPlaceholder.supernode == nil {
strongSelf.insertSubnode(strongSelf.secretVideoPlaceholder, aboveSubnode: videoNode)
}
} }
updatedPlayerStatusSignal = videoNode.status updatedPlayerStatusSignal = videoNode.status
@ -867,8 +883,11 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
if let durationNode = strongSelf.durationNode { if let durationNode = strongSelf.durationNode {
var durationFrame = CGRect(origin: CGPoint(x: displayVideoFrame.midX - 56.0 - 25.0 * scaleProgress, y: displayVideoFrame.maxY - 18.0), size: CGSize(width: 1.0, height: 1.0)) var durationFrame = CGRect(origin: CGPoint(x: displayVideoFrame.midX - 56.0 - 25.0 * scaleProgress, y: displayVideoFrame.maxY - 18.0), size: CGSize(width: 1.0, height: 1.0))
if item.presentationData.isPreview {
durationFrame.origin.x -= 9.0
}
durationNode.isSeen = !notConsumed durationNode.isSeen = !notConsumed || item.presentationData.isPreview
let size = durationNode.size let size = durationNode.size
if let durationBackgroundNode = strongSelf.durationBackgroundNode, size.width > 1.0 { if let durationBackgroundNode = strongSelf.durationBackgroundNode, size.width > 1.0 {
durationBackgroundNode.update(size: size, cornerRadius: size.height / 2.0, transition: .immediate) durationBackgroundNode.update(size: size, cornerRadius: size.height / 2.0, transition: .immediate)
@ -898,8 +917,12 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
animation.animator.updateFrame(layer: strongSelf.dateAndStatusNode.layer, frame: CGRect(origin: dateAndStatusOrigin, size: dateAndStatusSize), completion: nil) animation.animator.updateFrame(layer: strongSelf.dateAndStatusNode.layer, frame: CGRect(origin: dateAndStatusOrigin, size: dateAndStatusSize), completion: nil)
case let .constrained(_, right): case let .constrained(_, right):
var dateAndStatusFrame = CGRect(origin: CGPoint(x: min(floorToScreenPixels(displayVideoFrame.midX) + 55.0 + 25.0 * scaleProgress, displayVideoFrame.maxX + right - dateAndStatusSize.width - 4.0), y: displayVideoFrame.maxY - dateAndStatusSize.height), size: dateAndStatusSize) var dateAndStatusFrame = CGRect(origin: CGPoint(x: min(floorToScreenPixels(displayVideoFrame.midX) + 55.0 + 25.0 * scaleProgress, displayVideoFrame.maxX + right - dateAndStatusSize.width - 4.0), y: displayVideoFrame.maxY - dateAndStatusSize.height), size: dateAndStatusSize)
if incoming, let audioTranscriptionButton = strongSelf.audioTranscriptionButton, displayTranscribe { if incoming {
dateAndStatusFrame.origin.x = audioTranscriptionButton.frame.maxX + 7.0 if let audioTranscriptionButton = strongSelf.audioTranscriptionButton, displayTranscribe {
dateAndStatusFrame.origin.x = audioTranscriptionButton.frame.maxX + 7.0
} else if item.presentationData.isPreview {
dateAndStatusFrame.origin.x = displayVideoFrame.midX + 64.0
}
} }
animation.animator.updateFrame(layer: strongSelf.dateAndStatusNode.layer, frame: dateAndStatusFrame, completion: nil) animation.animator.updateFrame(layer: strongSelf.dateAndStatusNode.layer, frame: dateAndStatusFrame, completion: nil)
} }
@ -1146,7 +1169,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
return return
} }
let displayMute: Bool var displayMute: Bool
switch status.mediaStatus { switch status.mediaStatus {
case let .fetchStatus(fetchStatus): case let .fetchStatus(fetchStatus):
switch fetchStatus { switch fetchStatus {
@ -1158,6 +1181,9 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
case .playbackStatus: case .playbackStatus:
displayMute = false displayMute = false
} }
if item.presentationData.isPreview {
displayMute = false
}
if displayMute != (!self.infoBackgroundNode.alpha.isZero) { if displayMute != (!self.infoBackgroundNode.alpha.isZero) {
if displayMute { if displayMute {
self.infoBackgroundNode.alpha = 1.0 self.infoBackgroundNode.alpha = 1.0
@ -1193,10 +1219,14 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
} else if isBuffering ?? false { } else if isBuffering ?? false {
progressRequired = true progressRequired = true
} }
if item.presentationData.isPreview {
progressRequired = true
}
if progressRequired { if progressRequired {
if self.statusNode == nil { if self.statusNode == nil {
let statusNode = RadialStatusNode(backgroundNodeColor: item.presentationData.theme.theme.chat.message.mediaOverlayControlColors.fillColor) let statusNode = RadialStatusNode(backgroundNodeColor: item.presentationData.theme.theme.chat.message.mediaOverlayControlColors.fillColor)
statusNode.displaysAsynchronously = !item.presentationData.isPreview
self.isUserInteractionEnabled = false self.isUserInteractionEnabled = false
self.statusNode = statusNode self.statusNode = statusNode
self.addSubnode(statusNode) self.addSubnode(statusNode)
@ -1270,6 +1300,9 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
state = .none state = .none
} }
} }
if item.presentationData.isPreview {
state = .play(messageTheme.mediaOverlayControlColors.foregroundColor)
}
if let statusNode = self.statusNode { if let statusNode = self.statusNode {
if state == .none { if state == .none {
self.statusNode = nil self.statusNode = nil
@ -1323,7 +1356,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
self.durationNode?.status = .single(nil) self.durationNode?.status = .single(nil)
self.videoNode?.isHidden = isSecretMedia self.videoNode?.isHidden = isSecretMedia
self.secretVideoPlaceholderBackground.isHidden = !isSecretMedia self.secretVideoPlaceholderBackground.isHidden = !isSecretMedia
self.secretVideoPlaceholder.isHidden = !isSecretMedia self.secretVideoPlaceholder.isHidden = !isSecretMedia && !item.presentationData.isPreview
} }
} }

View File

@ -861,7 +861,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
let statusSuggestedWidthAndContinue = statusLayout(ChatMessageDateAndStatusNode.Arguments( let statusSuggestedWidthAndContinue = statusLayout(ChatMessageDateAndStatusNode.Arguments(
context: context, context: context,
presentationData: presentationData, presentationData: presentationData,
edited: dateAndStatus.edited, edited: dateAndStatus.edited && !presentationData.isPreview,
impressionCount: dateAndStatus.viewCount, impressionCount: dateAndStatus.viewCount,
dateText: dateAndStatus.dateText, dateText: dateAndStatus.dateText,
type: dateAndStatus.type, type: dateAndStatus.type,

View File

@ -1751,7 +1751,7 @@ final class MediaEditorScreenComponent: Component {
), ),
colors: ["__allcolors__": .white], colors: ["__allcolors__": .white],
size: CGSize(width: 30.0, height: 30.0) size: CGSize(width: 30.0, height: 30.0)
).tagged(muteButtonTag) ).tagged(dayNightButtonTag)
) )
) )
} else { } else {
@ -1821,11 +1821,11 @@ final class MediaEditorScreenComponent: Component {
topButtonOffsetX += 50.0 topButtonOffsetX += 50.0
} else { } else {
if let muteButtonView = self.muteButton.view, muteButtonView.superview != nil { if let doneButtonView = self.doneButton.view, doneButtonView.superview != nil {
muteButtonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak muteButtonView] _ in doneButtonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak doneButtonView] _ in
muteButtonView?.removeFromSuperview() doneButtonView?.removeFromSuperview()
}) })
muteButtonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false) doneButtonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
} }
} }