Various Improvements

This commit is contained in:
Ilya Laktyushin 2021-12-23 13:18:00 +04:00
parent 888e5e1474
commit ed7cf17233
38 changed files with 1506 additions and 265 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,9 @@
{ {
"info" : { "info" : {
"version" : 1, "author" : "xcode",
"author" : "xcode" "version" : 1
}, },
"properties" : { "properties" : {
"provides-namespace" : true "provides-namespace" : true
} }
} }

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "instagram_60.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View 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

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_qrlogo.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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