mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge commit '314f02d36d'
This commit is contained in:
commit
a4e9b0468e
34 changed files with 670 additions and 311 deletions
|
|
@ -423,16 +423,8 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
|||
case "oauth_request":
|
||||
let url = json?["url"] as? String
|
||||
if let url {
|
||||
let securityOrigin = message.frameInfo.securityOrigin
|
||||
var origin = ""
|
||||
origin.append(securityOrigin.protocol)
|
||||
origin.append("://")
|
||||
origin.append(securityOrigin.host)
|
||||
if securityOrigin.port != 0 {
|
||||
origin.append(":")
|
||||
origin.append("\(securityOrigin.port)")
|
||||
}
|
||||
|
||||
let origin = message.frameInfo.securityOriginString
|
||||
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let subject: MessageActionUrlSubject = .url(url: url, inAppOrigin: origin)
|
||||
let _ = (self.context.engine.messages.requestMessageActionUrlAuth(subject: subject)
|
||||
|
|
@ -2035,3 +2027,18 @@ private func findScrollView(view: UIView?) -> UIScrollView? {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private extension WKFrameInfo {
|
||||
var securityOriginString: String {
|
||||
let securityOrigin = self.securityOrigin
|
||||
var origin = ""
|
||||
origin.append(securityOrigin.protocol)
|
||||
origin.append("://")
|
||||
origin.append(securityOrigin.host)
|
||||
if securityOrigin.port != 0 {
|
||||
origin.append(":")
|
||||
origin.append("\(securityOrigin.port)")
|
||||
}
|
||||
return origin
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -475,6 +475,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||
private var credibilityIconComponent: EmojiStatusComponent?
|
||||
private var verifiedIconView: ComponentHostView<Empty>?
|
||||
private var verifiedIconComponent: EmojiStatusComponent?
|
||||
private var emojiStatusIconView: ComponentHostView<Empty>?
|
||||
private var emojiStatusIconComponent: EmojiStatusComponent?
|
||||
public let statusNode: TextNodeWithEntities
|
||||
private var statusIconNode: ASImageNode?
|
||||
private var badgeBackgroundNode: ASImageNode?
|
||||
|
|
@ -552,6 +554,14 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||
containerSize: verifiedIconView.bounds.size
|
||||
)
|
||||
}
|
||||
if let emojiStatusIconView = self.emojiStatusIconView, let emojiStatusIconComponent = self.emojiStatusIconComponent {
|
||||
let _ = emojiStatusIconView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(emojiStatusIconComponent.withVisibleForAnimations(self.visibilityStatus)),
|
||||
environment: {},
|
||||
containerSize: emojiStatusIconView.bounds.size
|
||||
)
|
||||
}
|
||||
if let avatarIconView = self.avatarIconView, let avatarIconComponent = self.avatarIconComponent {
|
||||
let _ = avatarIconView.update(
|
||||
transition: .immediate,
|
||||
|
|
@ -840,27 +850,29 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 })
|
||||
|
||||
var credibilityIcon: EmojiStatusComponent.Content?
|
||||
var credibilityParticleColor: UIColor?
|
||||
var credibilityStatusIcon: EmojiStatusComponent.Content?
|
||||
var verifiedIcon: EmojiStatusComponent.Content?
|
||||
var emojiStatusIcon: EmojiStatusComponent.Content?
|
||||
var emojiStatusParticleColor: UIColor?
|
||||
|
||||
switch item.peer {
|
||||
case let .peer(peer, _):
|
||||
if let peer = peer, (peer.id != item.context.account.peerId || item.peerMode == .memberList || item.aliasHandling == .standard) {
|
||||
if peer.isScam {
|
||||
credibilityIcon = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_ScamAccount.uppercased())
|
||||
credibilityStatusIcon = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_ScamAccount.uppercased())
|
||||
} else if peer.isFake {
|
||||
credibilityIcon = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_FakeAccount.uppercased())
|
||||
credibilityStatusIcon = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_FakeAccount.uppercased())
|
||||
} else if let emojiStatus = peer.emojiStatus, !item.isAd {
|
||||
credibilityIcon = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 20.0, height: 20.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2))
|
||||
emojiStatusIcon = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 20.0, height: 20.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2))
|
||||
if let color = emojiStatus.color {
|
||||
credibilityParticleColor = UIColor(rgb: UInt32(bitPattern: color))
|
||||
emojiStatusParticleColor = UIColor(rgb: UInt32(bitPattern: color))
|
||||
}
|
||||
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
|
||||
credibilityIcon = .premium(color: item.presentationData.theme.list.itemAccentColor)
|
||||
credibilityStatusIcon = .premium(color: item.presentationData.theme.list.itemAccentColor)
|
||||
}
|
||||
|
||||
if peer.isVerified {
|
||||
credibilityIcon = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .compact)
|
||||
credibilityStatusIcon = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .compact)
|
||||
}
|
||||
if let verificationIconFileId = peer.verificationIconFileId {
|
||||
verifiedIcon = .animation(content: .customEmoji(fileId: verificationIconFileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(0))
|
||||
|
|
@ -1105,9 +1117,9 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||
additionalTitleInset += 16.0
|
||||
}
|
||||
}
|
||||
if let credibilityIcon {
|
||||
if let credibilityStatusIcon {
|
||||
additionalTitleInset += 3.0
|
||||
switch credibilityIcon {
|
||||
switch credibilityStatusIcon {
|
||||
case let .text(_, string):
|
||||
let textString = NSAttributedString(string: string, font: Font.bold(10.0), textColor: .black, paragraphAlignment: .center)
|
||||
let stringRect = textString.boundingRect(with: CGSize(width: 100.0, height: 16.0), options: .usesLineFragmentOrigin, context: nil)
|
||||
|
|
@ -1116,6 +1128,10 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||
additionalTitleInset += 16.0
|
||||
}
|
||||
}
|
||||
if let _ = emojiStatusIcon {
|
||||
additionalTitleInset += 3.0
|
||||
additionalTitleInset += 16.0
|
||||
}
|
||||
if let actionButtons = actionButtons {
|
||||
additionalTitleInset += 3.0
|
||||
for actionButton in actionButtons {
|
||||
|
|
@ -1587,7 +1603,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||
}
|
||||
}
|
||||
|
||||
if let credibilityIcon {
|
||||
if let credibilityStatusIcon {
|
||||
let animationCache = item.context.animationCache
|
||||
let animationRenderer = item.context.animationRenderer
|
||||
|
||||
|
|
@ -1604,8 +1620,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||
context: item.context,
|
||||
animationCache: animationCache,
|
||||
animationRenderer: animationRenderer,
|
||||
content: credibilityIcon,
|
||||
particleColor: credibilityParticleColor,
|
||||
content: credibilityStatusIcon,
|
||||
particleColor: nil,
|
||||
isVisibleForAnimations: strongSelf.visibilityStatus,
|
||||
action: nil,
|
||||
emojiFileUpdated: nil
|
||||
|
|
@ -1627,6 +1643,46 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||
credibilityIconView.removeFromSuperview()
|
||||
}
|
||||
|
||||
if let emojiStatusIcon {
|
||||
let animationCache = item.context.animationCache
|
||||
let animationRenderer = item.context.animationRenderer
|
||||
|
||||
let emojiStatusIconView: ComponentHostView<Empty>
|
||||
if let current = strongSelf.emojiStatusIconView {
|
||||
emojiStatusIconView = current
|
||||
} else {
|
||||
emojiStatusIconView = ComponentHostView<Empty>()
|
||||
strongSelf.offsetContainerNode.view.addSubview(emojiStatusIconView)
|
||||
strongSelf.emojiStatusIconView = emojiStatusIconView
|
||||
}
|
||||
|
||||
let emojiStatusIconComponent = EmojiStatusComponent(
|
||||
context: item.context,
|
||||
animationCache: animationCache,
|
||||
animationRenderer: animationRenderer,
|
||||
content: emojiStatusIcon,
|
||||
particleColor: emojiStatusParticleColor,
|
||||
isVisibleForAnimations: strongSelf.visibilityStatus,
|
||||
action: nil,
|
||||
emojiFileUpdated: nil
|
||||
)
|
||||
strongSelf.emojiStatusIconComponent = emojiStatusIconComponent
|
||||
|
||||
let iconSize = emojiStatusIconView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(emojiStatusIconComponent),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 16.0, height: 16.0)
|
||||
)
|
||||
|
||||
nextIconX += 4.0
|
||||
transition.updateFrame(view: emojiStatusIconView, frame: CGRect(origin: CGPoint(x: nextIconX, y: floorToScreenPixels(titleFrame.midY - iconSize.height / 2.0)), size: iconSize))
|
||||
nextIconX += iconSize.width
|
||||
} else if let emojiStatusIconView = strongSelf.emojiStatusIconView {
|
||||
strongSelf.emojiStatusIconView = nil
|
||||
emojiStatusIconView.removeFromSuperview()
|
||||
}
|
||||
|
||||
if let (titleBadgeLayout, titleBadgeApply) = titleBadgeLayoutAndApply {
|
||||
let titleBadgeNode = titleBadgeApply()
|
||||
let backgroundView: UIImageView
|
||||
|
|
|
|||
|
|
@ -95,7 +95,8 @@ private func descriptionWithUrl(_ url: URL) -> ICloudFileDescription? {
|
|||
}
|
||||
|
||||
var audioMetadata: ICloudFileDescription.AudioMetadata?
|
||||
if ["mp3", "m4a"].contains(url.pathExtension.lowercased()) {
|
||||
let fileExtension = url.pathExtension.lowercased()
|
||||
if ["mp3", "m4a"].contains(fileExtension) {
|
||||
let asset = AVURLAsset(url: url)
|
||||
let title = AVMetadataItem.metadataItems(from: asset.commonMetadata, withKey: AVMetadataKey.commonKeyTitle, keySpace: AVMetadataKeySpace.common).first?.stringValue
|
||||
let performer = AVMetadataItem.metadataItems(from: asset.commonMetadata, withKey: AVMetadataKey.commonKeyArtist, keySpace: AVMetadataKeySpace.common).first?.stringValue
|
||||
|
|
@ -103,6 +104,26 @@ private func descriptionWithUrl(_ url: URL) -> ICloudFileDescription? {
|
|||
if duration > 0 {
|
||||
audioMetadata = ICloudFileDescription.AudioMetadata(title: title, performer: performer, duration: Int(duration))
|
||||
}
|
||||
} else if fileExtension == "flac" {
|
||||
let asset = AVURLAsset(url: url)
|
||||
var title: String?
|
||||
var performer: String?
|
||||
let vorbisComment = AVMetadataFormat(rawValue: "org.xiph.vorbis-comment")
|
||||
if asset.availableMetadataFormats.contains(vorbisComment) {
|
||||
let items = asset.metadata(forFormat: vorbisComment)
|
||||
for item in items {
|
||||
if item.commonKey == AVMetadataKey.commonKeyTitle {
|
||||
title = item.stringValue
|
||||
}
|
||||
if item.commonKey == AVMetadataKey.commonKeyArtist {
|
||||
performer = item.stringValue
|
||||
}
|
||||
}
|
||||
}
|
||||
let duration = CMTimeGetSeconds(asset.duration)
|
||||
if duration > 0 {
|
||||
audioMetadata = ICloudFileDescription.AudioMetadata(title: title, performer: performer, duration: Int(duration))
|
||||
}
|
||||
}
|
||||
|
||||
let result = ICloudFileDescription(urlData: urlData.base64EncodedString(), fileName: fileName, fileSize: fileSize, audioMetadata: audioMetadata)
|
||||
|
|
|
|||
|
|
@ -16,97 +16,157 @@ private struct Asn1Entry {
|
|||
let length: Int
|
||||
}
|
||||
|
||||
private func parse(_ data: Data, startIndex: Int = 0) -> Asn1Entry {
|
||||
private func dataByte(_ data: Data, at index: Int) -> UInt8? {
|
||||
guard index >= 0 && index < data.count else {
|
||||
return nil
|
||||
}
|
||||
return data[index]
|
||||
}
|
||||
|
||||
private func parse(_ data: Data, startIndex: Int = 0) -> Asn1Entry? {
|
||||
guard startIndex >= 0 && startIndex < data.count else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var index = startIndex
|
||||
var value = data[index]
|
||||
guard var value = dataByte(data, at: index) else {
|
||||
return nil
|
||||
}
|
||||
index += 1
|
||||
var tagValue = Int32(value & 0x1f)
|
||||
if tagValue == 31 {
|
||||
value = data[index]
|
||||
index += 1
|
||||
while (value & 0x80) != 0 {
|
||||
repeat {
|
||||
guard let nextValue = dataByte(data, at: index) else {
|
||||
return nil
|
||||
}
|
||||
value = nextValue
|
||||
index += 1
|
||||
guard tagValue <= Int32.max >> 8 else {
|
||||
return nil
|
||||
}
|
||||
tagValue <<= 8
|
||||
tagValue |= Int32(value & 0x7f)
|
||||
value = data[index]
|
||||
index += 1
|
||||
}
|
||||
tagValue <<= 8
|
||||
tagValue |= Int32(value & 0x7f)
|
||||
} while (value & 0x80) != 0
|
||||
}
|
||||
|
||||
var length = 0
|
||||
var nextTag = 0
|
||||
value = data[index]
|
||||
guard let lengthValue = dataByte(data, at: index) else {
|
||||
return nil
|
||||
}
|
||||
value = lengthValue
|
||||
let isIndefiniteLength = value == 0x80
|
||||
index += 1
|
||||
if value & 0x80 == 0 {
|
||||
if !isIndefiniteLength && value & 0x80 == 0 {
|
||||
length = Int(value)
|
||||
nextTag = index + length
|
||||
} else if value != 0x80 {
|
||||
} else if !isIndefiniteLength {
|
||||
let octetsCount = Int(value & 0x7f)
|
||||
guard octetsCount > 0 else {
|
||||
return nil
|
||||
}
|
||||
for _ in 0 ..< octetsCount {
|
||||
guard length <= Int.max >> 8 else {
|
||||
return nil
|
||||
}
|
||||
length <<= 8
|
||||
value = data[index]
|
||||
guard let nextValue = dataByte(data, at: index) else {
|
||||
return nil
|
||||
}
|
||||
value = nextValue
|
||||
index += 1
|
||||
length |= Int(value) & 0xff
|
||||
}
|
||||
nextTag = index + length
|
||||
} else {
|
||||
var scanIndex = index
|
||||
while data[scanIndex] != 0 && data[scanIndex + 1] != 0 {
|
||||
while true {
|
||||
guard scanIndex + 1 < data.count else {
|
||||
return nil
|
||||
}
|
||||
if data[scanIndex] == 0 && data[scanIndex + 1] == 0 {
|
||||
break
|
||||
}
|
||||
scanIndex += 1
|
||||
}
|
||||
length = scanIndex - index
|
||||
nextTag = scanIndex + 2
|
||||
}
|
||||
return Asn1Entry(tag: tagValue, data: data.subdata(in: index ..< (index + length)), length: nextTag - startIndex)
|
||||
|
||||
guard length >= 0, length <= data.count - index else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let payloadEndIndex = index + length
|
||||
let entryEndIndex: Int
|
||||
if isIndefiniteLength {
|
||||
entryEndIndex = payloadEndIndex + 2
|
||||
guard entryEndIndex <= data.count else {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
entryEndIndex = payloadEndIndex
|
||||
}
|
||||
|
||||
return Asn1Entry(tag: tagValue, data: data.subdata(in: index ..< payloadEndIndex), length: entryEndIndex - startIndex)
|
||||
}
|
||||
|
||||
private func parseSequence(_ data: Data) -> [Asn1Entry] {
|
||||
private func parseSequence(_ data: Data) -> [Asn1Entry]? {
|
||||
var result : [Asn1Entry] = []
|
||||
var index = 0
|
||||
while index < data.count {
|
||||
let entry = parse(data, startIndex: index)
|
||||
guard let entry = parse(data, startIndex: index), entry.length > 0 else {
|
||||
return nil
|
||||
}
|
||||
result.append(entry)
|
||||
index += entry.length
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private func parseInteger(_ data: Data) -> Int32 {
|
||||
let length = data.count
|
||||
var value: Int32 = 0
|
||||
for i in 0 ..< length {
|
||||
if i == 0 {
|
||||
value = Int32(data[i] & 0x7f)
|
||||
} else {
|
||||
value <<= 8
|
||||
value |= Int32(data[i])
|
||||
}
|
||||
private func parseInteger(_ data: Data) -> Int32? {
|
||||
guard !data.isEmpty, data.count <= MemoryLayout<Int32>.size else {
|
||||
return nil
|
||||
}
|
||||
if length > 0 && data[0] & 0x80 != 0 {
|
||||
let complement: Int32 = 1 << (length * 8)
|
||||
value -= complement
|
||||
|
||||
var value: UInt32 = 0
|
||||
for byte in data {
|
||||
value = (value << 8) | UInt32(byte)
|
||||
}
|
||||
return value
|
||||
|
||||
if let firstByte = data.first, firstByte & 0x80 != 0 && data.count < MemoryLayout<UInt32>.size {
|
||||
let shift = UInt32(data.count * 8)
|
||||
value |= ~UInt32(0) << shift
|
||||
}
|
||||
|
||||
return Int32(bitPattern: value)
|
||||
}
|
||||
|
||||
private func parseObjectIdentifier(_ data: Data, startIndex: Int = 0, length: Int? = nil) -> [Int32] {
|
||||
private func parseObjectIdentifier(_ data: Data, startIndex: Int = 0, length: Int? = nil) -> [Int32]? {
|
||||
let dataLen = length ?? data.count
|
||||
guard startIndex >= 0, dataLen > 0, startIndex <= data.count, dataLen <= data.count - startIndex else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let endIndex = startIndex + dataLen
|
||||
var index = startIndex
|
||||
var identifier: [Int32] = []
|
||||
while index < startIndex + dataLen {
|
||||
var subidentifier: Int32 = 0
|
||||
var value = data[index]
|
||||
index += 1
|
||||
while (value & 0x80) != 0 {
|
||||
subidentifier <<= 7
|
||||
subidentifier |= Int32(value & 0x7f)
|
||||
value = data[index]
|
||||
while index < endIndex {
|
||||
var subidentifier: Int64 = 0
|
||||
while true {
|
||||
guard let value = dataByte(data, at: index) else {
|
||||
return nil
|
||||
}
|
||||
index += 1
|
||||
guard subidentifier <= Int64(Int32.max >> 7) else {
|
||||
return nil
|
||||
}
|
||||
subidentifier <<= 7
|
||||
subidentifier |= Int64(value & 0x7f)
|
||||
if (value & 0x80) == 0 {
|
||||
break
|
||||
}
|
||||
guard index < endIndex else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
subidentifier <<= 7
|
||||
subidentifier |= Int32(value & 0x7f)
|
||||
identifier.append(subidentifier)
|
||||
identifier.append(Int32(subidentifier))
|
||||
}
|
||||
return identifier
|
||||
}
|
||||
|
|
@ -137,51 +197,71 @@ struct Receipt {
|
|||
}
|
||||
|
||||
func parseReceipt(_ data: Data) -> Receipt? {
|
||||
let root = parseSequence(data)
|
||||
guard let root = parseSequence(data) else {
|
||||
return nil
|
||||
}
|
||||
guard root.count == 1 && root[0].tag == Asn1Tag.sequence else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let rootSeq = parseSequence(root[0].data)
|
||||
guard let rootSeq = parseSequence(root[0].data) else {
|
||||
return nil
|
||||
}
|
||||
guard rootSeq.count == 2 && rootSeq[0].tag == Asn1Tag.objectIdentifier && parseObjectIdentifier(rootSeq[0].data) == ObjectIdentifier.pkcs7SignedData else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let signedData = parseSequence(rootSeq[1].data)
|
||||
guard let signedData = parseSequence(rootSeq[1].data) else {
|
||||
return nil
|
||||
}
|
||||
guard signedData.count == 1 && signedData[0].tag == Asn1Tag.sequence else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let signedDataSeq = parseSequence(signedData[0].data)
|
||||
guard let signedDataSeq = parseSequence(signedData[0].data) else {
|
||||
return nil
|
||||
}
|
||||
guard signedDataSeq.count > 3 && signedDataSeq[2].tag == Asn1Tag.sequence else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let contentData = parseSequence(signedDataSeq[2].data)
|
||||
guard let contentData = parseSequence(signedDataSeq[2].data) else {
|
||||
return nil
|
||||
}
|
||||
guard contentData.count == 2 && contentData[0].tag == Asn1Tag.objectIdentifier && parseObjectIdentifier(contentData[0].data) == ObjectIdentifier.pkcs7Data else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let payload = parse(contentData[1].data)
|
||||
guard let payload = parse(contentData[1].data) else {
|
||||
return nil
|
||||
}
|
||||
guard payload.tag == Asn1Tag.octetString else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let payloadRoot = parse(payload.data)
|
||||
guard let payloadRoot = parse(payload.data) else {
|
||||
return nil
|
||||
}
|
||||
guard payloadRoot.tag == Asn1Tag.set else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var purchases: [Receipt.Purchase] = []
|
||||
|
||||
let receiptAttributes = parseSequence(payloadRoot.data)
|
||||
guard let receiptAttributes = parseSequence(payloadRoot.data) else {
|
||||
return nil
|
||||
}
|
||||
for attribute in receiptAttributes {
|
||||
if attribute.tag != Asn1Tag.sequence { continue }
|
||||
let attributeEntries = parseSequence(attribute.data)
|
||||
guard let attributeEntries = parseSequence(attribute.data) else {
|
||||
return nil
|
||||
}
|
||||
guard attributeEntries.count == 3 && attributeEntries[0].tag == Asn1Tag.integer && attributeEntries[1].tag == Asn1Tag.integer && attributeEntries[2].tag == Asn1Tag.octetString else { return nil
|
||||
}
|
||||
|
||||
let type = parseInteger(attributeEntries[0].data)
|
||||
guard let type = parseInteger(attributeEntries[0].data) else {
|
||||
return nil
|
||||
}
|
||||
let value = attributeEntries[2].data
|
||||
switch (type) {
|
||||
case Receipt.Tag.purchases:
|
||||
|
|
@ -202,22 +282,41 @@ private func parseRfc3339Date(_ str: String) -> Date? {
|
|||
formatter1.locale = posixLocale
|
||||
formatter1.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssX5"
|
||||
formatter1.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
|
||||
let result = formatter1.date(from: str)
|
||||
|
||||
var result = formatter1.date(from: str)
|
||||
if result != nil {
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
let formatter2 = DateFormatter()
|
||||
formatter2.locale = posixLocale
|
||||
formatter2.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSSSSSX5"
|
||||
formatter2.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
|
||||
result = formatter2.date(from: str)
|
||||
if result != nil {
|
||||
return result
|
||||
}
|
||||
|
||||
return formatter2.date(from: str)
|
||||
let formatterWithFractionalSeconds = ISO8601DateFormatter()
|
||||
formatterWithFractionalSeconds.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
formatterWithFractionalSeconds.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
||||
|
||||
if let result = formatterWithFractionalSeconds.date(from: str) {
|
||||
return result
|
||||
}
|
||||
|
||||
let formatterWithoutFractionalSeconds = ISO8601DateFormatter()
|
||||
formatterWithoutFractionalSeconds.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
formatterWithoutFractionalSeconds.formatOptions = [.withInternetDateTime]
|
||||
|
||||
return formatterWithoutFractionalSeconds.date(from: str)
|
||||
}
|
||||
|
||||
private func parsePurchaseAttributes(_ data: Data) -> Receipt.Purchase? {
|
||||
let root = parse(data)
|
||||
guard let root = parse(data) else {
|
||||
return nil
|
||||
}
|
||||
guard root.tag == Asn1Tag.set else {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -226,26 +325,38 @@ private func parsePurchaseAttributes(_ data: Data) -> Receipt.Purchase? {
|
|||
var transactionId: String?
|
||||
var expirationDate: Date?
|
||||
|
||||
let receiptAttributes = parseSequence(root.data)
|
||||
guard let receiptAttributes = parseSequence(root.data) else {
|
||||
return nil
|
||||
}
|
||||
for attribute in receiptAttributes {
|
||||
if attribute.tag != Asn1Tag.sequence { continue }
|
||||
let attributeEntries = parseSequence(attribute.data)
|
||||
guard let attributeEntries = parseSequence(attribute.data) else {
|
||||
return nil
|
||||
}
|
||||
guard attributeEntries.count == 3 && attributeEntries[0].tag == Asn1Tag.integer && attributeEntries[1].tag == Asn1Tag.integer && attributeEntries[2].tag == Asn1Tag.octetString else { return nil
|
||||
}
|
||||
|
||||
let type = parseInteger(attributeEntries[0].data)
|
||||
guard let type = parseInteger(attributeEntries[0].data) else {
|
||||
return nil
|
||||
}
|
||||
let value = attributeEntries[2].data
|
||||
switch (type) {
|
||||
case Receipt.Purchase.Tag.productIdentifier:
|
||||
let valEntry = parse(value)
|
||||
guard let valEntry = parse(value) else {
|
||||
return nil
|
||||
}
|
||||
guard valEntry.tag == Asn1Tag.utf8String else { return nil }
|
||||
productId = String(bytes: valEntry.data, encoding: .utf8)
|
||||
case Receipt.Purchase.Tag.transactionIdentifier:
|
||||
let valEntry = parse(value)
|
||||
guard let valEntry = parse(value) else {
|
||||
return nil
|
||||
}
|
||||
guard valEntry.tag == Asn1Tag.utf8String else { return nil }
|
||||
transactionId = String(bytes: valEntry.data, encoding: .utf8)
|
||||
case Receipt.Purchase.Tag.expirationDate:
|
||||
let valEntry = parse(value)
|
||||
guard let valEntry = parse(value) else {
|
||||
return nil
|
||||
}
|
||||
guard valEntry.tag == Asn1Tag.date else { return nil }
|
||||
expirationDate = parseRfc3339Date(String(bytes: valEntry.data, encoding: .utf8) ?? "")
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -952,6 +952,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
|||
|
||||
if item.peer.isVerified {
|
||||
credibilityIcon = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .compact)
|
||||
credibilityParticleColor = nil
|
||||
}
|
||||
if let verificationIconFileId = item.peer.verificationIconFileId {
|
||||
verifiedIcon = .animation(content: .customEmoji(fileId: verificationIconFileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(0))
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ static void initializeMapping()
|
|||
mimeToExtension[@"application/x-dms"] = @"dms"; extensionToMime[@"dms"] = @"application/x-dms";
|
||||
mimeToExtension[@"application/x-doom"] = @"wad"; extensionToMime[@"wad"] = @"application/x-doom";
|
||||
mimeToExtension[@"application/x-dvi"] = @"dvi"; extensionToMime[@"dvi"] = @"application/x-dvi";
|
||||
mimeToExtension[@"application/x-flac"] = @"flac"; extensionToMime[@"flac"] = @"application/x-flac";
|
||||
mimeToExtension[@"audio/flac"] = @"flac"; extensionToMime[@"flac"] = @"audio/flac";
|
||||
mimeToExtension[@"application/x-font"] = @"pfa"; extensionToMime[@"pfa"] = @"application/x-font";
|
||||
mimeToExtension[@"application/x-font"] = @"pfb"; extensionToMime[@"pfb"] = @"application/x-font";
|
||||
mimeToExtension[@"application/x-font"] = @"gsf"; extensionToMime[@"gsf"] = @"application/x-font";
|
||||
|
|
|
|||
|
|
@ -448,6 +448,34 @@ public class PremiumLimitDisplayComponent: Component {
|
|||
}
|
||||
view.frame = CGRect(origin: CGPoint(x: activityPosition - 12.0 - inactiveValueSize.width, y: floorToScreenPixels((lineHeight - inactiveValueSize.height) / 2.0)), size: inactiveValueSize)
|
||||
}
|
||||
|
||||
let activeValueSize = self.activeValueLabel.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(
|
||||
text: .plain(
|
||||
NSAttributedString(
|
||||
string: component.activeValue,
|
||||
font: Font.semibold(15.0),
|
||||
textColor: rightTextColor
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
let activeValueLabelFrame = CGRect(origin: CGPoint(x: containerFrame.width - 12.0 - activeValueSize.width, y: floorToScreenPixels((lineHeight - activeValueSize.height) / 2.0)), size: activeValueSize)
|
||||
if let view = self.activeValueLabel.view {
|
||||
if view.superview == nil {
|
||||
self.container.addSubview(view)
|
||||
|
||||
if component.invertProgress {
|
||||
self.container.bringSubviewToFront(self.activeContainer)
|
||||
}
|
||||
}
|
||||
view.frame = activeValueLabelFrame
|
||||
}
|
||||
|
||||
let activeTitleSize = self.activeTitleLabel.update(
|
||||
transition: .immediate,
|
||||
|
|
@ -470,33 +498,12 @@ public class PremiumLimitDisplayComponent: Component {
|
|||
self.container.addSubview(view)
|
||||
}
|
||||
view.frame = CGRect(origin: CGPoint(x: activityPosition + 12.0, y: floorToScreenPixels((lineHeight - activeTitleSize.height) / 2.0)), size: activeTitleSize)
|
||||
}
|
||||
|
||||
let activeValueSize = self.activeValueLabel.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(
|
||||
text: .plain(
|
||||
NSAttributedString(
|
||||
string: component.activeValue,
|
||||
font: Font.semibold(15.0),
|
||||
textColor: rightTextColor
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
if let view = self.activeValueLabel.view {
|
||||
if view.superview == nil {
|
||||
self.container.addSubview(view)
|
||||
|
||||
if component.invertProgress {
|
||||
self.container.bringSubviewToFront(self.activeContainer)
|
||||
}
|
||||
|
||||
if view.frame.maxX > activeValueLabelFrame.minX - 8.0 {
|
||||
view.alpha = 0.0
|
||||
} else {
|
||||
view.alpha = 1.0
|
||||
}
|
||||
view.frame = CGRect(origin: CGPoint(x: containerFrame.width - 12.0 - activeValueSize.width, y: floorToScreenPixels((lineHeight - activeValueSize.height) / 2.0)), size: activeValueSize)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1013,6 +1013,11 @@ func revalidateMediaResourceReference(accountPeerId: PeerId, postbox: Postbox, n
|
|||
return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil))
|
||||
}
|
||||
}
|
||||
if let music = item.music {
|
||||
if let updatedResource = findUpdatedMediaResource(media: music, previousMedia: nil, resource: resource) {
|
||||
return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil))
|
||||
}
|
||||
}
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ import Postbox
|
|||
public class ParticipantRankMessageAttribute: MessageAttribute {
|
||||
public let rank: String
|
||||
|
||||
public var associatedMessageIds: [MessageId] = []
|
||||
|
||||
public init(rank: String) {
|
||||
self.rank = rank
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1286,7 +1286,7 @@ public struct PresentationResourcesChat {
|
|||
|
||||
UIGraphicsPopContext()
|
||||
}
|
||||
})
|
||||
})?.withRenderingMode(.alwaysTemplate)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -1312,7 +1312,7 @@ public struct PresentationResourcesChat {
|
|||
|
||||
UIGraphicsPopContext()
|
||||
}
|
||||
})
|
||||
})?.withRenderingMode(.alwaysTemplate)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -1338,7 +1338,7 @@ public struct PresentationResourcesChat {
|
|||
|
||||
UIGraphicsPopContext()
|
||||
}
|
||||
})
|
||||
})?.withRenderingMode(.alwaysTemplate)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@ public final class ButtonTextContentComponent: Component {
|
|||
}
|
||||
|
||||
if let badgeSize, let badge = self.badge {
|
||||
let badgeFrame = CGRect(origin: CGPoint(x: contentFrame.minX + contentSize.width + badgeSpacing, y: floorToScreenPixels((size.height - badgeSize.height) * 0.5) + 1.0), size: badgeSize)
|
||||
let badgeFrame = CGRect(origin: CGPoint(x: contentFrame.minX + contentSize.width + badgeSpacing, y: floorToScreenPixels((size.height - badgeSize.height) * 0.5) + UIScreenPixel), size: badgeSize)
|
||||
|
||||
if let badgeView = badge.view {
|
||||
var animateIn = false
|
||||
|
|
@ -461,7 +461,11 @@ public final class ButtonComponent: Component {
|
|||
|
||||
private var containerView: UIView
|
||||
private var glassContainerView: GlassBackgroundView?
|
||||
private var glassShadowView: UIImageView?
|
||||
private var glassShadowCornerRadius: CGFloat?
|
||||
private var glassHighlightContainerView: UIView?
|
||||
private let button: HighlightTrackingButton
|
||||
private let legacyGlassHighlightRecognizer: GlassHighlightGestureRecognizer
|
||||
|
||||
private var shimmeringView: ButtonShimmeringView?
|
||||
private var chromeView: UIImageView?
|
||||
|
|
@ -476,6 +480,7 @@ public final class ButtonComponent: Component {
|
|||
self.containerView.isUserInteractionEnabled = false
|
||||
|
||||
self.button = HighlightTrackingButton()
|
||||
self.legacyGlassHighlightRecognizer = GlassHighlightGestureRecognizer(target: nil, action: nil)
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
|
|
@ -484,6 +489,8 @@ public final class ButtonComponent: Component {
|
|||
|
||||
self.addSubview(self.containerView)
|
||||
self.addSubview(self.button)
|
||||
self.addGestureRecognizer(self.legacyGlassHighlightRecognizer)
|
||||
self.legacyGlassHighlightRecognizer.isEnabled = false
|
||||
|
||||
self.button.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
|
||||
|
|
@ -495,28 +502,6 @@ public final class ButtonComponent: Component {
|
|||
self.button.highligthedChanged = { [weak self] highlighted in
|
||||
if let self, let component = self.component, component.isEnabled {
|
||||
switch component.background.style {
|
||||
case .glass:
|
||||
let transition = ComponentTransition(animation: .curve(duration: highlighted ? 0.25 : 0.35, curve: .spring))
|
||||
if highlighted {
|
||||
self.layer.shouldRasterize = true
|
||||
|
||||
let highlightedColor = component.background.color.withMultiplied(hue: 1.0, saturation: 0.77, brightness: 1.01)
|
||||
transition.setBackgroundColor(view: self.containerView, color: highlightedColor)
|
||||
transition.setScale(view: self.containerView, scale: 1.05, completion: { finished in
|
||||
if finished {
|
||||
self.layer.shouldRasterize = false
|
||||
}
|
||||
})
|
||||
} else {
|
||||
self.layer.shouldRasterize = true
|
||||
|
||||
transition.setBackgroundColor(view: self.containerView, color: component.background.color)
|
||||
transition.setScale(view: self.containerView, scale: 1.0, completion: { finished in
|
||||
if finished {
|
||||
self.layer.shouldRasterize = false
|
||||
}
|
||||
})
|
||||
}
|
||||
case .legacy:
|
||||
if highlighted {
|
||||
self.containerView.layer.removeAnimation(forKey: "opacity")
|
||||
|
|
@ -536,6 +521,74 @@ public final class ButtonComponent: Component {
|
|||
preconditionFailure()
|
||||
}
|
||||
|
||||
private func ensureGlassShadowView() -> UIImageView {
|
||||
if let glassShadowView = self.glassShadowView {
|
||||
return glassShadowView
|
||||
}
|
||||
let glassShadowView = UIImageView()
|
||||
glassShadowView.isUserInteractionEnabled = false
|
||||
self.glassShadowView = glassShadowView
|
||||
return glassShadowView
|
||||
}
|
||||
|
||||
private func ensureGlassHighlightContainerView() -> UIView {
|
||||
if let glassHighlightContainerView = self.glassHighlightContainerView {
|
||||
return glassHighlightContainerView
|
||||
}
|
||||
let glassHighlightContainerView = UIView()
|
||||
glassHighlightContainerView.isUserInteractionEnabled = false
|
||||
glassHighlightContainerView.clipsToBounds = true
|
||||
self.glassHighlightContainerView = glassHighlightContainerView
|
||||
return glassHighlightContainerView
|
||||
}
|
||||
|
||||
private func removeLegacyGlassEffectViews() {
|
||||
self.legacyGlassHighlightRecognizer.isEnabled = false
|
||||
self.legacyGlassHighlightRecognizer.highlightContainerView = nil
|
||||
|
||||
if let glassShadowView = self.glassShadowView, glassShadowView.superview != nil {
|
||||
glassShadowView.removeFromSuperview()
|
||||
}
|
||||
if let glassHighlightContainerView = self.glassHighlightContainerView, glassHighlightContainerView.superview != nil {
|
||||
glassHighlightContainerView.removeFromSuperview()
|
||||
}
|
||||
self.glassShadowCornerRadius = nil
|
||||
|
||||
self.layer.removeAnimation(forKey: "sublayerTransform")
|
||||
self.layer.sublayerTransform = CATransform3DIdentity
|
||||
}
|
||||
|
||||
private func updateLegacyGlassEffectViews(component: ButtonComponent, size: CGSize, cornerRadius: CGFloat, transition: ComponentTransition) {
|
||||
let shadowInset: CGFloat = 48.0
|
||||
|
||||
let glassShadowView = self.ensureGlassShadowView()
|
||||
if glassShadowView.superview == nil {
|
||||
self.insertSubview(glassShadowView, at: 0)
|
||||
} else {
|
||||
self.sendSubviewToBack(glassShadowView)
|
||||
}
|
||||
if self.glassShadowCornerRadius != cornerRadius || glassShadowView.image == nil {
|
||||
glassShadowView.image = GlassBackgroundView.generateLegacyShadowImage(cornerRadius: cornerRadius, shadowInset: shadowInset, shadowIntensity: 0.18, shadowBlur: 64.0)
|
||||
self.glassShadowCornerRadius = cornerRadius
|
||||
}
|
||||
transition.setFrame(view: glassShadowView, frame: CGRect(origin: .zero, size: size).insetBy(dx: -shadowInset, dy: -shadowInset))
|
||||
transition.setAlpha(view: glassShadowView, alpha: 1.0)
|
||||
|
||||
let glassHighlightContainerView = self.ensureGlassHighlightContainerView()
|
||||
if glassHighlightContainerView.superview == nil {
|
||||
self.insertSubview(glassHighlightContainerView, aboveSubview: self.containerView)
|
||||
} else if self.button.superview === self {
|
||||
self.insertSubview(glassHighlightContainerView, belowSubview: self.button)
|
||||
} else {
|
||||
self.bringSubviewToFront(glassHighlightContainerView)
|
||||
}
|
||||
transition.setFrame(view: glassHighlightContainerView, frame: CGRect(origin: .zero, size: size))
|
||||
transition.setCornerRadius(layer: glassHighlightContainerView.layer, cornerRadius: cornerRadius)
|
||||
|
||||
self.legacyGlassHighlightRecognizer.highlightContainerView = glassHighlightContainerView
|
||||
self.legacyGlassHighlightRecognizer.isEnabled = component.isEnabled && !component.displaysProgress
|
||||
}
|
||||
|
||||
@objc private func pressed() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
|
|
@ -641,6 +694,12 @@ public final class ButtonComponent: Component {
|
|||
transition.setCornerRadius(layer: self.containerView.layer, cornerRadius: cornerRadius)
|
||||
}
|
||||
|
||||
if component.background.style == .glass {
|
||||
self.updateLegacyGlassEffectViews(component: component, size: size, cornerRadius: cornerRadius, transition: transition)
|
||||
} else {
|
||||
self.removeLegacyGlassEffectViews()
|
||||
}
|
||||
|
||||
if let contentView = contentItem.view.view {
|
||||
var animateIn = false
|
||||
var contentTransition = transition
|
||||
|
|
|
|||
|
|
@ -1957,7 +1957,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||
availableSize: CGSize(width: 40.0, height: 40.0),
|
||||
transition: .immediate
|
||||
)
|
||||
if component.cameraState.isCollageEnabled {
|
||||
if state.displayingCollageSelection {
|
||||
nextButtonX += 48.0
|
||||
}
|
||||
var collageButtonX = nextButtonX
|
||||
|
|
|
|||
|
|
@ -5963,44 +5963,64 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
disableDefaultPressAnimation = true
|
||||
}
|
||||
case let .phone(number):
|
||||
return .action(InternalBubbleTapAction.Action({ [weak self] in
|
||||
guard let self, let contentNode = self.contextContentNodeForLink(number, rects: rects) else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.longTap(.phone(number), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?()))
|
||||
}, contextMenuOnLongPress: !tapAction.hasLongTapAction))
|
||||
if tapAction.hasLongTapAction {
|
||||
return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in
|
||||
guard let self, let contentNode = self.contextContentNodeForLink(number, rects: rects) else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.longTap(.phone(number), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture))
|
||||
}, contextMenuOnLongPress: false))
|
||||
} else {
|
||||
disableDefaultPressAnimation = true
|
||||
}
|
||||
case let .peerMention(peerId, mention, _):
|
||||
return .action(InternalBubbleTapAction.Action { [weak self] in
|
||||
guard let self, let contentNode = self.contextContentNodeForLink(mention, rects: rects) else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.longTap(.peerMention(peerId, mention), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?()))
|
||||
})
|
||||
if tapAction.hasLongTapAction {
|
||||
return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in
|
||||
guard let self, let contentNode = self.contextContentNodeForLink(mention, rects: rects) else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.longTap(.peerMention(peerId, mention), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture))
|
||||
}, contextMenuOnLongPress: false))
|
||||
} else {
|
||||
disableDefaultPressAnimation = true
|
||||
}
|
||||
case let .textMention(name):
|
||||
return .action(InternalBubbleTapAction.Action { [weak self] in
|
||||
guard let self, let contentNode = self.contextContentNodeForLink(name, rects: rects) else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.longTap(.mention(name), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?()))
|
||||
})
|
||||
if tapAction.hasLongTapAction {
|
||||
return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in
|
||||
guard let self, let contentNode = self.contextContentNodeForLink(name, rects: rects) else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.longTap(.mention(name), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture))
|
||||
}, contextMenuOnLongPress: false))
|
||||
} else {
|
||||
disableDefaultPressAnimation = true
|
||||
}
|
||||
case let .botCommand(command):
|
||||
return .action(InternalBubbleTapAction.Action { [weak self] in
|
||||
guard let self, let contentNode = self.contextContentNodeForLink(command, rects: rects) else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.longTap(.command(command), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?()))
|
||||
})
|
||||
if tapAction.hasLongTapAction {
|
||||
return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in
|
||||
guard let self, let contentNode = self.contextContentNodeForLink(command, rects: rects) else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.longTap(.command(command), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture))
|
||||
}, contextMenuOnLongPress: false))
|
||||
} else {
|
||||
disableDefaultPressAnimation = true
|
||||
}
|
||||
case let .hashtag(peerName, hashtag):
|
||||
var fullHashtag = hashtag
|
||||
if let peerName {
|
||||
fullHashtag += "@\(peerName)"
|
||||
}
|
||||
return .action(InternalBubbleTapAction.Action { [weak self] in
|
||||
guard let self, let contentNode = self.contextContentNodeForLink(fullHashtag, rects: rects) else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.longTap(.hashtag(fullHashtag), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?()))
|
||||
})
|
||||
if tapAction.hasLongTapAction {
|
||||
return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in
|
||||
guard let self, let contentNode = self.contextContentNodeForLink(fullHashtag, rects: rects) else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.longTap(.hashtag(fullHashtag), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture))
|
||||
}, contextMenuOnLongPress: false))
|
||||
} else {
|
||||
disableDefaultPressAnimation = true
|
||||
}
|
||||
case .instantPage:
|
||||
break
|
||||
case .wallpaper:
|
||||
|
|
@ -6014,21 +6034,29 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
case .openMessage:
|
||||
break
|
||||
case let .timecode(timecode, text):
|
||||
if let mediaMessage = mediaMessage {
|
||||
return .action(InternalBubbleTapAction.Action { [weak self] in
|
||||
guard let self, let contentNode = self.contextContentNodeForLink(text, rects: rects) else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.longTap(.timecode(timecode, text), ChatControllerInteraction.LongTapParams(message: mediaMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?()))
|
||||
})
|
||||
if let mediaMessage {
|
||||
if tapAction.hasLongTapAction {
|
||||
return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in
|
||||
guard let self, let contentNode = self.contextContentNodeForLink(text, rects: rects) else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.longTap(.timecode(timecode, text), ChatControllerInteraction.LongTapParams(message: mediaMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture))
|
||||
}, contextMenuOnLongPress: false))
|
||||
} else {
|
||||
disableDefaultPressAnimation = true
|
||||
}
|
||||
}
|
||||
case let .bankCard(number):
|
||||
return .action(InternalBubbleTapAction.Action { [weak self] in
|
||||
guard let self, let contentNode = self.contextContentNodeForLink(number, rects: rects) else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.longTap(.bankCard(number), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?()))
|
||||
})
|
||||
if tapAction.hasLongTapAction {
|
||||
return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in
|
||||
guard let self, let contentNode = self.contextContentNodeForLink(number, rects: rects) else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.longTap(.bankCard(number), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture))
|
||||
}, contextMenuOnLongPress: false))
|
||||
} else {
|
||||
disableDefaultPressAnimation = true
|
||||
}
|
||||
case .tooltip:
|
||||
break
|
||||
case .openPollResults:
|
||||
|
|
@ -6040,13 +6068,17 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
case .customEmoji:
|
||||
break
|
||||
case let .date(date, _):
|
||||
return .action(InternalBubbleTapAction.Action { [weak self] in
|
||||
let fullDate = stringForEntityFormattedDate(timestamp: date, format: .full(timeFormat: .short, dateFormat: .long, dayOfWeek: false), strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat)
|
||||
guard let self, let contentNode = self.contextContentNodeForLink(fullDate, rects: rects) else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.longTap(.date(date), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?()))
|
||||
})
|
||||
if tapAction.hasLongTapAction {
|
||||
return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in
|
||||
let fullDate = stringForEntityFormattedDate(timestamp: date, format: .full(timeFormat: .short, dateFormat: .long, dayOfWeek: false), strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat)
|
||||
guard let self, let contentNode = self.contextContentNodeForLink(fullDate, rects: rects) else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.longTap(.date(date), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture))
|
||||
}, contextMenuOnLongPress: false))
|
||||
} else {
|
||||
disableDefaultPressAnimation = true
|
||||
}
|
||||
case .custom:
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -430,8 +430,6 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
let textColor: UIColor
|
||||
let iconColor: UIColor
|
||||
|
||||
switch arguments.type {
|
||||
case let .bubble(incoming):
|
||||
if isExpiredStory || isStory {
|
||||
|
|
@ -441,10 +439,8 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
|||
} else {
|
||||
textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.primaryTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.primaryTextColor
|
||||
}
|
||||
iconColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
case .standalone:
|
||||
textColor = titleColor
|
||||
iconColor = titleColor
|
||||
}
|
||||
|
||||
var textLeftInset: CGFloat = 0.0
|
||||
|
|
@ -872,6 +868,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
|||
expiredStoryIconView.frame = CGRect(origin: CGPoint(x: textFrame.minX - 1.0, y: textFrame.minY + 3.0 + UIScreenPixel), size: imageSize)
|
||||
}
|
||||
}
|
||||
expiredStoryIconView.tintColor = titleColor
|
||||
} else if let expiredStoryIconView = node.expiredStoryIconView {
|
||||
expiredStoryIconView.removeFromSuperview()
|
||||
}
|
||||
|
|
@ -951,7 +948,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
|||
|
||||
if let todoItemCompleted {
|
||||
let checkLayerFrame = CGRect(origin: CGPoint(x: textFrame.minX - 16.0, y: textFrame.minY + 5.0), size: CGSize(width: 12.0, height: 12.0))
|
||||
let checkTheme = CheckNodeTheme(backgroundColor: iconColor, strokeColor: .clear, borderColor: iconColor, overlayBorder: false, hasInset: true, hasShadow: false, borderWidth: 1.0)
|
||||
let checkTheme = CheckNodeTheme(backgroundColor: titleColor, strokeColor: .clear, borderColor: titleColor, overlayBorder: false, hasInset: true, hasShadow: false, borderWidth: 1.0)
|
||||
|
||||
let checkLayer: CheckLayer
|
||||
if let current = node.checkLayer {
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ private final class GiftSetupScreenComponent: Component {
|
|||
private let backgroundHandleView: UIImageView
|
||||
|
||||
private let closeButton = ComponentView<Empty>()
|
||||
private var doneButton: ComponentView<Empty>?
|
||||
|
||||
private let remainingCount = ComponentView<Empty>()
|
||||
private let auctionFooter = ComponentView<Empty>()
|
||||
|
|
@ -1150,6 +1151,65 @@ private final class GiftSetupScreenComponent: Component {
|
|||
transition.setFrame(view: closeButtonView, frame: closeButtonFrame)
|
||||
}
|
||||
|
||||
if environment.inputHeight > 0.0 {
|
||||
var doneButtonTransition = transition
|
||||
let doneButton: ComponentView<Empty>
|
||||
if let current = self.doneButton {
|
||||
doneButton = current
|
||||
} else {
|
||||
doneButton = ComponentView()
|
||||
self.doneButton = doneButton
|
||||
doneButtonTransition = .immediate
|
||||
}
|
||||
|
||||
let doneButtonSize = doneButton.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(GlassBarButtonComponent(
|
||||
size: CGSize(width: 44.0, height: 44.0),
|
||||
backgroundColor: environment.theme.list.itemCheckColors.fillColor,
|
||||
isDark: environment.theme.overallDarkAppearance,
|
||||
state: .tintedGlass,
|
||||
component: AnyComponentWithIdentity(id: "done", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Done",
|
||||
tintColor: environment.theme.list.itemCheckColors.foregroundColor
|
||||
)
|
||||
)),
|
||||
action: { [weak self] _ in
|
||||
self?.proceed()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 44.0, height: 44.0)
|
||||
)
|
||||
let doneButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - rawSideInset - 16.0 - doneButtonSize.width, y: 16.0), size: doneButtonSize)
|
||||
if let doneButtonView = doneButton.view {
|
||||
if doneButtonView.superview == nil {
|
||||
doneButtonView.layer.rasterizationScale = UIScreenScale
|
||||
self.navigationBarContainer.addSubview(doneButtonView)
|
||||
|
||||
if !transition.animation.isImmediate {
|
||||
doneButtonView.layer.shouldRasterize = true
|
||||
transition.animateScale(view: doneButtonView, from: 0.01, to: 1.0, completion: { _ in
|
||||
doneButtonView.layer.shouldRasterize = false
|
||||
})
|
||||
transition.animateAlpha(view: doneButtonView, from: 0.0, to: 1.0)
|
||||
}
|
||||
}
|
||||
doneButtonTransition.setPosition(view: doneButtonView, position: doneButtonFrame.center)
|
||||
doneButtonTransition.setBounds(view: doneButtonView, bounds: CGRect(origin: .zero, size: doneButtonFrame.size))
|
||||
}
|
||||
} else if let doneButton = self.doneButton {
|
||||
self.doneButton = nil
|
||||
if let doneButtonView = doneButton.view {
|
||||
doneButtonView.layer.shouldRasterize = true
|
||||
doneButtonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false)
|
||||
doneButtonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
doneButtonView.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let containerInset: CGFloat = environment.statusBarHeight + 10.0
|
||||
|
||||
var initialContentHeight = contentHeight
|
||||
|
|
@ -1409,14 +1469,15 @@ private final class GiftSetupScreenComponent: Component {
|
|||
)
|
||||
)),
|
||||
], alignment: .left, spacing: 2.0)),
|
||||
accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(
|
||||
icon: ListActionItemComponent.Icon(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: presentationStringsFormattedNumber(Int32(availability.resale), environment.dateTimeFormat.groupingSeparator),
|
||||
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
||||
textColor: theme.list.itemSecondaryTextColor
|
||||
)),
|
||||
maximumNumberOfLines: 0
|
||||
))), insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 16.0))),
|
||||
maximumNumberOfLines: 1
|
||||
)))),
|
||||
accessory: .arrow,
|
||||
action: { [weak self] _ in
|
||||
guard let self, let component = self.component, let controller = environment.controller() else {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -541,18 +541,7 @@ public class GlassBackgroundView: UIView {
|
|||
}
|
||||
|
||||
if let shadowView = self.shadowView {
|
||||
let shadowInnerInset: CGFloat = 0.5
|
||||
shadowView.image = generateImage(CGSize(width: shadowInset * 2.0 + outerCornerRadius * 2.0, height: shadowInset * 2.0 + outerCornerRadius * 2.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setFillColor(UIColor.black.cgColor)
|
||||
context.setShadow(offset: CGSize(width: 0.0, height: 1.0), blur: 40.0, color: UIColor(white: 0.0, alpha: 0.04).cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowInset + shadowInnerInset, y: shadowInset + shadowInnerInset), size: CGSize(width: size.width - shadowInset * 2.0 - shadowInnerInset * 2.0, height: size.height - shadowInset * 2.0 - shadowInnerInset * 2.0)))
|
||||
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.setBlendMode(.copy)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowInset + shadowInnerInset, y: shadowInset + shadowInnerInset), size: CGSize(width: size.width - shadowInset * 2.0 - shadowInnerInset * 2.0, height: size.height - shadowInset * 2.0 - shadowInnerInset * 2.0)))
|
||||
})?.stretchableImage(withLeftCapWidth: Int(shadowInset + outerCornerRadius), topCapHeight: Int(shadowInset + outerCornerRadius))
|
||||
shadowView.image = Self.generateLegacyShadowImage(cornerRadius: outerCornerRadius, shadowInset: shadowInset)
|
||||
transition.setAlpha(view: shadowView, alpha: isVisible ? 1.0 : 0.0)
|
||||
}
|
||||
|
||||
|
|
@ -867,6 +856,35 @@ private extension CGContext {
|
|||
}
|
||||
|
||||
public extension GlassBackgroundView {
|
||||
static func generateLegacyShadowImage(cornerRadius: CGFloat, shadowInset: CGFloat = 32.0, shadowIntensity: CGFloat = 0.04, shadowBlur: CGFloat = 40.0) -> UIImage? {
|
||||
let shadowInnerInset: CGFloat = 0.5
|
||||
let diameter = max(1.0, cornerRadius * 2.0)
|
||||
let size = CGSize(width: shadowInset * 2.0 + diameter, height: shadowInset * 2.0 + diameter)
|
||||
|
||||
return generateImage(size, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let shadowRect = CGRect(
|
||||
origin: CGPoint(x: shadowInset + shadowInnerInset, y: shadowInset + shadowInnerInset),
|
||||
size: CGSize(
|
||||
width: size.width - shadowInset * 2.0 - shadowInnerInset * 2.0,
|
||||
height: size.height - shadowInset * 2.0 - shadowInnerInset * 2.0
|
||||
)
|
||||
)
|
||||
|
||||
context.setFillColor(UIColor.black.cgColor)
|
||||
context.setShadow(offset: CGSize(width: 0.0, height: 1.0), blur: shadowBlur, color: UIColor(white: 0.0, alpha: shadowIntensity).cgColor)
|
||||
context.fillEllipse(in: shadowRect)
|
||||
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.setBlendMode(.copy)
|
||||
context.fillEllipse(in: shadowRect)
|
||||
})?.stretchableImage(
|
||||
withLeftCapWidth: Int(shadowInset + cornerRadius),
|
||||
topCapHeight: Int(shadowInset + cornerRadius)
|
||||
)
|
||||
}
|
||||
|
||||
static func generateLegacyGlassImage(size: CGSize, inset: CGFloat, borderWidthFactor: CGFloat = 1.0, isDark: Bool, fillColor: UIColor) -> UIImage {
|
||||
var size = size
|
||||
if size == .zero {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import Foundation
|
|||
import UIKit
|
||||
import Display
|
||||
|
||||
final class GlassHighlightGestureRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate {
|
||||
var highlightContainerView: UIView?
|
||||
public final class GlassHighlightGestureRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate {
|
||||
public weak var highlightContainerView: UIView?
|
||||
|
||||
private var touchEffect: TouchEffect?
|
||||
private var initialTouchLocation: CGPoint?
|
||||
|
|
@ -14,7 +14,7 @@ final class GlassHighlightGestureRecognizer: UIGestureRecognizer, UIGestureRecog
|
|||
}
|
||||
}
|
||||
|
||||
override init(target: Any?, action: Selector?) {
|
||||
public override init(target: Any?, action: Selector?) {
|
||||
super.init(target: target, action: action)
|
||||
|
||||
self.delegate = self
|
||||
|
|
@ -24,19 +24,19 @@ final class GlassHighlightGestureRecognizer: UIGestureRecognizer, UIGestureRecog
|
|||
self.requiresExclusiveTouchType = false
|
||||
}
|
||||
|
||||
override func canPrevent(_ preventedGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
override public func canPrevent(_ preventedGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
override func canBePrevented(by preventingGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
override public func canBePrevented(by preventingGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func reset() {
|
||||
override public func reset() {
|
||||
if let touchEffect = self.touchEffect {
|
||||
touchEffect.setIsTracking(false)
|
||||
}
|
||||
|
|
@ -45,7 +45,7 @@ final class GlassHighlightGestureRecognizer: UIGestureRecognizer, UIGestureRecog
|
|||
self.initialTouchLocation = nil
|
||||
}
|
||||
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
if let view = self.touchEffectView ?? self.view, let touch = touches.first {
|
||||
let touchLocation = touch.location(in: view)
|
||||
let touchEffect = TouchEffect(view: view, highlightContainerView: self.highlightContainerView)
|
||||
|
|
@ -60,21 +60,21 @@ final class GlassHighlightGestureRecognizer: UIGestureRecognizer, UIGestureRecog
|
|||
}
|
||||
}
|
||||
|
||||
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
override public func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
if let touchEffect = self.touchEffect {
|
||||
touchEffect.setIsTracking(false)
|
||||
}
|
||||
self.touchEffect = nil
|
||||
}
|
||||
|
||||
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
override public func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
if let touchEffect = self.touchEffect {
|
||||
touchEffect.setIsTracking(false)
|
||||
}
|
||||
self.touchEffect = nil
|
||||
}
|
||||
|
||||
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
guard let touchEffect = self.touchEffect,
|
||||
let view = self.touchEffectView ?? self.view,
|
||||
let touch = touches.first,
|
||||
|
|
|
|||
|
|
@ -716,7 +716,8 @@ public final class ListActionItemComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
if case .arrow = component.accessory {
|
||||
switch component.accessory {
|
||||
case .arrow:
|
||||
let arrowView: UIImageView
|
||||
var arrowTransition = transition
|
||||
if let current = self.arrowView {
|
||||
|
|
@ -735,14 +736,7 @@ public final class ListActionItemComponent: Component {
|
|||
let arrowFrame = CGRect(origin: CGPoint(x: availableSize.width - 7.0 - image.size.width, y: floor((contentHeight - image.size.height) * 0.5)), size: image.size)
|
||||
arrowTransition.setFrame(view: arrowView, frame: arrowFrame)
|
||||
}
|
||||
} else {
|
||||
if let arrowView = self.arrowView {
|
||||
self.arrowView = nil
|
||||
arrowView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
if case .expandArrows = component.accessory {
|
||||
case .expandArrows:
|
||||
let arrowView: UIImageView
|
||||
var arrowTransition = transition
|
||||
if let current = self.arrowView {
|
||||
|
|
@ -761,7 +755,7 @@ public final class ListActionItemComponent: Component {
|
|||
let arrowFrame = CGRect(origin: CGPoint(x: availableSize.width - 16.0 - image.size.width, y: floor((contentHeight - image.size.height) * 0.5)), size: image.size)
|
||||
arrowTransition.setFrame(view: arrowView, frame: arrowFrame)
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
if let arrowView = self.arrowView {
|
||||
self.arrowView = nil
|
||||
arrowView.removeFromSuperview()
|
||||
|
|
|
|||
|
|
@ -2765,7 +2765,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||
let navigationLeftButtonSize = self.navigationLeftButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(GlassBarButtonComponent(
|
||||
size: CGSize(width: 40.0, height: 40.0),
|
||||
size: CGSize(width: 44.0, height: 44.0),
|
||||
backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
||||
isDark: environment.theme.overallDarkAppearance,
|
||||
state: .generic,
|
||||
|
|
|
|||
|
|
@ -911,7 +911,7 @@ final class DataUsageScreenComponent: Component {
|
|||
self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(AnimationHint(value: .modeChanged)))
|
||||
})),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - (environment.safeInsets.left + 16.0 + 44.0 + 10.0) * 2.0, height: 100.0)
|
||||
containerSize: CGSize(width: availableSize.width - (environment.safeInsets.left + 48.0) * 2.0, height: 100.0)
|
||||
)
|
||||
let segmentedControlFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - segmentedSize.width) * 0.5), y: contentHeight), size: segmentedSize)
|
||||
if let segmentedControlComponentView = self.segmentedControlView.view {
|
||||
|
|
|
|||
|
|
@ -377,6 +377,7 @@ public final class StoryPeerListComponent: Component {
|
|||
self.scrollView.alwaysBounceVertical = false
|
||||
self.scrollView.alwaysBounceHorizontal = true
|
||||
self.scrollView.clipsToBounds = false
|
||||
self.scrollView.scrollsToTop = false
|
||||
|
||||
self.scrollContainerView = UIView()
|
||||
self.scrollContainerView.clipsToBounds = true
|
||||
|
|
|
|||
|
|
@ -965,7 +965,7 @@ public final class StoryPeerListItemComponent: Component {
|
|||
var titleTransition = transition
|
||||
if let previousAnimation = previousComponent?.ringAnimation, case .progress = previousAnimation, component.ringAnimation == nil {
|
||||
if let titleView = self.title.view, let snapshotView = titleView.snapshotView(afterScreenUpdates: false) {
|
||||
self.button.addSubview(snapshotView)
|
||||
self.titleContainer.addSubview(snapshotView)
|
||||
snapshotView.frame = titleView.frame
|
||||
snapshotView.layer.animateAlpha(from: component.expandedAlphaFraction, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
|
|
|
|||
|
|
@ -536,7 +536,7 @@ private final class VideoMessageCameraScreenComponent: CombinedComponent {
|
|||
let availableSize = context.component.containerSize
|
||||
|
||||
var sideInset: CGFloat = 8.0
|
||||
if component.safeInsets.bottom <= 32.0 {
|
||||
if component.safeInsets.bottom <= 32.0 && environment.inputHeight == 0.0 {
|
||||
sideInset += 18.0
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -606,7 +606,11 @@ extension ChatControllerImpl {
|
|||
}
|
||||
}
|
||||
|
||||
let location = CGRect(origin: CGPoint(x: screenWidth - layout.safeInsets.right - 50.0, y: layout.size.height - insets.bottom - 128.0), size: CGSize())
|
||||
var sideOffset: CGFloat = 18.0
|
||||
if let inputHeight = layout.inputHeight, inputHeight > 0.0 {
|
||||
sideOffset = 0.0
|
||||
}
|
||||
let location = CGRect(origin: CGPoint(x: screenWidth - layout.safeInsets.right - 50.0 - sideOffset, y: layout.size.height - insets.bottom - 128.0), size: CGSize())
|
||||
|
||||
let tooltipController = TooltipScreen(
|
||||
account: self.context.account,
|
||||
|
|
|
|||
|
|
@ -31,15 +31,10 @@ extension ChatControllerImpl {
|
|||
}
|
||||
}
|
||||
|
||||
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer
|
||||
let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture
|
||||
|
||||
let source: ContextContentSource
|
||||
// if let location = location {
|
||||
// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y)))
|
||||
// } else {
|
||||
source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode))
|
||||
// }
|
||||
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture
|
||||
let gesture: ContextGesture? = nil
|
||||
|
||||
let source: ContextContentSource = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode))
|
||||
|
||||
params.progress?.set(.single(true))
|
||||
|
||||
|
|
|
|||
|
|
@ -31,15 +31,10 @@ extension ChatControllerImpl {
|
|||
}
|
||||
}
|
||||
|
||||
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer
|
||||
let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture
|
||||
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture
|
||||
let gesture: ContextGesture? = nil
|
||||
|
||||
let source: ContextContentSource
|
||||
// if let location = location {
|
||||
// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y)))
|
||||
// } else {
|
||||
source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode))
|
||||
// }
|
||||
let source: ContextContentSource = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode))
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
|
|
|
|||
|
|
@ -35,15 +35,10 @@ extension ChatControllerImpl: EKEventEditViewDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer
|
||||
let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture
|
||||
|
||||
let source: ContextContentSource
|
||||
// if let location = location {
|
||||
// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y)))
|
||||
// } else {
|
||||
source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode))
|
||||
// }
|
||||
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture
|
||||
let gesture: ContextGesture? = nil
|
||||
|
||||
let source: ContextContentSource = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode))
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
items.append(
|
||||
|
|
|
|||
|
|
@ -31,15 +31,10 @@ extension ChatControllerImpl {
|
|||
}
|
||||
}
|
||||
|
||||
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer
|
||||
let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture
|
||||
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture
|
||||
let gesture: ContextGesture? = nil
|
||||
|
||||
let source: ContextContentSource
|
||||
// if let location = location {
|
||||
// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y)))
|
||||
// } else {
|
||||
source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode))
|
||||
// }
|
||||
let source: ContextContentSource = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode))
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
|
|
|
|||
|
|
@ -142,14 +142,9 @@ extension ChatControllerImpl {
|
|||
}
|
||||
|
||||
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture
|
||||
let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture
|
||||
let gesture: ContextGesture? = nil
|
||||
|
||||
let source: ContextContentSource
|
||||
// if let location = location {
|
||||
// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y)))
|
||||
// } else {
|
||||
source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode))
|
||||
// }
|
||||
let source: ContextContentSource = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode))
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
|
|
|
|||
|
|
@ -35,15 +35,10 @@ extension ChatControllerImpl: MFMessageComposeViewControllerDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer
|
||||
let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture
|
||||
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture
|
||||
let gesture: ContextGesture? = nil
|
||||
|
||||
let source: ContextContentSource
|
||||
// if let location = location {
|
||||
// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y)))
|
||||
// } else {
|
||||
source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode))
|
||||
// }
|
||||
let source: ContextContentSource = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode))
|
||||
|
||||
params.progress?.set(.single(true))
|
||||
|
||||
|
|
|
|||
|
|
@ -31,15 +31,10 @@ extension ChatControllerImpl {
|
|||
}
|
||||
}
|
||||
|
||||
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer
|
||||
let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture
|
||||
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture
|
||||
let gesture: ContextGesture? = nil
|
||||
|
||||
let source: ContextContentSource
|
||||
// if let location = location {
|
||||
// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y)))
|
||||
// } else {
|
||||
source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode))
|
||||
// }
|
||||
let source: ContextContentSource = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode))
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
|
|
|
|||
|
|
@ -19,15 +19,10 @@ extension ChatControllerImpl {
|
|||
return
|
||||
}
|
||||
|
||||
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer
|
||||
let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture
|
||||
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture
|
||||
let gesture: ContextGesture? = nil
|
||||
|
||||
let source: ContextContentSource
|
||||
// if let location = location {
|
||||
// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y)))
|
||||
// } else {
|
||||
source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode))
|
||||
// }
|
||||
let source: ContextContentSource = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode))
|
||||
|
||||
params.progress?.set(.single(true))
|
||||
|
||||
|
|
|
|||
|
|
@ -361,7 +361,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||
self.avatarNode = nil
|
||||
self.iconNode = nil
|
||||
self.iconCheckNode = nil
|
||||
self.animationNode = AnimationNode(animation: "anim_banned", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
|
||||
self.animationNode = AnimationNode(animation: "anim_banned", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 0.9)
|
||||
self.animatedStickerNode = nil
|
||||
|
||||
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
||||
|
|
|
|||
|
|
@ -277,4 +277,31 @@ final class WebAppWebView: WKWebView {
|
|||
override var inputAccessoryView: UIView? {
|
||||
return nil
|
||||
}
|
||||
|
||||
var origin: String? {
|
||||
guard let url = self.url, let scheme = url.scheme, let host = url.host else {
|
||||
return nil
|
||||
}
|
||||
let port = url.port
|
||||
var origin = "\(scheme)://\(host)"
|
||||
if let port {
|
||||
origin += ":\(port)"
|
||||
}
|
||||
return origin
|
||||
}
|
||||
}
|
||||
|
||||
extension WKFrameInfo {
|
||||
var securityOriginString: String {
|
||||
let securityOrigin = self.securityOrigin
|
||||
var origin = ""
|
||||
origin.append(securityOrigin.protocol)
|
||||
origin.append("://")
|
||||
origin.append(securityOrigin.host)
|
||||
if securityOrigin.port != 0 {
|
||||
origin.append(":")
|
||||
origin.append("\(securityOrigin.port)")
|
||||
}
|
||||
return origin
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue