Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2024-08-16 19:29:22 +02:00
commit 8de066f912
28 changed files with 733 additions and 278 deletions

View File

@ -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()

View File

@ -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

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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))

View File

@ -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))
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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
}
}
}
}
}

View File

@ -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()
}

View File

@ -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([])
}

View File

@ -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

View File

@ -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

View File

@ -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()
}
}
}

View File

@ -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,

View File

@ -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
}
}

View File

@ -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

View File

@ -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

View File

@ -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
}
}

View File

@ -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)

View File

@ -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)
}
}
)),

View File

@ -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)
}

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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))
}))
})
})
}

View File

@ -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
}
}

View File

@ -1,5 +1,5 @@
{
"app": "11.0",
"app": "11.0.1",
"xcode": "15.2",
"bazel": "7.1.1",
"macos": "13.0"