Various improvements

This commit is contained in:
Ilya Laktyushin 2022-09-02 20:26:03 +02:00
parent 95bbe5a518
commit 84679a71cd
37 changed files with 676 additions and 294 deletions

View File

@ -7973,6 +7973,7 @@ Sorry for the inconvenience.";
"Settings.ChangeProfilePhoto" = "Change Profile Photo";
"Premium.EmojiStatusShortTitle" = "This is %1$@'s current status.";
"Premium.EmojiStatusTitle" = "This is %1$@'s current status from #[%2$@]().";
"Premium.EmojiStatusText" = "Emoji status is a premium feature.\n Other features included in **Telegram Premium**:";
@ -8013,3 +8014,5 @@ Sorry for the inconvenience.";
"Premium.PricePerMonth" = "%@/month";
"Premium.PricePerYear" = "%@/year";
"Conversation.SendMesageAsPremiumInfo" = "Subscribe to **Telegram Premium** to be able to comment on behalf your channels in group chats.";

View File

@ -25,7 +25,7 @@ private final class PhoneAndCountryNode: ASDisplayNode {
var selectCountryCode: (() -> Void)?
var checkPhone: (() -> Void)?
var hasNumberUpdated: ((Bool) -> Void)?
var numberUpdated: (() -> Void)?
var keyPressed: ((Int) -> Void)?
var preferredCountryIdForCode: [String: String] = [:]
@ -146,9 +146,7 @@ private final class PhoneAndCountryNode: ASDisplayNode {
self.phoneInputNode.numberTextUpdated = { [weak self] number in
if let strongSelf = self {
let _ = processNumberChange(strongSelf.phoneInputNode.number)
strongSelf.numberUpdated?()
if strongSelf.hasCountry {
strongSelf.hasNumberUpdated?(!strongSelf.phoneInputNode.codeAndNumber.2.isEmpty)
} else {
@ -162,9 +160,7 @@ private final class PhoneAndCountryNode: ASDisplayNode {
if let name = name {
strongSelf.preferredCountryIdForCode[code] = name
}
strongSelf.numberUpdated?()
if processNumberChange(strongSelf.phoneInputNode.number) {
} else if let code = Int(code), let name = name, let countryName = countryCodeAndIdToName[CountryCodeAndId(code: code, id: name)] {
let flagString = emojiFlagForISOCountryCode(name)
@ -211,6 +207,10 @@ private final class PhoneAndCountryNode: ASDisplayNode {
self.phoneInputNode.returnAction = { [weak self] in
self?.checkPhone?()
}
self.phoneInputNode.keyPressed = { [weak self] num in
self?.keyPressed?(num)
}
}
@objc func countryPressed() {
@ -410,11 +410,9 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
self.phoneAndCountryNode.hasNumberUpdated = { [weak self] hasNumber in
self?.proceedNode.isEnabled = hasNumber
}
self.phoneAndCountryNode.numberUpdated = { [weak self] in
self.phoneAndCountryNode.keyPressed = { [weak self] num in
if let strongSelf = self, !strongSelf.managedAnimationNode.isHidden {
if let state = ManagedPhoneAnimationState.allCases.randomElement() {
strongSelf.managedAnimationNode.enqueue(state)
}
strongSelf.managedAnimationNode.animate(num: num)
}
}
@ -1049,40 +1047,113 @@ final class PhoneConfirmationController: ViewController {
}
}
private enum ManagedPhoneAnimationState: CaseIterable, Equatable {
case keypad1
case keypad2
case keypad3
case keypad4
private final class PhoneKeyNode: ASDisplayNode {
private let imageNode: ASImageNode
private var highlightedNode: ASImageNode?
private let image: UIImage?
private let highlightedImage: UIImage?
init(offset: CGPoint, image: UIImage?, highlightedImage: UIImage?) {
self.image = image
self.highlightedImage = highlightedImage
self.imageNode = ASImageNode()
self.imageNode.displaysAsynchronously = false
self.imageNode.image = image
super.init()
self.clipsToBounds = true
if let imageSize = self.imageNode.image?.size {
self.imageNode.frame = CGRect(origin: CGPoint(x: -offset.x, y: -offset.y), size: imageSize)
}
self.addSubnode(self.imageNode)
}
func animatePress() {
guard self.highlightedNode == nil else {
return
}
let highlightedNode = ASImageNode()
highlightedNode.displaysAsynchronously = false
highlightedNode.image = self.highlightedImage
highlightedNode.frame = self.imageNode.frame
self.addSubnode(highlightedNode)
self.highlightedNode = highlightedNode
highlightedNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, removeOnCompletion: false, completion: { [weak self] _ in
self?.highlightedNode?.removeFromSupernode()
self?.highlightedNode = nil
})
let values: [NSNumber] = [0.75, 0.5, 0.75, 1.0]
self.layer.animateKeyframes(values: values, duration: 0.16, keyPath: "transform.scale")
}
}
private final class ManagedPhoneAnimationNode: ManagedAnimationNode {
private var phoneState: ManagedPhoneAnimationState = .keypad1
private var timer: SwiftSignalKit.Timer?
private let plateNode: ASDisplayNode
private var nodes: [PhoneKeyNode]
init() {
self.plateNode = ASDisplayNode()
self.plateNode.backgroundColor = UIColor(rgb: 0xc30023)
self.plateNode.frame = CGRect(x: 27.0, y: 38.0, width: 46.0, height: 32.0)
let image = UIImage(bundleImageName: "Settings/Keypad")
let highlightedImage = generateTintedImage(image: image, color: UIColor(rgb: 0x000000, alpha: 0.4))
var nodes: [PhoneKeyNode] = []
for i in 0 ..< 9 {
let offset: CGPoint
switch i {
case 1:
offset = CGPoint(x: 15.0, y: 0.0)
case 2:
offset = CGPoint(x: 30.0, y: 0.0)
case 3:
offset = CGPoint(x: 0.0, y: 10.0)
case 4:
offset = CGPoint(x: 15.0, y: 10.0)
case 5:
offset = CGPoint(x: 30.0, y: 10.0)
case 6:
offset = CGPoint(x: 0.0, y: 21.0)
case 7:
offset = CGPoint(x: 15.0, y: 21.0)
case 8:
offset = CGPoint(x: 30.0, y: 21.0)
default:
offset = CGPoint(x: 0.0, y: 0.0)
}
let node = PhoneKeyNode(offset: offset, image: image, highlightedImage: highlightedImage)
node.frame = CGRect(origin: offset.offsetBy(dx: 28.0, dy: 38.0), size: CGSize(width: 15.0, height: 10.0))
nodes.append(node)
}
self.nodes = nodes
super.init(size: CGSize(width: 100.0, height: 100.0))
self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.3))
}
func enqueue(_ state: ManagedPhoneAnimationState) {
switch state {
case .keypad1:
self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 0, endFrame: 10), duration: 0.15))
self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 10, endFrame: 0), duration: 0.15))
case .keypad2:
self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 24, endFrame: 34), duration: 0.15))
self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 34, endFrame: 24), duration: 0.15))
self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.1))
case .keypad3:
self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 36, endFrame: 46), duration: 0.15))
self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 46, endFrame: 36), duration: 0.15))
self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.1))
case .keypad4:
self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 0, endFrame: 10), duration: 0.15))
self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 10, endFrame: 0), duration: 0.15))
self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.001))
self.addSubnode(self.plateNode)
for node in nodes {
self.addSubnode(node)
}
}
func animate(num: Int) {
guard num != 0 else {
return
}
let index = max(0, min(self.nodes.count - 1, num - 1))
self.nodes[index].animatePress()
}
}

View File

