Merge commit '9396d116759ce395c58a5670e2f197469d0f526d'

This commit is contained in:
Peter 2018-12-01 02:43:02 +04:00
commit f501699cdf
61 changed files with 4007 additions and 3371 deletions

View File

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 841 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1016 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 727 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 790 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

View File

@ -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 */,

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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