mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
[WIP] Star reactions
This commit is contained in:
parent
33cb344b45
commit
3ff98fd2a8
@ -166,6 +166,7 @@ public final class BrowserBookmarksScreen: ViewController {
|
|||||||
}, scrollToMessageId: { _ in
|
}, scrollToMessageId: { _ in
|
||||||
}, navigateToStory: { _, _ in
|
}, navigateToStory: { _, _ in
|
||||||
}, attemptedNavigationToPrivateQuote: { _ in
|
}, attemptedNavigationToPrivateQuote: { _ in
|
||||||
|
}, forceUpdateWarpContents: {
|
||||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil))
|
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil))
|
||||||
|
|
||||||
|
|
||||||
|
@ -176,7 +176,7 @@ public final class ReactionIconView: PortalSourceView {
|
|||||||
animationLayer.masksToBounds = true
|
animationLayer.masksToBounds = true
|
||||||
animationLayer.cornerRadius = floor(size.width * 0.2)
|
animationLayer.cornerRadius = floor(size.width * 0.2)
|
||||||
case .stars:
|
case .stars:
|
||||||
iconSize = CGSize(width: floor(size.width * 2.0), height: floor(size.height * 2.0))
|
iconSize = CGSize(width: floor(size.width * 1.25), height: floor(size.height * 1.25))
|
||||||
animationLayer.masksToBounds = false
|
animationLayer.masksToBounds = false
|
||||||
animationLayer.cornerRadius = 0.0
|
animationLayer.cornerRadius = 0.0
|
||||||
}
|
}
|
||||||
@ -212,7 +212,7 @@ public final class ReactionIconView: PortalSourceView {
|
|||||||
case .custom:
|
case .custom:
|
||||||
iconSize = CGSize(width: floor(size.width * 1.25), height: floor(size.height * 1.25))
|
iconSize = CGSize(width: floor(size.width * 1.25), height: floor(size.height * 1.25))
|
||||||
case .stars:
|
case .stars:
|
||||||
iconSize = CGSize(width: floor(size.width * 2.0), height: floor(size.height * 2.0))
|
iconSize = CGSize(width: floor(size.width * 1.25), height: floor(size.height * 1.25))
|
||||||
}
|
}
|
||||||
|
|
||||||
let animationLayer = InlineStickerItemLayer(
|
let animationLayer = InlineStickerItemLayer(
|
||||||
|
@ -1535,8 +1535,6 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
|||||||
additive: true,
|
additive: true,
|
||||||
completion: { [weak self] _ in
|
completion: { [weak self] _ in
|
||||||
Queue.mainQueue().after(reactionContextNodeIsAnimatingOut ? 0.2 * UIView.animationDurationFactor() : 0.0, {
|
Queue.mainQueue().after(reactionContextNodeIsAnimatingOut ? 0.2 * UIView.animationDurationFactor() : 0.0, {
|
||||||
contentNode.containingItem.isExtractedToContextPreview = false
|
|
||||||
contentNode.containingItem.isExtractedToContextPreviewUpdated?(false)
|
|
||||||
|
|
||||||
if let strongSelf = self, let contentNode = strongSelf.itemContentNode {
|
if let strongSelf = self, let contentNode = strongSelf.itemContentNode {
|
||||||
switch contentNode.containingItem {
|
switch contentNode.containingItem {
|
||||||
@ -1547,6 +1545,9 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contentNode.containingItem.isExtractedToContextPreview = false
|
||||||
|
contentNode.containingItem.isExtractedToContextPreviewUpdated?(false)
|
||||||
|
|
||||||
completion()
|
completion()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -538,6 +538,17 @@ public extension ContainedViewLayoutTransition {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func animateScaleWithKeyframes(layer: CALayer, keyframes: [CGFloat], removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||||
|
switch self {
|
||||||
|
case .immediate:
|
||||||
|
completion?(true)
|
||||||
|
case let .animated(duration, curve):
|
||||||
|
layer.animateKeyframes(values: keyframes.map { NSNumber(value: Float($0)) }, duration: duration, keyPath: "transform.scale", timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, completion: { value in
|
||||||
|
completion?(value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func animateFrame(node: ASDisplayNode, from frame: CGRect, to toFrame: CGRect? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
func animateFrame(node: ASDisplayNode, from frame: CGRect, to toFrame: CGRect? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||||
switch self {
|
switch self {
|
||||||
case .immediate:
|
case .immediate:
|
||||||
|
@ -25,8 +25,9 @@ private final class ChannelAdminsControllerArguments {
|
|||||||
let openAdmin: (ChannelParticipant) -> Void
|
let openAdmin: (ChannelParticipant) -> Void
|
||||||
let updateAntiSpamEnabled: (Bool) -> Void
|
let updateAntiSpamEnabled: (Bool) -> Void
|
||||||
let updateSignMessagesEnabled: (Bool) -> Void
|
let updateSignMessagesEnabled: (Bool) -> Void
|
||||||
|
let updateShowAuthorProfilesEnabled: (Bool) -> Void
|
||||||
|
|
||||||
init(context: AccountContext, openRecentActions: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, removeAdmin: @escaping (EnginePeer.Id) -> Void, addAdmin: @escaping () -> Void, openAdmin: @escaping (ChannelParticipant) -> Void, updateAntiSpamEnabled: @escaping (Bool) -> Void, updateSignMessagesEnabled: @escaping (Bool) -> Void) {
|
init(context: AccountContext, openRecentActions: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, removeAdmin: @escaping (EnginePeer.Id) -> Void, addAdmin: @escaping () -> Void, openAdmin: @escaping (ChannelParticipant) -> Void, updateAntiSpamEnabled: @escaping (Bool) -> Void, updateSignMessagesEnabled: @escaping (Bool) -> Void, updateShowAuthorProfilesEnabled: @escaping (Bool) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.openRecentActions = openRecentActions
|
self.openRecentActions = openRecentActions
|
||||||
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
|
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
|
||||||
@ -35,6 +36,7 @@ private final class ChannelAdminsControllerArguments {
|
|||||||
self.openAdmin = openAdmin
|
self.openAdmin = openAdmin
|
||||||
self.updateAntiSpamEnabled = updateAntiSpamEnabled
|
self.updateAntiSpamEnabled = updateAntiSpamEnabled
|
||||||
self.updateSignMessagesEnabled = updateSignMessagesEnabled
|
self.updateSignMessagesEnabled = updateSignMessagesEnabled
|
||||||
|
self.updateShowAuthorProfilesEnabled = updateShowAuthorProfilesEnabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +62,7 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
|
|||||||
case adminsInfo(PresentationTheme, String)
|
case adminsInfo(PresentationTheme, String)
|
||||||
|
|
||||||
case signMessages(PresentationTheme, String, Bool)
|
case signMessages(PresentationTheme, String, Bool)
|
||||||
|
case showAuthorProfiles(PresentationTheme, String, Bool)
|
||||||
case signMessagesInfo(PresentationTheme, String)
|
case signMessagesInfo(PresentationTheme, String)
|
||||||
|
|
||||||
var section: ItemListSectionId {
|
var section: ItemListSectionId {
|
||||||
@ -68,7 +71,7 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
|
|||||||
return ChannelAdminsSection.administration.rawValue
|
return ChannelAdminsSection.administration.rawValue
|
||||||
case .adminsHeader, .adminPeerItem, .addAdmin, .adminsInfo:
|
case .adminsHeader, .adminPeerItem, .addAdmin, .adminsInfo:
|
||||||
return ChannelAdminsSection.admins.rawValue
|
return ChannelAdminsSection.admins.rawValue
|
||||||
case .signMessages, .signMessagesInfo:
|
case .signMessages, .showAuthorProfiles, .signMessagesInfo:
|
||||||
return ChannelAdminsSection.signMessages.rawValue
|
return ChannelAdminsSection.signMessages.rawValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,8 +94,10 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
|
|||||||
return .peer(participant.peer.id)
|
return .peer(participant.peer.id)
|
||||||
case .signMessages:
|
case .signMessages:
|
||||||
return .index(6)
|
return .index(6)
|
||||||
case .signMessagesInfo:
|
case .showAuthorProfiles:
|
||||||
return .index(7)
|
return .index(7)
|
||||||
|
case .signMessagesInfo:
|
||||||
|
return .index(9)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,6 +181,12 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
case let .showAuthorProfiles(lhsTheme, lhsText, lhsValue):
|
||||||
|
if case let .showAuthorProfiles(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
case let .signMessagesInfo(lhsTheme, lhsText):
|
case let .signMessagesInfo(lhsTheme, lhsText):
|
||||||
if case let .signMessagesInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
if case let .signMessagesInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||||
return true
|
return true
|
||||||
@ -240,6 +251,13 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
|
|||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
case .showAuthorProfiles:
|
||||||
|
switch rhs {
|
||||||
|
case .recentActions, .antiSpam, .antiSpamInfo, .adminsHeader, .addAdmin, .adminPeerItem, .adminsInfo, .signMessages, .showAuthorProfiles:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
case .signMessagesInfo:
|
case .signMessagesInfo:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -301,6 +319,10 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
|
|||||||
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
arguments.updateSignMessagesEnabled(value)
|
arguments.updateSignMessagesEnabled(value)
|
||||||
})
|
})
|
||||||
|
case let .showAuthorProfiles(_, text, value):
|
||||||
|
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
|
arguments.updateShowAuthorProfilesEnabled(value)
|
||||||
|
})
|
||||||
case let .signMessagesInfo(_, text):
|
case let .signMessagesInfo(_, text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||||
}
|
}
|
||||||
@ -381,7 +403,7 @@ private struct ChannelAdminsControllerState: Equatable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func channelAdminsControllerEntries(presentationData: PresentationData, accountPeerId: EnginePeer.Id, peer: EnginePeer?, state: ChannelAdminsControllerState, participants: [RenderedChannelParticipant]?, antiSpamAvailable: Bool, antiSpamEnabled: Bool, signMessagesEnabled: Bool) -> [ChannelAdminsEntry] {
|
private func channelAdminsControllerEntries(presentationData: PresentationData, accountPeerId: EnginePeer.Id, peer: EnginePeer?, state: ChannelAdminsControllerState, participants: [RenderedChannelParticipant]?, antiSpamAvailable: Bool, antiSpamEnabled: Bool, signMessagesEnabled: Bool, showAuthorProfilesEnabled: Bool) -> [ChannelAdminsEntry] {
|
||||||
if participants == nil || participants?.count == nil {
|
if participants == nil || participants?.count == nil {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@ -476,7 +498,9 @@ private func channelAdminsControllerEntries(presentationData: PresentationData,
|
|||||||
|
|
||||||
if !isGroup && peer.hasPermission(.sendSomething) {
|
if !isGroup && peer.hasPermission(.sendSomething) {
|
||||||
entries.append(.signMessages(presentationData.theme, presentationData.strings.Channel_SignMessages, signMessagesEnabled))
|
entries.append(.signMessages(presentationData.theme, presentationData.strings.Channel_SignMessages, signMessagesEnabled))
|
||||||
entries.append(.signMessagesInfo(presentationData.theme, presentationData.strings.Channel_SignMessages_Help))
|
//TODO:localize
|
||||||
|
entries.append(.showAuthorProfiles(presentationData.theme, "Show Authors' Profiles", showAuthorProfilesEnabled))
|
||||||
|
entries.append(.signMessagesInfo(presentationData.theme, "Add names and photos of admins to the messages they post, linking to their profiles."))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if case let .legacyGroup(peer) = peer {
|
} else if case let .legacyGroup(peer) = peer {
|
||||||
@ -588,6 +612,9 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
|
|||||||
let updateSignMessagesDisposable = MetaDisposable()
|
let updateSignMessagesDisposable = MetaDisposable()
|
||||||
actionsDisposable.add(updateSignMessagesDisposable)
|
actionsDisposable.add(updateSignMessagesDisposable)
|
||||||
|
|
||||||
|
let updateShowAuthorProfilesDisposable = MetaDisposable()
|
||||||
|
actionsDisposable.add(updateShowAuthorProfilesDisposable)
|
||||||
|
|
||||||
let adminsPromise = Promise<[RenderedChannelParticipant]?>(nil)
|
let adminsPromise = Promise<[RenderedChannelParticipant]?>(nil)
|
||||||
|
|
||||||
let antiSpamConfiguration = AntiSpamBotConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
let antiSpamConfiguration = AntiSpamBotConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
||||||
@ -790,6 +817,12 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
|
|||||||
|> deliverOnMainQueue).start(next: { peerId in
|
|> deliverOnMainQueue).start(next: { peerId in
|
||||||
updateSignMessagesDisposable.set(context.engine.peers.toggleShouldChannelMessagesSignatures(peerId: peerId, enabled: value).start())
|
updateSignMessagesDisposable.set(context.engine.peers.toggleShouldChannelMessagesSignatures(peerId: peerId, enabled: value).start())
|
||||||
})
|
})
|
||||||
|
}, updateShowAuthorProfilesEnabled: { value in
|
||||||
|
let _ = (currentPeerId.get()
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOnMainQueue).start(next: { peerId in
|
||||||
|
updateSignMessagesDisposable.set(context.engine.peers.toggleShouldChannelMessagesSignatures(peerId: peerId, enabled: value).start())
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
let membersAndLoadMoreControlValue = Atomic<(Disposable, PeerChannelMemberCategoryControl?)?>(value: nil)
|
let membersAndLoadMoreControlValue = Atomic<(Disposable, PeerChannelMemberCategoryControl?)?>(value: nil)
|
||||||
@ -911,8 +944,10 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
|
|||||||
}
|
}
|
||||||
|
|
||||||
var signMessagesEnabled = false
|
var signMessagesEnabled = false
|
||||||
|
var showAuthorProfilesEnabled = false
|
||||||
if case let .channel(channel) = view.peer, case let .broadcast(info) = channel.info {
|
if case let .channel(channel) = view.peer, case let .broadcast(info) = channel.info {
|
||||||
signMessagesEnabled = info.flags.contains(.messagesShouldHaveSignatures)
|
signMessagesEnabled = info.flags.contains(.messagesShouldHaveSignatures)
|
||||||
|
showAuthorProfilesEnabled = info.flags.contains(.messagesShouldHaveSignatures)
|
||||||
}
|
}
|
||||||
|
|
||||||
var rightNavigationButton: ItemListNavigationButton?
|
var rightNavigationButton: ItemListNavigationButton?
|
||||||
@ -986,7 +1021,7 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
|
|||||||
}
|
}
|
||||||
|
|
||||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(isGroup ? presentationData.strings.ChatAdmins_Title : presentationData.strings.Channel_Management_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, secondaryRightNavigationButton: secondaryRightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(isGroup ? presentationData.strings.ChatAdmins_Title : presentationData.strings.Channel_Management_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, secondaryRightNavigationButton: secondaryRightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelAdminsControllerEntries(presentationData: presentationData, accountPeerId: context.account.peerId, peer: view.peer, state: state, participants: admins, antiSpamAvailable: antiSpamAvailable, antiSpamEnabled: antiSpamEnabled, signMessagesEnabled: signMessagesEnabled), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, animateChanges: previous != nil && admins != nil && previous!.count >= admins!.count)
|
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelAdminsControllerEntries(presentationData: presentationData, accountPeerId: context.account.peerId, peer: view.peer, state: state, participants: admins, antiSpamAvailable: antiSpamAvailable, antiSpamEnabled: antiSpamEnabled, signMessagesEnabled: signMessagesEnabled, showAuthorProfilesEnabled: showAuthorProfilesEnabled), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, animateChanges: previous != nil && admins != nil && previous!.count >= admins!.count)
|
||||||
|
|
||||||
return (controllerState, (listState, arguments))
|
return (controllerState, (listState, arguments))
|
||||||
} |> afterDisposed {
|
} |> afterDisposed {
|
||||||
|
@ -38,6 +38,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/Utils/GenerateStickerPlaceholderImage",
|
"//submodules/TelegramUI/Components/Utils/GenerateStickerPlaceholderImage",
|
||||||
"//submodules/Components/BalancedTextComponent",
|
"//submodules/Components/BalancedTextComponent",
|
||||||
"//submodules/Markdown",
|
"//submodules/Markdown",
|
||||||
|
"//submodules/TelegramUI/Components/Premium/PremiumStarComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -24,6 +24,7 @@ import TextFormat
|
|||||||
import GZip
|
import GZip
|
||||||
import BalancedTextComponent
|
import BalancedTextComponent
|
||||||
import Markdown
|
import Markdown
|
||||||
|
import PremiumStarComponent
|
||||||
|
|
||||||
public final class ReactionItem {
|
public final class ReactionItem {
|
||||||
public struct Reaction: Equatable {
|
public struct Reaction: Equatable {
|
||||||
@ -2672,7 +2673,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
if case .builtin = itemNode.item.reaction.rawValue {
|
if case .builtin = itemNode.item.reaction.rawValue {
|
||||||
selfTargetBounds = selfTargetBounds.insetBy(dx: -selfTargetBounds.width * 0.5, dy: -selfTargetBounds.height * 0.5)
|
selfTargetBounds = selfTargetBounds.insetBy(dx: -selfTargetBounds.width * 0.5, dy: -selfTargetBounds.height * 0.5)
|
||||||
} else if case .stars = itemNode.item.reaction.rawValue {
|
} else if case .stars = itemNode.item.reaction.rawValue {
|
||||||
selfTargetBounds = selfTargetBounds.insetBy(dx: -selfTargetBounds.width * 0.5, dy: -selfTargetBounds.height * 0.5)
|
selfTargetBounds = selfTargetBounds.insetBy(dx: selfTargetBounds.width * 0.0, dy: selfTargetBounds.height * 0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
let selfTargetRect = self.view.convert(selfTargetBounds, from: targetView)
|
let selfTargetRect = self.view.convert(selfTargetBounds, from: targetView)
|
||||||
@ -2863,7 +2864,6 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
strongSelf.hapticFeedback = HapticFeedback()
|
strongSelf.hapticFeedback = HapticFeedback()
|
||||||
}
|
}
|
||||||
strongSelf.hapticFeedback?.tap()
|
strongSelf.hapticFeedback?.tap()
|
||||||
onHit?()
|
|
||||||
|
|
||||||
if let targetView = targetView as? ReactionIconView {
|
if let targetView = targetView as? ReactionIconView {
|
||||||
if switchToInlineImmediately {
|
if switchToInlineImmediately {
|
||||||
@ -2886,6 +2886,8 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onHit?()
|
||||||
|
|
||||||
if switchToInlineImmediately {
|
if switchToInlineImmediately {
|
||||||
afterCompletion()
|
afterCompletion()
|
||||||
} else {
|
} else {
|
||||||
@ -3723,7 +3725,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
if let itemNode = self.itemNode, case .builtin = itemNode.item.reaction.rawValue {
|
if let itemNode = self.itemNode, case .builtin = itemNode.item.reaction.rawValue {
|
||||||
selfTargetBounds = selfTargetBounds.insetBy(dx: -selfTargetBounds.width * 0.5, dy: -selfTargetBounds.height * 0.5)
|
selfTargetBounds = selfTargetBounds.insetBy(dx: -selfTargetBounds.width * 0.5, dy: -selfTargetBounds.height * 0.5)
|
||||||
} else if let itemNode = self.itemNode, case .stars = itemNode.item.reaction.rawValue {
|
} else if let itemNode = self.itemNode, case .stars = itemNode.item.reaction.rawValue {
|
||||||
selfTargetBounds = selfTargetBounds.insetBy(dx: -selfTargetBounds.width * 0.5, dy: -selfTargetBounds.height * 0.5)
|
selfTargetBounds = selfTargetBounds.insetBy(dx: -selfTargetBounds.width * 0.0, dy: -selfTargetBounds.height * 0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
var targetFrame = self.view.convert(targetView.convert(selfTargetBounds, to: nil), from: nil)
|
var targetFrame = self.view.convert(targetView.convert(selfTargetBounds, to: nil), from: nil)
|
||||||
@ -3731,7 +3733,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
if let itemNode = self.itemNode, case .builtin = itemNode.item.reaction.rawValue {
|
if let itemNode = self.itemNode, case .builtin = itemNode.item.reaction.rawValue {
|
||||||
targetFrame = targetFrame.insetBy(dx: -targetFrame.width * 0.5, dy: -targetFrame.height * 0.5)
|
targetFrame = targetFrame.insetBy(dx: -targetFrame.width * 0.5, dy: -targetFrame.height * 0.5)
|
||||||
} else if let itemNode = self.itemNode, case .stars = itemNode.item.reaction.rawValue {
|
} else if let itemNode = self.itemNode, case .stars = itemNode.item.reaction.rawValue {
|
||||||
targetFrame = targetFrame.insetBy(dx: -targetFrame.width * 0.5, dy: -targetFrame.height * 0.5)
|
targetFrame = targetFrame.insetBy(dx: -targetFrame.width * 0.0, dy: -targetFrame.height * 0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
targetSnapshotView.frame = targetFrame
|
targetSnapshotView.frame = targetFrame
|
||||||
@ -3778,30 +3780,43 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func animateOutToReaction(context: AccountContext, theme: PresentationTheme, item: ReactionItem, value: MessageReaction.Reaction, sourceView: UIView, targetView: UIView, hideNode: Bool, forceSwitchToInlineImmediately: Bool = false, animateTargetContainer: UIView?, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, onHit: (() -> Void)?, completion: @escaping () -> Void) {
|
public func animateOutToReaction(context: AccountContext, theme: PresentationTheme, item: ReactionItem, value: MessageReaction.Reaction, sourceView: UIView, targetView: UIView, hideNode: Bool, forceSwitchToInlineImmediately: Bool = false, animateTargetContainer: UIView?, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, onHit: (() -> Void)?, completion: @escaping () -> Void) {
|
||||||
let didTriggerExpandedReaction = !"".isEmpty
|
let star = ComponentView<Empty>()
|
||||||
|
let starSize = star.update(
|
||||||
let itemNode = ReactionNode(context: context, theme: theme, item: item, icon: .none, animationCache: context.animationCache, animationRenderer: context.animationRenderer, loopIdle: false, isLocked: false, useDirectRendering: true)
|
transition: .immediate,
|
||||||
if let contents = sourceView.layer.contents {
|
component: AnyComponent(StandalonePremiumStarComponent(
|
||||||
itemNode.setCustomContents(contents: contents)
|
theme: theme,
|
||||||
|
colors: [
|
||||||
|
UIColor(rgb: 0xe57d02),
|
||||||
|
UIColor(rgb: 0xf09903),
|
||||||
|
UIColor(rgb: 0xf9b004),
|
||||||
|
UIColor(rgb: 0xfdd219)
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: 200.0, height: 200.0)
|
||||||
|
)
|
||||||
|
guard let starView = star.view else {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
self.addSubnode(itemNode)
|
|
||||||
itemNode.frame = sourceView.convert(sourceView.bounds, to: self.view)
|
guard let sourceCloneView = sourceView.snapshotContentTree() else {
|
||||||
itemNode.updateLayout(size: itemNode.frame.size, isExpanded: false, largeExpanded: false, isPreviewing: false, transition: .immediate)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.view.addSubview(sourceCloneView)
|
||||||
|
self.view.addSubview(starView)
|
||||||
|
|
||||||
|
sourceCloneView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.08, removeOnCompletion: false)
|
||||||
|
starView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12)
|
||||||
|
|
||||||
|
let didTriggerExpandedReaction = "".isEmpty
|
||||||
|
|
||||||
|
let sourceFrame = sourceView.convert(sourceView.bounds, to: self.view)
|
||||||
|
starView.bounds = CGRect(origin: CGPoint(), size: starSize)
|
||||||
|
|
||||||
sourceView.layer.isHidden = true
|
sourceView.layer.isHidden = true
|
||||||
|
|
||||||
let switchToInlineImmediately: Bool
|
let switchToInlineImmediately: Bool = "".isEmpty
|
||||||
if itemNode.item.listAnimation.isVideoEmoji || itemNode.item.listAnimation.isVideoSticker || itemNode.item.listAnimation.isAnimatedSticker || itemNode.item.listAnimation.isStaticEmoji {
|
|
||||||
switch itemNode.item.reaction.rawValue {
|
|
||||||
case .builtin:
|
|
||||||
switchToInlineImmediately = forceSwitchToInlineImmediately
|
|
||||||
case .custom:
|
|
||||||
switchToInlineImmediately = !didTriggerExpandedReaction
|
|
||||||
case .stars:
|
|
||||||
switchToInlineImmediately = forceSwitchToInlineImmediately
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switchToInlineImmediately = !didTriggerExpandedReaction
|
|
||||||
}
|
|
||||||
|
|
||||||
if hideNode {
|
if hideNode {
|
||||||
if let animateTargetContainer = animateTargetContainer {
|
if let animateTargetContainer = animateTargetContainer {
|
||||||
@ -3812,26 +3827,20 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
targetView.layer.animateAlpha(from: targetView.alpha, to: 0.0, duration: 0.2)
|
targetView.layer.animateAlpha(from: targetView.alpha, to: 0.0, duration: 0.2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let targetView = targetView as? ReactionIconView {
|
||||||
|
targetView.updateIsAnimationHidden(isAnimationHidden: true, transition: .immediate)
|
||||||
|
}
|
||||||
|
|
||||||
itemNode.isExtracted = true
|
let selfSourceRect = sourceFrame
|
||||||
let selfSourceRect = itemNode.view.convert(itemNode.view.bounds, to: self.view)
|
|
||||||
|
|
||||||
var selfTargetBounds = targetView.bounds
|
var selfTargetBounds = targetView.bounds
|
||||||
if case .builtin = itemNode.item.reaction.rawValue {
|
selfTargetBounds = selfTargetBounds.insetBy(dx: -selfTargetBounds.width * 0.5, dy: -selfTargetBounds.height * 0.5)
|
||||||
selfTargetBounds = selfTargetBounds.insetBy(dx: -selfTargetBounds.width * 0.5, dy: -selfTargetBounds.height * 0.5)
|
|
||||||
} else if case .stars = itemNode.item.reaction.rawValue {
|
|
||||||
selfTargetBounds = selfTargetBounds.insetBy(dx: -selfTargetBounds.width * 0.5, dy: -selfTargetBounds.height * 0.5)
|
|
||||||
}
|
|
||||||
|
|
||||||
let selfTargetRect = self.view.convert(selfTargetBounds, from: targetView)
|
let selfTargetRect = self.view.convert(selfTargetBounds, from: targetView)
|
||||||
|
|
||||||
var expandedSize: CGSize = selfTargetRect.size
|
var expandedSize: CGSize = selfTargetRect.size
|
||||||
if didTriggerExpandedReaction {
|
if didTriggerExpandedReaction {
|
||||||
if itemNode.item.listAnimation.isVideoEmoji || itemNode.item.listAnimation.isVideoSticker || itemNode.item.listAnimation.isStaticEmoji {
|
expandedSize = CGSize(width: 120.0, height: 120.0)
|
||||||
expandedSize = CGSize(width: 80.0, height: 80.0)
|
|
||||||
} else {
|
|
||||||
expandedSize = CGSize(width: 120.0, height: 120.0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let expandedFrame = CGRect(origin: CGPoint(x: selfTargetRect.midX - expandedSize.width / 2.0, y: selfTargetRect.midY - expandedSize.height / 2.0), size: expandedSize)
|
let expandedFrame = CGRect(origin: CGPoint(x: selfTargetRect.midX - expandedSize.width / 2.0, y: selfTargetRect.midY - expandedSize.height / 2.0), size: expandedSize)
|
||||||
@ -3840,29 +3849,24 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
let incomingMessage: Bool = expandedFrame.midX < self.bounds.width / 2.0
|
let incomingMessage: Bool = expandedFrame.midX < self.bounds.width / 2.0
|
||||||
if didTriggerExpandedReaction {
|
if didTriggerExpandedReaction {
|
||||||
let expandFactor: CGFloat = 0.5
|
let expandFactor: CGFloat = 0.5
|
||||||
effectFrame = expandedFrame.insetBy(dx: -expandedFrame.width * expandFactor, dy: -expandedFrame.height * expandFactor).offsetBy(dx: incomingMessage ? (expandedFrame.width - 50.0) : (-expandedFrame.width + 50.0), dy: 0.0)
|
effectFrame = expandedFrame.insetBy(dx: -expandedFrame.width * expandFactor, dy: -expandedFrame.height * expandFactor)//.offsetBy(dx: incomingMessage ? (expandedFrame.width - 50.0) : (-expandedFrame.width + 50.0), dy: 0.0)
|
||||||
} else {
|
} else {
|
||||||
effectFrame = expandedFrame.insetBy(dx: -expandedSize.width, dy: -expandedSize.height)
|
effectFrame = expandedFrame.insetBy(dx: -expandedSize.width, dy: -expandedSize.height)
|
||||||
if itemNode.item.isCustom {
|
|
||||||
effectFrame = effectFrame.insetBy(dx: -expandedSize.width, dy: -expandedSize.height)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .linear)
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .linear)
|
||||||
|
|
||||||
self.addSubnode(itemNode)
|
starView.center = expandedFrame.center
|
||||||
itemNode.position = expandedFrame.center
|
sourceCloneView.frame = sourceFrame
|
||||||
transition.updateBounds(node: itemNode, bounds: CGRect(origin: CGPoint(), size: expandedFrame.size))
|
|
||||||
itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, largeExpanded: didTriggerExpandedReaction, isPreviewing: false, transition: transition)
|
|
||||||
|
|
||||||
let additionalAnimationNode: DefaultAnimatedStickerNodeImpl?
|
let additionalAnimationNode: DefaultAnimatedStickerNodeImpl?
|
||||||
var genericAnimationView: AnimationView?
|
var genericAnimationView: AnimationView?
|
||||||
|
|
||||||
var additionalAnimation: TelegramMediaFile?
|
var additionalAnimation: TelegramMediaFile?
|
||||||
if didTriggerExpandedReaction {
|
if didTriggerExpandedReaction {
|
||||||
additionalAnimation = itemNode.item.largeApplicationAnimation
|
additionalAnimation = item.largeApplicationAnimation
|
||||||
} else {
|
} else {
|
||||||
additionalAnimation = itemNode.item.applicationAnimation
|
additionalAnimation = item.applicationAnimation
|
||||||
}
|
}
|
||||||
|
|
||||||
if let additionalAnimation = additionalAnimation {
|
if let additionalAnimation = additionalAnimation {
|
||||||
@ -3874,11 +3878,11 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
additionalAnimationNodeValue.setup(source: AnimatedStickerResourceSource(account: itemNode.context.account, resource: additionalAnimation.resource), width: Int(effectFrame.width * 2.0), height: Int(effectFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(additionalAnimation.resource.id)))
|
additionalAnimationNodeValue.setup(source: AnimatedStickerResourceSource(account: context.account, resource: additionalAnimation.resource), width: Int(effectFrame.width * 2.0), height: Int(effectFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(additionalAnimation.resource.id)))
|
||||||
additionalAnimationNodeValue.frame = effectFrame
|
additionalAnimationNodeValue.frame = effectFrame
|
||||||
additionalAnimationNodeValue.updateLayout(size: effectFrame.size)
|
additionalAnimationNodeValue.updateLayout(size: effectFrame.size)
|
||||||
self.addSubnode(additionalAnimationNodeValue)
|
self.addSubnode(additionalAnimationNodeValue)
|
||||||
} else if itemNode.item.isCustom {
|
} else if item.isCustom {
|
||||||
additionalAnimationNode = nil
|
additionalAnimationNode = nil
|
||||||
|
|
||||||
var effectData: Data?
|
var effectData: Data?
|
||||||
@ -3906,36 +3910,6 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
|
|
||||||
genericAnimationView = view
|
genericAnimationView = view
|
||||||
|
|
||||||
let animationCache = itemNode.context.animationCache
|
|
||||||
let animationRenderer = itemNode.context.animationRenderer
|
|
||||||
|
|
||||||
for i in 1 ... 32 {
|
|
||||||
let allLayers = view.allLayers(forKeypath: AnimationKeypath(keypath: "placeholder_\(i)"))
|
|
||||||
for animationLayer in allLayers {
|
|
||||||
let baseItemLayer = InlineStickerItemLayer(
|
|
||||||
context: itemNode.context,
|
|
||||||
userLocation: .other,
|
|
||||||
attemptSynchronousLoad: false,
|
|
||||||
emoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: itemNode.item.listAnimation.fileId.id, file: itemNode.item.listAnimation),
|
|
||||||
file: itemNode.item.listAnimation,
|
|
||||||
cache: animationCache,
|
|
||||||
renderer: animationRenderer,
|
|
||||||
placeholderColor: UIColor(white: 0.0, alpha: 0.0),
|
|
||||||
pointSize: CGSize(width: didTriggerExpandedReaction ? 64.0 : 32.0, height: didTriggerExpandedReaction ? 64.0 : 32.0)
|
|
||||||
)
|
|
||||||
|
|
||||||
if let sublayers = animationLayer.sublayers {
|
|
||||||
for sublayer in sublayers {
|
|
||||||
sublayer.isHidden = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
baseItemLayer.isVisibleForAnimations = true
|
|
||||||
baseItemLayer.frame = CGRect(origin: CGPoint(x: -0.0, y: -0.0), size: CGSize(width: 500.0, height: 500.0))
|
|
||||||
animationLayer.addSublayer(baseItemLayer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if didTriggerExpandedReaction {
|
if didTriggerExpandedReaction {
|
||||||
view.frame = effectFrame.insetBy(dx: -10.0, dy: -10.0).offsetBy(dx: incomingMessage ? 22.0 : -22.0, dy: 0.0)
|
view.frame = effectFrame.insetBy(dx: -10.0, dy: -10.0).offsetBy(dx: incomingMessage ? 22.0 : -22.0, dy: 0.0)
|
||||||
} else {
|
} else {
|
||||||
@ -3969,12 +3943,19 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
additionalAnimationCompleted = true
|
additionalAnimationCompleted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.animatePositionWithKeyframes(node: itemNode, keyframes: generateParabollicMotionKeyframes(from: selfSourceRect.center, to: expandedFrame.center, elevation: 30.0), completion: { [weak itemNode, weak targetView, weak animateTargetContainer] _ in
|
starView.center = selfTargetRect.center
|
||||||
|
sourceCloneView.center = selfTargetRect.center
|
||||||
|
|
||||||
|
let starSourceScale = sourceFrame.width / starSize.width
|
||||||
|
let starDestinationScale = selfTargetRect.width / starSize.width
|
||||||
|
|
||||||
|
let keyframes = generateParabollicMotionKeyframes(from: selfSourceRect.center, to: expandedFrame.center, elevation: 40.0)
|
||||||
|
let scaleKeyframes = generateScaleKeyframes(from: starSourceScale, center: 1.0, to: starDestinationScale)
|
||||||
|
starView.layer.transform = CATransform3DMakeScale(starDestinationScale, starDestinationScale, 1.0)
|
||||||
|
transition.animateScaleWithKeyframes(layer: starView.layer, keyframes: scaleKeyframes)
|
||||||
|
transition.animatePositionWithKeyframes(layer: starView.layer, keyframes: keyframes, completion: { [weak starView, weak targetView, weak animateTargetContainer] _ in
|
||||||
let afterCompletion: () -> Void = {
|
let afterCompletion: () -> Void = {
|
||||||
if didTriggerExpandedReaction {
|
guard let starView else {
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let itemNode = itemNode else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let animateTargetContainer = animateTargetContainer {
|
if let animateTargetContainer = animateTargetContainer {
|
||||||
@ -3988,19 +3969,18 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HapticFeedback().tap()
|
HapticFeedback().tap()
|
||||||
onHit?()
|
|
||||||
|
|
||||||
if let targetView = targetView as? ReactionIconView {
|
if let targetView = targetView as? ReactionIconView {
|
||||||
if switchToInlineImmediately {
|
if switchToInlineImmediately {
|
||||||
targetView.updateIsAnimationHidden(isAnimationHidden: false, transition: .immediate)
|
targetView.updateIsAnimationHidden(isAnimationHidden: false, transition: .immediate)
|
||||||
itemNode.isHidden = true
|
starView.isHidden = true
|
||||||
} else {
|
} else {
|
||||||
targetView.updateIsAnimationHidden(isAnimationHidden: true, transition: .immediate)
|
targetView.updateIsAnimationHidden(isAnimationHidden: true, transition: .immediate)
|
||||||
targetView.addSubnode(itemNode)
|
//TODO:release
|
||||||
itemNode.frame = selfTargetBounds
|
//targetView.addSubnode(itemNode)
|
||||||
}
|
}
|
||||||
} else if let targetView = targetView as? UIImageView {
|
} else if let targetView = targetView as? UIImageView {
|
||||||
itemNode.isHidden = true
|
starView.isHidden = true
|
||||||
targetView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12)
|
targetView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12)
|
||||||
targetView.layer.animateScale(from: 0.2, to: 1.0, duration: 0.12)
|
targetView.layer.animateScale(from: 0.2, to: 1.0, duration: 0.12)
|
||||||
}
|
}
|
||||||
@ -4011,14 +3991,17 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onHit?()
|
||||||
|
|
||||||
if switchToInlineImmediately {
|
if switchToInlineImmediately {
|
||||||
afterCompletion()
|
afterCompletion()
|
||||||
} else {
|
} else {
|
||||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: afterCompletion)
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: afterCompletion)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
transition.animatePositionWithKeyframes(layer: sourceCloneView.layer, keyframes: keyframes)
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.15 * UIView.animationDurationFactor(), execute: {
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3 * UIView.animationDurationFactor(), execute: {
|
||||||
additionalAnimationNode?.visibility = true
|
additionalAnimationNode?.visibility = true
|
||||||
if let animateTargetContainer = animateTargetContainer {
|
if let animateTargetContainer = animateTargetContainer {
|
||||||
animateTargetContainer.isHidden = false
|
animateTargetContainer.isHidden = false
|
||||||
@ -4029,52 +4012,15 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
|
|
||||||
if !switchToInlineImmediately {
|
if !switchToInlineImmediately {
|
||||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + min(5.0, 2.0 * UIView.animationDurationFactor()), execute: {
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + min(5.0, 2.0 * UIView.animationDurationFactor()), execute: {
|
||||||
if didTriggerExpandedReaction {
|
if hideNode {
|
||||||
self.animateFromItemNodeToReaction(itemNode: itemNode, targetView: targetView, hideNode: hideNode, completion: { [weak self] in
|
targetView.alpha = 1.0
|
||||||
if let strongSelf = self, didTriggerExpandedReaction, let addStandaloneReactionAnimation = addStandaloneReactionAnimation {
|
targetView.isHidden = false
|
||||||
let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: strongSelf.genericReactionEffect)
|
if let targetView = targetView as? ReactionIconView {
|
||||||
|
targetView.updateIsAnimationHidden(isAnimationHidden: false, transition: .immediate)
|
||||||
addStandaloneReactionAnimation(standaloneReactionAnimation)
|
|
||||||
|
|
||||||
standaloneReactionAnimation.animateReactionSelection(
|
|
||||||
context: context,
|
|
||||||
theme: theme,
|
|
||||||
animationCache: context.animationCache,
|
|
||||||
reaction: itemNode.item,
|
|
||||||
avatarPeers: [],
|
|
||||||
playHaptic: false,
|
|
||||||
isLarge: false,
|
|
||||||
targetView: targetView,
|
|
||||||
addStandaloneReactionAnimation: nil,
|
|
||||||
completion: { [weak standaloneReactionAnimation] in
|
|
||||||
if let _ = standaloneReactionAnimation?.supernode {
|
|
||||||
standaloneReactionAnimation?.removeFromSupernode()
|
|
||||||
} else {
|
|
||||||
standaloneReactionAnimation?.view.removeFromSuperview()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
mainAnimationCompleted = true
|
|
||||||
intermediateCompletion()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
if hideNode {
|
|
||||||
targetView.alpha = 1.0
|
|
||||||
targetView.isHidden = false
|
|
||||||
if let targetView = targetView as? ReactionIconView {
|
|
||||||
targetView.updateIsAnimationHidden(isAnimationHidden: false, transition: .immediate)
|
|
||||||
if let _ = itemNode.supernode {
|
|
||||||
itemNode.removeFromSupernode()
|
|
||||||
} else {
|
|
||||||
itemNode.view.removeFromSuperview()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
mainAnimationCompleted = true
|
|
||||||
intermediateCompletion()
|
|
||||||
}
|
}
|
||||||
|
mainAnimationCompleted = true
|
||||||
|
intermediateCompletion()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4182,3 +4128,24 @@ private func generateParabollicMotionKeyframes(from sourcePoint: CGPoint, to tar
|
|||||||
|
|
||||||
return keyframes
|
return keyframes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func generateScaleKeyframes(from: CGFloat, center: CGFloat, to: CGFloat) -> [CGFloat] {
|
||||||
|
var keyframes: [CGFloat] = []
|
||||||
|
for i in 0 ..< 10 {
|
||||||
|
var k = CGFloat(i) / CGFloat(10 - 1)
|
||||||
|
let valueFrom: CGFloat
|
||||||
|
let valueTo: CGFloat
|
||||||
|
if k <= 0.5 {
|
||||||
|
k /= 0.5
|
||||||
|
valueFrom = from
|
||||||
|
valueTo = center
|
||||||
|
} else {
|
||||||
|
k = (k - 0.5) / 0.5
|
||||||
|
valueFrom = center
|
||||||
|
valueTo = to
|
||||||
|
}
|
||||||
|
let value = valueFrom * (1.0 - k) + valueTo * k
|
||||||
|
keyframes.append(value)
|
||||||
|
}
|
||||||
|
return keyframes
|
||||||
|
}
|
||||||
|
@ -3,6 +3,45 @@ import TelegramApi
|
|||||||
import Postbox
|
import Postbox
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
|
||||||
|
private func generateStarsReactionFile(kind: Int, isAnimatedSticker: Bool) -> TelegramMediaFile {
|
||||||
|
let baseId: Int64 = 52343278047832950
|
||||||
|
let fileId = baseId + Int64(kind)
|
||||||
|
|
||||||
|
var attributes: [TelegramMediaFileAttribute] = []
|
||||||
|
attributes.append(TelegramMediaFileAttribute.FileName(fileName: isAnimatedSticker ? "sticker.tgs" : "sticker.webp"))
|
||||||
|
if !isAnimatedSticker {
|
||||||
|
attributes.append(.CustomEmoji(isPremium: false, isSingleColor: false, alt: ".", packReference: nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
return TelegramMediaFile(
|
||||||
|
fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: fileId),
|
||||||
|
partialReference: nil,
|
||||||
|
resource: LocalFileMediaResource(fileId: fileId),
|
||||||
|
previewRepresentations: [],
|
||||||
|
videoThumbnails: [],
|
||||||
|
immediateThumbnailData: nil,
|
||||||
|
mimeType: isAnimatedSticker ? "application/x-tgsticker" : "image/webp",
|
||||||
|
size: nil,
|
||||||
|
attributes: attributes
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func generateStarsReaction() -> AvailableReactions.Reaction {
|
||||||
|
return AvailableReactions.Reaction(
|
||||||
|
isEnabled: false,
|
||||||
|
isPremium: false,
|
||||||
|
value: .stars,
|
||||||
|
title: "Star",
|
||||||
|
staticIcon: generateStarsReactionFile(kind: 0, isAnimatedSticker: true),
|
||||||
|
appearAnimation: generateStarsReactionFile(kind: 1, isAnimatedSticker: true),
|
||||||
|
selectAnimation: generateStarsReactionFile(kind: 2, isAnimatedSticker: true),
|
||||||
|
activateAnimation: generateStarsReactionFile(kind: 3, isAnimatedSticker: true),
|
||||||
|
effectAnimation: generateStarsReactionFile(kind: 4, isAnimatedSticker: true),
|
||||||
|
aroundAnimation: generateStarsReactionFile(kind: 5, isAnimatedSticker: true),
|
||||||
|
centerAnimation: generateStarsReactionFile(kind: 6, isAnimatedSticker: true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
public final class AvailableReactions: Equatable, Codable {
|
public final class AvailableReactions: Equatable, Codable {
|
||||||
public final class Reaction: Equatable, Codable {
|
public final class Reaction: Equatable, Codable {
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
@ -181,24 +220,10 @@ public final class AvailableReactions: Equatable, Codable {
|
|||||||
) {
|
) {
|
||||||
self.hash = hash
|
self.hash = hash
|
||||||
|
|
||||||
//TODO:release
|
|
||||||
var reactions = reactions
|
var reactions = reactions
|
||||||
reactions.removeAll(where: { if case .stars = $0.value { return true } else { return false } })
|
reactions.removeAll(where: { if case .stars = $0.value { return true } else { return false } })
|
||||||
if let item = reactions.first(where: { if case .builtin("🤩") = $0.value { return true } else { return false } }) {
|
//TODO:release
|
||||||
reactions.append(Reaction(
|
reactions.append(generateStarsReaction())
|
||||||
isEnabled: false,
|
|
||||||
isPremium: false,
|
|
||||||
value: .stars,
|
|
||||||
title: "Star",
|
|
||||||
staticIcon: item.staticIcon,
|
|
||||||
appearAnimation: item.appearAnimation,
|
|
||||||
selectAnimation: item.selectAnimation,
|
|
||||||
activateAnimation: item.activateAnimation,
|
|
||||||
effectAnimation: item.effectAnimation,
|
|
||||||
aroundAnimation: item.aroundAnimation,
|
|
||||||
centerAnimation: item.centerAnimation
|
|
||||||
))
|
|
||||||
}
|
|
||||||
self.reactions = reactions
|
self.reactions = reactions
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,21 +245,7 @@ public final class AvailableReactions: Equatable, Codable {
|
|||||||
//TODO:release
|
//TODO:release
|
||||||
var reactions = try container.decode([Reaction].self, forKey: .reactions)
|
var reactions = try container.decode([Reaction].self, forKey: .reactions)
|
||||||
reactions.removeAll(where: { if case .stars = $0.value { return true } else { return false } })
|
reactions.removeAll(where: { if case .stars = $0.value { return true } else { return false } })
|
||||||
if let item = reactions.first(where: { if case .builtin("🤩") = $0.value { return true } else { return false } }) {
|
reactions.append(generateStarsReaction())
|
||||||
reactions.append(Reaction(
|
|
||||||
isEnabled: false,
|
|
||||||
isPremium: false,
|
|
||||||
value: .stars,
|
|
||||||
title: "Star",
|
|
||||||
staticIcon: item.staticIcon,
|
|
||||||
appearAnimation: item.appearAnimation,
|
|
||||||
selectAnimation: item.selectAnimation,
|
|
||||||
activateAnimation: item.activateAnimation,
|
|
||||||
effectAnimation: item.effectAnimation,
|
|
||||||
aroundAnimation: item.aroundAnimation,
|
|
||||||
centerAnimation: item.centerAnimation
|
|
||||||
))
|
|
||||||
}
|
|
||||||
self.reactions = reactions
|
self.reactions = reactions
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,6 +325,31 @@ func _internal_setCachedAvailableReactions(transaction: Transaction, availableRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
func managedSynchronizeAvailableReactions(postbox: Postbox, network: Network) -> Signal<Never, NoError> {
|
func managedSynchronizeAvailableReactions(postbox: Postbox, network: Network) -> Signal<Never, NoError> {
|
||||||
|
let starsReaction = generateStarsReaction()
|
||||||
|
let mapping: [String: KeyPath<AvailableReactions.Reaction, TelegramMediaFile>] = [
|
||||||
|
"star_reaction_activate.tgs": \.activateAnimation,
|
||||||
|
"star_reaction_appear.tgs": \.appearAnimation,
|
||||||
|
"star_reaction_effect.tgs": \.effectAnimation,
|
||||||
|
"star_reaction_select.tgs": \.selectAnimation,
|
||||||
|
"star_reaction_static_icon.webp": \.staticIcon
|
||||||
|
]
|
||||||
|
let optionalMapping: [String: KeyPath<AvailableReactions.Reaction, TelegramMediaFile?>] = [
|
||||||
|
"star_reaction_center.tgs": \.centerAnimation,
|
||||||
|
"star_reaction_effect.tgs": \.aroundAnimation
|
||||||
|
]
|
||||||
|
for (key, path) in mapping {
|
||||||
|
if let filePath = Bundle.main.path(forResource: key, ofType: nil), let data = try? Data(contentsOf: URL(fileURLWithPath: filePath)) {
|
||||||
|
postbox.mediaBox.storeResourceData(starsReaction[keyPath: path].resource.id, data: data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (key, path) in optionalMapping {
|
||||||
|
if let filePath = Bundle.main.path(forResource: key, ofType: nil), let data = try? Data(contentsOf: URL(fileURLWithPath: filePath)) {
|
||||||
|
if let file = starsReaction[keyPath: path] {
|
||||||
|
postbox.mediaBox.storeResourceData(file.resource.id, data: data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let poll = Signal<Never, NoError> { subscriber in
|
let poll = Signal<Never, NoError> { subscriber in
|
||||||
let signal: Signal<Never, NoError> = _internal_cachedAvailableReactions(postbox: postbox)
|
let signal: Signal<Never, NoError> = _internal_cachedAvailableReactions(postbox: postbox)
|
||||||
|> mapToSignal { current in
|
|> mapToSignal { current in
|
||||||
@ -327,6 +363,7 @@ func managedSynchronizeAvailableReactions(postbox: Postbox, network: Network) ->
|
|||||||
guard let result = result else {
|
guard let result = result else {
|
||||||
return .complete()
|
return .complete()
|
||||||
}
|
}
|
||||||
|
|
||||||
switch result {
|
switch result {
|
||||||
case let .availableReactions(hash, reactions):
|
case let .availableReactions(hash, reactions):
|
||||||
let availableReactions = AvailableReactions(
|
let availableReactions = AvailableReactions(
|
||||||
|
@ -696,7 +696,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
|
|||||||
switch participantResult {
|
switch participantResult {
|
||||||
case let .channelParticipant(participant, _, _):
|
case let .channelParticipant(participant, _, _):
|
||||||
switch participant {
|
switch participant {
|
||||||
case let .channelParticipantSelf(_, _, inviterId, invitedDate, _):
|
case let .channelParticipantSelf(flags, _, inviterId, invitedDate, _):
|
||||||
invitedBy = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(inviterId))
|
invitedBy = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(inviterId))
|
||||||
if (flags & (1 << 0)) != 0 {
|
if (flags & (1 << 0)) != 0 {
|
||||||
invitedOn = invitedDate
|
invitedOn = invitedDate
|
||||||
|
@ -530,6 +530,15 @@ public extension Message {
|
|||||||
var paidContent: TelegramMediaPaidContent? {
|
var paidContent: TelegramMediaPaidContent? {
|
||||||
return self.media.first(where: { $0 is TelegramMediaPaidContent }) as? TelegramMediaPaidContent
|
return self.media.first(where: { $0 is TelegramMediaPaidContent }) as? TelegramMediaPaidContent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var authorSignatureAttribute: AuthorSignatureMessageAttribute? {
|
||||||
|
for attribute in self.attributes {
|
||||||
|
if let attribute = attribute as? AuthorSignatureMessageAttribute {
|
||||||
|
return attribute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension Message {
|
public extension Message {
|
||||||
|
@ -849,6 +849,12 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
for contentNode in self.contentNodes {
|
for contentNode in self.contentNodes {
|
||||||
contentNode.updateIsExtractedToContextPreview(isExtractedToContextPreview)
|
contentNode.updateIsExtractedToContextPreview(isExtractedToContextPreview)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !isExtractedToContextPreview {
|
||||||
|
if let item = self.item {
|
||||||
|
item.controllerInteraction.forceUpdateWarpContents()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.mainContextSourceNode.updateAbsoluteRect = { [weak self] rect, size in
|
self.mainContextSourceNode.updateAbsoluteRect = { [weak self] rect, size in
|
||||||
@ -1474,6 +1480,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
}
|
}
|
||||||
|
|
||||||
var effectiveAuthor: Peer?
|
var effectiveAuthor: Peer?
|
||||||
|
var overrideEffectiveAuthor = false
|
||||||
var ignoreForward = false
|
var ignoreForward = false
|
||||||
var displayAuthorInfo: Bool
|
var displayAuthorInfo: Bool
|
||||||
var ignoreNameHiding = false
|
var ignoreNameHiding = false
|
||||||
@ -1543,6 +1550,31 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO:release
|
||||||
|
if let channel = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.messagesShouldHaveSignatures) {
|
||||||
|
hasAvatar = true
|
||||||
|
if let authorSignatureAttribute = firstMessage.authorSignatureAttribute {
|
||||||
|
effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignatureAttribute.signature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignatureAttribute.signature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil)
|
||||||
|
overrideEffectiveAuthor = true
|
||||||
|
|
||||||
|
var allowAuthor = incoming
|
||||||
|
|
||||||
|
if let author = firstMessage.author, author is TelegramChannel, !incoming || item.presentationData.isPreview {
|
||||||
|
allowAuthor = true
|
||||||
|
ignoreNameHiding = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if let subject = item.associatedData.subject, case let .customChatContents(contents) = subject, case .hashTagSearch = contents.kind {
|
||||||
|
ignoreNameHiding = true
|
||||||
|
}
|
||||||
|
|
||||||
|
displayAuthorInfo = !mergedTop.merged && allowAuthor && peerId.isGroupOrChannel && effectiveAuthor != nil
|
||||||
|
if let forwardInfo = firstMessage.forwardInfo, forwardInfo.psaType != nil {
|
||||||
|
displayAuthorInfo = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) {
|
if !peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) {
|
||||||
if peerId.isGroupOrChannel && effectiveAuthor != nil {
|
if peerId.isGroupOrChannel && effectiveAuthor != nil {
|
||||||
var isBroadcastChannel = false
|
var isBroadcastChannel = false
|
||||||
@ -2089,7 +2121,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
}
|
}
|
||||||
|
|
||||||
if initialDisplayHeader && displayAuthorInfo {
|
if initialDisplayHeader && displayAuthorInfo {
|
||||||
if let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case .broadcast = peer.info, item.content.firstMessage.adAttribute == nil {
|
if let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case .broadcast = peer.info, item.content.firstMessage.adAttribute == nil, !overrideEffectiveAuthor {
|
||||||
authorNameString = EnginePeer(peer).displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)
|
authorNameString = EnginePeer(peer).displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)
|
||||||
|
|
||||||
let peer = (peer as Peer)
|
let peer = (peer as Peer)
|
||||||
|
@ -341,8 +341,16 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
|||||||
}
|
}
|
||||||
|
|
||||||
var hasAvatar = false
|
var hasAvatar = false
|
||||||
if !hasActionMedia && !isBroadcastChannel {
|
if !hasActionMedia {
|
||||||
hasAvatar = true
|
if !isBroadcastChannel {
|
||||||
|
hasAvatar = true
|
||||||
|
} else if let channel = message.peers[message.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.messagesShouldHaveSignatures) {
|
||||||
|
//TODO:release
|
||||||
|
hasAvatar = true
|
||||||
|
if let authorSignatureAttribute = message.authorSignatureAttribute {
|
||||||
|
effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignatureAttribute.signature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignatureAttribute.signature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasAvatar {
|
if hasAvatar {
|
||||||
|
@ -628,6 +628,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||||||
}, scrollToMessageId: { _ in
|
}, scrollToMessageId: { _ in
|
||||||
}, navigateToStory: { _, _ in
|
}, navigateToStory: { _, _ in
|
||||||
}, attemptedNavigationToPrivateQuote: { _ in
|
}, attemptedNavigationToPrivateQuote: { _ in
|
||||||
|
}, forceUpdateWarpContents: {
|
||||||
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,
|
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,
|
||||||
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: self.backgroundNode))
|
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: self.backgroundNode))
|
||||||
self.controllerInteraction = controllerInteraction
|
self.controllerInteraction = controllerInteraction
|
||||||
|
@ -492,6 +492,7 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess
|
|||||||
}, scrollToMessageId: { _ in
|
}, scrollToMessageId: { _ in
|
||||||
}, navigateToStory: { _, _ in
|
}, navigateToStory: { _, _ in
|
||||||
}, attemptedNavigationToPrivateQuote: { _ in
|
}, attemptedNavigationToPrivateQuote: { _ in
|
||||||
|
}, forceUpdateWarpContents: {
|
||||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||||
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: self.context, backgroundNode: self.wallpaperBackgroundNode))
|
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: self.context, backgroundNode: self.wallpaperBackgroundNode))
|
||||||
|
|
||||||
|
@ -745,14 +745,14 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
let peer: EnginePeer
|
let peer: EnginePeer
|
||||||
let balance: Int64?
|
let balance: Int64?
|
||||||
let topPeers: [ChatSendStarsScreen.TopPeer]
|
let topPeers: [ChatSendStarsScreen.TopPeer]
|
||||||
let completion: (Int64, ChatSendStarsScreen.TransitionOut) -> Void
|
let completion: (Int64, Bool, ChatSendStarsScreen.TransitionOut) -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
peer: EnginePeer,
|
peer: EnginePeer,
|
||||||
balance: Int64?,
|
balance: Int64?,
|
||||||
topPeers: [ChatSendStarsScreen.TopPeer],
|
topPeers: [ChatSendStarsScreen.TopPeer],
|
||||||
completion: @escaping (Int64, ChatSendStarsScreen.TransitionOut) -> Void
|
completion: @escaping (Int64, Bool, ChatSendStarsScreen.TransitionOut) -> Void
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
@ -1104,9 +1104,11 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
|
|
||||||
let progressFraction: CGFloat = CGFloat(self.amount) / CGFloat(1000 - 1)
|
let progressFraction: CGFloat = CGFloat(self.amount) / CGFloat(1000 - 1)
|
||||||
|
|
||||||
|
let topCount = component.topPeers.max(by: { $0.count < $1.count })?.count
|
||||||
|
|
||||||
var topCutoffFraction: CGFloat?
|
var topCutoffFraction: CGFloat?
|
||||||
if let maxCount = component.topPeers.max(by: { $0.count < $1.count })?.count {
|
if let topCount {
|
||||||
let topCutoffFractionValue = CGFloat(maxCount) / CGFloat(1000 - 1)
|
let topCutoffFractionValue = CGFloat(topCount) / CGFloat(1000 - 1)
|
||||||
topCutoffFraction = topCutoffFractionValue
|
topCutoffFraction = topCutoffFractionValue
|
||||||
|
|
||||||
let isPastCutoff = progressFraction >= topCutoffFractionValue
|
let isPastCutoff = progressFraction >= topCutoffFractionValue
|
||||||
@ -1470,8 +1472,15 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
guard let badgeView = self.badge.view as? BadgeComponent.View else {
|
guard let badgeView = self.badge.view as? BadgeComponent.View else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let isBecomingTop: Bool
|
||||||
|
if let topCount {
|
||||||
|
isBecomingTop = self.amount > topCount
|
||||||
|
} else {
|
||||||
|
isBecomingTop = true
|
||||||
|
}
|
||||||
component.completion(
|
component.completion(
|
||||||
self.amount,
|
self.amount,
|
||||||
|
isBecomingTop,
|
||||||
ChatSendStarsScreen.TransitionOut(
|
ChatSendStarsScreen.TransitionOut(
|
||||||
sourceView: badgeView.badgeIcon
|
sourceView: badgeView.badgeIcon
|
||||||
)
|
)
|
||||||
@ -1617,7 +1626,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
|||||||
|
|
||||||
private var presenceDisposable: Disposable?
|
private var presenceDisposable: Disposable?
|
||||||
|
|
||||||
public init(context: AccountContext, initialData: InitialData, completion: @escaping (Int64, TransitionOut) -> Void) {
|
public init(context: AccountContext, initialData: InitialData, completion: @escaping (Int64, Bool, TransitionOut) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
||||||
super.init(context: context, component: ChatSendStarsScreenComponent(
|
super.init(context: context, component: ChatSendStarsScreenComponent(
|
||||||
|
@ -272,6 +272,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
|||||||
public let scrollToMessageId: (MessageIndex) -> Void
|
public let scrollToMessageId: (MessageIndex) -> Void
|
||||||
public let navigateToStory: (Message, StoryId) -> Void
|
public let navigateToStory: (Message, StoryId) -> Void
|
||||||
public let attemptedNavigationToPrivateQuote: (Peer?) -> Void
|
public let attemptedNavigationToPrivateQuote: (Peer?) -> Void
|
||||||
|
public let forceUpdateWarpContents: () -> Void
|
||||||
|
|
||||||
public var canPlayMedia: Bool = false
|
public var canPlayMedia: Bool = false
|
||||||
public var hiddenMedia: [MessageId: [Media]] = [:]
|
public var hiddenMedia: [MessageId: [Media]] = [:]
|
||||||
@ -400,6 +401,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
|||||||
scrollToMessageId: @escaping (MessageIndex) -> Void,
|
scrollToMessageId: @escaping (MessageIndex) -> Void,
|
||||||
navigateToStory: @escaping (Message, StoryId) -> Void,
|
navigateToStory: @escaping (Message, StoryId) -> Void,
|
||||||
attemptedNavigationToPrivateQuote: @escaping (Peer?) -> Void,
|
attemptedNavigationToPrivateQuote: @escaping (Peer?) -> Void,
|
||||||
|
forceUpdateWarpContents: @escaping () -> Void,
|
||||||
automaticMediaDownloadSettings: MediaAutoDownloadSettings,
|
automaticMediaDownloadSettings: MediaAutoDownloadSettings,
|
||||||
pollActionState: ChatInterfacePollActionState,
|
pollActionState: ChatInterfacePollActionState,
|
||||||
stickerSettings: ChatInterfaceStickerSettings,
|
stickerSettings: ChatInterfaceStickerSettings,
|
||||||
@ -509,6 +511,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
|||||||
self.scrollToMessageId = scrollToMessageId
|
self.scrollToMessageId = scrollToMessageId
|
||||||
self.navigateToStory = navigateToStory
|
self.navigateToStory = navigateToStory
|
||||||
self.attemptedNavigationToPrivateQuote = attemptedNavigationToPrivateQuote
|
self.attemptedNavigationToPrivateQuote = attemptedNavigationToPrivateQuote
|
||||||
|
self.forceUpdateWarpContents = forceUpdateWarpContents
|
||||||
|
|
||||||
self.automaticMediaDownloadSettings = automaticMediaDownloadSettings
|
self.automaticMediaDownloadSettings = automaticMediaDownloadSettings
|
||||||
|
|
||||||
|
@ -3485,6 +3485,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
}, scrollToMessageId: { _ in
|
}, scrollToMessageId: { _ in
|
||||||
}, navigateToStory: { _, _ in
|
}, navigateToStory: { _, _ in
|
||||||
}, attemptedNavigationToPrivateQuote: { _ in
|
}, attemptedNavigationToPrivateQuote: { _ in
|
||||||
|
}, forceUpdateWarpContents: {
|
||||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||||
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil))
|
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil))
|
||||||
self.hiddenMediaDisposable = context.sharedContext.mediaManager.galleryHiddenMediaManager.hiddenIds().startStrict(next: { [weak self] ids in
|
self.hiddenMediaDisposable = context.sharedContext.mediaManager.galleryHiddenMediaManager.hiddenIds().startStrict(next: { [weak self] ids in
|
||||||
|
@ -684,3 +684,208 @@ public final class PremiumStarComponent: Component {
|
|||||||
return view.update(component: self, availableSize: availableSize, transition: transition)
|
return view.update(component: self, availableSize: availableSize, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class StandalonePremiumStarComponent: Component {
|
||||||
|
let theme: PresentationTheme
|
||||||
|
let colors: [UIColor]?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
theme: PresentationTheme,
|
||||||
|
colors: [UIColor]? = nil
|
||||||
|
) {
|
||||||
|
self.theme = theme
|
||||||
|
self.colors = colors
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: StandalonePremiumStarComponent, rhs: StandalonePremiumStarComponent) -> Bool {
|
||||||
|
return lhs.theme === rhs.theme && lhs.colors == rhs.colors
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class View: UIView, SCNSceneRendererDelegate, ComponentTaggedView {
|
||||||
|
public final class Tag {
|
||||||
|
public init() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func matches(tag: Any) -> Bool {
|
||||||
|
if let _ = tag as? Tag {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private var component: StandalonePremiumStarComponent?
|
||||||
|
|
||||||
|
private var _ready = Promise<Bool>()
|
||||||
|
public var ready: Signal<Bool, NoError> {
|
||||||
|
return self._ready.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
private let sceneView: SCNView
|
||||||
|
|
||||||
|
private var timer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: CGSize(width: 64.0, height: 64.0)))
|
||||||
|
self.sceneView.backgroundColor = .clear
|
||||||
|
self.sceneView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
|
||||||
|
self.sceneView.isUserInteractionEnabled = false
|
||||||
|
self.sceneView.preferredFramesPerSecond = 60
|
||||||
|
self.sceneView.isJitteringEnabled = true
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.addSubview(self.sceneView)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.timer?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var didSetup = false
|
||||||
|
private func setup() {
|
||||||
|
guard !self.didSetup, let scene = loadCompressedScene(name: "star2", version: sceneVersion) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.didSetup = true
|
||||||
|
self.sceneView.scene = scene
|
||||||
|
self.sceneView.delegate = self
|
||||||
|
|
||||||
|
if let component = self.component, let node = scene.rootNode.childNode(withName: "star", recursively: false), let colors =
|
||||||
|
component.colors {
|
||||||
|
node.geometry?.materials.first?.diffuse.contents = generateDiffuseTexture(colors: colors)
|
||||||
|
}
|
||||||
|
|
||||||
|
for node in scene.rootNode.childNodes {
|
||||||
|
if let name = node.name, name.hasPrefix("particles") {
|
||||||
|
node.isHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.didSetReady = true
|
||||||
|
self._ready.set(.single(true))
|
||||||
|
self.onReady()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var didSetReady = false
|
||||||
|
public func renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval) {
|
||||||
|
if !self.didSetReady {
|
||||||
|
self.didSetReady = true
|
||||||
|
|
||||||
|
Queue.mainQueue().justDispatch {
|
||||||
|
self._ready.set(.single(true))
|
||||||
|
self.onReady()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func onReady() {
|
||||||
|
//self.setupScaleAnimation()
|
||||||
|
//self.setupGradientAnimation()
|
||||||
|
|
||||||
|
self.playAppearanceAnimation(mirror: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupScaleAnimation() {
|
||||||
|
guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let fromScale: Float = 0.1
|
||||||
|
let toScale: Float = 0.092
|
||||||
|
|
||||||
|
let animation = CABasicAnimation(keyPath: "scale")
|
||||||
|
animation.duration = 2.0
|
||||||
|
animation.fromValue = NSValue(scnVector3: SCNVector3(x: fromScale, y: fromScale, z: fromScale))
|
||||||
|
animation.toValue = NSValue(scnVector3: SCNVector3(x: toScale, y: toScale, z: toScale))
|
||||||
|
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
||||||
|
animation.autoreverses = true
|
||||||
|
animation.repeatCount = .infinity
|
||||||
|
|
||||||
|
node.addAnimation(animation, forKey: "scale")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupGradientAnimation() {
|
||||||
|
guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let initial = node.geometry?.materials.first?.diffuse.contentsTransform else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let animation = CABasicAnimation(keyPath: "contentsTransform")
|
||||||
|
animation.duration = 4.5
|
||||||
|
animation.fromValue = NSValue(scnMatrix4: initial)
|
||||||
|
animation.toValue = NSValue(scnMatrix4: SCNMatrix4Translate(initial, -0.35, 0.35, 0))
|
||||||
|
animation.timingFunction = CAMediaTimingFunction(name: .linear)
|
||||||
|
animation.autoreverses = true
|
||||||
|
animation.repeatCount = .infinity
|
||||||
|
|
||||||
|
node.geometry?.materials.first?.diffuse.addAnimation(animation, forKey: "gradient")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func playAppearanceAnimation(velocity: CGFloat? = nil, smallAngle: Bool = false, mirror: Bool = false) {
|
||||||
|
guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var from = node.presentation.eulerAngles
|
||||||
|
if abs(from.y - .pi * 2.0) < 0.001 {
|
||||||
|
from.y = 0.0
|
||||||
|
}
|
||||||
|
node.removeAnimation(forKey: "tapRotate")
|
||||||
|
|
||||||
|
var toValue: Float = smallAngle ? 0.0 : .pi * 2.0
|
||||||
|
if let velocity = velocity, !smallAngle && abs(velocity) > 200 && velocity < 0.0 {
|
||||||
|
toValue *= -1
|
||||||
|
}
|
||||||
|
if mirror {
|
||||||
|
toValue *= -1
|
||||||
|
}
|
||||||
|
let to = SCNVector3(x: 0.0, y: toValue, z: 0.0)
|
||||||
|
let distance = rad2deg(to.y - from.y)
|
||||||
|
|
||||||
|
guard !distance.isZero else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let animation = CABasicAnimation(keyPath: "eulerAngles")
|
||||||
|
animation.fromValue = NSValue(scnVector3: from)
|
||||||
|
animation.toValue = NSValue(scnVector3: to)
|
||||||
|
animation.duration = 0.4 * UIView.animationDurationFactor()
|
||||||
|
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
||||||
|
animation.completion = { [weak node] finished in
|
||||||
|
if finished {
|
||||||
|
node?.eulerAngles = SCNVector3(x: 0.0, y: 0.0, z: 0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node.addAnimation(animation, forKey: "rotate")
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: StandalonePremiumStarComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
|
||||||
|
self.component = component
|
||||||
|
|
||||||
|
self.setup()
|
||||||
|
|
||||||
|
self.sceneView.bounds = CGRect(origin: .zero, size: CGSize(width: availableSize.width * 2.0, height: availableSize.height * 2.0))
|
||||||
|
if self.sceneView.superview == self {
|
||||||
|
self.sceneView.center = CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return availableSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func makeView() -> View {
|
||||||
|
return View(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
|
||||||
|
final class MeshLayer: CALayer {
|
||||||
|
|
||||||
|
}
|
@ -255,7 +255,7 @@ private final class MaskGridLayer: SimpleLayer {
|
|||||||
|
|
||||||
private var resolution: (x: Int, y: Int)?
|
private var resolution: (x: Int, y: Int)?
|
||||||
|
|
||||||
func updateGrid(resolutionX: Int, resolutionY: Int) {
|
func updateGrid(size: CGSize, resolutionX: Int, resolutionY: Int, cornerRadius: CGFloat) {
|
||||||
if let resolution = self.resolution, resolution.x == resolutionX, resolution.y == resolutionY {
|
if let resolution = self.resolution, resolution.x == resolutionX, resolution.y == resolutionY {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -266,13 +266,52 @@ private final class MaskGridLayer: SimpleLayer {
|
|||||||
}
|
}
|
||||||
self.itemLayers.removeAll()
|
self.itemLayers.removeAll()
|
||||||
|
|
||||||
for _ in 0 ..< resolutionX * resolutionY {
|
let itemSize = CGSize(width: size.width / CGFloat(resolutionX), height: size.height / CGFloat(resolutionY))
|
||||||
let itemLayer = SimpleLayer()
|
|
||||||
itemLayer.backgroundColor = UIColor.black.cgColor
|
let topLeftCorner = CGRect(origin: CGPoint(), size: CGSize(width: cornerRadius, height: cornerRadius))
|
||||||
itemLayer.opacity = 1.0
|
let topRightCorner = CGRect(origin: CGPoint(x: size.width - cornerRadius, y: 0.0), size: CGSize(width: cornerRadius, height: cornerRadius))
|
||||||
itemLayer.anchorPoint = CGPoint()
|
let bottomLeftCorner = CGRect(origin: CGPoint(x: 0.0, y: size.height - cornerRadius), size: CGSize(width: cornerRadius, height: cornerRadius))
|
||||||
self.addSublayer(itemLayer)
|
let bottomRightCorner = CGRect(origin: CGPoint(x: size.width - cornerRadius, y: size.height - cornerRadius), size: CGSize(width: cornerRadius, height: cornerRadius))
|
||||||
self.itemLayers.append(itemLayer)
|
|
||||||
|
var cornersImage: UIImage?
|
||||||
|
if cornerRadius > 0.0 {
|
||||||
|
cornersImage = generateImage(CGSize(width: cornerRadius * 2.0 + 200.0, height: cornerRadius * 2.0 + 200.0), rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
context.setFillColor(UIColor.black.cgColor)
|
||||||
|
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: cornerRadius).cgPath)
|
||||||
|
context.fillPath()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for y in 0 ..< resolutionY {
|
||||||
|
for x in 0 ..< resolutionX {
|
||||||
|
let itemLayer = SimpleLayer()
|
||||||
|
itemLayer.backgroundColor = UIColor.black.cgColor
|
||||||
|
itemLayer.isOpaque = true
|
||||||
|
itemLayer.opacity = 1.0
|
||||||
|
itemLayer.anchorPoint = CGPoint()
|
||||||
|
self.addSublayer(itemLayer)
|
||||||
|
self.itemLayers.append(itemLayer)
|
||||||
|
|
||||||
|
if cornerRadius > 0.0, let cornersImage {
|
||||||
|
let gridPosition = CGPoint(x: CGFloat(x) / CGFloat(resolutionX), y: CGFloat(y) / CGFloat(resolutionY))
|
||||||
|
let sourceRect = CGRect(origin: CGPoint(x: gridPosition.x * (size.width), y: gridPosition.y * (size.height)), size: itemSize)
|
||||||
|
if sourceRect.intersects(topLeftCorner) || sourceRect.intersects(topRightCorner) || sourceRect.intersects(bottomLeftCorner) || sourceRect.intersects(bottomRightCorner) {
|
||||||
|
var clippedCornersRect = sourceRect
|
||||||
|
if clippedCornersRect.maxX > cornersImage.size.width {
|
||||||
|
clippedCornersRect.origin.x -= size.width - cornersImage.size.width
|
||||||
|
}
|
||||||
|
if clippedCornersRect.maxY > cornersImage.size.height {
|
||||||
|
clippedCornersRect.origin.y -= size.height - cornersImage.size.height
|
||||||
|
}
|
||||||
|
|
||||||
|
itemLayer.contents = cornersImage.cgImage
|
||||||
|
itemLayer.contentsRect = CGRect(origin: CGPoint(x: clippedCornersRect.minX / cornersImage.size.width, y: clippedCornersRect.minY / cornersImage.size.height), size: CGSize(width: clippedCornersRect.width / cornersImage.size.width, height: clippedCornersRect.height / cornersImage.size.height))
|
||||||
|
itemLayer.backgroundColor = nil
|
||||||
|
itemLayer.isOpaque = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -503,7 +542,7 @@ open class SpaceWarpNodeImpl: ASDisplayNode, SpaceWarpNode {
|
|||||||
let endEndPoint = CGPoint(x: (gradientLayer.startPoint.x + radius.width), y: (gradientLayer.startPoint.y + radius.height))
|
let endEndPoint = CGPoint(x: (gradientLayer.startPoint.x + radius.width), y: (gradientLayer.startPoint.y + radius.height))
|
||||||
gradientLayer.endPoint = endEndPoint
|
gradientLayer.endPoint = endEndPoint
|
||||||
|
|
||||||
let maxWavefrontNorm: CGFloat = 0.3
|
let maxWavefrontNorm: CGFloat = 0.4
|
||||||
|
|
||||||
let normProgress = max(0.0, min(1.0, progress))
|
let normProgress = max(0.0, min(1.0, progress))
|
||||||
let interpolatedNorm: CGFloat = 1.0 * (1.0 - normProgress) + maxWavefrontNorm * normProgress
|
let interpolatedNorm: CGFloat = 1.0 * (1.0 - normProgress) + maxWavefrontNorm * normProgress
|
||||||
@ -587,7 +626,7 @@ open class SpaceWarpNodeImpl: ASDisplayNode, SpaceWarpNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let gradientMaskLayer = self.gradientMaskLayer {
|
if let gradientMaskLayer = self.gradientMaskLayer {
|
||||||
gradientMaskLayer.updateGrid(resolutionX: resolutionX, resolutionY: resolutionY)
|
gradientMaskLayer.updateGrid(size: size, resolutionX: resolutionX, resolutionY: resolutionY, cornerRadius: cornerRadius)
|
||||||
gradientMaskLayer.update(positions: instancePositions, bounds: instanceBounds, transforms: instanceTransforms)
|
gradientMaskLayer.update(positions: instancePositions, bounds: instanceBounds, transforms: instanceTransforms)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
@ -17,6 +17,9 @@ func peerMessageSelectedReactions(context: AccountContext, message: Message) ->
|
|||||||
if !reaction.isSelected {
|
if !reaction.isSelected {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if case .stars = reaction.value {
|
||||||
|
continue
|
||||||
|
}
|
||||||
reactions.insert(reaction.value)
|
reactions.insert(reaction.value)
|
||||||
switch reaction.value {
|
switch reaction.value {
|
||||||
case .builtin, .stars:
|
case .builtin, .stars:
|
||||||
|
@ -4525,6 +4525,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
text = self.presentationData.strings.Chat_ToastQuoteChatUnavailbalePrivateChat
|
text = self.presentationData.strings.Chat_ToastQuoteChatUnavailbalePrivateChat
|
||||||
}
|
}
|
||||||
self.controllerInteraction?.displayUndo(.info(title: nil, text: text, timeout: nil, customUndoText: nil))
|
self.controllerInteraction?.displayUndo(.info(title: nil, text: text, timeout: nil, customUndoText: nil))
|
||||||
|
}, forceUpdateWarpContents: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.chatDisplayNode.forceUpdateWarpContents()
|
||||||
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: self.stickerSettings, presentationContext: ChatPresentationContext(context: context, backgroundNode: self.chatBackgroundNode))
|
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: self.stickerSettings, presentationContext: ChatPresentationContext(context: context, backgroundNode: self.chatBackgroundNode))
|
||||||
controllerInteraction.enableFullTranslucency = context.sharedContext.energyUsageSettings.fullTranslucency
|
controllerInteraction.enableFullTranslucency = context.sharedContext.energyUsageSettings.fullTranslucency
|
||||||
|
|
||||||
|
@ -1076,6 +1076,13 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
return CGSize(width: layout.size.width, height: height)
|
return CGSize(width: layout.size.width, height: height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func forceUpdateWarpContents() {
|
||||||
|
guard let (layout, _) = self.validLayout else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.wrappingNode.update(size: layout.size, cornerRadius: layout.deviceMetrics.screenCornerRadius, transition: .immediate)
|
||||||
|
}
|
||||||
|
|
||||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition protoTransition: ContainedViewLayoutTransition, listViewTransaction: (ListViewUpdateSizeAndInsets, CGFloat, Bool, @escaping () -> Void) -> Void, updateExtraNavigationBarBackgroundHeight: (CGFloat, CGFloat, ContainedViewLayoutTransition) -> Void) {
|
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition protoTransition: ContainedViewLayoutTransition, listViewTransaction: (ListViewUpdateSizeAndInsets, CGFloat, Bool, @escaping () -> Void) -> Void, updateExtraNavigationBarBackgroundHeight: (CGFloat, CGFloat, ContainedViewLayoutTransition) -> Void) {
|
||||||
let transition: ContainedViewLayoutTransition
|
let transition: ContainedViewLayoutTransition
|
||||||
if let _ = self.scheduledAnimateInAsOverlayFromNode {
|
if let _ = self.scheduledAnimateInAsOverlayFromNode {
|
||||||
|
@ -171,7 +171,7 @@ extension ChatControllerImpl {
|
|||||||
guard let self, let initialData else {
|
guard let self, let initialData else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.push(ChatSendStarsScreen(context: self.context, initialData: initialData, completion: { [weak self] amount, transitionOut in
|
self.push(ChatSendStarsScreen(context: self.context, initialData: initialData, completion: { [weak self] amount, isBecomingTop, transitionOut in
|
||||||
guard let self, amount > 0 else {
|
guard let self, amount > 0 else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -216,7 +216,7 @@ extension ChatControllerImpl {
|
|||||||
|
|
||||||
self.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
|
self.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
|
||||||
|
|
||||||
self.chatDisplayNode.addSubnode(standaloneReactionAnimation)
|
self.view.window?.addSubview(standaloneReactionAnimation.view)
|
||||||
standaloneReactionAnimation.frame = self.chatDisplayNode.bounds
|
standaloneReactionAnimation.frame = self.chatDisplayNode.bounds
|
||||||
standaloneReactionAnimation.animateOutToReaction(
|
standaloneReactionAnimation.animateOutToReaction(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
@ -240,47 +240,23 @@ extension ChatControllerImpl {
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isBecomingTop {
|
||||||
|
self.chatDisplayNode.animateQuizCorrectOptionSelected()
|
||||||
|
}
|
||||||
|
|
||||||
if let itemNode, let targetView = itemNode.targetReactionView(value: .stars) {
|
if let itemNode, let targetView = itemNode.targetReactionView(value: .stars) {
|
||||||
self.chatDisplayNode.wrappingNode.triggerRipple(at: targetView.convert(targetView.bounds.center, to: self.chatDisplayNode.view))
|
self.chatDisplayNode.wrappingNode.triggerRipple(at: targetView.convert(targetView.bounds.center, to: self.chatDisplayNode.view))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
completion: { [weak standaloneReactionAnimation] in
|
completion: { [weak standaloneReactionAnimation] in
|
||||||
standaloneReactionAnimation?.removeFromSupernode()
|
standaloneReactionAnimation?.view.removeFromSuperview()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
/*standaloneReactionAnimation.animateReactionSelection(
|
|
||||||
context: strongSelf.context,
|
|
||||||
theme: strongSelf.presentationData.theme,
|
|
||||||
animationCache: strongSelf.controllerInteraction!.presentationContext.animationCache,
|
|
||||||
reaction: reactionItem,
|
|
||||||
avatarPeers: [],
|
|
||||||
playHaptic: false,
|
|
||||||
isLarge: false,
|
|
||||||
targetView: targetView,
|
|
||||||
addStandaloneReactionAnimation: { standaloneReactionAnimation in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
strongSelf.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
|
|
||||||
standaloneReactionAnimation.frame = strongSelf.chatDisplayNode.bounds
|
|
||||||
strongSelf.chatDisplayNode.addSubnode(standaloneReactionAnimation)
|
|
||||||
},
|
|
||||||
onHit: { [weak itemNode] in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if let itemNode = itemNode, let targetView = itemNode.targetReactionView(value: chosenReaction) {
|
|
||||||
strongSelf.chatDisplayNode.wrappingNode.triggerRipple(at: targetView.convert(targetView.bounds.center, to: strongSelf.chatDisplayNode.view))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
completion: { [weak standaloneReactionAnimation] in
|
|
||||||
standaloneReactionAnimation?.removeFromSupernode()
|
|
||||||
}
|
|
||||||
)*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = self.context.engine.messages.sendStarsReaction(id: message.id, count: Int(amount))
|
//let _ = self.context.engine.messages.sendStarsReaction(id: message.id, count: Int(amount))
|
||||||
|
|
||||||
let _ = (self.context.engine.stickers.resolveInlineStickers(fileIds: [MessageReaction.starsReactionId])
|
let _ = (self.context.engine.stickers.resolveInlineStickers(fileIds: [MessageReaction.starsReactionId])
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] files in
|
|> deliverOnMainQueue).start(next: { [weak self] files in
|
||||||
|
@ -184,6 +184,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||||||
}, scrollToMessageId: { _ in
|
}, scrollToMessageId: { _ in
|
||||||
}, navigateToStory: { _, _ in
|
}, navigateToStory: { _, _ in
|
||||||
}, attemptedNavigationToPrivateQuote: { _ in
|
}, attemptedNavigationToPrivateQuote: { _ in
|
||||||
|
}, forceUpdateWarpContents: {
|
||||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil))
|
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil))
|
||||||
|
|
||||||
self.dimNode = ASDisplayNode()
|
self.dimNode = ASDisplayNode()
|
||||||
|
@ -1787,6 +1787,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
}, scrollToMessageId: { _ in
|
}, scrollToMessageId: { _ in
|
||||||
}, navigateToStory: { _, _ in
|
}, navigateToStory: { _, _ in
|
||||||
}, attemptedNavigationToPrivateQuote: { _ in
|
}, attemptedNavigationToPrivateQuote: { _ in
|
||||||
|
}, forceUpdateWarpContents: {
|
||||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||||
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: backgroundNode as? WallpaperBackgroundNode))
|
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: backgroundNode as? WallpaperBackgroundNode))
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user