mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various Improvements
This commit is contained in:
parent
888e5e1474
commit
ed7cf17233
@ -7168,12 +7168,6 @@ Sorry for the inconvenience.";
|
|||||||
"Conversation.LargeEmojiEnable" = "Enable Large Emoji";
|
"Conversation.LargeEmojiEnable" = "Enable Large Emoji";
|
||||||
"Conversation.LargeEmojiEnabled" = "Large emoji enabled.";
|
"Conversation.LargeEmojiEnabled" = "Large emoji enabled.";
|
||||||
|
|
||||||
"GroupInfo.QRCode.Info" = "Everyone on Telegram can scan this code to join this group.";
|
|
||||||
"ChannelInfo.QRCode.Info" = "Everyone on Telegram can scan this code to join this channel.";
|
|
||||||
"UserInfo.QRCode.InfoYou" = "Everyone on Telegram can scan this code to message you.";
|
|
||||||
"UserInfo.QRCode.InfoBot" = "Everyone on Telegram can scan this code to use this bot.";
|
|
||||||
"UserInfo.QRCode.InfoOther" = "Everyone on Telegram can scan this code to message %@.";
|
|
||||||
|
|
||||||
"PeerInfo.QRCode.Title" = "QR Code";
|
"PeerInfo.QRCode.Title" = "QR Code";
|
||||||
|
|
||||||
"ChatList.Archive" = "Archive";
|
"ChatList.Archive" = "Archive";
|
||||||
@ -7190,8 +7184,17 @@ Sorry for the inconvenience.";
|
|||||||
"Localization.ShowTranslate" = "Show Translate Button";
|
"Localization.ShowTranslate" = "Show Translate Button";
|
||||||
"Localization.ShowTranslateInfo" = "Show 'Translate' button in the message action menu.";
|
"Localization.ShowTranslateInfo" = "Show 'Translate' button in the message action menu.";
|
||||||
"Localization.DoNotTranslate" = "Do Not Translate";
|
"Localization.DoNotTranslate" = "Do Not Translate";
|
||||||
"Localization.DoNotTranslateInfo" = "Do not show 'Translate' button in the message action menu for this language";
|
"Localization.DoNotTranslateInfo" = "Do not show 'Translate' button in the message action menu for this language.";
|
||||||
"Localization.DoNotTranslateManyInfo" = "Do not show 'Translate' button in the message action menu for this languages";
|
"Localization.DoNotTranslateManyInfo" = "Do not show 'Translate' button in the message action menu for this languages.";
|
||||||
"Localization.InterfaceLanguage" = "Interface Language";
|
"Localization.InterfaceLanguage" = "Interface Language";
|
||||||
|
|
||||||
"DoNotTranslate.Title" = "Do Not Translate";
|
"DoNotTranslate.Title" = "Do Not Translate";
|
||||||
|
|
||||||
|
"Contacts.ScanQrCode" = "Scan QR Code";
|
||||||
|
"Contacts.QrCode.MyCode" = "My QR Code";
|
||||||
|
"Contacts.QrCode.NoCodeFound" = "No valid QR code found in the image. Please try again.";
|
||||||
|
|
||||||
|
"AccessDenied.QrCode" = "Telegram needs access to your photo library to scan QR codes.\n\nPlease go to Settings > Privacy > Photos and set Telegram to ON.";
|
||||||
|
"AccessDenied.QrCamera" = "Telegram needs access to your camera to scan QR codes.\n\nPlease go to Settings > Privacy > Camera and set Telegram to ON.";
|
||||||
|
|
||||||
|
"Share.ShareToInstagramStories" = "Share to Instagram Stories";
|
||||||
|
@ -630,6 +630,8 @@ public protocol SharedAccountContext: AnyObject {
|
|||||||
|
|
||||||
func makeRecentSessionsController(context: AccountContext, activeSessionsContext: ActiveSessionsContext) -> ViewController & RecentSessionsController
|
func makeRecentSessionsController(context: AccountContext, activeSessionsContext: ActiveSessionsContext) -> ViewController & RecentSessionsController
|
||||||
|
|
||||||
|
func makeChatQrCodeScreen(context: AccountContext, peer: Peer) -> ViewController
|
||||||
|
|
||||||
func navigateToCurrentCall()
|
func navigateToCurrentCall()
|
||||||
var hasOngoingCall: ValuePromise<Bool> { get }
|
var hasOngoingCall: ValuePromise<Bool> { get }
|
||||||
var immediateHasOngoingCall: Bool { get }
|
var immediateHasOngoingCall: Bool { get }
|
||||||
|
@ -30,6 +30,7 @@ swift_library(
|
|||||||
"//submodules/UndoUI:UndoUI",
|
"//submodules/UndoUI:UndoUI",
|
||||||
"//submodules/TextFormat:TextFormat",
|
"//submodules/TextFormat:TextFormat",
|
||||||
"//submodules/Markdown:Markdown",
|
"//submodules/Markdown:Markdown",
|
||||||
|
"//submodules/QrCodeUI:QrCodeUI",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -13,6 +13,7 @@ import PresentationDataUtils
|
|||||||
import TelegramCore
|
import TelegramCore
|
||||||
import Markdown
|
import Markdown
|
||||||
import DeviceAccess
|
import DeviceAccess
|
||||||
|
import QrCodeUI
|
||||||
|
|
||||||
private func transformedWithTheme(data: Data, theme: PresentationTheme) -> Data {
|
private func transformedWithTheme(data: Data, theme: PresentationTheme) -> Data {
|
||||||
return transformedWithColors(data: data, colors: [(UIColor(rgb: 0x333333), theme.list.itemPrimaryTextColor.mixedWith(.white, alpha: 0.2)), (UIColor(rgb: 0xFFFFFF), theme.list.plainBackgroundColor), (UIColor(rgb: 0x50A7EA), theme.list.itemAccentColor), (UIColor(rgb: 0x212121), theme.list.plainBackgroundColor)])
|
return transformedWithColors(data: data, colors: [(UIColor(rgb: 0x333333), theme.list.itemPrimaryTextColor.mixedWith(.white, alpha: 0.2)), (UIColor(rgb: 0xFFFFFF), theme.list.plainBackgroundColor), (UIColor(rgb: 0x50A7EA), theme.list.itemAccentColor), (UIColor(rgb: 0x212121), theme.list.plainBackgroundColor)])
|
||||||
@ -55,7 +56,7 @@ public final class AuthDataTransferSplashScreen: ViewController {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceAccess.authorizeAccess(to: .camera(.video), presentationData: strongSelf.presentationData, present: { c, a in
|
DeviceAccess.authorizeAccess(to: .camera(.qrCode), presentationData: strongSelf.presentationData, present: { c, a in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -70,7 +71,7 @@ public final class AuthDataTransferSplashScreen: ViewController {
|
|||||||
guard granted else {
|
guard granted else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
(strongSelf.navigationController as? NavigationController)?.replaceController(strongSelf, with: AuthTransferScanScreen(context: strongSelf.context, activeSessionsContext: strongSelf.activeSessionsContext), animated: true)
|
(strongSelf.navigationController as? NavigationController)?.replaceController(strongSelf, with: QrCodeScanScreen(context: strongSelf.context, subject: .authTransfer(activeSessionsContext: strongSelf.activeSessionsContext)), animated: true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ swift_library(
|
|||||||
"//submodules/StickerResources:StickerResources",
|
"//submodules/StickerResources:StickerResources",
|
||||||
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||||
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||||
|
"//submodules/QrCodeUI:QrCodeUI",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -18,6 +18,7 @@ import TelegramPermissionsUI
|
|||||||
import AppBundle
|
import AppBundle
|
||||||
import StickerResources
|
import StickerResources
|
||||||
import ContextUI
|
import ContextUI
|
||||||
|
import QrCodeUI
|
||||||
|
|
||||||
private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBarSearchContentNode) -> Bool {
|
private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBarSearchContentNode) -> Bool {
|
||||||
if searchNode.expansionProgress > 0.0 && searchNode.expansionProgress < 1.0 {
|
if searchNode.expansionProgress > 0.0 && searchNode.expansionProgress < 1.0 {
|
||||||
@ -375,6 +376,43 @@ public class ContactsController: ViewController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.contactsNode.openQrScan = { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
let context = strongSelf.context
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
DeviceAccess.authorizeAccess(to: .camera(.qrCode), presentationData: presentationData, present: { c, a in
|
||||||
|
c.presentationArguments = a
|
||||||
|
context.sharedContext.mainWindow?.present(c, on: .root)
|
||||||
|
}, openSettings: {
|
||||||
|
context.sharedContext.applicationBindings.openSettings()
|
||||||
|
}, { [weak self] granted in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard granted else {
|
||||||
|
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let controller = QrCodeScanScreen(context: strongSelf.context, subject: .peer)
|
||||||
|
controller.showMyCode = { [weak self, weak controller] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
let _ = (strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.context.account.peerId)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self, weak controller] peer in
|
||||||
|
if let strongSelf = self, let controller = controller {
|
||||||
|
controller.present(strongSelf.context.sharedContext.makeChatQrCodeScreen(context: strongSelf.context, peer: peer), in: .window(.root))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller, completion: {
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.contactsNode.contactListNode.openSortMenu = { [weak self] in
|
self.contactsNode.contactListNode.openSortMenu = { [weak self] in
|
||||||
self?.presentSortMenu()
|
self?.presentSortMenu()
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,7 @@ final class ContactsControllerNode: ASDisplayNode {
|
|||||||
var requestAddContact: ((String) -> Void)?
|
var requestAddContact: ((String) -> Void)?
|
||||||
var openPeopleNearby: (() -> Void)?
|
var openPeopleNearby: (() -> Void)?
|
||||||
var openInvite: (() -> Void)?
|
var openInvite: (() -> Void)?
|
||||||
|
var openQrScan: (() -> Void)?
|
||||||
|
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private var presentationDataDisposable: Disposable?
|
private var presentationDataDisposable: Disposable?
|
||||||
@ -70,8 +71,12 @@ final class ContactsControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
var addNearbyImpl: (() -> Void)?
|
var addNearbyImpl: (() -> Void)?
|
||||||
var inviteImpl: (() -> Void)?
|
var inviteImpl: (() -> Void)?
|
||||||
|
var qrScanImpl: (() -> Void)?
|
||||||
|
|
||||||
let options = [ContactListAdditionalOption(title: presentationData.strings.Contacts_AddPeopleNearby, icon: .generic(UIImage(bundleImageName: "Contact List/PeopleNearbyIcon")!), action: {
|
let options = [ContactListAdditionalOption(title: presentationData.strings.Contacts_AddPeopleNearby, icon: .generic(UIImage(bundleImageName: "Contact List/PeopleNearbyIcon")!), action: {
|
||||||
addNearbyImpl?()
|
addNearbyImpl?()
|
||||||
|
}), ContactListAdditionalOption(title: presentationData.strings.Contacts_ScanQrCode, icon: .generic(UIImage(bundleImageName: "Settings/QrIcon")!), action: {
|
||||||
|
qrScanImpl?()
|
||||||
}), ContactListAdditionalOption(title: presentationData.strings.Contacts_InviteFriends, icon: .generic(UIImage(bundleImageName: "Contact List/AddMemberIcon")!), action: {
|
}), ContactListAdditionalOption(title: presentationData.strings.Contacts_InviteFriends, icon: .generic(UIImage(bundleImageName: "Contact List/AddMemberIcon")!), action: {
|
||||||
inviteImpl?()
|
inviteImpl?()
|
||||||
})]
|
})]
|
||||||
@ -128,6 +133,12 @@ final class ContactsControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qrScanImpl = { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.openQrScan?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
contextAction = { [weak self] peer, node, gesture in
|
contextAction = { [weak self] peer, node, gesture in
|
||||||
self?.contextAction(peer: peer, node: node, gesture: gesture)
|
self?.contextAction(peer: peer, node: node, gesture: gesture)
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import AccountContext
|
|||||||
public enum DeviceAccessCameraSubject {
|
public enum DeviceAccessCameraSubject {
|
||||||
case video
|
case video
|
||||||
case videoCall
|
case videoCall
|
||||||
|
case qrCode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -30,6 +31,7 @@ public enum DeviceAccessMediaLibrarySubject {
|
|||||||
case send
|
case send
|
||||||
case save
|
case save
|
||||||
case wallpaper
|
case wallpaper
|
||||||
|
case qrCode
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum DeviceAccessLocationSubject {
|
public enum DeviceAccessLocationSubject {
|
||||||
@ -269,6 +271,8 @@ public final class DeviceAccess {
|
|||||||
text = presentationData.strings.AccessDenied_Camera
|
text = presentationData.strings.AccessDenied_Camera
|
||||||
case .videoCall:
|
case .videoCall:
|
||||||
text = presentationData.strings.AccessDenied_VideoCallCamera
|
text = presentationData.strings.AccessDenied_VideoCallCamera
|
||||||
|
case .qrCode:
|
||||||
|
text = presentationData.strings.AccessDenied_QrCamera
|
||||||
}
|
}
|
||||||
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||||
openSettings()
|
openSettings()
|
||||||
@ -289,6 +293,8 @@ public final class DeviceAccess {
|
|||||||
text = presentationData.strings.AccessDenied_Camera
|
text = presentationData.strings.AccessDenied_Camera
|
||||||
case .videoCall:
|
case .videoCall:
|
||||||
text = presentationData.strings.AccessDenied_VideoCallCamera
|
text = presentationData.strings.AccessDenied_VideoCallCamera
|
||||||
|
case .qrCode:
|
||||||
|
text = presentationData.strings.AccessDenied_QrCamera
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
completion(false)
|
completion(false)
|
||||||
@ -345,6 +351,8 @@ public final class DeviceAccess {
|
|||||||
text = presentationData.strings.AccessDenied_SaveMedia
|
text = presentationData.strings.AccessDenied_SaveMedia
|
||||||
case .wallpaper:
|
case .wallpaper:
|
||||||
text = presentationData.strings.AccessDenied_Wallpapers
|
text = presentationData.strings.AccessDenied_Wallpapers
|
||||||
|
case .qrCode:
|
||||||
|
text = presentationData.strings.AccessDenied_QrCode
|
||||||
}
|
}
|
||||||
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||||
openSettings()
|
openSettings()
|
||||||
|
@ -207,8 +207,11 @@ public final class TextNodeLayout: NSObject {
|
|||||||
if line.isRTL {
|
if line.isRTL {
|
||||||
hasRTL = true
|
hasRTL = true
|
||||||
}
|
}
|
||||||
spoilers.append(contentsOf: line.spoilers.map { ( $0.range, $0.frame.offsetBy(dx: line.frame.minX, dy: line.frame.minY)) })
|
|
||||||
spoilerWords.append(contentsOf: line.spoilerWords.map { ( $0.range, $0.frame.offsetBy(dx: line.frame.minX, dy: line.frame.minY)) })
|
let lineFrame = displayLineFrame(frame: line.frame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: size), cutout: cutout)
|
||||||
|
|
||||||
|
spoilers.append(contentsOf: line.spoilers.map { ( $0.range, $0.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)) })
|
||||||
|
spoilerWords.append(contentsOf: line.spoilerWords.map { ( $0.range, $0.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)) })
|
||||||
}
|
}
|
||||||
self.hasRTL = hasRTL
|
self.hasRTL = hasRTL
|
||||||
self.spoilers = spoilers
|
self.spoilers = spoilers
|
||||||
@ -1095,7 +1098,7 @@ public class TextNode: ASDisplayNode {
|
|||||||
addSpoilerWord(line: coreTextLine, ascent: ascent, descent: descent, startIndex: currentStartIndex, endIndex: endIndex, rightInset: truncated ? 12.0 : 0.0)
|
addSpoilerWord(line: coreTextLine, ascent: ascent, descent: descent, startIndex: currentStartIndex, endIndex: endIndex, rightInset: truncated ? 12.0 : 0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length - 1)
|
addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length)
|
||||||
} else if let _ = attributes[NSAttributedString.Key.strikethroughStyle] {
|
} else if let _ = attributes[NSAttributedString.Key.strikethroughStyle] {
|
||||||
let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil))
|
let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil))
|
||||||
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil))
|
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil))
|
||||||
@ -1176,7 +1179,7 @@ public class TextNode: ASDisplayNode {
|
|||||||
addSpoilerWord(line: coreTextLine, ascent: ascent, descent: descent, startIndex: currentStartIndex, endIndex: endIndex)
|
addSpoilerWord(line: coreTextLine, ascent: ascent, descent: descent, startIndex: currentStartIndex, endIndex: endIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length - 1)
|
addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length)
|
||||||
} else if let _ = attributes[NSAttributedString.Key.strikethroughStyle] {
|
} else if let _ = attributes[NSAttributedString.Key.strikethroughStyle] {
|
||||||
let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil))
|
let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil))
|
||||||
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil))
|
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil))
|
||||||
|
@ -540,7 +540,13 @@ public final class RecognizedTextSelectionNode: ASDisplayNode {
|
|||||||
return self.view
|
return self.view
|
||||||
}
|
}
|
||||||
if self.bounds.contains(point) {
|
if self.bounds.contains(point) {
|
||||||
return self.view
|
for recognition in self.recognitions {
|
||||||
|
let mappedRect = recognition.rect.convertTo(size: self.bounds.size)
|
||||||
|
if mappedRect.boundingFrame.insetBy(dx: -20.0, dy: -20.0).contains(point) {
|
||||||
|
return self.view
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -351,3 +351,33 @@ public func recognizedContent(postbox: Postbox, image: @escaping () -> UIImage?,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func recognizeQRCode(in image: UIImage?) -> Signal<String?, NoError> {
|
||||||
|
if #available(iOS 11.0, *) {
|
||||||
|
guard let cgImage = image?.cgImage else {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
return Signal { subscriber in
|
||||||
|
let barcodeRequest = VNDetectBarcodesRequest { request, error in
|
||||||
|
if let result = request.results?.first as? VNBarcodeObservation {
|
||||||
|
subscriber.putNext(result.payloadStringValue)
|
||||||
|
} else {
|
||||||
|
subscriber.putNext(nil)
|
||||||
|
}
|
||||||
|
subscriber.putCompletion()
|
||||||
|
}
|
||||||
|
barcodeRequest.preferBackgroundProcessing = true
|
||||||
|
|
||||||
|
let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
|
||||||
|
try? handler.perform([barcodeRequest])
|
||||||
|
|
||||||
|
return ActionDisposable {
|
||||||
|
if #available(iOS 13.0, *) {
|
||||||
|
barcodeRequest.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -201,8 +201,14 @@ public class InvisibleInkDustNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let textLength = CGFloat((textNode.cachedLayout?.attributedString?.string ?? "").count)
|
var spoilersLength: Int = 0
|
||||||
let timeToRead = min(45.0, ceil(max(4.0, textLength * 0.04)))
|
if let spoilers = textNode.cachedLayout?.spoilers {
|
||||||
|
for spoiler in spoilers {
|
||||||
|
spoilersLength += spoiler.0.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeToRead = min(45.0, ceil(max(4.0, Double(spoilersLength) * 0.04)))
|
||||||
Queue.mainQueue().after(timeToRead * UIView.animationDurationFactor()) {
|
Queue.mainQueue().after(timeToRead * UIView.animationDurationFactor()) {
|
||||||
self.isRevealed = false
|
self.isRevealed = false
|
||||||
|
|
||||||
|
@ -339,13 +339,14 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
|
|||||||
let hasTopStripe: Bool
|
let hasTopStripe: Bool
|
||||||
let hasTopGroupInset: Bool
|
let hasTopGroupInset: Bool
|
||||||
let noInsets: Bool
|
let noInsets: Bool
|
||||||
|
let noCorners: Bool
|
||||||
public let tag: ItemListItemTag?
|
public let tag: ItemListItemTag?
|
||||||
let header: ListViewItemHeader?
|
let header: ListViewItemHeader?
|
||||||
let shimmering: ItemListPeerItemShimmering?
|
let shimmering: ItemListPeerItemShimmering?
|
||||||
let displayDecorations: Bool
|
let displayDecorations: Bool
|
||||||
let disableInteractiveTransitionIfNecessary: Bool
|
let disableInteractiveTransitionIfNecessary: Bool
|
||||||
|
|
||||||
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: EnginePeer, height: ItemListPeerItemHeight = .peerList, aliasHandling: ItemListPeerItemAliasHandling = .standard, nameColor: ItemListPeerItemNameColor = .primary, nameStyle: ItemListPeerItemNameStyle = .distinctBold, presence: EnginePeer.Presence?, text: ItemListPeerItemText, label: ItemListPeerItemLabel, editing: ItemListPeerItemEditing, revealOptions: ItemListPeerItemRevealOptions? = nil, switchValue: ItemListPeerItemSwitch?, enabled: Bool, highlighted: Bool = false, selectable: Bool, sectionId: ItemListSectionId, action: (() -> Void)?, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, toggleUpdated: ((Bool) -> Void)? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, hasTopStripe: Bool = true, hasTopGroupInset: Bool = true, noInsets: Bool = false, tag: ItemListItemTag? = nil, header: ListViewItemHeader? = nil, shimmering: ItemListPeerItemShimmering? = nil, displayDecorations: Bool = true, disableInteractiveTransitionIfNecessary: Bool = false) {
|
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: EnginePeer, height: ItemListPeerItemHeight = .peerList, aliasHandling: ItemListPeerItemAliasHandling = .standard, nameColor: ItemListPeerItemNameColor = .primary, nameStyle: ItemListPeerItemNameStyle = .distinctBold, presence: EnginePeer.Presence?, text: ItemListPeerItemText, label: ItemListPeerItemLabel, editing: ItemListPeerItemEditing, revealOptions: ItemListPeerItemRevealOptions? = nil, switchValue: ItemListPeerItemSwitch?, enabled: Bool, highlighted: Bool = false, selectable: Bool, sectionId: ItemListSectionId, action: (() -> Void)?, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, toggleUpdated: ((Bool) -> Void)? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, hasTopStripe: Bool = true, hasTopGroupInset: Bool = true, noInsets: Bool = false, noCorners: Bool = false, tag: ItemListItemTag? = nil, header: ListViewItemHeader? = nil, shimmering: ItemListPeerItemShimmering? = nil, displayDecorations: Bool = true, disableInteractiveTransitionIfNecessary: Bool = false) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.dateTimeFormat = dateTimeFormat
|
self.dateTimeFormat = dateTimeFormat
|
||||||
self.nameDisplayOrder = nameDisplayOrder
|
self.nameDisplayOrder = nameDisplayOrder
|
||||||
@ -373,6 +374,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
|
|||||||
self.hasTopStripe = hasTopStripe
|
self.hasTopStripe = hasTopStripe
|
||||||
self.hasTopGroupInset = hasTopGroupInset
|
self.hasTopGroupInset = hasTopGroupInset
|
||||||
self.noInsets = noInsets
|
self.noInsets = noInsets
|
||||||
|
self.noCorners = noCorners
|
||||||
self.tag = tag
|
self.tag = tag
|
||||||
self.header = header
|
self.header = header
|
||||||
self.shimmering = shimmering
|
self.shimmering = shimmering
|
||||||
@ -999,7 +1001,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
|||||||
strongSelf.addSubnode(strongSelf.maskNode)
|
strongSelf.addSubnode(strongSelf.maskNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
let hasCorners = itemListHasRoundedBlockLayout(params) && !item.noInsets
|
let hasCorners = itemListHasRoundedBlockLayout(params) && !item.noCorners
|
||||||
var hasTopCorners = false
|
var hasTopCorners = false
|
||||||
var hasBottomCorners = false
|
var hasBottomCorners = false
|
||||||
switch neighbors.top {
|
switch neighbors.top {
|
||||||
|
@ -7,11 +7,11 @@ import TelegramPresentationData
|
|||||||
import DeviceAccess
|
import DeviceAccess
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
|
||||||
public func legacyWallpaperPicker(context: AccountContext, presentationData: PresentationData) -> Signal<(LegacyComponentsContext) -> TGMediaAssetsController, Void> {
|
public func legacyWallpaperPicker(context: AccountContext, presentationData: PresentationData, subject: DeviceAccessMediaLibrarySubject = .wallpaper) -> Signal<(LegacyComponentsContext) -> TGMediaAssetsController, Void> {
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
let intent = TGMediaAssetsControllerSetCustomWallpaperIntent
|
let intent = TGMediaAssetsControllerSetCustomWallpaperIntent
|
||||||
|
|
||||||
DeviceAccess.authorizeAccess(to: .mediaLibrary(.wallpaper), presentationData: presentationData, present: context.sharedContext.presentGlobalController, openSettings: context.sharedContext.applicationBindings.openSettings, { value in
|
DeviceAccess.authorizeAccess(to: .mediaLibrary(subject), presentationData: presentationData, present: context.sharedContext.presentGlobalController, openSettings: context.sharedContext.applicationBindings.openSettings, { value in
|
||||||
if !value {
|
if !value {
|
||||||
subscriber.putError(Void())
|
subscriber.putError(Void())
|
||||||
return
|
return
|
||||||
|
@ -22,6 +22,15 @@ swift_library(
|
|||||||
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||||
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||||
|
"//submodules/GlassButtonNode:GlassButtonNode",
|
||||||
|
"//submodules/TextFormat:TextFormat",
|
||||||
|
"//submodules/Markdown:Markdown",
|
||||||
|
"//submodules/UndoUI:UndoUI",
|
||||||
|
"//submodules/Camera:Camera",
|
||||||
|
"//submodules/LegacyUI:LegacyUI",
|
||||||
|
"//submodules/LegacyComponents:LegacyComponents",
|
||||||
|
"//submodules/LegacyMediaPickerUI:LegacyMediaPickerUI",
|
||||||
|
"//submodules/ImageContentAnalysis:ImageContentAnalysis",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
725
submodules/QrCodeUI/Sources/QrCodeScanScreen.swift
Normal file
725
submodules/QrCodeUI/Sources/QrCodeScanScreen.swift
Normal file
@ -0,0 +1,725 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import AccountContext
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import Display
|
||||||
|
import SwiftSignalKit
|
||||||
|
import Camera
|
||||||
|
import GlassButtonNode
|
||||||
|
import CoreImage
|
||||||
|
import AlertUI
|
||||||
|
import TelegramPresentationData
|
||||||
|
import TelegramCore
|
||||||
|
import UndoUI
|
||||||
|
import Markdown
|
||||||
|
import TextFormat
|
||||||
|
import LegacyUI
|
||||||
|
import LegacyComponents
|
||||||
|
import LegacyMediaPickerUI
|
||||||
|
import ImageContentAnalysis
|
||||||
|
import PresentationDataUtils
|
||||||
|
|
||||||
|
private func parseAuthTransferUrl(_ url: URL) -> Data? {
|
||||||
|
var tokenString: String?
|
||||||
|
if let query = url.query, let components = URLComponents(string: "/?" + query), let queryItems = components.queryItems {
|
||||||
|
for queryItem in queryItems {
|
||||||
|
if let value = queryItem.value {
|
||||||
|
if queryItem.name == "token", !value.isEmpty {
|
||||||
|
tokenString = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if var tokenString = tokenString {
|
||||||
|
tokenString = tokenString.replacingOccurrences(of: "-", with: "+")
|
||||||
|
tokenString = tokenString.replacingOccurrences(of: "_", with: "/")
|
||||||
|
while tokenString.count % 4 != 0 {
|
||||||
|
tokenString.append("=")
|
||||||
|
}
|
||||||
|
if let data = Data(base64Encoded: tokenString) {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func generateFrameImage() -> UIImage? {
|
||||||
|
return generateImage(CGSize(width: 64.0, height: 64.0), contextGenerator: { size, context in
|
||||||
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
|
context.clear(bounds)
|
||||||
|
context.setStrokeColor(UIColor.white.cgColor)
|
||||||
|
context.setLineWidth(4.0)
|
||||||
|
context.setLineCap(.round)
|
||||||
|
|
||||||
|
let path = CGMutablePath()
|
||||||
|
path.move(to: CGPoint(x: 2.0, y: 2.0 + 26.0))
|
||||||
|
path.addArc(tangent1End: CGPoint(x: 2.0, y: 2.0), tangent2End: CGPoint(x: 2.0 + 26.0, y: 2.0), radius: 6.0)
|
||||||
|
path.addLine(to: CGPoint(x: 2.0 + 26.0, y: 2.0))
|
||||||
|
context.addPath(path)
|
||||||
|
context.strokePath()
|
||||||
|
|
||||||
|
path.move(to: CGPoint(x: size.width - 2.0, y: 2.0 + 26.0))
|
||||||
|
path.addArc(tangent1End: CGPoint(x: size.width - 2.0, y: 2.0), tangent2End: CGPoint(x: 2.0 + 26.0, y: 2.0), radius: 6.0)
|
||||||
|
path.addLine(to: CGPoint(x: size.width - 2.0 - 26.0, y: 2.0))
|
||||||
|
context.addPath(path)
|
||||||
|
context.strokePath()
|
||||||
|
|
||||||
|
path.move(to: CGPoint(x: 2.0, y: size.height - 2.0 - 26.0))
|
||||||
|
path.addArc(tangent1End: CGPoint(x: 2.0, y: size.height - 2.0), tangent2End: CGPoint(x: 2.0 + 26.0, y: size.height - 2.0), radius: 6.0)
|
||||||
|
path.addLine(to: CGPoint(x: 2.0 + 26.0, y: size.height - 2.0))
|
||||||
|
context.addPath(path)
|
||||||
|
context.strokePath()
|
||||||
|
|
||||||
|
path.move(to: CGPoint(x: size.width - 2.0, y: size.height - 2.0 - 26.0))
|
||||||
|
path.addArc(tangent1End: CGPoint(x: size.width - 2.0, y: size.height - 2.0), tangent2End: CGPoint(x: 2.0 + 26.0, y: size.height - 2.0), radius: 6.0)
|
||||||
|
path.addLine(to: CGPoint(x: size.width - 2.0 - 26.0, y: size.height - 2.0))
|
||||||
|
context.addPath(path)
|
||||||
|
context.strokePath()
|
||||||
|
})?.stretchableImage(withLeftCapWidth: 32, topCapHeight: 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class QrCodeScanScreen: ViewController {
|
||||||
|
public enum Subject {
|
||||||
|
case authTransfer(activeSessionsContext: ActiveSessionsContext)
|
||||||
|
case peer
|
||||||
|
}
|
||||||
|
|
||||||
|
private let context: AccountContext
|
||||||
|
private let subject: QrCodeScanScreen.Subject
|
||||||
|
private var presentationData: PresentationData
|
||||||
|
|
||||||
|
private var codeDisposable: Disposable?
|
||||||
|
private var inForegroundDisposable: Disposable?
|
||||||
|
private let approveDisposable = MetaDisposable()
|
||||||
|
|
||||||
|
private var controllerNode: QrCodeScanScreenNode {
|
||||||
|
return self.displayNode as! QrCodeScanScreenNode
|
||||||
|
}
|
||||||
|
|
||||||
|
public var showMyCode: () -> Void = {}
|
||||||
|
|
||||||
|
private var codeResolved = false
|
||||||
|
|
||||||
|
public init(context: AccountContext, subject: QrCodeScanScreen.Subject) {
|
||||||
|
self.context = context
|
||||||
|
self.subject = subject
|
||||||
|
|
||||||
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
let navigationBarTheme = NavigationBarTheme(buttonColor: .white, disabledButtonColor: .white, primaryTextColor: .white, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear)
|
||||||
|
|
||||||
|
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Common_Back, close: self.presentationData.strings.Common_Close)))
|
||||||
|
|
||||||
|
self.statusBar.statusBarStyle = .White
|
||||||
|
|
||||||
|
self.navigationPresentation = .modalInLargeLayout
|
||||||
|
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||||
|
self.navigationBar?.intrinsicCanTransitionInline = false
|
||||||
|
|
||||||
|
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
||||||
|
|
||||||
|
self.inForegroundDisposable = (context.sharedContext.applicationBindings.applicationInForeground
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] inForeground in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
(strongSelf.displayNode as! QrCodeScanScreenNode).updateInForeground(inForeground)
|
||||||
|
})
|
||||||
|
|
||||||
|
if case .peer = subject {
|
||||||
|
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Contacts_QrCode_MyCode, style: .plain, target: self, action: #selector(self.myCodePressed))
|
||||||
|
} else {
|
||||||
|
#if DEBUG
|
||||||
|
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Test", style: .plain, target: self, action: #selector(self.testPressed))
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
required init(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.codeDisposable?.dispose()
|
||||||
|
self.inForegroundDisposable?.dispose()
|
||||||
|
self.approveDisposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func myCodePressed() {
|
||||||
|
self.showMyCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func testPressed() {
|
||||||
|
self.dismissWithSession(session: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func dismissWithSession(session: RecentAccountSession?) {
|
||||||
|
guard case let .authTransfer(activeSessionsContext) = self.subject else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let navigationController = navigationController as? NavigationController {
|
||||||
|
self.present(UndoOverlayController(presentationData: self.presentationData, content: .actionSucceeded(title: self.presentationData.strings.AuthSessions_AddedDeviceTitle, text: session?.appName ?? "Telegram for macOS", cancel: self.presentationData.strings.AuthSessions_AddedDeviceTerminate), elevatedLayout: false, animateInAsReplacement: false, action: { value in
|
||||||
|
if value == .undo, let session = session {
|
||||||
|
let _ = activeSessionsContext.remove(hash: session.hash).start()
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}), in: .window(.root))
|
||||||
|
|
||||||
|
var viewControllers = navigationController.viewControllers
|
||||||
|
viewControllers = viewControllers.filter { controller in
|
||||||
|
if controller is RecentSessionsController {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if controller === self {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
viewControllers.append(self.context.sharedContext.makeRecentSessionsController(context: self.context, activeSessionsContext: activeSessionsContext))
|
||||||
|
navigationController.setViewControllers(viewControllers, animated: true)
|
||||||
|
} else {
|
||||||
|
self.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func loadDisplayNode() {
|
||||||
|
self.displayNode = QrCodeScanScreenNode(context: self.context, presentationData: self.presentationData, controller: self, subject: self.subject)
|
||||||
|
|
||||||
|
self.displayNodeDidLoad()
|
||||||
|
|
||||||
|
self.codeDisposable = ((self.displayNode as! QrCodeScanScreenNode).focusedCode.get()
|
||||||
|
|> map { code -> String? in
|
||||||
|
return code?.message
|
||||||
|
}
|
||||||
|
|> distinctUntilChanged
|
||||||
|
|> mapToSignal { code -> Signal<String?, NoError> in
|
||||||
|
return .single(code)
|
||||||
|
|> delay(0.5, queue: Queue.mainQueue())
|
||||||
|
}).start(next: { [weak self] code in
|
||||||
|
guard let strongSelf = self, !strongSelf.codeResolved else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let code = code else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch strongSelf.subject {
|
||||||
|
case let .authTransfer(activeSessionsContext):
|
||||||
|
if let url = URL(string: code), let parsedToken = parseAuthTransferUrl(url) {
|
||||||
|
strongSelf.approveDisposable.set((approveAuthTransferToken(account: strongSelf.context.account, token: parsedToken, activeSessionsContext: activeSessionsContext)
|
||||||
|
|> deliverOnMainQueue).start(next: { session in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.controllerNode.codeWithError = nil
|
||||||
|
if case let .authTransfer(activeSessionsContext) = strongSelf.subject {
|
||||||
|
Queue.mainQueue().after(1.5, {
|
||||||
|
activeSessionsContext.loadMore()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
strongSelf.dismissWithSession(session: session)
|
||||||
|
}, error: { _ in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.controllerNode.codeWithError = code
|
||||||
|
strongSelf.controllerNode.updateFocusedRect(nil)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
case .peer:
|
||||||
|
if let _ = URL(string: code) {
|
||||||
|
strongSelf.controllerNode.resolveCode(code: code, completion: { [weak self] result in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.codeResolved = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.controllerNode.present = { [weak self] c in
|
||||||
|
self?.present(c, in: .window(.root))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
|
|
||||||
|
(self.displayNode as! QrCodeScanScreenNode).containerLayoutUpdated(layout: layout, navigationHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class QrCodeScanScreenNode: ViewControllerTracingNode, UIScrollViewDelegate {
|
||||||
|
private let context: AccountContext
|
||||||
|
private var presentationData: PresentationData
|
||||||
|
private weak var controller: QrCodeScanScreen?
|
||||||
|
private let subject: QrCodeScanScreen.Subject
|
||||||
|
|
||||||
|
private let previewNode: CameraPreviewNode
|
||||||
|
private let fadeNode: ASDisplayNode
|
||||||
|
private let topDimNode: ASDisplayNode
|
||||||
|
private let bottomDimNode: ASDisplayNode
|
||||||
|
private let leftDimNode: ASDisplayNode
|
||||||
|
private let rightDimNode: ASDisplayNode
|
||||||
|
private let centerDimNode: ASDisplayNode
|
||||||
|
private let frameNode: ASImageNode
|
||||||
|
private let galleryButtonNode: GlassButtonNode
|
||||||
|
private let torchButtonNode: GlassButtonNode
|
||||||
|
private let titleNode: ImmediateTextNode
|
||||||
|
private let textNode: ImmediateTextNode
|
||||||
|
private let errorTextNode: ImmediateTextNode
|
||||||
|
|
||||||
|
private let camera: Camera
|
||||||
|
private let codeDisposable = MetaDisposable()
|
||||||
|
private var torchDisposable: Disposable?
|
||||||
|
private let resolveDisposable = MetaDisposable()
|
||||||
|
|
||||||
|
fileprivate let focusedCode = ValuePromise<CameraCode?>(ignoreRepeated: true)
|
||||||
|
private var focusedRect: CGRect?
|
||||||
|
|
||||||
|
var present: (ViewController) -> Void = { _ in }
|
||||||
|
|
||||||
|
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||||
|
|
||||||
|
var codeWithError: String? {
|
||||||
|
didSet {
|
||||||
|
if self.codeWithError != oldValue {
|
||||||
|
if self.codeWithError != nil {
|
||||||
|
self.errorTextNode.isHidden = false
|
||||||
|
} else {
|
||||||
|
self.errorTextNode.isHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var highlightViews: [UIVisualEffectView] = []
|
||||||
|
|
||||||
|
init(context: AccountContext, presentationData: PresentationData, controller: QrCodeScanScreen, subject: QrCodeScanScreen.Subject) {
|
||||||
|
self.context = context
|
||||||
|
self.presentationData = presentationData
|
||||||
|
self.controller = controller
|
||||||
|
self.subject = subject
|
||||||
|
|
||||||
|
self.previewNode = CameraPreviewNode()
|
||||||
|
self.previewNode.backgroundColor = .black
|
||||||
|
|
||||||
|
self.fadeNode = ASDisplayNode()
|
||||||
|
self.fadeNode.alpha = 0.0
|
||||||
|
self.fadeNode.backgroundColor = .black
|
||||||
|
|
||||||
|
self.topDimNode = ASDisplayNode()
|
||||||
|
self.topDimNode.alpha = 0.625
|
||||||
|
self.topDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.8)
|
||||||
|
|
||||||
|
self.bottomDimNode = ASDisplayNode()
|
||||||
|
self.bottomDimNode.alpha = 0.625
|
||||||
|
self.bottomDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.8)
|
||||||
|
|
||||||
|
self.leftDimNode = ASDisplayNode()
|
||||||
|
self.leftDimNode.alpha = 0.625
|
||||||
|
self.leftDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.8)
|
||||||
|
|
||||||
|
self.rightDimNode = ASDisplayNode()
|
||||||
|
self.rightDimNode.alpha = 0.625
|
||||||
|
self.rightDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.8)
|
||||||
|
|
||||||
|
self.centerDimNode = ASDisplayNode()
|
||||||
|
self.centerDimNode.alpha = 0.0
|
||||||
|
self.centerDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.8)
|
||||||
|
|
||||||
|
self.frameNode = ASImageNode()
|
||||||
|
self.frameNode.image = generateFrameImage()
|
||||||
|
|
||||||
|
self.galleryButtonNode = GlassButtonNode(icon: UIImage(bundleImageName: "Wallet/CameraGalleryIcon")!, label: nil)
|
||||||
|
self.torchButtonNode = GlassButtonNode(icon: UIImage(bundleImageName: "Wallet/CameraFlashIcon")!, label: nil)
|
||||||
|
|
||||||
|
let title: String
|
||||||
|
var text: String
|
||||||
|
switch subject {
|
||||||
|
case .authTransfer:
|
||||||
|
title = presentationData.strings.AuthSessions_AddDevice_ScanTitle
|
||||||
|
text = presentationData.strings.AuthSessions_AddDevice_ScanInstallInfo
|
||||||
|
case .peer:
|
||||||
|
title = ""
|
||||||
|
text = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
self.titleNode = ImmediateTextNode()
|
||||||
|
self.titleNode.displaysAsynchronously = false
|
||||||
|
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(32.0), textColor: .white)
|
||||||
|
self.titleNode.maximumNumberOfLines = 0
|
||||||
|
self.titleNode.textAlignment = .center
|
||||||
|
|
||||||
|
let textFont = Font.regular(17.0)
|
||||||
|
let boldFont = Font.bold(17.0)
|
||||||
|
|
||||||
|
text = text.replacingOccurrences(of: " [", with: " [").replacingOccurrences(of: ") ", with: ") ")
|
||||||
|
|
||||||
|
let attributedText = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: .white), bold: MarkdownAttributeSet(font: boldFont, textColor: .white), link: MarkdownAttributeSet(font: boldFont, textColor: .white), linkAttribute: { contents in
|
||||||
|
return (TelegramTextAttributes.URL, contents)
|
||||||
|
})))
|
||||||
|
|
||||||
|
self.textNode = ImmediateTextNode()
|
||||||
|
self.textNode.displaysAsynchronously = false
|
||||||
|
self.textNode.attributedText = attributedText
|
||||||
|
self.textNode.maximumNumberOfLines = 0
|
||||||
|
self.textNode.textAlignment = .center
|
||||||
|
self.textNode.lineSpacing = 0.5
|
||||||
|
|
||||||
|
self.errorTextNode = ImmediateTextNode()
|
||||||
|
self.errorTextNode.displaysAsynchronously = false
|
||||||
|
self.errorTextNode.attributedText = NSAttributedString(string: presentationData.strings.AuthSessions_AddDevice_InvalidQRCode, font: Font.medium(16.0), textColor: .white)
|
||||||
|
self.errorTextNode.maximumNumberOfLines = 0
|
||||||
|
self.errorTextNode.textAlignment = .center
|
||||||
|
self.errorTextNode.isHidden = true
|
||||||
|
|
||||||
|
self.camera = Camera(configuration: .init(preset: .hd1920x1080, position: .back, audio: false))
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||||
|
|
||||||
|
self.torchDisposable = (self.camera.hasTorch
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] hasTorch in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.torchButtonNode.isHidden = !hasTorch
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.addSubnode(self.previewNode)
|
||||||
|
self.addSubnode(self.fadeNode)
|
||||||
|
self.addSubnode(self.topDimNode)
|
||||||
|
self.addSubnode(self.bottomDimNode)
|
||||||
|
self.addSubnode(self.leftDimNode)
|
||||||
|
self.addSubnode(self.rightDimNode)
|
||||||
|
self.addSubnode(self.centerDimNode)
|
||||||
|
self.addSubnode(self.frameNode)
|
||||||
|
if case .peer = subject {
|
||||||
|
self.addSubnode(self.galleryButtonNode)
|
||||||
|
}
|
||||||
|
self.addSubnode(self.torchButtonNode)
|
||||||
|
self.addSubnode(self.titleNode)
|
||||||
|
self.addSubnode(self.textNode)
|
||||||
|
self.addSubnode(self.errorTextNode)
|
||||||
|
|
||||||
|
self.galleryButtonNode.addTarget(self, action: #selector(self.galleryPressed), forControlEvents: .touchUpInside)
|
||||||
|
self.torchButtonNode.addTarget(self, action: #selector(self.torchPressed), forControlEvents: .touchUpInside)
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.codeDisposable.dispose()
|
||||||
|
self.torchDisposable?.dispose()
|
||||||
|
self.resolveDisposable.dispose()
|
||||||
|
self.camera.stopCapture(invalidate: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func updateInForeground(_ inForeground: Bool) {
|
||||||
|
if !inForeground {
|
||||||
|
self.camera.stopCapture(invalidate: false)
|
||||||
|
} else {
|
||||||
|
self.camera.startCapture()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
|
||||||
|
self.camera.attachPreviewNode(self.previewNode)
|
||||||
|
self.camera.startCapture()
|
||||||
|
|
||||||
|
let throttledSignal = self.camera.detectedCodes
|
||||||
|
|> mapToThrottled { next -> Signal<[CameraCode], NoError> in
|
||||||
|
return .single(next) |> then(.complete() |> delay(0.3, queue: Queue.concurrentDefaultQueue()))
|
||||||
|
}
|
||||||
|
|
||||||
|
self.codeDisposable.set((throttledSignal
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] codes in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let filteredCodes: [CameraCode]
|
||||||
|
switch strongSelf.subject {
|
||||||
|
case .authTransfer:
|
||||||
|
filteredCodes = codes.filter { $0.message.hasPrefix("tg://") }
|
||||||
|
case .peer:
|
||||||
|
filteredCodes = codes.filter { $0.message.hasPrefix("https://t.me/") || $0.message.hasPrefix("t.me/") }
|
||||||
|
}
|
||||||
|
if let code = filteredCodes.first, CGRect(x: 0.3, y: 0.3, width: 0.4, height: 0.4).contains(code.boundingBox.center) {
|
||||||
|
if strongSelf.codeWithError != code.message {
|
||||||
|
strongSelf.codeWithError = nil
|
||||||
|
}
|
||||||
|
if strongSelf.codeWithError == code.message {
|
||||||
|
strongSelf.focusedCode.set(nil)
|
||||||
|
strongSelf.updateFocusedRect(nil)
|
||||||
|
} else {
|
||||||
|
strongSelf.focusedCode.set(code)
|
||||||
|
strongSelf.updateFocusedRect(code.boundingBox)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
strongSelf.codeWithError = nil
|
||||||
|
strongSelf.focusedCode.set(nil)
|
||||||
|
strongSelf.updateFocusedRect(nil)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:)))
|
||||||
|
recognizer.tapActionAtPoint = { _ in
|
||||||
|
return .waitForSingleTap
|
||||||
|
}
|
||||||
|
self.textNode.view.addGestureRecognizer(recognizer)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||||
|
switch recognizer.state {
|
||||||
|
case .ended:
|
||||||
|
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
|
||||||
|
switch gesture {
|
||||||
|
case .tap:
|
||||||
|
if let (_, attributes) = self.textNode.attributesAtPoint(location) {
|
||||||
|
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||||
|
switch url {
|
||||||
|
case "desktop":
|
||||||
|
self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: "https://getdesktop.telegram.org", forceExternal: true, presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
|
||||||
|
case "web":
|
||||||
|
self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: "https://web.telegram.org", forceExternal: true, presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateFocusedRect(_ rect: CGRect?) {
|
||||||
|
self.focusedRect = rect
|
||||||
|
if let (layout, navigationHeight) = self.validLayout {
|
||||||
|
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
self.validLayout = (layout, navigationHeight)
|
||||||
|
|
||||||
|
let sideInset: CGFloat = 66.0
|
||||||
|
let titleSpacing: CGFloat = 48.0
|
||||||
|
let bounds = CGRect(origin: CGPoint(), size: layout.size)
|
||||||
|
|
||||||
|
if case .tablet = layout.deviceMetrics.type {
|
||||||
|
if UIDevice.current.orientation == .landscapeLeft {
|
||||||
|
self.previewNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||||
|
} else if UIDevice.current.orientation == .landscapeRight {
|
||||||
|
self.previewNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||||
|
} else {
|
||||||
|
self.previewNode.transform = CATransform3DIdentity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transition.updateFrame(node: self.previewNode, frame: bounds)
|
||||||
|
transition.updateFrame(node: self.fadeNode, frame: bounds)
|
||||||
|
|
||||||
|
let frameSide = max(240.0, layout.size.width - sideInset * 2.0)
|
||||||
|
let dimHeight = ceil((layout.size.height - frameSide) / 2.0)
|
||||||
|
let dimInset = (layout.size.width - frameSide) / 2.0
|
||||||
|
|
||||||
|
let dimAlpha: CGFloat
|
||||||
|
let dimRect: CGRect
|
||||||
|
let controlsAlpha: CGFloat
|
||||||
|
let centerDimAlpha: CGFloat = 0.0
|
||||||
|
let frameAlpha: CGFloat = 1.0
|
||||||
|
if let focusedRect = self.focusedRect {
|
||||||
|
controlsAlpha = 0.0
|
||||||
|
dimAlpha = 1.0
|
||||||
|
let side = max(bounds.width * focusedRect.width, bounds.height * focusedRect.height) * 0.6
|
||||||
|
let center = CGPoint(x: (1.0 - focusedRect.center.y) * bounds.width, y: focusedRect.center.x * bounds.height)
|
||||||
|
dimRect = CGRect(x: center.x - side / 2.0, y: center.y - side / 2.0, width: side, height: side)
|
||||||
|
} else {
|
||||||
|
controlsAlpha = 1.0
|
||||||
|
dimAlpha = 0.625
|
||||||
|
dimRect = CGRect(x: dimInset, y: dimHeight, width: layout.size.width - dimInset * 2.0, height: layout.size.height - dimHeight * 2.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.updateAlpha(node: self.topDimNode, alpha: dimAlpha)
|
||||||
|
transition.updateAlpha(node: self.bottomDimNode, alpha: dimAlpha)
|
||||||
|
transition.updateAlpha(node: self.leftDimNode, alpha: dimAlpha)
|
||||||
|
transition.updateAlpha(node: self.rightDimNode, alpha: dimAlpha)
|
||||||
|
transition.updateAlpha(node: self.centerDimNode, alpha: centerDimAlpha)
|
||||||
|
transition.updateAlpha(node: self.frameNode, alpha: frameAlpha)
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.topDimNode, frame: CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: dimRect.minY))
|
||||||
|
transition.updateFrame(node: self.bottomDimNode, frame: CGRect(x: 0.0, y: dimRect.maxY, width: layout.size.width, height: max(0.0, layout.size.height - dimRect.maxY)))
|
||||||
|
transition.updateFrame(node: self.leftDimNode, frame: CGRect(x: 0.0, y: dimRect.minY, width: max(0.0, dimRect.minX), height: dimRect.height))
|
||||||
|
transition.updateFrame(node: self.rightDimNode, frame: CGRect(x: dimRect.maxX, y: dimRect.minY, width: max(0.0, layout.size.width - dimRect.maxX), height: dimRect.height))
|
||||||
|
transition.updateFrame(node: self.frameNode, frame: dimRect.insetBy(dx: -2.0, dy: -2.0))
|
||||||
|
transition.updateFrame(node: self.centerDimNode, frame: dimRect)
|
||||||
|
|
||||||
|
let buttonSize = CGSize(width: 72.0, height: 72.0)
|
||||||
|
var torchFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - buttonSize.width) / 2.0), y: dimHeight + frameSide + 98.0), size: buttonSize)
|
||||||
|
let updatedTorchY = min(torchFrame.minY, layout.size.height - torchFrame.height - 10.0)
|
||||||
|
let additionalTorchOffset: CGFloat = updatedTorchY - torchFrame.minY
|
||||||
|
torchFrame.origin.y = updatedTorchY
|
||||||
|
|
||||||
|
var galleryFrame = torchFrame
|
||||||
|
if case .peer = self.subject {
|
||||||
|
galleryFrame.origin.x -= buttonSize.width
|
||||||
|
torchFrame.origin.x += buttonSize.width
|
||||||
|
}
|
||||||
|
transition.updateFrame(node: self.galleryButtonNode, frame: galleryFrame)
|
||||||
|
transition.updateFrame(node: self.torchButtonNode, frame: torchFrame)
|
||||||
|
|
||||||
|
transition.updateAlpha(node: self.textNode, alpha: controlsAlpha)
|
||||||
|
transition.updateAlpha(node: self.errorTextNode, alpha: controlsAlpha)
|
||||||
|
transition.updateAlpha(node: self.galleryButtonNode, alpha: controlsAlpha)
|
||||||
|
transition.updateAlpha(node: self.torchButtonNode, alpha: controlsAlpha)
|
||||||
|
for view in self.highlightViews {
|
||||||
|
transition.updateAlpha(layer: view.layer, alpha: controlsAlpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - 16.0, height: layout.size.height))
|
||||||
|
let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - 16.0, height: layout.size.height))
|
||||||
|
let errorTextSize = self.errorTextNode.updateLayout(CGSize(width: layout.size.width - 16.0, height: layout.size.height))
|
||||||
|
var textFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: max(dimHeight - textSize.height - titleSpacing, navigationHeight + floorToScreenPixels((dimHeight - navigationHeight - textSize.height) / 2.0) + 5.0)), size: textSize)
|
||||||
|
let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: textFrame.minY - 18.0 - titleSize.height), size: titleSize)
|
||||||
|
if titleFrame.minY < navigationHeight {
|
||||||
|
transition.updateAlpha(node: self.titleNode, alpha: 0.0)
|
||||||
|
textFrame = textFrame.offsetBy(dx: 0.0, dy: -5.0)
|
||||||
|
} else {
|
||||||
|
transition.updateAlpha(node: self.titleNode, alpha: controlsAlpha)
|
||||||
|
}
|
||||||
|
var errorTextFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - errorTextSize.width) / 2.0), y: dimHeight + frameSide + 48.0), size: errorTextSize)
|
||||||
|
errorTextFrame.origin.y += floor(additionalTorchOffset / 2.0)
|
||||||
|
|
||||||
|
transition.updateFrameAdditive(node: self.titleNode, frame: titleFrame)
|
||||||
|
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
|
||||||
|
transition.updateFrameAdditive(node: self.errorTextNode, frame: errorTextFrame)
|
||||||
|
|
||||||
|
if self.highlightViews.isEmpty {
|
||||||
|
let urlAttributesAndRects = self.textNode.cachedLayout?.allAttributeRects(name: "UrlAttributeT") ?? []
|
||||||
|
|
||||||
|
for (_, rect) in urlAttributesAndRects {
|
||||||
|
let view = UIVisualEffectView(effect: UIBlurEffect(style: .light))
|
||||||
|
view.clipsToBounds = true
|
||||||
|
view.layer.cornerRadius = 5.0
|
||||||
|
view.frame = rect.offsetBy(dx: self.textNode.frame.minX, dy: self.textNode.frame.minY).insetBy(dx: -4.0, dy: -2.0)
|
||||||
|
self.view.insertSubview(view, belowSubview: self.textNode.view)
|
||||||
|
self.highlightViews.append(view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func galleryPressed() {
|
||||||
|
let context = self.context
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
let presentError = { [weak self] in
|
||||||
|
let alertController = textAlertController(context: context, title: nil, text: presentationData.strings.Contacts_QrCode_NoCodeFound, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
|
||||||
|
self?.present(alertController)
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = legacyWallpaperPicker(context: context, presentationData: presentationData, subject: .qrCode).start(next: { [weak self] generator in
|
||||||
|
let legacyController = LegacyController(presentation: .modal(animateIn: true), theme: presentationData.theme)
|
||||||
|
legacyController.statusBar.statusBarStyle = presentationData.theme.rootController.statusBarStyle.style
|
||||||
|
|
||||||
|
let controller = generator(legacyController.context)
|
||||||
|
legacyController.bind(controller: controller)
|
||||||
|
legacyController.deferScreenEdgeGestures = [.top]
|
||||||
|
controller.selectionBlock = { [weak legacyController] asset, _ in
|
||||||
|
if let asset = asset {
|
||||||
|
TGMediaAssetImageSignals.image(for: asset, imageType: TGMediaAssetImageTypeScreen, size: CGSize(width: 1280.0, height: 1280.0)).start(next: { image in
|
||||||
|
if let image = image as? UIImage {
|
||||||
|
let _ = (recognizeQRCode(in: image)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
|
if let result = result, let strongSelf = self {
|
||||||
|
strongSelf.resolveCode(code: result, completion: { result in
|
||||||
|
if result {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
presentError()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
presentError()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
presentError()
|
||||||
|
}
|
||||||
|
}, error: { _ in
|
||||||
|
presentError()
|
||||||
|
}, completed: {
|
||||||
|
})
|
||||||
|
|
||||||
|
legacyController?.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
controller.dismissalBlock = { [weak legacyController] in
|
||||||
|
if let legacyController = legacyController {
|
||||||
|
legacyController.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self?.present(legacyController)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func torchPressed() {
|
||||||
|
self.torchButtonNode.isSelected = !self.torchButtonNode.isSelected
|
||||||
|
self.camera.setTorchActive(self.torchButtonNode.isSelected)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func resolveCode(code: String, completion: @escaping (Bool) -> Void) {
|
||||||
|
self.resolveDisposable.set((self.context.sharedContext.resolveUrl(context: self.context, peerId: nil, url: code, skipUrlAuth: false)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
|
if let strongSelf = self {
|
||||||
|
completion(strongSelf.openResolved(result))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func openResolved(_ result: ResolvedUrl) -> Bool {
|
||||||
|
switch result {
|
||||||
|
case .peer, .stickerPack, .join, .wallpaper, .theme:
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let navigationController = self.controller?.navigationController as? NavigationController else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
self.context.sharedContext.openResolvedUrl(result, context: self.context, urlContext: .generic, navigationController: navigationController, openPeer: { [weak self] peerId, navigation in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: nil, keepStack: .always, peekData: nil, completion: { [weak navigationController] _ in
|
||||||
|
if let navigationController = navigationController {
|
||||||
|
var viewControllers = navigationController.viewControllers
|
||||||
|
viewControllers = viewControllers.filter { controller in
|
||||||
|
if controller is QrCodeScanScreen {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
navigationController.setViewControllers(viewControllers, animated: false)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}, sendFile: nil,
|
||||||
|
sendSticker: { _, _, _ in
|
||||||
|
return false
|
||||||
|
}, requestMessageActionUrlAuth: nil,
|
||||||
|
joinVoiceChat: { peerId, invite, call in
|
||||||
|
}, present: { [weak self] c, a in
|
||||||
|
self?.controller?.present(c, in: .window(.root), with: a)
|
||||||
|
}, dismissInput: { [weak self] in
|
||||||
|
self?.view.endEditing(true)
|
||||||
|
}, contentContext: nil)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -230,24 +230,12 @@ public final class QrCodeScreen: ViewController {
|
|||||||
let title: String
|
let title: String
|
||||||
let text: String
|
let text: String
|
||||||
switch subject {
|
switch subject {
|
||||||
case let .peer(peer):
|
|
||||||
title = self.presentationData.strings.PeerInfo_QRCode_Title
|
|
||||||
if case let .user(user) = peer {
|
|
||||||
if user.id == context.account.peerId {
|
|
||||||
text = self.presentationData.strings.UserInfo_QRCode_InfoYou
|
|
||||||
} else if user.botInfo != nil {
|
|
||||||
text = self.presentationData.strings.UserInfo_QRCode_InfoBot
|
|
||||||
} else {
|
|
||||||
text = self.presentationData.strings.UserInfo_QRCode_InfoOther(peer.compactDisplayTitle).string
|
|
||||||
}
|
|
||||||
} else if case let .channel(channel) = peer, case .broadcast = channel.info {
|
|
||||||
text = self.presentationData.strings.GroupInfo_QRCode_Info
|
|
||||||
} else {
|
|
||||||
text = self.presentationData.strings.ChannelInfo_QRCode_Info
|
|
||||||
}
|
|
||||||
case let .invite(_, isGroup):
|
case let .invite(_, isGroup):
|
||||||
title = self.presentationData.strings.InviteLink_QRCode_Title
|
title = self.presentationData.strings.InviteLink_QRCode_Title
|
||||||
text = isGroup ? self.presentationData.strings.InviteLink_QRCode_Info : self.presentationData.strings.InviteLink_QRCode_InfoChannel
|
text = isGroup ? self.presentationData.strings.InviteLink_QRCode_Info : self.presentationData.strings.InviteLink_QRCode_InfoChannel
|
||||||
|
default:
|
||||||
|
title = ""
|
||||||
|
text = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
self.titleNode = ASTextNode()
|
self.titleNode = ASTextNode()
|
||||||
|
@ -93,6 +93,7 @@ swift_library(
|
|||||||
"//submodules/WallpaperBackgroundNode:WallpaperBackgroundNode",
|
"//submodules/WallpaperBackgroundNode:WallpaperBackgroundNode",
|
||||||
"//submodules/WebPBinding:WebPBinding",
|
"//submodules/WebPBinding:WebPBinding",
|
||||||
"//submodules/Translate:Translate",
|
"//submodules/Translate:Translate",
|
||||||
|
"//submodules/QrCodeUI:QrCodeUI",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -12,6 +12,7 @@ import AccountContext
|
|||||||
import AuthTransferUI
|
import AuthTransferUI
|
||||||
import ItemListPeerActionItem
|
import ItemListPeerActionItem
|
||||||
import DeviceAccess
|
import DeviceAccess
|
||||||
|
import QrCodeUI
|
||||||
|
|
||||||
private final class RecentSessionsControllerArguments {
|
private final class RecentSessionsControllerArguments {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
@ -749,7 +750,7 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
|
|||||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
}, addDevice: {
|
}, addDevice: {
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
DeviceAccess.authorizeAccess(to: .camera(.video), presentationData: presentationData, present: { c, a in
|
DeviceAccess.authorizeAccess(to: .camera(.qrCode), presentationData: presentationData, present: { c, a in
|
||||||
c.presentationArguments = a
|
c.presentationArguments = a
|
||||||
context.sharedContext.mainWindow?.present(c, on: .root)
|
context.sharedContext.mainWindow?.present(c, on: .root)
|
||||||
}, openSettings: {
|
}, openSettings: {
|
||||||
@ -758,7 +759,7 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
|
|||||||
guard granted else {
|
guard granted else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pushControllerImpl?(AuthTransferScanScreen(context: context, activeSessionsContext: activeSessionsContext))
|
pushControllerImpl?(QrCodeScanScreen(context: context, subject: .authTransfer(activeSessionsContext: activeSessionsContext)))
|
||||||
})
|
})
|
||||||
}, openOtherAppsUrl: {
|
}, openOtherAppsUrl: {
|
||||||
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://telegram.org/apps", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
|
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://telegram.org/apps", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
|
||||||
|
@ -1080,41 +1080,50 @@ final class MessageStoryRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ShareToInstagramActivity: UIActivity {
|
public class ShareToInstagramActivity: UIActivity {
|
||||||
|
private let context: AccountContext
|
||||||
private var activityItems = [Any]()
|
private var activityItems = [Any]()
|
||||||
private var action: ([Any]) -> Void
|
|
||||||
|
|
||||||
init(action: @escaping ([Any]) -> Void) {
|
public init(context: AccountContext) {
|
||||||
self.action = action
|
self.context = context
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
override var activityTitle: String? {
|
public override var activityTitle: String? {
|
||||||
return "Share to Instagram Stories"
|
return self.context.sharedContext.currentPresentationData.with { $0 }.strings.Share_ShareToInstagramStories
|
||||||
}
|
}
|
||||||
|
|
||||||
override var activityImage: UIImage? {
|
public override var activityImage: UIImage? {
|
||||||
return nil
|
return UIImage(bundleImageName: "Share/Instagram")
|
||||||
}
|
}
|
||||||
|
|
||||||
override var activityType: UIActivity.ActivityType? {
|
public override var activityType: UIActivity.ActivityType? {
|
||||||
return UIActivity.ActivityType(rawValue: "org.telegram.Telegram.ShareToInstagram")
|
return UIActivity.ActivityType(rawValue: "org.telegram.Telegram.ShareToInstagram")
|
||||||
}
|
}
|
||||||
|
|
||||||
override class var activityCategory: UIActivity.Category {
|
public override class var activityCategory: UIActivity.Category {
|
||||||
return .action
|
return .action
|
||||||
}
|
}
|
||||||
|
|
||||||
override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
|
public override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
|
||||||
return true
|
return self.context.sharedContext.applicationBindings.canOpenUrl("instagram-stories://")
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepare(withActivityItems activityItems: [Any]) {
|
public override func prepare(withActivityItems activityItems: [Any]) {
|
||||||
self.activityItems = activityItems
|
self.activityItems = activityItems
|
||||||
}
|
}
|
||||||
|
|
||||||
override func perform() {
|
public override func perform() {
|
||||||
self.action(self.activityItems)
|
if let url = self.activityItems.first as? URL, let data = try? Data(contentsOf: url) {
|
||||||
|
let pasteboardItems: [[String: Any]] = [["com.instagram.sharedSticker.backgroundImage": data]]
|
||||||
|
if #available(iOS 10.0, *) {
|
||||||
|
UIPasteboard.general.setItems(pasteboardItems, options: [.expirationDate: Date().addingTimeInterval(5 * 60)])
|
||||||
|
} else {
|
||||||
|
UIPasteboard.general.items = pasteboardItems
|
||||||
|
}
|
||||||
|
context.sharedContext.applicationBindings.openUrl("instagram-stories://share")
|
||||||
|
}
|
||||||
activityDidFinish(true)
|
activityDidFinish(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -228,6 +228,8 @@ public final class PermissionController: ViewController {
|
|||||||
self.displayNode = PermissionControllerNode(context: self.context, splitTest: self.splitTest)
|
self.displayNode = PermissionControllerNode(context: self.context, splitTest: self.splitTest)
|
||||||
self.displayNodeDidLoad()
|
self.displayNodeDidLoad()
|
||||||
|
|
||||||
|
self.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate)
|
||||||
|
|
||||||
self.controllerNode.allow = { [weak self] in
|
self.controllerNode.allow = { [weak self] in
|
||||||
self?.allow?()
|
self?.allow?()
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@ public func customizeDefaultDarkPresentationTheme(theme: PresentationTheme, edit
|
|||||||
} else {
|
} else {
|
||||||
badgeFillColor = UIColor(rgb: 0xeb5545)
|
badgeFillColor = UIColor(rgb: 0xeb5545)
|
||||||
badgeTextColor = UIColor(rgb: 0xffffff)
|
badgeTextColor = UIColor(rgb: 0xffffff)
|
||||||
if initialAccentColor.lightness > 0.7 {
|
if initialAccentColor.lightness > 0.735 {
|
||||||
secondaryBadgeTextColor = UIColor(rgb: 0x000000)
|
secondaryBadgeTextColor = UIColor(rgb: 0x000000)
|
||||||
} else {
|
} else {
|
||||||
secondaryBadgeTextColor = UIColor(rgb: 0xffffff)
|
secondaryBadgeTextColor = UIColor(rgb: 0xffffff)
|
||||||
@ -153,7 +153,7 @@ public func customizeDefaultDarkPresentationTheme(theme: PresentationTheme, edit
|
|||||||
outgoingBubbleFillColors = bubbleColors.map(UIColor.init(rgb:))
|
outgoingBubbleFillColors = bubbleColors.map(UIColor.init(rgb:))
|
||||||
|
|
||||||
let lightnessColor = topBubbleColor.mixedWith(bottomBubbleColor, alpha: 0.5)
|
let lightnessColor = topBubbleColor.mixedWith(bottomBubbleColor, alpha: 0.5)
|
||||||
if lightnessColor.lightness > 0.7 {
|
if lightnessColor.lightness > 0.735 {
|
||||||
outgoingPrimaryTextColor = UIColor(rgb: 0x000000)
|
outgoingPrimaryTextColor = UIColor(rgb: 0x000000)
|
||||||
outgoingSecondaryTextColor = UIColor(rgb: 0x000000, alpha: 0.5)
|
outgoingSecondaryTextColor = UIColor(rgb: 0x000000, alpha: 0.5)
|
||||||
outgoingLinkTextColor = UIColor(rgb: 0x000000)
|
outgoingLinkTextColor = UIColor(rgb: 0x000000)
|
||||||
|
@ -309,7 +309,6 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager<Te
|
|||||||
let effectiveColors = themeSettings.themeSpecificAccentColors[effectiveTheme.index]
|
let effectiveColors = themeSettings.themeSpecificAccentColors[effectiveTheme.index]
|
||||||
let theme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: effectiveTheme, baseTheme: preferredBaseTheme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors ?? [], baseColor: effectiveColors?.baseColor) ?? defaultPresentationTheme
|
let theme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: effectiveTheme, baseTheme: preferredBaseTheme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors ?? [], baseColor: effectiveColors?.baseColor) ?? defaultPresentationTheme
|
||||||
|
|
||||||
|
|
||||||
let effectiveChatWallpaper: TelegramWallpaper = (themeSettings.themeSpecificChatWallpapers[coloredThemeIndex(reference: effectiveTheme, accentColor: effectiveColors)] ?? themeSettings.themeSpecificChatWallpapers[effectiveTheme.index]) ?? theme.chat.defaultWallpaper
|
let effectiveChatWallpaper: TelegramWallpaper = (themeSettings.themeSpecificChatWallpapers[coloredThemeIndex(reference: effectiveTheme, accentColor: effectiveColors)] ?? themeSettings.themeSpecificChatWallpapers[effectiveTheme.index]) ?? theme.chat.defaultWallpaper
|
||||||
|
|
||||||
let dateTimeFormat = currentDateTimeFormat()
|
let dateTimeFormat = currentDateTimeFormat()
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"info" : {
|
"info" : {
|
||||||
"version" : 1,
|
"author" : "xcode",
|
||||||
"author" : "xcode"
|
"version" : 1
|
||||||
},
|
},
|
||||||
"properties" : {
|
"properties" : {
|
||||||
"provides-namespace" : true
|
"provides-namespace" : true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
submodules/TelegramUI/Images.xcassets/Share/Instagram.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Share/Instagram.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "instagram_60.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
151
submodules/TelegramUI/Images.xcassets/Share/Instagram.imageset/instagram_60.pdf
vendored
Normal file
151
submodules/TelegramUI/Images.xcassets/Share/Instagram.imageset/instagram_60.pdf
vendored
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
%PDF-1.7
|
||||||
|
|
||||||
|
1 0 obj
|
||||||
|
<< >>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
2 0 obj
|
||||||
|
<< /Length 3 0 R >>
|
||||||
|
stream
|
||||||
|
/DeviceRGB CS
|
||||||
|
/DeviceRGB cs
|
||||||
|
q
|
||||||
|
1.000000 0.000000 -0.000000 1.000000 10.500000 10.500000 cm
|
||||||
|
0.000000 0.000000 0.000000 scn
|
||||||
|
17.432882 39.000000 m
|
||||||
|
17.500000 39.000000 l
|
||||||
|
21.500000 39.000000 l
|
||||||
|
21.567118 39.000000 l
|
||||||
|
24.310560 39.000008 26.461430 39.000015 28.190128 38.858776 c
|
||||||
|
29.950411 38.714954 31.404631 38.417244 32.720890 37.746574 c
|
||||||
|
34.884754 36.644032 36.644032 34.884754 37.746574 32.720890 c
|
||||||
|
38.417244 31.404629 38.714954 29.950411 38.858776 28.190128 c
|
||||||
|
39.000015 26.461479 39.000008 24.310677 39.000000 21.567352 c
|
||||||
|
39.000000 21.567255 l
|
||||||
|
39.000000 21.567158 l
|
||||||
|
39.000000 21.500000 l
|
||||||
|
39.000000 17.500000 l
|
||||||
|
39.000000 17.432842 l
|
||||||
|
39.000000 17.432745 l
|
||||||
|
39.000000 17.432648 l
|
||||||
|
39.000008 14.689320 39.000015 12.538519 38.858776 10.809872 c
|
||||||
|
38.714954 9.049589 38.417244 7.595369 37.746574 6.279110 c
|
||||||
|
36.644032 4.115246 34.884754 2.355968 32.720890 1.253426 c
|
||||||
|
31.404631 0.582756 29.950411 0.285046 28.190128 0.141224 c
|
||||||
|
26.461481 -0.000015 24.310678 -0.000008 21.567352 0.000000 c
|
||||||
|
21.567255 0.000000 l
|
||||||
|
21.567158 0.000000 l
|
||||||
|
21.500000 0.000000 l
|
||||||
|
17.500000 0.000000 l
|
||||||
|
17.432840 0.000000 l
|
||||||
|
17.432743 0.000000 l
|
||||||
|
17.432646 0.000000 l
|
||||||
|
14.689321 -0.000008 12.538522 -0.000015 10.809872 0.141224 c
|
||||||
|
9.049589 0.285046 7.595370 0.582756 6.279109 1.253426 c
|
||||||
|
4.115245 2.355968 2.355969 4.115246 1.253425 6.279110 c
|
||||||
|
0.582757 7.595369 0.285045 9.049589 0.141224 10.809872 c
|
||||||
|
-0.000016 12.538570 -0.000009 14.689442 0.000000 17.432882 c
|
||||||
|
0.000000 17.500000 l
|
||||||
|
0.000000 21.500000 l
|
||||||
|
0.000000 21.567120 l
|
||||||
|
-0.000009 24.310558 -0.000016 26.461430 0.141224 28.190128 c
|
||||||
|
0.285045 29.950411 0.582757 31.404629 1.253425 32.720890 c
|
||||||
|
2.355969 34.884754 4.115245 36.644032 6.279109 37.746574 c
|
||||||
|
7.595370 38.417244 9.049589 38.714954 10.809872 38.858776 c
|
||||||
|
12.538570 39.000015 14.689443 39.000008 17.432882 39.000000 c
|
||||||
|
h
|
||||||
|
11.054168 35.868740 m
|
||||||
|
9.479408 35.740078 8.463938 35.492821 7.641081 35.073555 c
|
||||||
|
6.041704 34.258633 4.741369 32.958298 3.926445 31.358919 c
|
||||||
|
3.507179 30.536062 3.259924 29.520592 3.131261 27.945833 c
|
||||||
|
3.001167 26.353561 3.000000 24.325014 3.000000 21.500000 c
|
||||||
|
3.000000 17.500000 l
|
||||||
|
3.000000 14.674986 3.001167 12.646439 3.131261 11.054167 c
|
||||||
|
3.259924 9.479408 3.507179 8.463938 3.926445 7.641081 c
|
||||||
|
4.741369 6.041702 6.041704 4.741367 7.641081 3.926445 c
|
||||||
|
8.463938 3.507179 9.479408 3.259922 11.054167 3.131260 c
|
||||||
|
12.646438 3.001167 14.674986 3.000000 17.500000 3.000000 c
|
||||||
|
21.500000 3.000000 l
|
||||||
|
24.325014 3.000000 26.353561 3.001167 27.945833 3.131260 c
|
||||||
|
29.520592 3.259922 30.536062 3.507179 31.358919 3.926445 c
|
||||||
|
32.958298 4.741367 34.258633 6.041702 35.073555 7.641081 c
|
||||||
|
35.492821 8.463938 35.740078 9.479408 35.868740 11.054167 c
|
||||||
|
35.998833 12.646437 36.000000 14.674986 36.000000 17.500000 c
|
||||||
|
36.000000 21.500000 l
|
||||||
|
36.000000 24.325014 35.998833 26.353561 35.868740 27.945831 c
|
||||||
|
35.740078 29.520592 35.492821 30.536062 35.073555 31.358919 c
|
||||||
|
34.258633 32.958298 32.958298 34.258633 31.358919 35.073555 c
|
||||||
|
30.536062 35.492821 29.520592 35.740078 27.945833 35.868740 c
|
||||||
|
26.353563 35.998833 24.325014 36.000000 21.500000 36.000000 c
|
||||||
|
17.500000 36.000000 l
|
||||||
|
14.674986 36.000000 12.646438 35.998833 11.054168 35.868740 c
|
||||||
|
h
|
||||||
|
32.100098 30.099976 m
|
||||||
|
32.100098 28.995407 31.204668 28.099976 30.100098 28.099976 c
|
||||||
|
28.995527 28.099976 28.100098 28.995407 28.100098 30.099976 c
|
||||||
|
28.100098 31.204544 28.995527 32.099976 30.100098 32.099976 c
|
||||||
|
31.204668 32.099976 32.100098 31.204544 32.100098 30.099976 c
|
||||||
|
h
|
||||||
|
12.000000 19.500000 m
|
||||||
|
12.000000 23.642136 15.357864 27.000000 19.500000 27.000000 c
|
||||||
|
23.642136 27.000000 27.000000 23.642136 27.000000 19.500000 c
|
||||||
|
27.000000 15.357864 23.642136 12.000000 19.500000 12.000000 c
|
||||||
|
15.357864 12.000000 12.000000 15.357864 12.000000 19.500000 c
|
||||||
|
h
|
||||||
|
19.500000 30.000000 m
|
||||||
|
13.701010 30.000000 9.000000 25.298990 9.000000 19.500000 c
|
||||||
|
9.000000 13.701010 13.701010 9.000000 19.500000 9.000000 c
|
||||||
|
25.298990 9.000000 30.000000 13.701010 30.000000 19.500000 c
|
||||||
|
30.000000 25.298990 25.298990 30.000000 19.500000 30.000000 c
|
||||||
|
h
|
||||||
|
f*
|
||||||
|
n
|
||||||
|
Q
|
||||||
|
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
|
||||||
|
3 0 obj
|
||||||
|
3898
|
||||||
|
endobj
|
||||||
|
|
||||||
|
4 0 obj
|
||||||
|
<< /Annots []
|
||||||
|
/Type /Page
|
||||||
|
/MediaBox [ 0.000000 0.000000 60.000000 60.000000 ]
|
||||||
|
/Resources 1 0 R
|
||||||
|
/Contents 2 0 R
|
||||||
|
/Parent 5 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
5 0 obj
|
||||||
|
<< /Kids [ 4 0 R ]
|
||||||
|
/Count 1
|
||||||
|
/Type /Pages
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
6 0 obj
|
||||||
|
<< /Pages 5 0 R
|
||||||
|
/Type /Catalog
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
xref
|
||||||
|
0 7
|
||||||
|
0000000000 65535 f
|
||||||
|
0000000010 00000 n
|
||||||
|
0000000034 00000 n
|
||||||
|
0000003988 00000 n
|
||||||
|
0000004011 00000 n
|
||||||
|
0000004184 00000 n
|
||||||
|
0000004258 00000 n
|
||||||
|
trailer
|
||||||
|
<< /ID [ (some) (id) ]
|
||||||
|
/Root 6 0 R
|
||||||
|
/Size 7
|
||||||
|
>>
|
||||||
|
startxref
|
||||||
|
4317
|
||||||
|
%%EOF
|
12
submodules/TelegramUI/Images.xcassets/Share/QrPlaneIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Share/QrPlaneIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "ic_qrlogo.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
submodules/TelegramUI/Images.xcassets/Share/QrPlaneIcon.imageset/ic_qrlogo.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Share/QrPlaneIcon.imageset/ic_qrlogo.pdf
vendored
Normal file
Binary file not shown.
@ -135,7 +135,7 @@ func chatHistoryEntriesForView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if presentationData.largeEmoji, message.media.isEmpty {
|
if presentationData.largeEmoji, message.media.isEmpty {
|
||||||
if stickersEnabled && message.text.count == 1, let _ = associatedData.animatedEmojiStickers[message.text.basicEmoji.0] {
|
if stickersEnabled && message.text.count == 1, let _ = associatedData.animatedEmojiStickers[message.text.basicEmoji.0], (message.textEntitiesAttribute?.entities.isEmpty ?? true) {
|
||||||
contentTypeHint = .animatedEmoji
|
contentTypeHint = .animatedEmoji
|
||||||
} else if message.text.count < 10 && messageIsElligibleForLargeEmoji(message) {
|
} else if message.text.count < 10 && messageIsElligibleForLargeEmoji(message) {
|
||||||
contentTypeHint = .largeEmoji
|
contentTypeHint = .largeEmoji
|
||||||
|
@ -23,6 +23,7 @@ import ShimmerEffect
|
|||||||
import WallpaperBackgroundNode
|
import WallpaperBackgroundNode
|
||||||
import QrCode
|
import QrCode
|
||||||
import AvatarNode
|
import AvatarNode
|
||||||
|
import ShareController
|
||||||
|
|
||||||
private func closeButtonImage(theme: PresentationTheme) -> UIImage? {
|
private func closeButtonImage(theme: PresentationTheme) -> UIImage? {
|
||||||
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
|
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
|
||||||
@ -554,7 +555,6 @@ final class ChatQrCodeScreen: ViewController {
|
|||||||
private var animatedIn = false
|
private var animatedIn = false
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let animatedEmojiStickers: [String: [StickerPackItem]]
|
|
||||||
private let peer: Peer
|
private let peer: Peer
|
||||||
|
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
@ -563,10 +563,9 @@ final class ChatQrCodeScreen: ViewController {
|
|||||||
|
|
||||||
var dismissed: (() -> Void)?
|
var dismissed: (() -> Void)?
|
||||||
|
|
||||||
init(context: AccountContext, animatedEmojiStickers: [String: [StickerPackItem]], peer: Peer) {
|
init(context: AccountContext, peer: Peer) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
self.animatedEmojiStickers = animatedEmojiStickers
|
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
|
|
||||||
super.init(navigationBarPresentationData: nil)
|
super.init(navigationBarPresentationData: nil)
|
||||||
@ -591,6 +590,8 @@ final class ChatQrCodeScreen: ViewController {
|
|||||||
})
|
})
|
||||||
|
|
||||||
self.statusBar.statusBarStyle = .Ignore
|
self.statusBar.statusBarStyle = .Ignore
|
||||||
|
|
||||||
|
self.ready.set(self.controllerNode.ready.get())
|
||||||
}
|
}
|
||||||
|
|
||||||
required init(coder aDecoder: NSCoder) {
|
required init(coder aDecoder: NSCoder) {
|
||||||
@ -602,7 +603,7 @@ final class ChatQrCodeScreen: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override public func loadDisplayNode() {
|
override public func loadDisplayNode() {
|
||||||
self.displayNode = ChatQrCodeScreenNode(context: self.context, presentationData: self.presentationData, controller: self, animatedEmojiStickers: self.animatedEmojiStickers, peer: self.peer)
|
self.displayNode = ChatQrCodeScreenNode(context: self.context, presentationData: self.presentationData, controller: self, peer: self.peer)
|
||||||
self.controllerNode.previewTheme = { [weak self] _, _, theme in
|
self.controllerNode.previewTheme = { [weak self] _, _, theme in
|
||||||
self?.presentationThemePromise.set(.single(theme))
|
self?.presentationThemePromise.set(.single(theme))
|
||||||
}
|
}
|
||||||
@ -659,10 +660,6 @@ final class ChatQrCodeScreen: ViewController {
|
|||||||
|
|
||||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
|
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
func dimTapped() {
|
|
||||||
self.controllerNode.dimTapped()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func iconColors(theme: PresentationTheme) -> [String: UIColor] {
|
private func iconColors(theme: PresentationTheme) -> [String: UIColor] {
|
||||||
@ -689,20 +686,7 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
|
|||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private weak var controller: ChatQrCodeScreen?
|
private weak var controller: ChatQrCodeScreen?
|
||||||
|
|
||||||
private let dimNode: ASDisplayNode
|
private let contentNode: QrContentNode
|
||||||
private let containerNode: ASDisplayNode
|
|
||||||
private let wallpaperBackgroundNode: WallpaperBackgroundNode
|
|
||||||
private let codeBackgroundNode: ASDisplayNode
|
|
||||||
private let codeForegroundNode: ASDisplayNode
|
|
||||||
private var codeForegroundContentNode: ASDisplayNode?
|
|
||||||
private var codeForegroundDimNode: ASDisplayNode
|
|
||||||
private let codeMaskNode: ASDisplayNode
|
|
||||||
private let codeTextNode: ImmediateTextNode
|
|
||||||
private let codeImageNode: TransformImageNode
|
|
||||||
private let codeIconBackgroundNode: ASImageNode
|
|
||||||
private let codeIconNode: AnimatedStickerNode
|
|
||||||
private let avatarNode: ImageNode
|
|
||||||
private var qrCodeSize: Int?
|
|
||||||
|
|
||||||
private let wrappingScrollNode: ASScrollNode
|
private let wrappingScrollNode: ASScrollNode
|
||||||
private let contentContainerNode: ASDisplayNode
|
private let contentContainerNode: ASDisplayNode
|
||||||
@ -723,14 +707,17 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
|
|||||||
private var initialized = false
|
private var initialized = false
|
||||||
private var themes: [TelegramTheme] = []
|
private var themes: [TelegramTheme] = []
|
||||||
|
|
||||||
|
let ready = Promise<Bool>()
|
||||||
|
|
||||||
private let peer: Peer
|
private let peer: Peer
|
||||||
|
|
||||||
private var selectedEmoticon: String? {
|
private var initiallySelectedEmoticon: String?
|
||||||
|
private var selectedEmoticon: String? = nil {
|
||||||
didSet {
|
didSet {
|
||||||
self.selectedEmoticonPromise.set(self.selectedEmoticon)
|
self.selectedEmoticonPromise.set(self.selectedEmoticon)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private var selectedEmoticonPromise: ValuePromise<String?>
|
private var selectedEmoticonPromise = ValuePromise<String?>(nil)
|
||||||
|
|
||||||
private var isDarkAppearancePromise: ValuePromise<Bool>
|
private var isDarkAppearancePromise: ValuePromise<Bool>
|
||||||
private var isDarkAppearance: Bool = false {
|
private var isDarkAppearance: Bool = false {
|
||||||
@ -749,26 +736,19 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
|
|||||||
var dismiss: (() -> Void)?
|
var dismiss: (() -> Void)?
|
||||||
var cancel: (() -> Void)?
|
var cancel: (() -> Void)?
|
||||||
|
|
||||||
init(context: AccountContext, presentationData: PresentationData, controller: ChatQrCodeScreen, animatedEmojiStickers: [String: [StickerPackItem]], peer: Peer) {
|
init(context: AccountContext, presentationData: PresentationData, controller: ChatQrCodeScreen, peer: Peer) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.selectedEmoticon = defaultEmoticon
|
|
||||||
self.selectedEmoticonPromise = ValuePromise(self.selectedEmoticon)
|
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
|
|
||||||
self.wrappingScrollNode = ASScrollNode()
|
self.wrappingScrollNode = ASScrollNode()
|
||||||
self.wrappingScrollNode.view.alwaysBounceVertical = true
|
self.wrappingScrollNode.view.alwaysBounceVertical = true
|
||||||
self.wrappingScrollNode.view.delaysContentTouches = false
|
self.wrappingScrollNode.view.delaysContentTouches = false
|
||||||
self.wrappingScrollNode.view.canCancelContentTouches = true
|
self.wrappingScrollNode.view.canCancelContentTouches = true
|
||||||
|
|
||||||
self.dimNode = ASDisplayNode()
|
|
||||||
self.dimNode.backgroundColor = .clear
|
|
||||||
|
|
||||||
self.containerNode = ASDisplayNode()
|
|
||||||
|
|
||||||
self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false, useExperimentalImplementation: self.context.sharedContext.immediateExperimentalUISettings.experimentalBackground)
|
|
||||||
|
|
||||||
|
self.contentNode = QrContentNode(context: context, peer: peer, isStatic: false)
|
||||||
|
|
||||||
self.contentContainerNode = ASDisplayNode()
|
self.contentContainerNode = ASDisplayNode()
|
||||||
self.contentContainerNode.isOpaque = false
|
self.contentContainerNode.isOpaque = false
|
||||||
|
|
||||||
@ -812,62 +792,14 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
|
|||||||
self.listNode = ListView()
|
self.listNode = ListView()
|
||||||
self.listNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
self.listNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||||
|
|
||||||
self.codeBackgroundNode = ASDisplayNode()
|
|
||||||
self.codeBackgroundNode.backgroundColor = .white
|
|
||||||
self.codeBackgroundNode.cornerRadius = 42.0
|
|
||||||
if #available(iOS 13.0, *) {
|
|
||||||
self.codeBackgroundNode.layer.cornerCurve = .continuous
|
|
||||||
}
|
|
||||||
self.codeForegroundNode = ASDisplayNode()
|
|
||||||
self.codeForegroundNode.backgroundColor = .black
|
|
||||||
|
|
||||||
self.codeForegroundDimNode = ASDisplayNode()
|
|
||||||
self.codeForegroundDimNode.alpha = 0.3
|
|
||||||
self.codeForegroundDimNode.backgroundColor = .black
|
|
||||||
|
|
||||||
self.codeMaskNode = ASDisplayNode()
|
|
||||||
|
|
||||||
self.codeImageNode = TransformImageNode()
|
|
||||||
|
|
||||||
self.codeIconBackgroundNode = ASImageNode()
|
|
||||||
|
|
||||||
self.codeIconNode = AnimatedStickerNode()
|
|
||||||
self.codeIconNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "PlaneLogoPlain"), width: 240, height: 240, mode: .direct(cachePathPrefix: nil))
|
|
||||||
self.codeIconNode.visibility = true
|
|
||||||
|
|
||||||
self.codeTextNode = ImmediateTextNode()
|
|
||||||
self.codeTextNode.attributedText = NSAttributedString(string: "@\(peer.addressName ?? "")".uppercased(), font: Font.with(size: 24.0, design: .round, weight: .bold, traits: []), textColor: .black)
|
|
||||||
|
|
||||||
self.avatarNode = ImageNode()
|
|
||||||
self.avatarNode.displaysAsynchronously = false
|
|
||||||
self.avatarNode.setSignal(peerAvatarCompleteImage(account: self.context.account, peer: EnginePeer(peer), size: CGSize(width: 180.0, height: 180.0), font: avatarPlaceholderFont(size: 78.0), fullSize: true))
|
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.backgroundColor = nil
|
self.backgroundColor = nil
|
||||||
self.isOpaque = false
|
self.isOpaque = false
|
||||||
|
|
||||||
self.addSubnode(self.dimNode)
|
|
||||||
|
|
||||||
self.wrappingScrollNode.view.delegate = self
|
|
||||||
self.addSubnode(self.wrappingScrollNode)
|
self.addSubnode(self.wrappingScrollNode)
|
||||||
|
|
||||||
self.wrappingScrollNode.addSubnode(self.containerNode)
|
self.wrappingScrollNode.addSubnode(self.contentNode)
|
||||||
|
|
||||||
self.containerNode.addSubnode(self.wallpaperBackgroundNode)
|
|
||||||
|
|
||||||
self.containerNode.addSubnode(self.codeBackgroundNode)
|
|
||||||
self.containerNode.addSubnode(self.codeForegroundNode)
|
|
||||||
|
|
||||||
self.codeForegroundNode.addSubnode(self.codeForegroundDimNode)
|
|
||||||
|
|
||||||
self.codeMaskNode.addSubnode(self.codeImageNode)
|
|
||||||
self.codeMaskNode.addSubnode(self.codeIconBackgroundNode)
|
|
||||||
self.codeMaskNode.addSubnode(self.codeTextNode)
|
|
||||||
|
|
||||||
self.containerNode.addSubnode(self.avatarNode)
|
|
||||||
|
|
||||||
self.wrappingScrollNode.addSubnode(self.codeIconNode)
|
|
||||||
|
|
||||||
self.wrappingScrollNode.addSubnode(self.backgroundNode)
|
self.wrappingScrollNode.addSubnode(self.backgroundNode)
|
||||||
self.wrappingScrollNode.addSubnode(self.contentContainerNode)
|
self.wrappingScrollNode.addSubnode(self.contentContainerNode)
|
||||||
@ -889,20 +821,102 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
|
|||||||
self.doneButton.pressed = { [weak self] in
|
self.doneButton.pressed = { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.doneButton.isUserInteractionEnabled = false
|
strongSelf.doneButton.isUserInteractionEnabled = false
|
||||||
strongSelf.completion?(strongSelf.selectedEmoticon)
|
|
||||||
|
strongSelf.contentNode.generateImage { [weak self] image in
|
||||||
|
if let strongSelf = self, let image = image, let jpgData = image.jpegData(compressionQuality: 0.9) {
|
||||||
|
let tempFilePath = NSTemporaryDirectory() + "t_me-\(peer.addressName ?? "").jpg"
|
||||||
|
try? FileManager.default.removeItem(atPath: tempFilePath)
|
||||||
|
let tempFileUrl = URL(fileURLWithPath: tempFilePath)
|
||||||
|
try? jpgData.write(to: tempFileUrl)
|
||||||
|
|
||||||
|
let activityController = UIActivityViewController(activityItems: [tempFileUrl], applicationActivities: [ShareToInstagramActivity(context: strongSelf.context)])
|
||||||
|
activityController.completionWithItemsHandler = { [weak self] _, finished, _, _ in
|
||||||
|
if let strongSelf = self {
|
||||||
|
if finished {
|
||||||
|
strongSelf.completion?(strongSelf.selectedEmoticon)
|
||||||
|
} else {
|
||||||
|
strongSelf.doneButton.isUserInteractionEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let window = strongSelf.view.window {
|
||||||
|
activityController.popoverPresentationController?.sourceView = window
|
||||||
|
activityController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0))
|
||||||
|
}
|
||||||
|
context.sharedContext.applicationBindings.presentNativeController(activityController)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.disposable.set(combineLatest(queue: Queue.mainQueue(), self.context.engine.themes.getChatThemes(accountManager: self.context.sharedContext.accountManager), self.selectedEmoticonPromise.get(), self.isDarkAppearancePromise.get()).start(next: { [weak self] themes, selectedEmoticon, isDarkAppearance in
|
let animatedEmojiStickers = context.engine.stickers.loadedStickerPack(reference: .animatedEmoji, forceActualized: false)
|
||||||
|
|> map { animatedEmoji -> [String: [StickerPackItem]] in
|
||||||
|
var animatedEmojiStickers: [String: [StickerPackItem]] = [:]
|
||||||
|
switch animatedEmoji {
|
||||||
|
case let .result(_, items, _):
|
||||||
|
for item in items {
|
||||||
|
if let emoji = item.getStringRepresentationsOfIndexKeys().first {
|
||||||
|
animatedEmojiStickers[emoji.basicEmoji.0] = [item]
|
||||||
|
let strippedEmoji = emoji.basicEmoji.0.strippedEmoji
|
||||||
|
if animatedEmojiStickers[strippedEmoji] == nil {
|
||||||
|
animatedEmojiStickers[strippedEmoji] = [item]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return animatedEmojiStickers
|
||||||
|
}
|
||||||
|
|
||||||
|
let initiallySelectedEmoticon: Signal<String, NoError>
|
||||||
|
if self.peer.id == self.context.account.peerId {
|
||||||
|
initiallySelectedEmoticon = self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings])
|
||||||
|
|> take(1)
|
||||||
|
|> map { sharedData -> String in
|
||||||
|
let themeSettings: PresentationThemeSettings
|
||||||
|
if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) {
|
||||||
|
themeSettings = current
|
||||||
|
} else {
|
||||||
|
themeSettings = PresentationThemeSettings.defaultSettings
|
||||||
|
}
|
||||||
|
return themeSettings.theme.emoticon ?? defaultEmoticon
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
initiallySelectedEmoticon = self.context.account.postbox.transaction { transaction in
|
||||||
|
return transaction.getPeerCachedData(peerId: peer.id)
|
||||||
|
}
|
||||||
|
|> take(1)
|
||||||
|
|> map { cachedData -> String in
|
||||||
|
if let cachedData = cachedData as? CachedUserData {
|
||||||
|
return cachedData.themeEmoticon ?? defaultEmoticon
|
||||||
|
} else if let cachedData = cachedData as? CachedGroupData {
|
||||||
|
return cachedData.themeEmoticon ?? defaultEmoticon
|
||||||
|
} else if let cachedData = cachedData as? CachedChannelData {
|
||||||
|
return cachedData.themeEmoticon ?? defaultEmoticon
|
||||||
|
} else {
|
||||||
|
return defaultEmoticon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.disposable.set(combineLatest(queue: Queue.mainQueue(), animatedEmojiStickers, initiallySelectedEmoticon, self.context.engine.themes.getChatThemes(accountManager: self.context.sharedContext.accountManager), self.selectedEmoticonPromise.get(), self.isDarkAppearancePromise.get()).start(next: { [weak self] animatedEmojiStickers, initiallySelectedEmoticon, themes, selectedEmoticon, isDarkAppearance in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var selectedEmoticon = selectedEmoticon
|
||||||
|
if strongSelf.initiallySelectedEmoticon == nil {
|
||||||
|
strongSelf.initiallySelectedEmoticon = initiallySelectedEmoticon
|
||||||
|
strongSelf.selectedEmoticon = initiallySelectedEmoticon
|
||||||
|
selectedEmoticon = initiallySelectedEmoticon
|
||||||
|
}
|
||||||
|
|
||||||
let isFirstTime = strongSelf.entries == nil
|
let isFirstTime = strongSelf.entries == nil
|
||||||
let presentationData = strongSelf.presentationData
|
let presentationData = strongSelf.presentationData
|
||||||
|
|
||||||
var entries: [ThemeSettingsThemeEntry] = []
|
var entries: [ThemeSettingsThemeEntry] = []
|
||||||
entries.append(ThemeSettingsThemeEntry(index: 0, emoticon: defaultEmoticon, emojiFile: animatedEmojiStickers[defaultEmoticon]?.first?.file, themeReference: .builtin(.dayClassic), nightMode: isDarkAppearance, selected: selectedEmoticon == defaultEmoticon, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil))
|
entries.append(ThemeSettingsThemeEntry(index: 0, emoticon: defaultEmoticon, emojiFile: animatedEmojiStickers[defaultEmoticon]?.first?.file, themeReference: .builtin(isDarkAppearance ? .night : .dayClassic), nightMode: isDarkAppearance, selected: selectedEmoticon == defaultEmoticon, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil))
|
||||||
for theme in themes {
|
for theme in themes {
|
||||||
guard let emoticon = theme.emoticon else {
|
guard let emoticon = theme.emoticon else {
|
||||||
continue
|
continue
|
||||||
@ -910,13 +924,14 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
|
|||||||
entries.append(ThemeSettingsThemeEntry(index: entries.count, emoticon: emoticon, emojiFile: animatedEmojiStickers[emoticon]?.first?.file, themeReference: .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: nil)), nightMode: isDarkAppearance, selected: selectedEmoticon == theme.emoticon, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil))
|
entries.append(ThemeSettingsThemeEntry(index: entries.count, emoticon: emoticon, emojiFile: animatedEmojiStickers[emoticon]?.first?.file, themeReference: .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: nil)), nightMode: isDarkAppearance, selected: selectedEmoticon == theme.emoticon, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let wallpaper: TelegramWallpaper
|
||||||
if selectedEmoticon == defaultEmoticon {
|
if selectedEmoticon == defaultEmoticon {
|
||||||
let presentationTheme = makeDefaultPresentationTheme(reference: isDarkAppearance ? .night : .dayClassic, serviceBackgroundColor: nil)
|
let presentationTheme = makeDefaultPresentationTheme(reference: isDarkAppearance ? .night : .dayClassic, serviceBackgroundColor: nil)
|
||||||
strongSelf.wallpaperBackgroundNode.update(wallpaper: presentationTheme.chat.defaultWallpaper)
|
wallpaper = presentationTheme.chat.defaultWallpaper
|
||||||
} else if let theme = themes.first(where: { $0.emoticon == selectedEmoticon }) {
|
} else if let theme = themes.first(where: { $0.emoticon == selectedEmoticon }), let presentationTheme = makePresentationTheme(cloudTheme: theme, dark: isDarkAppearance) {
|
||||||
if let presentationTheme = makePresentationTheme(cloudTheme: theme, dark: isDarkAppearance) {
|
wallpaper = presentationTheme.chat.defaultWallpaper
|
||||||
strongSelf.wallpaperBackgroundNode.update(wallpaper: presentationTheme.chat.defaultWallpaper)
|
} else {
|
||||||
}
|
wallpaper = .color(0x000000)
|
||||||
}
|
}
|
||||||
|
|
||||||
let action: (String?) -> Void = { [weak self] emoticon in
|
let action: (String?) -> Void = { [weak self] emoticon in
|
||||||
@ -946,16 +961,7 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
|
|||||||
strongSelf.entries = entries
|
strongSelf.entries = entries
|
||||||
strongSelf.themes = themes
|
strongSelf.themes = themes
|
||||||
|
|
||||||
if isDarkAppearance && selectedEmoticon == defaultEmoticon {
|
strongSelf.contentNode.update(wallpaper: wallpaper, isDarkAppearance: isDarkAppearance, selectedEmoticon: selectedEmoticon)
|
||||||
strongSelf.codeForegroundDimNode.alpha = 1.0
|
|
||||||
} else {
|
|
||||||
strongSelf.codeForegroundDimNode.alpha = isDarkAppearance ? 0.4 : 0.3
|
|
||||||
}
|
|
||||||
if strongSelf.codeForegroundContentNode == nil, let contentNode = strongSelf.wallpaperBackgroundNode.makeDimmedNode() {
|
|
||||||
contentNode.frame = CGRect(origin: CGPoint(x: -strongSelf.codeForegroundNode.frame.minX, y: -strongSelf.codeForegroundNode.frame.minY), size: strongSelf.wallpaperBackgroundNode.frame.size)
|
|
||||||
strongSelf.codeForegroundContentNode = contentNode
|
|
||||||
strongSelf.codeForegroundNode.insertSubnode(contentNode, at: 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if isFirstTime {
|
if isFirstTime {
|
||||||
for theme in themes {
|
for theme in themes {
|
||||||
@ -1001,15 +1007,7 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.codeImageNode.setSignal(qrCode(string: "https://t.me/\(peer.addressName ?? "")", color: .black, backgroundColor: nil, icon: .cutout, ecl: "Q") |> beforeNext { [weak self] size, _ in
|
self.ready.set(self.contentNode.isReady)
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
strongSelf.qrCodeSize = size
|
|
||||||
if let (layout, navigationHeight) = strongSelf.containerLayout {
|
|
||||||
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .immediate)
|
|
||||||
}
|
|
||||||
} |> map { $0.1 }, attemptSynchronously: true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func enqueueTransition(_ transition: ThemeSettingsThemeItemNodeTransition) {
|
private func enqueueTransition(_ transition: ThemeSettingsThemeItemNodeTransition) {
|
||||||
@ -1034,7 +1032,12 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
|
|||||||
|
|
||||||
var scrollToItem: ListViewScrollToItem?
|
var scrollToItem: ListViewScrollToItem?
|
||||||
if !self.initialized {
|
if !self.initialized {
|
||||||
scrollToItem = ListViewScrollToItem(index: 0, position: .bottom(-57.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Down)
|
if let index = transition.entries.firstIndex(where: { entry in
|
||||||
|
return entry.emoticon == self.initiallySelectedEmoticon
|
||||||
|
}) {
|
||||||
|
scrollToItem = ListViewScrollToItem(index: index, position: .bottom(-57.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Down)
|
||||||
|
self.initialized = true
|
||||||
|
}
|
||||||
self.initialized = true
|
self.initialized = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1080,23 +1083,18 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
|
|||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
super.didLoad()
|
super.didLoad()
|
||||||
|
|
||||||
|
self.wrappingScrollNode.view.delegate = self
|
||||||
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||||
self.wrappingScrollNode.view.contentInsetAdjustmentBehavior = .never
|
self.wrappingScrollNode.view.contentInsetAdjustmentBehavior = .never
|
||||||
}
|
}
|
||||||
|
|
||||||
self.codeForegroundNode.view.mask = self.codeMaskNode.view
|
|
||||||
|
|
||||||
self.listNode.view.disablesInteractiveTransitionGestureRecognizer = true
|
self.listNode.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func cancelButtonPressed() {
|
@objc func cancelButtonPressed() {
|
||||||
self.cancel?()
|
self.cancel?()
|
||||||
}
|
}
|
||||||
|
|
||||||
func dimTapped() {
|
|
||||||
self.cancelButtonPressed()
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func switchThemePressed() {
|
@objc func switchThemePressed() {
|
||||||
self.switchThemeButton.isUserInteractionEnabled = false
|
self.switchThemeButton.isUserInteractionEnabled = false
|
||||||
Queue.mainQueue().after(0.5) {
|
Queue.mainQueue().after(0.5) {
|
||||||
@ -1131,8 +1129,8 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func animateCrossfade(animateIcon: Bool) {
|
private func animateCrossfade(animateIcon: Bool) {
|
||||||
if let snapshotView = self.containerNode.view.snapshotView(afterScreenUpdates: false) {
|
if let snapshotView = self.contentNode.containerNode.view.snapshotView(afterScreenUpdates: false) {
|
||||||
self.wrappingScrollNode.view.insertSubview(snapshotView, aboveSubview: self.containerNode.view)
|
self.contentNode.view.insertSubview(snapshotView, aboveSubview: self.contentNode.containerNode.view)
|
||||||
|
|
||||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: ChatQrCodeScreen.themeCrossfadeDuration, delay: ChatQrCodeScreen.themeCrossfadeDelay, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: ChatQrCodeScreen.themeCrossfadeDuration, delay: ChatQrCodeScreen.themeCrossfadeDelay, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||||
snapshotView?.removeFromSuperview()
|
snapshotView?.removeFromSuperview()
|
||||||
@ -1180,15 +1178,12 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
|
|||||||
private var animatedOut = false
|
private var animatedOut = false
|
||||||
func animateIn() {
|
func animateIn() {
|
||||||
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
|
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
|
||||||
let dimPosition = self.dimNode.layer.position
|
|
||||||
|
|
||||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
|
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
|
||||||
let targetBounds = self.bounds
|
let targetBounds = self.bounds
|
||||||
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset)
|
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset)
|
||||||
self.dimNode.position = CGPoint(x: dimPosition.x, y: dimPosition.y - offset)
|
|
||||||
transition.animateView({
|
transition.animateView({
|
||||||
self.bounds = targetBounds
|
self.bounds = targetBounds
|
||||||
self.dimNode.position = dimPosition
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1234,16 +1229,11 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
|
|||||||
if backgroundFrame.minY < contentFrame.minY {
|
if backgroundFrame.minY < contentFrame.minY {
|
||||||
backgroundFrame.origin.y = contentFrame.minY
|
backgroundFrame.origin.y = contentFrame.minY
|
||||||
}
|
}
|
||||||
transition.updateFrame(node: self.wallpaperBackgroundNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
|
||||||
self.wallpaperBackgroundNode.updateLayout(size: layout.size, transition: transition)
|
|
||||||
|
|
||||||
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
|
||||||
|
|
||||||
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
|
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
|
||||||
transition.updateFrame(node: self.effectNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
|
transition.updateFrame(node: self.effectNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
|
||||||
transition.updateFrame(node: self.contentBackgroundNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
|
transition.updateFrame(node: self.contentBackgroundNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
|
||||||
transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||||
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
|
||||||
|
|
||||||
let titleSize = self.titleNode.measure(CGSize(width: width - 90.0, height: titleHeight))
|
let titleSize = self.titleNode.measure(CGSize(width: width - 90.0, height: titleHeight))
|
||||||
let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 19.0 + UIScreenPixel), size: titleSize)
|
let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 19.0 + UIScreenPixel), size: titleSize)
|
||||||
@ -1276,10 +1266,263 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
|
|||||||
self.listNode.position = CGPoint(x: contentSize.width / 2.0, y: contentSize.height / 2.0 + titleHeight + 6.0)
|
self.listNode.position = CGPoint(x: contentSize.width / 2.0, y: contentSize.height / 2.0 + titleHeight + 6.0)
|
||||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: contentSize.height, height: contentSize.width), insets: listInsets, duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: contentSize.height, height: contentSize.width), insets: listInsets, duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||||
|
|
||||||
let codeInset: CGFloat = 45.0
|
self.contentNode.updateLayout(size: layout.size, topInset: 44.0, bottomInset: contentHeight, transition: transition)
|
||||||
let codeBackgroundWidth = layout.size.width - codeInset * 2.0
|
transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class QrContentNode: ASDisplayNode {
|
||||||
|
private let context: AccountContext
|
||||||
|
private let peer: Peer
|
||||||
|
private let isStatic: Bool
|
||||||
|
|
||||||
|
fileprivate let containerNode: ASDisplayNode
|
||||||
|
fileprivate let wallpaperBackgroundNode: WallpaperBackgroundNode
|
||||||
|
private let codeBackgroundNode: ASDisplayNode
|
||||||
|
private let codeForegroundNode: ASDisplayNode
|
||||||
|
private var codeForegroundContentNode: ASDisplayNode?
|
||||||
|
private var codeForegroundDimNode: ASDisplayNode
|
||||||
|
private let codeMaskNode: ASDisplayNode
|
||||||
|
private let codeTextNode: ImmediateTextNode
|
||||||
|
private let codeImageNode: TransformImageNode
|
||||||
|
private let codeIconBackgroundNode: ASImageNode
|
||||||
|
private let codeStaticIconNode: ASImageNode?
|
||||||
|
private let codeAnimatedIconNode: AnimatedStickerNode?
|
||||||
|
private let avatarNode: ImageNode
|
||||||
|
private var qrCodeSize: Int?
|
||||||
|
|
||||||
|
private var currentParams: (TelegramWallpaper, Bool, String?)?
|
||||||
|
private var validLayout: (CGSize, CGFloat, CGFloat)?
|
||||||
|
|
||||||
|
private let _ready = Promise<Bool>()
|
||||||
|
var isReady: Signal<Bool, NoError> {
|
||||||
|
return self._ready.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
init(context: AccountContext, peer: Peer, isStatic: Bool = false) {
|
||||||
|
self.context = context
|
||||||
|
self.peer = peer
|
||||||
|
self.isStatic = isStatic
|
||||||
|
|
||||||
|
self.containerNode = ASDisplayNode()
|
||||||
|
|
||||||
|
self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false, useExperimentalImplementation: context.sharedContext.immediateExperimentalUISettings.experimentalBackground)
|
||||||
|
|
||||||
|
self.codeBackgroundNode = ASDisplayNode()
|
||||||
|
self.codeBackgroundNode.backgroundColor = .white
|
||||||
|
self.codeBackgroundNode.cornerRadius = 42.0
|
||||||
|
if #available(iOS 13.0, *) {
|
||||||
|
self.codeBackgroundNode.layer.cornerCurve = .continuous
|
||||||
|
}
|
||||||
|
|
||||||
|
self.codeForegroundNode = ASDisplayNode()
|
||||||
|
self.codeForegroundNode.backgroundColor = .black
|
||||||
|
|
||||||
|
self.codeForegroundDimNode = ASDisplayNode()
|
||||||
|
self.codeForegroundDimNode.alpha = 0.3
|
||||||
|
self.codeForegroundDimNode.backgroundColor = .black
|
||||||
|
|
||||||
|
self.codeMaskNode = ASDisplayNode()
|
||||||
|
|
||||||
|
self.codeImageNode = TransformImageNode()
|
||||||
|
self.codeIconBackgroundNode = ASImageNode()
|
||||||
|
|
||||||
|
if isStatic {
|
||||||
|
let codeStaticIconNode = ASImageNode()
|
||||||
|
codeStaticIconNode.displaysAsynchronously = false
|
||||||
|
codeStaticIconNode.contentMode = .scaleToFill
|
||||||
|
codeStaticIconNode.image = UIImage(bundleImageName: "Share/QrPlaneIcon")
|
||||||
|
self.codeStaticIconNode = codeStaticIconNode
|
||||||
|
self.codeAnimatedIconNode = nil
|
||||||
|
} else {
|
||||||
|
let codeAnimatedIconNode = AnimatedStickerNode()
|
||||||
|
codeAnimatedIconNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "PlaneLogoPlain"), width: 120, height: 120, mode: .direct(cachePathPrefix: nil))
|
||||||
|
codeAnimatedIconNode.visibility = true
|
||||||
|
self.codeAnimatedIconNode = codeAnimatedIconNode
|
||||||
|
self.codeStaticIconNode = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
self.codeTextNode = ImmediateTextNode()
|
||||||
|
self.codeTextNode.displaysAsynchronously = false
|
||||||
|
self.codeTextNode.attributedText = NSAttributedString(string: "@\(peer.addressName ?? "")".uppercased(), font: Font.with(size: 23.0, design: .round, weight: .bold, traits: []), textColor: .black)
|
||||||
|
self.codeTextNode.truncationMode = .byCharWrapping
|
||||||
|
self.codeTextNode.maximumNumberOfLines = 2
|
||||||
|
self.codeTextNode.textAlignment = .center
|
||||||
|
if isStatic {
|
||||||
|
self.codeTextNode.setNeedsDisplayAtScale(3.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.avatarNode = ImageNode()
|
||||||
|
self.avatarNode.displaysAsynchronously = false
|
||||||
|
self.avatarNode.setSignal(peerAvatarCompleteImage(account: context.account, peer: EnginePeer(peer), size: CGSize(width: 180.0, height: 180.0), font: avatarPlaceholderFont(size: 78.0), fullSize: true))
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.containerNode)
|
||||||
|
|
||||||
|
self.containerNode.addSubnode(self.wallpaperBackgroundNode)
|
||||||
|
|
||||||
|
self.containerNode.addSubnode(self.codeBackgroundNode)
|
||||||
|
self.containerNode.addSubnode(self.codeForegroundNode)
|
||||||
|
|
||||||
|
self.codeForegroundNode.addSubnode(self.codeForegroundDimNode)
|
||||||
|
|
||||||
|
self.codeMaskNode.addSubnode(self.codeImageNode)
|
||||||
|
self.codeMaskNode.addSubnode(self.codeIconBackgroundNode)
|
||||||
|
self.codeMaskNode.addSubnode(self.codeTextNode)
|
||||||
|
|
||||||
|
self.containerNode.addSubnode(self.avatarNode)
|
||||||
|
|
||||||
|
if let codeStaticIconNode = self.codeStaticIconNode {
|
||||||
|
self.containerNode.addSubnode(codeStaticIconNode)
|
||||||
|
} else if let codeAnimatedIconNode = self.codeAnimatedIconNode {
|
||||||
|
self.addSubnode(codeAnimatedIconNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
let codeReadyPromise = ValuePromise<Bool>()
|
||||||
|
self.codeImageNode.setSignal(qrCode(string: "https://t.me/\(peer.addressName ?? "")", color: .black, backgroundColor: nil, icon: .cutout, ecl: "Q") |> beforeNext { [weak self] size, _ in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.qrCodeSize = size
|
||||||
|
if let (size, topInset, bottomInset) = strongSelf.validLayout {
|
||||||
|
strongSelf.updateLayout(size: size, topInset: topInset, bottomInset: bottomInset, transition: .immediate)
|
||||||
|
}
|
||||||
|
codeReadyPromise.set(true)
|
||||||
|
} |> map { $0.1 }, attemptSynchronously: true)
|
||||||
|
|
||||||
|
self._ready.set(combineLatest(codeReadyPromise.get(), self.wallpaperBackgroundNode.isReady)
|
||||||
|
|> map { codeReady, wallpaperReady in
|
||||||
|
return codeReady && wallpaperReady
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
|
||||||
|
self.codeForegroundNode.view.mask = self.codeMaskNode.view
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateImage(completion: @escaping (UIImage?) -> Void) {
|
||||||
|
guard let (wallpaper, isDarkAppearance, selectedEmoticon) = self.currentParams else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = CGSize(width: 390.0, height: 844.0)
|
||||||
|
let scale: CGFloat = 3.0
|
||||||
|
|
||||||
|
let copyNode = QrContentNode(context: self.context, peer: self.peer, isStatic: true)
|
||||||
|
|
||||||
|
func prepare(view: UIView, scale: CGFloat) {
|
||||||
|
view.contentScaleFactor = scale
|
||||||
|
for subview in view.subviews {
|
||||||
|
prepare(view: subview, scale: scale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prepare(view: copyNode.view, scale: scale)
|
||||||
|
|
||||||
|
copyNode.updateLayout(size: size, topInset: 0.0, bottomInset: 0.0, transition: .immediate)
|
||||||
|
copyNode.update(wallpaper: wallpaper, isDarkAppearance: isDarkAppearance, selectedEmoticon: selectedEmoticon)
|
||||||
|
copyNode.frame = CGRect(x: -1000, y: -1000, width: size.width, height: size.height)
|
||||||
|
|
||||||
|
self.addSubnode(copyNode)
|
||||||
|
|
||||||
|
|
||||||
|
let _ = (copyNode.isReady
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak copyNode] _ in
|
||||||
|
Queue.mainQueue().after(0.1) {
|
||||||
|
if #available(iOS 10.0, *) {
|
||||||
|
let format = UIGraphicsImageRendererFormat()
|
||||||
|
format.scale = scale
|
||||||
|
let renderer = UIGraphicsImageRenderer(size: size, format: format)
|
||||||
|
let image = renderer.image { rendererContext in
|
||||||
|
copyNode?.containerNode.layer.render(in: rendererContext.cgContext)
|
||||||
|
}
|
||||||
|
completion(image)
|
||||||
|
} else {
|
||||||
|
UIGraphicsBeginImageContextWithOptions(size, true, scale)
|
||||||
|
copyNode?.containerNode.view.drawHierarchy(in: CGRect(origin: CGPoint(), size: size), afterScreenUpdates: true)
|
||||||
|
let image = UIGraphicsGetImageFromCurrentImageContext()
|
||||||
|
UIGraphicsEndImageContext()
|
||||||
|
completion(image)
|
||||||
|
}
|
||||||
|
copyNode?.removeFromSupernode()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(wallpaper: TelegramWallpaper, isDarkAppearance: Bool, selectedEmoticon: String?) {
|
||||||
|
self.currentParams = (wallpaper, isDarkAppearance, selectedEmoticon)
|
||||||
|
|
||||||
|
self.wallpaperBackgroundNode.update(wallpaper: wallpaper)
|
||||||
|
|
||||||
|
if isDarkAppearance && selectedEmoticon == defaultEmoticon {
|
||||||
|
self.codeForegroundDimNode.alpha = 1.0
|
||||||
|
} else {
|
||||||
|
self.codeForegroundDimNode.alpha = isDarkAppearance ? 0.4 : 0.3
|
||||||
|
}
|
||||||
|
if self.codeForegroundContentNode == nil, let contentNode = self.wallpaperBackgroundNode.makeDimmedNode() {
|
||||||
|
contentNode.frame = CGRect(origin: CGPoint(x: -self.codeForegroundNode.frame.minX, y: -self.codeForegroundNode.frame.minY), size: self.wallpaperBackgroundNode.frame.size)
|
||||||
|
self.codeForegroundContentNode = contentNode
|
||||||
|
self.codeForegroundNode.insertSubnode(contentNode, at: 0)
|
||||||
|
}
|
||||||
|
if isDarkAppearance && selectedEmoticon == defaultEmoticon, let codeForegroundContentNode = self.codeForegroundContentNode {
|
||||||
|
codeForegroundContentNode.removeFromSupernode()
|
||||||
|
self.codeForegroundContentNode = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
self.validLayout = (size, topInset, bottomInset)
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.wallpaperBackgroundNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
self.wallpaperBackgroundNode.updateLayout(size: size, transition: transition)
|
||||||
|
|
||||||
|
let textLength = self.codeTextNode.attributedText?.string.count ?? 0
|
||||||
|
|
||||||
|
var topInset = topInset
|
||||||
|
let avatarSize: CGSize
|
||||||
|
let codeInset: CGFloat
|
||||||
|
let imageSide: CGFloat
|
||||||
|
let fontSize: CGFloat
|
||||||
|
if size.width > 320.0 {
|
||||||
|
avatarSize = CGSize(width: 100.0, height: 100.0)
|
||||||
|
codeInset = 45.0
|
||||||
|
imageSide = 220.0
|
||||||
|
|
||||||
|
if size.width > 375.0 {
|
||||||
|
if textLength > 12 {
|
||||||
|
fontSize = 22.0
|
||||||
|
} else {
|
||||||
|
fontSize = 24.0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if textLength > 12 {
|
||||||
|
fontSize = 21.0
|
||||||
|
} else {
|
||||||
|
fontSize = 23.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
avatarSize = CGSize(width: 70.0, height: 70.0)
|
||||||
|
codeInset = 55.0
|
||||||
|
imageSide = 160.0
|
||||||
|
topInset = floor(topInset * 0.6)
|
||||||
|
if textLength > 12 {
|
||||||
|
fontSize = 18.0
|
||||||
|
} else {
|
||||||
|
fontSize = 20.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.codeTextNode.attributedText = NSAttributedString(string: self.codeTextNode.attributedText?.string ?? "", font: Font.with(size: fontSize, design: .round, weight: .bold, traits: []), textColor: .black)
|
||||||
|
|
||||||
|
let codeBackgroundWidth = size.width - codeInset * 2.0
|
||||||
let codeBackgroundHeight = floor(codeBackgroundWidth * 1.1)
|
let codeBackgroundHeight = floor(codeBackgroundWidth * 1.1)
|
||||||
let codeBackgroundFrame = CGRect(x: codeInset, y: floor((layout.size.height - contentHeight - codeBackgroundHeight) / 2.0) + 44.0, width: codeBackgroundWidth, height: codeBackgroundHeight)
|
let codeBackgroundFrame = CGRect(x: codeInset, y: topInset + floor((size.height - bottomInset - codeBackgroundHeight) / 2.0), width: codeBackgroundWidth, height: codeBackgroundHeight)
|
||||||
transition.updateFrame(node: self.codeBackgroundNode, frame: codeBackgroundFrame)
|
transition.updateFrame(node: self.codeBackgroundNode, frame: codeBackgroundFrame)
|
||||||
transition.updateFrame(node: self.codeForegroundNode, frame: codeBackgroundFrame)
|
transition.updateFrame(node: self.codeForegroundNode, frame: codeBackgroundFrame)
|
||||||
transition.updateFrame(node: self.codeMaskNode, frame: CGRect(origin: CGPoint(), size: codeBackgroundFrame.size))
|
transition.updateFrame(node: self.codeMaskNode, frame: CGRect(origin: CGPoint(), size: codeBackgroundFrame.size))
|
||||||
@ -1290,33 +1533,38 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
|
|||||||
}
|
}
|
||||||
|
|
||||||
let makeImageLayout = self.codeImageNode.asyncLayout()
|
let makeImageLayout = self.codeImageNode.asyncLayout()
|
||||||
let imageSide: CGFloat = 220.0
|
|
||||||
let imageSize = CGSize(width: imageSide, height: imageSide)
|
let imageSize = CGSize(width: imageSide, height: imageSide)
|
||||||
let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: nil))
|
let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: nil, scale: self.isStatic ? 3.0 : nil ))
|
||||||
let _ = imageApply()
|
let _ = imageApply()
|
||||||
|
|
||||||
let imageFrame = CGRect(origin: CGPoint(x: floor((codeBackgroundFrame.width - imageSize.width) / 2.0), y: floor((codeBackgroundFrame.width - imageSize.height) / 2.0)), size: imageSize)
|
let imageFrame = CGRect(origin: CGPoint(x: floor((codeBackgroundFrame.width - imageSize.width) / 2.0), y: floor((codeBackgroundFrame.width - imageSize.height) / 2.0)), size: imageSize)
|
||||||
transition.updateFrame(node: self.codeImageNode, frame: imageFrame)
|
transition.updateFrame(node: self.codeImageNode, frame: imageFrame)
|
||||||
|
|
||||||
let codeTextSize = self.codeTextNode.updateLayout(codeBackgroundFrame.size)
|
let codeTextSize = self.codeTextNode.updateLayout(CGSize(width: codeBackgroundFrame.width - floor(imageFrame.minX * 1.5), height: codeBackgroundFrame.height))
|
||||||
transition.updateFrame(node: self.codeTextNode, frame: CGRect(origin: CGPoint(x: floor((codeBackgroundFrame.width - codeTextSize.width) / 2.0), y: imageFrame.maxY + floor((codeBackgroundHeight - imageFrame.maxY - codeTextSize.height) / 2.0) - 7.0), size: codeTextSize))
|
transition.updateFrame(node: self.codeTextNode, frame: CGRect(origin: CGPoint(x: floor((codeBackgroundFrame.width - codeTextSize.width) / 2.0), y: imageFrame.maxY + floor((codeBackgroundHeight - imageFrame.maxY - codeTextSize.height) / 2.0) - 5.0), size: codeTextSize))
|
||||||
|
|
||||||
let avatarSize = CGSize(width: 100.0, height: 100.0)
|
transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0), y: codeBackgroundFrame.minY - floor(avatarSize.height * 0.7)), size: avatarSize))
|
||||||
transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - avatarSize.width) / 2.0), y: codeBackgroundFrame.minY - 70.0), size: avatarSize))
|
|
||||||
|
|
||||||
if let qrCodeSize = self.qrCodeSize {
|
if let qrCodeSize = self.qrCodeSize {
|
||||||
let (_, cutoutFrame, _) = qrCodeCutout(size: qrCodeSize, dimensions: imageSize, scale: nil)
|
let (_, cutoutFrame, _) = qrCodeCutout(size: qrCodeSize, dimensions: imageSize, scale: nil)
|
||||||
self.codeIconNode.updateLayout(size: cutoutFrame.size)
|
let imageCenter = imageFrame.center.offsetBy(dx: codeBackgroundFrame.minX, dy: codeBackgroundFrame.minY)
|
||||||
|
|
||||||
|
if let codeStaticIconNode = self.codeStaticIconNode {
|
||||||
|
transition.updateBounds(node: codeStaticIconNode, bounds: CGRect(origin: CGPoint(), size: cutoutFrame.size))
|
||||||
|
transition.updatePosition(node: codeStaticIconNode, position: imageCenter.offsetBy(dx: 0.0, dy: -1.0))
|
||||||
|
} else if let codeAnimatedIconNode = self.codeAnimatedIconNode {
|
||||||
|
codeAnimatedIconNode.updateLayout(size: cutoutFrame.size)
|
||||||
|
|
||||||
|
transition.updateBounds(node: codeAnimatedIconNode, bounds: CGRect(origin: CGPoint(), size: cutoutFrame.size))
|
||||||
|
transition.updatePosition(node: codeAnimatedIconNode, position: imageCenter.offsetBy(dx: 0.0, dy: -1.0))
|
||||||
|
}
|
||||||
|
|
||||||
let backgroundSize = CGSize(width: floorToScreenPixels(cutoutFrame.width - 8.0), height: floorToScreenPixels(cutoutFrame.height - 8.0))
|
let backgroundSize = CGSize(width: floorToScreenPixels(cutoutFrame.width - 8.0), height: floorToScreenPixels(cutoutFrame.height - 8.0))
|
||||||
transition.updateFrame(node: self.codeIconBackgroundNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels(imageFrame.center.x - backgroundSize.width / 2.0), y: floorToScreenPixels(imageFrame.center.y - backgroundSize.height / 2.0)), size: backgroundSize))
|
transition.updateFrame(node: self.codeIconBackgroundNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels(imageFrame.center.x - backgroundSize.width / 2.0), y: floorToScreenPixels(imageFrame.center.y - backgroundSize.height / 2.0)), size: backgroundSize))
|
||||||
if self.codeIconBackgroundNode.image == nil {
|
if self.codeIconBackgroundNode.image == nil {
|
||||||
self.codeIconBackgroundNode.image = generateFilledCircleImage(diameter: backgroundSize.width, color: .black)
|
self.codeIconBackgroundNode.image = generateFilledCircleImage(diameter: backgroundSize.width, color: .black)
|
||||||
}
|
}
|
||||||
|
|
||||||
let imageCenter = imageFrame.center.offsetBy(dx: codeBackgroundFrame.minX, dy: codeBackgroundFrame.minY)
|
|
||||||
transition.updateBounds(node: self.codeIconNode, bounds: CGRect(origin: CGPoint(), size: cutoutFrame.size))
|
|
||||||
transition.updatePosition(node: self.codeIconNode, position: imageCenter.offsetBy(dx: 0.0, dy: -1.0))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -185,15 +185,7 @@ private func matchingEmojiEntry(_ emoji: String) -> (UInt8, UInt8, UInt8)? {
|
|||||||
|
|
||||||
func messageIsElligibleForLargeEmoji(_ message: Message) -> Bool {
|
func messageIsElligibleForLargeEmoji(_ message: Message) -> Bool {
|
||||||
if !message.text.isEmpty && message.text.containsOnlyEmoji && message.text.emojis.count < 4 {
|
if !message.text.isEmpty && message.text.containsOnlyEmoji && message.text.emojis.count < 4 {
|
||||||
var messageEntities: [MessageTextEntity]?
|
if !(message.textEntitiesAttribute?.entities.isEmpty ?? true) {
|
||||||
for attribute in message.attributes {
|
|
||||||
if let attribute = attribute as? TextEntitiesMessageAttribute {
|
|
||||||
messageEntities = attribute.entities
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(messageEntities?.isEmpty ?? true) {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,20 +309,6 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let labelSize = self.labelNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude))
|
|
||||||
let textLayout = self.textNode.updateLayoutInfo(CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude))
|
|
||||||
let textSize = textLayout.size
|
|
||||||
|
|
||||||
if case .multiLine = item.textBehavior, textLayout.truncated, !self.isExpanded {
|
|
||||||
self.expandBackgroundNode.isHidden = false
|
|
||||||
self.expandNode.isHidden = false
|
|
||||||
self.expandButonNode.isHidden = false
|
|
||||||
} else {
|
|
||||||
self.expandBackgroundNode.isHidden = true
|
|
||||||
self.expandNode.isHidden = true
|
|
||||||
self.expandButonNode.isHidden = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if let icon = item.icon {
|
if let icon = item.icon {
|
||||||
let iconImage: UIImage?
|
let iconImage: UIImage?
|
||||||
switch icon {
|
switch icon {
|
||||||
@ -337,6 +323,21 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode {
|
|||||||
self.iconButtonNode.isHidden = true
|
self.iconButtonNode.isHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let additionalSideInset: CGFloat = !self.iconNode.isHidden ? 32.0 : 0.0
|
||||||
|
let labelSize = self.labelNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude))
|
||||||
|
let textLayout = self.textNode.updateLayoutInfo(CGSize(width: width - sideInset * 2.0 - additionalSideInset, height: .greatestFiniteMagnitude))
|
||||||
|
let textSize = textLayout.size
|
||||||
|
|
||||||
|
if case .multiLine = item.textBehavior, textLayout.truncated, !self.isExpanded {
|
||||||
|
self.expandBackgroundNode.isHidden = false
|
||||||
|
self.expandNode.isHidden = false
|
||||||
|
self.expandButonNode.isHidden = false
|
||||||
|
} else {
|
||||||
|
self.expandBackgroundNode.isHidden = true
|
||||||
|
self.expandNode.isHidden = true
|
||||||
|
self.expandButonNode.isHidden = true
|
||||||
|
}
|
||||||
|
|
||||||
let labelFrame = CGRect(origin: CGPoint(x: sideInset, y: 11.0), size: labelSize)
|
let labelFrame = CGRect(origin: CGPoint(x: sideInset, y: 11.0), size: labelSize)
|
||||||
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: labelFrame.maxY + 3.0), size: textSize)
|
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: labelFrame.maxY + 3.0), size: textSize)
|
||||||
|
|
||||||
|
@ -192,7 +192,7 @@ private final class PeerInfoScreenMemberItemNode: PeerInfoScreenItemNode {
|
|||||||
|
|
||||||
}, removePeer: { _ in
|
}, removePeer: { _ in
|
||||||
|
|
||||||
}, contextAction: item.contextAction, hasTopStripe: false, hasTopGroupInset: false, noInsets: true, displayDecorations: false)
|
}, contextAction: item.contextAction, hasTopStripe: false, hasTopGroupInset: false, noInsets: true, noCorners: true, displayDecorations: false)
|
||||||
|
|
||||||
let params = ListViewItemLayoutParams(width: width, leftInset: safeInsets.left, rightInset: safeInsets.right, availableHeight: 1000.0)
|
let params = ListViewItemLayoutParams(width: width, leftInset: safeInsets.left, rightInset: safeInsets.right, availableHeight: 1000.0)
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ private struct GroupsInCommonListEntry: Comparable, Identifiable {
|
|||||||
}, removePeer: { _ in
|
}, removePeer: { _ in
|
||||||
}, contextAction: { node, gesture in
|
}, contextAction: { node, gesture in
|
||||||
openPeerContextAction(peer, node, gesture)
|
openPeerContextAction(peer, node, gesture)
|
||||||
}, hasTopStripe: false, noInsets: true)
|
}, hasTopStripe: false, noInsets: true, noCorners: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3245,6 +3245,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
self?.paneContainerNode.currentPane?.node.ensureMessageIsVisible(id: messageId)
|
self?.paneContainerNode.currentPane?.node.ensureMessageIsVisible(id: messageId)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func openResolved(_ result: ResolvedUrl) {
|
private func openResolved(_ result: ResolvedUrl) {
|
||||||
guard let navigationController = self.controller?.navigationController as? NavigationController else {
|
guard let navigationController = self.controller?.navigationController as? NavigationController else {
|
||||||
return
|
return
|
||||||
@ -5479,35 +5480,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
guard let data = self.data, let peer = data.peer, let controller = self.controller else {
|
guard let data = self.data, let peer = data.peer, let controller = self.controller else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let animatedEmojiStickers = context.engine.stickers.loadedStickerPack(reference: .animatedEmoji, forceActualized: false)
|
controller.present(ChatQrCodeScreen(context: self.context, peer: peer), in: .window(.root))
|
||||||
|> map { animatedEmoji -> [String: [StickerPackItem]] in
|
|
||||||
var animatedEmojiStickers: [String: [StickerPackItem]] = [:]
|
|
||||||
switch animatedEmoji {
|
|
||||||
case let .result(_, items, _):
|
|
||||||
for item in items {
|
|
||||||
if let emoji = item.getStringRepresentationsOfIndexKeys().first {
|
|
||||||
animatedEmojiStickers[emoji.basicEmoji.0] = [item]
|
|
||||||
let strippedEmoji = emoji.basicEmoji.0.strippedEmoji
|
|
||||||
if animatedEmojiStickers[strippedEmoji] == nil {
|
|
||||||
animatedEmojiStickers[strippedEmoji] = [item]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return animatedEmojiStickers
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = (animatedEmojiStickers
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self, weak controller] animatedEmojiStickers in
|
|
||||||
if let strongSelf = self, let controller = controller {
|
|
||||||
controller.present(ChatQrCodeScreen(context: strongSelf.context, animatedEmojiStickers: animatedEmojiStickers, peer: peer), in: .window(.root))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// controller.present(QrCodeScreen(context: self.context, updatedPresentationData: controller.updatedPresentationData, subject: .peer(peer: EnginePeer(peer))), in: .window(.root))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func openSettings(section: PeerInfoSettingsSection) {
|
fileprivate func openSettings(section: PeerInfoSettingsSection) {
|
||||||
|
@ -1414,6 +1414,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
return recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: context.engine.privacy.webSessions(), websitesOnly: false)
|
return recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: context.engine.privacy.webSessions(), websitesOnly: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func makeChatQrCodeScreen(context: AccountContext, peer: Peer) -> ViewController {
|
||||||
|
return ChatQrCodeScreen(context: context, peer: peer)
|
||||||
|
}
|
||||||
|
|
||||||
public func makePrivacyAndSecurityController(context: AccountContext) -> ViewController {
|
public func makePrivacyAndSecurityController(context: AccountContext) -> ViewController {
|
||||||
return SettingsUI.makePrivacyAndSecurityController(context: context)
|
return SettingsUI.makePrivacyAndSecurityController(context: context)
|
||||||
}
|
}
|
||||||
|
@ -40,10 +40,11 @@ public func canTranslateText(context: AccountContext, text: String, showTranslat
|
|||||||
|
|
||||||
let text = String(text.prefix(64))
|
let text = String(text.prefix(64))
|
||||||
languageRecognizer.processString(text)
|
languageRecognizer.processString(text)
|
||||||
let hypotheses = languageRecognizer.languageHypotheses(withMaximum: 2)
|
let hypotheses = languageRecognizer.languageHypotheses(withMaximum: 3)
|
||||||
languageRecognizer.reset()
|
languageRecognizer.reset()
|
||||||
|
|
||||||
if let language = hypotheses.first(where: { supportedTranslationLanguages.contains($0.key.rawValue) }) {
|
let filteredLanguages = hypotheses.filter { supportedTranslationLanguages.contains($0.key.rawValue) }.sorted(by: { $0.value > $1.value })
|
||||||
|
if let language = filteredLanguages.first(where: { supportedTranslationLanguages.contains($0.key.rawValue) }) {
|
||||||
return !dontTranslateLanguages.contains(language.key.rawValue)
|
return !dontTranslateLanguages.contains(language.key.rawValue)
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
|
@ -1740,18 +1740,18 @@ private class WallpaperNewYearNode: ASDisplayNode {
|
|||||||
let cell1 = CAEmitterCell()
|
let cell1 = CAEmitterCell()
|
||||||
cell1.contents = UIImage(bundleImageName: "Components/Snowflake")?.cgImage
|
cell1.contents = UIImage(bundleImageName: "Components/Snowflake")?.cgImage
|
||||||
cell1.name = "snow"
|
cell1.name = "snow"
|
||||||
cell1.birthRate = 352.0
|
cell1.birthRate = 252.0
|
||||||
cell1.lifetime = 20.0
|
cell1.lifetime = 20.0
|
||||||
cell1.velocity = 39.0
|
cell1.velocity = 19.0
|
||||||
cell1.velocityRange = -15.0
|
cell1.velocityRange = -5.0
|
||||||
cell1.xAcceleration = 5.0
|
cell1.xAcceleration = 2.5
|
||||||
cell1.yAcceleration = 25.0
|
cell1.yAcceleration = 10.0
|
||||||
cell1.emissionRange = .pi
|
cell1.emissionRange = .pi
|
||||||
cell1.spin = -28.6 * (.pi / 180.0)
|
cell1.spin = -28.6 * (.pi / 180.0)
|
||||||
cell1.spinRange = 57.2 * (.pi / 180.0)
|
cell1.spinRange = 57.2 * (.pi / 180.0)
|
||||||
cell1.scale = 0.04
|
cell1.scale = 0.04
|
||||||
cell1.scaleRange = 0.15
|
cell1.scaleRange = 0.15
|
||||||
cell1.color = UIColor.white.withAlphaComponent(0.88).cgColor
|
cell1.color = UIColor.white.withAlphaComponent(0.58).cgColor
|
||||||
// cell1.alphaRange = -0.2
|
// cell1.alphaRange = -0.2
|
||||||
|
|
||||||
particlesLayer.emitterCells = [cell1]
|
particlesLayer.emitterCells = [cell1]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user