@ -384,7 +384,7 @@ public final class ChatPresentationInterfaceState: Equatable {
public let showCommands: Bool
public let hasBotCommands: Bool
public let showSendAsPeers: Bool
public let sendAsPeers: [FoundPeer]?
public let sendAsPeers: [SendAsPeer]?
public let botMenuButton: BotMenuButton
public let showWebView: Bool
public let currentSendAsPeerId: PeerId?
@ -462,7 +462,7 @@ public final class ChatPresentationInterfaceState: Equatable {
self.customEmojiAvailable = true
}
public init(interfaceState: ChatInterfaceState, chatLocation: ChatLocation, renderedPeer: RenderedPeer?, isNotAccessible: Bool, explicitelyCanPinMessages: Bool, contactStatus: ChatContactStatus?, hasBots: Bool, isArchived: Bool, inputTextPanelState: ChatTextInputPanelState, editMessageState: ChatEditInterfaceMessageState?, recordedMediaPreview: ChatRecordedMediaPreview?, inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult], inputMode: ChatInputMode, titlePanelContexts: [ChatTitlePanelContext], keyboardButtonsMessage: Message?, pinnedMessageId: MessageId?, pinnedMessage: ChatPinnedMessage?, peerIsBlocked: Bool, peerIsMuted: Bool, peerDiscussionId: PeerId?, peerGeoLocation: PeerGeoLocation?, callsAvailable: Bool, callsPrivate: Bool, slowmodeState: ChatSlowmodeState?, chatHistoryState: ChatHistoryNodeHistoryState?, botStartPayload: String?, urlPreview: (String, TelegramMediaWebpage)?, editingUrlPreview: (String, TelegramMediaWebpage)?, search: ChatSearchData?, searchQuerySuggestionResult: ChatPresentationInputQueryResult?, presentationReady: Bool, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, limitsConfiguration: LimitsConfiguration, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, accountPeerId: PeerId, mode: ChatControllerPresentationMode, hasScheduledMessages: Bool, autoremoveTimeout: Int32?, subject: ChatControllerSubject?, peerNearbyData: ChatPeerNearbyData?, greetingData: ChatGreetingData?, pendingUnpinnedAllMessages: Bool, activeGroupCallInfo: ChatActiveGroupCallInfo?, hasActiveGroupCall: Bool, importState: ChatPresentationImportState?, reportReason: ReportReason?, showCommands: Bool, hasBotCommands: Bool, showSendAsPeers: Bool, sendAsPeers: [FoundPeer]?, botMenuButton: BotMenuButton, showWebView: Bool, currentSendAsPeerId: PeerId?, copyProtectionEnabled: Bool, hasPlentyOfMessages: Bool, isPremium: Bool, forceInputCommandsHidden: Bool, voiceMessagesAvailable: Bool, customEmojiAvailable: Bool) {
public init(interfaceState: ChatInterfaceState, chatLocation: ChatLocation, renderedPeer: RenderedPeer?, isNotAccessible: Bool, explicitelyCanPinMessages: Bool, contactStatus: ChatContactStatus?, hasBots: Bool, isArchived: Bool, inputTextPanelState: ChatTextInputPanelState, editMessageState: ChatEditInterfaceMessageState?, recordedMediaPreview: ChatRecordedMediaPreview?, inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult], inputMode: ChatInputMode, titlePanelContexts: [ChatTitlePanelContext], keyboardButtonsMessage: Message?, pinnedMessageId: MessageId?, pinnedMessage: ChatPinnedMessage?, peerIsBlocked: Bool, peerIsMuted: Bool, peerDiscussionId: PeerId?, peerGeoLocation: PeerGeoLocation?, callsAvailable: Bool, callsPrivate: Bool, slowmodeState: ChatSlowmodeState?, chatHistoryState: ChatHistoryNodeHistoryState?, botStartPayload: String?, urlPreview: (String, TelegramMediaWebpage)?, editingUrlPreview: (String, TelegramMediaWebpage)?, search: ChatSearchData?, searchQuerySuggestionResult: ChatPresentationInputQueryResult?, presentationReady: Bool, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, limitsConfiguration: LimitsConfiguration, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, accountPeerId: PeerId, mode: ChatControllerPresentationMode, hasScheduledMessages: Bool, autoremoveTimeout: Int32?, subject: ChatControllerSubject?, peerNearbyData: ChatPeerNearbyData?, greetingData: ChatGreetingData?, pendingUnpinnedAllMessages: Bool, activeGroupCallInfo: ChatActiveGroupCallInfo?, hasActiveGroupCall: Bool, importState: ChatPresentationImportState?, reportReason: ReportReason?, showCommands: Bool, hasBotCommands: Bool, showSendAsPeers: Bool, sendAsPeers: [SendAsPeer]?, botMenuButton: BotMenuButton, showWebView: Bool, currentSendAsPeerId: PeerId?, copyProtectionEnabled: Bool, hasPlentyOfMessages: Bool, isPremium: Bool, forceInputCommandsHidden: Bool, voiceMessagesAvailable: Bool, customEmojiAvailable: Bool) {
self.interfaceState = interfaceState
self.chatLocation = chatLocation
self.renderedPeer = renderedPeer
@ -929,7 +929,7 @@ public final class ChatPresentationInterfaceState: Equatable {
return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable)
}
public func updatedSendAsPeers(_ sendAsPeers: [FoundPeer]?) -> ChatPresentationInterfaceState {
public func updatedSendAsPeers(_ sendAsPeers: [SendAsPeer]?) -> ChatPresentationInterfaceState {
return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable)
}

View File

