mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
8de066f912
@ -124,13 +124,14 @@ def load_codesigning_data_from_git(working_dir, repo_url, temp_key_path, branch,
|
||||
|
||||
encrypted_working_dir = working_dir + '/encrypted'
|
||||
if os.path.exists(encrypted_working_dir):
|
||||
original_working_dir = os.getcwd()
|
||||
os.chdir(encrypted_working_dir)
|
||||
if always_fetch:
|
||||
original_working_dir = os.getcwd()
|
||||
os.chdir(encrypted_working_dir)
|
||||
check_run_system('GIT_SSH_COMMAND="{ssh_command}" git fetch'.format(ssh_command=ssh_command))
|
||||
check_run_system('git checkout "{branch}"'.format(branch=branch))
|
||||
check_run_system('git checkout "{branch}"'.format(branch=branch))
|
||||
if always_fetch:
|
||||
check_run_system('GIT_SSH_COMMAND="{ssh_command}" git pull'.format(ssh_command=ssh_command))
|
||||
os.chdir(original_working_dir)
|
||||
os.chdir(original_working_dir)
|
||||
else:
|
||||
os.makedirs(encrypted_working_dir, exist_ok=True)
|
||||
original_working_dir = os.getcwd()
|
||||
|
@ -22,6 +22,7 @@ public let repostStoryIcon = generateTintedImage(image: UIImage(bundleImageName:
|
||||
private let archivedChatsIcon = UIImage(bundleImageName: "Avatar/ArchiveAvatarIcon")?.precomposed()
|
||||
private let repliesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/RepliesMessagesIcon"), color: .white)
|
||||
private let anonymousSavedMessagesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/AnonymousSenderIcon"), color: .white)
|
||||
private let anonymousSavedMessagesDarkIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/AnonymousSenderIcon"), color: UIColor(white: 1.0, alpha: 0.4))
|
||||
private let myNotesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/MyNotesIcon"), color: .white)
|
||||
|
||||
public func avatarPlaceholderFont(size: CGFloat) -> UIFont {
|
||||
@ -98,7 +99,11 @@ private func calculateColors(context: AccountContext?, explicitColorIndex: Int?,
|
||||
if isColored {
|
||||
colors = AvatarNode.savedMessagesColors
|
||||
} else {
|
||||
colors = AvatarNode.grayscaleColors
|
||||
if let theme, theme.overallDarkAppearance {
|
||||
colors = AvatarNode.grayscaleDarkColors
|
||||
} else {
|
||||
colors = AvatarNode.grayscaleColors
|
||||
}
|
||||
}
|
||||
} else if case .myNotesIcon = icon {
|
||||
colors = AvatarNode.savedMessagesColors
|
||||
@ -269,6 +274,10 @@ public final class AvatarNode: ASDisplayNode {
|
||||
UIColor(rgb: 0xb1b1b1), UIColor(rgb: 0xcdcdcd)
|
||||
]
|
||||
|
||||
static let grayscaleDarkColors: [UIColor] = [
|
||||
UIColor(white: 1.0, alpha: 0.22), UIColor(white: 1.0, alpha: 0.18)
|
||||
]
|
||||
|
||||
static let savedMessagesColors: [UIColor] = [
|
||||
UIColor(rgb: 0x2a9ef1), UIColor(rgb: 0x72d5fd)
|
||||
]
|
||||
@ -928,8 +937,14 @@ public final class AvatarNode: ASDisplayNode {
|
||||
context.scaleBy(x: factor, y: -factor)
|
||||
context.translateBy(x: -bounds.size.width / 2.0, y: -bounds.size.height / 2.0)
|
||||
|
||||
if let anonymousSavedMessagesIcon = anonymousSavedMessagesIcon {
|
||||
context.draw(anonymousSavedMessagesIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - anonymousSavedMessagesIcon.size.width) / 2.0), y: floor((bounds.size.height - anonymousSavedMessagesIcon.size.height) / 2.0)), size: anonymousSavedMessagesIcon.size))
|
||||
if let theme = parameters.theme, theme.overallDarkAppearance {
|
||||
if let anonymousSavedMessagesDarkIcon = anonymousSavedMessagesDarkIcon {
|
||||
context.draw(anonymousSavedMessagesDarkIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - anonymousSavedMessagesDarkIcon.size.width) / 2.0), y: floor((bounds.size.height - anonymousSavedMessagesDarkIcon.size.height) / 2.0)), size: anonymousSavedMessagesDarkIcon.size))
|
||||
}
|
||||
} else {
|
||||
if let anonymousSavedMessagesIcon = anonymousSavedMessagesIcon {
|
||||
context.draw(anonymousSavedMessagesIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - anonymousSavedMessagesIcon.size.width) / 2.0), y: floor((bounds.size.height - anonymousSavedMessagesIcon.size.height) / 2.0)), size: anonymousSavedMessagesIcon.size))
|
||||
}
|
||||
}
|
||||
} else if case .myNotesIcon = parameters.icon {
|
||||
let factor = bounds.size.width / 60.0
|
||||
|
@ -24,10 +24,10 @@ public func isViewVisibleInHierarchy(_ view: UIView, _ initial: Bool = true) ->
|
||||
}
|
||||
|
||||
public final class HierarchyTrackingNode: ASDisplayNode {
|
||||
private let f: (Bool) -> Void
|
||||
public var updated: (Bool) -> Void
|
||||
|
||||
public init(_ f: @escaping (Bool) -> Void) {
|
||||
self.f = f
|
||||
public init(_ f: @escaping (Bool) -> Void = { _ in }) {
|
||||
self.updated = f
|
||||
|
||||
super.init()
|
||||
|
||||
@ -37,13 +37,13 @@ public final class HierarchyTrackingNode: ASDisplayNode {
|
||||
override public func didEnterHierarchy() {
|
||||
super.didEnterHierarchy()
|
||||
|
||||
self.f(true)
|
||||
self.updated(true)
|
||||
}
|
||||
|
||||
override public func didExitHierarchy() {
|
||||
super.didExitHierarchy()
|
||||
|
||||
self.f(false)
|
||||
self.updated(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,11 +57,13 @@ private let lockedBackgroundImage: UIImage = generateFilledCircleImage(diameter:
|
||||
private let lockedBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelBadgeLock"), color: .white)
|
||||
|
||||
private final class StarsButtonEffectLayer: SimpleLayer {
|
||||
let gradientLayer = SimpleGradientLayer()
|
||||
let emitterLayer = CAEmitterLayer()
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
self.addSublayer(self.gradientLayer)
|
||||
self.addSublayer(self.emitterLayer)
|
||||
}
|
||||
|
||||
@ -73,8 +75,8 @@ private final class StarsButtonEffectLayer: SimpleLayer {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func setup() {
|
||||
let color = UIColor(rgb: 0xffbe27)
|
||||
private func setup(theme: PresentationTheme) {
|
||||
let color = UIColor(rgb: 0xffbe27, alpha: theme.overallDarkAppearance ? 0.2 : 1.0)
|
||||
|
||||
let emitter = CAEmitterCell()
|
||||
emitter.name = "emitter"
|
||||
@ -101,17 +103,31 @@ private final class StarsButtonEffectLayer: SimpleLayer {
|
||||
emitter.setValue([staticColorBehavior], forKey: "emitterBehaviors")
|
||||
|
||||
self.emitterLayer.emitterCells = [emitter]
|
||||
|
||||
let gradientColor = UIColor(rgb: 0xffbe27, alpha: theme.overallDarkAppearance ? 0.2 : 1.0)
|
||||
|
||||
self.gradientLayer.type = .radial
|
||||
self.gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
|
||||
self.gradientLayer.endPoint = CGPoint(x: 0.0, y: 1.0)
|
||||
self.gradientLayer.colors = [
|
||||
gradientColor.withMultipliedAlpha(0.4).cgColor,
|
||||
gradientColor.withMultipliedAlpha(0.4).cgColor,
|
||||
gradientColor.withMultipliedAlpha(0.25).cgColor,
|
||||
gradientColor.withMultipliedAlpha(0.0).cgColor
|
||||
] as [CGColor]
|
||||
}
|
||||
|
||||
func update(size: CGSize) {
|
||||
func update(theme: PresentationTheme, size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||
if self.emitterLayer.emitterCells == nil {
|
||||
self.setup()
|
||||
self.setup(theme: theme)
|
||||
}
|
||||
self.emitterLayer.emitterShape = .circle
|
||||
self.emitterLayer.emitterSize = CGSize(width: size.width * 0.7, height: size.height * 0.7)
|
||||
self.emitterLayer.emitterMode = .surface
|
||||
self.emitterLayer.frame = CGRect(origin: .zero, size: size)
|
||||
self.emitterLayer.emitterPosition = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||
|
||||
transition.updateFrame(layer: self.gradientLayer, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -6.0, dy: -6.0).offsetBy(dx: 0.0, dy: 2.0))
|
||||
}
|
||||
}
|
||||
|
||||
@ -307,7 +323,7 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
|
||||
if let starsEffectLayer = self.starsEffectLayer {
|
||||
transition.updateFrame(layer: starsEffectLayer, frame: CGRect(origin: CGPoint(), size: size))
|
||||
starsEffectLayer.update(size: size)
|
||||
starsEffectLayer.update(theme: self.theme, size: size, transition: transition)
|
||||
}
|
||||
|
||||
let animationSize = self.item.stillAnimation.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0)
|
||||
|
@ -842,7 +842,7 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
|
||||
}
|
||||
|
||||
if case .phoneNumber = kind, state.setting == .nobody {
|
||||
if state.phoneDiscoveryEnabled == false {
|
||||
if state.phoneDiscoveryEnabled == false || phoneNumber.hasPrefix("888") {
|
||||
entries.append(.phoneDiscoveryHeader(presentationData.theme, presentationData.strings.PrivacyPhoneNumberSettings_DiscoveryHeader))
|
||||
entries.append(.phoneDiscoveryEverybody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenEverybody, state.phoneDiscoveryEnabled != false))
|
||||
entries.append(.phoneDiscoveryMyContacts(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenContacts, state.phoneDiscoveryEnabled == false))
|
||||
|
@ -716,7 +716,11 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
|
||||
|
||||
var authorId: PeerId?
|
||||
if let sendAsPeer = sendAsPeer {
|
||||
authorId = sendAsPeer.id
|
||||
if let peer = peer as? TelegramChannel, case let .broadcast(info) = peer.info, info.flags.contains(.messagesShouldHaveProfiles) {
|
||||
authorId = sendAsPeer.id
|
||||
} else {
|
||||
authorId = peer.id
|
||||
}
|
||||
} else if let peer = peer as? TelegramChannel {
|
||||
if case .broadcast = peer.info {
|
||||
authorId = peer.id
|
||||
@ -748,7 +752,11 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
|
||||
}
|
||||
}
|
||||
if info.flags.contains(.messagesShouldHaveSignatures) {
|
||||
if let sendAsPeer, sendAsPeer.id == peerId {
|
||||
if let sendAsPeer {
|
||||
if sendAsPeer.id == peerId {
|
||||
} else {
|
||||
attributes.append(AuthorSignatureMessageAttribute(signature: sendAsPeer.debugDisplayTitle))
|
||||
}
|
||||
} else {
|
||||
attributes.append(AuthorSignatureMessageAttribute(signature: accountPeer.debugDisplayTitle))
|
||||
}
|
||||
|
@ -172,7 +172,7 @@ public func updateMessageReactionsInteractively(account: Account, messageIds: [M
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public func sendStarsReactionsInteractively(account: Account, messageId: MessageId, count: Int, isAnonymous: Bool) -> Signal<Never, NoError> {
|
||||
public func sendStarsReactionsInteractively(account: Account, messageId: MessageId, count: Int, isAnonymous: Bool?) -> Signal<Never, NoError> {
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
transaction.setPendingMessageAction(type: .sendStarsReaction, id: messageId, action: SendStarsReactionsAction(randomId: Int64.random(in: Int64.min ... Int64.max)))
|
||||
transaction.updateMessage(messageId, update: { currentMessage in
|
||||
@ -182,15 +182,29 @@ public func sendStarsReactionsInteractively(account: Account, messageId: Message
|
||||
}
|
||||
var mappedCount = Int32(count)
|
||||
var attributes = currentMessage.attributes
|
||||
var resolvedIsAnonymous = _internal_getStarsReactionDefaultToPrivate(peerId: messageId.peerId, transaction: transaction)
|
||||
for attribute in attributes {
|
||||
if let attribute = attribute as? ReactionsMessageAttribute {
|
||||
if let myReaction = attribute.topPeers.first(where: { $0.isMy }) {
|
||||
resolvedIsAnonymous = myReaction.isAnonymous
|
||||
}
|
||||
}
|
||||
}
|
||||
loop: for j in 0 ..< attributes.count {
|
||||
if let current = attributes[j] as? PendingStarsReactionsMessageAttribute {
|
||||
mappedCount += current.count
|
||||
resolvedIsAnonymous = current.isAnonymous
|
||||
attributes.remove(at: j)
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
if let isAnonymous {
|
||||
resolvedIsAnonymous = isAnonymous
|
||||
_internal_setStarsReactionDefaultToPrivate(peerId: messageId.peerId, isPrivate: isAnonymous, transaction: transaction)
|
||||
}
|
||||
|
||||
attributes.append(PendingStarsReactionsMessageAttribute(accountPeerId: account.peerId, count: mappedCount, isAnonymous: isAnonymous))
|
||||
attributes.append(PendingStarsReactionsMessageAttribute(accountPeerId: account.peerId, count: mappedCount, isAnonymous: resolvedIsAnonymous))
|
||||
|
||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||
})
|
||||
@ -228,6 +242,8 @@ func _internal_forceSendPendingSendStarsReaction(account: Account, messageId: Me
|
||||
|
||||
func _internal_updateStarsReactionIsAnonymous(account: Account, messageId: MessageId, isAnonymous: Bool) -> Signal<Never, NoError> {
|
||||
return account.postbox.transaction { transaction -> Api.InputPeer? in
|
||||
_internal_setStarsReactionDefaultToPrivate(peerId: messageId.peerId, isPrivate: isAnonymous, transaction: transaction)
|
||||
|
||||
transaction.updateMessage(messageId, update: { currentMessage in
|
||||
var storeForwardInfo: StoreMessageForwardInfo?
|
||||
if let forwardInfo = currentMessage.forwardInfo {
|
||||
@ -395,7 +411,6 @@ private func requestSendStarsReaction(postbox: Postbox, network: Network, stateM
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Never, RequestUpdateMessageReactionError> in
|
||||
stateManager.starsContext?.add(balance: Int64(-count), addTransaction: false)
|
||||
//stateManager.starsContext?.load(force: true)
|
||||
|
||||
return postbox.transaction { transaction -> Void in
|
||||
transaction.setPendingMessageAction(type: .sendStarsReaction, id: messageId, action: UpdateMessageReactionsAction())
|
||||
@ -1012,3 +1027,31 @@ func _internal_updateDefaultReaction(account: Account, reaction: MessageReaction
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
struct StarsReactionDefaultToPrivateData: Codable {
|
||||
var isPrivate: Bool
|
||||
|
||||
init(isPrivate: Bool) {
|
||||
self.isPrivate = isPrivate
|
||||
}
|
||||
|
||||
static func key(peerId: PeerId) -> ValueBoxKey {
|
||||
let value = ValueBoxKey(length: 8)
|
||||
value.setInt64(0, value: peerId.toInt64())
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_getStarsReactionDefaultToPrivate(peerId: PeerId, transaction: Transaction) -> Bool {
|
||||
guard let value = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.starsReactionDefaultToPrivate, key: StarsReactionDefaultToPrivateData.key(peerId: peerId)))?.get(StarsReactionDefaultToPrivateData.self) else {
|
||||
return false
|
||||
}
|
||||
return value.isPrivate
|
||||
}
|
||||
|
||||
func _internal_setStarsReactionDefaultToPrivate(peerId: PeerId, isPrivate: Bool, transaction: Transaction) {
|
||||
guard let entry = CodableEntry(StarsReactionDefaultToPrivateData(isPrivate: isPrivate)) else {
|
||||
return
|
||||
}
|
||||
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.starsReactionDefaultToPrivate, key: StarsReactionDefaultToPrivateData.key(peerId: peerId)), entry: entry)
|
||||
}
|
||||
|
@ -135,6 +135,7 @@ public struct Namespaces {
|
||||
public static let cachedStarsRevenueStats: Int8 = 38
|
||||
public static let cachedRevenueStats: Int8 = 39
|
||||
public static let recommendedApps: Int8 = 40
|
||||
public static let starsReactionDefaultToPrivate: Int8 = 41
|
||||
}
|
||||
|
||||
public struct UnorderedItemList {
|
||||
|
@ -2159,5 +2159,30 @@ public extension TelegramEngine.EngineData.Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct StarsReactionDefaultToPrivate: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
|
||||
public typealias Result = Bool
|
||||
|
||||
fileprivate var id: EnginePeer.Id
|
||||
public var mapKey: EnginePeer.Id {
|
||||
return self.id
|
||||
}
|
||||
|
||||
public init(id: EnginePeer.Id) {
|
||||
self.id = id
|
||||
}
|
||||
|
||||
var key: PostboxViewKey {
|
||||
return .cachedItem(ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.starsReactionDefaultToPrivate, key: StarsReactionDefaultToPrivateData.key(peerId: self.id)))
|
||||
}
|
||||
|
||||
func extract(view: PostboxView) -> Result {
|
||||
if let value = (view as? CachedItemView)?.value?.get(StarsReactionDefaultToPrivateData.self) {
|
||||
return value.isPrivate
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -532,21 +532,7 @@ private class AdMessagesHistoryContextImpl {
|
||||
}
|
||||
|
||||
func markAction(opaqueId: Data) {
|
||||
let account = self.account
|
||||
let signal: Signal<Never, NoError> = account.postbox.transaction { transaction -> Api.InputChannel? in
|
||||
return transaction.getPeer(self.peerId).flatMap(apiInputChannel)
|
||||
}
|
||||
|> mapToSignal { inputChannel -> Signal<Never, NoError> in
|
||||
guard let inputChannel = inputChannel else {
|
||||
return .complete()
|
||||
}
|
||||
return account.network.request(Api.functions.channels.clickSponsoredMessage(channel: inputChannel, randomId: Buffer(data: opaqueId)))
|
||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||
return .single(.boolFalse)
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
let _ = signal.start()
|
||||
_internal_markAdAction(account: self.account, peerId: self.peerId, opaqueId: opaqueId)
|
||||
}
|
||||
|
||||
func remove(opaqueId: Data) {
|
||||
@ -619,3 +605,21 @@ public class AdMessagesHistoryContext {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func _internal_markAdAction(account: Account, peerId: EnginePeer.Id, opaqueId: Data) {
|
||||
let signal: Signal<Never, NoError> = account.postbox.transaction { transaction -> Api.InputChannel? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputChannel)
|
||||
}
|
||||
|> mapToSignal { inputChannel -> Signal<Never, NoError> in
|
||||
guard let inputChannel = inputChannel else {
|
||||
return .complete()
|
||||
}
|
||||
return account.network.request(Api.functions.channels.clickSponsoredMessage(channel: inputChannel, randomId: Buffer(data: opaqueId)))
|
||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||
return .single(.boolFalse)
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
let _ = signal.start()
|
||||
}
|
||||
|
@ -109,13 +109,7 @@ func _internal_peerSendAsAvailablePeers(accountPeerId: PeerId, network: Network,
|
||||
return .single([])
|
||||
}
|
||||
|
||||
if let channel = peer as? TelegramChannel {
|
||||
if case .group = channel.info {
|
||||
} else if case let .broadcast(info) = channel.info {
|
||||
if !info.flags.contains(.messagesShouldHaveProfiles) {
|
||||
return .single([])
|
||||
}
|
||||
}
|
||||
if let _ = peer as? TelegramChannel {
|
||||
} else {
|
||||
return .single([])
|
||||
}
|
||||
|
@ -334,7 +334,7 @@ public extension TelegramEngine {
|
||||
).startStandalone()
|
||||
}
|
||||
|
||||
public func sendStarsReaction(id: EngineMessage.Id, count: Int, isAnonymous: Bool) {
|
||||
public func sendStarsReaction(id: EngineMessage.Id, count: Int, isAnonymous: Bool?) {
|
||||
let _ = sendStarsReactionsInteractively(account: self.account, messageId: id, count: count, isAnonymous: isAnonymous).startStandalone()
|
||||
}
|
||||
|
||||
@ -1466,6 +1466,9 @@ public extension TelegramEngine {
|
||||
public func updateExtendedMedia(messageIds: [EngineMessage.Id]) -> Signal<Never, NoError> {
|
||||
return _internal_updateExtendedMedia(account: self.account, messageIds: messageIds)
|
||||
}
|
||||
public func markAdAction(peerId: EnginePeer.Id, opaqueId: Data) {
|
||||
_internal_markAdAction(account: self.account, peerId: peerId, opaqueId: opaqueId)
|
||||
}
|
||||
|
||||
public func getAllLocalChannels(count: Int) -> Signal<[EnginePeer.Id], NoError> {
|
||||
return self.account.postbox.transaction { transaction -> [EnginePeer.Id] in
|
||||
|
@ -230,7 +230,7 @@ private func _internal_requestStarsSubscriptions(account: Account, peerId: Engin
|
||||
}
|
||||
|> castError(RequestStarsSubscriptionsError.self)
|
||||
|> mapToSignal { peer -> Signal<InternalStarsStatus, RequestStarsSubscriptionsError> in
|
||||
guard let peer, let inputPeer = apiInputPeer(peer) else {
|
||||
guard let peer, let inputPeer = apiInputPeerOrSelf(peer, accountPeerId: peerId) else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
var flags: Int32 = 0
|
||||
@ -965,6 +965,7 @@ private final class StarsSubscriptionsContextImpl {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.nextOffset = status.nextSubscriptionsOffset
|
||||
|
||||
var updatedState = self._state
|
||||
@ -1002,13 +1003,18 @@ private final class StarsSubscriptionsContextImpl {
|
||||
func load(force: Bool) {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
|
||||
guard !self._state.isLoading else {
|
||||
return
|
||||
}
|
||||
|
||||
let currentTimestamp = CFAbsoluteTimeGetCurrent()
|
||||
if let previousLoadTimestamp = self.previousLoadTimestamp, currentTimestamp - previousLoadTimestamp < 60 && !force {
|
||||
return
|
||||
}
|
||||
self.previousLoadTimestamp = currentTimestamp
|
||||
self._state.isLoading = true
|
||||
|
||||
self.disposable.set((_internal_requestStarsSubscriptions(account: self.account, peerId: self.account.peerId, offset: "", missingBalance: false)
|
||||
self.disposable.set((_internal_requestStarsSubscriptions(account: self.account, peerId: self.account.peerId, offset: "", missingBalance: self.missingBalance)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
guard let self else {
|
||||
return
|
||||
|
@ -1622,6 +1622,12 @@ public extension TelegramEngine {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func setStarsReactionDefaultToPrivate(peerId: EnginePeer.Id, isPrivate: Bool) {
|
||||
let _ = self.account.postbox.transaction({ transaction in
|
||||
_internal_setStarsReactionDefaultToPrivate(peerId: peerId, isPrivate: isPrivate, transaction: transaction)
|
||||
}).startStandalone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -531,7 +531,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
|
||||
reactionInactiveForeground: UIColor(rgb: 0xffffff),
|
||||
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
|
||||
reactionActiveForeground: .clear,
|
||||
reactionStarsInactiveBackground: UIColor(rgb: 0xFEF1D4, alpha: 1.0),
|
||||
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
|
||||
reactionStarsInactiveForeground: UIColor(rgb: 0xD3720A),
|
||||
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
|
||||
reactionStarsActiveForeground: .white,
|
||||
@ -547,7 +547,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
|
||||
reactionInactiveForeground: UIColor(rgb: 0xffffff),
|
||||
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
|
||||
reactionActiveForeground: .clear,
|
||||
reactionStarsInactiveBackground: UIColor(rgb: 0xFEF1D4, alpha: 1.0),
|
||||
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
|
||||
reactionStarsInactiveForeground: UIColor(rgb: 0xD3720A),
|
||||
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
|
||||
reactionStarsActiveForeground: .white,
|
||||
@ -569,7 +569,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
|
||||
reactionInactiveForeground: UIColor(rgb: 0xffffff),
|
||||
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
|
||||
reactionActiveForeground: .clear,
|
||||
reactionStarsInactiveBackground: UIColor(rgb: 0xFEF1D4, alpha: 1.0),
|
||||
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
|
||||
reactionStarsInactiveForeground: UIColor(rgb: 0xD3720A),
|
||||
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
|
||||
reactionStarsActiveForeground: .white,
|
||||
@ -585,7 +585,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
|
||||
reactionInactiveForeground: UIColor(rgb: 0xffffff),
|
||||
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
|
||||
reactionActiveForeground: .clear,
|
||||
reactionStarsInactiveBackground: UIColor(rgb: 0xFEF1D4, alpha: 1.0),
|
||||
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
|
||||
reactionStarsInactiveForeground: UIColor(rgb: 0xD3720A),
|
||||
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
|
||||
reactionStarsActiveForeground: .white,
|
||||
@ -604,7 +604,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
|
||||
reactionInactiveForeground: UIColor(rgb: 0xffffff),
|
||||
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
|
||||
reactionActiveForeground: .clear,
|
||||
reactionStarsInactiveBackground: UIColor(rgb: 0xFEF1D4, alpha: 1.0),
|
||||
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
|
||||
reactionStarsInactiveForeground: UIColor(rgb: 0xD3720A),
|
||||
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
|
||||
reactionStarsActiveForeground: .white,
|
||||
@ -620,7 +620,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
|
||||
reactionInactiveForeground: UIColor(rgb: 0xffffff),
|
||||
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
|
||||
reactionActiveForeground: .clear,
|
||||
reactionStarsInactiveBackground: UIColor(rgb: 0xFEF1D4, alpha: 1.0),
|
||||
reactionStarsInactiveBackground: UIColor(rgb: 0xD3720A, alpha: 0.2),
|
||||
reactionStarsInactiveForeground: UIColor(rgb: 0xD3720A),
|
||||
reactionStarsActiveBackground: UIColor(rgb: 0xD3720A, alpha: 1.0),
|
||||
reactionStarsActiveForeground: .white,
|
||||
|
@ -842,12 +842,11 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
case let .peer(peerId):
|
||||
if peerId != item.context.account.peerId {
|
||||
if peerId.isGroupOrChannel && item.message.author != nil {
|
||||
var isBroadcastChannel = false
|
||||
if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
||||
isBroadcastChannel = true
|
||||
}
|
||||
|
||||
if !isBroadcastChannel {
|
||||
if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case let .broadcast(info) = peer.info {
|
||||
if info.flags.contains(.messagesShouldHaveProfiles) {
|
||||
hasAvatar = incoming
|
||||
}
|
||||
} else {
|
||||
hasAvatar = true
|
||||
}
|
||||
}
|
||||
|
@ -1550,7 +1550,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
}
|
||||
}
|
||||
|
||||
if let channel = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info, firstMessage.author?.id != channel.id {
|
||||
if let channel = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info {
|
||||
if info.flags.contains(.messagesShouldHaveProfiles) {
|
||||
var allowAuthor = incoming
|
||||
overrideEffectiveAuthor = true
|
||||
|
@ -75,6 +75,13 @@ private func messagesShouldBeMerged(accountPeerId: PeerId, _ lhs: Message, _ rhs
|
||||
}
|
||||
}
|
||||
|
||||
if let channel = lhs.peers[lhs.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info {
|
||||
if info.flags.contains(.messagesShouldHaveProfiles) {
|
||||
lhsEffectiveAuthor = lhs.author
|
||||
rhsEffectiveAuthor = rhs.author
|
||||
}
|
||||
}
|
||||
|
||||
var sameChat = true
|
||||
if lhs.id.peerId != rhs.id.peerId {
|
||||
sameChat = false
|
||||
@ -344,7 +351,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
||||
if !hasActionMedia {
|
||||
if !isBroadcastChannel {
|
||||
hasAvatar = true
|
||||
} else if let channel = message.peers[message.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info, message.author?.id != channel.id {
|
||||
} else if let channel = message.peers[message.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info {
|
||||
if info.flags.contains(.messagesShouldHaveProfiles) {
|
||||
hasAvatar = true
|
||||
effectiveAuthor = message.author
|
||||
|
@ -464,14 +464,15 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
case let .peer(peerId):
|
||||
if !peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) {
|
||||
if peerId.isGroupOrChannel && item.message.author != nil {
|
||||
var isBroadcastChannel = false
|
||||
if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
||||
isBroadcastChannel = true
|
||||
if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case let .broadcast(info) = peer.info {
|
||||
if info.flags.contains(.messagesShouldHaveProfiles) {
|
||||
hasAvatar = incoming
|
||||
}
|
||||
} else {
|
||||
hasAvatar = true
|
||||
}
|
||||
|
||||
if !isBroadcastChannel {
|
||||
hasAvatar = true
|
||||
} else if case .customChatContents = item.chatLocation {
|
||||
if case .customChatContents = item.chatLocation {
|
||||
hasAvatar = false
|
||||
}
|
||||
}
|
||||
|
@ -128,22 +128,15 @@ private final class BalanceComponent: CombinedComponent {
|
||||
}
|
||||
|
||||
private final class BadgeComponent: Component {
|
||||
enum Direction {
|
||||
case left
|
||||
case right
|
||||
}
|
||||
let theme: PresentationTheme
|
||||
let title: String
|
||||
let inertiaDirection: Direction?
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
title: String,
|
||||
inertiaDirection: Direction?
|
||||
title: String
|
||||
) {
|
||||
self.theme = theme
|
||||
self.title = title
|
||||
self.inertiaDirection = inertiaDirection
|
||||
}
|
||||
|
||||
static func ==(lhs: BadgeComponent, rhs: BadgeComponent) -> Bool {
|
||||
@ -153,9 +146,6 @@ private final class BadgeComponent: Component {
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.inertiaDirection != rhs.inertiaDirection {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -175,7 +165,6 @@ private final class BadgeComponent: Component {
|
||||
private var component: BadgeComponent?
|
||||
|
||||
private var previousAvailableSize: CGSize?
|
||||
private var previousInertiaDirection: BadgeComponent.Direction?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.badgeView = UIView()
|
||||
@ -189,6 +178,7 @@ private final class BadgeComponent: Component {
|
||||
self.badgeView.mask = self.badgeMaskView
|
||||
|
||||
self.badgeForeground = SimpleLayer()
|
||||
self.badgeForeground.anchorPoint = CGPoint()
|
||||
|
||||
self.badgeIcon = UIImageView()
|
||||
self.badgeIcon.contentMode = .center
|
||||
@ -227,6 +217,14 @@ private final class BadgeComponent: Component {
|
||||
required init(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if self.badgeView.frame.contains(point) {
|
||||
return self
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: BadgeComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
if self.component == nil {
|
||||
@ -249,31 +247,7 @@ private final class BadgeComponent: Component {
|
||||
|
||||
self.badgeView.bounds = CGRect(origin: .zero, size: badgeFullSize)
|
||||
|
||||
transition.setAnchorPoint(layer: self.badgeView.layer, anchorPoint: CGPoint(x: 0.5, y: 1.0))
|
||||
|
||||
if component.inertiaDirection != self.previousInertiaDirection {
|
||||
self.previousInertiaDirection = component.inertiaDirection
|
||||
|
||||
var angle: CGFloat = 0.0
|
||||
let transition: ContainedViewLayoutTransition
|
||||
if let inertiaDirection = component.inertiaDirection {
|
||||
switch inertiaDirection {
|
||||
case .left:
|
||||
angle = 0.22
|
||||
case .right:
|
||||
angle = -0.22
|
||||
}
|
||||
transition = .animated(duration: 0.45, curve: .spring)
|
||||
} else {
|
||||
transition = .animated(duration: 0.45, curve: .customSpring(damping: 65.0, initialVelocity: 0.0))
|
||||
}
|
||||
transition.updateTransformRotation(view: self.badgeView, angle: angle)
|
||||
}
|
||||
|
||||
self.badgeForeground.bounds = CGRect(origin: CGPoint(), size: CGSize(width: badgeFullSize.width * 3.0, height: badgeFullSize.height))
|
||||
if self.badgeForeground.animation(forKey: "movement") == nil {
|
||||
self.badgeForeground.position = CGPoint(x: badgeSize.width * 3.0 / 2.0 - self.badgeForeground.frame.width * 0.35, y: badgeFullSize.height / 2.0)
|
||||
}
|
||||
self.badgeForeground.bounds = CGRect(origin: CGPoint(), size: CGSize(width: 600.0, height: badgeFullSize.height + 10.0))
|
||||
|
||||
self.badgeIcon.frame = CGRect(x: 10.0, y: 9.0, width: 30.0, height: 30.0)
|
||||
self.badgeLabelMaskView.frame = CGRect(x: 0.0, y: 0.0, width: 100.0, height: 36.0)
|
||||
@ -312,7 +286,17 @@ private final class BadgeComponent: Component {
|
||||
tailPosition += overflowWidth
|
||||
tailPosition = max(0.0, min(size.width, tailPosition))
|
||||
|
||||
self.badgeShapeLayer.path = generateRoundedRectWithTailPath(rectSize: size, tailPosition: tailPosition / size.width).cgPath
|
||||
let tailPositionFraction = tailPosition / size.width
|
||||
self.badgeShapeLayer.path = generateRoundedRectWithTailPath(rectSize: size, tailPosition: tailPositionFraction).cgPath
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .immediate
|
||||
transition.updateAnchorPoint(layer: self.badgeView.layer, anchorPoint: CGPoint(x: tailPositionFraction, y: 1.0))
|
||||
transition.updatePosition(layer: self.badgeView.layer, position: CGPoint(x: (tailPositionFraction - 0.5) * size.width, y: 0.0))
|
||||
}
|
||||
|
||||
func updateBadgeAngle(angle: CGFloat) {
|
||||
let transition: ContainedViewLayoutTransition = .immediate
|
||||
transition.updateTransformRotation(view: self.badgeView, angle: angle)
|
||||
}
|
||||
|
||||
private func setupGradientAnimations() {
|
||||
@ -323,13 +307,14 @@ private final class BadgeComponent: Component {
|
||||
} else {
|
||||
CATransaction.begin()
|
||||
|
||||
let badgeOffset = (self.badgeForeground.frame.width - self.badgeView.bounds.width) / 2.0
|
||||
let badgePreviousValue = self.badgeForeground.position.x
|
||||
var badgeNewValue: CGFloat = badgeOffset
|
||||
if badgeOffset - badgePreviousValue < self.badgeForeground.frame.width * 0.25 {
|
||||
badgeNewValue -= self.badgeForeground.frame.width * 0.35
|
||||
let badgeNewValue: CGFloat
|
||||
if self.badgeForeground.position.x == -300.0 {
|
||||
badgeNewValue = 0.0
|
||||
} else {
|
||||
badgeNewValue = -300.0
|
||||
}
|
||||
self.badgeForeground.position = CGPoint(x: badgeNewValue, y: self.badgeForeground.bounds.size.height / 2.0)
|
||||
self.badgeForeground.position = CGPoint(x: badgeNewValue, y: 0.0)
|
||||
|
||||
let badgeAnimation = CABasicAnimation(keyPath: "position.x")
|
||||
badgeAnimation.duration = 4.5
|
||||
@ -471,14 +456,14 @@ private final class PeerComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let peer: EnginePeer?
|
||||
let count: Int
|
||||
let count: String
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
peer: EnginePeer?,
|
||||
count: Int
|
||||
count: String
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
@ -547,7 +532,7 @@ private final class PeerComponent: Component {
|
||||
transition: .immediate,
|
||||
component: AnyComponent(PeerBadgeComponent(
|
||||
theme: component.theme,
|
||||
title: "\(component.count)"
|
||||
title: component.count
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 200.0, height: 200.0)
|
||||
@ -806,7 +791,7 @@ private final class SliderBackgroundComponent: Component {
|
||||
topBackgroundTextView.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: animateTopTextAdditionalX, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.3, damping: 100.0, additive: true)
|
||||
}
|
||||
|
||||
topForegroundTextView.isHidden = component.topCutoff == nil
|
||||
topForegroundTextView.isHidden = component.topCutoff == nil || topLineFrame.maxX + topTextSize.width + 20.0 > availableSize.width
|
||||
topBackgroundTextView.isHidden = topForegroundTextView.isHidden
|
||||
self.topBackgroundLine.isHidden = topX < 10.0
|
||||
self.topForegroundLine.isHidden = self.topBackgroundLine.isHidden
|
||||
@ -915,6 +900,83 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
private struct Amount: Equatable {
|
||||
private let sliderSteps: [Int]
|
||||
private let maxRealValue: Int
|
||||
let maxSliderValue: Int
|
||||
private let isLogarithmic: Bool
|
||||
|
||||
private(set) var realValue: Int
|
||||
private(set) var sliderValue: Int
|
||||
|
||||
private static func makeSliderSteps(maxRealValue: Int, isLogarithmic: Bool) -> [Int] {
|
||||
if isLogarithmic {
|
||||
var sliderSteps: [Int] = [ 1, 10, 50, 100, 500, 1_000, 2_000, 5_000, 7_500, 10_000 ]
|
||||
sliderSteps.removeAll(where: { $0 >= maxRealValue })
|
||||
sliderSteps.append(maxRealValue)
|
||||
return sliderSteps
|
||||
} else {
|
||||
return [1, maxRealValue]
|
||||
}
|
||||
}
|
||||
|
||||
private static func remapValueToSlider(realValue: Int, maxSliderValue: Int, steps: [Int]) -> Int {
|
||||
guard realValue >= steps.first!, realValue <= steps.last! else { return 0 }
|
||||
|
||||
for i in 0 ..< steps.count - 1 {
|
||||
if realValue >= steps[i] && realValue <= steps[i + 1] {
|
||||
let range = steps[i + 1] - steps[i]
|
||||
let relativeValue = realValue - steps[i]
|
||||
let stepFraction = Float(relativeValue) / Float(range)
|
||||
return Int(Float(i) * Float(maxSliderValue) / Float(steps.count - 1)) + Int(stepFraction * Float(maxSliderValue) / Float(steps.count - 1))
|
||||
}
|
||||
}
|
||||
return maxSliderValue // Return max slider position if value equals the last step
|
||||
}
|
||||
|
||||
private static func remapSliderToValue(sliderValue: Int, maxSliderValue: Int, steps: [Int]) -> Int {
|
||||
guard sliderValue >= 0, sliderValue <= maxSliderValue else { return steps.first! }
|
||||
|
||||
let stepIndex = Int(Float(sliderValue) / Float(maxSliderValue) * Float(steps.count - 1))
|
||||
let fraction = Float(sliderValue) / Float(maxSliderValue) * Float(steps.count - 1) - Float(stepIndex)
|
||||
|
||||
if stepIndex >= steps.count - 1 {
|
||||
return steps.last!
|
||||
} else {
|
||||
let range = steps[stepIndex + 1] - steps[stepIndex]
|
||||
return steps[stepIndex] + Int(fraction * Float(range))
|
||||
}
|
||||
}
|
||||
|
||||
init(realValue: Int, maxRealValue: Int, maxSliderValue: Int, isLogarithmic: Bool) {
|
||||
self.sliderSteps = Amount.makeSliderSteps(maxRealValue: maxRealValue, isLogarithmic: isLogarithmic)
|
||||
self.maxRealValue = maxRealValue
|
||||
self.maxSliderValue = maxSliderValue
|
||||
self.isLogarithmic = isLogarithmic
|
||||
|
||||
self.realValue = realValue
|
||||
self.sliderValue = Amount.remapValueToSlider(realValue: self.realValue, maxSliderValue: self.maxSliderValue, steps: self.sliderSteps)
|
||||
}
|
||||
|
||||
init(sliderValue: Int, maxRealValue: Int, maxSliderValue: Int, isLogarithmic: Bool) {
|
||||
self.sliderSteps = Amount.makeSliderSteps(maxRealValue: maxRealValue, isLogarithmic: isLogarithmic)
|
||||
self.maxRealValue = maxRealValue
|
||||
self.maxSliderValue = maxSliderValue
|
||||
self.isLogarithmic = isLogarithmic
|
||||
|
||||
self.sliderValue = sliderValue
|
||||
self.realValue = Amount.remapSliderToValue(sliderValue: self.sliderValue, maxSliderValue: self.maxSliderValue, steps: self.sliderSteps)
|
||||
}
|
||||
|
||||
func withRealValue(_ realValue: Int) -> Amount {
|
||||
return Amount(realValue: realValue, maxRealValue: self.maxRealValue, maxSliderValue: self.maxSliderValue, isLogarithmic: self.isLogarithmic)
|
||||
}
|
||||
|
||||
func withSliderValue(_ sliderValue: Int) -> Amount {
|
||||
return Amount(sliderValue: sliderValue, maxRealValue: self.maxRealValue, maxSliderValue: self.maxSliderValue, isLogarithmic: self.isLogarithmic)
|
||||
}
|
||||
}
|
||||
|
||||
final class View: UIView, UIScrollViewDelegate {
|
||||
private let dimView: UIView
|
||||
private let backgroundLayer: SimpleLayer
|
||||
@ -922,6 +984,7 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
private let scrollView: ScrollView
|
||||
private let scrollContentClippingView: SparseContainerView
|
||||
private let scrollContentView: UIView
|
||||
private let hierarchyTrackingNode: HierarchyTrackingNode
|
||||
|
||||
private let leftButton = ComponentView<Empty>()
|
||||
private let closeButton = ComponentView<Empty>()
|
||||
@ -959,7 +1022,10 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
private var topOffsetDistance: CGFloat?
|
||||
|
||||
private var balance: Int64?
|
||||
private var amount: Int64 = 1
|
||||
|
||||
private var amount: Amount = Amount(realValue: 1, maxRealValue: 1000, maxSliderValue: 1000, isLogarithmic: true)
|
||||
private var didChangeAmount: Bool = false
|
||||
|
||||
private var isAnonymous: Bool = false
|
||||
private var cachedStarImage: (UIImage, PresentationTheme)?
|
||||
private var cachedCloseImage: UIImage?
|
||||
@ -968,6 +1034,8 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
|
||||
private var balanceDisposable: Disposable?
|
||||
|
||||
private var badgePhysicsLink: SharedDisplayLinkDriver.Link?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.bottomOverscrollLimit = 200.0
|
||||
|
||||
@ -986,6 +1054,8 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
|
||||
self.scrollContentView = UIView()
|
||||
|
||||
self.hierarchyTrackingNode = HierarchyTrackingNode()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.dimView)
|
||||
@ -1016,6 +1086,30 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
self.addSubview(self.navigationBarContainer)
|
||||
|
||||
self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
||||
|
||||
self.addSubnode(self.hierarchyTrackingNode)
|
||||
|
||||
self.hierarchyTrackingNode.updated = { [weak self] value in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if value {
|
||||
if self.badgePhysicsLink == nil {
|
||||
let badgePhysicsLink = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.updateBadgePhysics()
|
||||
})
|
||||
self.badgePhysicsLink = badgePhysicsLink
|
||||
}
|
||||
} else {
|
||||
if let badgePhysicsLink = self.badgePhysicsLink {
|
||||
self.badgePhysicsLink = nil
|
||||
badgePhysicsLink.invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@ -1058,6 +1152,12 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
return result
|
||||
}
|
||||
|
||||
if let badgeView = self.badge.view, badgeView.hitTest(self.convert(point, to: badgeView), with: event) != nil {
|
||||
if let sliderView = self.slider.view as? SliderComponent.View, let hitTestTarget = sliderView.hitTestTarget {
|
||||
return hitTestTarget
|
||||
}
|
||||
}
|
||||
|
||||
let result = super.hitTest(point, with: event)
|
||||
return result
|
||||
}
|
||||
@ -1081,7 +1181,7 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
|
||||
transition.setPosition(view: self.navigationBarContainer, position: CGPoint(x: 0.0, y: topOffset + itemLayout.containerInset))
|
||||
|
||||
let topOffsetDistance: CGFloat = min(200.0, floor(itemLayout.containerSize.height * 0.25))
|
||||
let topOffsetDistance: CGFloat = min(60.0, floor(itemLayout.containerSize.height * 0.25))
|
||||
self.topOffsetDistance = topOffsetDistance
|
||||
var topOffsetFraction = topOffset / topOffsetDistance
|
||||
topOffsetFraction = max(0.0, min(1.0, topOffsetFraction))
|
||||
@ -1123,7 +1223,78 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
|
||||
private var previousSliderValue: Float = 0.0
|
||||
private var previousTimestamp: Double?
|
||||
private var inertiaDirection: BadgeComponent.Direction?
|
||||
|
||||
private var badgeAngularSpeed: CGFloat = 0.0
|
||||
private var badgeAngle: CGFloat = 0.0
|
||||
private var previousBadgeX: CGFloat?
|
||||
private var previousPhysicsTimestamp: Double?
|
||||
|
||||
private func updateBadgePhysics() {
|
||||
let timestamp = CACurrentMediaTime()
|
||||
|
||||
let deltaTime: CGFloat
|
||||
if let previousPhysicsTimestamp = self.previousPhysicsTimestamp {
|
||||
deltaTime = CGFloat(min(1.0 / 60.0, timestamp - previousPhysicsTimestamp))
|
||||
} else {
|
||||
deltaTime = CGFloat(1.0 / 60.0)
|
||||
}
|
||||
self.previousPhysicsTimestamp = timestamp
|
||||
|
||||
guard let badgeView = self.badge.view as? BadgeComponent.View else {
|
||||
return
|
||||
}
|
||||
let badgeX = badgeView.center.x
|
||||
|
||||
let horizontalVelocity: CGFloat
|
||||
if let previousBadgeX = self.previousBadgeX {
|
||||
horizontalVelocity = (badgeX - previousBadgeX) / deltaTime
|
||||
} else {
|
||||
horizontalVelocity = 0.0
|
||||
}
|
||||
self.previousBadgeX = badgeX
|
||||
|
||||
let testSpringFriction: CGFloat = 9.0
|
||||
let testSpringConstant: CGFloat = 243.0
|
||||
|
||||
let frictionConstant: CGFloat = testSpringFriction
|
||||
let springConstant: CGFloat = testSpringConstant
|
||||
let time: CGFloat = deltaTime
|
||||
|
||||
var badgeAngle = self.badgeAngle
|
||||
|
||||
badgeAngle -= horizontalVelocity * 0.0001
|
||||
if abs(badgeAngle) > 0.22 {
|
||||
badgeAngle = badgeAngle < 0.0 ? -0.22 : 0.22
|
||||
}
|
||||
|
||||
// friction force = velocity * friction constant
|
||||
let frictionForce = self.badgeAngularSpeed * frictionConstant
|
||||
// spring force = (target point - current position) * spring constant
|
||||
let springForce = -badgeAngle * springConstant
|
||||
// force = spring force - friction force
|
||||
let force = springForce - frictionForce
|
||||
|
||||
// velocity = current velocity + force * time / mass
|
||||
self.badgeAngularSpeed = self.badgeAngularSpeed + force * time
|
||||
// position = current position + velocity * time
|
||||
badgeAngle = badgeAngle + self.badgeAngularSpeed * time
|
||||
badgeAngle = badgeAngle.isNaN ? 0.0 : badgeAngle
|
||||
|
||||
let epsilon: CGFloat = 0.01
|
||||
if abs(badgeAngle) < epsilon && abs(self.badgeAngularSpeed) < epsilon {
|
||||
badgeAngle = 0.0
|
||||
self.badgeAngularSpeed = 0.0
|
||||
}
|
||||
|
||||
if abs(badgeAngle) > 0.22 {
|
||||
badgeAngle = badgeAngle < 0.0 ? -0.22 : 0.22
|
||||
}
|
||||
|
||||
if self.badgeAngle != badgeAngle {
|
||||
self.badgeAngle = badgeAngle
|
||||
badgeView.updateBadgeAngle(angle: self.badgeAngle)
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: ChatSendStarsScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
|
||||
let environment = environment[ViewControllerComponentContainer.Environment.self].value
|
||||
@ -1131,11 +1302,21 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
|
||||
let resetScrolling = self.scrollView.bounds.width != availableSize.width
|
||||
|
||||
let sideInset: CGFloat = 16.0
|
||||
let fillingSize: CGFloat
|
||||
if case .regular = environment.metrics.widthClass {
|
||||
fillingSize = min(availableSize.width, 414.0) - environment.safeInsets.left * 2.0
|
||||
} else {
|
||||
fillingSize = min(availableSize.width, 428.0) - environment.safeInsets.left * 2.0
|
||||
}
|
||||
let sideInset: CGFloat = floor((availableSize.width - fillingSize) * 0.5) + 16.0
|
||||
|
||||
if self.component == nil {
|
||||
self.balance = component.balance
|
||||
self.amount = 50
|
||||
var isLogarithmic = true
|
||||
if let data = component.context.currentAppConfiguration.with({ $0 }).data, let value = data["ios_stars_reaction_logarithmic_scale"] as? Double {
|
||||
isLogarithmic = Int(value) != 0
|
||||
}
|
||||
self.amount = Amount(realValue: 50, maxRealValue: component.maxAmount, maxSliderValue: 999, isLogarithmic: isLogarithmic)
|
||||
if let myTopPeer = component.myTopPeer {
|
||||
self.isAnonymous = myTopPeer.isAnonymous
|
||||
}
|
||||
@ -1182,8 +1363,8 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
let sliderSize = self.slider.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(SliderComponent(
|
||||
valueCount: component.maxAmount,
|
||||
value: Int(self.amount),
|
||||
valueCount: self.amount.maxSliderValue + 1,
|
||||
value: self.amount.sliderValue,
|
||||
markPositions: false,
|
||||
trackBackgroundColor: .clear,
|
||||
trackForegroundColor: .clear,
|
||||
@ -1193,7 +1374,9 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
self.amount = 1 + Int64(value)
|
||||
self.amount = self.amount.withSliderValue(value)
|
||||
self.didChangeAmount = true
|
||||
|
||||
self.state?.updated(transition: ComponentTransition(animation: .none).withUserData(IsAdjustingAmountHint()))
|
||||
|
||||
let sliderValue = Float(value) / Float(component.maxAmount)
|
||||
@ -1207,21 +1390,7 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
let speed = deltaValue / Float(deltaTime)
|
||||
let newSpeed = max(0, min(65.0, speed * 70.0))
|
||||
|
||||
var inertiaDirection: BadgeComponent.Direction?
|
||||
if newSpeed >= 1.0 {
|
||||
if delta > 0.0 {
|
||||
inertiaDirection = .right
|
||||
} else {
|
||||
inertiaDirection = .left
|
||||
}
|
||||
}
|
||||
if inertiaDirection != self.inertiaDirection {
|
||||
self.inertiaDirection = inertiaDirection
|
||||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
|
||||
if newSpeed < 0.01 && deltaValue < 0.001 {
|
||||
|
||||
} else {
|
||||
self.badgeStars.update(speed: newSpeed, delta: delta)
|
||||
}
|
||||
@ -1238,10 +1407,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
self.previousTimestamp = nil
|
||||
self.badgeStars.update(speed: 0.0)
|
||||
}
|
||||
if self.inertiaDirection != nil {
|
||||
self.inertiaDirection = nil
|
||||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
@ -1250,7 +1415,7 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
let sliderFrame = CGRect(origin: CGPoint(x: sliderInset, y: contentHeight + 127.0), size: sliderSize)
|
||||
let sliderBackgroundFrame = CGRect(origin: CGPoint(x: sliderFrame.minX - 8.0, y: sliderFrame.minY + 7.0), size: CGSize(width: sliderFrame.width + 16.0, height: sliderFrame.height - 14.0))
|
||||
|
||||
let progressFraction: CGFloat = CGFloat(self.amount) / CGFloat(component.maxAmount - 1)
|
||||
let progressFraction: CGFloat = CGFloat(self.amount.sliderValue) / CGFloat(self.amount.maxSliderValue)
|
||||
|
||||
let topOthersCount: Int? = component.topPeers.filter({ !$0.isMy }).max(by: { $0.count < $1.count })?.count
|
||||
var topCount: Int?
|
||||
@ -1301,17 +1466,11 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
|
||||
transition.setFrame(view: sliderBackgroundView, frame: sliderBackgroundFrame)
|
||||
|
||||
var effectiveInertiaDirection = self.inertiaDirection
|
||||
if progressFraction <= 0.03 || progressFraction >= 0.97 {
|
||||
effectiveInertiaDirection = nil
|
||||
}
|
||||
|
||||
let badgeSize = self.badge.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(BadgeComponent(
|
||||
theme: environment.theme,
|
||||
title: "\(self.amount)",
|
||||
inertiaDirection: effectiveInertiaDirection
|
||||
title: "\(self.amount.realValue)"
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 200.0, height: 200.0)
|
||||
@ -1363,7 +1522,7 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 120.0, height: 100.0)
|
||||
)
|
||||
let leftButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: floor((56.0 - leftButtonSize.height) * 0.5)), size: leftButtonSize)
|
||||
let leftButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: floor((56.0 - leftButtonSize.height) * 0.5)), size: leftButtonSize)
|
||||
if let leftButtonView = self.leftButton.view {
|
||||
if leftButtonView.superview == nil {
|
||||
self.navigationBarContainer.addSubview(leftButtonView)
|
||||
@ -1542,15 +1701,24 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
if let index = mappedTopPeers.firstIndex(where: { $0.isMy }) {
|
||||
mappedTopPeers.remove(at: index)
|
||||
}
|
||||
var myCount = Int(self.amount)
|
||||
|
||||
var myCount = 0
|
||||
if let myTopPeer = component.myTopPeer {
|
||||
myCount += myTopPeer.count
|
||||
}
|
||||
mappedTopPeers.append(ChatSendStarsScreen.TopPeer(
|
||||
peer: self.isAnonymous ? nil : component.myPeer,
|
||||
isMy: true,
|
||||
count: myCount
|
||||
))
|
||||
var myCountAddition = 0
|
||||
if self.didChangeAmount {
|
||||
myCountAddition = Int(self.amount.realValue)
|
||||
}
|
||||
myCount += myCountAddition
|
||||
if myCount != 0 {
|
||||
mappedTopPeers.append(ChatSendStarsScreen.TopPeer(
|
||||
randomIndex: -1,
|
||||
peer: self.isAnonymous ? nil : component.myPeer,
|
||||
isMy: true,
|
||||
count: myCount
|
||||
))
|
||||
}
|
||||
mappedTopPeers.sort(by: { $0.count > $1.count })
|
||||
if mappedTopPeers.count > 3 {
|
||||
mappedTopPeers = Array(mappedTopPeers.prefix(3))
|
||||
@ -1578,6 +1746,11 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
self.topPeerItems[topPeer.id] = itemView
|
||||
}
|
||||
|
||||
let itemCountString = "\(topPeer.count)"
|
||||
/*if topPeer.isMy && myCountAddition != 0 && topPeer.count > myCountAddition {
|
||||
itemCountString = "\(topPeer.count - myCountAddition) +\(myCountAddition)"
|
||||
}*/
|
||||
|
||||
let itemSize = itemView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(PlainButtonComponent(
|
||||
@ -1586,7 +1759,7 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
peer: topPeer.peer,
|
||||
count: topPeer.count
|
||||
count: itemCountString
|
||||
)),
|
||||
effectAlignment: .center,
|
||||
action: { [weak self] in
|
||||
@ -1747,7 +1920,7 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
self.cachedStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: .white)!, environment.theme)
|
||||
}
|
||||
|
||||
let buttonString = environment.strings.SendStarReactions_SendButtonTitle("\(self.amount)").string
|
||||
let buttonString = environment.strings.SendStarReactions_SendButtonTitle("\(self.amount.realValue)").string
|
||||
let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: .white, paragraphAlignment: .center)
|
||||
if let range = buttonAttributedString.string.range(of: "#"), let starImage = self.cachedStarImage?.0 {
|
||||
buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string))
|
||||
@ -1778,7 +1951,7 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
return
|
||||
}
|
||||
|
||||
if balance < self.amount {
|
||||
if balance < self.amount.realValue {
|
||||
let _ = (component.context.engine.payments.starsTopUpOptions()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] options in
|
||||
@ -1789,7 +1962,7 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
return
|
||||
}
|
||||
|
||||
let purchaseScreen = component.context.sharedContext.makeStarsPurchaseScreen(context: component.context, starsContext: starsContext, options: options, purpose: .transfer(peerId: component.peer.id, requiredStars: self.amount), completion: { result in
|
||||
let purchaseScreen = component.context.sharedContext.makeStarsPurchaseScreen(context: component.context, starsContext: starsContext, options: options, purpose: .transfer(peerId: component.peer.id, requiredStars: Int64(self.amount.realValue)), completion: { result in
|
||||
let _ = result
|
||||
//TODO:release
|
||||
})
|
||||
@ -1805,13 +1978,13 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
}
|
||||
let isBecomingTop: Bool
|
||||
if let topCount {
|
||||
isBecomingTop = self.amount > topCount
|
||||
isBecomingTop = self.amount.realValue > topCount
|
||||
} else {
|
||||
isBecomingTop = true
|
||||
}
|
||||
|
||||
component.completion(
|
||||
self.amount,
|
||||
Int64(self.amount.realValue),
|
||||
self.isAnonymous,
|
||||
isBecomingTop,
|
||||
ChatSendStarsScreen.TransitionOut(
|
||||
@ -1891,7 +2064,7 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
transition.setFrame(view: self.scrollContentView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset + containerInset), size: CGSize(width: availableSize.width, height: contentHeight)))
|
||||
|
||||
transition.setPosition(layer: self.backgroundLayer, position: CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0))
|
||||
transition.setBounds(layer: self.backgroundLayer, bounds: CGRect(origin: CGPoint(), size: availableSize))
|
||||
transition.setBounds(layer: self.backgroundLayer, bounds: CGRect(origin: CGPoint(), size: CGSize(width: fillingSize, height: availableSize.height)))
|
||||
|
||||
let scrollClippingFrame = CGRect(origin: CGPoint(x: 0.0, y: containerInset), size: CGSize(width: availableSize.width, height: clippingY - containerInset))
|
||||
transition.setPosition(view: self.scrollContentClippingView, position: scrollClippingFrame.center)
|
||||
@ -1953,7 +2126,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
|
||||
fileprivate final class TopPeer: Equatable {
|
||||
enum Id: Hashable {
|
||||
case anonymous
|
||||
case anonymous(Int)
|
||||
case my
|
||||
case peer(EnginePeer.Id)
|
||||
}
|
||||
@ -1964,7 +2137,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
} else if let peer = self.peer {
|
||||
return .peer(peer.id)
|
||||
} else {
|
||||
return .anonymous
|
||||
return .anonymous(self.randomIndex)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1972,17 +2145,22 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
return self.peer == nil
|
||||
}
|
||||
|
||||
let randomIndex: Int
|
||||
let peer: EnginePeer?
|
||||
let isMy: Bool
|
||||
let count: Int
|
||||
|
||||
init(peer: EnginePeer?, isMy: Bool, count: Int) {
|
||||
init(randomIndex: Int, peer: EnginePeer?, isMy: Bool, count: Int) {
|
||||
self.randomIndex = randomIndex
|
||||
self.peer = peer
|
||||
self.isMy = isMy
|
||||
self.count = count
|
||||
}
|
||||
|
||||
static func ==(lhs: TopPeer, rhs: TopPeer) -> Bool {
|
||||
if lhs.randomIndex != rhs.randomIndex {
|
||||
return false
|
||||
}
|
||||
if lhs.peer != rhs.peer {
|
||||
return false
|
||||
}
|
||||
@ -2099,6 +2277,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
return nil
|
||||
}
|
||||
|
||||
var nextRandomIndex = 0
|
||||
return InitialData(
|
||||
peer: peer,
|
||||
myPeer: myPeer,
|
||||
@ -2107,7 +2286,10 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
currentSentAmount: currentSentAmount,
|
||||
topPeers: topPeers.compactMap { topPeer -> ChatSendStarsScreen.TopPeer? in
|
||||
guard let topPeerId = topPeer.peerId else {
|
||||
let randomIndex = nextRandomIndex
|
||||
nextRandomIndex += 1
|
||||
return ChatSendStarsScreen.TopPeer(
|
||||
randomIndex: randomIndex,
|
||||
peer: nil,
|
||||
isMy: topPeer.isMy,
|
||||
count: Int(topPeer.count)
|
||||
@ -2119,7 +2301,10 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
guard let topPeerValue else {
|
||||
return nil
|
||||
}
|
||||
let randomIndex = nextRandomIndex
|
||||
nextRandomIndex += 1
|
||||
return ChatSendStarsScreen.TopPeer(
|
||||
randomIndex: randomIndex,
|
||||
peer: topPeer.isAnonymous ? nil : topPeerValue,
|
||||
isMy: topPeer.isMy,
|
||||
count: Int(topPeer.count)
|
||||
@ -2128,6 +2313,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
myTopPeer: myTopPeer.flatMap { topPeer -> ChatSendStarsScreen.TopPeer? in
|
||||
guard let topPeerId = topPeer.peerId else {
|
||||
return ChatSendStarsScreen.TopPeer(
|
||||
randomIndex: -1,
|
||||
peer: nil,
|
||||
isMy: topPeer.isMy,
|
||||
count: Int(topPeer.count)
|
||||
@ -2140,6 +2326,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
return nil
|
||||
}
|
||||
return ChatSendStarsScreen.TopPeer(
|
||||
randomIndex: -1,
|
||||
peer: topPeer.isAnonymous ? nil : topPeerValue,
|
||||
isMy: topPeer.isMy,
|
||||
count: Int(topPeer.count)
|
||||
|
@ -154,13 +154,17 @@ final class PeerAllowedReactionsScreenComponent: Component {
|
||||
if Set(availableReactions.reactions.filter({ $0.isEnabled }).map(\.value)) == Set(enabledReactions.map(\.reaction)) {
|
||||
allowedReactions = .all
|
||||
} else {
|
||||
allowedReactions = .limited(enabledReactions.map(\.reaction))
|
||||
if enabledReactions.isEmpty {
|
||||
allowedReactions = .empty
|
||||
} else {
|
||||
allowedReactions = .limited(enabledReactions.map(\.reaction))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
allowedReactions = .empty
|
||||
}
|
||||
|
||||
let reactionSettings = PeerReactionSettings(allowedReactions: allowedReactions, maxReactionCount: self.allowedReactionCount >= 11 ? nil : Int32(self.allowedReactionCount), starsAllowed: self.areStarsReactionsEnabled)
|
||||
let reactionSettings = PeerReactionSettings(allowedReactions: allowedReactions, maxReactionCount: self.allowedReactionCount >= 11 ? nil : Int32(self.allowedReactionCount), starsAllowed: self.isEnabled && self.areStarsReactionsEnabled)
|
||||
|
||||
if self.appliedReactionSettings != reactionSettings {
|
||||
if case .empty = allowedReactions {
|
||||
@ -255,7 +259,7 @@ final class PeerAllowedReactionsScreenComponent: Component {
|
||||
} else {
|
||||
allowedReactions = .empty
|
||||
}
|
||||
let reactionSettings = PeerReactionSettings(allowedReactions: allowedReactions, maxReactionCount: self.allowedReactionCount == 11 ? nil : Int32(self.allowedReactionCount), starsAllowed: self.areStarsReactionsEnabled)
|
||||
let reactionSettings = PeerReactionSettings(allowedReactions: allowedReactions, maxReactionCount: self.allowedReactionCount == 11 ? nil : Int32(self.allowedReactionCount), starsAllowed: self.isEnabled && self.areStarsReactionsEnabled)
|
||||
|
||||
let applyDisposable = (component.context.engine.peers.updatePeerReactionSettings(peerId: component.peerId, reactionSettings: reactionSettings)
|
||||
|> deliverOnMainQueue).start(error: { [weak self] error in
|
||||
@ -603,7 +607,7 @@ final class PeerAllowedReactionsScreenComponent: Component {
|
||||
self.displayInput = false
|
||||
}
|
||||
|
||||
self.state?.updated(transition: .easeInOut(duration: 0.25))
|
||||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
}
|
||||
)),
|
||||
|
@ -70,6 +70,10 @@ public final class SliderComponent: Component {
|
||||
private var component: SliderComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
public var hitTestTarget: UIView? {
|
||||
return self.sliderView
|
||||
}
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
@ -294,7 +294,7 @@ extension ChatControllerImpl {
|
||||
if !hasAnonymousPeer {
|
||||
allPeers?.insert(SendAsPeer(peer: channel, subscribers: 0, isPremiumRequired: false), at: 0)
|
||||
}
|
||||
} else if let channel = peerViewMainPeer(peerView) as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.messagesShouldHaveProfiles) {
|
||||
} else if let channel = peerViewMainPeer(peerView) as? TelegramChannel, case .broadcast = channel.info {
|
||||
allPeers = peers
|
||||
|
||||
var hasAnonymousPeer = false
|
||||
|
@ -375,7 +375,9 @@ extension ChatControllerImpl {
|
||||
}
|
||||
if let itemNode = itemNode, let targetView = itemNode.targetReactionView(value: chosenReaction) {
|
||||
if !"".isEmpty {
|
||||
self.chatDisplayNode.wrappingNode.triggerRipple(at: targetView.convert(targetView.bounds.center, to: self.chatDisplayNode.view))
|
||||
if self.context.sharedContext.energyUsageSettings.fullTranslucency {
|
||||
self.chatDisplayNode.wrappingNode.triggerRipple(at: targetView.convert(targetView.bounds.center, to: self.chatDisplayNode.view))
|
||||
}
|
||||
}
|
||||
}
|
||||
}, completion: {})
|
||||
@ -387,8 +389,62 @@ extension ChatControllerImpl {
|
||||
}
|
||||
}
|
||||
|
||||
self.context.engine.messages.sendStarsReaction(id: message.id, count: 1, isAnonymous: false)
|
||||
self.displayOrUpdateSendStarsUndo(messageId: message.id, count: 1)
|
||||
guard let starsContext = self.context.starsContext else {
|
||||
return
|
||||
}
|
||||
guard let peerId = self.chatLocation.peerId else {
|
||||
return
|
||||
}
|
||||
let _ = (combineLatest(
|
||||
starsContext.state,
|
||||
self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ReactionSettings(id: peerId))
|
||||
)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] state, reactionSettings in
|
||||
guard let strongSelf = self, let balance = state?.balance else {
|
||||
return
|
||||
}
|
||||
|
||||
if case let .known(reactionSettings) = reactionSettings, let starsAllowed = reactionSettings.starsAllowed, !starsAllowed {
|
||||
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer {
|
||||
//TODO:localize
|
||||
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: "Star Reactions were disabled in \(peer.debugDisplayTitle).", actions: [
|
||||
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})
|
||||
]), in: .window(.root))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if balance < 1 {
|
||||
controller?.dismiss(completion: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = (strongSelf.context.engine.payments.starsTopUpOptions()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak strongSelf] options in
|
||||
guard let strongSelf else {
|
||||
return
|
||||
}
|
||||
guard let starsContext = strongSelf.context.starsContext else {
|
||||
return
|
||||
}
|
||||
|
||||
let purchaseScreen = strongSelf.context.sharedContext.makeStarsPurchaseScreen(context: strongSelf.context, starsContext: starsContext, options: options, purpose: .transfer(peerId: peerId, requiredStars: 1), completion: { result in
|
||||
let _ = result
|
||||
//TODO:release
|
||||
})
|
||||
strongSelf.push(purchaseScreen)
|
||||
})
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.context.engine.messages.sendStarsReaction(id: message.id, count: 1, isAnonymous: nil)
|
||||
strongSelf.displayOrUpdateSendStarsUndo(messageId: message.id, count: 1)
|
||||
})
|
||||
} else {
|
||||
let chosenReaction: MessageReaction.Reaction = chosenUpdatedReaction.reaction
|
||||
|
||||
|
@ -1686,7 +1686,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let itemNode = itemNode, let targetView = itemNode.targetReactionView(value: chosenReaction) {
|
||||
if let itemNode = itemNode, let targetView = itemNode.targetReactionView(value: chosenReaction), strongSelf.context.sharedContext.energyUsageSettings.fullTranslucency {
|
||||
strongSelf.chatDisplayNode.wrappingNode.triggerRipple(at: targetView.convert(targetView.bounds.center, to: strongSelf.chatDisplayNode.view))
|
||||
}
|
||||
},
|
||||
@ -1701,13 +1701,29 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
guard let starsContext = strongSelf.context.starsContext else {
|
||||
return
|
||||
}
|
||||
let _ = (starsContext.state
|
||||
guard let peerId = strongSelf.chatLocation.peerId else {
|
||||
return
|
||||
}
|
||||
let _ = (combineLatest(
|
||||
starsContext.state,
|
||||
strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ReactionSettings(id: peerId))
|
||||
)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak strongSelf] state in
|
||||
|> deliverOnMainQueue).start(next: { [weak strongSelf] state, reactionSettings in
|
||||
guard let strongSelf, let balance = state?.balance else {
|
||||
return
|
||||
}
|
||||
|
||||
if case let .known(reactionSettings) = reactionSettings, let starsAllowed = reactionSettings.starsAllowed, !starsAllowed {
|
||||
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer {
|
||||
//TODO:localize
|
||||
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: "Star Reactions were disabled in \(peer.debugDisplayTitle).", actions: [
|
||||
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})
|
||||
]), in: .window(.root))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if balance < 1 {
|
||||
let _ = (strongSelf.context.engine.payments.starsTopUpOptions()
|
||||
|> take(1)
|
||||
@ -1729,7 +1745,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.context.engine.messages.sendStarsReaction(id: message.id, count: 1, isAnonymous: false)
|
||||
strongSelf.context.engine.messages.sendStarsReaction(id: message.id, count: 1, isAnonymous: nil)
|
||||
strongSelf.displayOrUpdateSendStarsUndo(messageId: message.id, count: 1)
|
||||
})
|
||||
} else {
|
||||
|
@ -375,101 +375,121 @@ extension ChatControllerImpl {
|
||||
}
|
||||
self.context.engine.messages.forceSendPendingSendStarsReaction(id: message.id)
|
||||
|
||||
let reactionsAttribute = mergedMessageReactions(attributes: message.attributes, isTags: false)
|
||||
let _ = (ChatSendStarsScreen.initialData(context: self.context, peerId: message.id.peerId, messageId: message.id, topPeers: reactionsAttribute?.topPeers ?? [])
|
||||
|> deliverOnMainQueue).start(next: { [weak self] initialData in
|
||||
guard let self, let initialData else {
|
||||
guard let peerId = self.chatLocation.peerId else {
|
||||
return
|
||||
}
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ReactionSettings(id: peerId))
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] reactionSettings in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
HapticFeedback().tap()
|
||||
self.push(ChatSendStarsScreen(context: self.context, initialData: initialData, completion: { [weak self] amount, isAnonymous, isBecomingTop, transitionOut in
|
||||
guard let self, amount > 0 else {
|
||||
|
||||
let reactionsAttribute = mergedMessageReactions(attributes: message.attributes, isTags: false)
|
||||
let _ = (ChatSendStarsScreen.initialData(context: self.context, peerId: message.id.peerId, messageId: message.id, topPeers: reactionsAttribute?.topPeers ?? [])
|
||||
|> deliverOnMainQueue).start(next: { [weak self] initialData in
|
||||
guard let self, let initialData else {
|
||||
return
|
||||
}
|
||||
|
||||
var sourceItemNode: ChatMessageItemView?
|
||||
self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ChatMessageItemView {
|
||||
if itemNode.item?.message.id == message.id {
|
||||
sourceItemNode = itemNode
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let itemNode = sourceItemNode, let item = itemNode.item, let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: .stars) {
|
||||
var reactionItem: ReactionItem?
|
||||
|
||||
for reaction in availableReactions.reactions {
|
||||
guard let centerAnimation = reaction.centerAnimation else {
|
||||
continue
|
||||
}
|
||||
guard let aroundAnimation = reaction.aroundAnimation else {
|
||||
continue
|
||||
}
|
||||
if reaction.value == .stars {
|
||||
reactionItem = ReactionItem(
|
||||
reaction: ReactionItem.Reaction(rawValue: reaction.value),
|
||||
appearAnimation: reaction.appearAnimation,
|
||||
stillAnimation: reaction.selectAnimation,
|
||||
listAnimation: centerAnimation,
|
||||
largeListAnimation: reaction.activateAnimation,
|
||||
applicationAnimation: aroundAnimation,
|
||||
largeApplicationAnimation: reaction.effectAnimation,
|
||||
isCustom: false
|
||||
)
|
||||
break
|
||||
}
|
||||
HapticFeedback().tap()
|
||||
self.push(ChatSendStarsScreen(context: self.context, initialData: initialData, completion: { [weak self] amount, isAnonymous, isBecomingTop, transitionOut in
|
||||
guard let self, amount > 0 else {
|
||||
return
|
||||
}
|
||||
|
||||
if let reactionItem {
|
||||
let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: self.chatDisplayNode.historyNode.takeGenericReactionEffect())
|
||||
|
||||
self.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
|
||||
|
||||
self.view.window?.addSubview(standaloneReactionAnimation.view)
|
||||
standaloneReactionAnimation.frame = self.chatDisplayNode.bounds
|
||||
standaloneReactionAnimation.animateOutToReaction(
|
||||
context: self.context,
|
||||
theme: self.presentationData.theme,
|
||||
item: reactionItem,
|
||||
value: .stars,
|
||||
sourceView: transitionOut.sourceView,
|
||||
targetView: targetView,
|
||||
hideNode: false,
|
||||
forceSwitchToInlineImmediately: false,
|
||||
animateTargetContainer: nil,
|
||||
addStandaloneReactionAnimation: { [weak self] standaloneReactionAnimation in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
|
||||
standaloneReactionAnimation.frame = self.chatDisplayNode.bounds
|
||||
self.chatDisplayNode.addSubnode(standaloneReactionAnimation)
|
||||
},
|
||||
onHit: { [weak self, weak itemNode] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
if isBecomingTop {
|
||||
self.chatDisplayNode.animateQuizCorrectOptionSelected()
|
||||
}
|
||||
|
||||
if let itemNode, let targetView = itemNode.targetReactionView(value: .stars) {
|
||||
self.chatDisplayNode.wrappingNode.triggerRipple(at: targetView.convert(targetView.bounds.center, to: self.chatDisplayNode.view))
|
||||
}
|
||||
},
|
||||
completion: { [weak standaloneReactionAnimation] in
|
||||
standaloneReactionAnimation?.view.removeFromSuperview()
|
||||
if case let .known(reactionSettings) = reactionSettings, let starsAllowed = reactionSettings.starsAllowed, !starsAllowed {
|
||||
if let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer {
|
||||
//TODO:localize
|
||||
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: "Star Reactions were disabled in \(peer.debugDisplayTitle).", actions: [
|
||||
TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_OK, action: {})
|
||||
]), in: .window(.root))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var sourceItemNode: ChatMessageItemView?
|
||||
self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ChatMessageItemView {
|
||||
if itemNode.item?.message.id == message.id {
|
||||
sourceItemNode = itemNode
|
||||
return
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = self.context.engine.messages.sendStarsReaction(id: message.id, count: Int(amount), isAnonymous: isAnonymous)
|
||||
self.displayOrUpdateSendStarsUndo(messageId: message.id, count: Int(amount))
|
||||
}))
|
||||
|
||||
if let itemNode = sourceItemNode, let item = itemNode.item, let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: .stars) {
|
||||
var reactionItem: ReactionItem?
|
||||
|
||||
for reaction in availableReactions.reactions {
|
||||
guard let centerAnimation = reaction.centerAnimation else {
|
||||
continue
|
||||
}
|
||||
guard let aroundAnimation = reaction.aroundAnimation else {
|
||||
continue
|
||||
}
|
||||
if reaction.value == .stars {
|
||||
reactionItem = ReactionItem(
|
||||
reaction: ReactionItem.Reaction(rawValue: reaction.value),
|
||||
appearAnimation: reaction.appearAnimation,
|
||||
stillAnimation: reaction.selectAnimation,
|
||||
listAnimation: centerAnimation,
|
||||
largeListAnimation: reaction.activateAnimation,
|
||||
applicationAnimation: aroundAnimation,
|
||||
largeApplicationAnimation: reaction.effectAnimation,
|
||||
isCustom: false
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let reactionItem {
|
||||
let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: self.chatDisplayNode.historyNode.takeGenericReactionEffect())
|
||||
|
||||
self.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
|
||||
|
||||
self.view.window?.addSubview(standaloneReactionAnimation.view)
|
||||
standaloneReactionAnimation.frame = self.chatDisplayNode.bounds
|
||||
standaloneReactionAnimation.animateOutToReaction(
|
||||
context: self.context,
|
||||
theme: self.presentationData.theme,
|
||||
item: reactionItem,
|
||||
value: .stars,
|
||||
sourceView: transitionOut.sourceView,
|
||||
targetView: targetView,
|
||||
hideNode: false,
|
||||
forceSwitchToInlineImmediately: false,
|
||||
animateTargetContainer: nil,
|
||||
addStandaloneReactionAnimation: { [weak self] standaloneReactionAnimation in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
|
||||
standaloneReactionAnimation.frame = self.chatDisplayNode.bounds
|
||||
self.chatDisplayNode.addSubnode(standaloneReactionAnimation)
|
||||
},
|
||||
onHit: { [weak self, weak itemNode] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
if isBecomingTop {
|
||||
self.chatDisplayNode.animateQuizCorrectOptionSelected()
|
||||
}
|
||||
|
||||
if let itemNode, let targetView = itemNode.targetReactionView(value: .stars), self.context.sharedContext.energyUsageSettings.fullTranslucency {
|
||||
self.chatDisplayNode.wrappingNode.triggerRipple(at: targetView.convert(targetView.bounds.center, to: self.chatDisplayNode.view))
|
||||
}
|
||||
},
|
||||
completion: { [weak standaloneReactionAnimation] in
|
||||
standaloneReactionAnimation?.view.removeFromSuperview()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let _ = self.context.engine.messages.sendStarsReaction(id: message.id, count: Int(amount), isAnonymous: isAnonymous)
|
||||
self.displayOrUpdateSendStarsUndo(messageId: message.id, count: Int(amount))
|
||||
}))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
private let elevatedLayout: Bool
|
||||
private let placementPosition: UndoOverlayController.Position
|
||||
private var statusNode: RadialStatusNode?
|
||||
private var didStartStatusNode: Bool = false
|
||||
private let timerTextNode: ImmediateTextNode
|
||||
private let avatarNode: AvatarNode?
|
||||
private let iconNode: ASImageNode?
|
||||
@ -67,6 +68,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
private var isTimeoutDisabled: Bool = false
|
||||
private var originalRemainingSeconds: Double
|
||||
private var remainingSeconds: Double
|
||||
private let undoTextColor: UIColor
|
||||
private var timer: SwiftSignalKit.Timer?
|
||||
|
||||
private var validLayout: ContainerViewLayout?
|
||||
@ -413,8 +415,10 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
self.animatedTextItems = textItems
|
||||
|
||||
displayUndo = true
|
||||
self.originalRemainingSeconds = 4.5
|
||||
self.originalRemainingSeconds = 4.9
|
||||
isUserInteractionEnabled = true
|
||||
|
||||
self.statusNode = RadialStatusNode(backgroundNodeColor: .clear)
|
||||
case let .messagesUnpinned(title, text, undo, isHidden):
|
||||
self.avatarNode = nil
|
||||
self.iconNode = nil
|
||||
@ -1246,6 +1250,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
}
|
||||
|
||||
self.remainingSeconds = self.originalRemainingSeconds
|
||||
self.undoTextColor = undoTextColor
|
||||
|
||||
self.undoButtonTextNode = ImmediateTextNode()
|
||||
self.undoButtonTextNode.displaysAsynchronously = false
|
||||
@ -1271,7 +1276,15 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
switch content {
|
||||
case .removedChat:
|
||||
self.panelWrapperNode.addSubnode(self.timerTextNode)
|
||||
case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .copy, .mediaSaved, .paymentSent, .starsSent, .image, .inviteRequestSent, .notificationSoundAdded, .universal, .premiumPaywall, .peers, .messageTagged:
|
||||
case .starsSent:
|
||||
self.panelWrapperNode.addSubnode(self.timerTextNode)
|
||||
|
||||
if self.textNode.tapAttributeAction != nil || displayUndo {
|
||||
self.isUserInteractionEnabled = true
|
||||
} else {
|
||||
self.isUserInteractionEnabled = false
|
||||
}
|
||||
case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .copy, .mediaSaved, .paymentSent, .image, .inviteRequestSent, .notificationSoundAdded, .universal, .premiumPaywall, .peers, .messageTagged:
|
||||
if self.textNode.tapAttributeAction != nil || displayUndo {
|
||||
self.isUserInteractionEnabled = true
|
||||
} else {
|
||||
@ -1420,7 +1433,8 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
let _ = self.action(.commit)
|
||||
self.dismiss()
|
||||
} else {
|
||||
if Int(self.remainingSeconds) != previousRemainingSeconds || (self.timerTextNode.attributedText?.string ?? "").isEmpty {
|
||||
let remainingSecondsString = "\(Int(self.remainingSeconds))"
|
||||
if Int(self.remainingSeconds) != previousRemainingSeconds || self.timerTextNode.attributedText?.string != remainingSecondsString {
|
||||
if !self.timerTextNode.bounds.size.width.isZero, let snapshot = self.timerTextNode.view.snapshotContentTree() {
|
||||
self.panelNode.view.insertSubview(snapshot, aboveSubview: self.timerTextNode.view)
|
||||
snapshot.frame = self.timerTextNode.frame
|
||||
@ -1431,7 +1445,13 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
snapshot?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
self.timerTextNode.attributedText = NSAttributedString(string: "\(Int(self.remainingSeconds))", font: Font.regular(16.0), textColor: .white)
|
||||
let timerColor: UIColor
|
||||
if case .starsSent = self.content {
|
||||
timerColor = self.undoTextColor
|
||||
} else {
|
||||
timerColor = .white
|
||||
}
|
||||
self.timerTextNode.attributedText = NSAttributedString(string: remainingSecondsString, font: Font.regular(16.0), textColor: timerColor)
|
||||
if let validLayout = self.validLayout {
|
||||
self.containerLayoutUpdated(layout: validLayout, transition: .immediate)
|
||||
}
|
||||
@ -1450,6 +1470,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
self.timer?.invalidate()
|
||||
self.timer = nil
|
||||
self.remainingSeconds = self.originalRemainingSeconds
|
||||
self.didStartStatusNode = false
|
||||
self.checkTimer()
|
||||
}
|
||||
|
||||
@ -1508,7 +1529,6 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
let firstLayout = self.validLayout == nil
|
||||
self.validLayout = layout
|
||||
|
||||
var preferredSize: CGSize?
|
||||
@ -1630,10 +1650,14 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
transition.updateFrame(node: self.panelWrapperNode, frame: panelWrapperFrame)
|
||||
self.effectView.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width - leftMargin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight)
|
||||
|
||||
let buttonTextFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - leftMargin * 2.0, y: floor((contentHeight - buttonTextSize.height) / 2.0)), size: buttonTextSize)
|
||||
var buttonTextFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - leftMargin * 2.0, y: floor((contentHeight - buttonTextSize.height) / 2.0)), size: buttonTextSize)
|
||||
var undoButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - 8.0 - leftMargin * 2.0, y: 0.0), size: CGSize(width: layout.safeInsets.right + rightInset + buttonTextSize.width + 8.0 + leftMargin, height: contentHeight))
|
||||
if case .starsSent = self.content {
|
||||
let buttonOffset: CGFloat = -34.0
|
||||
undoButtonFrame.origin.x += buttonOffset
|
||||
buttonTextFrame.origin.x += buttonOffset
|
||||
}
|
||||
transition.updateFrame(node: self.undoButtonTextNode, frame: buttonTextFrame)
|
||||
|
||||
let undoButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - 8.0 - leftMargin * 2.0, y: 0.0), size: CGSize(width: layout.safeInsets.right + rightInset + buttonTextSize.width + 8.0 + leftMargin, height: contentHeight))
|
||||
self.undoButtonNode.frame = undoButtonFrame
|
||||
|
||||
self.buttonNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.undoButtonNode.supernode == nil ? panelFrame.width : undoButtonFrame.minX, height: contentHeight))
|
||||
@ -1728,13 +1752,28 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
}
|
||||
|
||||
let timerTextSize = self.timerTextNode.updateLayout(CGSize(width: 100.0, height: 100.0))
|
||||
transition.updateFrame(node: self.timerTextNode, frame: CGRect(origin: CGPoint(x: floor((leftInset - timerTextSize.width) / 2.0), y: floor((contentHeight - timerTextSize.height) / 2.0)), size: timerTextSize))
|
||||
if case .starsSent = self.content {
|
||||
transition.updateFrame(node: self.timerTextNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset + floor((rightInset - timerTextSize.width) * 0.5) - 46.0)), y: floor((contentHeight - timerTextSize.height) / 2.0)), size: timerTextSize))
|
||||
} else {
|
||||
transition.updateFrame(node: self.timerTextNode, frame: CGRect(origin: CGPoint(x: floor((leftInset - timerTextSize.width) / 2.0), y: floor((contentHeight - timerTextSize.height) / 2.0)), size: timerTextSize))
|
||||
}
|
||||
|
||||
if let statusNode = self.statusNode {
|
||||
let statusSize: CGFloat = 30.0
|
||||
transition.updateFrame(node: statusNode, frame: CGRect(origin: CGPoint(x: floor((leftInset - statusSize) / 2.0), y: floor((contentHeight - statusSize) / 2.0)), size: CGSize(width: statusSize, height: statusSize)))
|
||||
if firstLayout {
|
||||
statusNode.transitionToState(.secretTimeout(color: .white, icon: .none, beginTime: CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, timeout: Double(self.remainingSeconds), sparks: false), completion: {})
|
||||
var statusFrame = CGRect(origin: CGPoint(x: floor((leftInset - statusSize) / 2.0), y: floor((contentHeight - statusSize) / 2.0)), size: CGSize(width: statusSize, height: statusSize))
|
||||
if case .starsSent = self.content {
|
||||
statusFrame.origin.x = layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - statusSize - 23.0
|
||||
}
|
||||
transition.updateFrame(node: statusNode, frame: statusFrame)
|
||||
if !self.didStartStatusNode {
|
||||
let statusColor: UIColor
|
||||
if case .starsSent = self.content {
|
||||
statusColor = self.undoTextColor
|
||||
} else {
|
||||
statusColor = .white
|
||||
}
|
||||
statusNode.transitionToState(.secretTimeout(color: statusColor, icon: .none, beginTime: CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, timeout: Double(self.remainingSeconds), sparks: false), completion: {})
|
||||
self.didStartStatusNode = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"app": "11.0",
|
||||
"app": "11.0.1",
|
||||
"xcode": "15.2",
|
||||
"bazel": "7.1.1",
|
||||
"macos": "13.0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user