mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Merge commit 'aca9fbcae8683d30919592494f076d51ef98d756'
This commit is contained in:
commit
229623491b
@ -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**:";
|
||||
|
||||
@ -8014,6 +8015,8 @@ 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.";
|
||||
|
||||
"SetTimeoutFor.Minutes_1" = "Set for 1 minute";
|
||||
"SetTimeoutFor.Minutes_any" = "Set for %@ minutes";
|
||||
|
||||
|
@ -504,12 +504,10 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
let appleIdProvider = ASAuthorizationAppleIDProvider()
|
||||
let passwordProvider = ASAuthorizationPasswordProvider()
|
||||
let request = appleIdProvider.createRequest()
|
||||
request.user = number
|
||||
let passwordRequest = passwordProvider.createRequest()
|
||||
|
||||
let authorizationController = ASAuthorizationController(authorizationRequests: [request, passwordRequest])
|
||||
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
|
||||
authorizationController.delegate = strongSelf
|
||||
authorizationController.presentationContextProvider = strongSelf
|
||||
authorizationController.performRequests()
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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))!
|
||||
|
97
submodules/InvisibleInkDustNode/Sources/MediaDustNode.swift
Normal file
97
submodules/InvisibleInkDustNode/Sources/MediaDustNode.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Foundation
|
||||
|
||||
public struct ItemCollectionId: Comparable, Hashable {
|
||||
public struct ItemCollectionId: Comparable, Hashable, Codable {
|
||||
public typealias Namespace = Int32
|
||||
public typealias Id = Int64
|
||||
|
||||
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 2.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 18 KiB |
Binary file not shown.
@ -120,10 +120,9 @@ class EmojiHeaderComponent: Component {
|
||||
self.statusView.center = targetPosition
|
||||
|
||||
animateFrom.alpha = 0.0
|
||||
self.statusView.layer.animateScale(from: 0.24, to: 1.0, duration: 0.36, timingFunction: CAMediaTimingFunctionName.linear.rawValue)
|
||||
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.36, curve: .linear)
|
||||
transition.animatePositionWithKeyframes(layer: self.statusView.layer, keyframes: generateParabollicMotionKeyframes(from: sourcePosition, to: targetPosition, elevation: 50.0))
|
||||
self.statusView.layer.animateScale(from: 0.24, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.linear.rawValue)
|
||||
|
||||
self.statusView.layer.animatePosition(from: sourcePosition, to: targetPosition, duration: 0.55, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
Queue.mainQueue().after(0.55, {
|
||||
self.addSubview(self.statusView)
|
||||
|
@ -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 {
|
||||
|
@ -208,15 +208,10 @@ class PremiumStarComponent: Component {
|
||||
"rotate",
|
||||
"tapRotate"
|
||||
]
|
||||
// if #available(iOS 11.0, *) {
|
||||
// for key in keys {
|
||||
// node.removeAnimation(forKey: key, blendOutDuration: 0.1)
|
||||
// }
|
||||
// } else {
|
||||
for key in keys {
|
||||
node.removeAnimation(forKey: key)
|
||||
}
|
||||
// }
|
||||
|
||||
for key in keys {
|
||||
node.removeAnimation(forKey: key)
|
||||
}
|
||||
|
||||
switch gesture.state {
|
||||
case .began:
|
||||
@ -454,12 +449,10 @@ class PremiumStarComponent: Component {
|
||||
rightParticleSystem.birthRate = 60.0
|
||||
rightParticleSystem.particleLifeSpan = 4.0
|
||||
|
||||
// leftBottomParticleSystem.speedFactor = 2.0
|
||||
leftBottomParticleSystem.particleVelocity = 1.6
|
||||
leftBottomParticleSystem.birthRate = 24.0
|
||||
leftBottomParticleSystem.particleLifeSpan = 7.0
|
||||
|
||||
// rightBottomParticleSystem.speedFactor = 2.0
|
||||
rightBottomParticleSystem.particleVelocity = 1.6
|
||||
rightBottomParticleSystem.birthRate = 24.0
|
||||
rightBottomParticleSystem.particleLifeSpan = 7.0
|
||||
|
@ -1199,12 +1199,10 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
||||
emailController.signInWithApple = { [weak controller, weak emailController] in
|
||||
if #available(iOS 13.0, *) {
|
||||
let appleIdProvider = ASAuthorizationAppleIDProvider()
|
||||
let passwordProvider = ASAuthorizationPasswordProvider()
|
||||
let request = appleIdProvider.createRequest()
|
||||
request.requestedScopes = [.email]
|
||||
|
||||
let passwordRequest = passwordProvider.createRequest()
|
||||
|
||||
let authorizationController = ASAuthorizationController(authorizationRequests: [request, passwordRequest])
|
||||
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
|
||||
authorizationController.delegate = controller
|
||||
authorizationController.presentationContextProvider = controller
|
||||
authorizationController.performRequests()
|
||||
|
@ -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
|
||||
|
@ -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)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,9 @@ func cacheStickerPack(transaction: Transaction, info: StickerPackCollectionInfo,
|
||||
case .iconStatusEmoji:
|
||||
namespace = Namespaces.ItemCollection.CloudIconStatusEmoji
|
||||
id = 0
|
||||
case .premiumGifts:
|
||||
namespace = Namespaces.ItemCollection.CloudPremiumGifts
|
||||
id = 0
|
||||
case .id:
|
||||
break
|
||||
default:
|
||||
|
@ -299,6 +299,13 @@ public func customizeDefaultDarkPresentationTheme(theme: PresentationTheme, edit
|
||||
)
|
||||
}
|
||||
|
||||
public let defaultDarkWallpaperGradientColors: [UIColor] = [
|
||||
UIColor(rgb: 0x00b3dd),
|
||||
UIColor(rgb: 0x3b59f2),
|
||||
UIColor(rgb: 0x358be2),
|
||||
UIColor(rgb: 0xa434cf)
|
||||
]
|
||||
|
||||
public func makeDefaultDarkPresentationTheme(extendingThemeReference: PresentationThemeReference? = nil, preview: Bool) -> PresentationTheme {
|
||||
let rootNavigationBar = PresentationThemeRootNavigationBar(
|
||||
buttonColor: UIColor(rgb: 0xffffff),
|
||||
@ -309,7 +316,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
|
||||
accentTextColor: UIColor(rgb: 0xffffff),
|
||||
blurredBackgroundColor: UIColor(rgb: 0x1d1d1d, alpha: 0.9),
|
||||
opaqueBackgroundColor: UIColor(rgb: 0x1d1d1d).mixedWith(UIColor(rgb: 0x000000), alpha: 0.1),
|
||||
separatorColor: UIColor(rgb: 0x3d3d40),
|
||||
separatorColor: UIColor(rgb: 0x545458, alpha: 0.65),
|
||||
badgeBackgroundColor: UIColor(rgb: 0xffffff),
|
||||
badgeStrokeColor: UIColor(rgb: 0x1c1c1d),
|
||||
badgeTextColor: UIColor(rgb: 0x000000),
|
||||
@ -486,7 +493,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
|
||||
withWallpaper: PresentationThemeBubbleColorComponents(
|
||||
fill: [UIColor(rgb: 0x1D1D1D, alpha: incomingBubbleAlpha)],
|
||||
highlightedFill: UIColor(rgb: 0x353539),
|
||||
stroke: UIColor(rgb: 0x262628),
|
||||
stroke: .clear,
|
||||
shadow: nil,
|
||||
reactionInactiveBackground: UIColor(rgb: 0xffffff, alpha: 0.1),
|
||||
reactionInactiveForeground: UIColor(rgb: 0xffffff),
|
||||
@ -496,7 +503,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
|
||||
withoutWallpaper: PresentationThemeBubbleColorComponents(
|
||||
fill: [UIColor(rgb: 0x1D1D1D, alpha: incomingBubbleAlpha)],
|
||||
highlightedFill: UIColor(rgb: 0x353539),
|
||||
stroke: UIColor(rgb: 0x262628),
|
||||
stroke: .clear,
|
||||
shadow: nil,
|
||||
reactionInactiveBackground: UIColor(rgb: 0xffffff, alpha: 0.1),
|
||||
reactionInactiveForeground: UIColor(rgb: 0xffffff),
|
||||
@ -556,7 +563,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)),
|
||||
@ -643,8 +650,10 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
|
||||
badgeTextColor: UIColor(rgb: 0x000000)
|
||||
)
|
||||
|
||||
let defaultPatternWallpaper: TelegramWallpaper = defaultBuiltinWallpaper(data: .default, colors: defaultDarkWallpaperGradientColors.map(\.rgb), intensity: -35)
|
||||
|
||||
let chat = PresentationThemeChat(
|
||||
defaultWallpaper: .color(0x000000),
|
||||
defaultWallpaper: defaultPatternWallpaper,
|
||||
animateMessageColors: false,
|
||||
message: message,
|
||||
serviceMessage: serviceMessage,
|
||||
|
@ -791,7 +791,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),
|
||||
|
@ -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)),
|
||||
|
21
submodules/TelegramUI/Images.xcassets/Settings/Keypad.imageset/Contents.json
vendored
Normal file
21
submodules/TelegramUI/Images.xcassets/Settings/Keypad.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Settings/Keypad.imageset/Keyboard.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Settings/Keypad.imageset/Keyboard.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.7 KiB |
@ -5750,16 +5750,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
|
||||
@ -5772,7 +5779,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 }
|
||||
@ -5782,8 +5789,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)
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -8936,6 +8953,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
|
||||
@ -8950,7 +8969,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()
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user