@ -31,6 +31,7 @@ public final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
private let textNode: ImmediateTextNode
private let statusNode: ImmediateTextNode?
private let iconNode: ASImageNode
private let titleIconNode: ASImageNode
private let badgeBackgroundNode: ASImageNode
private let badgeTextNode: ImmediateTextNode
private let buttonNode: HighlightTrackingButtonNode
@ -134,6 +135,13 @@ public final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
self.iconNode.image = action.icon(presentationData.theme)
}
self.titleIconNode = ASImageNode()
self.titleIconNode.isAccessibilityElement = false
self.titleIconNode.displaysAsynchronously = false
self.titleIconNode.displayWithoutProcessing = true
self.titleIconNode.isUserInteractionEnabled = false
self.titleIconNode.image = action.textIcon(presentationData.theme)
self.badgeBackgroundNode = ASImageNode()
self.badgeBackgroundNode.isAccessibilityElement = false
self.badgeBackgroundNode.displaysAsynchronously = false
@ -173,6 +181,9 @@ public final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
self.addSubnode(self.badgeBackgroundNode)
self.addSubnode(self.badgeTextNode)
self.addSubnode(self.buttonNode)
if let _ = self.titleIconNode.image {
self.addSubnode(self.titleIconNode)
}
self.buttonNode.highligthedChanged = { [weak self] highligted in
guard let strongSelf = self else {
@ -272,6 +283,10 @@ public final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
transition.updateFrame(node: self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
if let iconSize = self.titleIconNode.image?.size {
transition.updateFrame(node: self.titleIconNode, frame: CGRect(origin: CGPoint(x: self.textNode.frame.maxX + 7.0, y: floorToScreenPixels(self.textNode.frame.midY - iconSize.height / 2.0)), size: iconSize))
}
})
} else {
return (CGSize(width: textSize.width + sideInset + rightTextInset + badgeWidthSpace, height: verticalInset * 2.0 + textSize.height), { size, transition in
@ -290,6 +305,10 @@ public final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
transition.updateFrame(node: self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
if let iconSize = self.titleIconNode.image?.size {
transition.updateFrame(node: self.titleIconNode, frame: CGRect(origin: CGPoint(x: self.textNode.frame.maxX + 7.0, y: floorToScreenPixels(self.textNode.frame.midY - iconSize.height / 2.0)), size: iconSize))
}
})
}
}
@ -378,13 +397,15 @@ public final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
self.requestLayout()
}
private var performedAction = false
public func performAction() {
guard let controller = self.getController() else {
guard let controller = self.getController(), !self.performedAction else {
return
}
self.action.action?(ContextMenuActionItem.Action(
controller: controller,
dismissWithResult: { [weak self] result in
self?.performedAction = true
self?.actionSelected(result)
},
updateAction: { [weak self] id, updatedAction in

View File

@ -102,6 +102,7 @@ public final class ContextMenuActionItem {
public let badge: ContextMenuActionBadge?
public let icon: (PresentationTheme) -> UIImage?
public let iconSource: ContextMenuActionItemIconSource?
public let textIcon: (PresentationTheme) -> UIImage?
public let action: ((Action) -> Void)?
convenience public init(
@ -114,6 +115,7 @@ public final class ContextMenuActionItem {
badge: ContextMenuActionBadge? = nil,
icon: @escaping (PresentationTheme) -> UIImage?,
iconSource: ContextMenuActionItemIconSource? = nil,
textIcon: @escaping (PresentationTheme) -> UIImage? = { _ in return nil },
action: ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?
) {
self.init(
@ -126,6 +128,7 @@ public final class ContextMenuActionItem {
badge: badge,
icon: icon,
iconSource: iconSource,
textIcon: textIcon,
action: action.flatMap { action in
return { impl in
action(impl.controller, impl.dismissWithResult)
@ -144,6 +147,7 @@ public final class ContextMenuActionItem {
badge: ContextMenuActionBadge? = nil,
icon: @escaping (PresentationTheme) -> UIImage?,
iconSource: ContextMenuActionItemIconSource? = nil,
textIcon: @escaping (PresentationTheme) -> UIImage? = { _ in return nil },
action: ((Action) -> Void)?
) {
self.id = id
@ -155,6 +159,7 @@ public final class ContextMenuActionItem {
self.badge = badge
self.icon = icon
self.iconSource = iconSource
self.textIcon = textIcon
self.action = action
}
}

View File

@ -7,7 +7,7 @@ import Display
import AppBundle
import LegacyComponents
private func createEmitterBehavior(type: String) -> NSObject {
func createEmitterBehavior(type: String) -> NSObject {
let selector = ["behaviorWith", "Type:"].joined(separator: "")
let behaviorClass = NSClassFromString(["CA", "Emitter", "Behavior"].joined(separator: "")) as! NSObject.Type
let behaviorWithType = behaviorClass.method(for: NSSelectorFromString(selector))!

View File

@ -0,0 +1,97 @@
import Foundation
import UIKit
import SwiftSignalKit
import AsyncDisplayKit
import Display
import AppBundle
import LegacyComponents
public class MediaDustNode: ASDisplayNode {
private var currentParams: (size: CGSize, color: UIColor)?
private var animColor: CGColor?
private var emitterNode: ASDisplayNode
private var emitter: CAEmitterCell?
private var emitterLayer: CAEmitterLayer?
public override init() {
self.emitterNode = ASDisplayNode()
self.emitterNode.isUserInteractionEnabled = false
self.emitterNode.clipsToBounds = true
super.init()
self.addSubnode(self.emitterNode)
}
public override func didLoad() {
super.didLoad()
let emitter = CAEmitterCell()
emitter.color = UIColor(rgb: 0xffffff, alpha: 0.0).cgColor
emitter.contents = UIImage(bundleImageName: "Components/TextSpeckle")?.cgImage
emitter.contentsScale = 1.8
emitter.emissionRange = .pi * 2.0
emitter.lifetime = 8.0
emitter.scale = 0.5
emitter.velocityRange = 0.0
emitter.name = "dustCell"
emitter.alphaRange = 1.0
emitter.setValue("point", forKey: "particleType")
emitter.setValue(1.0, forKey: "mass")
emitter.setValue(0.01, forKey: "massRange")
self.emitter = emitter
let alphaBehavior = createEmitterBehavior(type: "valueOverLife")
alphaBehavior.setValue("color.alpha", forKey: "keyPath")
alphaBehavior.setValue([0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1], forKey: "values")
alphaBehavior.setValue(true, forKey: "additive")
let scaleBehavior = createEmitterBehavior(type: "valueOverLife")
scaleBehavior.setValue("scale", forKey: "keyPath")
scaleBehavior.setValue([0.0, 0.5], forKey: "values")
scaleBehavior.setValue([0.0, 0.05], forKey: "locations")
let behaviors = [alphaBehavior, scaleBehavior]
let emitterLayer = CAEmitterLayer()
emitterLayer.masksToBounds = true
emitterLayer.allowsGroupOpacity = true
emitterLayer.lifetime = 1
emitterLayer.emitterCells = [emitter]
emitterLayer.seed = arc4random()
emitterLayer.emitterShape = .rectangle
emitterLayer.setValue(behaviors, forKey: "emitterBehaviors")
self.emitterLayer = emitterLayer
self.emitterNode.layer.addSublayer(emitterLayer)
self.updateEmitter()
}
private func updateEmitter() {
guard let (size, _) = self.currentParams else {
return
}
self.emitterLayer?.frame = CGRect(origin: CGPoint(), size: size)
self.emitterLayer?.emitterSize = size
self.emitterLayer?.emitterPosition = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
let square = Float(size.width * size.height)
Queue.mainQueue().async {
self.emitter?.birthRate = min(100000.0, square * 0.016)
}
}
public func update(size: CGSize, color: UIColor) {
self.currentParams = (size, color)
self.emitterNode.frame = CGRect(origin: CGPoint(), size: size)
if self.isNodeLoaded {
self.updateEmitter()
}
}
}

View File

@ -198,7 +198,7 @@ public func inviteRequestsController(context: AccountContext, updatedPresentatio
} else {
string = presentationData.strings.MemberRequests_UserAddedToGroup(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string
}
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .invitedToVoiceChat(context: context, peer: peer, text: string), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .invitedToVoiceChat(context: context, peer: peer, text: string, action: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
})
}

View File

@ -167,6 +167,8 @@ public final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate {
public var countryCodeTextUpdated: ((String) -> Void)?
public var numberTextUpdated: ((String) -> Void)?
public var keyPressed: ((Int) -> Void)?
public var returnAction: (() -> Void)?
private let phoneFormatter = InteractivePhoneFormatter()
@ -245,7 +247,7 @@ public final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate {
@objc private func numberTextChanged(_ textField: UITextField) {
self.updateNumberFromTextFields()
}
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if !self.enableEditing {
return false
@ -254,6 +256,11 @@ public final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate {
self.updateNumber(cleanPhoneNumber(string), tryRestoringInputPosition: false)
return false
}
if string.count == 1, let num = Int(string) {
self.keyPressed?(num)
}
return true
}

View File

@ -1271,7 +1271,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let layoutOptions = {
if state.isPremium == true {
} else if let products = state.products {
} else if let products = state.products, products.count > 1 {
var optionsItems: [SectionGroupComponent.Item] = []
let gradientColors: [UIColor] = [
UIColor(rgb: 0x8e77ff),
@ -2113,9 +2113,22 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
var highlightableLinks = false
let secondaryTitleText: String
if let otherPeerName = state.otherPeerName {
if case let .emojiStatus(_, _, _, emojiPackTitle) = context.component.source {
if case let .emojiStatus(_, _, file, emojiPackTitle) = context.component.source {
highlightableLinks = true
secondaryTitleText = environment.strings.Premium_EmojiStatusTitle(otherPeerName, emojiPackTitle ?? "").string.replacingOccurrences(of: "#", with: " # ")
var packReference: StickerPackReference?
if let file = file {
for attribute in file.attributes {
if case let .CustomEmoji(_, _, reference) = attribute {
packReference = reference
}
}
}
if let packReference = packReference, case let .id(id, _) = packReference, id == 773947703670341676 {
secondaryTitleText = environment.strings.Premium_EmojiStatusShortTitle(otherPeerName).string
} else {
secondaryTitleText = environment.strings.Premium_EmojiStatusTitle(otherPeerName, emojiPackTitle ?? "").string.replacingOccurrences(of: "#", with: " # ")
}
} else if case .profile = context.component.source {
secondaryTitleText = environment.strings.Premium_PersonalTitle(otherPeerName).string
} else if case let .gift(fromPeerId, _, duration) = context.component.source {

View File

@ -205,6 +205,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1275374751] = { return Api.EmojiLanguage.parse_emojiLanguage($0) }
dict[-1835310691] = { return Api.EmojiStatus.parse_emojiStatus($0) }
dict[769727150] = { return Api.EmojiStatus.parse_emojiStatusEmpty($0) }
dict[-97474361] = { return Api.EmojiStatus.parse_emojiStatusUntil($0) }
dict[-1519029347] = { return Api.EmojiURL.parse_emojiURL($0) }
dict[1643173063] = { return Api.EncryptedChat.parse_encryptedChat($0) }
dict[505183301] = { return Api.EncryptedChat.parse_encryptedChatDiscarded($0) }
@ -354,6 +355,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[42402760] = { return Api.InputStickerSet.parse_inputStickerSetAnimatedEmoji($0) }
dict[215889721] = { return Api.InputStickerSet.parse_inputStickerSetAnimatedEmojiAnimations($0) }
dict[-427863538] = { return Api.InputStickerSet.parse_inputStickerSetDice($0) }
dict[701560302] = { return Api.InputStickerSet.parse_inputStickerSetEmojiDefaultStatuses($0) }
dict[80008398] = { return Api.InputStickerSet.parse_inputStickerSetEmojiGenericAnimations($0) }
dict[-4838507] = { return Api.InputStickerSet.parse_inputStickerSetEmpty($0) }
dict[-1645763991] = { return Api.InputStickerSet.parse_inputStickerSetID($0) }
@ -696,6 +698,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1954007928] = { return Api.SecureValueType.parse_secureValueTypeRentalAgreement($0) }
dict[-368907213] = { return Api.SecureValueType.parse_secureValueTypeTemporaryRegistration($0) }
dict[-63531698] = { return Api.SecureValueType.parse_secureValueTypeUtilityBill($0) }
dict[-1206095820] = { return Api.SendAsPeer.parse_sendAsPeer($0) }
dict[-44119819] = { return Api.SendMessageAction.parse_sendMessageCancelAction($0) }
dict[1653390447] = { return Api.SendMessageAction.parse_sendMessageChooseContactAction($0) }
dict[-1336228175] = { return Api.SendMessageAction.parse_sendMessageChooseStickerAction($0) }
@ -805,6 +808,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1398708869] = { return Api.Update.parse_updateMessagePoll($0) }
dict[274961865] = { return Api.Update.parse_updateMessagePollVote($0) }
dict[357013699] = { return Api.Update.parse_updateMessageReactions($0) }
dict[-2030252155] = { return Api.Update.parse_updateMoveStickerSetToTop($0) }
dict[1656358105] = { return Api.Update.parse_updateNewChannelMessage($0) }
dict[314359194] = { return Api.Update.parse_updateNewEncryptedMessage($0) }
dict[522914557] = { return Api.Update.parse_updateNewMessage($0) }
@ -936,7 +940,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-541588713] = { return Api.channels.ChannelParticipant.parse_channelParticipant($0) }
dict[-1699676497] = { return Api.channels.ChannelParticipants.parse_channelParticipants($0) }
dict[-266911767] = { return Api.channels.ChannelParticipants.parse_channelParticipantsNotModified($0) }
dict[-2091463255] = { return Api.channels.SendAsPeers.parse_sendAsPeers($0) }
dict[-191450938] = { return Api.channels.SendAsPeers.parse_sendAsPeers($0) }
dict[182326673] = { return Api.contacts.Blocked.parse_blocked($0) }
dict[-513392236] = { return Api.contacts.Blocked.parse_blockedSlice($0) }
dict[-353862078] = { return Api.contacts.Contacts.parse_contacts($0) }
@ -1554,6 +1558,8 @@ public extension Api {
_1.serialize(buffer, boxed)
case let _1 as Api.SecureValueType:
_1.serialize(buffer, boxed)
case let _1 as Api.SendAsPeer:
_1.serialize(buffer, boxed)
case let _1 as Api.SendMessageAction:
_1.serialize(buffer, boxed)
case let _1 as Api.ShippingOption:

View File

@ -541,7 +541,7 @@ public extension Api {
}
}
public extension Api {
enum MessageExtendedMedia: TypeConstructorDescription {
indirect enum MessageExtendedMedia: TypeConstructorDescription {
case messageExtendedMedia(media: Api.MessageMedia)
case messageExtendedMediaPreview(flags: Int32, w: Int32?, h: Int32?, thumb: Api.PhotoSize?, videoDuration: Int32?)

View File

@ -1,3 +1,45 @@
public extension Api {
enum SendAsPeer: TypeConstructorDescription {
case sendAsPeer(flags: Int32, peer: Api.Peer)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .sendAsPeer(let flags, let peer):
if boxed {
buffer.appendInt32(-1206095820)
}
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .sendAsPeer(let flags, let peer):
return ("sendAsPeer", [("flags", String(describing: flags)), ("peer", String(describing: peer))])
}
}
public static func parse_sendAsPeer(_ reader: BufferReader) -> SendAsPeer? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.Peer?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.Peer
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.SendAsPeer.sendAsPeer(flags: _1!, peer: _2!)
}
else {
return nil
}
}
}
}
public extension Api {
enum SendMessageAction: TypeConstructorDescription {
case sendMessageCancelAction
@ -842,49 +884,3 @@ public extension Api {
}
}
public extension Api {
enum StickerPack: TypeConstructorDescription {
case stickerPack(emoticon: String, documents: [Int64])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .stickerPack(let emoticon, let documents):
if boxed {
buffer.appendInt32(313694676)
}
serializeString(emoticon, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(documents.count))
for item in documents {
serializeInt64(item, buffer: buffer, boxed: false)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .stickerPack(let emoticon, let documents):
return ("stickerPack", [("emoticon", String(describing: emoticon)), ("documents", String(describing: documents))])
}
}
public static func parse_stickerPack(_ reader: BufferReader) -> StickerPack? {
var _1: String?
_1 = parseString(reader)
var _2: [Int64]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.StickerPack.stickerPack(emoticon: _1!, documents: _2!)
}
else {
return nil
}
}
}
}

View File

@ -1,3 +1,49 @@
public extension Api {
enum StickerPack: TypeConstructorDescription {
case stickerPack(emoticon: String, documents: [Int64])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .stickerPack(let emoticon, let documents):
if boxed {
buffer.appendInt32(313694676)
}
serializeString(emoticon, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(documents.count))
for item in documents {
serializeInt64(item, buffer: buffer, boxed: false)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .stickerPack(let emoticon, let documents):
return ("stickerPack", [("emoticon", String(describing: emoticon)), ("documents", String(describing: documents))])
}
}
public static func parse_stickerPack(_ reader: BufferReader) -> StickerPack? {
var _1: String?
_1 = parseString(reader)
var _2: [Int64]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.StickerPack.stickerPack(emoticon: _1!, documents: _2!)
}
else {
return nil
}
}
}
}
public extension Api {
enum StickerSet: TypeConstructorDescription {
case stickerSet(flags: Int32, installedDate: Int32?, id: Int64, accessHash: Int64, title: String, shortName: String, thumbs: [Api.PhotoSize]?, thumbDcId: Int32?, thumbVersion: Int32?, thumbDocumentId: Int64?, count: Int32, hash: Int32)
@ -613,6 +659,7 @@ public extension Api {
case updateMessagePoll(flags: Int32, pollId: Int64, poll: Api.Poll?, results: Api.PollResults)
case updateMessagePollVote(pollId: Int64, userId: Int64, options: [Buffer], qts: Int32)
case updateMessageReactions(peer: Api.Peer, msgId: Int32, reactions: Api.MessageReactions)
case updateMoveStickerSetToTop(flags: Int32, stickerset: Int64)
case updateNewChannelMessage(message: Api.Message, pts: Int32, ptsCount: Int32)
case updateNewEncryptedMessage(message: Api.EncryptedMessage, qts: Int32)
case updateNewMessage(message: Api.Message, pts: Int32, ptsCount: Int32)
@ -1195,6 +1242,13 @@ public extension Api {
serializeInt32(msgId, buffer: buffer, boxed: false)
reactions.serialize(buffer, true)
break
case .updateMoveStickerSetToTop(let flags, let stickerset):
if boxed {
buffer.appendInt32(-2030252155)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(stickerset, buffer: buffer, boxed: false)
break
case .updateNewChannelMessage(let message, let pts, let ptsCount):
if boxed {
buffer.appendInt32(1656358105)
@ -1698,6 +1752,8 @@ public extension Api {
return ("updateMessagePollVote", [("pollId", String(describing: pollId)), ("userId", String(describing: userId)), ("options", String(describing: options)), ("qts", String(describing: qts))])
case .updateMessageReactions(let peer, let msgId, let reactions):
return ("updateMessageReactions", [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("reactions", String(describing: reactions))])
case .updateMoveStickerSetToTop(let flags, let stickerset):
return ("updateMoveStickerSetToTop", [("flags", String(describing: flags)), ("stickerset", String(describing: stickerset))])
case .updateNewChannelMessage(let message, let pts, let ptsCount):
return ("updateNewChannelMessage", [("message", String(describing: message)), ("pts", String(describing: pts)), ("ptsCount", String(describing: ptsCount))])
case .updateNewEncryptedMessage(let message, let qts):
@ -2918,6 +2974,20 @@ public extension Api {
return nil
}
}
public static func parse_updateMoveStickerSetToTop(_ reader: BufferReader) -> Update? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.Update.updateMoveStickerSetToTop(flags: _1!, stickerset: _2!)
}
else {
return nil
}
}
public static func parse_updateNewChannelMessage(_ reader: BufferReader) -> Update? {
var _1: Api.Message?
if let signature = reader.readInt32() {

View File

@ -692,13 +692,13 @@ public extension Api.channels {
}
public extension Api.channels {
enum SendAsPeers: TypeConstructorDescription {
case sendAsPeers(peers: [Api.Peer], chats: [Api.Chat], users: [Api.User])
case sendAsPeers(peers: [Api.SendAsPeer], chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .sendAsPeers(let peers, let chats, let users):
if boxed {
buffer.appendInt32(-2091463255)
buffer.appendInt32(-191450938)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(peers.count))
@ -727,9 +727,9 @@ public extension Api.channels {
}
public static func parse_sendAsPeers(_ reader: BufferReader) -> SendAsPeers? {
var _1: [Api.Peer]?
var _1: [Api.SendAsPeer]?
if let _ = reader.readInt32() {
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self)
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SendAsPeer.self)
}
var _2: [Api.Chat]?
if let _ = reader.readInt32() {

View File

@ -390,6 +390,7 @@ public extension Api {
enum EmojiStatus: TypeConstructorDescription {
case emojiStatus(documentId: Int64)
case emojiStatusEmpty
case emojiStatusUntil(documentId: Int64, until: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -404,6 +405,13 @@ public extension Api {
buffer.appendInt32(769727150)
}
break
case .emojiStatusUntil(let documentId, let until):
if boxed {
buffer.appendInt32(-97474361)
}
serializeInt64(documentId, buffer: buffer, boxed: false)
serializeInt32(until, buffer: buffer, boxed: false)
break
}
}
@ -414,6 +422,8 @@ public extension Api {
return ("emojiStatus", [("documentId", String(describing: documentId))])
case .emojiStatusEmpty:
return ("emojiStatusEmpty", [])
case .emojiStatusUntil(let documentId, let until):
return ("emojiStatusUntil", [("documentId", String(describing: documentId)), ("until", String(describing: until))])
}
}
@ -431,6 +441,20 @@ public extension Api {
public static func parse_emojiStatusEmpty(_ reader: BufferReader) -> EmojiStatus? {
return Api.EmojiStatus.emojiStatusEmpty
}
public static func parse_emojiStatusUntil(_ reader: BufferReader) -> EmojiStatus? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Int32?
_2 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.EmojiStatus.emojiStatusUntil(documentId: _1!, until: _2!)
}
else {
return nil
}
}
}
}
@ -1136,67 +1160,3 @@ public extension Api {
}
}
public extension Api {
enum GeoPoint: TypeConstructorDescription {
case geoPoint(flags: Int32, long: Double, lat: Double, accessHash: Int64, accuracyRadius: Int32?)
case geoPointEmpty
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .geoPoint(let flags, let long, let lat, let accessHash, let accuracyRadius):
if boxed {
buffer.appendInt32(-1297942941)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeDouble(long, buffer: buffer, boxed: false)
serializeDouble(lat, buffer: buffer, boxed: false)
serializeInt64(accessHash, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(accuracyRadius!, buffer: buffer, boxed: false)}
break
case .geoPointEmpty:
if boxed {
buffer.appendInt32(286776671)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .geoPoint(let flags, let long, let lat, let accessHash, let accuracyRadius):
return ("geoPoint", [("flags", String(describing: flags)), ("long", String(describing: long)), ("lat", String(describing: lat)), ("accessHash", String(describing: accessHash)), ("accuracyRadius", String(describing: accuracyRadius))])
case .geoPointEmpty:
return ("geoPointEmpty", [])
}
}
public static func parse_geoPoint(_ reader: BufferReader) -> GeoPoint? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Double?
_2 = reader.readDouble()
var _3: Double?
_3 = reader.readDouble()
var _4: Int64?
_4 = reader.readInt64()
var _5: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt32() }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.GeoPoint.geoPoint(flags: _1!, long: _2!, lat: _3!, accessHash: _4!, accuracyRadius: _5)
}
else {
return nil
}
}
public static func parse_geoPointEmpty(_ reader: BufferReader) -> GeoPoint? {
return Api.GeoPoint.geoPointEmpty
}
}
}

View File

@ -1,3 +1,67 @@
public extension Api {
enum GeoPoint: TypeConstructorDescription {
case geoPoint(flags: Int32, long: Double, lat: Double, accessHash: Int64, accuracyRadius: Int32?)
case geoPointEmpty
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .geoPoint(let flags, let long, let lat, let accessHash, let accuracyRadius):
if boxed {
buffer.appendInt32(-1297942941)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeDouble(long, buffer: buffer, boxed: false)
serializeDouble(lat, buffer: buffer, boxed: false)
serializeInt64(accessHash, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(accuracyRadius!, buffer: buffer, boxed: false)}
break
case .geoPointEmpty:
if boxed {
buffer.appendInt32(286776671)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .geoPoint(let flags, let long, let lat, let accessHash, let accuracyRadius):
return ("geoPoint", [("flags", String(describing: flags)), ("long", String(describing: long)), ("lat", String(describing: lat)), ("accessHash", String(describing: accessHash)), ("accuracyRadius", String(describing: accuracyRadius))])
case .geoPointEmpty:
return ("geoPointEmpty", [])
}
}
public static func parse_geoPoint(_ reader: BufferReader) -> GeoPoint? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Double?
_2 = reader.readDouble()
var _3: Double?
_3 = reader.readDouble()
var _4: Int64?
_4 = reader.readInt64()
var _5: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt32() }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.GeoPoint.geoPoint(flags: _1!, long: _2!, lat: _3!, accessHash: _4!, accuracyRadius: _5)
}
else {
return nil
}
}
public static func parse_geoPointEmpty(_ reader: BufferReader) -> GeoPoint? {
return Api.GeoPoint.geoPointEmpty
}
}
}
public extension Api {
enum GlobalPrivacySettings: TypeConstructorDescription {
case globalPrivacySettings(flags: Int32, archiveAndMuteNewNoncontactPeers: Api.Bool?)

View File

@ -677,6 +677,7 @@ public extension Api {
case inputStickerSetAnimatedEmoji
case inputStickerSetAnimatedEmojiAnimations
case inputStickerSetDice(emoticon: String)
case inputStickerSetEmojiDefaultStatuses
case inputStickerSetEmojiGenericAnimations
case inputStickerSetEmpty
case inputStickerSetID(id: Int64, accessHash: Int64)
@ -702,6 +703,12 @@ public extension Api {
buffer.appendInt32(-427863538)
}
serializeString(emoticon, buffer: buffer, boxed: false)
break
case .inputStickerSetEmojiDefaultStatuses:
if boxed {
buffer.appendInt32(701560302)
}
break
case .inputStickerSetEmojiGenericAnimations:
if boxed {
@ -745,6 +752,8 @@ public extension Api {
return ("inputStickerSetAnimatedEmojiAnimations", [])
case .inputStickerSetDice(let emoticon):
return ("inputStickerSetDice", [("emoticon", String(describing: emoticon))])
case .inputStickerSetEmojiDefaultStatuses:
return ("inputStickerSetEmojiDefaultStatuses", [])
case .inputStickerSetEmojiGenericAnimations:
return ("inputStickerSetEmojiGenericAnimations", [])
case .inputStickerSetEmpty:
@ -775,6 +784,9 @@ public extension Api {
return nil
}
}
public static func parse_inputStickerSetEmojiDefaultStatuses(_ reader: BufferReader) -> InputStickerSet? {
return Api.InputStickerSet.inputStickerSetEmojiDefaultStatuses
}
public static func parse_inputStickerSetEmojiGenericAnimations(_ reader: BufferReader) -> InputStickerSet? {
return Api.InputStickerSet.inputStickerSetEmojiGenericAnimations
}

View File

@ -1254,7 +1254,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
} else {
text = strongSelf.presentationData.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string
}
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: EnginePeer(participant.peer), text: text), action: { _ in return false })
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: EnginePeer(participant.peer), text: text, action: nil), action: { _ in return false })
}
} else {
if case let .channel(groupPeer) = groupPeer, let listenerLink = inviteLinks?.listenerLink, !groupPeer.hasPermission(.inviteMembers) {
@ -1362,7 +1362,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
} else {
text = strongSelf.presentationData.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string
}
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: text), action: { _ in return false })
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: text, action: nil), action: { _ in return false })
}
}))
} else if case let .legacyGroup(groupPeer) = groupPeer {
@ -1430,7 +1430,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
} else {
text = strongSelf.presentationData.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string
}
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: text), action: { _ in return false })
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: text, action: nil), action: { _ in return false })
}
}))
}
@ -2262,7 +2262,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
return
}
let text = strongSelf.presentationData.strings.VoiceChat_PeerJoinedText(EnginePeer(event.peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: EnginePeer(event.peer), text: text), action: { _ in return false })
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: EnginePeer(event.peer), text: text, action: nil), action: { _ in return false })
}
}))
@ -2277,7 +2277,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
} else {
text = strongSelf.presentationData.strings.VoiceChat_DisplayAsSuccess(EnginePeer(peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string
}
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: EnginePeer(peer), text: text), action: { _ in return false })
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: EnginePeer(peer), text: text, action: nil), action: { _ in return false })
}))
self.stateVersionDisposable.set((self.call.stateVersion

View File

@ -66,6 +66,8 @@ extension StickerPackReference {
self = .premiumGifts
case .inputStickerSetEmojiGenericAnimations:
self = .emojiGenericAnimations
case .inputStickerSetEmojiDefaultStatuses:
return nil
}
}
}

