mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 11:23:48 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
53d3ecc6a6
@ -12779,3 +12779,29 @@ Sorry for the inconvenience.";
|
||||
|
||||
"ChatList.SubscriptionsLowBalance.Multiple.Title" = "%@ for your subscriptions";
|
||||
"ChatList.SubscriptionsLowBalance.Multiple.Text" = "Insufficient funds to cover your subscriptions.";
|
||||
|
||||
"ChatList.Search.SectionApps" = "APPS";
|
||||
|
||||
"Channel.ShowAuthors" = "Show Authors' Profiles";
|
||||
"Channel.ShowAuthorsFooter" = "Add names and photos of admins to the messages they post, linking to their profiles.";
|
||||
|
||||
"SendStarReactions.Title" = "React with Stars";
|
||||
"SendStarReactions.Balance" = "Balance";
|
||||
"SendStarReactions.UserLabelAnonymous" = "Anonymous";
|
||||
"SendStarReactions.SliderTop" = "TOP";
|
||||
"SendStarReactions.TextSentStars_1" = "You sent **1** star to support this post.";
|
||||
"SendStarReactions.TextSentStars_any" = "You sent **%d** stars to support this post.";
|
||||
"SendStarReactions.TextGeneric" = "Choose how many stars you want to send to **%@** to support this post.";
|
||||
"SendStarReactions.SectionTop" = "Top Senders";
|
||||
"SendStarReactions.ShowMyselfInTop" = "Show me in Top Senders";
|
||||
"SendStarReactions.SendButtonTitle" = "Send # %@";
|
||||
"SendStarReactions.TermsOfServiceFooter" = "By sending Stars you agree to the [Terms of Service](https://telegram.org/privacy)";
|
||||
|
||||
"PeerInfo.AllowedReactions.StarReactions" = "Enable Paid Reactions";
|
||||
"PeerInfo.AllowedReactions.StarReactionsFooter" = "Switch this on to let your subscribers set paid reactions with Telegram Stars, which you will be able to withdraw later as TON. [Learn More >](https://telegram.org/privacy)";
|
||||
|
||||
"Chat.ToastStarsSent.Title_1" = "Star sent!";
|
||||
"Chat.ToastStarsSent.Title_any" = "Stars sent!";
|
||||
"Chat.ToastStarsSent.Text" = "You have reacted with %1$@ %2$@.";
|
||||
"Chat.ToastStarsSent.TextStarAmount_1" = "star";
|
||||
"Chat.ToastStarsSent.TextStarAmount_any" = "stars";
|
||||
|
@ -94,8 +94,12 @@ private func calculateColors(context: AccountContext?, explicitColorIndex: Int?,
|
||||
colors = AvatarNode.repostColors
|
||||
} else if case .repliesIcon = icon {
|
||||
colors = AvatarNode.savedMessagesColors
|
||||
} else if case .anonymousSavedMessagesIcon = icon {
|
||||
colors = AvatarNode.savedMessagesColors
|
||||
} else if case let .anonymousSavedMessagesIcon(isColored) = icon {
|
||||
if isColored {
|
||||
colors = AvatarNode.savedMessagesColors
|
||||
} else {
|
||||
colors = AvatarNode.grayscaleColors
|
||||
}
|
||||
} else if case .myNotesIcon = icon {
|
||||
colors = AvatarNode.savedMessagesColors
|
||||
} else if case .editAvatarIcon = icon, let theme {
|
||||
@ -178,7 +182,7 @@ private enum AvatarNodeIcon: Equatable {
|
||||
case none
|
||||
case savedMessagesIcon
|
||||
case repliesIcon
|
||||
case anonymousSavedMessagesIcon
|
||||
case anonymousSavedMessagesIcon(isColored: Bool)
|
||||
case myNotesIcon
|
||||
case archivedChatsIcon(hiddenByDefault: Bool)
|
||||
case editAvatarIcon
|
||||
@ -192,7 +196,7 @@ public enum AvatarNodeImageOverride: Equatable {
|
||||
case image(TelegramMediaImageRepresentation)
|
||||
case savedMessagesIcon
|
||||
case repliesIcon
|
||||
case anonymousSavedMessagesIcon
|
||||
case anonymousSavedMessagesIcon(isColored: Bool)
|
||||
case myNotesIcon
|
||||
case archivedChatsIcon(hiddenByDefault: Bool)
|
||||
case editAvatarIcon(forceNone: Bool)
|
||||
@ -506,9 +510,9 @@ public final class AvatarNode: ASDisplayNode {
|
||||
case .repliesIcon:
|
||||
representation = nil
|
||||
icon = .repliesIcon
|
||||
case .anonymousSavedMessagesIcon:
|
||||
case let .anonymousSavedMessagesIcon(isColored):
|
||||
representation = nil
|
||||
icon = .anonymousSavedMessagesIcon
|
||||
icon = .anonymousSavedMessagesIcon(isColored: isColored)
|
||||
case .myNotesIcon:
|
||||
representation = nil
|
||||
icon = .myNotesIcon
|
||||
@ -681,9 +685,9 @@ public final class AvatarNode: ASDisplayNode {
|
||||
case .repliesIcon:
|
||||
representation = nil
|
||||
icon = .repliesIcon
|
||||
case .anonymousSavedMessagesIcon:
|
||||
case let .anonymousSavedMessagesIcon(isColored):
|
||||
representation = nil
|
||||
icon = .anonymousSavedMessagesIcon
|
||||
icon = .anonymousSavedMessagesIcon(isColored: isColored)
|
||||
case .myNotesIcon:
|
||||
representation = nil
|
||||
icon = .myNotesIcon
|
||||
|
@ -742,8 +742,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
if case .channels = key {
|
||||
headerType = .channels
|
||||
} else if case .apps = key {
|
||||
//TODO:localize
|
||||
headerType = .text("APPS", AnyHashable("apps"))
|
||||
headerType = .text(strings.ChatList_Search_SectionApps, AnyHashable("apps"))
|
||||
} else {
|
||||
if filter.contains(.onlyGroups) {
|
||||
headerType = .chats
|
||||
|
@ -1661,7 +1661,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
} else if peer.id.isReplies {
|
||||
overrideImage = .repliesIcon
|
||||
} else if peer.id.isAnonymousSavedMessages {
|
||||
overrideImage = .anonymousSavedMessagesIcon
|
||||
overrideImage = .anonymousSavedMessagesIcon(isColored: true)
|
||||
} else if peer.id == item.context.account.peerId && !displayAsMessage {
|
||||
if case .savedMessagesChats = item.chatListLocation {
|
||||
overrideImage = .myNotesIcon
|
||||
|
@ -1093,7 +1093,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
} else if peer.id.isReplies, case .generalSearch = item.peerMode {
|
||||
overrideImage = .repliesIcon
|
||||
} else if peer.id.isAnonymousSavedMessages, case .generalSearch = item.peerMode {
|
||||
overrideImage = .anonymousSavedMessagesIcon
|
||||
overrideImage = .anonymousSavedMessagesIcon(isColored: true)
|
||||
} else if peer.isDeleted {
|
||||
overrideImage = .deletedIcon
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ final class ReactionPreviewView: UIView {
|
||||
size: size,
|
||||
placeholderColor: .clear,
|
||||
themeColor: .white,
|
||||
loopMode: .count(0)
|
||||
loopMode: .forever
|
||||
),
|
||||
isVisibleForAnimations: true,
|
||||
action: nil
|
||||
|
@ -85,7 +85,7 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
|
||||
} else if chatPeer.id.isReplies {
|
||||
self.avatarNode.setPeer(context: context, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: peer, overrideImage: .repliesIcon)
|
||||
} else if chatPeer.id.isAnonymousSavedMessages {
|
||||
self.avatarNode.setPeer(context: context, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: peer, overrideImage: .anonymousSavedMessagesIcon)
|
||||
self.avatarNode.setPeer(context: context, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: peer, overrideImage: .anonymousSavedMessagesIcon(isColored: true))
|
||||
} else {
|
||||
var overrideImage: AvatarNodeImageOverride?
|
||||
if chatPeer.isDeleted {
|
||||
|
@ -496,9 +496,10 @@ private func channelAdminsControllerEntries(presentationData: PresentationData,
|
||||
|
||||
if !isGroup && peer.hasPermission(.sendSomething) {
|
||||
entries.append(.signMessages(presentationData.theme, presentationData.strings.Channel_SignMessages, signMessagesEnabled, showAuthorProfilesEnabled))
|
||||
//TODO:localize
|
||||
entries.append(.showAuthorProfiles(presentationData.theme, "Show Authors' Profiles", showAuthorProfilesEnabled, signMessagesEnabled))
|
||||
entries.append(.signMessagesInfo(presentationData.theme, "Add names and photos of admins to the messages they post, linking to their profiles."))
|
||||
entries.append(.showAuthorProfiles(presentationData.theme, presentationData.strings.Channel_ShowAuthors, showAuthorProfilesEnabled, signMessagesEnabled))
|
||||
if signMessagesEnabled {
|
||||
entries.append(.signMessagesInfo(presentationData.theme, presentationData.strings.Channel_ShowAuthorsFooter))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if case let .legacyGroup(peer) = peer {
|
||||
|
@ -2622,6 +2622,8 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
foundItemNode?.animateHideEffects()
|
||||
|
||||
if let customReactionSource = self.customReactionSource {
|
||||
let itemNode = ReactionNode(context: self.context, theme: self.presentationData.theme, item: customReactionSource.item, icon: .none, animationCache: self.animationCache, animationRenderer: self.animationRenderer, loopIdle: false, isLocked: false, useDirectRendering: false)
|
||||
if let contents = customReactionSource.layer.contents {
|
||||
@ -2673,7 +2675,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
if case .builtin = itemNode.item.reaction.rawValue {
|
||||
selfTargetBounds = selfTargetBounds.insetBy(dx: -selfTargetBounds.width * 0.5, dy: -selfTargetBounds.height * 0.5)
|
||||
} else if case .stars = itemNode.item.reaction.rawValue {
|
||||
selfTargetBounds = selfTargetBounds.insetBy(dx: selfTargetBounds.width * 0.0, dy: selfTargetBounds.height * 0.0)
|
||||
selfTargetBounds = selfTargetBounds.insetBy(dx: -selfTargetBounds.width * 0.13, dy: -selfTargetBounds.height * 0.13).offsetBy(dx: -0.5, dy: -0.5)
|
||||
}
|
||||
|
||||
let selfTargetRect = self.view.convert(selfTargetBounds, from: targetView)
|
||||
@ -2905,7 +2907,14 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
})
|
||||
|
||||
if !switchToInlineImmediately {
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + min(5.0, 2.0 * UIView.animationDurationFactor()), execute: {
|
||||
let maxDuration: Double
|
||||
if case .stars = value {
|
||||
maxDuration = 3.0
|
||||
} else {
|
||||
maxDuration = 2.0
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + min(5.0, maxDuration * UIView.animationDurationFactor()), execute: {
|
||||
if self.didTriggerExpandedReaction {
|
||||
self.animateFromItemNodeToReaction(itemNode: itemNode, targetView: targetView, hideNode: hideNode, completion: { [weak self] in
|
||||
if let strongSelf = self, strongSelf.didTriggerExpandedReaction, let addStandaloneReactionAnimation = addStandaloneReactionAnimation {
|
||||
@ -3986,8 +3995,6 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
||||
starView.isHidden = true
|
||||
} else {
|
||||
targetView.updateIsAnimationHidden(isAnimationHidden: true, transition: .immediate)
|
||||
//TODO:release
|
||||
//targetView.addSubnode(itemNode)
|
||||
}
|
||||
} else if let targetView = targetView as? UIImageView {
|
||||
starView.isHidden = true
|
||||
|
@ -296,6 +296,12 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
self.customContentsNode?.contents = contents
|
||||
}
|
||||
|
||||
public func animateHideEffects() {
|
||||
if let starsEffectLayer = self.starsEffectLayer {
|
||||
starsEffectLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
}
|
||||
}
|
||||
|
||||
public func updateLayout(size: CGSize, isExpanded: Bool, largeExpanded: Bool, isPreviewing: Bool, transition: ContainedViewLayoutTransition) {
|
||||
let intrinsicSize = size
|
||||
|
||||
|
@ -64,8 +64,9 @@ extension ReactionsMessageAttribute {
|
||||
peerId: peerId?.peerId,
|
||||
count: count,
|
||||
isTop: (flags & (1 << 0)) != 0,
|
||||
isMy: (flags & (1 << 1)) != 0)
|
||||
)
|
||||
isMy: (flags & (1 << 1)) != 0,
|
||||
isAnonymous: (flags & (1 << 2)) != 0
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -210,10 +211,16 @@ public func mergedMessageReactions(attributes: [MessageAttribute], isTags: Bool)
|
||||
updatedCount += reactions[index].count
|
||||
reactions.remove(at: index)
|
||||
}
|
||||
var topPeers = result.topPeers
|
||||
if let index = topPeers.firstIndex(where: { $0.isMy }) {
|
||||
topPeers[index].count += pendingStars.count
|
||||
} else {
|
||||
topPeers.append(ReactionsMessageAttribute.TopPeer(peerId: pendingStars.accountPeerId, count: pendingStars.count, isTop: false, isMy: true, isAnonymous: pendingStars.isAnonymous))
|
||||
}
|
||||
reactions.insert(MessageReaction(value: .stars, count: updatedCount, chosenOrder: -1), at: 0)
|
||||
return ReactionsMessageAttribute(canViewList: current?.canViewList ?? false, isTags: current?.isTags ?? isTags, reactions: reactions, recentPeers: result.recentPeers, topPeers: result.topPeers)
|
||||
return ReactionsMessageAttribute(canViewList: current?.canViewList ?? false, isTags: current?.isTags ?? isTags, reactions: reactions, recentPeers: result.recentPeers, topPeers: topPeers)
|
||||
} else {
|
||||
return ReactionsMessageAttribute(canViewList: current?.canViewList ?? false, isTags: current?.isTags ?? isTags, reactions: [MessageReaction(value: .stars, count: pendingStars.count, chosenOrder: -1)], recentPeers: [], topPeers: [])
|
||||
return ReactionsMessageAttribute(canViewList: current?.canViewList ?? false, isTags: current?.isTags ?? isTags, reactions: [MessageReaction(value: .stars, count: pendingStars.count, chosenOrder: -1)], recentPeers: [], topPeers: [ReactionsMessageAttribute.TopPeer(peerId: pendingStars.accountPeerId, count: pendingStars.count, isTop: false, isMy: true, isAnonymous: pendingStars.isAnonymous)])
|
||||
}
|
||||
} else {
|
||||
return result
|
||||
@ -254,8 +261,9 @@ extension ReactionsMessageAttribute {
|
||||
peerId: peerId?.peerId,
|
||||
count: count,
|
||||
isTop: (flags & (1 << 0)) != 0,
|
||||
isMy: (flags & (1 << 1)) != 0)
|
||||
)
|
||||
isMy: (flags & (1 << 1)) != 0,
|
||||
isAnonymous: (flags & (1 << 2)) != 0
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -172,7 +172,7 @@ public func updateMessageReactionsInteractively(account: Account, messageIds: [M
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public func sendStarsReactionsInteractively(account: Account, messageId: MessageId, count: Int) -> 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
|
||||
@ -190,7 +190,7 @@ public func sendStarsReactionsInteractively(account: Account, messageId: Message
|
||||
}
|
||||
}
|
||||
|
||||
attributes.append(PendingStarsReactionsMessageAttribute(accountPeerId: account.peerId, count: mappedCount))
|
||||
attributes.append(PendingStarsReactionsMessageAttribute(accountPeerId: account.peerId, count: mappedCount, isAnonymous: isAnonymous))
|
||||
|
||||
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))
|
||||
})
|
||||
@ -220,6 +220,41 @@ func cancelPendingSendStarsReactionInteractively(account: Account, messageId: Me
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
func _internal_updateStarsReactionIsAnonymous(account: Account, messageId: MessageId, isAnonymous: Bool) -> Signal<Never, NoError> {
|
||||
return account.postbox.transaction { transaction -> Api.InputPeer? in
|
||||
transaction.updateMessage(messageId, update: { currentMessage in
|
||||
var storeForwardInfo: StoreMessageForwardInfo?
|
||||
if let forwardInfo = currentMessage.forwardInfo {
|
||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags)
|
||||
}
|
||||
var attributes = currentMessage.attributes
|
||||
for j in (0 ..< attributes.count).reversed() {
|
||||
if let attribute = attributes[j] as? ReactionsMessageAttribute {
|
||||
var updatedTopPeers = attribute.topPeers
|
||||
if let index = updatedTopPeers.firstIndex(where: { $0.isMy }) {
|
||||
updatedTopPeers[index].isAnonymous = isAnonymous
|
||||
}
|
||||
attributes[j] = ReactionsMessageAttribute(canViewList: attribute.canViewList, isTags: attribute.isTags, reactions: attribute.reactions, recentPeers: attribute.recentPeers, topPeers: updatedTopPeers)
|
||||
}
|
||||
}
|
||||
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))
|
||||
})
|
||||
|
||||
return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
|
||||
}
|
||||
|> mapToSignal { inputPeer -> Signal<Never, NoError> in
|
||||
guard let inputPeer else {
|
||||
return .complete()
|
||||
}
|
||||
|
||||
return account.network.request(Api.functions.messages.togglePaidReactionPrivacy(peer: inputPeer, msgId: messageId.id, private: isAnonymous ? .boolTrue : .boolFalse))
|
||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||
return .single(.boolFalse)
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
}
|
||||
|
||||
private enum RequestUpdateMessageReactionError {
|
||||
case generic
|
||||
}
|
||||
@ -308,7 +343,7 @@ private func requestUpdateMessageReaction(postbox: Postbox, network: Network, st
|
||||
}
|
||||
|
||||
private func requestSendStarsReaction(postbox: Postbox, network: Network, stateManager: AccountStateManager, messageId: MessageId) -> Signal<Never, RequestUpdateMessageReactionError> {
|
||||
return postbox.transaction { transaction -> (Peer, Int32)? in
|
||||
return postbox.transaction { transaction -> (Peer, Int32, Bool)? in
|
||||
guard let peer = transaction.getPeer(messageId.peerId) else {
|
||||
return nil
|
||||
}
|
||||
@ -316,17 +351,19 @@ private func requestSendStarsReaction(postbox: Postbox, network: Network, stateM
|
||||
return nil
|
||||
}
|
||||
var count: Int32 = 0
|
||||
var isAnonymous = false
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? PendingStarsReactionsMessageAttribute {
|
||||
count += attribute.count
|
||||
isAnonymous = attribute.isAnonymous
|
||||
break
|
||||
}
|
||||
}
|
||||
return (peer, count)
|
||||
return (peer, count, isAnonymous)
|
||||
}
|
||||
|> castError(RequestUpdateMessageReactionError.self)
|
||||
|> mapToSignal { peerAndValue in
|
||||
guard let (peer, count) = peerAndValue else {
|
||||
guard let (peer, count, isAnonymous) = peerAndValue else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
guard let inputPeer = apiInputPeer(peer) else {
|
||||
@ -341,6 +378,11 @@ private func requestSendStarsReaction(postbox: Postbox, network: Network, stateM
|
||||
let timestampPart = UInt64(UInt32(bitPattern: Int32(Date().timeIntervalSince1970)))
|
||||
let randomId = (timestampPart << 32) | randomPartId
|
||||
|
||||
var flags: Int32 = 0
|
||||
if isAnonymous {
|
||||
flags |= 1 << 0
|
||||
}
|
||||
|
||||
let signal: Signal<Never, RequestUpdateMessageReactionError> = network.request(Api.functions.messages.sendPaidReaction(flags: 0, peer: inputPeer, msgId: messageId.id, count: count, randomId: Int64(bitPattern: randomId)))
|
||||
|> mapError { _ -> RequestUpdateMessageReactionError in
|
||||
return .generic
|
||||
|
@ -333,12 +333,14 @@ public final class ReactionsMessageAttribute: Equatable, MessageAttribute {
|
||||
public var count: Int32
|
||||
public var isTop: Bool
|
||||
public var isMy: Bool
|
||||
public var isAnonymous: Bool
|
||||
|
||||
public init(peerId: PeerId?, count: Int32, isTop: Bool, isMy: Bool) {
|
||||
public init(peerId: PeerId?, count: Int32, isTop: Bool, isMy: Bool, isAnonymous: Bool) {
|
||||
self.peerId = peerId
|
||||
self.count = count
|
||||
self.isMy = isMy
|
||||
self.isTop = isTop
|
||||
self.isAnonymous = isAnonymous
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
@ -350,6 +352,7 @@ public final class ReactionsMessageAttribute: Equatable, MessageAttribute {
|
||||
self.count = decoder.decodeInt32ForKey("c", orElse: 0)
|
||||
self.isTop = decoder.decodeBoolForKey("t", orElse: false)
|
||||
self.isMy = decoder.decodeBoolForKey("m", orElse: false)
|
||||
self.isAnonymous = decoder.decodeBoolForKey("anon", orElse: false)
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
@ -361,6 +364,7 @@ public final class ReactionsMessageAttribute: Equatable, MessageAttribute {
|
||||
encoder.encodeInt32(self.count, forKey: "c")
|
||||
encoder.encodeBool(self.isTop, forKey: "t")
|
||||
encoder.encodeBool(self.isMy, forKey: "m")
|
||||
encoder.encodeBool(self.isAnonymous, forKey: "anon")
|
||||
}
|
||||
}
|
||||
|
||||
@ -561,6 +565,7 @@ public final class PendingReactionsMessageAttribute: MessageAttribute {
|
||||
public final class PendingStarsReactionsMessageAttribute: MessageAttribute {
|
||||
public let accountPeerId: PeerId?
|
||||
public let count: Int32
|
||||
public let isAnonymous: Bool
|
||||
|
||||
public var associatedPeerIds: [PeerId] {
|
||||
var peerIds: [PeerId] = []
|
||||
@ -570,14 +575,16 @@ public final class PendingStarsReactionsMessageAttribute: MessageAttribute {
|
||||
return peerIds
|
||||
}
|
||||
|
||||
public init(accountPeerId: PeerId?, count: Int32) {
|
||||
public init(accountPeerId: PeerId?, count: Int32, isAnonymous: Bool) {
|
||||
self.accountPeerId = accountPeerId
|
||||
self.count = count
|
||||
self.isAnonymous = isAnonymous
|
||||
}
|
||||
|
||||
required public init(decoder: PostboxDecoder) {
|
||||
self.accountPeerId = decoder.decodeOptionalInt64ForKey("ap").flatMap(PeerId.init)
|
||||
self.count = decoder.decodeInt32ForKey("cnt", orElse: 1)
|
||||
self.isAnonymous = decoder.decodeBoolForKey("anon", orElse: false)
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
@ -587,5 +594,6 @@ public final class PendingStarsReactionsMessageAttribute: MessageAttribute {
|
||||
encoder.encodeNil(forKey: "ap")
|
||||
}
|
||||
encoder.encodeInt32(self.count, forKey: "cnt")
|
||||
encoder.encodeBool(self.isAnonymous, forKey: "anon")
|
||||
}
|
||||
}
|
||||
|
@ -334,13 +334,17 @@ public extension TelegramEngine {
|
||||
).startStandalone()
|
||||
}
|
||||
|
||||
public func sendStarsReaction(id: EngineMessage.Id, count: Int) {
|
||||
let _ = sendStarsReactionsInteractively(account: self.account, messageId: id, count: count).startStandalone()
|
||||
public func sendStarsReaction(id: EngineMessage.Id, count: Int, isAnonymous: Bool) {
|
||||
let _ = sendStarsReactionsInteractively(account: self.account, messageId: id, count: count, isAnonymous: isAnonymous).startStandalone()
|
||||
}
|
||||
|
||||
public func cancelPendingSendStarsReaction(id: EngineMessage.Id) {
|
||||
let _ = cancelPendingSendStarsReactionInteractively(account: self.account, messageId: id).startStandalone()
|
||||
}
|
||||
|
||||
public func updateStarsReactionIsAnonymous(id: EngineMessage.Id, isAnonymous: Bool) -> Signal<Never, NoError> {
|
||||
return _internal_updateStarsReactionIsAnonymous(account: self.account, messageId: id, isAnonymous: isAnonymous)
|
||||
}
|
||||
|
||||
public func requestChatContextResults(botId: PeerId, peerId: PeerId, query: String, location: Signal<(Double, Double)?, NoError> = .single(nil), offset: String, incompleteResults: Bool = false, staleCachedResults: Bool = false) -> Signal<RequestChatContextResultsResult?, RequestChatContextResultsError> {
|
||||
return _internal_requestChatContextResults(account: self.account, botId: botId, peerId: peerId, query: query, location: location, offset: offset, incompleteResults: incompleteResults, staleCachedResults: staleCachedResults)
|
||||
|
@ -1550,7 +1550,6 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
}
|
||||
}
|
||||
|
||||
//TODO:release
|
||||
if let channel = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info, firstMessage.author?.id != channel.id {
|
||||
if info.flags.contains(.messagesShouldHaveProfiles) {
|
||||
var allowAuthor = incoming
|
||||
|
@ -127,7 +127,6 @@ public func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Mess
|
||||
if let author = message.author as? TelegramUser {
|
||||
if let peer = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
||||
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info, message.author?.id != channel.id, info.flags.contains(.messagesShouldHaveProfiles) {
|
||||
//TODO:release
|
||||
} else {
|
||||
authorTitle = EnginePeer(author).displayTitle(strings: strings, displayOrder: nameDisplayOrder)
|
||||
}
|
||||
@ -137,7 +136,6 @@ public func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Mess
|
||||
} else {
|
||||
if let peer = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
||||
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info, message.author?.id != channel.id, info.flags.contains(.messagesShouldHaveProfiles) {
|
||||
//TODO:release
|
||||
} else {
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? AuthorSignatureMessageAttribute {
|
||||
|
@ -345,7 +345,6 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
||||
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 {
|
||||
//TODO:release
|
||||
if info.flags.contains(.messagesShouldHaveProfiles) {
|
||||
hasAvatar = true
|
||||
effectiveAuthor = message.author
|
||||
|
@ -754,7 +754,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
}
|
||||
strongSelf.textAccessibilityOverlayNode.frame = textFrame
|
||||
//TODO:localize
|
||||
//TODO:release
|
||||
//strongSelf.textAccessibilityOverlayNode.cachedLayout = textLayout
|
||||
|
||||
strongSelf.updateIsTranslating(isTranslating)
|
||||
|
@ -31,6 +31,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath",
|
||||
"//submodules/AvatarNode",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/CheckNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -18,6 +18,7 @@ import SliderComponent
|
||||
import RoundedRectWithTailPath
|
||||
import AvatarNode
|
||||
import BundleIconComponent
|
||||
import CheckNode
|
||||
|
||||
private final class BalanceComponent: CombinedComponent {
|
||||
let context: AccountContext
|
||||
@ -61,10 +62,9 @@ private final class BalanceComponent: CombinedComponent {
|
||||
return { context in
|
||||
var size = CGSize(width: 0.0, height: 0.0)
|
||||
|
||||
//TODO:localize
|
||||
let title = title.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: "Balance", font: Font.regular(14.0), textColor: context.component.theme.list.itemPrimaryTextColor))
|
||||
text: .plain(NSAttributedString(string: context.component.strings.SendStarReactions_Balance, font: Font.regular(14.0), textColor: context.component.theme.list.itemPrimaryTextColor))
|
||||
),
|
||||
availableSize: context.availableSize,
|
||||
transition: .immediate
|
||||
@ -536,9 +536,9 @@ private final class PeerComponent: Component {
|
||||
let avatarFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: avatarSize)
|
||||
avatarNode.frame = avatarFrame
|
||||
if let peer = component.peer {
|
||||
avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer)
|
||||
avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, synchronousLoad: true)
|
||||
} else {
|
||||
avatarNode.setPeer(context: component.context, theme: component.theme, peer: nil, overrideImage: .anonymousSavedMessagesIcon)
|
||||
avatarNode.setPeer(context: component.context, theme: component.theme, peer: nil, overrideImage: .anonymousSavedMessagesIcon(isColored: false), synchronousLoad: true)
|
||||
}
|
||||
avatarNode.updateSize(size: avatarFrame.size)
|
||||
|
||||
@ -565,8 +565,7 @@ private final class PeerComponent: Component {
|
||||
if let peer = component.peer {
|
||||
peerTitle = peer.compactDisplayTitle
|
||||
} else {
|
||||
//TODO:localize
|
||||
peerTitle = "Anonymous"
|
||||
peerTitle = component.strings.SendStarReactions_UserLabelAnonymous
|
||||
}
|
||||
|
||||
let titleSize = self.title.update(
|
||||
@ -600,15 +599,18 @@ private final class PeerComponent: Component {
|
||||
|
||||
private final class SliderBackgroundComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let value: CGFloat
|
||||
let topCutoff: CGFloat?
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
value: CGFloat,
|
||||
topCutoff: CGFloat?
|
||||
) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.value = value
|
||||
self.topCutoff = topCutoff
|
||||
}
|
||||
@ -617,6 +619,9 @@ private final class SliderBackgroundComponent: Component {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.value != rhs.value {
|
||||
return false
|
||||
}
|
||||
@ -626,6 +631,37 @@ private final class SliderBackgroundComponent: Component {
|
||||
return true
|
||||
}
|
||||
|
||||
private enum TopTextOverflowState {
|
||||
case left
|
||||
case center
|
||||
case right
|
||||
|
||||
func animates(from: TopTextOverflowState) -> Bool {
|
||||
switch self {
|
||||
case .left:
|
||||
return false
|
||||
case .center:
|
||||
switch from {
|
||||
case .left:
|
||||
return false
|
||||
case .center:
|
||||
return false
|
||||
case .right:
|
||||
return true
|
||||
}
|
||||
case .right:
|
||||
switch from {
|
||||
case .left:
|
||||
return false
|
||||
case .center:
|
||||
return true
|
||||
case .right:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let sliderBackground = UIView()
|
||||
private let sliderForeground = UIView()
|
||||
@ -636,6 +672,8 @@ private final class SliderBackgroundComponent: Component {
|
||||
private let topForegroundText = ComponentView<Empty>()
|
||||
private let topBackgroundText = ComponentView<Empty>()
|
||||
|
||||
private var topTextOverflowState: TopTextOverflowState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
@ -656,7 +694,7 @@ private final class SliderBackgroundComponent: Component {
|
||||
}
|
||||
|
||||
func update(component: SliderBackgroundComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.sliderBackground.backgroundColor = UIColor(rgb: 0xEEEEEF)
|
||||
self.sliderBackground.backgroundColor = component.theme.list.itemPrimaryTextColor.withMultipliedAlpha(component.theme.overallDarkAppearance ? 0.2 : 0.07)
|
||||
self.sliderForeground.backgroundColor = UIColor(rgb: 0xFFB10D)
|
||||
self.topForegroundLine.backgroundColor = component.theme.list.plainBackgroundColor.cgColor
|
||||
self.topBackgroundLine.backgroundColor = component.theme.list.plainBackgroundColor.cgColor
|
||||
@ -678,15 +716,37 @@ private final class SliderBackgroundComponent: Component {
|
||||
|
||||
let topCutoff = component.topCutoff ?? 0.0
|
||||
|
||||
let topLineFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(sliderAreaWidth * topCutoff), y: 0.0), size: CGSize(width: 1.0, height: availableSize.height))
|
||||
transition.setFrame(layer: self.topForegroundLine, frame: topLineFrame)
|
||||
transition.setFrame(layer: self.topBackgroundLine, frame: topLineFrame)
|
||||
let topX = floorToScreenPixels(sliderAreaWidth * topCutoff)
|
||||
let topLineAvoidDistance = 6.0
|
||||
let knobWidth: CGFloat = 30.0
|
||||
let topLineClosestEdge = min(abs(sliderForegroundFrame.maxX - topX), abs(sliderForegroundFrame.maxX - knobWidth - topX))
|
||||
var topLineOverlayFactor = topLineClosestEdge / topLineAvoidDistance
|
||||
topLineOverlayFactor = max(0.0, min(1.0, topLineOverlayFactor))
|
||||
if sliderForegroundFrame.maxX - knobWidth <= topX && sliderForegroundFrame.maxX >= topX {
|
||||
topLineOverlayFactor = 0.0
|
||||
}
|
||||
|
||||
let topLineHeight: CGFloat = availableSize.height
|
||||
let topLineAlpha: CGFloat = topLineOverlayFactor * topLineOverlayFactor
|
||||
|
||||
let topLineFrameTransition = transition
|
||||
let topLineAlphaTransition = transition
|
||||
/*if transition.userData(ChatSendStarsScreenComponent.IsAdjustingAmountHint.self) != nil {
|
||||
topLineFrameTransition = .easeInOut(duration: 0.12)
|
||||
topLineAlphaTransition = .easeInOut(duration: 0.12)
|
||||
}*/
|
||||
|
||||
let topLineFrame = CGRect(origin: CGPoint(x: topX, y: (availableSize.height - topLineHeight) * 0.5), size: CGSize(width: 1.0, height: topLineHeight))
|
||||
|
||||
topLineFrameTransition.setFrame(layer: self.topForegroundLine, frame: topLineFrame)
|
||||
topLineAlphaTransition.setAlpha(layer: self.topForegroundLine, alpha: topLineAlpha)
|
||||
topLineFrameTransition.setFrame(layer: self.topBackgroundLine, frame: topLineFrame)
|
||||
topLineAlphaTransition.setAlpha(layer: self.topBackgroundLine, alpha: topLineAlpha)
|
||||
|
||||
//TODO:localize
|
||||
let topTextSize = self.topForegroundText.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: "TOP", font: Font.medium(17.0), textColor: UIColor(white: 1.0, alpha: 0.4)))
|
||||
text: .plain(NSAttributedString(string: component.strings.SendStarReactions_SliderTop, font: Font.semibold(15.0), textColor: UIColor(white: 1.0, alpha: 0.4)))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: 100.0)
|
||||
@ -694,12 +754,30 @@ private final class SliderBackgroundComponent: Component {
|
||||
let _ = self.topBackgroundText.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: "TOP", font: Font.medium(17.0), textColor: UIColor(white: 0.0, alpha: 0.1)))
|
||||
text: .plain(NSAttributedString(string: component.strings.SendStarReactions_SliderTop, font: Font.semibold(15.0), textColor: component.theme.overallDarkAppearance ? UIColor(white: 1.0, alpha: 0.22) : UIColor(white: 0.0, alpha: 0.2)))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: 100.0)
|
||||
)
|
||||
let topTextFrame = CGRect(origin: CGPoint(x: topLineFrame.maxX + 6.0, y: floor((availableSize.height - topTextSize.height) * 0.5)), size: topTextSize)
|
||||
|
||||
var topTextFrame = CGRect(origin: CGPoint(x: topLineFrame.maxX + 6.0, y: floor((availableSize.height - topTextSize.height) * 0.5)), size: topTextSize)
|
||||
|
||||
let topTextFrameTransition = transition
|
||||
|
||||
let topTextLeftInset: CGFloat = 4.0
|
||||
var topTextOverflowWidth: CGFloat = 0.0
|
||||
let topTextOverflowState: TopTextOverflowState
|
||||
if sliderForegroundFrame.maxX < topTextFrame.minX - topTextLeftInset {
|
||||
topTextOverflowState = .left
|
||||
} else if sliderForegroundFrame.maxX >= topTextFrame.minX - topTextLeftInset && sliderForegroundFrame.maxX - knobWidth < topTextFrame.maxX + topTextLeftInset {
|
||||
topTextOverflowWidth = sliderForegroundFrame.maxX - (topTextFrame.minX - topTextLeftInset)
|
||||
topTextOverflowState = .center
|
||||
} else {
|
||||
topTextOverflowState = .right
|
||||
}
|
||||
|
||||
topTextFrame.origin.x += topTextOverflowWidth
|
||||
|
||||
if let topForegroundTextView = self.topForegroundText.view, let topBackgroundTextView = self.topBackgroundText.view {
|
||||
if topForegroundTextView.superview == nil {
|
||||
topBackgroundTextView.layer.anchorPoint = CGPoint()
|
||||
@ -709,17 +787,30 @@ private final class SliderBackgroundComponent: Component {
|
||||
self.sliderForeground.addSubview(topForegroundTextView)
|
||||
}
|
||||
|
||||
transition.setPosition(view: topForegroundTextView, position: topTextFrame.origin)
|
||||
transition.setPosition(view: topBackgroundTextView, position: topTextFrame.origin)
|
||||
var animateTopTextAdditionalX: CGFloat = 0.0
|
||||
if transition.userData(ChatSendStarsScreenComponent.IsAdjustingAmountHint.self) != nil {
|
||||
if let previousState = self.topTextOverflowState, previousState != topTextOverflowState, topTextOverflowState.animates(from: previousState) {
|
||||
animateTopTextAdditionalX = topForegroundTextView.center.x - topTextFrame.origin.x
|
||||
}
|
||||
}
|
||||
|
||||
topTextFrameTransition.setPosition(view: topForegroundTextView, position: topTextFrame.origin)
|
||||
topTextFrameTransition.setPosition(view: topBackgroundTextView, position: topTextFrame.origin)
|
||||
|
||||
topForegroundTextView.bounds = CGRect(origin: CGPoint(), size: topTextFrame.size)
|
||||
topBackgroundTextView.bounds = CGRect(origin: CGPoint(), size: topTextFrame.size)
|
||||
|
||||
topForegroundTextView.isHidden = component.topCutoff == nil || topTextFrame.minX <= 10.0 || topTextFrame.maxX >= availableSize.width - 4.0
|
||||
if animateTopTextAdditionalX != 0.0 {
|
||||
topForegroundTextView.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)
|
||||
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
|
||||
topBackgroundTextView.isHidden = topForegroundTextView.isHidden
|
||||
self.topBackgroundLine.isHidden = topForegroundTextView.isHidden
|
||||
self.topForegroundLine.isHidden = topForegroundTextView.isHidden
|
||||
self.topBackgroundLine.isHidden = topX < 10.0
|
||||
self.topForegroundLine.isHidden = self.topBackgroundLine.isHidden
|
||||
}
|
||||
self.topTextOverflowState = topTextOverflowState
|
||||
|
||||
return availableSize
|
||||
}
|
||||
@ -735,31 +826,43 @@ private final class SliderBackgroundComponent: Component {
|
||||
}
|
||||
|
||||
private final class ChatSendStarsScreenComponent: Component {
|
||||
final class IsAdjustingAmountHint {
|
||||
}
|
||||
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let peer: EnginePeer
|
||||
let myPeer: EnginePeer
|
||||
let messageId: EngineMessage.Id
|
||||
let maxAmount: Int
|
||||
let balance: Int64?
|
||||
let currentSentAmount: Int?
|
||||
let topPeers: [ChatSendStarsScreen.TopPeer]
|
||||
let completion: (Int64, Bool, ChatSendStarsScreen.TransitionOut) -> Void
|
||||
let myTopPeer: ChatSendStarsScreen.TopPeer?
|
||||
let completion: (Int64, Bool, Bool, ChatSendStarsScreen.TransitionOut) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
peer: EnginePeer,
|
||||
myPeer: EnginePeer,
|
||||
messageId: EngineMessage.Id,
|
||||
maxAmount: Int,
|
||||
balance: Int64?,
|
||||
currentSentAmount: Int?,
|
||||
topPeers: [ChatSendStarsScreen.TopPeer],
|
||||
completion: @escaping (Int64, Bool, ChatSendStarsScreen.TransitionOut) -> Void
|
||||
myTopPeer: ChatSendStarsScreen.TopPeer?,
|
||||
completion: @escaping (Int64, Bool, Bool, ChatSendStarsScreen.TransitionOut) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.peer = peer
|
||||
self.myPeer = myPeer
|
||||
self.messageId = messageId
|
||||
self.maxAmount = maxAmount
|
||||
self.balance = balance
|
||||
self.currentSentAmount = currentSentAmount
|
||||
self.topPeers = topPeers
|
||||
self.myTopPeer = myTopPeer
|
||||
self.completion = completion
|
||||
}
|
||||
|
||||
@ -770,6 +873,9 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
if lhs.peer != rhs.peer {
|
||||
return false
|
||||
}
|
||||
if lhs.myPeer != rhs.myPeer {
|
||||
return false
|
||||
}
|
||||
if lhs.maxAmount != rhs.maxAmount {
|
||||
return false
|
||||
}
|
||||
@ -782,6 +888,9 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
if lhs.topPeers != rhs.topPeers {
|
||||
return false
|
||||
}
|
||||
if lhs.myTopPeer != rhs.myTopPeer {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -829,6 +938,9 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
private var topPeersTitleBackground: SimpleLayer?
|
||||
private var topPeersTitle: ComponentView<Empty>?
|
||||
|
||||
private var anonymousSeparator = SimpleLayer()
|
||||
private var anonymousContents = ComponentView<Empty>()
|
||||
|
||||
private var topPeerItems: [ChatSendStarsScreen.TopPeer.Id: ComponentView<Empty>] = [:]
|
||||
|
||||
private let actionButton = ComponentView<Empty>()
|
||||
@ -846,6 +958,7 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
private var topOffsetDistance: CGFloat?
|
||||
|
||||
private var amount: Int64 = 1
|
||||
private var isAnonymous: Bool = false
|
||||
private var cachedStarImage: (UIImage, PresentationTheme)?
|
||||
private var cachedCloseImage: UIImage?
|
||||
|
||||
@ -1014,6 +1127,9 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
|
||||
if self.component == nil {
|
||||
self.amount = 50
|
||||
if let myTopPeer = component.myTopPeer {
|
||||
self.isAnonymous = myTopPeer.isAnonymous
|
||||
}
|
||||
}
|
||||
|
||||
self.component = component
|
||||
@ -1054,7 +1170,7 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
return
|
||||
}
|
||||
self.amount = 1 + Int64(value)
|
||||
self.state?.updated(transition: .immediate)
|
||||
self.state?.updated(transition: ComponentTransition(animation: .none).withUserData(IsAdjustingAmountHint()))
|
||||
|
||||
let sliderValue = Float(value) / Float(component.maxAmount)
|
||||
let currentTimestamp = CACurrentMediaTime()
|
||||
@ -1112,7 +1228,18 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
|
||||
let progressFraction: CGFloat = CGFloat(self.amount) / CGFloat(component.maxAmount - 1)
|
||||
|
||||
let topCount = component.topPeers.max(by: { $0.count < $1.count })?.count
|
||||
let topOthersCount: Int? = component.topPeers.filter({ !$0.isMy }).max(by: { $0.count < $1.count })?.count
|
||||
var topCount: Int?
|
||||
if let topOthersCount {
|
||||
if let myTopPeer = component.myTopPeer {
|
||||
topCount = max(0, topOthersCount - myTopPeer.count + 1)
|
||||
} else {
|
||||
topCount = topOthersCount
|
||||
}
|
||||
if topCount == 0 {
|
||||
topCount = nil
|
||||
}
|
||||
}
|
||||
|
||||
var topCutoffFraction: CGFloat?
|
||||
if let topCount {
|
||||
@ -1132,6 +1259,7 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
transition: transition,
|
||||
component: AnyComponent(SliderBackgroundComponent(
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
value: progressFraction,
|
||||
topCutoff: topCutoffFraction
|
||||
)),
|
||||
@ -1264,7 +1392,7 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
let titleSize = title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: "React with Stars", font: Font.semibold(17.0), textColor: environment.theme.list.itemPrimaryTextColor))
|
||||
text: .plain(NSAttributedString(string: environment.strings.SendStarReactions_Title, font: Font.semibold(17.0), textColor: environment.theme.list.itemPrimaryTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - leftButtonFrame.maxX * 2.0, height: 100.0)
|
||||
@ -1282,9 +1410,9 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
|
||||
let text: String
|
||||
if let currentSentAmount = component.currentSentAmount {
|
||||
text = "You sent **\(currentSentAmount)** stars to support this post."
|
||||
text = environment.strings.SendStarReactions_TextSentStars(Int32(currentSentAmount))
|
||||
} else {
|
||||
text = "Choose how many stars you want to send to **\(component.peer.debugDisplayTitle)** to support this post."
|
||||
text = environment.strings.SendStarReactions_TextGeneric(component.peer.debugDisplayTitle).string
|
||||
}
|
||||
|
||||
let body = MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor)
|
||||
@ -1358,11 +1486,10 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
topPeersLeftSeparator.backgroundColor = environment.theme.list.itemPlainSeparatorColor.cgColor
|
||||
topPeersRightSeparator.backgroundColor = environment.theme.list.itemPlainSeparatorColor.cgColor
|
||||
|
||||
//TODO:localize
|
||||
let topPeersTitleSize = topPeersTitle.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: "Top Senders", font: Font.semibold(15.0), textColor: .white))
|
||||
text: .plain(NSAttributedString(string: environment.strings.SendStarReactions_SectionTop, font: Font.semibold(15.0), textColor: .white))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 300.0, height: 100.0)
|
||||
@ -1387,9 +1514,36 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
transition.setFrame(layer: topPeersLeftSeparator, frame: CGRect(origin: CGPoint(x: sideInset, y: separatorY), size: CGSize(width: max(0.0, topPeersBackgroundFrame.minX - separatorSpacing - sideInset), height: UIScreenPixel)))
|
||||
transition.setFrame(layer: topPeersRightSeparator, frame: CGRect(origin: CGPoint(x: topPeersBackgroundFrame.maxX + separatorSpacing, y: separatorY), size: CGSize(width: max(0.0, availableSize.width - sideInset - (topPeersBackgroundFrame.maxX + separatorSpacing)), height: UIScreenPixel)))
|
||||
|
||||
var mappedTopPeers = component.topPeers
|
||||
if let index = mappedTopPeers.firstIndex(where: { $0.isMy }) {
|
||||
mappedTopPeers.remove(at: index)
|
||||
}
|
||||
var myCount = Int(self.amount)
|
||||
if let myTopPeer = component.myTopPeer {
|
||||
myCount += myTopPeer.count
|
||||
}
|
||||
mappedTopPeers.append(ChatSendStarsScreen.TopPeer(
|
||||
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))
|
||||
}
|
||||
|
||||
var animateItems = false
|
||||
var itemPositionTransition = transition
|
||||
var itemAlphaTransition = transition
|
||||
if transition.userData(IsAdjustingAmountHint.self) != nil {
|
||||
animateItems = true
|
||||
itemPositionTransition = .spring(duration: 0.3)
|
||||
itemAlphaTransition = .easeInOut(duration: 0.15)
|
||||
}
|
||||
|
||||
var validIds: [ChatSendStarsScreen.TopPeer.Id] = []
|
||||
var items: [(itemView: ComponentView<Empty>, size: CGSize)] = []
|
||||
for topPeer in component.topPeers {
|
||||
for topPeer in mappedTopPeers {
|
||||
validIds.append(topPeer.id)
|
||||
|
||||
let itemView: ComponentView<Empty>
|
||||
@ -1439,7 +1593,17 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
for (id, itemView) in self.topPeerItems {
|
||||
if !validIds.contains(id) {
|
||||
removedIds.append(id)
|
||||
itemView.view?.removeFromSuperview()
|
||||
|
||||
if animateItems {
|
||||
if let itemComponentView = itemView.view {
|
||||
itemPositionTransition.setScale(view: itemComponentView, scale: 0.001)
|
||||
itemAlphaTransition.setAlpha(view: itemComponentView, alpha: 0.0, completion: { [weak itemComponentView] _ in
|
||||
itemComponentView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
itemView.view?.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
for id in removedIds {
|
||||
@ -1459,10 +1623,26 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
var itemX: CGFloat = floor((availableSize.width - totalWidth) * 0.5) + itemSpacing
|
||||
for (itemView, itemSize) in items {
|
||||
if let itemComponentView = itemView.view {
|
||||
var animateItem = animateItems
|
||||
if itemComponentView.superview == nil {
|
||||
self.scrollContentView.addSubview(itemComponentView)
|
||||
animateItem = false
|
||||
ComponentTransition.immediate.setScale(view: itemComponentView, scale: 0.001)
|
||||
itemComponentView.alpha = 0.0
|
||||
}
|
||||
itemComponentView.frame = CGRect(origin: CGPoint(x: itemX, y: contentHeight + 56.0), size: itemSize)
|
||||
|
||||
let itemFrame = CGRect(origin: CGPoint(x: itemX, y: contentHeight + 56.0), size: itemSize)
|
||||
|
||||
if animateItem {
|
||||
itemPositionTransition.setPosition(view: itemComponentView, position: itemFrame.center)
|
||||
itemPositionTransition.setBounds(view: itemComponentView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
|
||||
} else {
|
||||
itemComponentView.center = itemFrame.center
|
||||
itemComponentView.bounds = CGRect(origin: CGPoint(), size: itemFrame.size)
|
||||
}
|
||||
|
||||
itemPositionTransition.setScale(view: itemComponentView, scale: 1.0)
|
||||
itemAlphaTransition.setAlpha(view: itemComponentView, alpha: 1.0)
|
||||
}
|
||||
itemX += itemSize.width + itemSpacing
|
||||
}
|
||||
@ -1470,13 +1650,80 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
contentHeight += 161.0
|
||||
}
|
||||
|
||||
do {
|
||||
if !component.topPeers.isEmpty {
|
||||
contentHeight += 2.0
|
||||
}
|
||||
|
||||
if self.anonymousSeparator.superlayer == nil {
|
||||
self.scrollContentView.layer.addSublayer(self.anonymousSeparator)
|
||||
}
|
||||
|
||||
self.anonymousSeparator.backgroundColor = environment.theme.list.itemPlainSeparatorColor.cgColor
|
||||
|
||||
let checkTheme = CheckComponent.Theme(
|
||||
backgroundColor: environment.theme.list.itemCheckColors.fillColor,
|
||||
strokeColor: environment.theme.list.itemCheckColors.foregroundColor,
|
||||
borderColor: environment.theme.list.itemCheckColors.strokeColor,
|
||||
overlayBorder: false,
|
||||
hasInset: false,
|
||||
hasShadow: false
|
||||
)
|
||||
let anonymousContentsSize = self.anonymousContents.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(PlainButtonComponent(
|
||||
content: AnyComponent(HStack([
|
||||
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(CheckComponent(
|
||||
theme: checkTheme,
|
||||
selected: !self.isAnonymous
|
||||
))),
|
||||
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: environment.strings.SendStarReactions_ShowMyselfInTop, font: Font.regular(16.0), textColor: environment.theme.list.itemPrimaryTextColor))
|
||||
)))
|
||||
],
|
||||
spacing: 10.0
|
||||
)),
|
||||
effectAlignment: .center,
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
self.isAnonymous = !self.isAnonymous
|
||||
self.state?.updated(transition: .easeInOut(duration: 0.2))
|
||||
|
||||
if component.myTopPeer != nil {
|
||||
let _ = component.context.engine.messages.updateStarsReactionIsAnonymous(id: component.messageId, isAnonymous: self.isAnonymous).startStandalone()
|
||||
}
|
||||
},
|
||||
animateAlpha: false,
|
||||
animateScale: false
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)
|
||||
)
|
||||
|
||||
transition.setFrame(layer: self.anonymousSeparator, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: CGSize(width: availableSize.width - sideInset * 2.0, height: UIScreenPixel)))
|
||||
|
||||
contentHeight += 21.0
|
||||
|
||||
let anonymousContentsFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - anonymousContentsSize.width) * 0.5), y: contentHeight), size: anonymousContentsSize)
|
||||
if let anonymousContentsView = self.anonymousContents.view {
|
||||
if anonymousContentsView.superview == nil {
|
||||
self.scrollContentView.addSubview(anonymousContentsView)
|
||||
}
|
||||
transition.setFrame(view: anonymousContentsView, frame: anonymousContentsFrame)
|
||||
}
|
||||
|
||||
contentHeight += anonymousContentsSize.height + 27.0
|
||||
}
|
||||
|
||||
initialContentHeight = contentHeight
|
||||
|
||||
if self.cachedStarImage == nil || self.cachedStarImage?.1 !== environment.theme {
|
||||
self.cachedStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: .white)!, environment.theme)
|
||||
}
|
||||
|
||||
let buttonString = "Send # \(self.amount)"
|
||||
let buttonString = environment.strings.SendStarReactions_SendButtonTitle("\(self.amount)").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))
|
||||
@ -1541,6 +1788,7 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
|
||||
component.completion(
|
||||
self.amount,
|
||||
self.isAnonymous,
|
||||
isBecomingTop,
|
||||
ChatSendStarsScreen.TransitionOut(
|
||||
sourceView: badgeView.badgeIcon
|
||||
@ -1556,7 +1804,7 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
let buttonDescriptionTextSize = self.buttonDescriptionText.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .markdown(text: "By sending Stars you agree to the [Terms of Service]()", attributes: MarkdownAttributes(
|
||||
text: .markdown(text: environment.strings.SendStarReactions_TermsOfServiceFooter, attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemSecondaryTextColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.itemSecondaryTextColor),
|
||||
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemAccentColor),
|
||||
@ -1639,41 +1887,60 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
public final class InitialData {
|
||||
fileprivate let peer: EnginePeer
|
||||
fileprivate let myPeer: EnginePeer
|
||||
fileprivate let messageId: EngineMessage.Id
|
||||
fileprivate let balance: Int64?
|
||||
fileprivate let currentSentAmount: Int?
|
||||
fileprivate let topPeers: [ChatSendStarsScreen.TopPeer]
|
||||
fileprivate let myTopPeer: ChatSendStarsScreen.TopPeer?
|
||||
|
||||
fileprivate init(
|
||||
peer: EnginePeer,
|
||||
myPeer: EnginePeer,
|
||||
messageId: EngineMessage.Id,
|
||||
balance: Int64?,
|
||||
currentSentAmount: Int?,
|
||||
topPeers: [ChatSendStarsScreen.TopPeer]
|
||||
topPeers: [ChatSendStarsScreen.TopPeer],
|
||||
myTopPeer: ChatSendStarsScreen.TopPeer?
|
||||
) {
|
||||
self.peer = peer
|
||||
self.myPeer = myPeer
|
||||
self.messageId = messageId
|
||||
self.balance = balance
|
||||
self.currentSentAmount = currentSentAmount
|
||||
self.topPeers = topPeers
|
||||
self.myTopPeer = myTopPeer
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate final class TopPeer: Equatable {
|
||||
struct Id: Hashable {
|
||||
var value: EnginePeer.Id?
|
||||
|
||||
init(_ value: EnginePeer.Id?) {
|
||||
self.value = value
|
||||
}
|
||||
enum Id: Hashable {
|
||||
case anonymous
|
||||
case my
|
||||
case peer(EnginePeer.Id)
|
||||
}
|
||||
|
||||
var id: Id {
|
||||
return Id(self.peer?.id)
|
||||
if self.isMy {
|
||||
return .my
|
||||
} else if let peer = self.peer {
|
||||
return .peer(peer.id)
|
||||
} else {
|
||||
return .anonymous
|
||||
}
|
||||
}
|
||||
|
||||
var isAnonymous: Bool {
|
||||
return self.peer == nil
|
||||
}
|
||||
|
||||
let peer: EnginePeer?
|
||||
let isMy: Bool
|
||||
let count: Int
|
||||
|
||||
init(peer: EnginePeer?, count: Int) {
|
||||
init(peer: EnginePeer?, isMy: Bool, count: Int) {
|
||||
self.peer = peer
|
||||
self.isMy = isMy
|
||||
self.count = count
|
||||
}
|
||||
|
||||
@ -1681,6 +1948,9 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
if lhs.peer != rhs.peer {
|
||||
return false
|
||||
}
|
||||
if lhs.isMy != rhs.isMy {
|
||||
return false
|
||||
}
|
||||
if lhs.count != rhs.count {
|
||||
return false
|
||||
}
|
||||
@ -1703,7 +1973,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
|
||||
private var presenceDisposable: Disposable?
|
||||
|
||||
public init(context: AccountContext, initialData: InitialData, completion: @escaping (Int64, Bool, TransitionOut) -> Void) {
|
||||
public init(context: AccountContext, initialData: InitialData, completion: @escaping (Int64, Bool, Bool, TransitionOut) -> Void) {
|
||||
self.context = context
|
||||
|
||||
var maxAmount = 2500
|
||||
@ -1714,10 +1984,13 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
super.init(context: context, component: ChatSendStarsScreenComponent(
|
||||
context: context,
|
||||
peer: initialData.peer,
|
||||
myPeer: initialData.myPeer,
|
||||
messageId: initialData.messageId,
|
||||
maxAmount: maxAmount,
|
||||
balance: initialData.balance,
|
||||
currentSentAmount: initialData.currentSentAmount,
|
||||
topPeers: initialData.topPeers,
|
||||
myTopPeer: initialData.myTopPeer,
|
||||
completion: completion
|
||||
), navigationBarAppearance: .none)
|
||||
|
||||
@ -1748,7 +2021,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
}
|
||||
}
|
||||
|
||||
public static func initialData(context: AccountContext, peerId: EnginePeer.Id, topPeers: [ReactionsMessageAttribute.TopPeer]) -> Signal<InitialData?, NoError> {
|
||||
public static func initialData(context: AccountContext, peerId: EnginePeer.Id, messageId: EngineMessage.Id, topPeers: [ReactionsMessageAttribute.TopPeer]) -> Signal<InitialData?, NoError> {
|
||||
let balance: Signal<Int64?, NoError>
|
||||
if let starsContext = context.starsContext {
|
||||
balance = starsContext.state
|
||||
@ -1761,10 +2034,14 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
}
|
||||
|
||||
var currentSentAmount: Int?
|
||||
var myTopPeer: ReactionsMessageAttribute.TopPeer?
|
||||
if let myPeer = topPeers.first(where: { $0.isMy }) {
|
||||
myTopPeer = myPeer
|
||||
currentSentAmount = Int(myPeer.count)
|
||||
}
|
||||
|
||||
let allPeerIds = topPeers.compactMap(\.peerId)
|
||||
|
||||
var topPeers = topPeers.sorted(by: { $0.count > $1.count })
|
||||
if topPeers.count > 3 {
|
||||
topPeers = Array(topPeers.prefix(3))
|
||||
@ -1773,26 +2050,28 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
return combineLatest(
|
||||
context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId),
|
||||
EngineDataMap(topPeers.map(\.peerId).compactMap {
|
||||
$0.flatMap(TelegramEngine.EngineData.Item.Peer.Peer.init(id:))
|
||||
})
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId),
|
||||
EngineDataMap(allPeerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
|
||||
),
|
||||
balance
|
||||
)
|
||||
|> map { peerAndTopPeerMap, balance -> InitialData? in
|
||||
let (peer, topPeerMap) = peerAndTopPeerMap
|
||||
guard let peer else {
|
||||
let (peer, myPeer, topPeerMap) = peerAndTopPeerMap
|
||||
guard let peer, let myPeer else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return InitialData(
|
||||
peer: peer,
|
||||
myPeer: myPeer,
|
||||
messageId: messageId,
|
||||
balance: balance,
|
||||
currentSentAmount: currentSentAmount,
|
||||
topPeers: topPeers.compactMap { topPeer -> ChatSendStarsScreen.TopPeer? in
|
||||
guard let topPeerId = topPeer.peerId else {
|
||||
return ChatSendStarsScreen.TopPeer(
|
||||
peer: nil,
|
||||
isMy: topPeer.isMy,
|
||||
count: Int(topPeer.count)
|
||||
)
|
||||
}
|
||||
@ -1803,7 +2082,28 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
return nil
|
||||
}
|
||||
return ChatSendStarsScreen.TopPeer(
|
||||
peer: topPeerValue,
|
||||
peer: topPeer.isAnonymous ? nil : topPeerValue,
|
||||
isMy: topPeer.isMy,
|
||||
count: Int(topPeer.count)
|
||||
)
|
||||
},
|
||||
myTopPeer: myTopPeer.flatMap { topPeer -> ChatSendStarsScreen.TopPeer? in
|
||||
guard let topPeerId = topPeer.peerId else {
|
||||
return ChatSendStarsScreen.TopPeer(
|
||||
peer: nil,
|
||||
isMy: topPeer.isMy,
|
||||
count: Int(topPeer.count)
|
||||
)
|
||||
}
|
||||
guard let topPeerValue = topPeerMap[topPeerId] else {
|
||||
return nil
|
||||
}
|
||||
guard let topPeerValue else {
|
||||
return nil
|
||||
}
|
||||
return ChatSendStarsScreen.TopPeer(
|
||||
peer: topPeer.isAnonymous ? nil : topPeerValue,
|
||||
isMy: topPeer.isMy,
|
||||
count: Int(topPeer.count)
|
||||
)
|
||||
}
|
||||
@ -2042,3 +2342,97 @@ private final class SliderStarsView: UIView {
|
||||
self.emitterLayer.emitterSize = size
|
||||
}
|
||||
}
|
||||
|
||||
private final class CheckComponent: Component {
|
||||
struct Theme: Equatable {
|
||||
public let backgroundColor: UIColor
|
||||
public let strokeColor: UIColor
|
||||
public let borderColor: UIColor
|
||||
public let overlayBorder: Bool
|
||||
public let hasInset: Bool
|
||||
public let hasShadow: Bool
|
||||
public let filledBorder: Bool
|
||||
public let borderWidth: CGFloat?
|
||||
|
||||
public init(backgroundColor: UIColor, strokeColor: UIColor, borderColor: UIColor, overlayBorder: Bool, hasInset: Bool, hasShadow: Bool, filledBorder: Bool = false, borderWidth: CGFloat? = nil) {
|
||||
self.backgroundColor = backgroundColor
|
||||
self.strokeColor = strokeColor
|
||||
self.borderColor = borderColor
|
||||
self.overlayBorder = overlayBorder
|
||||
self.hasInset = hasInset
|
||||
self.hasShadow = hasShadow
|
||||
self.filledBorder = filledBorder
|
||||
self.borderWidth = borderWidth
|
||||
}
|
||||
|
||||
var checkNodeTheme: CheckNodeTheme {
|
||||
return CheckNodeTheme(
|
||||
backgroundColor: self.backgroundColor,
|
||||
strokeColor: self.strokeColor,
|
||||
borderColor: self.borderColor,
|
||||
overlayBorder: self.overlayBorder,
|
||||
hasInset: self.hasInset,
|
||||
hasShadow: self.hasShadow,
|
||||
filledBorder: self.filledBorder,
|
||||
borderWidth: self.borderWidth
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let theme: Theme
|
||||
let selected: Bool
|
||||
|
||||
init(
|
||||
theme: Theme,
|
||||
selected: Bool
|
||||
) {
|
||||
self.theme = theme
|
||||
self.selected = selected
|
||||
}
|
||||
|
||||
static func ==(lhs: CheckComponent, rhs: CheckComponent) -> Bool {
|
||||
if lhs.theme != rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.selected != rhs.selected {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private var currentValue: CGFloat?
|
||||
private var animator: DisplayLinkAnimator?
|
||||
|
||||
private var checkLayer: CheckLayer {
|
||||
return self.layer as! CheckLayer
|
||||
}
|
||||
|
||||
override class var layerClass: AnyClass {
|
||||
return CheckLayer.self
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(frame: CGRect())
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
func update(component: CheckComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
|
||||
self.checkLayer.setSelected(component.selected, animated: true)
|
||||
self.checkLayer.theme = component.theme.checkNodeTheme
|
||||
|
||||
return CGSize(width: 22.0, height: 22.0)
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View()
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, transition: transition)
|
||||
}
|
||||
}
|
||||
|
@ -907,8 +907,7 @@ final class PeerAllowedReactionsScreenComponent: Component {
|
||||
self.paidReactionsSection = paidReactionsSection
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let parsedString = parseMarkdownIntoAttributedString("Switch this on to let your subscribers set paid reactions with Telegram Stars, which you will be able to withdraw later as TON. [Learn More >](https://telegram.org/privacy)", attributes: MarkdownAttributes(
|
||||
let parsedString = parseMarkdownIntoAttributedString(environment.strings.PeerInfo_AllowedReactions_StarReactionsFooter, attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.freeTextColor),
|
||||
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemAccentColor),
|
||||
@ -925,7 +924,6 @@ final class PeerAllowedReactionsScreenComponent: Component {
|
||||
paidReactionsFooterText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: paidReactionsFooterText.string))
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let paidReactionsSectionSize = paidReactionsSection.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(ListSectionComponent(
|
||||
@ -953,7 +951,7 @@ final class PeerAllowedReactionsScreenComponent: Component {
|
||||
items: [
|
||||
AnyComponentWithIdentity(id: 0, component: AnyComponent(ListSwitchItemComponent(
|
||||
theme: environment.theme,
|
||||
title: "Enable Paid Reactions",
|
||||
title: environment.strings.PeerInfo_AllowedReactions_StarReactions,
|
||||
value: self.areStarsReactionsEnabled,
|
||||
valueUpdated: { [weak self] value in
|
||||
guard let self, let component = self.component else {
|
||||
|
@ -438,7 +438,7 @@ open class SpaceWarpNodeImpl: ASDisplayNode, SpaceWarpNode {
|
||||
|
||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let params = RippleParams(amplitude: 10.0, frequency: 15.0, decay: 8.0, speed: 1400.0)
|
||||
let params = RippleParams(amplitude: 10.0, frequency: 15.0, decay: 5.5, speed: 1400.0)
|
||||
|
||||
if let currentCloneView = self.currentCloneView {
|
||||
currentCloneView.removeFromSuperview()
|
||||
|
@ -1655,7 +1655,7 @@ private final class StoryContainerScreenComponent: Component {
|
||||
}
|
||||
|
||||
if case let .user(user) = slice.peer, user.botInfo != nil {
|
||||
//TODO:localize
|
||||
//TODO:release
|
||||
let _ = component.context.engine.messages.deleteBotPreviews(peerId: slice.peer.id, language: nil, media: [slice.item.storyItem.media._asMedia()]).startStandalone()
|
||||
} else {
|
||||
let _ = component.context.engine.messages.deleteStories(peerId: slice.peer.id, ids: [slice.item.storyItem.id]).startStandalone()
|
||||
|
@ -387,7 +387,7 @@ extension ChatControllerImpl {
|
||||
}
|
||||
}
|
||||
|
||||
self.context.engine.messages.sendStarsReaction(id: message.id, count: 1)
|
||||
self.context.engine.messages.sendStarsReaction(id: message.id, count: 1, isAnonymous: false)
|
||||
self.displayOrUpdateSendStarsUndo(messageId: message.id, count: 1)
|
||||
} else {
|
||||
let chosenReaction: MessageReaction.Reaction = chosenUpdatedReaction.reaction
|
||||
|
@ -1729,7 +1729,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.context.engine.messages.sendStarsReaction(id: message.id, count: 1)
|
||||
strongSelf.context.engine.messages.sendStarsReaction(id: message.id, count: 1, isAnonymous: false)
|
||||
strongSelf.displayOrUpdateSendStarsUndo(messageId: message.id, count: 1)
|
||||
})
|
||||
} else {
|
||||
@ -5092,7 +5092,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
} else if peer.id.isReplies {
|
||||
imageOverride = .repliesIcon
|
||||
} else if peer.id.isAnonymousSavedMessages {
|
||||
imageOverride = .anonymousSavedMessagesIcon
|
||||
imageOverride = .anonymousSavedMessagesIcon(isColored: true)
|
||||
} else if peer.isDeleted {
|
||||
imageOverride = .deletedIcon
|
||||
} else {
|
||||
@ -5888,7 +5888,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
} else if savedMessagesPeerId.isReplies {
|
||||
imageOverride = .repliesIcon
|
||||
} else if savedMessagesPeerId.isAnonymousSavedMessages {
|
||||
imageOverride = .anonymousSavedMessagesIcon
|
||||
imageOverride = .anonymousSavedMessagesIcon(isColored: true)
|
||||
} else if let peer = savedMessagesPeer?.peer, peer.isDeleted {
|
||||
imageOverride = .deletedIcon
|
||||
} else {
|
||||
|
@ -369,16 +369,14 @@ extension ChatControllerImpl {
|
||||
}
|
||||
|
||||
func openMessageSendStarsScreen(message: Message) {
|
||||
guard let reactionsAttribute = mergedMessageReactions(attributes: message.attributes, isTags: false) else {
|
||||
return
|
||||
}
|
||||
let _ = (ChatSendStarsScreen.initialData(context: self.context, peerId: message.id.peerId, topPeers: reactionsAttribute.topPeers)
|
||||
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
|
||||
}
|
||||
HapticFeedback().tap()
|
||||
self.push(ChatSendStarsScreen(context: self.context, initialData: initialData, completion: { [weak self] amount, isBecomingTop, transitionOut in
|
||||
self.push(ChatSendStarsScreen(context: self.context, initialData: initialData, completion: { [weak self] amount, isAnonymous, isBecomingTop, transitionOut in
|
||||
guard let self, amount > 0 else {
|
||||
return
|
||||
}
|
||||
@ -463,10 +461,7 @@ extension ChatControllerImpl {
|
||||
}
|
||||
}
|
||||
|
||||
#if !DEBUG
|
||||
let _ = self.context.engine.messages.sendStarsReaction(id: message.id, count: Int(amount))
|
||||
#endif
|
||||
|
||||
let _ = self.context.engine.messages.sendStarsReaction(id: message.id, count: Int(amount), isAnonymous: isAnonymous)
|
||||
self.displayOrUpdateSendStarsUndo(messageId: message.id, count: Int(amount))
|
||||
}))
|
||||
})
|
||||
@ -486,21 +481,14 @@ extension ChatControllerImpl {
|
||||
self.currentSendStarsUndoCount = count
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let title: String
|
||||
if self.currentSendStarsUndoCount == 1 {
|
||||
title = "Star sent!"
|
||||
} else {
|
||||
title = "Stars sent!"
|
||||
}
|
||||
let title: String = self.presentationData.strings.Chat_ToastStarsSent_Title(Int32(self.currentSendStarsUndoCount))
|
||||
|
||||
var textItems: [AnimatedTextComponent.Item] = []
|
||||
textItems.append(AnimatedTextComponent.Item(id: AnyHashable(0), isUnbreakable: true, content: .text("You have reacted with ")))
|
||||
textItems.append(AnimatedTextComponent.Item(id: AnyHashable(1), content: .number(self.currentSendStarsUndoCount, minDigits: 1)))
|
||||
textItems.append(AnimatedTextComponent.Item(id: AnyHashable(2), isUnbreakable: true, content: .text(self.currentSendStarsUndoCount == 1 ? " star." : " stars.")))
|
||||
let textItems = extractAnimatedTextString(string: self.presentationData.strings.Chat_ToastStarsSent_Text("", ""), id: "text", mapping: [
|
||||
0: .number(self.currentSendStarsUndoCount, minDigits: 1),
|
||||
1: .text(self.presentationData.strings.Chat_ToastStarsSent_TextStarAmount(Int32(self.currentSendStarsUndoCount)))
|
||||
])
|
||||
|
||||
self.currentSendStarsUndoMessageId = messageId
|
||||
//TODO:localize
|
||||
if let current = self.currentSendStarsUndoController {
|
||||
current.content = .starsSent(context: self.context, title: title, text: textItems)
|
||||
} else {
|
||||
@ -518,3 +506,31 @@ extension ChatControllerImpl {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func extractAnimatedTextString(string: PresentationStrings.FormattedString, id: String, mapping: [Int: AnimatedTextComponent.Item.Content]) -> [AnimatedTextComponent.Item] {
|
||||
var textItems: [AnimatedTextComponent.Item] = []
|
||||
|
||||
var previousIndex = 0
|
||||
let nsString = string.string as NSString
|
||||
for range in string.ranges.sorted(by: { $0.range.lowerBound < $1.range.lowerBound }) {
|
||||
if range.range.lowerBound > previousIndex {
|
||||
textItems.append(AnimatedTextComponent.Item(id: AnyHashable("\(id)_text_before_\(range.index)"), isUnbreakable: true, content: .text(nsString.substring(with: NSRange(location: previousIndex, length: range.range.lowerBound - previousIndex)))))
|
||||
}
|
||||
if let value = mapping[range.index] {
|
||||
let isUnbreakable: Bool
|
||||
switch value {
|
||||
case .text:
|
||||
isUnbreakable = true
|
||||
case .number:
|
||||
isUnbreakable = false
|
||||
}
|
||||
textItems.append(AnimatedTextComponent.Item(id: AnyHashable("\(id)_item_\(range.index)"), isUnbreakable: isUnbreakable, content: value))
|
||||
}
|
||||
previousIndex = range.range.upperBound
|
||||
}
|
||||
if nsString.length > previousIndex {
|
||||
textItems.append(AnimatedTextComponent.Item(id: AnyHashable("\(id)_text_end"), isUnbreakable: true, content: .text(nsString.substring(with: NSRange(location: previousIndex, length: nsString.length - previousIndex)))))
|
||||
}
|
||||
|
||||
return textItems
|
||||
}
|
||||
|
@ -223,7 +223,7 @@ private enum PollResultsEntry: ItemListNodeEntry {
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .solutionText(text, entities):
|
||||
let _ = entities
|
||||
//TODO:localize
|
||||
//TODO:release
|
||||
return ItemListMultilineTextItem(presentationData: presentationData, text: text, enabledEntityTypes: [], sectionId: self.section, style: .blocks)
|
||||
case let .optionPeer(optionId, _, peer, optionText, optionTextEntities, optionAdditionalText, optionCount, optionExpanded, opaqueIdentifier, shimmeringAlternation, isFirstInOption):
|
||||
let font = Font.regular(13.0)
|
||||
|
Loading…
x
Reference in New Issue
Block a user