Merge commit '9396d116759ce395c58a5670e2f197469d0f526d'
9
Images.xcassets/Components/Volume/Contents.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
||||
22
Images.xcassets/Components/Volume/VolumeFull.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "vol_full@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "vol_full@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
Images.xcassets/Components/Volume/VolumeFull.imageset/vol_full@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 841 B |
BIN
Images.xcassets/Components/Volume/VolumeFull.imageset/vol_full@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1016 B |
22
Images.xcassets/Components/Volume/VolumeHalf.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "vol_half@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "vol_half@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
Images.xcassets/Components/Volume/VolumeHalf.imageset/vol_half@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 859 B |
BIN
Images.xcassets/Components/Volume/VolumeHalf.imageset/vol_half@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
22
Images.xcassets/Components/Volume/VolumeOff.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "vol_off@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "vol_off@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
Images.xcassets/Components/Volume/VolumeOff.imageset/vol_off@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 727 B |
BIN
Images.xcassets/Components/Volume/VolumeOff.imageset/vol_off@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 790 B |
22
Images.xcassets/Settings/Permissions/Siri.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Siri@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Siri@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
Images.xcassets/Settings/Permissions/Siri.imageset/Siri@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
Images.xcassets/Settings/Permissions/Siri.imageset/Siri@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 189 KiB |
@ -38,6 +38,7 @@
|
||||
09619B8E21A34C0100493558 /* InstantPageScrollableNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09619B8D21A34C0100493558 /* InstantPageScrollableNode.swift */; };
|
||||
09619B9521A4ABF600493558 /* InstantPageReferenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09619B9321A4ABF500493558 /* InstantPageReferenceController.swift */; };
|
||||
09619B9621A4ABF600493558 /* InstantPageReferenceControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09619B9421A4ABF600493558 /* InstantPageReferenceControllerNode.swift */; };
|
||||
0962E65D21B1486D00245FD9 /* CallDebugNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0962E65C21B1486D00245FD9 /* CallDebugNode.swift */; };
|
||||
096C98BA21787A5C00C211FF /* LegacyBridgeAudio.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096C98B921787A5C00C211FF /* LegacyBridgeAudio.swift */; };
|
||||
096C98BF21787C6700C211FF /* TGBridgeAudioEncoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 096C98BB21787C6600C211FF /* TGBridgeAudioEncoder.m */; };
|
||||
096C98C021787C6700C211FF /* TGBridgeAudioEncoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 096C98BC21787C6600C211FF /* TGBridgeAudioEncoder.h */; };
|
||||
@ -62,7 +63,7 @@
|
||||
09B4EE4D21A7B73800847FA6 /* PermissionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE4C21A7B73800847FA6 /* PermissionController.swift */; };
|
||||
09B4EE4F21A7B75D00847FA6 /* PermissionControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE4E21A7B75D00847FA6 /* PermissionControllerNode.swift */; };
|
||||
09B4EE5221A7CC3E00847FA6 /* SolidRoundedButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE5121A7CC3E00847FA6 /* SolidRoundedButtonNode.swift */; };
|
||||
09B4EE5621A8149C00847FA6 /* NotificationPermissionInfoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE5521A8149C00847FA6 /* NotificationPermissionInfoItem.swift */; };
|
||||
09B4EE5621A8149C00847FA6 /* PermissionInfoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE5521A8149C00847FA6 /* PermissionInfoItem.swift */; };
|
||||
09B4EE5E21AC626B00847FA6 /* PermissionContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE5D21AC626B00847FA6 /* PermissionContentNode.swift */; };
|
||||
09B4EE6021AD4A0E00847FA6 /* InstantPageContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE5F21AD4A0E00847FA6 /* InstantPageContentNode.swift */; };
|
||||
09B4EE6221AD791600847FA6 /* InstantPageStoredState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE6121AD791600847FA6 /* InstantPageStoredState.swift */; };
|
||||
@ -1110,6 +1111,7 @@
|
||||
09619B8D21A34C0100493558 /* InstantPageScrollableNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageScrollableNode.swift; sourceTree = "<group>"; };
|
||||
09619B9321A4ABF500493558 /* InstantPageReferenceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantPageReferenceController.swift; sourceTree = "<group>"; };
|
||||
09619B9421A4ABF600493558 /* InstantPageReferenceControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantPageReferenceControllerNode.swift; sourceTree = "<group>"; };
|
||||
0962E65C21B1486D00245FD9 /* CallDebugNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallDebugNode.swift; sourceTree = "<group>"; };
|
||||
096C98B921787A5C00C211FF /* LegacyBridgeAudio.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyBridgeAudio.swift; sourceTree = "<group>"; };
|
||||
096C98BB21787C6600C211FF /* TGBridgeAudioEncoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGBridgeAudioEncoder.m; sourceTree = "<group>"; };
|
||||
096C98BC21787C6600C211FF /* TGBridgeAudioEncoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGBridgeAudioEncoder.h; sourceTree = "<group>"; };
|
||||
@ -1138,7 +1140,7 @@
|
||||
09B4EE4C21A7B73800847FA6 /* PermissionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionController.swift; sourceTree = "<group>"; };
|
||||
09B4EE4E21A7B75D00847FA6 /* PermissionControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionControllerNode.swift; sourceTree = "<group>"; };
|
||||
09B4EE5121A7CC3E00847FA6 /* SolidRoundedButtonNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SolidRoundedButtonNode.swift; sourceTree = "<group>"; };
|
||||
09B4EE5521A8149C00847FA6 /* NotificationPermissionInfoItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionInfoItem.swift; sourceTree = "<group>"; };
|
||||
09B4EE5521A8149C00847FA6 /* PermissionInfoItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionInfoItem.swift; sourceTree = "<group>"; };
|
||||
09B4EE5D21AC626B00847FA6 /* PermissionContentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionContentNode.swift; sourceTree = "<group>"; };
|
||||
09B4EE5F21AD4A0E00847FA6 /* InstantPageContentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageContentNode.swift; sourceTree = "<group>"; };
|
||||
09B4EE6121AD791600847FA6 /* InstantPageStoredState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageStoredState.swift; sourceTree = "<group>"; };
|
||||
@ -2605,7 +2607,7 @@
|
||||
D01B279C1E394A500022A4C0 /* NotificationsAndSounds.swift */,
|
||||
D0A749961E3AA25200AD786E /* NotificationSoundSelection.swift */,
|
||||
D02C81722177AC5900CD1006 /* NotificationSearchItem.swift */,
|
||||
09B4EE5521A8149C00847FA6 /* NotificationPermissionInfoItem.swift */,
|
||||
09B4EE5521A8149C00847FA6 /* PermissionInfoItem.swift */,
|
||||
);
|
||||
name = Notifications;
|
||||
sourceTree = "<group>";
|
||||
@ -3968,6 +3970,7 @@
|
||||
D0F0AAE51EC21B68005EE2A5 /* CallControllerButton.swift */,
|
||||
D0ACCB191EC5E0C20079D8BF /* CallControllerKeyPreviewNode.swift */,
|
||||
09FE756C2153F5F900A3120F /* CallRouteActionSheetItem.swift */,
|
||||
0962E65C21B1486D00245FD9 /* CallDebugNode.swift */,
|
||||
);
|
||||
name = Call;
|
||||
sourceTree = "<group>";
|
||||
@ -5474,6 +5477,7 @@
|
||||
D0EC6DBC1EB9F58900EBF1C3 /* ChatMediaInputGifPane.swift in Sources */,
|
||||
D0EC6DBD1EB9F58900EBF1C3 /* ChatMediaInputPanelEntries.swift in Sources */,
|
||||
D0471B4F1EFD84600074D609 /* BotCheckoutPriceItem.swift in Sources */,
|
||||
0962E65D21B1486D00245FD9 /* CallDebugNode.swift in Sources */,
|
||||
D00ADFDB1EBA2EAF00873D2E /* OngoingCallContext.swift in Sources */,
|
||||
D0EC6DBE1EB9F58900EBF1C3 /* ChatMediaInputGridEntries.swift in Sources */,
|
||||
D0EC6DBF1EB9F58900EBF1C3 /* ChatMediaInputMetaSectionItemNode.swift in Sources */,
|
||||
@ -5509,7 +5513,7 @@
|
||||
D0BCC3D2203F0A6C008126C2 /* StringForMessageTimestampStatus.swift in Sources */,
|
||||
D0EC6DCF1EB9F58900EBF1C3 /* HorizontalStickerGridItem.swift in Sources */,
|
||||
D0EC6DD01EB9F58900EBF1C3 /* HashtagChatInputContextPanelNode.swift in Sources */,
|
||||
09B4EE5621A8149C00847FA6 /* NotificationPermissionInfoItem.swift in Sources */,
|
||||
09B4EE5621A8149C00847FA6 /* PermissionInfoItem.swift in Sources */,
|
||||
D0EC6DD11EB9F58900EBF1C3 /* HashtagChatInputPanelItem.swift in Sources */,
|
||||
D0EC6DD21EB9F58900EBF1C3 /* MentionChatInputContextPanelNode.swift in Sources */,
|
||||
D00701A22029F6D0006B9E34 /* TGMimeTypeMap.m in Sources */,
|
||||
|
||||
@ -178,6 +178,7 @@ final class CallControllerNode: ASDisplayNode {
|
||||
self.callState = callState
|
||||
|
||||
let statusValue: CallControllerStatusValue
|
||||
var statusReception: Int32?
|
||||
switch callState {
|
||||
case .waiting, .connecting:
|
||||
statusValue = .text(self.presentationData.strings.Call_StatusConnecting)
|
||||
@ -207,7 +208,7 @@ final class CallControllerNode: ASDisplayNode {
|
||||
}
|
||||
case .ringing:
|
||||
statusValue = .text(self.presentationData.strings.Call_StatusIncoming)
|
||||
case let .active(timestamp, keyVisualHash):
|
||||
case let .active(timestamp, reception, keyVisualHash):
|
||||
let strings = self.presentationData.strings
|
||||
statusValue = .timer({ value in
|
||||
return strings.Call_StatusOngoing(value).0
|
||||
@ -226,6 +227,7 @@ final class CallControllerNode: ASDisplayNode {
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||
}
|
||||
}
|
||||
statusReception = reception
|
||||
}
|
||||
switch callState {
|
||||
case .terminated, .terminating:
|
||||
@ -258,6 +260,7 @@ final class CallControllerNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
self.statusNode.status = statusValue
|
||||
self.statusNode.reception = statusReception
|
||||
|
||||
self.updateButtonsMode()
|
||||
}
|
||||
@ -503,126 +506,3 @@ final class CallControllerNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func attributedStringForDebugInfo(_ info: String, version: String) -> NSAttributedString {
|
||||
guard !info.isEmpty else {
|
||||
return NSAttributedString(string: "")
|
||||
}
|
||||
|
||||
var string = info
|
||||
string = "libtgvoip v\(version)\n" + string
|
||||
string = string.replacingOccurrences(of: "Remote endpoints: \n", with: "")
|
||||
string = string.replacingOccurrences(of: "Jitter ", with: "\nJitter ")
|
||||
string = string.replacingOccurrences(of: "Key fingerprint:\n", with: "Key fingerprint: ")
|
||||
|
||||
let attributedString = NSMutableAttributedString(string: string, attributes: [NSAttributedStringKey.font: Font.monospace(10), NSAttributedStringKey.foregroundColor: UIColor.white])
|
||||
|
||||
let titleStyle = NSMutableParagraphStyle()
|
||||
titleStyle.alignment = .center
|
||||
titleStyle.lineSpacing = 7.0
|
||||
|
||||
let style = NSMutableParagraphStyle()
|
||||
style.lineHeightMultiple = 1.15
|
||||
|
||||
let secondaryColor = UIColor(rgb: 0xa6a9a8)
|
||||
let activeColor = UIColor(rgb: 0xa0d875)
|
||||
|
||||
let titleAttributes = [NSAttributedStringKey.font: Font.semiboldMonospace(15), NSAttributedStringKey.paragraphStyle: titleStyle]
|
||||
let nameAttributes = [NSAttributedStringKey.font: Font.semiboldMonospace(10), NSAttributedStringKey.foregroundColor: secondaryColor]
|
||||
let styleAttributes = [NSAttributedStringKey.paragraphStyle: style]
|
||||
let typeAttributes = [NSAttributedStringKey.foregroundColor: secondaryColor]
|
||||
let activeAttributes = [NSAttributedStringKey.font: Font.semiboldMonospace(10), NSAttributedStringKey.foregroundColor: activeColor]
|
||||
|
||||
let range = string.startIndex ..< string.endIndex
|
||||
string.enumerateSubstrings(in: range, options: NSString.EnumerationOptions.byLines) { (line, range, _, _) in
|
||||
guard let line = line else {
|
||||
return
|
||||
}
|
||||
if range.lowerBound == string.startIndex {
|
||||
attributedString.addAttributes(titleAttributes, range: NSRange(range, in: string))
|
||||
}
|
||||
else {
|
||||
if let semicolonRange = line.range(of: ":") {
|
||||
if let bracketRange = line.range(of: "[") {
|
||||
if let _ = line.range(of: "IN_USE") {
|
||||
attributedString.addAttributes(activeAttributes, range: NSRange(range, in: string))
|
||||
} else {
|
||||
let offset = line.distance(from: line.startIndex, to: bracketRange.lowerBound)
|
||||
let distance = line.distance(from: line.startIndex, to: line.endIndex)
|
||||
attributedString.addAttributes(typeAttributes, range: NSRange(string.index(range.lowerBound, offsetBy: offset) ..< string.index(range.lowerBound, offsetBy: distance), in: string))
|
||||
}
|
||||
} else {
|
||||
attributedString.addAttributes(styleAttributes, range: NSRange(range, in: string))
|
||||
|
||||
let offset = line.distance(from: line.startIndex, to: semicolonRange.upperBound)
|
||||
attributedString.addAttributes(nameAttributes, range: NSRange(range.lowerBound ..< string.index(range.lowerBound, offsetBy: offset), in: string))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return attributedString
|
||||
}
|
||||
|
||||
final private class CallDebugNode: ASDisplayNode {
|
||||
private let disposable = MetaDisposable()
|
||||
|
||||
private let dimNode: ASDisplayNode
|
||||
private let textNode: ASTextNode
|
||||
|
||||
private let timestamp = CACurrentMediaTime()
|
||||
|
||||
public var dismiss: (() -> Void)?
|
||||
|
||||
init(signal: Signal<(String, String), NoError>) {
|
||||
self.dimNode = ASDisplayNode()
|
||||
self.dimNode.isLayerBacked = true
|
||||
self.dimNode.backgroundColor = UIColor(rgb: 0x26282c, alpha: 0.95)
|
||||
self.dimNode.isUserInteractionEnabled = false
|
||||
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.dimNode)
|
||||
self.addSubnode(self.textNode)
|
||||
|
||||
self.disposable.set((signal
|
||||
|> deliverOnMainQueue).start(next: { [weak self] (version, info) in
|
||||
self?.update(info, version: version)
|
||||
}))
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
|
||||
self.view.addGestureRecognizer(tapRecognizer)
|
||||
}
|
||||
|
||||
private func update(_ info: String, version: String) {
|
||||
self.textNode.attributedText = attributedStringForDebugInfo(info, version: version)
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
|
||||
@objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if CACurrentMediaTime() - self.timestamp > 1.0 {
|
||||
self.dismiss?()
|
||||
}
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
|
||||
let size = self.bounds.size
|
||||
self.dimNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))
|
||||
|
||||
let textSize = textNode.measure(CGSize(width: size.width - 20.0, height: size.height))
|
||||
self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize)
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,6 +35,7 @@ final class CallControllerStatusNode: ASDisplayNode {
|
||||
private let titleNode: TextNode
|
||||
private let statusNode: TextNode
|
||||
private let statusMeasureNode: TextNode
|
||||
private let receptionNode: CallControllerReceptionNode
|
||||
|
||||
var title: String = ""
|
||||
var status: CallControllerStatusValue = .text("") {
|
||||
@ -57,6 +58,23 @@ final class CallControllerStatusNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
var reception: Int32? {
|
||||
didSet {
|
||||
if self.reception != oldValue {
|
||||
if let reception = self.reception {
|
||||
self.receptionNode.reception = reception
|
||||
|
||||
if oldValue == nil {
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring)
|
||||
transition.updateAlpha(node: self.receptionNode, alpha: 1.0)
|
||||
}
|
||||
} else if self.reception == nil, oldValue != nil {
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring)
|
||||
transition.updateAlpha(node: self.receptionNode, alpha: 0.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var statusTimer: SwiftSignalKit.Timer?
|
||||
private var validLayoutWidth: CGFloat?
|
||||
@ -66,6 +84,9 @@ final class CallControllerStatusNode: ASDisplayNode {
|
||||
self.statusNode = TextNode()
|
||||
self.statusNode.displaysAsynchronously = false
|
||||
self.statusMeasureNode = TextNode()
|
||||
|
||||
self.receptionNode = CallControllerReceptionNode()
|
||||
self.receptionNode.alpha = 0.0
|
||||
|
||||
super.init()
|
||||
|
||||
@ -73,6 +94,7 @@ final class CallControllerStatusNode: ASDisplayNode {
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.statusNode)
|
||||
self.addSubnode(self.receptionNode)
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -92,6 +114,7 @@ final class CallControllerStatusNode: ASDisplayNode {
|
||||
statusFont = regularStatusFont
|
||||
}
|
||||
|
||||
var statusOffset: CGFloat = 0.0
|
||||
let statusText: String
|
||||
let statusMeasureText: String
|
||||
switch self.status {
|
||||
@ -111,6 +134,7 @@ final class CallControllerStatusNode: ASDisplayNode {
|
||||
}
|
||||
statusText = format(durationString)
|
||||
statusMeasureText = format(measureDurationString)
|
||||
statusOffset += 8.0
|
||||
}
|
||||
|
||||
let spacing: CGFloat = 4.0
|
||||
@ -123,8 +147,65 @@ final class CallControllerStatusNode: ASDisplayNode {
|
||||
let _ = statusMeasureApply()
|
||||
|
||||
self.titleNode.frame = CGRect(origin: CGPoint(x: floor((constrainedWidth - titleLayout.size.width) / 2.0), y: 0.0), size: titleLayout.size)
|
||||
self.statusNode.frame = CGRect(origin: CGPoint(x: floor((constrainedWidth - statusMeasureLayout.size.width) / 2.0), y: titleLayout.size.height + spacing), size: statusLayout.size)
|
||||
self.statusNode.frame = CGRect(origin: CGPoint(x: floor((constrainedWidth - statusMeasureLayout.size.width) / 2.0) + statusOffset, y: titleLayout.size.height + spacing), size: statusLayout.size)
|
||||
self.receptionNode.frame = CGRect(origin: CGPoint(x: self.statusNode.frame.minX - receptionNodeSize.width, y: titleLayout.size.height + spacing + 9.0), size: receptionNodeSize)
|
||||
|
||||
return titleLayout.size.height + spacing + statusLayout.size.height
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final class CallControllerReceptionNodeParameters: NSObject {
|
||||
let reception: Int32
|
||||
|
||||
init(reception: Int32) {
|
||||
self.reception = reception
|
||||
}
|
||||
}
|
||||
|
||||
private let receptionNodeSize = CGSize(width: 24.0, height: 10.0)
|
||||
|
||||
final class CallControllerReceptionNode : ASDisplayNode {
|
||||
var reception: Int32 = 4 {
|
||||
didSet {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
self.isOpaque = false
|
||||
self.isLayerBacked = true
|
||||
}
|
||||
|
||||
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
|
||||
return CallControllerReceptionNodeParameters(reception: self.reception)
|
||||
}
|
||||
|
||||
@objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
|
||||
if let parameters = parameters as? CallControllerReceptionNodeParameters{
|
||||
let width: CGFloat = 3.0
|
||||
var spacing: CGFloat = 1.5
|
||||
if UIScreenScale > 2 {
|
||||
spacing = 4.0 / 3.0
|
||||
}
|
||||
|
||||
for i in 0 ..< 4 {
|
||||
let height = 4.0 + 2.0 * CGFloat(i)
|
||||
let rect = CGRect(x: bounds.minX + CGFloat(i) * (width + spacing), y: receptionNodeSize.height - height, width: width, height: height)
|
||||
|
||||
if i >= parameters.reception {
|
||||
context.setAlpha(0.4)
|
||||
}
|
||||
|
||||
let path = UIBezierPath(roundedRect: rect, cornerRadius: 1.0)
|
||||
context.addPath(path.cgPath)
|
||||
context.fillPath()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
127
TelegramUI/CallDebugNode.swift
Normal file
@ -0,0 +1,127 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
|
||||
private func attributedStringForDebugInfo(_ info: String, version: String) -> NSAttributedString {
|
||||
guard !info.isEmpty else {
|
||||
return NSAttributedString(string: "")
|
||||
}
|
||||
|
||||
var string = info
|
||||
string = "libtgvoip v\(version)\n" + string
|
||||
string = string.replacingOccurrences(of: "Remote endpoints: \n", with: "")
|
||||
string = string.replacingOccurrences(of: "Jitter ", with: "\nJitter ")
|
||||
string = string.replacingOccurrences(of: "Key fingerprint:\n", with: "Key fingerprint: ")
|
||||
|
||||
let attributedString = NSMutableAttributedString(string: string, attributes: [NSAttributedStringKey.font: Font.monospace(10), NSAttributedStringKey.foregroundColor: UIColor.white])
|
||||
|
||||
let titleStyle = NSMutableParagraphStyle()
|
||||
titleStyle.alignment = .center
|
||||
titleStyle.lineSpacing = 7.0
|
||||
|
||||
let style = NSMutableParagraphStyle()
|
||||
style.lineHeightMultiple = 1.15
|
||||
|
||||
let secondaryColor = UIColor(rgb: 0xa6a9a8)
|
||||
let activeColor = UIColor(rgb: 0xa0d875)
|
||||
|
||||
let titleAttributes = [NSAttributedStringKey.font: Font.semiboldMonospace(15), NSAttributedStringKey.paragraphStyle: titleStyle]
|
||||
let nameAttributes = [NSAttributedStringKey.font: Font.semiboldMonospace(10), NSAttributedStringKey.foregroundColor: secondaryColor]
|
||||
let styleAttributes = [NSAttributedStringKey.paragraphStyle: style]
|
||||
let typeAttributes = [NSAttributedStringKey.foregroundColor: secondaryColor]
|
||||
let activeAttributes = [NSAttributedStringKey.font: Font.semiboldMonospace(10), NSAttributedStringKey.foregroundColor: activeColor]
|
||||
|
||||
let range = string.startIndex ..< string.endIndex
|
||||
string.enumerateSubstrings(in: range, options: NSString.EnumerationOptions.byLines) { (line, range, _, _) in
|
||||
guard let line = line else {
|
||||
return
|
||||
}
|
||||
if range.lowerBound == string.startIndex {
|
||||
attributedString.addAttributes(titleAttributes, range: NSRange(range, in: string))
|
||||
}
|
||||
else {
|
||||
if let semicolonRange = line.range(of: ":") {
|
||||
if let bracketRange = line.range(of: "[") {
|
||||
if let _ = line.range(of: "IN_USE") {
|
||||
attributedString.addAttributes(activeAttributes, range: NSRange(range, in: string))
|
||||
} else {
|
||||
let offset = line.distance(from: line.startIndex, to: bracketRange.lowerBound)
|
||||
let distance = line.distance(from: line.startIndex, to: line.endIndex)
|
||||
attributedString.addAttributes(typeAttributes, range: NSRange(string.index(range.lowerBound, offsetBy: offset) ..< string.index(range.lowerBound, offsetBy: distance), in: string))
|
||||
}
|
||||
} else {
|
||||
attributedString.addAttributes(styleAttributes, range: NSRange(range, in: string))
|
||||
|
||||
let offset = line.distance(from: line.startIndex, to: semicolonRange.upperBound)
|
||||
attributedString.addAttributes(nameAttributes, range: NSRange(range.lowerBound ..< string.index(range.lowerBound, offsetBy: offset), in: string))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return attributedString
|
||||
}
|
||||
|
||||
final class CallDebugNode: ASDisplayNode {
|
||||
private let disposable = MetaDisposable()
|
||||
|
||||
private let dimNode: ASDisplayNode
|
||||
private let textNode: ASTextNode
|
||||
|
||||
private let timestamp = CACurrentMediaTime()
|
||||
|
||||
public var dismiss: (() -> Void)?
|
||||
|
||||
init(signal: Signal<(String, String), NoError>) {
|
||||
self.dimNode = ASDisplayNode()
|
||||
self.dimNode.isLayerBacked = true
|
||||
self.dimNode.backgroundColor = UIColor(rgb: 0x26282c, alpha: 0.95)
|
||||
self.dimNode.isUserInteractionEnabled = false
|
||||
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.dimNode)
|
||||
self.addSubnode(self.textNode)
|
||||
|
||||
self.disposable.set((signal
|
||||
|> deliverOnMainQueue).start(next: { [weak self] (version, info) in
|
||||
self?.update(info, version: version)
|
||||
}))
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
|
||||
self.view.addGestureRecognizer(tapRecognizer)
|
||||
}
|
||||
|
||||
private func update(_ info: String, version: String) {
|
||||
self.textNode.attributedText = attributedStringForDebugInfo(info, version: version)
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
|
||||
@objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if CACurrentMediaTime() - self.timestamp > 1.0 {
|
||||
self.dismiss?()
|
||||
}
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
|
||||
let size = self.bounds.size
|
||||
self.dimNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))
|
||||
|
||||
let textSize = textNode.measure(CGSize(width: size.width - 20.0, height: size.height))
|
||||
self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize)
|
||||
}
|
||||
}
|
||||
@ -417,7 +417,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
|
||||
self?.openPeer(peerId: id, navigation: navigation, fromMessage: fromMessage)
|
||||
}, openPeerMention: { [weak self] name in
|
||||
self?.openPeerMention(name)
|
||||
}, openMessageContextMenu: { [weak self] message, node, frame in
|
||||
}, openMessageContextMenu: { [weak self] message, selectAll, node, frame in
|
||||
guard let strongSelf = self, strongSelf.isNodeLoaded else {
|
||||
return
|
||||
}
|
||||
@ -430,7 +430,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
|
||||
break
|
||||
}
|
||||
}
|
||||
let _ = contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: strongSelf.presentationInterfaceState, account: strongSelf.account, messages: updatedMessages, controllerInteraction: strongSelf.controllerInteraction, interfaceInteraction: strongSelf.interfaceInteraction).start(next: { actions in
|
||||
let _ = contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: strongSelf.presentationInterfaceState, account: strongSelf.account, messages: updatedMessages, controllerInteraction: strongSelf.controllerInteraction, selectAll: selectAll, interfaceInteraction: strongSelf.interfaceInteraction).start(next: { actions in
|
||||
guard let strongSelf = self, !actions.isEmpty else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ public final class ChatControllerInteraction {
|
||||
let openMessage: (Message, ChatControllerInteractionOpenMessageMode) -> Bool
|
||||
let openPeer: (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void
|
||||
let openPeerMention: (String) -> Void
|
||||
let openMessageContextMenu: (Message, ASDisplayNode, CGRect) -> Void
|
||||
let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect) -> Void
|
||||
let navigateToMessage: (MessageId, MessageId) -> Void
|
||||
let clickThroughMessage: () -> Void
|
||||
let toggleMessagesSelection: ([MessageId], Bool) -> Void
|
||||
@ -87,7 +87,7 @@ public final class ChatControllerInteraction {
|
||||
var contextHighlightedState: ChatInterfaceHighlightedState?
|
||||
var automaticMediaDownloadSettings: AutomaticMediaDownloadSettings
|
||||
|
||||
init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, ASDisplayNode, CGRect) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool) -> Void, sendGif: @escaping (FileMediaReference) -> Void, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: AutomaticMediaDownloadSettings) {
|
||||
init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool) -> Void, sendGif: @escaping (FileMediaReference) -> Void, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: AutomaticMediaDownloadSettings) {
|
||||
self.openMessage = openMessage
|
||||
self.openPeer = openPeer
|
||||
self.openPeerMention = openPeerMention
|
||||
|
||||
@ -149,7 +149,7 @@ func updatedChatEditInterfaceMessagetState(state: ChatPresentationInterfaceState
|
||||
return updated
|
||||
}
|
||||
|
||||
func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: ChatPresentationInterfaceState, account: Account, messages: [Message], controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> Signal<[ChatMessageContextMenuAction], NoError> {
|
||||
func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: ChatPresentationInterfaceState, account: Account, messages: [Message], controllerInteraction: ChatControllerInteraction?, selectAll: Bool, interfaceInteraction: ChatPanelInterfaceInteraction?) -> Signal<[ChatMessageContextMenuAction], NoError> {
|
||||
guard let interfaceInteraction = interfaceInteraction, let controllerInteraction = controllerInteraction else {
|
||||
return .single([])
|
||||
}
|
||||
@ -444,7 +444,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
|
||||
}
|
||||
if data.canSelect {
|
||||
actions.append(.context(ContextMenuAction(content: .text(chatPresentationInterfaceState.strings.Conversation_ContextMenuMore), action: {
|
||||
interfaceInteraction.beginMessageSelection([message.id])
|
||||
interfaceInteraction.beginMessageSelection(selectAll ? messages.map { $0.id } : [message.id])
|
||||
})))
|
||||
}
|
||||
if !data.messageActions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty && isAction {
|
||||
@ -461,19 +461,19 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
|
||||
|
||||
if data.messageActions.options.contains(.forward) {
|
||||
actions.append(.sheet(ChatMessageContextMenuSheetAction(color: .accent, title: chatPresentationInterfaceState.strings.Conversation_ContextMenuForward, action: {
|
||||
interfaceInteraction.forwardMessages([message])
|
||||
interfaceInteraction.forwardMessages(selectAll ? messages : [message])
|
||||
})))
|
||||
}
|
||||
|
||||
if data.messageActions.options.contains(.report) {
|
||||
actions.append(.sheet(ChatMessageContextMenuSheetAction(color: .accent, title: chatPresentationInterfaceState.strings.Conversation_ContextMenuReport, action: {
|
||||
interfaceInteraction.reportMessages([message])
|
||||
interfaceInteraction.reportMessages(selectAll ? messages : [message])
|
||||
})))
|
||||
}
|
||||
|
||||
if !data.messageActions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty {
|
||||
actions.append(.sheet(ChatMessageContextMenuSheetAction(color: .destructive, title: chatPresentationInterfaceState.strings.Conversation_ContextMenuDelete, action: {
|
||||
interfaceInteraction.deleteMessages([message])
|
||||
interfaceInteraction.deleteMessages(selectAll ? messages : [message])
|
||||
})))
|
||||
}
|
||||
|
||||
|
||||
@ -200,7 +200,7 @@ private func revealOptions(strings: PresentationStrings, theme: PresentationThem
|
||||
private func leftRevealOptions(strings: PresentationStrings, theme: PresentationTheme, isUnread: Bool) -> [ItemListRevealOption] {
|
||||
var options: [ItemListRevealOption] = []
|
||||
if isUnread {
|
||||
options.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: strings.DialogList_Read, icon: readIcon, color: theme.list.itemDisclosureActions.neutral1.fillColor, textColor: theme.list.itemDisclosureActions.neutral1.foregroundColor))
|
||||
options.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: strings.DialogList_Read, icon: readIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.neutral1.foregroundColor))
|
||||
} else {
|
||||
options.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: strings.DialogList_Unread, icon: unreadIcon, color: theme.list.itemDisclosureActions.accent.fillColor, textColor: theme.list.itemDisclosureActions.accent.foregroundColor))
|
||||
}
|
||||
|
||||
@ -1619,9 +1619,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
|
||||
if let item = self.item, self.backgroundNode.frame.contains(location) {
|
||||
var foundTapAction = false
|
||||
var tapMessage: Message? = item.content.firstMessage
|
||||
var selectAll = false
|
||||
loop: for contentNode in self.contentNodes {
|
||||
if !contentNode.frame.contains(location) {
|
||||
continue loop
|
||||
} else if contentNode is ChatMessageTextBubbleContentNode {
|
||||
selectAll = true
|
||||
}
|
||||
tapMessage = contentNode.item?.message
|
||||
let tapAction = contentNode.tapActionAtPoint(CGPoint(x: location.x - contentNode.frame.minX, y: location.y - contentNode.frame.minY))
|
||||
@ -1667,7 +1670,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
|
||||
}
|
||||
}
|
||||
}
|
||||
item.controllerInteraction.openMessageContextMenu(tapMessage, self, subFrame)
|
||||
item.controllerInteraction.openMessageContextMenu(tapMessage, selectAll, self, subFrame)
|
||||
}
|
||||
}
|
||||
default:
|
||||
|
||||
@ -362,7 +362,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
|
||||
self.item?.controllerInteraction.clickThroughMessage()
|
||||
case .longTap, .doubleTap:
|
||||
if let item = self.item, let videoContentNode = self.interactiveVideoNode.videoContentNode(at: self.view.convert(location, to: self.interactiveVideoNode.view)) {
|
||||
item.controllerInteraction.openMessageContextMenu(item.message, videoContentNode, videoContentNode.bounds)
|
||||
item.controllerInteraction.openMessageContextMenu(item.message, false, videoContentNode, videoContentNode.bounds)
|
||||
}
|
||||
case .hold:
|
||||
break
|
||||
|
||||
@ -524,6 +524,10 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
strongSelf.iconNode = nil
|
||||
}
|
||||
|
||||
if let streamingStatusNode = strongSelf.streamingStatusNode {
|
||||
streamingStatusNode.frame = streamingCacheStatusFrame
|
||||
}
|
||||
|
||||
if let updatedStatusSignal = updatedStatusSignal {
|
||||
strongSelf.statusDisposable.set((updatedStatusSignal |> deliverOnMainQueue).start(next: { [weak strongSelf] status in
|
||||
displayLinkDispatcher.dispatch {
|
||||
|
||||
@ -574,7 +574,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
self.item?.controllerInteraction.clickThroughMessage()
|
||||
case .longTap, .doubleTap:
|
||||
if let item = self.item, let videoNode = self.videoNode, videoNode.frame.contains(location) {
|
||||
item.controllerInteraction.openMessageContextMenu(item.message, self, videoNode.frame)
|
||||
item.controllerInteraction.openMessageContextMenu(item.message, false, self, videoNode.frame)
|
||||
}
|
||||
case .hold:
|
||||
break
|
||||
@ -610,7 +610,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
private func progressPressed() {
|
||||
guard let item = self.item, let _ = self.telegramFile else {
|
||||
guard let item = self.item, let file = self.telegramFile else {
|
||||
return
|
||||
}
|
||||
if let status = self.status {
|
||||
@ -624,7 +624,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
deleteMessages(transaction: transaction, mediaBox: item.account.postbox.mediaBox, ids: [messageId])
|
||||
}).start()
|
||||
} else {
|
||||
self.videoNode?.fetchControl(.cancel)
|
||||
messageMediaFileCancelInteractiveFetch(account: item.account, messageId: item.message.id, file: file)
|
||||
}
|
||||
case .Remote:
|
||||
self.videoNode?.fetchControl(.fetch)
|
||||
|
||||
@ -377,7 +377,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
self.item?.controllerInteraction.clickThroughMessage()
|
||||
case .longTap, .doubleTap:
|
||||
if let item = self.item, self.imageNode.frame.contains(location) {
|
||||
item.controllerInteraction.openMessageContextMenu(item.message, self, self.imageNode.frame)
|
||||
item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.imageNode.frame)
|
||||
}
|
||||
case .hold:
|
||||
break
|
||||
|
||||
@ -176,8 +176,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}
|
||||
}, openPeerMention: { [weak self] name in
|
||||
self?.openPeerMention(name)
|
||||
}, openMessageContextMenu: { [weak self] message, node, frame in
|
||||
self?.openMessageContextMenu(message: message, node: node, frame: frame)
|
||||
}, openMessageContextMenu: { [weak self] message, selectAll, node, frame in
|
||||
self?.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame)
|
||||
}, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _ in
|
||||
self?.openUrl(url)
|
||||
}, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message in
|
||||
@ -622,7 +622,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}))
|
||||
}
|
||||
|
||||
private func openMessageContextMenu(message: Message, node: ASDisplayNode, frame: CGRect) {
|
||||
private func openMessageContextMenu(message: Message, selectAll: Bool, node: ASDisplayNode, frame: CGRect) {
|
||||
var actions: [ContextMenuAction] = []
|
||||
if !message.text.isEmpty {
|
||||
actions.append(ContextMenuAction(content: .text(self.presentationData.strings.Conversation_ContextMenuCopy), action: {
|
||||
|
||||
@ -8,18 +8,21 @@ class ContactListActionItem: ListViewItem {
|
||||
let title: String
|
||||
let icon: UIImage?
|
||||
let action: () -> Void
|
||||
let header: ListViewItemHeader?
|
||||
|
||||
init(theme: PresentationTheme, title: String, icon: UIImage?, action: @escaping () -> Void) {
|
||||
init(theme: PresentationTheme, title: String, icon: UIImage?, header: ListViewItemHeader?, action: @escaping () -> Void) {
|
||||
self.theme = theme
|
||||
self.title = title
|
||||
self.icon = icon
|
||||
self.header = header
|
||||
self.action = action
|
||||
}
|
||||
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, () -> Void)) -> Void) {
|
||||
async {
|
||||
let node = ContactListActionItemNode()
|
||||
let (layout, apply) = node.asyncLayout()(self, params)
|
||||
let (_, _, firstWithHeader) = ContactListActionItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||
let (layout, apply) = node.asyncLayout()(self, params, firstWithHeader)
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
@ -38,7 +41,8 @@ class ContactListActionItem: ListViewItem {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params)
|
||||
let (_, _, firstWithHeader) = ContactListActionItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||
let (layout, apply) = makeLayout(self, params, firstWithHeader)
|
||||
Queue.mainQueue().async {
|
||||
completion(layout, {
|
||||
apply()
|
||||
@ -55,6 +59,40 @@ class ContactListActionItem: ListViewItem {
|
||||
listView.clearHighlightAnimated(true)
|
||||
self.action()
|
||||
}
|
||||
|
||||
static func mergeType(item: ContactListActionItem, previousItem: ListViewItem?, nextItem: ListViewItem?) -> (first: Bool, last: Bool, firstWithHeader: Bool) {
|
||||
var first = false
|
||||
var last = false
|
||||
var firstWithHeader = false
|
||||
if let previousItem = previousItem {
|
||||
if let header = item.header {
|
||||
if let previousItem = previousItem as? ContactsPeerItem {
|
||||
firstWithHeader = header.id != previousItem.header?.id
|
||||
} else if let previousItem = previousItem as? ContactListActionItem {
|
||||
firstWithHeader = header.id != previousItem.header?.id
|
||||
} else {
|
||||
firstWithHeader = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
first = true
|
||||
firstWithHeader = item.header != nil
|
||||
}
|
||||
if let nextItem = nextItem {
|
||||
if let header = item.header {
|
||||
if let nextItem = nextItem as? ContactsPeerItem {
|
||||
last = header.id != nextItem.header?.id
|
||||
} else if let nextItem = nextItem as? ContactListActionItem {
|
||||
last = header.id != nextItem.header?.id
|
||||
} else {
|
||||
last = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
last = true
|
||||
}
|
||||
return (first, last, firstWithHeader)
|
||||
}
|
||||
}
|
||||
|
||||
private let titleFont = Font.regular(17.0)
|
||||
@ -70,6 +108,8 @@ class ContactListActionItemNode: ListViewItemNode {
|
||||
|
||||
private var theme: PresentationTheme?
|
||||
|
||||
private var item: ContactListActionItem?
|
||||
|
||||
init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
@ -100,29 +140,33 @@ class ContactListActionItemNode: ListViewItemNode {
|
||||
self.addSubnode(self.titleNode)
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: ContactListActionItem, _ params: ListViewItemLayoutParams) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
func asyncLayout() -> (_ item: ContactListActionItem, _ params: ListViewItemLayoutParams, _ firstWithHeader: Bool) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let currentTheme = self.theme
|
||||
|
||||
return { item, params in
|
||||
return { item, params, firstWithHeader in
|
||||
var updatedTheme: PresentationTheme?
|
||||
|
||||
if currentTheme !== item.theme {
|
||||
updatedTheme = item.theme
|
||||
}
|
||||
|
||||
let leftInset: CGFloat = 65.0 + params.leftInset
|
||||
var leftInset: CGFloat = 16.0 + params.leftInset
|
||||
if item.icon != nil {
|
||||
leftInset += 49.0
|
||||
}
|
||||
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: item.theme.list.itemAccentColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - 10.0 - leftInset - params.rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let contentSize = CGSize(width: params.width, height: 48.0)
|
||||
let insets = UIEdgeInsets()
|
||||
let insets = UIEdgeInsets(top: firstWithHeader ? 29.0 : 0.0, left: 0.0, bottom: 0.0, right: 0.0)
|
||||
let separatorHeight = UIScreenPixel
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
|
||||
return (layout, { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
strongSelf.theme = item.theme
|
||||
|
||||
if let _ = updatedTheme {
|
||||
@ -207,4 +251,12 @@ class ContactListActionItemNode: ListViewItemNode {
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
override public func header() -> ListViewItemHeader? {
|
||||
if let item = self.item {
|
||||
return item.header
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import TelegramCore
|
||||
|
||||
private enum ContactListNodeEntryId: Hashable {
|
||||
case search
|
||||
case permission(action: Bool)
|
||||
case option(index: Int)
|
||||
case peerId(Int64)
|
||||
case deviceContact(DeviceContactStableId)
|
||||
@ -15,8 +16,10 @@ private enum ContactListNodeEntryId: Hashable {
|
||||
switch self {
|
||||
case .search:
|
||||
return 0
|
||||
case let .permission(action):
|
||||
return (action ? 3 : 2).hashValue
|
||||
case let .option(index):
|
||||
return (index + 2).hashValue
|
||||
return (index + 4).hashValue
|
||||
case let .peerId(peerId):
|
||||
return peerId.hashValue
|
||||
case let .deviceContact(id):
|
||||
@ -37,6 +40,12 @@ private enum ContactListNodeEntryId: Hashable {
|
||||
default:
|
||||
return false
|
||||
}
|
||||
case let .permission(action):
|
||||
if case .permission(action) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .option(index):
|
||||
if case .option(index) = rhs {
|
||||
return true
|
||||
@ -62,10 +71,12 @@ private enum ContactListNodeEntryId: Hashable {
|
||||
|
||||
private final class ContactListNodeInteraction {
|
||||
let activateSearch: () -> Void
|
||||
let authorize: () -> Void
|
||||
let openPeer: (ContactListPeer) -> Void
|
||||
|
||||
init(activateSearch: @escaping () -> Void, openPeer: @escaping (ContactListPeer) -> Void) {
|
||||
init(activateSearch: @escaping () -> Void, authorize: @escaping () -> Void, openPeer: @escaping (ContactListPeer) -> Void) {
|
||||
self.activateSearch = activateSearch
|
||||
self.authorize = authorize
|
||||
self.openPeer = openPeer
|
||||
}
|
||||
}
|
||||
@ -117,14 +128,20 @@ enum ContactListPeer: Equatable {
|
||||
|
||||
private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
case search(PresentationTheme, PresentationStrings)
|
||||
case option(Int, ContactListAdditionalOption, PresentationTheme, PresentationStrings)
|
||||
case permissionInfo(PresentationTheme, PresentationStrings)
|
||||
case permissionEnable(PresentationTheme, String)
|
||||
case option(Int, ContactListAdditionalOption, ListViewItemHeader?, PresentationTheme, PresentationStrings)
|
||||
case peer(Int, ContactListPeer, PeerPresence?, ListViewItemHeader?, ContactsPeerItemSelection, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, Bool)
|
||||
|
||||
var stableId: ContactListNodeEntryId {
|
||||
switch self {
|
||||
case .search:
|
||||
return .search
|
||||
case let .option(index, _, _, _):
|
||||
case .permissionInfo:
|
||||
return .permission(action: false)
|
||||
case .permissionEnable:
|
||||
return .permission(action: true)
|
||||
case let .option(index, _, _, _, _):
|
||||
return .option(index: index)
|
||||
case let .peer(_, peer, _, _, _, _, _, _, _, _, _):
|
||||
switch peer {
|
||||
@ -142,8 +159,14 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
return ChatListSearchItem(theme: theme, placeholder: strings.Contacts_SearchLabel, activate: {
|
||||
interaction.activateSearch()
|
||||
})
|
||||
case let .option(_, option, theme, _):
|
||||
return ContactListActionItem(theme: theme, title: option.title, icon: option.icon, action: option.action)
|
||||
case let .permissionInfo(theme, strings):
|
||||
return PermissionInfoItem(theme: theme, strings: strings, subject: .contacts)
|
||||
case let .permissionEnable(theme, text):
|
||||
return ContactListActionItem(theme: theme, title: text, icon: nil, header: nil, action: {
|
||||
interaction.authorize()
|
||||
})
|
||||
case let .option(_, option, header, theme, _):
|
||||
return ContactListActionItem(theme: theme, title: option.title, icon: option.icon, header: header, action: option.action)
|
||||
case let .peer(_, peer, presence, header, selection, theme, strings, dateTimeFormat, nameSortOrder, nameDisplayOrder, enabled):
|
||||
let status: ContactsPeerItemStatus
|
||||
let itemPeer: ContactsPeerItemPeer
|
||||
@ -174,8 +197,20 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .option(lhsIndex, lhsOption, lhsTheme, lhsStrings):
|
||||
if case let .option(rhsIndex, rhsOption, rhsTheme, rhsStrings) = rhs, lhsIndex == rhsIndex, lhsOption == rhsOption, lhsTheme === rhsTheme, lhsStrings === rhsStrings {
|
||||
case let .permissionInfo(lhsTheme, lhsStrings):
|
||||
if case let .permissionInfo(rhsTheme, rhsStrings) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .permissionEnable(lhsTheme, lhsText):
|
||||
if case let .permissionEnable(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .option(lhsIndex, lhsOption, lhsHeader, lhsTheme, lhsStrings):
|
||||
if case let .option(rhsIndex, rhsOption, rhsHeader, rhsTheme, rhsStrings) = rhs, lhsIndex == rhsIndex, lhsOption == rhsOption, lhsHeader?.id == rhsHeader?.id, lhsTheme === rhsTheme, lhsStrings === rhsStrings {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -231,18 +266,32 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
switch lhs {
|
||||
case .search:
|
||||
return true
|
||||
case let .option(lhsIndex, _, _, _):
|
||||
case .permissionInfo:
|
||||
switch rhs {
|
||||
case .search:
|
||||
return false
|
||||
case let .option(rhsIndex, _, _, _):
|
||||
return lhsIndex < rhsIndex
|
||||
case .peer:
|
||||
default:
|
||||
return true
|
||||
}
|
||||
case .permissionEnable:
|
||||
switch rhs {
|
||||
case .search, .permissionInfo:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
case let .option(lhsIndex, _, _, _, _):
|
||||
switch rhs {
|
||||
case .search, .permissionInfo, .permissionEnable:
|
||||
return false
|
||||
case let .option(rhsIndex, _, _, _, _):
|
||||
return lhsIndex < rhsIndex
|
||||
case .peer:
|
||||
return true
|
||||
}
|
||||
case let .peer(lhsIndex, _, _, _, _, _, _, _, _, _, _):
|
||||
switch rhs {
|
||||
case .search, .option:
|
||||
case .search, .permissionInfo, .permissionEnable, .option:
|
||||
return false
|
||||
case let .peer(rhsIndex, _, _, _, _, _, _, _, _, _, _):
|
||||
return lhsIndex < rhsIndex
|
||||
@ -297,15 +346,37 @@ private extension PeerIndexNameRepresentation {
|
||||
}
|
||||
}
|
||||
|
||||
private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer], presences: [PeerId: PeerPresence], presentation: ContactListPresentation, selectionState: ContactListNodeGroupSelectionState?, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, disabledPeerIds:Set<PeerId>) -> [ContactListNodeEntry] {
|
||||
private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer], presences: [PeerId: PeerPresence], presentation: ContactListPresentation, selectionState: ContactListNodeGroupSelectionState?, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, disabledPeerIds:Set<PeerId>, authorizationStatus: AccessType) -> [ContactListNodeEntry] {
|
||||
var entries: [ContactListNodeEntry] = []
|
||||
|
||||
var commonHeader: ListViewItemHeader?
|
||||
var orderedPeers: [ContactListPeer]
|
||||
var headers: [ContactListPeerId: ContactListNameIndexHeader] = [:]
|
||||
|
||||
switch presentation {
|
||||
case let .orderedByPresence(options):
|
||||
entries.append(.search(theme, strings))
|
||||
|
||||
var addHeader = false
|
||||
if !peers.isEmpty {
|
||||
switch authorizationStatus {
|
||||
case .denied:
|
||||
entries.append(.permissionInfo(theme, strings))
|
||||
entries.append(.permissionEnable(theme, strings.Permissions_ContactsAllowInSettings))
|
||||
addHeader = true
|
||||
case .notDetermined:
|
||||
entries.append(.permissionInfo(theme, strings))
|
||||
entries.append(.permissionEnable(theme, strings.Permissions_ContactsAllow))
|
||||
addHeader = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if addHeader {
|
||||
commonHeader = ChatListSearchItemHeader(type: .contacts, theme: theme, strings: strings, actionTitle: nil, action: nil)
|
||||
}
|
||||
|
||||
orderedPeers = peers.sorted(by: { lhs, rhs in
|
||||
if case let .peer(lhsPeer, _) = lhs, case let .peer(rhsPeer, _) = rhs {
|
||||
let lhsPresence = presences[lhsPeer.id]
|
||||
@ -329,7 +400,7 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer]
|
||||
}
|
||||
})
|
||||
for i in 0 ..< options.count {
|
||||
entries.append(.option(i, options[i], theme, strings))
|
||||
entries.append(.option(i, options[i], commonHeader, theme, strings))
|
||||
}
|
||||
case let .natural(displaySearch, options):
|
||||
orderedPeers = peers.sorted(by: { lhs, rhs in
|
||||
@ -385,7 +456,7 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer]
|
||||
entries.append(.search(theme, strings))
|
||||
}
|
||||
for i in 0 ..< options.count {
|
||||
entries.append(.option(i, options[i], theme, strings))
|
||||
entries.append(.option(i, options[i], commonHeader, theme, strings))
|
||||
}
|
||||
case .search:
|
||||
orderedPeers = peers
|
||||
@ -410,14 +481,6 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer]
|
||||
}
|
||||
}
|
||||
|
||||
var commonHeader: ListViewItemHeader?
|
||||
switch presentation {
|
||||
case .orderedByPresence:
|
||||
commonHeader = nil //ChatListSearchItemHeader(type: .contacts, theme: theme, strings: strings, actionTitle: nil, action: nil)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
for i in 0 ..< orderedPeers.count {
|
||||
let selection: ContactsPeerItemSelection
|
||||
if let selectionState = selectionState {
|
||||
@ -448,14 +511,14 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer]
|
||||
return entries
|
||||
}
|
||||
|
||||
private func preparedContactListNodeTransition(account: Account, from fromEntries: [ContactListNodeEntry], to toEntries: [ContactListNodeEntry], interaction: ContactListNodeInteraction, firstTime: Bool, animated: Bool) -> ContactsListNodeTransition {
|
||||
private func preparedContactListNodeTransition(account: Account, from fromEntries: [ContactListNodeEntry], to toEntries: [ContactListNodeEntry], interaction: ContactListNodeInteraction, firstTime: Bool, isEmpty: Bool, animated: Bool) -> ContactsListNodeTransition {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||
|
||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, interaction: interaction), directionHint: nil) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, interaction: interaction), directionHint: nil) }
|
||||
|
||||
return ContactsListNodeTransition(deletions: deletions, insertions: insertions, updates: updates, firstTime: firstTime, animated: animated)
|
||||
return ContactsListNodeTransition(deletions: deletions, insertions: insertions, updates: updates, firstTime: firstTime, isEmpty: isEmpty, animated: animated)
|
||||
}
|
||||
|
||||
private struct ContactsListNodeTransition {
|
||||
@ -463,6 +526,7 @@ private struct ContactsListNodeTransition {
|
||||
let insertions: [ListViewInsertItem]
|
||||
let updates: [ListViewUpdateItem]
|
||||
let firstTime: Bool
|
||||
let isEmpty: Bool
|
||||
let animated: Bool
|
||||
}
|
||||
|
||||
@ -568,7 +632,10 @@ final class ContactListNode: ASDisplayNode {
|
||||
private var presentationDataDisposable: Disposable?
|
||||
private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, Bool)>
|
||||
|
||||
private var authorizationNode: PermissionContentNode?
|
||||
private let authorizationPromise: Promise<AccessType>
|
||||
private var authorizationDisposable: Disposable?
|
||||
|
||||
private var authorizationNode: PermissionContentNode
|
||||
|
||||
init(account: Account, presentation: ContactListPresentation, filters: [ContactListFilter] = [.excludeSelf], selectionState: ContactListNodeGroupSelectionState? = nil) {
|
||||
self.account = account
|
||||
@ -582,7 +649,17 @@ final class ContactListNode: ASDisplayNode {
|
||||
|
||||
self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings, self.presentationData.dateTimeFormat, self.presentationData.nameSortOrder, self.presentationData.nameDisplayOrder, self.presentationData.disableAnimations))
|
||||
|
||||
//self.authorizationNode = //PermissionContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings)
|
||||
self.authorizationPromise = Promise(AccessType.allowed)
|
||||
|
||||
var authorizeImpl: (() -> Void)?
|
||||
var openPrivacyPolicyImpl: (() -> Void)?
|
||||
|
||||
self.authorizationNode = PermissionContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, kind: .contacts, icon: UIImage(bundleImageName: "Settings/Permissions/Contacts"), title: self.presentationData.strings.Permissions_ContactsTitle, text: self.presentationData.strings.Permissions_ContactsText, buttonTitle: self.presentationData.strings.Contacts_PermissionsAllow, buttonAction: {
|
||||
authorizeImpl?()
|
||||
}, openPrivacyPolicy: {
|
||||
openPrivacyPolicyImpl?()
|
||||
})
|
||||
self.authorizationNode.isHidden = true
|
||||
|
||||
super.init()
|
||||
|
||||
@ -593,12 +670,15 @@ final class ContactListNode: ASDisplayNode {
|
||||
self.selectionStatePromise.set(.single(selectionState))
|
||||
|
||||
self.addSubnode(self.listNode)
|
||||
|
||||
self.addSubnode(self.authorizationNode)
|
||||
|
||||
let processingQueue = Queue()
|
||||
let previousEntries = Atomic<[ContactListNodeEntry]?>(value: nil)
|
||||
|
||||
let interaction = ContactListNodeInteraction(activateSearch: { [weak self] in
|
||||
self?.activateSearch?()
|
||||
}, authorize: {
|
||||
authorizeImpl?()
|
||||
}, openPeer: { [weak self] peer in
|
||||
self?.openPeer?(peer)
|
||||
})
|
||||
@ -608,6 +688,7 @@ final class ContactListNode: ASDisplayNode {
|
||||
let selectionStateSignal = self.selectionStatePromise.get()
|
||||
let transition: Signal<ContactsListNodeTransition, NoError>
|
||||
let themeAndStringsPromise = self.themeAndStringsPromise
|
||||
let authorizationsPromise = self.authorizationPromise
|
||||
if case let .search(query, searchChatList, searchDeviceContacts) = presentation {
|
||||
transition = query
|
||||
|> mapToSignal { query in
|
||||
@ -712,9 +793,9 @@ final class ContactListNode: ASDisplayNode {
|
||||
peers.append(.deviceContact(stableId, contact))
|
||||
}
|
||||
|
||||
let entries = contactListNodeEntries(accountPeer: nil, peers: peers, presences: localPeersAndStatuses.1, presentation: presentation, selectionState: selectionState, theme: themeAndStrings.0, strings: themeAndStrings.1, dateTimeFormat: themeAndStrings.2, sortOrder: themeAndStrings.3, displayOrder: themeAndStrings.4, disabledPeerIds: disabledPeerIds)
|
||||
let entries = contactListNodeEntries(accountPeer: nil, peers: peers, presences: localPeersAndStatuses.1, presentation: presentation, selectionState: selectionState, theme: themeAndStrings.0, strings: themeAndStrings.1, dateTimeFormat: themeAndStrings.2, sortOrder: themeAndStrings.3, displayOrder: themeAndStrings.4, disabledPeerIds: disabledPeerIds, authorizationStatus: .allowed)
|
||||
let previous = previousEntries.swap(entries)
|
||||
return .single(preparedContactListNodeTransition(account: account, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, animated: false))
|
||||
return .single(preparedContactListNodeTransition(account: account, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: false, animated: false))
|
||||
}
|
||||
|
||||
if OSAtomicCompareAndSwap32(1, 0, &firstTime) {
|
||||
@ -725,10 +806,9 @@ final class ContactListNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
transition = (combineLatest(self.contactPeersViewPromise.get(), selectionStateSignal, themeAndStringsPromise.get())
|
||||
|> mapToQueue { view, selectionState, themeAndStrings -> Signal<ContactsListNodeTransition, NoError> in
|
||||
transition = (combineLatest(self.contactPeersViewPromise.get(), selectionStateSignal, themeAndStringsPromise.get(), authorizationsPromise.get())
|
||||
|> mapToQueue { view, selectionState, themeAndStrings, authorizationStatus -> Signal<ContactsListNodeTransition, NoError> in
|
||||
let signal = deferred { () -> Signal<ContactsListNodeTransition, NoError> in
|
||||
|
||||
var peers = view.peers.map({ ContactListPeer.peer(peer: $0, isGlobal: false) })
|
||||
var existingPeerIds = Set<PeerId>()
|
||||
var disabledPeerIds = Set<PeerId>()
|
||||
@ -752,7 +832,11 @@ final class ContactListNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
let entries = contactListNodeEntries(accountPeer: view.accountPeer, peers: peers, presences: view.peerPresences, presentation: presentation, selectionState: selectionState, theme: themeAndStrings.0, strings: themeAndStrings.1, dateTimeFormat: themeAndStrings.2, sortOrder: themeAndStrings.3, displayOrder: themeAndStrings.4, disabledPeerIds: disabledPeerIds)
|
||||
var isEmpty = false
|
||||
if (authorizationStatus == .notDetermined || authorizationStatus == .denied) && peers.isEmpty {
|
||||
isEmpty = true
|
||||
}
|
||||
let entries = contactListNodeEntries(accountPeer: view.accountPeer, peers: peers, presences: view.peerPresences, presentation: presentation, selectionState: selectionState, theme: themeAndStrings.0, strings: themeAndStrings.1, dateTimeFormat: themeAndStrings.2, sortOrder: themeAndStrings.3, displayOrder: themeAndStrings.4, disabledPeerIds: disabledPeerIds, authorizationStatus: authorizationStatus)
|
||||
let previous = previousEntries.swap(entries)
|
||||
let animated: Bool
|
||||
if let previous = previous, !themeAndStrings.5 {
|
||||
@ -760,7 +844,7 @@ final class ContactListNode: ASDisplayNode {
|
||||
} else {
|
||||
animated = false
|
||||
}
|
||||
return .single(preparedContactListNodeTransition(account: account, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, animated: animated))
|
||||
return .single(preparedContactListNodeTransition(account: account, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: isEmpty, animated: animated))
|
||||
}
|
||||
|
||||
if OSAtomicCompareAndSwap32(1, 0, &firstTime) {
|
||||
@ -789,6 +873,16 @@ final class ContactListNode: ASDisplayNode {
|
||||
strongSelf.listNode.verticalScrollIndicatorColor = presentationData.theme.list.scrollIndicatorColor
|
||||
strongSelf.themeAndStringsPromise.set(.single((presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameSortOrder, presentationData.nameDisplayOrder, presentationData.disableAnimations)))
|
||||
|
||||
let authorizationPreviousHidden = strongSelf.authorizationNode.isHidden
|
||||
strongSelf.authorizationNode.removeFromSupernode()
|
||||
strongSelf.authorizationNode = PermissionContentNode(theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, kind: .contacts, icon: UIImage(bundleImageName: "Settings/Permissions/Contacts"), title: strongSelf.presentationData.strings.Permissions_ContactsTitle, text: strongSelf.presentationData.strings.Permissions_ContactsText, buttonTitle: strongSelf.presentationData.strings.Contacts_PermissionsAllow, buttonAction: {
|
||||
authorizeImpl?()
|
||||
}, openPrivacyPolicy: {
|
||||
openPrivacyPolicyImpl?()
|
||||
})
|
||||
strongSelf.authorizationNode.isHidden = authorizationPreviousHidden
|
||||
strongSelf.addSubnode(strongSelf.authorizationNode)
|
||||
|
||||
strongSelf.listNode.dynamicBounceEnabled = !presentationData.disableAnimations
|
||||
|
||||
strongSelf.listNode.forEachAccessoryItemNode({ accessoryItemNode in
|
||||
@ -808,6 +902,13 @@ final class ContactListNode: ASDisplayNode {
|
||||
}
|
||||
})
|
||||
|
||||
self.authorizationDisposable = (DeviceAccess.authorizationStatus(account: account, subject: .contacts)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
if let strongSelf = self {
|
||||
strongSelf.authorizationPromise.set(.single(status))
|
||||
}
|
||||
})
|
||||
|
||||
self.listNode.didEndScrolling = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -815,12 +916,26 @@ final class ContactListNode: ASDisplayNode {
|
||||
fixSearchableListNodeScrolling(strongSelf.listNode)
|
||||
}
|
||||
|
||||
// self.authorizationNode.allow = { [weak self] in
|
||||
// self?.account.telegramApplicationContext.applicationBindings.openSettings()
|
||||
// }
|
||||
// self.authorizationNode.openPrivacyPolicy = { [weak self] in
|
||||
// self?.openPrivacyPolicy?()
|
||||
// }
|
||||
authorizeImpl = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
let _ = (DeviceAccess.authorizationStatus(account: account, subject: .contacts)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { status in
|
||||
switch status {
|
||||
case .notDetermined:
|
||||
DeviceAccess.authorizeAccess(to: .contacts, presentationData: strongSelf.presentationData, present: { _, _ in }, openSettings: {}, { _ in })
|
||||
case .denied, .restricted:
|
||||
account.telegramApplicationContext.applicationBindings.openSettings()
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
openPrivacyPolicyImpl = { [weak self] in
|
||||
self?.openPrivacyPolicy?()
|
||||
}
|
||||
|
||||
self.enableUpdates = true
|
||||
}
|
||||
@ -871,10 +986,11 @@ final class ContactListNode: ASDisplayNode {
|
||||
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
|
||||
//let sublayout = layout.addedInsets(insets: UIEdgeInsetsMake(0.0, 0.0, 40.0, 0.0))
|
||||
//transition.updateFrame(node: self.authorizationNode, frame: self.bounds)
|
||||
//self.authorizationNode.containerLayoutUpdated(sublayout, navigationBarHeight: 0.0, transition: transition)
|
||||
|
||||
//if let authorizationNode = self.authorizationNode {
|
||||
authorizationNode.updateLayout(size: layout.size, insets: insets, transition: transition)
|
||||
transition.updateFrame(node: authorizationNode, frame: self.bounds)
|
||||
//}
|
||||
|
||||
if !self.hasValidLayout {
|
||||
self.hasValidLayout = true
|
||||
self.dequeueTransitions()
|
||||
@ -911,6 +1027,9 @@ final class ContactListNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
self.listNode.isHidden = transition.isEmpty
|
||||
self.authorizationNode.isHidden = !transition.isEmpty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,8 +65,7 @@ public class ContactsController: ViewController {
|
||||
self.authorizationDisposable = (DeviceAccess.authorizationStatus(account: account, subject: .contacts)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
if let strongSelf = self {
|
||||
strongSelf.tabBarItem.badgeValue = nil
|
||||
//strongSelf.tabBarItem.badgeValue = status != .allowed ? "!" : nil
|
||||
strongSelf.tabBarItem.badgeValue = status != .allowed ? "!" : nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -245,6 +245,8 @@ class ContactsPeerItem: ListViewItem {
|
||||
if let header = item.header {
|
||||
if let previousItem = previousItem as? ContactsPeerItem {
|
||||
firstWithHeader = header.id != previousItem.header?.id
|
||||
} else if let previousItem = previousItem as? ContactListActionItem {
|
||||
firstWithHeader = header.id != previousItem.header?.id
|
||||
} else {
|
||||
firstWithHeader = true
|
||||
}
|
||||
@ -257,6 +259,8 @@ class ContactsPeerItem: ListViewItem {
|
||||
if let header = item.header {
|
||||
if let nextItem = nextItem as? ContactsPeerItem {
|
||||
last = header.id != nextItem.header?.id
|
||||
} else if let nextItem = nextItem as? ContactListActionItem {
|
||||
last = header.id != nextItem.header?.id
|
||||
} else {
|
||||
last = true
|
||||
}
|
||||
|
||||
@ -87,7 +87,8 @@ private let list = PresentationThemeList(
|
||||
destructive: PresentationThemeItemDisclosureAction(fillColor: destructiveColor, foregroundColor: .white),
|
||||
constructive: PresentationThemeItemDisclosureAction(fillColor: constructiveColor, foregroundColor: .white),
|
||||
accent: PresentationThemeItemDisclosureAction(fillColor: accentColor, foregroundColor: .white),
|
||||
warning: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0x3c4e61), foregroundColor: .white)
|
||||
warning: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0x3c4e61), foregroundColor: .white),
|
||||
inactive: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0x415A71), foregroundColor: .white)
|
||||
),
|
||||
itemCheckColors: PresentationThemeCheck(
|
||||
strokeColor: UIColor(rgb: 0xDBF5FF, alpha: 0.5),
|
||||
|
||||
@ -87,7 +87,8 @@ private let list = PresentationThemeList(
|
||||
destructive: PresentationThemeItemDisclosureAction(fillColor: destructiveColor, foregroundColor: .white),
|
||||
constructive: PresentationThemeItemDisclosureAction(fillColor: constructiveColor, foregroundColor: .white),
|
||||
accent: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0x666666), foregroundColor: .white),
|
||||
warning: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0x414141), foregroundColor: .white)
|
||||
warning: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0x414141), foregroundColor: .white),
|
||||
inactive: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0x666666), foregroundColor: .white)
|
||||
),
|
||||
itemCheckColors: PresentationThemeCheck(
|
||||
strokeColor: UIColor(rgb: 0xffffff, alpha: 0.5),
|
||||
|
||||
@ -87,7 +87,8 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> Pr
|
||||
destructive: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0xff3824), foregroundColor: .white),
|
||||
constructive: PresentationThemeItemDisclosureAction(fillColor: constructiveColor, foregroundColor: .white),
|
||||
accent: PresentationThemeItemDisclosureAction(fillColor: accentColor, foregroundColor: .white),
|
||||
warning: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0xff9500), foregroundColor: .white)
|
||||
warning: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0xff9500), foregroundColor: .white),
|
||||
inactive: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0xbcbcc3), foregroundColor: .white)
|
||||
),
|
||||
itemCheckColors: PresentationThemeCheck(
|
||||
strokeColor: UIColor(rgb: 0xC7C7CC),
|
||||
|
||||
@ -45,6 +45,12 @@ final class GenericEmbedImplementation: WebEmbedImplementation {
|
||||
webView.loadHTMLString(html, baseURL: URL(string: "about:blank"))
|
||||
|
||||
userContentController.addUserScript(WKUserScript(source: userScript, injectionTime: .atDocumentEnd, forMainFrameOnly: false))
|
||||
|
||||
if self.url.hasSuffix(".mp4") || self.url.hasSuffix(".mov") {
|
||||
if let onPlaybackStarted = self.onPlaybackStarted {
|
||||
onPlaybackStarted()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func play() {
|
||||
|
||||
@ -403,7 +403,7 @@ final class GridMessageItemNode: GridItemNode {
|
||||
}
|
||||
}
|
||||
case .longTap:
|
||||
controllerInteraction.openMessageContextMenu(message, self, self.bounds)
|
||||
controllerInteraction.openMessageContextMenu(message, false, self, self.bounds)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
@ -66,10 +66,11 @@ final class InstantPageArticleItem: InstantPageItem {
|
||||
|
||||
func layoutArticleItem(theme: InstantPageTheme, webPage: TelegramMediaWebpage, title: NSAttributedString, description: NSAttributedString, cover: TelegramMediaImage?, url: String, webpageId: MediaId, boundingWidth: CGFloat, rtl: Bool) -> InstantPageArticleItem {
|
||||
let inset: CGFloat = 17.0
|
||||
let imageSpacing: CGFloat = 10.0
|
||||
var sideInset = inset
|
||||
let imageSize = CGSize(width: 44.0, height: 44.0)
|
||||
if cover != nil {
|
||||
sideInset += imageSize.width + 10.0
|
||||
sideInset += imageSize.width + imageSpacing
|
||||
}
|
||||
|
||||
var availableLines: Int = 3
|
||||
@ -87,9 +88,16 @@ func layoutArticleItem(theme: InstantPageTheme, webPage: TelegramMediaWebpage, t
|
||||
hasRTL = true
|
||||
}
|
||||
}
|
||||
var descriptionInset = inset
|
||||
if hasRTL && cover != nil {
|
||||
descriptionInset += imageSize.width + imageSpacing
|
||||
for var item in titleItems {
|
||||
item.frame = item.frame.offsetBy(dx: imageSize.width + imageSpacing, dy: 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
if availableLines > 0 {
|
||||
let (descriptionTextItem, descriptionItems, descriptionSize) = layoutTextItemWithString(description, boundingWidth: boundingWidth - inset - sideInset, alignment: hasRTL ? .right : .natural, offset: CGPoint(x: inset, y: 15.0 + titleSize.height + 14.0), maxNumberOfLines: availableLines)
|
||||
let (descriptionTextItem, descriptionItems, descriptionSize) = layoutTextItemWithString(description, boundingWidth: boundingWidth - inset - sideInset, alignment: hasRTL ? .right : .natural, offset: CGPoint(x: descriptionInset, y: 15.0 + titleSize.height + 14.0), maxNumberOfLines: availableLines)
|
||||
contentItems.append(contentsOf: descriptionItems)
|
||||
|
||||
if let textItem = descriptionTextItem {
|
||||
|
||||
@ -49,16 +49,19 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
|
||||
var items: [InstantPageItem] = []
|
||||
var offset = contentSize.height
|
||||
var contentSize = CGSize()
|
||||
var rtl = rtl
|
||||
if case .empty = caption.text {
|
||||
} else {
|
||||
contentSize.height += 14.0
|
||||
offset += 14.0
|
||||
let styleStack = InstantPageTextStyleStack()
|
||||
setupStyleStack(styleStack, theme: theme, category: .caption, link: false)
|
||||
let (_, captionItems, captionContentSize) = layoutTextItemWithString(attributedStringForRichText(caption.text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: offset), media: media, webpage: webpage)
|
||||
let (textItem, captionItems, captionContentSize) = layoutTextItemWithString(attributedStringForRichText(caption.text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: offset), media: media, webpage: webpage)
|
||||
contentSize.height += captionContentSize.height
|
||||
offset += captionContentSize.height
|
||||
items.append(contentsOf: captionItems)
|
||||
|
||||
rtl = textItem?.containsRTL ?? rtl
|
||||
}
|
||||
|
||||
if case .empty = caption.credit {
|
||||
@ -72,7 +75,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
|
||||
}
|
||||
let styleStack = InstantPageTextStyleStack()
|
||||
setupStyleStack(styleStack, theme: theme, category: .credit, link: false)
|
||||
let (_, captionItems, captionContentSize) = layoutTextItemWithString(attributedStringForRichText(caption.credit, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: offset), media: media, webpage: webpage)
|
||||
let (_, captionItems, captionContentSize) = layoutTextItemWithString(attributedStringForRichText(caption.credit, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, alignment: rtl ? .right : .natural, offset: CGPoint(x: horizontalInset, y: offset), media: media, webpage: webpage)
|
||||
contentSize.height += captionContentSize.height
|
||||
offset += captionContentSize.height
|
||||
items.append(contentsOf: captionItems)
|
||||
@ -786,7 +789,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
|
||||
|
||||
let inset: CGFloat = i == articles.count - 1 ? 0.0 : 17.0
|
||||
let lineSize = CGSize(width: boundingWidth - inset, height: UIScreenPixel)
|
||||
let shapeItem = InstantPageShapeItem(frame: CGRect(origin: CGPoint(x: rtl ? 0.0 : inset, y: contentSize.height - lineSize.height), size: lineSize), shapeFrame: CGRect(origin: CGPoint(), size: lineSize), shape: .rect, color: theme.controlColor)
|
||||
let shapeItem = InstantPageShapeItem(frame: CGRect(origin: CGPoint(x: rtl || item.rtl ? 0.0 : inset, y: contentSize.height - lineSize.height), size: lineSize), shapeFrame: CGRect(origin: CGPoint(), size: lineSize), shape: .rect, color: theme.controlColor)
|
||||
items.append(shapeItem)
|
||||
}
|
||||
return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items)
|
||||
|
||||
@ -86,7 +86,7 @@ private enum InviteContactsEntry: Comparable, Identifiable {
|
||||
interaction.activateSearch()
|
||||
})
|
||||
case let .option(_, option, theme, _):
|
||||
return ContactListActionItem(theme: theme, title: option.title, icon: option.icon, action: option.action)
|
||||
return ContactListActionItem(theme: theme, title: option.title, icon: option.icon, header: nil, action: option.action)
|
||||
case let .peer(_, id, contact, count, selection, theme, strings, nameSortOrder, nameDisplayOrder):
|
||||
let status: ContactsPeerItemStatus
|
||||
if count != 0 {
|
||||
|
||||
@ -849,7 +849,7 @@ final class ListMessageFileItemNode: ListMessageNode {
|
||||
|
||||
override func longTapped() {
|
||||
if let item = self.item {
|
||||
item.controllerInteraction.openMessageContextMenu(item.message, self, self.bounds)
|
||||
item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.bounds)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -603,7 +603,7 @@ final class ListMessageSnippetItemNode: ListMessageNode {
|
||||
|
||||
override func longTapped() {
|
||||
if let item = self.item {
|
||||
item.controllerInteraction.openMessageContextMenu(item.message, self, self.bounds)
|
||||
item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.bounds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,12 +35,11 @@ final class NetworkStatusTitleView: UIView, NavigationBarTitleView, NavigationBa
|
||||
self.proxyNode.isHidden = !self.title.hasProxy
|
||||
self.proxyButton.isHidden = !self.title.hasProxy
|
||||
|
||||
self.buttonView.isHidden = !self.title.isPasscodeSet
|
||||
if self.title.isPasscodeSet && !self.title.activity {
|
||||
self.buttonView.isHidden = false
|
||||
self.lockView.isHidden = false
|
||||
self.lockView.setIsLocked(self.title.isManuallyLocked, theme: self.theme, animated: !self.bounds.size.width.isZero)
|
||||
} else {
|
||||
self.buttonView.isHidden = true
|
||||
self.lockView.isHidden = true
|
||||
self.lockView.setIsLocked(false, theme: self.theme, animated: false)
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ private final class NotificationsAndSoundsArguments {
|
||||
let pushController: (ViewController) -> Void
|
||||
let soundSelectionDisposable: MetaDisposable
|
||||
|
||||
let enableNotifications: () -> Void
|
||||
let authorizeNotifications: () -> Void
|
||||
|
||||
let updateMessageAlerts: (Bool) -> Void
|
||||
let updateMessagePreviews: (Bool) -> Void
|
||||
@ -39,12 +39,12 @@ private final class NotificationsAndSoundsArguments {
|
||||
|
||||
let openAppSettings: () -> Void
|
||||
|
||||
init(account: Account, presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, pushController: @escaping(ViewController)->Void, soundSelectionDisposable: MetaDisposable, enableNotifications: @escaping () -> Void, updateMessageAlerts: @escaping (Bool) -> Void, updateMessagePreviews: @escaping (Bool) -> Void, updateMessageSound: @escaping (PeerMessageSound) -> Void, updateGroupAlerts: @escaping (Bool) -> Void, updateGroupPreviews: @escaping (Bool) -> Void, updateGroupSound: @escaping (PeerMessageSound) -> Void, updateChannelAlerts: @escaping (Bool) -> Void, updateChannelPreviews: @escaping (Bool) -> Void, updateChannelSound: @escaping (PeerMessageSound) -> Void, updateInAppSounds: @escaping (Bool) -> Void, updateInAppVibration: @escaping (Bool) -> Void, updateInAppPreviews: @escaping (Bool) -> Void, updateDisplayNameOnLockscreen: @escaping (Bool) -> Void, updateTotalUnreadCountStyle: @escaping (Bool) -> Void, updateIncludeTag: @escaping (PeerSummaryCounterTags, Bool) -> Void, updateTotalUnreadCountCategory: @escaping (Bool) -> Void, resetNotifications: @escaping () -> Void, updatedExceptionMode: @escaping(NotificationExceptionMode) -> Void, openAppSettings: @escaping () -> Void) {
|
||||
init(account: Account, presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, pushController: @escaping(ViewController)->Void, soundSelectionDisposable: MetaDisposable, authorizeNotifications: @escaping () -> Void, updateMessageAlerts: @escaping (Bool) -> Void, updateMessagePreviews: @escaping (Bool) -> Void, updateMessageSound: @escaping (PeerMessageSound) -> Void, updateGroupAlerts: @escaping (Bool) -> Void, updateGroupPreviews: @escaping (Bool) -> Void, updateGroupSound: @escaping (PeerMessageSound) -> Void, updateChannelAlerts: @escaping (Bool) -> Void, updateChannelPreviews: @escaping (Bool) -> Void, updateChannelSound: @escaping (PeerMessageSound) -> Void, updateInAppSounds: @escaping (Bool) -> Void, updateInAppVibration: @escaping (Bool) -> Void, updateInAppPreviews: @escaping (Bool) -> Void, updateDisplayNameOnLockscreen: @escaping (Bool) -> Void, updateTotalUnreadCountStyle: @escaping (Bool) -> Void, updateIncludeTag: @escaping (PeerSummaryCounterTags, Bool) -> Void, updateTotalUnreadCountCategory: @escaping (Bool) -> Void, resetNotifications: @escaping () -> Void, updatedExceptionMode: @escaping(NotificationExceptionMode) -> Void, openAppSettings: @escaping () -> Void) {
|
||||
self.account = account
|
||||
self.presentController = presentController
|
||||
self.pushController = pushController
|
||||
self.soundSelectionDisposable = soundSelectionDisposable
|
||||
self.enableNotifications = enableNotifications
|
||||
self.authorizeNotifications = authorizeNotifications
|
||||
self.updateMessageAlerts = updateMessageAlerts
|
||||
self.updateMessagePreviews = updateMessagePreviews
|
||||
self.updateMessageSound = updateMessageSound
|
||||
@ -432,10 +432,10 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
|
||||
func item(_ arguments: NotificationsAndSoundsArguments) -> ListViewItem {
|
||||
switch self {
|
||||
case let .permissionInfo(theme, strings):
|
||||
return NotificationPermissionInfoItem(theme: theme, strings: strings, sectionId: self.section)
|
||||
return PermissionInfoItemListItem(theme: theme, strings: strings, subject: .notifications, sectionId: self.section)
|
||||
case let .permissionEnable(theme, text):
|
||||
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.enableNotifications()
|
||||
arguments.authorizeNotifications()
|
||||
})
|
||||
case let .messageHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
@ -572,16 +572,16 @@ private func filteredGlobalSound(_ sound: PeerMessageSound) -> PeerMessageSound
|
||||
private func notificationsAndSoundsEntries(authorizationStatus: AccessType, globalSettings: GlobalNotificationSettingsSet, inAppSettings: InAppNotificationSettings, exceptions: (users: NotificationExceptionMode, groups: NotificationExceptionMode, channels: NotificationExceptionMode), presentationData: PresentationData) -> [NotificationsAndSoundsEntry] {
|
||||
var entries: [NotificationsAndSoundsEntry] = []
|
||||
|
||||
// switch authorizationStatus {
|
||||
// case .denied:
|
||||
// entries.append(.permissionInfo(presentationData.theme, presentationData.strings))
|
||||
// entries.append(.permissionEnable(presentationData.theme, "Turn ON in Settings"))
|
||||
// case .notDetermined:
|
||||
// entries.append(.permissionInfo(presentationData.theme, presentationData.strings))
|
||||
// entries.append(.permissionEnable(presentationData.theme, "Turn Notifications ON"))
|
||||
// default:
|
||||
// break
|
||||
// }
|
||||
switch authorizationStatus {
|
||||
case .denied:
|
||||
entries.append(.permissionInfo(presentationData.theme, presentationData.strings))
|
||||
entries.append(.permissionEnable(presentationData.theme, presentationData.strings.Permissions_NotificationsAllowInSettings))
|
||||
case .notDetermined:
|
||||
entries.append(.permissionInfo(presentationData.theme, presentationData.strings))
|
||||
entries.append(.permissionEnable(presentationData.theme, presentationData.strings.Permissions_NotificationsAllow))
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
entries.append(.messageHeader(presentationData.theme, presentationData.strings.Notifications_MessageNotifications))
|
||||
entries.append(.messageAlerts(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsAlert, globalSettings.privateChats.enabled))
|
||||
@ -652,7 +652,7 @@ public func notificationsAndSoundsController(account: Account, exceptionsList: N
|
||||
presentControllerImpl?(controller, arguments)
|
||||
}, pushController: { controller in
|
||||
pushControllerImpl?(controller)
|
||||
}, soundSelectionDisposable: MetaDisposable(), enableNotifications: {
|
||||
}, soundSelectionDisposable: MetaDisposable(), authorizeNotifications: {
|
||||
let _ = (DeviceAccess.authorizationStatus(account: account, subject: .notifications)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { status in
|
||||
|
||||
@ -133,6 +133,11 @@ final class OngoingCallContext {
|
||||
}
|
||||
}
|
||||
|
||||
private let receptionPromise = Promise<Int32?>(nil)
|
||||
var reception: Signal<Int32?, NoError> {
|
||||
return self.receptionPromise.get()
|
||||
}
|
||||
|
||||
private let audioSessionDisposable = MetaDisposable()
|
||||
private var networkTypeDisposable: Disposable?
|
||||
|
||||
@ -163,6 +168,9 @@ final class OngoingCallContext {
|
||||
context.stateChanged = { [weak self] state in
|
||||
self?.contextState.set(.single(state))
|
||||
}
|
||||
context.signalBarsChanged = { [weak self] signalBars in
|
||||
self?.receptionPromise.set(.single(signalBars))
|
||||
}
|
||||
context.callEnded = { debugLog, bytesSentWifi, bytesReceivedWifi, bytesSentMobile, bytesReceivedMobile in
|
||||
let delta = NetworkUsageStatsConnectionsEntry(
|
||||
cellular: NetworkUsageStatsDirectionsEntry(
|
||||
|
||||
@ -60,6 +60,7 @@ typedef NS_ENUM(int32_t, OngoingCallDataSaving) {
|
||||
+ (int32_t)maxLayer;
|
||||
|
||||
@property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallState);
|
||||
@property (nonatomic, copy) void (^ _Nullable signalBarsChanged)(int32_t);
|
||||
@property (nonatomic, copy) void (^ _Nullable callEnded)(NSString * _Nullable debugLog, int64_t bytesSentWifi, int64_t bytesReceivedWifi, int64_t bytesSentMobile, int64_t bytesReceivedMobile);
|
||||
|
||||
- (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueue> _Nonnull)queue proxy:(VoipProxyServer * _Nullable)proxy networkType:(OngoingCallNetworkType)networkType dataSaving:(OngoingCallDataSaving)dataSaving;
|
||||
|
||||
@ -132,9 +132,11 @@ static void withContext(int32_t contextId, void (^f)(OngoingCallThreadLocalConte
|
||||
tgvoip::VoIPController *_controller;
|
||||
|
||||
OngoingCallState _state;
|
||||
int32_t _signalBars;
|
||||
}
|
||||
|
||||
- (void)controllerStateChanged:(int)state;
|
||||
- (void)signalBarsChanged:(int32_t)signalBars;
|
||||
|
||||
@end
|
||||
|
||||
@ -145,6 +147,13 @@ static void controllerStateCallback(tgvoip::VoIPController *controller, int stat
|
||||
});
|
||||
}
|
||||
|
||||
static void signalBarsCallback(tgvoip::VoIPController *controller, int signalBars) {
|
||||
int32_t contextId = (int32_t)((intptr_t)controller->implData);
|
||||
withContext(contextId, ^(OngoingCallThreadLocalContext *context) {
|
||||
[context signalBarsChanged:(int32_t)signalBars];
|
||||
});
|
||||
}
|
||||
|
||||
@implementation VoipProxyServer
|
||||
|
||||
- (instancetype _Nonnull)initWithHost:(NSString * _Nonnull)host port:(int32_t)port username:(NSString * _Nullable)username password:(NSString * _Nullable)password {
|
||||
@ -194,33 +203,9 @@ static int callControllerDataSavingForType(OngoingCallDataSaving type) {
|
||||
TGVoipLoggingFunction = loggingFunction;
|
||||
}
|
||||
|
||||
+ (void)applyServerConfig:(NSString *)data {
|
||||
if (data.length == 0) {
|
||||
return;
|
||||
}
|
||||
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:[data dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
|
||||
if (dict != nil) {
|
||||
std::vector<std::string> result;
|
||||
char **values = (char **)malloc(sizeof(char *) * (int)dict.count * 2);
|
||||
memset(values, 0, (int)dict.count * 2);
|
||||
__block int index = 0;
|
||||
[dict enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, __unused BOOL *stop) {
|
||||
NSString *valueText = [NSString stringWithFormat:@"%@", value];
|
||||
const char *keyText = [key UTF8String];
|
||||
const char *valueTextValue = [valueText UTF8String];
|
||||
values[index] = (char *)malloc(strlen(keyText) + 1);
|
||||
values[index][strlen(keyText)] = 0;
|
||||
memcpy(values[index], keyText, strlen(keyText));
|
||||
values[index + 1] = (char *)malloc(strlen(valueTextValue) + 1);
|
||||
values[index + 1][strlen(valueTextValue)] = 0;
|
||||
memcpy(values[index + 1], valueTextValue, strlen(valueTextValue));
|
||||
index += 2;
|
||||
}];
|
||||
tgvoip::ServerConfig::GetSharedInstance()->Update((const char **)values, index);
|
||||
for (int i = 0; i < (int)dict.count * 2; i++) {
|
||||
free(values[i]);
|
||||
}
|
||||
free(values);
|
||||
+ (void)applyServerConfig:(NSString *)string {
|
||||
if (string.length != 0) {
|
||||
tgvoip::ServerConfig::GetSharedInstance()->Update(std::string(string.UTF8String));
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,7 +239,7 @@ static int callControllerDataSavingForType(OngoingCallDataSaving type) {
|
||||
callbacks.connectionStateChanged = &controllerStateCallback;
|
||||
callbacks.groupCallKeyReceived = NULL;
|
||||
callbacks.groupCallKeySent = NULL;
|
||||
callbacks.signalBarCountChanged = NULL;
|
||||
callbacks.signalBarCountChanged = &signalBarsCallback;
|
||||
callbacks.upgradeToGroupCallRequested = NULL;
|
||||
_controller->SetCallbacks(callbacks);
|
||||
|
||||
@ -266,6 +251,7 @@ static int callControllerDataSavingForType(OngoingCallDataSaving type) {
|
||||
tgvoip::VoIPController::crypto.aes_ctr_encrypt = &TGCallAesCtrEncrypt;
|
||||
|
||||
_state = OngoingCallStateInitializing;
|
||||
_signalBars = -1;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@ -316,11 +302,10 @@ static int callControllerDataSavingForType(OngoingCallDataSaving type) {
|
||||
|
||||
- (void)stop {
|
||||
if (_controller != nil) {
|
||||
char *buffer = (char *)malloc(_controller->GetDebugLogLength());
|
||||
|
||||
_controller->Stop();
|
||||
_controller->GetDebugLog(buffer);
|
||||
NSString *debugLog = [[NSString alloc] initWithUTF8String:buffer];
|
||||
|
||||
auto debugString = _controller->GetDebugLog();
|
||||
NSString *debugLog = [NSString stringWithUTF8String:debugString.c_str()];
|
||||
|
||||
tgvoip::VoIPController::TrafficStats stats;
|
||||
_controller->GetStats(&stats);
|
||||
@ -372,6 +357,16 @@ static int callControllerDataSavingForType(OngoingCallDataSaving type) {
|
||||
}
|
||||
}
|
||||
|
||||
- (void)signalBarsChanged:(int32_t)signalBars {
|
||||
if (signalBars != _signalBars) {
|
||||
_signalBars = signalBars;
|
||||
|
||||
if (_signalBarsChanged) {
|
||||
_signalBarsChanged(signalBars);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setIsMuted:(bool)isMuted {
|
||||
if (_controller != nil) {
|
||||
_controller->SetMicMute(isMuted);
|
||||
|
||||
@ -54,7 +54,7 @@ final class OverlayPlayerControllerNode: ViewControllerTracingNode, UIGestureRec
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
|
||||
}, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
|
||||
}, presentController: { _, _ in }, navigationController: {
|
||||
return nil
|
||||
}, presentGlobalOverlayController: { _, _ in
|
||||
|
||||
@ -474,8 +474,8 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
||||
let scrubberVerticalOrigin: CGFloat = infoVerticalOrigin + 64.0
|
||||
|
||||
transition.updateFrame(node: self.scrubberNode, frame: CGRect(origin: CGPoint(x: leftInset + sideInset, y: scrubberVerticalOrigin - 8.0), size: CGSize(width: width - sideInset * 2.0 - leftInset - rightInset, height: 10.0 + 8.0 * 2.0)))
|
||||
transition.updateFrame(node: self.leftDurationLabel, frame: CGRect(origin: CGPoint(x: leftInset + sideInset, y: scrubberVerticalOrigin + 12.0), size: CGSize(width: 40.0, height: 20.0)))
|
||||
transition.updateFrame(node: self.rightDurationLabel, frame: CGRect(origin: CGPoint(x: width - sideInset - rightInset - 40.0, y: scrubberVerticalOrigin + 12.0), size: CGSize(width: 40.0, height: 20.0)))
|
||||
transition.updateFrame(node: self.leftDurationLabel, frame: CGRect(origin: CGPoint(x: leftInset + sideInset, y: scrubberVerticalOrigin + 12.0), size: CGSize(width: 64.0, height: 20.0)))
|
||||
transition.updateFrame(node: self.rightDurationLabel, frame: CGRect(origin: CGPoint(x: width - sideInset - rightInset - 64.0, y: scrubberVerticalOrigin + 12.0), size: CGSize(width: 64.0, height: 20.0)))
|
||||
|
||||
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -8.0), size: CGSize(width: width, height: panelHeight + 8.0)))
|
||||
|
||||
|
||||
@ -114,7 +114,7 @@ public class PeerMediaCollectionController: TelegramController {
|
||||
}
|
||||
}
|
||||
}, openPeerMention: { _ in
|
||||
}, openMessageContextMenu: { [weak self] message, _, _ in
|
||||
}, openMessageContextMenu: { [weak self] message, _, _, _ in
|
||||
if let strongSelf = self, strongSelf.isNodeLoaded {
|
||||
if let message = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id)?.message {
|
||||
let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme)
|
||||
|
||||
@ -3,19 +3,22 @@ import Display
|
||||
import AsyncDisplayKit
|
||||
|
||||
final class PermissionContentNode: ASDisplayNode {
|
||||
private var theme: PresentationTheme
|
||||
let kind: PermissionStateKind
|
||||
|
||||
private let iconNode: ASImageNode
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let textNode: ImmediateTextNode
|
||||
private let actionButton: SolidRoundedButtonNode
|
||||
private let privacyPolicyButton: HighlightableButtonNode
|
||||
|
||||
var kind: PermissionStateKind
|
||||
private var title: String
|
||||
|
||||
var buttonAction: (() -> Void)?
|
||||
var openPrivacyPolicy: (() -> Void)?
|
||||
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, kind: PermissionStateKind, icon: UIImage?, title: String, text: String, buttonTitle: String, buttonAction: @escaping () -> Void, openPrivacyPolicy: (() -> Void)?) {
|
||||
self.theme = theme
|
||||
self.kind = kind
|
||||
|
||||
self.buttonAction = buttonAction
|
||||
@ -42,7 +45,7 @@ final class PermissionContentNode: ASDisplayNode {
|
||||
self.actionButton = SolidRoundedButtonNode(theme: theme, height: 48.0, cornerRadius: 9.0)
|
||||
|
||||
self.privacyPolicyButton = HighlightableButtonNode()
|
||||
//self.privacyPolicyButton.setTitle(strings.Permissions_PrivacyPolicy, with: Font.regular(16.0), with: theme.list.itemAccentColor, for: .normal)
|
||||
self.privacyPolicyButton.setTitle(strings.Permissions_PrivacyPolicy, with: Font.regular(16.0), with: theme.list.itemAccentColor, for: .normal)
|
||||
|
||||
super.init()
|
||||
|
||||
@ -74,23 +77,43 @@ final class PermissionContentNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) {
|
||||
let sidePadding: CGFloat = 20.0
|
||||
//let sideButtonInset: CGFloat = 16.0
|
||||
let sidePadding: CGFloat
|
||||
let fontSize: CGFloat
|
||||
if size.width > 330.0 {
|
||||
fontSize = 24.0
|
||||
sidePadding = 38.0
|
||||
} else {
|
||||
fontSize = 20.0
|
||||
sidePadding = 20.0
|
||||
}
|
||||
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(fontSize), textColor: self.theme.list.itemPrimaryTextColor)
|
||||
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - sidePadding * 2.0, height: .greatestFiniteMagnitude))
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: size.width - sidePadding * 2.0, height: .greatestFiniteMagnitude))
|
||||
let buttonHeight = self.actionButton.updateLayout(width: size.width, transition: transition)
|
||||
|
||||
let titleSubtitleSpacing: CGFloat = 12.0
|
||||
|
||||
let textHeight = titleSize.height + titleSubtitleSpacing + textSize.height
|
||||
|
||||
|
||||
let minContentHeight = textHeight
|
||||
let contentHeight = min(215.0, max(size.height - insets.top - insets.bottom - 40.0, minContentHeight))
|
||||
let titleSubtitleSpacing: CGFloat = 26.0
|
||||
let buttonSpacing: CGFloat = 36.0
|
||||
var contentHeight = titleSize.height + titleSubtitleSpacing + textSize.height + buttonHeight + buttonSpacing
|
||||
|
||||
var imageSize = CGSize()
|
||||
var imageSpacing: CGFloat = 0.0
|
||||
if let icon = self.iconNode.image {
|
||||
imageSpacing = 60.0
|
||||
imageSize = icon.size
|
||||
contentHeight += imageSize.height + imageSpacing
|
||||
}
|
||||
|
||||
let contentOrigin = insets.top + floor((size.height - insets.top - insets.bottom - contentHeight) / 2.0)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentOrigin), size: titleSize)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: contentOrigin), size: imageSize)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: iconFrame.maxY + imageSpacing), size: titleSize)
|
||||
let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: titleFrame.maxY + titleSubtitleSpacing), size: textSize)
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: 0.0, y: textFrame.maxY + buttonSpacing), size: CGSize(width: size.width, height: buttonHeight))
|
||||
|
||||
transition.updateFrame(node: self.iconNode, frame: iconFrame)
|
||||
transition.updateFrame(node: self.titleNode, frame: titleFrame)
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: titleFrame.maxY + titleSubtitleSpacing), size: textSize))
|
||||
transition.updateFrame(node: self.actionButton, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: buttonHeight))
|
||||
transition.updateFrame(node: self.textNode, frame: textFrame)
|
||||
transition.updateFrame(node: self.actionButton, frame: buttonFrame)
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,79 +126,79 @@ final class PermissionControllerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
private func transition(state: PermissionControllerState, transition: ContainedViewLayoutTransition) {
|
||||
// let insets = state.layout.layout.insets(options: [.statusBar])
|
||||
// let contentFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: state.layout.layout.size.width, height: state.layout.layout.size.height))
|
||||
//
|
||||
// if state.data.state?.kind != self.contentNode?.kind {
|
||||
// if let dataState = state.data.state {
|
||||
// let icon: UIImage?
|
||||
// let title: String
|
||||
// let text: String
|
||||
// let buttonTitle: String
|
||||
// let hasPrivacyPolicy: Bool
|
||||
//
|
||||
// switch dataState {
|
||||
// case let .contacts(status):
|
||||
// icon = UIImage(bundleImageName: "Settings/Permissions/Contacts")
|
||||
// title = self.presentationData.strings.Permissions_ContactsTitle
|
||||
// text = self.presentationData.strings.Permissions_ContactsText
|
||||
// if status == .denied {
|
||||
// buttonTitle = self.presentationData.strings.Permissions_ContactsAllowInSettings
|
||||
// } else {
|
||||
// buttonTitle = self.presentationData.strings.Permissions_ContactsAllow
|
||||
// }
|
||||
// hasPrivacyPolicy = true
|
||||
// case let .notifications(status):
|
||||
// icon = UIImage(bundleImageName: "Settings/Permissions/Notifications")
|
||||
// title = self.presentationData.strings.Permissions_NotificationsTitle
|
||||
// text = self.presentationData.strings.Permissions_NotificationsText
|
||||
// if status == .denied {
|
||||
// buttonTitle = self.presentationData.strings.Permissions_NotificationsAllowInSettings
|
||||
// } else {
|
||||
// buttonTitle = self.presentationData.strings.Permissions_NotificationsAllow
|
||||
// }
|
||||
// hasPrivacyPolicy = false
|
||||
// case let .siri(status):
|
||||
// icon = UIImage(bundleImageName: "Settings/Permissions/CellularData")
|
||||
// title = self.presentationData.strings.Permissions_SiriTitle
|
||||
// text = self.presentationData.strings.Permissions_SiriText
|
||||
// if status == .denied {
|
||||
// buttonTitle = self.presentationData.strings.Permissions_SiriAllowInSettings
|
||||
// } else {
|
||||
// buttonTitle = self.presentationData.strings.Permissions_SiriAllow
|
||||
// }
|
||||
// hasPrivacyPolicy = false
|
||||
// case .cellularData:
|
||||
// icon = UIImage(bundleImageName: "Settings/Permissions/CellularData")
|
||||
// title = self.presentationData.strings.Permissions_CellularDataTitle
|
||||
// text = self.presentationData.strings.Permissions_CellularDataText
|
||||
// buttonTitle = self.presentationData.strings.Permissions_CellularDataAllowInSettings
|
||||
// hasPrivacyPolicy = false
|
||||
// }
|
||||
//
|
||||
// let contentNode = PermissionContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, kind: dataState.kind, icon: icon, title: title, text: text, buttonTitle: buttonTitle, buttonAction: {}, openPrivacyPolicy: hasPrivacyPolicy ? self.openPrivacyPolicy : nil)
|
||||
// self.insertSubnode(contentNode, at: 0)
|
||||
// contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: .immediate)
|
||||
// contentNode.frame = contentFrame
|
||||
// if let currentContentNode = self.contentNode {
|
||||
// transition.updatePosition(node: currentContentNode, position: CGPoint(x: -contentFrame.size.width / 2.0, y: contentFrame.midY), completion: { [weak currentContentNode] _ in
|
||||
// currentContentNode?.removeFromSupernode()
|
||||
// })
|
||||
// transition.animateHorizontalOffsetAdditive(node: contentNode, offset: -contentFrame.width)
|
||||
// } else if transition.isAnimated {
|
||||
// contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
// }
|
||||
// self.contentNode = contentNode
|
||||
// } else if let currentContentNode = self.contentNode {
|
||||
// transition.updateAlpha(node: currentContentNode, alpha: 0.0, completion: { [weak currentContentNode] _ in
|
||||
// currentContentNode?.removeFromSupernode()
|
||||
// })
|
||||
// self.contentNode = nil
|
||||
// }
|
||||
// } else if let contentNode = self.contentNode {
|
||||
// transition.updateFrame(node: contentNode, frame: contentFrame)
|
||||
// contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: transition)
|
||||
// }
|
||||
let insets = state.layout.layout.insets(options: [.statusBar])
|
||||
let contentFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: state.layout.layout.size.width, height: state.layout.layout.size.height))
|
||||
|
||||
if state.data.state?.kind != self.contentNode?.kind {
|
||||
if let dataState = state.data.state {
|
||||
let icon: UIImage?
|
||||
let title: String
|
||||
let text: String
|
||||
let buttonTitle: String
|
||||
let hasPrivacyPolicy: Bool
|
||||
|
||||
switch dataState {
|
||||
case let .contacts(status):
|
||||
icon = UIImage(bundleImageName: "Settings/Permissions/Contacts")
|
||||
title = self.presentationData.strings.Permissions_ContactsTitle
|
||||
text = self.presentationData.strings.Permissions_ContactsText
|
||||
if status == .denied {
|
||||
buttonTitle = self.presentationData.strings.Permissions_ContactsAllowInSettings
|
||||
} else {
|
||||
buttonTitle = self.presentationData.strings.Permissions_ContactsAllow
|
||||
}
|
||||
hasPrivacyPolicy = true
|
||||
case let .notifications(status):
|
||||
icon = UIImage(bundleImageName: "Settings/Permissions/Notifications")
|
||||
title = self.presentationData.strings.Permissions_NotificationsTitle
|
||||
text = self.presentationData.strings.Permissions_NotificationsText
|
||||
if status == .denied {
|
||||
buttonTitle = self.presentationData.strings.Permissions_NotificationsAllowInSettings
|
||||
} else {
|
||||
buttonTitle = self.presentationData.strings.Permissions_NotificationsAllow
|
||||
}
|
||||
hasPrivacyPolicy = false
|
||||
case let .siri(status):
|
||||
icon = UIImage(bundleImageName: "Settings/Permissions/Siri")
|
||||
title = self.presentationData.strings.Permissions_SiriTitle
|
||||
text = self.presentationData.strings.Permissions_SiriText
|
||||
if status == .denied {
|
||||
buttonTitle = self.presentationData.strings.Permissions_SiriAllowInSettings
|
||||
} else {
|
||||
buttonTitle = self.presentationData.strings.Permissions_SiriAllow
|
||||
}
|
||||
hasPrivacyPolicy = false
|
||||
case .cellularData:
|
||||
icon = UIImage(bundleImageName: "Settings/Permissions/CellularData")
|
||||
title = self.presentationData.strings.Permissions_CellularDataTitle
|
||||
text = self.presentationData.strings.Permissions_CellularDataText
|
||||
buttonTitle = self.presentationData.strings.Permissions_CellularDataAllowInSettings
|
||||
hasPrivacyPolicy = false
|
||||
}
|
||||
|
||||
let contentNode = PermissionContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, kind: dataState.kind, icon: icon, title: title, text: text, buttonTitle: buttonTitle, buttonAction: {}, openPrivacyPolicy: hasPrivacyPolicy ? self.openPrivacyPolicy : nil)
|
||||
self.insertSubnode(contentNode, at: 0)
|
||||
contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: .immediate)
|
||||
contentNode.frame = contentFrame
|
||||
if let currentContentNode = self.contentNode {
|
||||
transition.updatePosition(node: currentContentNode, position: CGPoint(x: -contentFrame.size.width / 2.0, y: contentFrame.midY), completion: { [weak currentContentNode] _ in
|
||||
currentContentNode?.removeFromSupernode()
|
||||
})
|
||||
transition.animateHorizontalOffsetAdditive(node: contentNode, offset: -contentFrame.width)
|
||||
} else if transition.isAnimated {
|
||||
contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
}
|
||||
self.contentNode = contentNode
|
||||
} else if let currentContentNode = self.contentNode {
|
||||
transition.updateAlpha(node: currentContentNode, alpha: 0.0, completion: { [weak currentContentNode] _ in
|
||||
currentContentNode?.removeFromSupernode()
|
||||
})
|
||||
self.contentNode = nil
|
||||
}
|
||||
} else if let contentNode = self.contentNode {
|
||||
transition.updateFrame(node: contentNode, frame: contentFrame)
|
||||
contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
|
||||
@ -3,23 +3,23 @@ import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
|
||||
class NotificationPermissionInfoItem: ListViewItem, ItemListItem {
|
||||
class PermissionInfoItem: ListViewItem {
|
||||
let selectable: Bool = false
|
||||
let sectionId: ItemListSectionId
|
||||
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let subject: DeviceAccessSubject
|
||||
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId) {
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, subject: DeviceAccessSubject) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.sectionId = sectionId
|
||||
self.subject = subject
|
||||
}
|
||||
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, () -> Void)) -> Void) {
|
||||
async {
|
||||
let node = NotificationPermissionInfoItemNode()
|
||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
let node = PermissionInfoItemNode()
|
||||
let (layout, apply) = node.asyncLayout()(self, params, nil)
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
@ -34,7 +34,49 @@ class NotificationPermissionInfoItem: ListViewItem, ItemListItem {
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? NotificationPermissionInfoItemNode {
|
||||
if let nodeValue = node() as? PermissionInfoItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, nil)
|
||||
Queue.mainQueue().async {
|
||||
completion(layout, {
|
||||
apply()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PermissionInfoItemListItem: PermissionInfoItem, ItemListItem {
|
||||
let sectionId: ItemListSectionId
|
||||
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, subject: DeviceAccessSubject, sectionId: ItemListSectionId) {
|
||||
self.sectionId = sectionId
|
||||
super.init(theme: theme, strings: strings, subject: subject)
|
||||
}
|
||||
|
||||
override func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, () -> Void)) -> Void) {
|
||||
async {
|
||||
let node = PermissionInfoItemNode()
|
||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? PermissionInfoItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
@ -54,7 +96,7 @@ private let titleFont = Font.semibold(17.0)
|
||||
private let textFont = Font.regular(16.0)
|
||||
private let badgeFont = Font.regular(15.0)
|
||||
|
||||
class NotificationPermissionInfoItemNode: ListViewItemNode {
|
||||
class PermissionInfoItemNode: ListViewItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
@ -64,7 +106,7 @@ class NotificationPermissionInfoItemNode: ListViewItemNode {
|
||||
let titleNode: TextNode
|
||||
let textNode: TextNode
|
||||
|
||||
private var item: NotificationPermissionInfoItem?
|
||||
private var item: PermissionInfoItem?
|
||||
|
||||
override var canBeSelected: Bool {
|
||||
return false
|
||||
@ -103,7 +145,7 @@ class NotificationPermissionInfoItemNode: ListViewItemNode {
|
||||
self.addSubnode(self.textNode)
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: NotificationPermissionInfoItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
func asyncLayout() -> (_ item: PermissionInfoItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors?) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||
@ -123,14 +165,33 @@ class NotificationPermissionInfoItemNode: ListViewItemNode {
|
||||
updatedBadgeImage = generateStretchableFilledCircleImage(diameter: badgeDiameter, color: item.theme.list.itemDestructiveColor)
|
||||
}
|
||||
|
||||
let insets = itemListNeighborsGroupedInsets(neighbors)
|
||||
let insets: UIEdgeInsets
|
||||
if let neighbors = neighbors {
|
||||
insets = itemListNeighborsGroupedInsets(neighbors)
|
||||
} else {
|
||||
insets = UIEdgeInsets()
|
||||
}
|
||||
let separatorHeight = UIScreenPixel
|
||||
let itemBackgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||
let itemSeparatorColor = item.theme.list.itemBlocksSeparatorColor
|
||||
|
||||
let title: String
|
||||
let text: String
|
||||
switch item.subject {
|
||||
case .contacts:
|
||||
title = item.strings.Contacts_PermissionsTitle
|
||||
text = item.strings.Contacts_PermissionsText
|
||||
case .notifications:
|
||||
title = item.strings.Notifications_PermissionsTitle
|
||||
text = item.strings.Notifications_PermissionsText
|
||||
default:
|
||||
title = ""
|
||||
text = ""
|
||||
}
|
||||
|
||||
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "!", font: badgeFont, textColor: item.theme.list.itemCheckColors.foregroundColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: badgeDiameter, height: badgeDiameter), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Turn ON Notifications", font: titleFont, textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - badgeDiameter - 8.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Don't miss important messages from your friends and coworkers.", font: textFont, textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 3, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: titleFont, textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - badgeDiameter - 8.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: text, font: textFont, textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 3, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let contentSize = CGSize(width: params.width, height: titleLayout.size.height + textLayout.size.height + 36.0)
|
||||
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in
|
||||
@ -156,18 +217,24 @@ class NotificationPermissionInfoItemNode: ListViewItemNode {
|
||||
if strongSelf.bottomStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
||||
}
|
||||
switch neighbors.top {
|
||||
case .sameSection(false):
|
||||
strongSelf.topStripeNode.isHidden = true
|
||||
default:
|
||||
strongSelf.topStripeNode.isHidden = false
|
||||
if let neighbors = neighbors {
|
||||
switch neighbors.top {
|
||||
case .sameSection(false):
|
||||
strongSelf.topStripeNode.isHidden = true
|
||||
default:
|
||||
strongSelf.topStripeNode.isHidden = false
|
||||
}
|
||||
}
|
||||
let bottomStripeInset: CGFloat
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
if let neighbors = neighbors {
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
}
|
||||
} else {
|
||||
bottomStripeInset = leftInset
|
||||
}
|
||||
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
@ -10,7 +10,7 @@ public enum PresentationCallState: Equatable {
|
||||
case ringing
|
||||
case requesting(Bool)
|
||||
case connecting(Data?)
|
||||
case active(Double, Data)
|
||||
case active(Double, Int32?, Data)
|
||||
case terminating
|
||||
case terminated(CallSessionTerminationReason?)
|
||||
}
|
||||
@ -173,6 +173,8 @@ public final class PresentationCall {
|
||||
private var callContextState: OngoingCallContextState?
|
||||
private var ongoingContext: OngoingCallContext
|
||||
private var ongoingContextStateDisposable: Disposable?
|
||||
private var reception: Int32?
|
||||
private var receptionDisposable: Disposable?
|
||||
private var reportedIncomingCall = false
|
||||
|
||||
private var sessionStateDisposable: Disposable?
|
||||
@ -236,7 +238,7 @@ public final class PresentationCall {
|
||||
self.sessionStateDisposable = (callSessionManager.callState(internalId: internalId)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] sessionState in
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateSessionState(sessionState: sessionState, callContextState: strongSelf.callContextState, audioSessionControl: strongSelf.audioSessionControl)
|
||||
strongSelf.updateSessionState(sessionState: sessionState, callContextState: strongSelf.callContextState, reception: strongSelf.reception, audioSessionControl: strongSelf.audioSessionControl)
|
||||
}
|
||||
})
|
||||
|
||||
@ -244,18 +246,29 @@ public final class PresentationCall {
|
||||
|> deliverOnMainQueue).start(next: { [weak self] contextState in
|
||||
if let strongSelf = self {
|
||||
if let sessionState = strongSelf.sessionState {
|
||||
strongSelf.updateSessionState(sessionState: sessionState, callContextState: contextState, audioSessionControl: strongSelf.audioSessionControl)
|
||||
strongSelf.updateSessionState(sessionState: sessionState, callContextState: contextState, reception: strongSelf.reception, audioSessionControl: strongSelf.audioSessionControl)
|
||||
} else {
|
||||
strongSelf.callContextState = contextState
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
self.receptionDisposable = (self.ongoingContext.reception
|
||||
|> deliverOnMainQueue).start(next: { [weak self] reception in
|
||||
if let strongSelf = self {
|
||||
if let sessionState = strongSelf.sessionState {
|
||||
strongSelf.updateSessionState(sessionState: sessionState, callContextState: strongSelf.callContextState, reception: reception, audioSessionControl: strongSelf.audioSessionControl)
|
||||
} else {
|
||||
strongSelf.reception = reception
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
self.audioSessionDisposable = audioSession.push(audioSessionType: .voiceCall, manualActivate: { [weak self] control in
|
||||
Queue.mainQueue().async {
|
||||
if let strongSelf = self {
|
||||
if let sessionState = strongSelf.sessionState {
|
||||
strongSelf.updateSessionState(sessionState: sessionState, callContextState: strongSelf.callContextState, audioSessionControl: control)
|
||||
strongSelf.updateSessionState(sessionState: sessionState, callContextState: strongSelf.callContextState, reception: strongSelf.reception, audioSessionControl: control)
|
||||
} else {
|
||||
strongSelf.audioSessionControl = control
|
||||
}
|
||||
@ -267,7 +280,7 @@ public final class PresentationCall {
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateIsAudioSessionActive(false)
|
||||
if let sessionState = strongSelf.sessionState {
|
||||
strongSelf.updateSessionState(sessionState: sessionState, callContextState: strongSelf.callContextState, audioSessionControl: nil)
|
||||
strongSelf.updateSessionState(sessionState: sessionState, callContextState: strongSelf.callContextState, reception: strongSelf.reception, audioSessionControl: nil)
|
||||
} else {
|
||||
strongSelf.audioSessionControl = nil
|
||||
}
|
||||
@ -342,6 +355,7 @@ public final class PresentationCall {
|
||||
self.audioSessionActiveDisposable?.dispose()
|
||||
self.sessionStateDisposable?.dispose()
|
||||
self.ongoingContextStateDisposable?.dispose()
|
||||
self.receptionDisposable?.dispose()
|
||||
self.audioSessionDisposable?.dispose()
|
||||
|
||||
if let dropCallKitCallTimer = self.dropCallKitCallTimer {
|
||||
@ -352,11 +366,12 @@ public final class PresentationCall {
|
||||
}
|
||||
}
|
||||
|
||||
private func updateSessionState(sessionState: CallSession, callContextState: OngoingCallContextState?, audioSessionControl: ManagedAudioSessionControl?) {
|
||||
private func updateSessionState(sessionState: CallSession, callContextState: OngoingCallContextState?, reception: Int32?, audioSessionControl: ManagedAudioSessionControl?) {
|
||||
let previous = self.sessionState
|
||||
let previousControl = self.audioSessionControl
|
||||
self.sessionState = sessionState
|
||||
self.callContextState = callContextState
|
||||
self.reception = reception
|
||||
self.audioSessionControl = audioSessionControl
|
||||
|
||||
if previousControl != nil && audioSessionControl == nil {
|
||||
@ -429,7 +444,7 @@ public final class PresentationCall {
|
||||
timestamp = CFAbsoluteTimeGetCurrent()
|
||||
self.activeTimestamp = timestamp
|
||||
}
|
||||
presentationState = .active(timestamp, keyVisualHash)
|
||||
presentationState = .active(timestamp, reception, keyVisualHash)
|
||||
}
|
||||
} else {
|
||||
presentationState = .connecting(keyVisualHash)
|
||||
|
||||
@ -42,16 +42,18 @@ public final class PresentationData: Equatable {
|
||||
public let strings: PresentationStrings
|
||||
public let theme: PresentationTheme
|
||||
public let chatWallpaper: TelegramWallpaper
|
||||
public let volumeControlStatusBarIcons: (UIImage, UIImage, UIImage)
|
||||
public let fontSize: PresentationFontSize
|
||||
public let dateTimeFormat: PresentationDateTimeFormat
|
||||
public let nameDisplayOrder: PresentationPersonNameOrder
|
||||
public let nameSortOrder: PresentationPersonNameOrder
|
||||
public let disableAnimations: Bool
|
||||
|
||||
public init(strings: PresentationStrings, theme: PresentationTheme, chatWallpaper: TelegramWallpaper, fontSize: PresentationFontSize, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, nameSortOrder: PresentationPersonNameOrder, disableAnimations: Bool) {
|
||||
public init(strings: PresentationStrings, theme: PresentationTheme, chatWallpaper: TelegramWallpaper, volumeControlStatusBarIcons: (UIImage, UIImage, UIImage), fontSize: PresentationFontSize, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, nameSortOrder: PresentationPersonNameOrder, disableAnimations: Bool) {
|
||||
self.strings = strings
|
||||
self.theme = theme
|
||||
self.chatWallpaper = chatWallpaper
|
||||
self.volumeControlStatusBarIcons = volumeControlStatusBarIcons
|
||||
self.fontSize = fontSize
|
||||
self.dateTimeFormat = dateTimeFormat
|
||||
self.nameDisplayOrder = nameDisplayOrder
|
||||
@ -60,7 +62,7 @@ public final class PresentationData: Equatable {
|
||||
}
|
||||
|
||||
public static func ==(lhs: PresentationData, rhs: PresentationData) -> Bool {
|
||||
return lhs.strings === rhs.strings && lhs.theme === rhs.theme && lhs.chatWallpaper == rhs.chatWallpaper && lhs.fontSize == rhs.fontSize && lhs.dateTimeFormat == rhs.dateTimeFormat && lhs.disableAnimations == rhs.disableAnimations
|
||||
return lhs.strings === rhs.strings && lhs.theme === rhs.theme && lhs.chatWallpaper == rhs.chatWallpaper && lhs.volumeControlStatusBarIcons == rhs.volumeControlStatusBarIcons && lhs.fontSize == rhs.fontSize && lhs.dateTimeFormat == rhs.dateTimeFormat && lhs.disableAnimations == rhs.disableAnimations
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,6 +94,10 @@ func dictFromLocalization(_ value: Localization) -> [String: String] {
|
||||
return dict
|
||||
}
|
||||
|
||||
private func volumeControlStatusBarIcons() -> (UIImage, UIImage, UIImage) {
|
||||
return (UIImage(bundleImageName: "Components/Volume/VolumeOff")!, UIImage(bundleImageName: "Components/Volume/VolumeHalf")!, UIImage(bundleImageName: "Components/Volume/VolumeFull")!)
|
||||
}
|
||||
|
||||
private func currentDateTimeFormat() -> PresentationDateTimeFormat {
|
||||
let locale = Locale.current
|
||||
let dateFormatter = DateFormatter()
|
||||
@ -281,7 +287,7 @@ public func currentPresentationDataAndSettings(postbox: Postbox) -> Signal<Initi
|
||||
let dateTimeFormat = currentDateTimeFormat()
|
||||
let nameDisplayOrder = currentPersonNameDisplayOrder()
|
||||
let nameSortOrder = currentPersonNameSortOrder()
|
||||
return InitialPresentationDataAndSettings(presentationData: PresentationData(strings: stringsValue, theme: themeValue, chatWallpaper: effectiveChatWallpaper, fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations), automaticMediaDownloadSettings: automaticMediaDownloadSettings, callListSettings: callListSettings, inAppNotificationSettings: inAppNotificationSettings, mediaInputSettings: mediaInputSettings, experimentalUISettings: experimentalUISettings)
|
||||
return InitialPresentationDataAndSettings(presentationData: PresentationData(strings: stringsValue, theme: themeValue, chatWallpaper: effectiveChatWallpaper, volumeControlStatusBarIcons: volumeControlStatusBarIcons(), fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations), automaticMediaDownloadSettings: automaticMediaDownloadSettings, callListSettings: callListSettings, inAppNotificationSettings: inAppNotificationSettings, mediaInputSettings: mediaInputSettings, experimentalUISettings: experimentalUISettings)
|
||||
}
|
||||
}
|
||||
|
||||
@ -417,7 +423,7 @@ public func updatedPresentationData(postbox: Postbox) -> Signal<PresentationData
|
||||
let nameDisplayOrder = currentPersonNameDisplayOrder()
|
||||
let nameSortOrder = currentPersonNameSortOrder()
|
||||
|
||||
return PresentationData(strings: stringsValue, theme: themeValue, chatWallpaper: effectiveChatWallpaper, fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations)
|
||||
return PresentationData(strings: stringsValue, theme: themeValue, chatWallpaper: effectiveChatWallpaper, volumeControlStatusBarIcons: volumeControlStatusBarIcons(), fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -428,5 +434,5 @@ public func defaultPresentationData() -> PresentationData {
|
||||
let nameSortOrder = currentPersonNameSortOrder()
|
||||
|
||||
let themeSettings = PresentationThemeSettings.defaultSettings
|
||||
return PresentationData(strings: defaultPresentationStrings, theme: defaultPresentationTheme, chatWallpaper: .builtin, fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations)
|
||||
return PresentationData(strings: defaultPresentationStrings, theme: defaultPresentationTheme, chatWallpaper: .builtin, volumeControlStatusBarIcons: volumeControlStatusBarIcons(), fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations)
|
||||
}
|
||||
|
||||
@ -247,14 +247,16 @@ public final class PresentationThemeItemDisclosureActions {
|
||||
public let constructive: PresentationThemeItemDisclosureAction
|
||||
public let accent: PresentationThemeItemDisclosureAction
|
||||
public let warning: PresentationThemeItemDisclosureAction
|
||||
public let inactive: PresentationThemeItemDisclosureAction
|
||||
|
||||
public init(neutral1: PresentationThemeItemDisclosureAction, neutral2: PresentationThemeItemDisclosureAction, destructive: PresentationThemeItemDisclosureAction, constructive: PresentationThemeItemDisclosureAction, accent: PresentationThemeItemDisclosureAction, warning: PresentationThemeItemDisclosureAction) {
|
||||
public init(neutral1: PresentationThemeItemDisclosureAction, neutral2: PresentationThemeItemDisclosureAction, destructive: PresentationThemeItemDisclosureAction, constructive: PresentationThemeItemDisclosureAction, accent: PresentationThemeItemDisclosureAction, warning: PresentationThemeItemDisclosureAction, inactive: PresentationThemeItemDisclosureAction) {
|
||||
self.neutral1 = neutral1
|
||||
self.neutral2 = neutral2
|
||||
self.destructive = destructive
|
||||
self.constructive = constructive
|
||||
self.accent = accent
|
||||
self.warning = warning
|
||||
self.inactive = inactive
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -757,8 +757,7 @@ public func settingsController(account: Account, accountManager: AccountManager)
|
||||
let icon = UIImage(bundleImageName: "Chat List/Tabs/IconSettings")
|
||||
|
||||
let controller = ItemListController(account: account, state: signal, tabBarItem: combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, notificationAuthorizationStatus.get()) |> map { presentationData, status in
|
||||
return ItemListControllerTabBarItem(title: presentationData.strings.Settings_Title, image: icon, selectedImage: icon, badgeValue: nil)
|
||||
//return ItemListControllerTabBarItem(title: presentationData.strings.Settings_Title, image: icon, selectedImage: icon, badgeValue: status != .allowed ? "!" : nil)
|
||||
return ItemListControllerTabBarItem(title: presentationData.strings.Settings_Title, image: icon, selectedImage: icon, badgeValue: status != .allowed ? "!" : nil)
|
||||
})
|
||||
pushControllerImpl = { [weak controller] value in
|
||||
(controller?.navigationController as? NavigationController)?.replaceAllButRootController(value, animated: true)
|
||||
|
||||
@ -8,6 +8,7 @@ final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
private var theme: PresentationTheme
|
||||
|
||||
private let buttonBackgroundNode: ASImageNode
|
||||
private let buttonGlossNode: SolidRoundedButtonGlossNode
|
||||
private let buttonNode: HighlightTrackingButtonNode
|
||||
private let labelNode: ImmediateTextNode
|
||||
|
||||
@ -37,6 +38,8 @@ final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
self.buttonBackgroundNode.displaysAsynchronously = false
|
||||
self.buttonBackgroundNode.image = generateStretchableFilledCircleImage(radius: cornerRadius, color: theme.list.itemCheckColors.fillColor)
|
||||
|
||||
self.buttonGlossNode = SolidRoundedButtonGlossNode(color: theme.list.itemCheckColors.foregroundColor, cornerRadius: cornerRadius)
|
||||
|
||||
self.buttonNode = HighlightTrackingButtonNode()
|
||||
|
||||
self.labelNode = ImmediateTextNode()
|
||||
@ -45,6 +48,7 @@ final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.buttonBackgroundNode)
|
||||
self.addSubnode(self.buttonGlossNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
self.addSubnode(self.labelNode)
|
||||
|
||||
@ -69,6 +73,7 @@ final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
let buttonSize = CGSize(width: width - inset * 2.0, height: self.buttonHeight)
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: inset, y: 0.0), size: buttonSize)
|
||||
transition.updateFrame(node: self.buttonBackgroundNode, frame: buttonFrame)
|
||||
transition.updateFrame(node: self.buttonGlossNode, frame: buttonFrame)
|
||||
transition.updateFrame(node: self.buttonNode, frame: buttonFrame)
|
||||
|
||||
if self.title != self.labelNode.attributedText?.string {
|
||||
@ -86,3 +91,104 @@ final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
self.pressed?()
|
||||
}
|
||||
}
|
||||
|
||||
private final class SolidRoundedButtonGlossNodeParameters: NSObject {
|
||||
let gradientColors: NSArray?
|
||||
let cornerRadius: CGFloat
|
||||
let progress: CGFloat
|
||||
|
||||
init(gradientColors: NSArray?, cornerRadius: CGFloat, progress: CGFloat) {
|
||||
self.gradientColors = gradientColors
|
||||
self.cornerRadius = cornerRadius
|
||||
self.progress = progress
|
||||
}
|
||||
}
|
||||
|
||||
final class SolidRoundedButtonGlossNode : ASDisplayNode {
|
||||
var color: UIColor {
|
||||
didSet {
|
||||
self.updateGradientColors()
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
private var progress: CGFloat = 0.0
|
||||
private var displayLink: CADisplayLink?
|
||||
private let buttonCornerRadius: CGFloat
|
||||
private var gradientColors: NSArray?
|
||||
|
||||
init(color: UIColor, cornerRadius: CGFloat) {
|
||||
self.color = color
|
||||
self.buttonCornerRadius = cornerRadius
|
||||
|
||||
super.init()
|
||||
|
||||
self.isOpaque = false
|
||||
self.isLayerBacked = true
|
||||
|
||||
class DisplayLinkProxy: NSObject {
|
||||
weak var target: SolidRoundedButtonGlossNode?
|
||||
init(target: SolidRoundedButtonGlossNode) {
|
||||
self.target = target
|
||||
}
|
||||
|
||||
@objc func displayLinkEvent() {
|
||||
self.target?.displayLinkEvent()
|
||||
}
|
||||
}
|
||||
|
||||
self.displayLink = CADisplayLink(target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.displayLinkEvent))
|
||||
self.displayLink?.isPaused = true
|
||||
self.displayLink?.add(to: RunLoop.main, forMode: RunLoopMode.commonModes)
|
||||
|
||||
self.updateGradientColors()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.displayLink?.invalidate()
|
||||
}
|
||||
|
||||
private func updateGradientColors() {
|
||||
let transparentColor = self.color.withAlphaComponent(0.0).cgColor
|
||||
self.gradientColors = [transparentColor, transparentColor, self.color.withAlphaComponent(0.12).cgColor, transparentColor, transparentColor]
|
||||
}
|
||||
|
||||
override func willEnterHierarchy() {
|
||||
super.willEnterHierarchy()
|
||||
self.displayLink?.isPaused = false
|
||||
}
|
||||
|
||||
override func didExitHierarchy() {
|
||||
super.didExitHierarchy()
|
||||
self.displayLink?.isPaused = true
|
||||
}
|
||||
|
||||
private func displayLinkEvent() {
|
||||
var newProgress = self.progress + 0.009
|
||||
if newProgress > 1.0 {
|
||||
newProgress = 0.0
|
||||
}
|
||||
self.progress = newProgress
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
|
||||
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
|
||||
return SolidRoundedButtonGlossNodeParameters(gradientColors: self.gradientColors, cornerRadius: self.buttonCornerRadius, progress: self.progress)
|
||||
}
|
||||
|
||||
@objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
if let parameters = parameters as? SolidRoundedButtonGlossNodeParameters, let gradientColors = parameters.gradientColors {
|
||||
let path = UIBezierPath(roundedRect: bounds, cornerRadius: parameters.cornerRadius)
|
||||
context.addPath(path.cgPath)
|
||||
context.clip()
|
||||
|
||||
var locations: [CGFloat] = [0.0, 0.15, 0.5, 0.85, 1.0]
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
|
||||
|
||||
let x = -4.0 * bounds.size.width + 8.0 * bounds.size.width * parameters.progress
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: x, y: 0.0), end: CGPoint(x: x + bounds.size.width, y: 0.0), options: CGGradientDrawingOptions())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,7 +91,7 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode {
|
||||
self.containerNode.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
|
||||
|
||||
self.controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in
|
||||
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
|
||||
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
|
||||
}, presentController: { _, _ in }, navigationController: {
|
||||
return nil
|
||||
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in
|
||||
|
||||
@ -77,7 +77,7 @@ private final class WebEmbedVideoContentNode: ASDisplayNode, UniversalVideoConte
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.playerNode)
|
||||
self.addSubnode(self.imageNode)
|
||||
//self.addSubnode(self.imageNode)
|
||||
|
||||
if let image = webpageContent.image {
|
||||
self.imageNode.setSignal(chatMessagePhoto(postbox: postbox, photoReference: .webPage(webPage: WebpageReference(webPage), media: image)))
|
||||
|
||||