View File

@ -101,6 +101,8 @@ extension PeerEmojiStatus {
self.init(fileId: documentId)
case .emojiStatusEmpty:
return nil
case .emojiStatusUntil:
return nil
}
}
}

View File

@ -4,12 +4,30 @@ import SwiftSignalKit
import TelegramApi
import MtProtoKit
public struct SendAsPeer: Equatable {
public let peer: Peer
public let subscribers: Int32?
public let isPremiumRequired: Bool
public init(peer: Peer, subscribers: Int32?, isPremiumRequired: Bool) {
self.peer = peer
self.subscribers = subscribers
self.isPremiumRequired = isPremiumRequired
}
public static func ==(lhs: SendAsPeer, rhs: SendAsPeer) -> Bool {
return lhs.peer.isEqual(rhs.peer) && lhs.subscribers == rhs.subscribers && lhs.isPremiumRequired == rhs.isPremiumRequired
}
}
public final class CachedSendAsPeers: Codable {
public let peerIds: [PeerId]
public let premiumRequiredPeerIds: Set<PeerId>
public let timestamp: Int32
public init(peerIds: [PeerId], timestamp: Int32) {
public init(peerIds: [PeerId], premiumRequiredPeerIds: Set<PeerId>, timestamp: Int32) {
self.peerIds = peerIds
self.premiumRequiredPeerIds = premiumRequiredPeerIds
self.timestamp = timestamp
}
@ -17,6 +35,7 @@ public final class CachedSendAsPeers: Codable {
let container = try decoder.container(keyedBy: StringCodingKey.self)
self.peerIds = (try container.decode([Int64].self, forKey: "peerIds")).map(PeerId.init)
self.premiumRequiredPeerIds = Set((try container.decodeIfPresent([Int64].self, forKey: "premiumRequiredPeerIds") ?? []).map(PeerId.init))
self.timestamp = try container.decode(Int32.self, forKey: "timestamp")
}
@ -24,24 +43,25 @@ public final class CachedSendAsPeers: Codable {
var container = encoder.container(keyedBy: StringCodingKey.self)
try container.encode(self.peerIds.map { $0.toInt64() }, forKey: "peerIds")
try container.encode(Array(self.premiumRequiredPeerIds).map { $0.toInt64() }, forKey: "premiumRequiredPeerIds")
try container.encode(self.timestamp, forKey: "timestamp")
}
}
func _internal_cachedPeerSendAsAvailablePeers(account: Account, peerId: PeerId) -> Signal<[FoundPeer], NoError> {
func _internal_cachedPeerSendAsAvailablePeers(account: Account, peerId: PeerId) -> Signal<[SendAsPeer], NoError> {
let key = ValueBoxKey(length: 8)
key.setInt64(0, value: peerId.toInt64())
return account.postbox.transaction { transaction -> ([FoundPeer], Int32)? in
return account.postbox.transaction { transaction -> ([SendAsPeer], Int32)? in
let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedSendAsPeers, key: key))?.get(CachedSendAsPeers.self)
if let cached = cached {
var peers: [FoundPeer] = []
var peers: [SendAsPeer] = []
for peerId in cached.peerIds {
if let peer = transaction.getPeer(peerId) {
var subscribers: Int32?
if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData {
subscribers = cachedData.participantsSummary.memberCount
}
peers.append(FoundPeer(peer: peer, subscribers: subscribers))
peers.append(SendAsPeer(peer: peer, subscribers: subscribers, isPremiumRequired: cached.premiumRequiredPeerIds.contains(peer.id)))
}
}
return (peers, cached.timestamp)
@ -49,8 +69,8 @@ func _internal_cachedPeerSendAsAvailablePeers(account: Account, peerId: PeerId)
return nil
}
}
|> mapToSignal { cachedPeersAndTimestamp -> Signal<[FoundPeer], NoError> in
let initialSignal: Signal<[FoundPeer], NoError>
|> mapToSignal { cachedPeersAndTimestamp -> Signal<[SendAsPeer], NoError> in
let initialSignal: Signal<[SendAsPeer], NoError>
if let (cachedPeers, _) = cachedPeersAndTimestamp {
initialSignal = .single(cachedPeers)
} else {
@ -58,10 +78,16 @@ func _internal_cachedPeerSendAsAvailablePeers(account: Account, peerId: PeerId)
}
return initialSignal
|> then(_internal_peerSendAsAvailablePeers(network: account.network, postbox: account.postbox, peerId: peerId)
|> mapToSignal { peers -> Signal<[FoundPeer], NoError> in
return account.postbox.transaction { transaction -> [FoundPeer] in
|> mapToSignal { peers -> Signal<[SendAsPeer], NoError> in
return account.postbox.transaction { transaction -> [SendAsPeer] in
let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
if let entry = CodableEntry(CachedSendAsPeers(peerIds: peers.map { $0.peer.id }, timestamp: currentTimestamp)) {
var premiumRequiredPeerIds = Set<PeerId>()
for peer in peers {
if peer.isPremiumRequired {
premiumRequiredPeerIds.insert(peer.peer.id)
}
}
if let entry = CodableEntry(CachedSendAsPeers(peerIds: peers.map { $0.peer.id }, premiumRequiredPeerIds: premiumRequiredPeerIds, timestamp: currentTimestamp)) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedSendAsPeers, key: key), entry: entry)
}
return peers
@ -71,7 +97,7 @@ func _internal_cachedPeerSendAsAvailablePeers(account: Account, peerId: PeerId)
}
func _internal_peerSendAsAvailablePeers(network: Network, postbox: Postbox, peerId: PeerId) -> Signal<[FoundPeer], NoError> {
func _internal_peerSendAsAvailablePeers(network: Network, postbox: Postbox, peerId: PeerId) -> Signal<[SendAsPeer], NoError> {
return postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
} |> mapToSignal { inputPeer in
@ -88,9 +114,15 @@ func _internal_peerSendAsAvailablePeers(network: Network, postbox: Postbox, peer
return .single([])
}
switch result {
case let .sendAsPeers(_, chats, _):
case let .sendAsPeers(sendAsPeers, chats, _):
var subscribers: [PeerId: Int32] = [:]
let peers = chats.compactMap(parseTelegramGroupOrChannel)
var premiumRequiredPeerIds = Set<PeerId>()
for sendAsPeer in sendAsPeers {
if case let .sendAsPeer(flags, peer) = sendAsPeer, (flags & (1 << 0)) != 0 {
premiumRequiredPeerIds.insert(peer.peerId)
}
}
for chat in chats {
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
switch chat {
@ -110,8 +142,8 @@ func _internal_peerSendAsAvailablePeers(network: Network, postbox: Postbox, peer
return updated
})
return peers
} |> map { peers -> [FoundPeer] in
return peers.map { FoundPeer(peer: $0, subscribers: subscribers[$0.id]) }
} |> map { peers -> [SendAsPeer] in
return peers.map { SendAsPeer(peer: $0, subscribers: subscribers[$0.id], isPremiumRequired: premiumRequiredPeerIds.contains($0.id)) }
}
}
}

View File

@ -690,7 +690,7 @@ public extension TelegramEngine {
|> ignoreValues
}
public func sendAsAvailablePeers(peerId: PeerId) -> Signal<[FoundPeer], NoError> {
public func sendAsAvailablePeers(peerId: PeerId) -> Signal<[SendAsPeer], NoError> {
return _internal_cachedPeerSendAsAvailablePeers(account: self.account, peerId: peerId)
}

View File

@ -556,7 +556,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
infoPrimaryTextColor: UIColor(rgb: 0xffffff),
infoLinkTextColor: UIColor(rgb: 0xffffff),
outgoingCheckColor: UIColor(rgb: 0xffffff),
mediaDateAndStatusFillColor: UIColor(white: 0.0, alpha: 0.5),
mediaDateAndStatusFillColor: UIColor(white: 0.0, alpha: 0.3),
mediaDateAndStatusTextColor: UIColor(rgb: 0xffffff),
shareButtonFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)),
shareButtonStrokeColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0xb2b2b2, alpha: 0.18), withoutWallpaper: UIColor(rgb: 0xb2b2b2, alpha: 0.18)),

View File

@ -789,7 +789,7 @@ public func makeDefaultDarkTintedPresentationTheme(extendingThemeReference: Pres
infoPrimaryTextColor: UIColor(rgb: 0xffffff),
infoLinkTextColor: accentColor,
outgoingCheckColor: outgoingCheckColor,
mediaDateAndStatusFillColor: UIColor(white: 0.0, alpha: 0.5),
mediaDateAndStatusFillColor: UIColor(white: 0.0, alpha: 0.3),
mediaDateAndStatusTextColor: UIColor(rgb: 0xffffff),
shareButtonFillColor: PresentationThemeVariableColor(color: additionalBackgroundColor.withAlphaComponent(0.5)),
shareButtonStrokeColor: PresentationThemeVariableColor(color: buttonStrokeColor),

View File

@ -671,7 +671,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio
infoPrimaryTextColor: UIColor(rgb: 0x000000),
infoLinkTextColor: UIColor(rgb: 0x004bad),
outgoingCheckColor: UIColor(rgb: 0x19c700),
mediaDateAndStatusFillColor: UIColor(white: 0.0, alpha: 0.5),
mediaDateAndStatusFillColor: UIColor(white: 0.0, alpha: 0.3),
mediaDateAndStatusTextColor: UIColor(rgb: 0xffffff),
shareButtonFillColor: PresentationThemeVariableColor(withWallpaper: serviceBackgroundColor, withoutWallpaper: UIColor(rgb: 0x748391, alpha: 0.45)),
shareButtonStrokeColor: PresentationThemeVariableColor(withWallpaper: .clear, withoutWallpaper: .clear),
@ -802,7 +802,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio
infoPrimaryTextColor: UIColor(rgb: 0x000000),
infoLinkTextColor: UIColor(rgb: 0x004bad),
outgoingCheckColor: UIColor(rgb: 0xffffff),
mediaDateAndStatusFillColor: UIColor(rgb: 0x000000, alpha: 0.5),
mediaDateAndStatusFillColor: UIColor(rgb: 0x000000, alpha: 0.3),
mediaDateAndStatusTextColor: UIColor(rgb: 0xffffff),
shareButtonFillColor: PresentationThemeVariableColor(withWallpaper: serviceBackgroundColor, withoutWallpaper: UIColor(rgb: 0xffffff, alpha: 0.8)),
shareButtonStrokeColor: PresentationThemeVariableColor(withWallpaper: .clear, withoutWallpaper: UIColor(rgb: 0xe5e5ea)),

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Keyboard.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

@ -5749,16 +5749,23 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let currentAccountPeer = self.context.account.postbox.loadedPeerWithId(self.context.account.peerId)
|> map { peer in
return FoundPeer(peer: peer, subscribers: nil)
return SendAsPeer(peer: peer, subscribers: nil, isPremiumRequired: false)
}
if let peerId = self.chatLocation.peerId, [Namespaces.Peer.CloudChannel, Namespaces.Peer.CloudGroup].contains(peerId.namespace) {
self.sendAsPeersDisposable = (combineLatest(queue: Queue.mainQueue(), currentAccountPeer, self.context.account.postbox.peerView(id: peerId), self.context.engine.peers.sendAsAvailablePeers(peerId: peerId)))
.start(next: { [weak self] currentAccountPeer, peerView, peers in
self.sendAsPeersDisposable = (combineLatest(
queue: Queue.mainQueue(),
currentAccountPeer,
self.context.account.postbox.peerView(id: peerId),
self.context.engine.peers.sendAsAvailablePeers(peerId: peerId))
).start(next: { [weak self] currentAccountPeer, peerView, peers in
guard let strongSelf = self else {
return
}
var allPeers: [FoundPeer]?
let isPremium = strongSelf.presentationInterfaceState.isPremium
var allPeers: [SendAsPeer]?
if !peers.isEmpty {
if let channel = peerViewMainPeer(peerView) as? TelegramChannel, case .group = channel.info, channel.hasPermission(.canBeAnonymous) {
allPeers = peers
@ -5771,7 +5778,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
if !hasAnonymousPeer {
allPeers?.insert(FoundPeer(peer: channel, subscribers: 0), at: 0)
allPeers?.insert(SendAsPeer(peer: channel, subscribers: 0, isPremiumRequired: false), at: 0)
}
} else {
allPeers = peers.filter { $0.peer.id != peerViewMainPeer(peerView)?.id }
@ -5781,8 +5788,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if allPeers?.count == 1 {
allPeers = nil
}
var currentSendAsPeerId = strongSelf.presentationInterfaceState.currentSendAsPeerId
if let peerId = currentSendAsPeerId, let peer = allPeers?.first(where: { $0.peer.id == peerId }) {
if !isPremium && peer.isPremiumRequired {
currentSendAsPeerId = nil
}
} else {
currentSendAsPeerId = nil
}
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
return $0.updatedSendAsPeers(allPeers)
return $0.updatedSendAsPeers(allPeers).updatedCurrentSendAsPeerId(currentSendAsPeerId)
})
})
}
@ -8935,6 +8952,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
let isPremium = strongSelf.presentationInterfaceState.isPremium
let cleanInsets = layout.intrinsicInsets
let insets = layout.insets(options: .input)
let bottomInset = max(insets.bottom, cleanInsets.bottom) + 43.0
@ -8949,7 +8968,23 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var items: [ContextMenuItem] = []
items.append(.custom(ChatSendAsPeerTitleContextItem(text: strongSelf.presentationInterfaceState.strings.Conversation_SendMesageAs.uppercased()), false))
items.append(.custom(ChatSendAsPeerListContextItem(context: strongSelf.context, chatPeerId: peerId, peers: peers, selectedPeerId: myPeerId), false))
items.append(.custom(ChatSendAsPeerListContextItem(context: strongSelf.context, chatPeerId: peerId, peers: peers, selectedPeerId: myPeerId, isPremium: isPremium, presentToast: { [weak self] peer in
if let strongSelf = self {
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: strongSelf.presentationData.strings.Conversation_SendMesageAsPremiumInfo, action: strongSelf.presentationData.strings.EmojiInput_PremiumEmojiToast_Action), elevatedLayout: false, action: { [weak self] action in
guard let strongSelf = self else {
return true
}
if case .undo = action {
strongSelf.chatDisplayNode.dismissTextInput()
let controller = PremiumIntroScreen(context: strongSelf.context, source: .settings)
strongSelf.push(controller)
}
return true
}), in: .current)
}
}), false))
strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()

View File

@ -22,6 +22,7 @@ import LocalMediaResources
import WallpaperResources
import ChatMessageInteractiveMediaBadge
import ContextUI
import InvisibleInkDustNode
private struct FetchControls {
let fetch: (Bool) -> Void
@ -76,6 +77,7 @@ struct ChatMessageDateAndStatus {
}
private class ExtendedMediaOverlayNode: ASDisplayNode {
private let dustNode: MediaDustNode
private let buttonNode: HighlightTrackingButtonNode
private let blurNode: NavigationBackgroundNode
private let highlightedBackgroundNode: ASDisplayNode
@ -83,6 +85,8 @@ private class ExtendedMediaOverlayNode: ASDisplayNode {
private let textNode: ImmediateTextNode
override init() {
self.dustNode = MediaDustNode()
self.buttonNode = HighlightTrackingButtonNode()
self.buttonNode.clipsToBounds = true
self.buttonNode.cornerRadius = 16.0
@ -105,6 +109,7 @@ private class ExtendedMediaOverlayNode: ASDisplayNode {
self.cornerRadius = 16.0
self.isUserInteractionEnabled = false
self.addSubnode(self.dustNode)
self.addSubnode(self.buttonNode)
self.buttonNode.addSubnode(self.blurNode)
self.buttonNode.addSubnode(self.highlightedBackgroundNode)
@ -142,6 +147,9 @@ private class ExtendedMediaOverlayNode: ASDisplayNode {
let spacing: CGFloat = 2.0
let padding: CGFloat = 10.0
self.dustNode.frame = CGRect(origin: .zero, size: size)
self.dustNode.update(size: size, color: .white)
self.textNode.attributedText = NSAttributedString(string: text, font: Font.semibold(14.0), textColor: .white, paragraphAlignment: .center)
let textSize = self.textNode.updateLayout(size)
if let iconSize = self.iconNode.image?.size {
@ -150,8 +158,8 @@ private class ExtendedMediaOverlayNode: ASDisplayNode {
self.highlightedBackgroundNode.frame = CGRect(origin: .zero, size: contentSize)
self.blurNode.frame = self.highlightedBackgroundNode.frame
self.blurNode.update(size: self.blurNode.frame.size, transition: .immediate)
self.blurNode.updateColor(color: UIColor(rgb: 0x000000, alpha: 0.5), enableBlur: true, transition: .immediate)
self.blurNode.updateColor(color: UIColor(rgb: 0x000000, alpha: 0.3), enableBlur: true, transition: .immediate)
self.iconNode.frame = CGRect(origin: CGPoint(x: self.buttonNode.frame.minX + padding, y: self.buttonNode.frame.minY + floorToScreenPixels((contentSize.height - iconSize.height) / 2.0) + 1.0), size: iconSize)
self.textNode.frame = CGRect(origin: CGPoint(x: self.iconNode.frame.maxX + spacing, y: self.buttonNode.frame.minY + floorToScreenPixels((contentSize.height - textSize.height) / 2.0)), size: textSize)
}

View File

@ -808,7 +808,11 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol
case .text:
item.controllerInteraction.sendMessage(button.title)
case let .url(url):
item.controllerInteraction.openUrl(url, true, nil, nil)
var concealed = true
if url.hasPrefix("tg://") {
concealed = false
}
item.controllerInteraction.openUrl(url, concealed, nil, nil)
case .requestMap:
item.controllerInteraction.shareCurrentLocation()
case .requestPhone:

View File

@ -363,8 +363,13 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
}
override func transitionNode(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
if self.item?.message.id == messageId, let currentMedia = self.media, currentMedia.isSemanticallyEqual(to: media) {
return self.interactiveImageNode.transitionNode()
if self.item?.message.id == messageId, var currentMedia = self.media {
if let invoice = currentMedia as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia {
currentMedia = fullMedia
}
if currentMedia.isSemanticallyEqual(to: media) {
return self.interactiveImageNode.transitionNode()
}
}
return nil
}
@ -380,7 +385,13 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
override func updateHiddenMedia(_ media: [Media]?) -> Bool {
var mediaHidden = false
if let currentMedia = self.media, let media = media {
var currentMedia = self.media
if let invoice = currentMedia as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia {
currentMedia = fullMedia
}
if let currentMedia = currentMedia, let media = media {
for item in media {
if item.isSemanticallyEqual(to: currentMedia) {
mediaHidden = true

View File

@ -11,18 +11,23 @@ import ContextUI
import TelegramStringFormatting
import AvatarNode
import AccountContext
import UndoUI
final class ChatSendAsPeerListContextItem: ContextMenuCustomItem {
let context: AccountContext
let chatPeerId: PeerId
let peers: [FoundPeer]
let peers: [SendAsPeer]
let selectedPeerId: PeerId?
let isPremium: Bool
let presentToast: (EnginePeer) -> Void
init(context: AccountContext, chatPeerId: PeerId, peers: [FoundPeer], selectedPeerId: PeerId?) {
init(context: AccountContext, chatPeerId: PeerId, peers: [SendAsPeer], selectedPeerId: PeerId?, isPremium: Bool, presentToast: @escaping (EnginePeer) -> Void) {
self.context = context
self.chatPeerId = chatPeerId
self.peers = peers
self.selectedPeerId = selectedPeerId
self.isPremium = isPremium
self.presentToast = presentToast
}
func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
@ -99,9 +104,16 @@ private final class ChatSendAsPeerListContextItemNode: ASDisplayNode, ContextMen
}
}
let action = ContextMenuActionItem(text: EnginePeer(peer.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: isSelected ? extendedAvatarSize : avatarSize, signal: avatarSignal), action: { _, f in
let action = ContextMenuActionItem(text: EnginePeer(peer.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: isSelected ? extendedAvatarSize : avatarSize, signal: avatarSignal), textIcon: { theme in
return !item.isPremium && peer.isPremiumRequired ? generateTintedImage(image: UIImage(bundleImageName: "Notification/SecretLock"), color: theme.contextMenu.badgeInactiveFillColor) : nil
}, action: { _, f in
f(.default)
if !item.isPremium && peer.isPremiumRequired {
item.presentToast(EnginePeer(peer.peer))
return
}
if peer.peer.id != item.selectedPeerId {
let _ = item.context.engine.peers.updatePeerSendAsPeer(peerId: item.chatPeerId, sendAs: peer.peer.id).start()
}

View File

@ -174,79 +174,3 @@ public func canTranslateText(context: AccountContext, text: String, showTranslat
return (false, nil)
}
}
public struct TextTranslationResult: Equatable {
let text: String
let detectedLanguage: String?
}
public enum TextTranslationError {
case generic
}
private let userAgents: [String] = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36", // 13.5%
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36", // 6.6%
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0", // 6.4%
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0", // 6.2%
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36", // 5.2%
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Safari/537.36" // 4.8%
]
public func translateText(context: AccountContext, text: String, from: String?, to: String) -> Signal<TextTranslationResult, TextTranslationError> {
return Signal { subscriber in
var uri = "https://translate.goo";
uri += "gleapis.com/transl";
uri += "ate_a";
uri += "/singl";
uri += "e?client=gtx&sl=" + (from ?? "auto") + "&tl=" + to + "&dt=t" + "&ie=UTF-8&oe=UTF-8&otf=1&ssel=0&tsel=0&kc=7&dt=at&dt=bd&dt=ex&dt=ld&dt=md&dt=qca&dt=rw&dt=rm&dt=ss&q=";
uri += text.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)!
var request = URLRequest(url: URL(string: uri)!)
request.httpMethod = "GET"
request.setValue(userAgents[Int.random(in: 0 ..< userAgents.count)], forHTTPHeaderField: "User-Agent")
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: { data, response, error in
if let _ = error {
subscriber.putError(.generic)
} else if let data = data {
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? NSArray
if let json = json, json.count > 0 {
let array = json[0] as? NSArray ?? NSArray()
var result: String = ""
for i in 0 ..< array.count {
let blockText = array[i] as? NSArray
if let blockText = blockText, blockText.count > 0 {
let value = blockText[0] as? String
if let value = value, value != "null" {
result += value
}
}
}
let translationResult = TextTranslationResult(text: result, detectedLanguage: json[2] as? String)
var fromLang: String?
if let lang = translationResult.detectedLanguage {
fromLang = lang
} else if let lang = from {
fromLang = lang
}
if let fromLang = fromLang {
let _ = context.engine.messages.translate(text: text, fromLang: fromLang, toLang: to).start()
}
subscriber.putNext(translationResult)
subscriber.putCompletion()
} else {
subscriber.putError(.generic)
}
}
})
task.resume()
return ActionDisposable {
task.cancel()
}
}
}

View File

@ -98,15 +98,15 @@ private final class TranslateScreenComponent: CombinedComponent {
self.availableSpeakLanguages = supportedSpeakLanguages()
super.init()
self.translationDisposable.set((translateText(context: context, text: text, from: fromLanguage, to: toLanguage) |> deliverOnMainQueue).start(next: { [weak self] result in
self.translationDisposable.set((context.engine.messages.translate(text: text, fromLang: fromLanguage, toLang: toLanguage) |> deliverOnMainQueue).start(next: { [weak self] text in
guard let strongSelf = self else {
return
}
strongSelf.translatedText = result.text
if strongSelf.fromLanguage == nil {
strongSelf.fromLanguage = result.detectedLanguage
}
strongSelf.translatedText = text
// if strongSelf.fromLanguage == nil {
// strongSelf.fromLanguage = result.detectedLanguage
// }
strongSelf.updated(transition: .immediate)
}, error: { error in
@ -127,14 +127,14 @@ private final class TranslateScreenComponent: CombinedComponent {
self.translatedText = nil
self.updated(transition: .immediate)
self.translationDisposable.set((translateText(context: self.context, text: text, from: fromLanguage, to: toLanguage) |> deliverOnMainQueue).start(next: { [weak self] result in
self.translationDisposable.set((self.context.engine.messages.translate(text: text, fromLang: fromLanguage, toLang: toLanguage) |> deliverOnMainQueue).start(next: { [weak self] text in
guard let strongSelf = self else {
return
}
strongSelf.translatedText = result.text
if strongSelf.fromLanguage == nil {
strongSelf.fromLanguage = result.detectedLanguage
}
strongSelf.translatedText = text
// if strongSelf.fromLanguage == nil {
// strongSelf.fromLanguage = result.detectedLanguage
// }
strongSelf.updated(transition: .immediate)
}, error: { error in

View File

@ -22,7 +22,7 @@ public enum UndoOverlayContent {
case chatRemovedFromFolder(chatTitle: String, folderTitle: String)
case messagesUnpinned(title: String, text: String, undo: Bool, isHidden: Bool)
case setProximityAlert(title: String, text: String, cancelled: Bool)
case invitedToVoiceChat(context: AccountContext, peer: EnginePeer, text: String)
case invitedToVoiceChat(context: AccountContext, peer: EnginePeer, text: String, action: String?)
case linkCopied(text: String)
case banned(text: String)
case importedMessage(text: String)

View File

@ -515,7 +515,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
displayUndo = false
self.originalRemainingSeconds = 3
case let .invitedToVoiceChat(context, peer, text):
case let .invitedToVoiceChat(context, peer, text, action):
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 15.0))
self.iconNode = nil
self.iconCheckNode = nil
@ -530,8 +530,14 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.avatarNode?.setPeer(context: context, theme: presentationData.theme, peer: peer, overrideImage: nil, emptyColor: presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: true)
displayUndo = false
self.originalRemainingSeconds = 3
if let action = action {
displayUndo = true
undoText = action
self.originalRemainingSeconds = 5
} else {
displayUndo = false
self.originalRemainingSeconds = 3
}
case let .audioRate(slowdown, text):
self.avatarNode = nil
self.iconNode = nil