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 */; }; 09619B8E21A34C0100493558 /* InstantPageScrollableNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09619B8D21A34C0100493558 /* InstantPageScrollableNode.swift */; };
09619B9521A4ABF600493558 /* InstantPageReferenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09619B9321A4ABF500493558 /* InstantPageReferenceController.swift */; }; 09619B9521A4ABF600493558 /* InstantPageReferenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09619B9321A4ABF500493558 /* InstantPageReferenceController.swift */; };
09619B9621A4ABF600493558 /* InstantPageReferenceControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09619B9421A4ABF600493558 /* InstantPageReferenceControllerNode.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 */; }; 096C98BA21787A5C00C211FF /* LegacyBridgeAudio.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096C98B921787A5C00C211FF /* LegacyBridgeAudio.swift */; };
096C98BF21787C6700C211FF /* TGBridgeAudioEncoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 096C98BB21787C6600C211FF /* TGBridgeAudioEncoder.m */; }; 096C98BF21787C6700C211FF /* TGBridgeAudioEncoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 096C98BB21787C6600C211FF /* TGBridgeAudioEncoder.m */; };
096C98C021787C6700C211FF /* TGBridgeAudioEncoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 096C98BC21787C6600C211FF /* TGBridgeAudioEncoder.h */; }; 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 */; }; 09B4EE4D21A7B73800847FA6 /* PermissionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE4C21A7B73800847FA6 /* PermissionController.swift */; };
09B4EE4F21A7B75D00847FA6 /* PermissionControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE4E21A7B75D00847FA6 /* PermissionControllerNode.swift */; }; 09B4EE4F21A7B75D00847FA6 /* PermissionControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE4E21A7B75D00847FA6 /* PermissionControllerNode.swift */; };
09B4EE5221A7CC3E00847FA6 /* SolidRoundedButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE5121A7CC3E00847FA6 /* SolidRoundedButtonNode.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 */; }; 09B4EE5E21AC626B00847FA6 /* PermissionContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE5D21AC626B00847FA6 /* PermissionContentNode.swift */; };
09B4EE6021AD4A0E00847FA6 /* InstantPageContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE5F21AD4A0E00847FA6 /* InstantPageContentNode.swift */; }; 09B4EE6021AD4A0E00847FA6 /* InstantPageContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE5F21AD4A0E00847FA6 /* InstantPageContentNode.swift */; };
09B4EE6221AD791600847FA6 /* InstantPageStoredState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE6121AD791600847FA6 /* InstantPageStoredState.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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 09B4EE6121AD791600847FA6 /* InstantPageStoredState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageStoredState.swift; sourceTree = "<group>"; };
@ -2605,7 +2607,7 @@
D01B279C1E394A500022A4C0 /* NotificationsAndSounds.swift */, D01B279C1E394A500022A4C0 /* NotificationsAndSounds.swift */,
D0A749961E3AA25200AD786E /* NotificationSoundSelection.swift */, D0A749961E3AA25200AD786E /* NotificationSoundSelection.swift */,
D02C81722177AC5900CD1006 /* NotificationSearchItem.swift */, D02C81722177AC5900CD1006 /* NotificationSearchItem.swift */,
09B4EE5521A8149C00847FA6 /* NotificationPermissionInfoItem.swift */, 09B4EE5521A8149C00847FA6 /* PermissionInfoItem.swift */,
); );
name = Notifications; name = Notifications;
sourceTree = "<group>"; sourceTree = "<group>";
@ -3968,6 +3970,7 @@
D0F0AAE51EC21B68005EE2A5 /* CallControllerButton.swift */, D0F0AAE51EC21B68005EE2A5 /* CallControllerButton.swift */,
D0ACCB191EC5E0C20079D8BF /* CallControllerKeyPreviewNode.swift */, D0ACCB191EC5E0C20079D8BF /* CallControllerKeyPreviewNode.swift */,
09FE756C2153F5F900A3120F /* CallRouteActionSheetItem.swift */, 09FE756C2153F5F900A3120F /* CallRouteActionSheetItem.swift */,
0962E65C21B1486D00245FD9 /* CallDebugNode.swift */,
); );
name = Call; name = Call;
sourceTree = "<group>"; sourceTree = "<group>";
@ -5474,6 +5477,7 @@
D0EC6DBC1EB9F58900EBF1C3 /* ChatMediaInputGifPane.swift in Sources */, D0EC6DBC1EB9F58900EBF1C3 /* ChatMediaInputGifPane.swift in Sources */,
D0EC6DBD1EB9F58900EBF1C3 /* ChatMediaInputPanelEntries.swift in Sources */, D0EC6DBD1EB9F58900EBF1C3 /* ChatMediaInputPanelEntries.swift in Sources */,
D0471B4F1EFD84600074D609 /* BotCheckoutPriceItem.swift in Sources */, D0471B4F1EFD84600074D609 /* BotCheckoutPriceItem.swift in Sources */,
0962E65D21B1486D00245FD9 /* CallDebugNode.swift in Sources */,
D00ADFDB1EBA2EAF00873D2E /* OngoingCallContext.swift in Sources */, D00ADFDB1EBA2EAF00873D2E /* OngoingCallContext.swift in Sources */,
D0EC6DBE1EB9F58900EBF1C3 /* ChatMediaInputGridEntries.swift in Sources */, D0EC6DBE1EB9F58900EBF1C3 /* ChatMediaInputGridEntries.swift in Sources */,
D0EC6DBF1EB9F58900EBF1C3 /* ChatMediaInputMetaSectionItemNode.swift in Sources */, D0EC6DBF1EB9F58900EBF1C3 /* ChatMediaInputMetaSectionItemNode.swift in Sources */,
@ -5509,7 +5513,7 @@
D0BCC3D2203F0A6C008126C2 /* StringForMessageTimestampStatus.swift in Sources */, D0BCC3D2203F0A6C008126C2 /* StringForMessageTimestampStatus.swift in Sources */,
D0EC6DCF1EB9F58900EBF1C3 /* HorizontalStickerGridItem.swift in Sources */, D0EC6DCF1EB9F58900EBF1C3 /* HorizontalStickerGridItem.swift in Sources */,
D0EC6DD01EB9F58900EBF1C3 /* HashtagChatInputContextPanelNode.swift in Sources */, D0EC6DD01EB9F58900EBF1C3 /* HashtagChatInputContextPanelNode.swift in Sources */,
09B4EE5621A8149C00847FA6 /* NotificationPermissionInfoItem.swift in Sources */, 09B4EE5621A8149C00847FA6 /* PermissionInfoItem.swift in Sources */,
D0EC6DD11EB9F58900EBF1C3 /* HashtagChatInputPanelItem.swift in Sources */, D0EC6DD11EB9F58900EBF1C3 /* HashtagChatInputPanelItem.swift in Sources */,
D0EC6DD21EB9F58900EBF1C3 /* MentionChatInputContextPanelNode.swift in Sources */, D0EC6DD21EB9F58900EBF1C3 /* MentionChatInputContextPanelNode.swift in Sources */,
D00701A22029F6D0006B9E34 /* TGMimeTypeMap.m in Sources */, D00701A22029F6D0006B9E34 /* TGMimeTypeMap.m in Sources */,

View File

@ -178,6 +178,7 @@ final class CallControllerNode: ASDisplayNode {
self.callState = callState self.callState = callState
let statusValue: CallControllerStatusValue let statusValue: CallControllerStatusValue
var statusReception: Int32?
switch callState { switch callState {
case .waiting, .connecting: case .waiting, .connecting:
statusValue = .text(self.presentationData.strings.Call_StatusConnecting) statusValue = .text(self.presentationData.strings.Call_StatusConnecting)
@ -207,7 +208,7 @@ final class CallControllerNode: ASDisplayNode {
} }
case .ringing: case .ringing:
statusValue = .text(self.presentationData.strings.Call_StatusIncoming) statusValue = .text(self.presentationData.strings.Call_StatusIncoming)
case let .active(timestamp, keyVisualHash): case let .active(timestamp, reception, keyVisualHash):
let strings = self.presentationData.strings let strings = self.presentationData.strings
statusValue = .timer({ value in statusValue = .timer({ value in
return strings.Call_StatusOngoing(value).0 return strings.Call_StatusOngoing(value).0
@ -226,6 +227,7 @@ final class CallControllerNode: ASDisplayNode {
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
} }
} }
statusReception = reception
} }
switch callState { switch callState {
case .terminated, .terminating: case .terminated, .terminating:
@ -258,6 +260,7 @@ final class CallControllerNode: ASDisplayNode {
} }
} }
self.statusNode.status = statusValue self.statusNode.status = statusValue
self.statusNode.reception = statusReception
self.updateButtonsMode() 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 titleNode: TextNode
private let statusNode: TextNode private let statusNode: TextNode
private let statusMeasureNode: TextNode private let statusMeasureNode: TextNode
private let receptionNode: CallControllerReceptionNode
var title: String = "" var title: String = ""
var status: CallControllerStatusValue = .text("") { 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 statusTimer: SwiftSignalKit.Timer?
private var validLayoutWidth: CGFloat? private var validLayoutWidth: CGFloat?
@ -66,6 +84,9 @@ final class CallControllerStatusNode: ASDisplayNode {
self.statusNode = TextNode() self.statusNode = TextNode()
self.statusNode.displaysAsynchronously = false self.statusNode.displaysAsynchronously = false
self.statusMeasureNode = TextNode() self.statusMeasureNode = TextNode()
self.receptionNode = CallControllerReceptionNode()
self.receptionNode.alpha = 0.0
super.init() super.init()
@ -73,6 +94,7 @@ final class CallControllerStatusNode: ASDisplayNode {
self.addSubnode(self.titleNode) self.addSubnode(self.titleNode)
self.addSubnode(self.statusNode) self.addSubnode(self.statusNode)
self.addSubnode(self.receptionNode)
} }
deinit { deinit {
@ -92,6 +114,7 @@ final class CallControllerStatusNode: ASDisplayNode {
statusFont = regularStatusFont statusFont = regularStatusFont
} }
var statusOffset: CGFloat = 0.0
let statusText: String let statusText: String
let statusMeasureText: String let statusMeasureText: String
switch self.status { switch self.status {
@ -111,6 +134,7 @@ final class CallControllerStatusNode: ASDisplayNode {
} }
statusText = format(durationString) statusText = format(durationString)
statusMeasureText = format(measureDurationString) statusMeasureText = format(measureDurationString)
statusOffset += 8.0
} }
let spacing: CGFloat = 4.0 let spacing: CGFloat = 4.0
@ -123,8 +147,65 @@ final class CallControllerStatusNode: ASDisplayNode {
let _ = statusMeasureApply() let _ = statusMeasureApply()
self.titleNode.frame = CGRect(origin: CGPoint(x: floor((constrainedWidth - titleLayout.size.width) / 2.0), y: 0.0), size: titleLayout.size) 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 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) self?.openPeer(peerId: id, navigation: navigation, fromMessage: fromMessage)
}, openPeerMention: { [weak self] name in }, openPeerMention: { [weak self] name in
self?.openPeerMention(name) 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 { guard let strongSelf = self, strongSelf.isNodeLoaded else {
return return
} }
@ -430,7 +430,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
break 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 { guard let strongSelf = self, !actions.isEmpty else {
return return
} }

View File

@ -47,7 +47,7 @@ public final class ChatControllerInteraction {
let openMessage: (Message, ChatControllerInteractionOpenMessageMode) -> Bool let openMessage: (Message, ChatControllerInteractionOpenMessageMode) -> Bool
let openPeer: (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void let openPeer: (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void
let openPeerMention: (String) -> Void let openPeerMention: (String) -> Void
let openMessageContextMenu: (Message, ASDisplayNode, CGRect) -> Void let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect) -> Void
let navigateToMessage: (MessageId, MessageId) -> Void let navigateToMessage: (MessageId, MessageId) -> Void
let clickThroughMessage: () -> Void let clickThroughMessage: () -> Void
let toggleMessagesSelection: ([MessageId], Bool) -> Void let toggleMessagesSelection: ([MessageId], Bool) -> Void
@ -87,7 +87,7 @@ public final class ChatControllerInteraction {
var contextHighlightedState: ChatInterfaceHighlightedState? var contextHighlightedState: ChatInterfaceHighlightedState?
var automaticMediaDownloadSettings: AutomaticMediaDownloadSettings 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.openMessage = openMessage
self.openPeer = openPeer self.openPeer = openPeer
self.openPeerMention = openPeerMention self.openPeerMention = openPeerMention

View File

@ -149,7 +149,7 @@ func updatedChatEditInterfaceMessagetState(state: ChatPresentationInterfaceState
return updated 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 { guard let interfaceInteraction = interfaceInteraction, let controllerInteraction = controllerInteraction else {
return .single([]) return .single([])
} }
@ -444,7 +444,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
} }
if data.canSelect { if data.canSelect {
actions.append(.context(ContextMenuAction(content: .text(chatPresentationInterfaceState.strings.Conversation_ContextMenuMore), action: { 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 { if !data.messageActions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty && isAction {
@ -461,19 +461,19 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
if data.messageActions.options.contains(.forward) { if data.messageActions.options.contains(.forward) {
actions.append(.sheet(ChatMessageContextMenuSheetAction(color: .accent, title: chatPresentationInterfaceState.strings.Conversation_ContextMenuForward, action: { 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) { if data.messageActions.options.contains(.report) {
actions.append(.sheet(ChatMessageContextMenuSheetAction(color: .accent, title: chatPresentationInterfaceState.strings.Conversation_ContextMenuReport, action: { 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 { if !data.messageActions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty {
actions.append(.sheet(ChatMessageContextMenuSheetAction(color: .destructive, title: chatPresentationInterfaceState.strings.Conversation_ContextMenuDelete, action: { 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] { private func leftRevealOptions(strings: PresentationStrings, theme: PresentationTheme, isUnread: Bool) -> [ItemListRevealOption] {
var options: [ItemListRevealOption] = [] var options: [ItemListRevealOption] = []
if isUnread { 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 { } 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)) 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) { if let item = self.item, self.backgroundNode.frame.contains(location) {
var foundTapAction = false var foundTapAction = false
var tapMessage: Message? = item.content.firstMessage var tapMessage: Message? = item.content.firstMessage
var selectAll = false
loop: for contentNode in self.contentNodes { loop: for contentNode in self.contentNodes {
if !contentNode.frame.contains(location) { if !contentNode.frame.contains(location) {
continue loop continue loop
} else if contentNode is ChatMessageTextBubbleContentNode {
selectAll = true
} }
tapMessage = contentNode.item?.message tapMessage = contentNode.item?.message
let tapAction = contentNode.tapActionAtPoint(CGPoint(x: location.x - contentNode.frame.minX, y: location.y - contentNode.frame.minY)) 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: default:

View File

@ -362,7 +362,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
self.item?.controllerInteraction.clickThroughMessage() self.item?.controllerInteraction.clickThroughMessage()
case .longTap, .doubleTap: case .longTap, .doubleTap:
if let item = self.item, let videoContentNode = self.interactiveVideoNode.videoContentNode(at: self.view.convert(location, to: self.interactiveVideoNode.view)) { 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: case .hold:
break break

View File

@ -524,6 +524,10 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
strongSelf.iconNode = nil strongSelf.iconNode = nil
} }
if let streamingStatusNode = strongSelf.streamingStatusNode {
streamingStatusNode.frame = streamingCacheStatusFrame
}
if let updatedStatusSignal = updatedStatusSignal { if let updatedStatusSignal = updatedStatusSignal {
strongSelf.statusDisposable.set((updatedStatusSignal |> deliverOnMainQueue).start(next: { [weak strongSelf] status in strongSelf.statusDisposable.set((updatedStatusSignal |> deliverOnMainQueue).start(next: { [weak strongSelf] status in
displayLinkDispatcher.dispatch { displayLinkDispatcher.dispatch {

View File

@ -574,7 +574,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
self.item?.controllerInteraction.clickThroughMessage() self.item?.controllerInteraction.clickThroughMessage()
case .longTap, .doubleTap: case .longTap, .doubleTap:
if let item = self.item, let videoNode = self.videoNode, videoNode.frame.contains(location) { 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: case .hold:
break break
@ -610,7 +610,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
} }
private func progressPressed() { private func progressPressed() {
guard let item = self.item, let _ = self.telegramFile else { guard let item = self.item, let file = self.telegramFile else {
return return
} }
if let status = self.status { if let status = self.status {
@ -624,7 +624,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
deleteMessages(transaction: transaction, mediaBox: item.account.postbox.mediaBox, ids: [messageId]) deleteMessages(transaction: transaction, mediaBox: item.account.postbox.mediaBox, ids: [messageId])
}).start() }).start()
} else { } else {
self.videoNode?.fetchControl(.cancel) messageMediaFileCancelInteractiveFetch(account: item.account, messageId: item.message.id, file: file)
} }
case .Remote: case .Remote:
self.videoNode?.fetchControl(.fetch) self.videoNode?.fetchControl(.fetch)

View File

@ -377,7 +377,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
self.item?.controllerInteraction.clickThroughMessage() self.item?.controllerInteraction.clickThroughMessage()
case .longTap, .doubleTap: case .longTap, .doubleTap:
if let item = self.item, self.imageNode.frame.contains(location) { 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: case .hold:
break break

View File

@ -176,8 +176,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
} }
}, openPeerMention: { [weak self] name in }, openPeerMention: { [weak self] name in
self?.openPeerMention(name) self?.openPeerMention(name)
}, openMessageContextMenu: { [weak self] message, node, frame in }, openMessageContextMenu: { [weak self] message, selectAll, node, frame in
self?.openMessageContextMenu(message: message, node: node, frame: frame) 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 }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _ in
self?.openUrl(url) self?.openUrl(url)
}, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message in }, 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] = [] var actions: [ContextMenuAction] = []
if !message.text.isEmpty { if !message.text.isEmpty {
actions.append(ContextMenuAction(content: .text(self.presentationData.strings.Conversation_ContextMenuCopy), action: { actions.append(ContextMenuAction(content: .text(self.presentationData.strings.Conversation_ContextMenuCopy), action: {

View File

@ -8,18 +8,21 @@ class ContactListActionItem: ListViewItem {
let title: String let title: String
let icon: UIImage? let icon: UIImage?
let action: () -> Void 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.theme = theme
self.title = title self.title = title
self.icon = icon self.icon = icon
self.header = header
self.action = action 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) { 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 { async {
let node = ContactListActionItemNode() 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.contentSize = layout.contentSize
node.insets = layout.insets node.insets = layout.insets
@ -38,7 +41,8 @@ class ContactListActionItem: ListViewItem {
let makeLayout = nodeValue.asyncLayout() let makeLayout = nodeValue.asyncLayout()
async { 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 { Queue.mainQueue().async {
completion(layout, { completion(layout, {
apply() apply()
@ -55,6 +59,40 @@ class ContactListActionItem: ListViewItem {
listView.clearHighlightAnimated(true) listView.clearHighlightAnimated(true)
self.action() 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) private let titleFont = Font.regular(17.0)
@ -70,6 +108,8 @@ class ContactListActionItemNode: ListViewItemNode {
private var theme: PresentationTheme? private var theme: PresentationTheme?
private var item: ContactListActionItem?
init() { init() {
self.backgroundNode = ASDisplayNode() self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true self.backgroundNode.isLayerBacked = true
@ -100,29 +140,33 @@ class ContactListActionItemNode: ListViewItemNode {
self.addSubnode(self.titleNode) 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 makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let currentTheme = self.theme let currentTheme = self.theme
return { item, params in return { item, params, firstWithHeader in
var updatedTheme: PresentationTheme? var updatedTheme: PresentationTheme?
if currentTheme !== item.theme { if currentTheme !== item.theme {
updatedTheme = 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 (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 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 separatorHeight = UIScreenPixel
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
return (layout, { [weak self] in return (layout, { [weak self] in
if let strongSelf = self { if let strongSelf = self {
strongSelf.item = item
strongSelf.theme = item.theme strongSelf.theme = item.theme
if let _ = updatedTheme { if let _ = updatedTheme {
@ -207,4 +251,12 @@ class ContactListActionItemNode: ListViewItemNode {
override func animateRemoved(_ currentTimestamp: Double, duration: Double) { override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) 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 { private enum ContactListNodeEntryId: Hashable {
case search case search
case permission(action: Bool)
case option(index: Int) case option(index: Int)
case peerId(Int64) case peerId(Int64)
case deviceContact(DeviceContactStableId) case deviceContact(DeviceContactStableId)
@ -15,8 +16,10 @@ private enum ContactListNodeEntryId: Hashable {
switch self { switch self {
case .search: case .search:
return 0 return 0
case let .permission(action):
return (action ? 3 : 2).hashValue
case let .option(index): case let .option(index):
return (index + 2).hashValue return (index + 4).hashValue
case let .peerId(peerId): case let .peerId(peerId):
return peerId.hashValue return peerId.hashValue
case let .deviceContact(id): case let .deviceContact(id):
@ -37,6 +40,12 @@ private enum ContactListNodeEntryId: Hashable {
default: default:
return false return false
} }
case let .permission(action):
if case .permission(action) = rhs {
return true
} else {
return false
}
case let .option(index): case let .option(index):
if case .option(index) = rhs { if case .option(index) = rhs {
return true return true
@ -62,10 +71,12 @@ private enum ContactListNodeEntryId: Hashable {
private final class ContactListNodeInteraction { private final class ContactListNodeInteraction {
let activateSearch: () -> Void let activateSearch: () -> Void
let authorize: () -> Void
let openPeer: (ContactListPeer) -> 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.activateSearch = activateSearch
self.authorize = authorize
self.openPeer = openPeer self.openPeer = openPeer
} }
} }
@ -117,14 +128,20 @@ enum ContactListPeer: Equatable {
private enum ContactListNodeEntry: Comparable, Identifiable { private enum ContactListNodeEntry: Comparable, Identifiable {
case search(PresentationTheme, PresentationStrings) 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) case peer(Int, ContactListPeer, PeerPresence?, ListViewItemHeader?, ContactsPeerItemSelection, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, Bool)
var stableId: ContactListNodeEntryId { var stableId: ContactListNodeEntryId {
switch self { switch self {
case .search: case .search:
return .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) return .option(index: index)
case let .peer(_, peer, _, _, _, _, _, _, _, _, _): case let .peer(_, peer, _, _, _, _, _, _, _, _, _):
switch peer { switch peer {
@ -142,8 +159,14 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
return ChatListSearchItem(theme: theme, placeholder: strings.Contacts_SearchLabel, activate: { return ChatListSearchItem(theme: theme, placeholder: strings.Contacts_SearchLabel, activate: {
interaction.activateSearch() interaction.activateSearch()
}) })
case let .option(_, option, theme, _): case let .permissionInfo(theme, strings):
return ContactListActionItem(theme: theme, title: option.title, icon: option.icon, action: option.action) 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): case let .peer(_, peer, presence, header, selection, theme, strings, dateTimeFormat, nameSortOrder, nameDisplayOrder, enabled):
let status: ContactsPeerItemStatus let status: ContactsPeerItemStatus
let itemPeer: ContactsPeerItemPeer let itemPeer: ContactsPeerItemPeer
@ -174,8 +197,20 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
} else { } else {
return false return false
} }
case let .option(lhsIndex, lhsOption, lhsTheme, lhsStrings): case let .permissionInfo(lhsTheme, lhsStrings):
if case let .option(rhsIndex, rhsOption, rhsTheme, rhsStrings) = rhs, lhsIndex == rhsIndex, lhsOption == rhsOption, lhsTheme === rhsTheme, lhsStrings === rhsStrings { 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 return true
} else { } else {
return false return false
@ -231,18 +266,32 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
switch lhs { switch lhs {
case .search: case .search:
return true return true
case let .option(lhsIndex, _, _, _): case .permissionInfo:
switch rhs { switch rhs {
case .search: case .search:
return false return false
case let .option(rhsIndex, _, _, _): default:
return lhsIndex < rhsIndex
case .peer:
return true 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, _, _, _, _, _, _, _, _, _, _): case let .peer(lhsIndex, _, _, _, _, _, _, _, _, _, _):
switch rhs { switch rhs {
case .search, .option: case .search, .permissionInfo, .permissionEnable, .option:
return false return false
case let .peer(rhsIndex, _, _, _, _, _, _, _, _, _, _): case let .peer(rhsIndex, _, _, _, _, _, _, _, _, _, _):
return lhsIndex < 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 entries: [ContactListNodeEntry] = []
var commonHeader: ListViewItemHeader?
var orderedPeers: [ContactListPeer] var orderedPeers: [ContactListPeer]
var headers: [ContactListPeerId: ContactListNameIndexHeader] = [:] var headers: [ContactListPeerId: ContactListNameIndexHeader] = [:]
switch presentation { switch presentation {
case let .orderedByPresence(options): case let .orderedByPresence(options):
entries.append(.search(theme, strings)) 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 orderedPeers = peers.sorted(by: { lhs, rhs in
if case let .peer(lhsPeer, _) = lhs, case let .peer(rhsPeer, _) = rhs { if case let .peer(lhsPeer, _) = lhs, case let .peer(rhsPeer, _) = rhs {
let lhsPresence = presences[lhsPeer.id] let lhsPresence = presences[lhsPeer.id]
@ -329,7 +400,7 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer]
} }
}) })
for i in 0 ..< options.count { 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): case let .natural(displaySearch, options):
orderedPeers = peers.sorted(by: { lhs, rhs in orderedPeers = peers.sorted(by: { lhs, rhs in
@ -385,7 +456,7 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer]
entries.append(.search(theme, strings)) entries.append(.search(theme, strings))
} }
for i in 0 ..< options.count { 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: case .search:
orderedPeers = peers 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 { for i in 0 ..< orderedPeers.count {
let selection: ContactsPeerItemSelection let selection: ContactsPeerItemSelection
if let selectionState = selectionState { if let selectionState = selectionState {
@ -448,14 +511,14 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer]
return entries 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 (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } 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 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) } 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 { private struct ContactsListNodeTransition {
@ -463,6 +526,7 @@ private struct ContactsListNodeTransition {
let insertions: [ListViewInsertItem] let insertions: [ListViewInsertItem]
let updates: [ListViewUpdateItem] let updates: [ListViewUpdateItem]
let firstTime: Bool let firstTime: Bool
let isEmpty: Bool
let animated: Bool let animated: Bool
} }
@ -568,7 +632,10 @@ final class ContactListNode: ASDisplayNode {
private var presentationDataDisposable: Disposable? private var presentationDataDisposable: Disposable?
private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, Bool)> 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) { init(account: Account, presentation: ContactListPresentation, filters: [ContactListFilter] = [.excludeSelf], selectionState: ContactListNodeGroupSelectionState? = nil) {
self.account = account 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.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() super.init()
@ -593,12 +670,15 @@ final class ContactListNode: ASDisplayNode {
self.selectionStatePromise.set(.single(selectionState)) self.selectionStatePromise.set(.single(selectionState))
self.addSubnode(self.listNode) self.addSubnode(self.listNode)
self.addSubnode(self.authorizationNode)
let processingQueue = Queue() let processingQueue = Queue()
let previousEntries = Atomic<[ContactListNodeEntry]?>(value: nil) let previousEntries = Atomic<[ContactListNodeEntry]?>(value: nil)
let interaction = ContactListNodeInteraction(activateSearch: { [weak self] in let interaction = ContactListNodeInteraction(activateSearch: { [weak self] in
self?.activateSearch?() self?.activateSearch?()
}, authorize: {
authorizeImpl?()
}, openPeer: { [weak self] peer in }, openPeer: { [weak self] peer in
self?.openPeer?(peer) self?.openPeer?(peer)
}) })
@ -608,6 +688,7 @@ final class ContactListNode: ASDisplayNode {
let selectionStateSignal = self.selectionStatePromise.get() let selectionStateSignal = self.selectionStatePromise.get()
let transition: Signal<ContactsListNodeTransition, NoError> let transition: Signal<ContactsListNodeTransition, NoError>
let themeAndStringsPromise = self.themeAndStringsPromise let themeAndStringsPromise = self.themeAndStringsPromise
let authorizationsPromise = self.authorizationPromise
if case let .search(query, searchChatList, searchDeviceContacts) = presentation { if case let .search(query, searchChatList, searchDeviceContacts) = presentation {
transition = query transition = query
|> mapToSignal { query in |> mapToSignal { query in
@ -712,9 +793,9 @@ final class ContactListNode: ASDisplayNode {
peers.append(.deviceContact(stableId, contact)) 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) 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) { if OSAtomicCompareAndSwap32(1, 0, &firstTime) {
@ -725,10 +806,9 @@ final class ContactListNode: ASDisplayNode {
} }
} }
} else { } else {
transition = (combineLatest(self.contactPeersViewPromise.get(), selectionStateSignal, themeAndStringsPromise.get()) transition = (combineLatest(self.contactPeersViewPromise.get(), selectionStateSignal, themeAndStringsPromise.get(), authorizationsPromise.get())
|> mapToQueue { view, selectionState, themeAndStrings -> Signal<ContactsListNodeTransition, NoError> in |> mapToQueue { view, selectionState, themeAndStrings, authorizationStatus -> Signal<ContactsListNodeTransition, NoError> in
let signal = deferred { () -> Signal<ContactsListNodeTransition, NoError> in let signal = deferred { () -> Signal<ContactsListNodeTransition, NoError> in
var peers = view.peers.map({ ContactListPeer.peer(peer: $0, isGlobal: false) }) var peers = view.peers.map({ ContactListPeer.peer(peer: $0, isGlobal: false) })
var existingPeerIds = Set<PeerId>() var existingPeerIds = Set<PeerId>()
var disabledPeerIds = 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 previous = previousEntries.swap(entries)
let animated: Bool let animated: Bool
if let previous = previous, !themeAndStrings.5 { if let previous = previous, !themeAndStrings.5 {
@ -760,7 +844,7 @@ final class ContactListNode: ASDisplayNode {
} else { } else {
animated = false 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) { if OSAtomicCompareAndSwap32(1, 0, &firstTime) {
@ -789,6 +873,16 @@ final class ContactListNode: ASDisplayNode {
strongSelf.listNode.verticalScrollIndicatorColor = presentationData.theme.list.scrollIndicatorColor strongSelf.listNode.verticalScrollIndicatorColor = presentationData.theme.list.scrollIndicatorColor
strongSelf.themeAndStringsPromise.set(.single((presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameSortOrder, presentationData.nameDisplayOrder, presentationData.disableAnimations))) 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.dynamicBounceEnabled = !presentationData.disableAnimations
strongSelf.listNode.forEachAccessoryItemNode({ accessoryItemNode in 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 self.listNode.didEndScrolling = { [weak self] in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -815,12 +916,26 @@ final class ContactListNode: ASDisplayNode {
fixSearchableListNodeScrolling(strongSelf.listNode) fixSearchableListNodeScrolling(strongSelf.listNode)
} }
// self.authorizationNode.allow = { [weak self] in authorizeImpl = { [weak self] in
// self?.account.telegramApplicationContext.applicationBindings.openSettings() if let strongSelf = self {
// } let _ = (DeviceAccess.authorizationStatus(account: account, subject: .contacts)
// self.authorizationNode.openPrivacyPolicy = { [weak self] in |> take(1)
// self?.openPrivacyPolicy?() |> 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 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 }) 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)) //if let authorizationNode = self.authorizationNode {
//transition.updateFrame(node: self.authorizationNode, frame: self.bounds) authorizationNode.updateLayout(size: layout.size, insets: insets, transition: transition)
//self.authorizationNode.containerLayoutUpdated(sublayout, navigationBarHeight: 0.0, transition: transition) transition.updateFrame(node: authorizationNode, frame: self.bounds)
//}
if !self.hasValidLayout { if !self.hasValidLayout {
self.hasValidLayout = true self.hasValidLayout = true
self.dequeueTransitions() 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) self.authorizationDisposable = (DeviceAccess.authorizationStatus(account: account, subject: .contacts)
|> deliverOnMainQueue).start(next: { [weak self] status in |> deliverOnMainQueue).start(next: { [weak self] status in
if let strongSelf = self { 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 header = item.header {
if let previousItem = previousItem as? ContactsPeerItem { if let previousItem = previousItem as? ContactsPeerItem {
firstWithHeader = header.id != previousItem.header?.id firstWithHeader = header.id != previousItem.header?.id
} else if let previousItem = previousItem as? ContactListActionItem {
firstWithHeader = header.id != previousItem.header?.id
} else { } else {
firstWithHeader = true firstWithHeader = true
} }
@ -257,6 +259,8 @@ class ContactsPeerItem: ListViewItem {
if let header = item.header { if let header = item.header {
if let nextItem = nextItem as? ContactsPeerItem { if let nextItem = nextItem as? ContactsPeerItem {
last = header.id != nextItem.header?.id last = header.id != nextItem.header?.id
} else if let nextItem = nextItem as? ContactListActionItem {
last = header.id != nextItem.header?.id
} else { } else {
last = true last = true
} }

View File

@ -87,7 +87,8 @@ private let list = PresentationThemeList(
destructive: PresentationThemeItemDisclosureAction(fillColor: destructiveColor, foregroundColor: .white), destructive: PresentationThemeItemDisclosureAction(fillColor: destructiveColor, foregroundColor: .white),
constructive: PresentationThemeItemDisclosureAction(fillColor: constructiveColor, foregroundColor: .white), constructive: PresentationThemeItemDisclosureAction(fillColor: constructiveColor, foregroundColor: .white),
accent: PresentationThemeItemDisclosureAction(fillColor: accentColor, 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( itemCheckColors: PresentationThemeCheck(
strokeColor: UIColor(rgb: 0xDBF5FF, alpha: 0.5), strokeColor: UIColor(rgb: 0xDBF5FF, alpha: 0.5),

View File

@ -87,7 +87,8 @@ private let list = PresentationThemeList(
destructive: PresentationThemeItemDisclosureAction(fillColor: destructiveColor, foregroundColor: .white), destructive: PresentationThemeItemDisclosureAction(fillColor: destructiveColor, foregroundColor: .white),
constructive: PresentationThemeItemDisclosureAction(fillColor: constructiveColor, foregroundColor: .white), constructive: PresentationThemeItemDisclosureAction(fillColor: constructiveColor, foregroundColor: .white),
accent: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0x666666), 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( itemCheckColors: PresentationThemeCheck(
strokeColor: UIColor(rgb: 0xffffff, alpha: 0.5), 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), destructive: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0xff3824), foregroundColor: .white),
constructive: PresentationThemeItemDisclosureAction(fillColor: constructiveColor, foregroundColor: .white), constructive: PresentationThemeItemDisclosureAction(fillColor: constructiveColor, foregroundColor: .white),
accent: PresentationThemeItemDisclosureAction(fillColor: accentColor, 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( itemCheckColors: PresentationThemeCheck(
strokeColor: UIColor(rgb: 0xC7C7CC), strokeColor: UIColor(rgb: 0xC7C7CC),

View File

@ -45,6 +45,12 @@ final class GenericEmbedImplementation: WebEmbedImplementation {
webView.loadHTMLString(html, baseURL: URL(string: "about:blank")) webView.loadHTMLString(html, baseURL: URL(string: "about:blank"))
userContentController.addUserScript(WKUserScript(source: userScript, injectionTime: .atDocumentEnd, forMainFrameOnly: false)) 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() { func play() {

View File

@ -403,7 +403,7 @@ final class GridMessageItemNode: GridItemNode {
} }
} }
case .longTap: case .longTap:
controllerInteraction.openMessageContextMenu(message, self, self.bounds) controllerInteraction.openMessageContextMenu(message, false, self, self.bounds)
default: default:
break 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 { 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 inset: CGFloat = 17.0
let imageSpacing: CGFloat = 10.0
var sideInset = inset var sideInset = inset
let imageSize = CGSize(width: 44.0, height: 44.0) let imageSize = CGSize(width: 44.0, height: 44.0)
if cover != nil { if cover != nil {
sideInset += imageSize.width + 10.0 sideInset += imageSize.width + imageSpacing
} }
var availableLines: Int = 3 var availableLines: Int = 3
@ -87,9 +88,16 @@ func layoutArticleItem(theme: InstantPageTheme, webPage: TelegramMediaWebpage, t
hasRTL = true 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 { 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) contentItems.append(contentsOf: descriptionItems)
if let textItem = descriptionTextItem { if let textItem = descriptionTextItem {

View File

@ -49,16 +49,19 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
var items: [InstantPageItem] = [] var items: [InstantPageItem] = []
var offset = contentSize.height var offset = contentSize.height
var contentSize = CGSize() var contentSize = CGSize()
var rtl = rtl
if case .empty = caption.text { if case .empty = caption.text {
} else { } else {
contentSize.height += 14.0 contentSize.height += 14.0
offset += 14.0 offset += 14.0
let styleStack = InstantPageTextStyleStack() let styleStack = InstantPageTextStyleStack()
setupStyleStack(styleStack, theme: theme, category: .caption, link: false) 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 contentSize.height += captionContentSize.height
offset += captionContentSize.height offset += captionContentSize.height
items.append(contentsOf: captionItems) items.append(contentsOf: captionItems)
rtl = textItem?.containsRTL ?? rtl
} }
if case .empty = caption.credit { if case .empty = caption.credit {
@ -72,7 +75,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
} }
let styleStack = InstantPageTextStyleStack() let styleStack = InstantPageTextStyleStack()
setupStyleStack(styleStack, theme: theme, category: .credit, link: false) 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 contentSize.height += captionContentSize.height
offset += captionContentSize.height offset += captionContentSize.height
items.append(contentsOf: captionItems) 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 inset: CGFloat = i == articles.count - 1 ? 0.0 : 17.0
let lineSize = CGSize(width: boundingWidth - inset, height: UIScreenPixel) 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) items.append(shapeItem)
} }
return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items)

View File

@ -86,7 +86,7 @@ private enum InviteContactsEntry: Comparable, Identifiable {
interaction.activateSearch() interaction.activateSearch()
}) })
case let .option(_, option, theme, _): 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): case let .peer(_, id, contact, count, selection, theme, strings, nameSortOrder, nameDisplayOrder):
let status: ContactsPeerItemStatus let status: ContactsPeerItemStatus
if count != 0 { if count != 0 {

View File

@ -849,7 +849,7 @@ final class ListMessageFileItemNode: ListMessageNode {
override func longTapped() { override func longTapped() {
if let item = self.item { 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() { override func longTapped() {
if let item = self.item { 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.proxyNode.isHidden = !self.title.hasProxy
self.proxyButton.isHidden = !self.title.hasProxy self.proxyButton.isHidden = !self.title.hasProxy
self.buttonView.isHidden = !self.title.isPasscodeSet
if self.title.isPasscodeSet && !self.title.activity { if self.title.isPasscodeSet && !self.title.activity {
self.buttonView.isHidden = false
self.lockView.isHidden = false self.lockView.isHidden = false
self.lockView.setIsLocked(self.title.isManuallyLocked, theme: self.theme, animated: !self.bounds.size.width.isZero) self.lockView.setIsLocked(self.title.isManuallyLocked, theme: self.theme, animated: !self.bounds.size.width.isZero)
} else { } else {
self.buttonView.isHidden = true
self.lockView.isHidden = true self.lockView.isHidden = true
self.lockView.setIsLocked(false, theme: self.theme, animated: false) self.lockView.setIsLocked(false, theme: self.theme, animated: false)
} }

View File

@ -10,7 +10,7 @@ private final class NotificationsAndSoundsArguments {
let pushController: (ViewController) -> Void let pushController: (ViewController) -> Void
let soundSelectionDisposable: MetaDisposable let soundSelectionDisposable: MetaDisposable
let enableNotifications: () -> Void let authorizeNotifications: () -> Void
let updateMessageAlerts: (Bool) -> Void let updateMessageAlerts: (Bool) -> Void
let updateMessagePreviews: (Bool) -> Void let updateMessagePreviews: (Bool) -> Void
@ -39,12 +39,12 @@ private final class NotificationsAndSoundsArguments {
let openAppSettings: () -> Void 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.account = account
self.presentController = presentController self.presentController = presentController
self.pushController = pushController self.pushController = pushController
self.soundSelectionDisposable = soundSelectionDisposable self.soundSelectionDisposable = soundSelectionDisposable
self.enableNotifications = enableNotifications self.authorizeNotifications = authorizeNotifications
self.updateMessageAlerts = updateMessageAlerts self.updateMessageAlerts = updateMessageAlerts
self.updateMessagePreviews = updateMessagePreviews self.updateMessagePreviews = updateMessagePreviews
self.updateMessageSound = updateMessageSound self.updateMessageSound = updateMessageSound
@ -432,10 +432,10 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
func item(_ arguments: NotificationsAndSoundsArguments) -> ListViewItem { func item(_ arguments: NotificationsAndSoundsArguments) -> ListViewItem {
switch self { switch self {
case let .permissionInfo(theme, strings): 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): case let .permissionEnable(theme, text):
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { 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): case let .messageHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) 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] { private func notificationsAndSoundsEntries(authorizationStatus: AccessType, globalSettings: GlobalNotificationSettingsSet, inAppSettings: InAppNotificationSettings, exceptions: (users: NotificationExceptionMode, groups: NotificationExceptionMode, channels: NotificationExceptionMode), presentationData: PresentationData) -> [NotificationsAndSoundsEntry] {
var entries: [NotificationsAndSoundsEntry] = [] var entries: [NotificationsAndSoundsEntry] = []
// switch authorizationStatus { switch authorizationStatus {
// case .denied: case .denied:
// entries.append(.permissionInfo(presentationData.theme, presentationData.strings)) entries.append(.permissionInfo(presentationData.theme, presentationData.strings))
// entries.append(.permissionEnable(presentationData.theme, "Turn ON in Settings")) entries.append(.permissionEnable(presentationData.theme, presentationData.strings.Permissions_NotificationsAllowInSettings))
// case .notDetermined: case .notDetermined:
// entries.append(.permissionInfo(presentationData.theme, presentationData.strings)) entries.append(.permissionInfo(presentationData.theme, presentationData.strings))
// entries.append(.permissionEnable(presentationData.theme, "Turn Notifications ON")) entries.append(.permissionEnable(presentationData.theme, presentationData.strings.Permissions_NotificationsAllow))
// default: default:
// break break
// } }
entries.append(.messageHeader(presentationData.theme, presentationData.strings.Notifications_MessageNotifications)) entries.append(.messageHeader(presentationData.theme, presentationData.strings.Notifications_MessageNotifications))
entries.append(.messageAlerts(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsAlert, globalSettings.privateChats.enabled)) 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) presentControllerImpl?(controller, arguments)
}, pushController: { controller in }, pushController: { controller in
pushControllerImpl?(controller) pushControllerImpl?(controller)
}, soundSelectionDisposable: MetaDisposable(), enableNotifications: { }, soundSelectionDisposable: MetaDisposable(), authorizeNotifications: {
let _ = (DeviceAccess.authorizationStatus(account: account, subject: .notifications) let _ = (DeviceAccess.authorizationStatus(account: account, subject: .notifications)
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { status in |> 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 let audioSessionDisposable = MetaDisposable()
private var networkTypeDisposable: Disposable? private var networkTypeDisposable: Disposable?
@ -163,6 +168,9 @@ final class OngoingCallContext {
context.stateChanged = { [weak self] state in context.stateChanged = { [weak self] state in
self?.contextState.set(.single(state)) self?.contextState.set(.single(state))
} }
context.signalBarsChanged = { [weak self] signalBars in
self?.receptionPromise.set(.single(signalBars))
}
context.callEnded = { debugLog, bytesSentWifi, bytesReceivedWifi, bytesSentMobile, bytesReceivedMobile in context.callEnded = { debugLog, bytesSentWifi, bytesReceivedWifi, bytesSentMobile, bytesReceivedMobile in
let delta = NetworkUsageStatsConnectionsEntry( let delta = NetworkUsageStatsConnectionsEntry(
cellular: NetworkUsageStatsDirectionsEntry( cellular: NetworkUsageStatsDirectionsEntry(

View File

@ -60,6 +60,7 @@ typedef NS_ENUM(int32_t, OngoingCallDataSaving) {
+ (int32_t)maxLayer; + (int32_t)maxLayer;
@property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallState); @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); @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; - (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; tgvoip::VoIPController *_controller;
OngoingCallState _state; OngoingCallState _state;
int32_t _signalBars;
} }
- (void)controllerStateChanged:(int)state; - (void)controllerStateChanged:(int)state;
- (void)signalBarsChanged:(int32_t)signalBars;
@end @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 @implementation VoipProxyServer
- (instancetype _Nonnull)initWithHost:(NSString * _Nonnull)host port:(int32_t)port username:(NSString * _Nullable)username password:(NSString * _Nullable)password { - (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; TGVoipLoggingFunction = loggingFunction;
} }
+ (void)applyServerConfig:(NSString *)data { + (void)applyServerConfig:(NSString *)string {
if (data.length == 0) { if (string.length != 0) {
return; tgvoip::ServerConfig::GetSharedInstance()->Update(std::string(string.UTF8String));
}
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);
} }
} }
@ -254,7 +239,7 @@ static int callControllerDataSavingForType(OngoingCallDataSaving type) {
callbacks.connectionStateChanged = &controllerStateCallback; callbacks.connectionStateChanged = &controllerStateCallback;
callbacks.groupCallKeyReceived = NULL; callbacks.groupCallKeyReceived = NULL;
callbacks.groupCallKeySent = NULL; callbacks.groupCallKeySent = NULL;
callbacks.signalBarCountChanged = NULL; callbacks.signalBarCountChanged = &signalBarsCallback;
callbacks.upgradeToGroupCallRequested = NULL; callbacks.upgradeToGroupCallRequested = NULL;
_controller->SetCallbacks(callbacks); _controller->SetCallbacks(callbacks);
@ -266,6 +251,7 @@ static int callControllerDataSavingForType(OngoingCallDataSaving type) {
tgvoip::VoIPController::crypto.aes_ctr_encrypt = &TGCallAesCtrEncrypt; tgvoip::VoIPController::crypto.aes_ctr_encrypt = &TGCallAesCtrEncrypt;
_state = OngoingCallStateInitializing; _state = OngoingCallStateInitializing;
_signalBars = -1;
} }
return self; return self;
} }
@ -316,11 +302,10 @@ static int callControllerDataSavingForType(OngoingCallDataSaving type) {
- (void)stop { - (void)stop {
if (_controller != nil) { if (_controller != nil) {
char *buffer = (char *)malloc(_controller->GetDebugLogLength());
_controller->Stop(); _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; tgvoip::VoIPController::TrafficStats stats;
_controller->GetStats(&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 { - (void)setIsMuted:(bool)isMuted {
if (_controller != nil) { if (_controller != nil) {
_controller->SetMicMute(isMuted); _controller->SetMicMute(isMuted);

View File

@ -54,7 +54,7 @@ final class OverlayPlayerControllerNode: ViewControllerTracingNode, UIGestureRec
} else { } else {
return false 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: { }, presentController: { _, _ in }, navigationController: {
return nil return nil
}, presentGlobalOverlayController: { _, _ in }, presentGlobalOverlayController: { _, _ in

View File

@ -474,8 +474,8 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
let scrubberVerticalOrigin: CGFloat = infoVerticalOrigin + 64.0 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.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.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 - 40.0, 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 - 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))) 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 }, openPeerMention: { _ in
}, openMessageContextMenu: { [weak self] message, _, _ in }, openMessageContextMenu: { [weak self] message, _, _, _ in
if let strongSelf = self, strongSelf.isNodeLoaded { if let strongSelf = self, strongSelf.isNodeLoaded {
if let message = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id)?.message { if let message = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id)?.message {
let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme) let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme)

View File

@ -3,19 +3,22 @@ import Display
import AsyncDisplayKit import AsyncDisplayKit
final class PermissionContentNode: ASDisplayNode { final class PermissionContentNode: ASDisplayNode {
private var theme: PresentationTheme
let kind: PermissionStateKind
private let iconNode: ASImageNode private let iconNode: ASImageNode
private let titleNode: ImmediateTextNode private let titleNode: ImmediateTextNode
private let textNode: ImmediateTextNode private let textNode: ImmediateTextNode
private let actionButton: SolidRoundedButtonNode private let actionButton: SolidRoundedButtonNode
private let privacyPolicyButton: HighlightableButtonNode private let privacyPolicyButton: HighlightableButtonNode
var kind: PermissionStateKind
private var title: String private var title: String
var buttonAction: (() -> Void)? var buttonAction: (() -> Void)?
var openPrivacyPolicy: (() -> Void)? var openPrivacyPolicy: (() -> Void)?
init(theme: PresentationTheme, strings: PresentationStrings, kind: PermissionStateKind, icon: UIImage?, title: String, text: String, buttonTitle: String, buttonAction: @escaping () -> Void, 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.kind = kind
self.buttonAction = buttonAction self.buttonAction = buttonAction
@ -42,7 +45,7 @@ final class PermissionContentNode: ASDisplayNode {
self.actionButton = SolidRoundedButtonNode(theme: theme, height: 48.0, cornerRadius: 9.0) self.actionButton = SolidRoundedButtonNode(theme: theme, height: 48.0, cornerRadius: 9.0)
self.privacyPolicyButton = HighlightableButtonNode() 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() super.init()
@ -74,23 +77,43 @@ final class PermissionContentNode: ASDisplayNode {
} }
func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) { func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) {
let sidePadding: CGFloat = 20.0 let sidePadding: CGFloat
//let sideButtonInset: CGFloat = 16.0 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 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 textSize = self.textNode.updateLayout(CGSize(width: size.width - sidePadding * 2.0, height: .greatestFiniteMagnitude))
let buttonHeight = self.actionButton.updateLayout(width: size.width, transition: transition) let buttonHeight = self.actionButton.updateLayout(width: size.width, transition: transition)
let titleSubtitleSpacing: CGFloat = 12.0
let textHeight = titleSize.height + titleSubtitleSpacing + textSize.height
let titleSubtitleSpacing: CGFloat = 26.0
let minContentHeight = textHeight let buttonSpacing: CGFloat = 36.0
let contentHeight = min(215.0, max(size.height - insets.top - insets.bottom - 40.0, minContentHeight)) 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 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.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.textNode, frame: textFrame)
transition.updateFrame(node: self.actionButton, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: buttonHeight)) transition.updateFrame(node: self.actionButton, frame: buttonFrame)
} }
} }

View File

@ -126,79 +126,79 @@ final class PermissionControllerNode: ASDisplayNode {
} }
private func transition(state: PermissionControllerState, transition: ContainedViewLayoutTransition) { private func transition(state: PermissionControllerState, transition: ContainedViewLayoutTransition) {
// let insets = state.layout.layout.insets(options: [.statusBar]) 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)) 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 state.data.state?.kind != self.contentNode?.kind {
// if let dataState = state.data.state { if let dataState = state.data.state {
// let icon: UIImage? let icon: UIImage?
// let title: String let title: String
// let text: String let text: String
// let buttonTitle: String let buttonTitle: String
// let hasPrivacyPolicy: Bool let hasPrivacyPolicy: Bool
//
// switch dataState { switch dataState {
// case let .contacts(status): case let .contacts(status):
// icon = UIImage(bundleImageName: "Settings/Permissions/Contacts") icon = UIImage(bundleImageName: "Settings/Permissions/Contacts")
// title = self.presentationData.strings.Permissions_ContactsTitle title = self.presentationData.strings.Permissions_ContactsTitle
// text = self.presentationData.strings.Permissions_ContactsText text = self.presentationData.strings.Permissions_ContactsText
// if status == .denied { if status == .denied {
// buttonTitle = self.presentationData.strings.Permissions_ContactsAllowInSettings buttonTitle = self.presentationData.strings.Permissions_ContactsAllowInSettings
// } else { } else {
// buttonTitle = self.presentationData.strings.Permissions_ContactsAllow buttonTitle = self.presentationData.strings.Permissions_ContactsAllow
// } }
// hasPrivacyPolicy = true hasPrivacyPolicy = true
// case let .notifications(status): case let .notifications(status):
// icon = UIImage(bundleImageName: "Settings/Permissions/Notifications") icon = UIImage(bundleImageName: "Settings/Permissions/Notifications")
// title = self.presentationData.strings.Permissions_NotificationsTitle title = self.presentationData.strings.Permissions_NotificationsTitle
// text = self.presentationData.strings.Permissions_NotificationsText text = self.presentationData.strings.Permissions_NotificationsText
// if status == .denied { if status == .denied {
// buttonTitle = self.presentationData.strings.Permissions_NotificationsAllowInSettings buttonTitle = self.presentationData.strings.Permissions_NotificationsAllowInSettings
// } else { } else {
// buttonTitle = self.presentationData.strings.Permissions_NotificationsAllow buttonTitle = self.presentationData.strings.Permissions_NotificationsAllow
// } }
// hasPrivacyPolicy = false hasPrivacyPolicy = false
// case let .siri(status): case let .siri(status):
// icon = UIImage(bundleImageName: "Settings/Permissions/CellularData") icon = UIImage(bundleImageName: "Settings/Permissions/Siri")
// title = self.presentationData.strings.Permissions_SiriTitle title = self.presentationData.strings.Permissions_SiriTitle
// text = self.presentationData.strings.Permissions_SiriText text = self.presentationData.strings.Permissions_SiriText
// if status == .denied { if status == .denied {
// buttonTitle = self.presentationData.strings.Permissions_SiriAllowInSettings buttonTitle = self.presentationData.strings.Permissions_SiriAllowInSettings
// } else { } else {
// buttonTitle = self.presentationData.strings.Permissions_SiriAllow buttonTitle = self.presentationData.strings.Permissions_SiriAllow
// } }
// hasPrivacyPolicy = false hasPrivacyPolicy = false
// case .cellularData: case .cellularData:
// icon = UIImage(bundleImageName: "Settings/Permissions/CellularData") icon = UIImage(bundleImageName: "Settings/Permissions/CellularData")
// title = self.presentationData.strings.Permissions_CellularDataTitle title = self.presentationData.strings.Permissions_CellularDataTitle
// text = self.presentationData.strings.Permissions_CellularDataText text = self.presentationData.strings.Permissions_CellularDataText
// buttonTitle = self.presentationData.strings.Permissions_CellularDataAllowInSettings buttonTitle = self.presentationData.strings.Permissions_CellularDataAllowInSettings
// hasPrivacyPolicy = false 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) 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) self.insertSubnode(contentNode, at: 0)
// contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: .immediate) contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: .immediate)
// contentNode.frame = contentFrame contentNode.frame = contentFrame
// if let currentContentNode = self.contentNode { if let currentContentNode = self.contentNode {
// transition.updatePosition(node: currentContentNode, position: CGPoint(x: -contentFrame.size.width / 2.0, y: contentFrame.midY), completion: { [weak currentContentNode] _ in transition.updatePosition(node: currentContentNode, position: CGPoint(x: -contentFrame.size.width / 2.0, y: contentFrame.midY), completion: { [weak currentContentNode] _ in
// currentContentNode?.removeFromSupernode() currentContentNode?.removeFromSupernode()
// }) })
// transition.animateHorizontalOffsetAdditive(node: contentNode, offset: -contentFrame.width) transition.animateHorizontalOffsetAdditive(node: contentNode, offset: -contentFrame.width)
// } else if transition.isAnimated { } else if transition.isAnimated {
// contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
// } }
// self.contentNode = contentNode self.contentNode = contentNode
// } else if let currentContentNode = self.contentNode { } else if let currentContentNode = self.contentNode {
// transition.updateAlpha(node: currentContentNode, alpha: 0.0, completion: { [weak currentContentNode] _ in transition.updateAlpha(node: currentContentNode, alpha: 0.0, completion: { [weak currentContentNode] _ in
// currentContentNode?.removeFromSupernode() currentContentNode?.removeFromSupernode()
// }) })
// self.contentNode = nil self.contentNode = nil
// } }
// } else if let contentNode = self.contentNode { } else if let contentNode = self.contentNode {
// transition.updateFrame(node: contentNode, frame: contentFrame) transition.updateFrame(node: contentNode, frame: contentFrame)
// contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: transition) contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: transition)
// } }
} }
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {

View File

@ -3,23 +3,23 @@ import Display
import AsyncDisplayKit import AsyncDisplayKit
import SwiftSignalKit import SwiftSignalKit
class NotificationPermissionInfoItem: ListViewItem, ItemListItem { class PermissionInfoItem: ListViewItem {
let selectable: Bool = false let selectable: Bool = false
let sectionId: ItemListSectionId
let theme: PresentationTheme let theme: PresentationTheme
let strings: PresentationStrings let strings: PresentationStrings
let subject: DeviceAccessSubject
init(theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId) { init(theme: PresentationTheme, strings: PresentationStrings, subject: DeviceAccessSubject) {
self.theme = theme self.theme = theme
self.strings = strings 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) { 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 { async {
let node = NotificationPermissionInfoItemNode() let node = PermissionInfoItemNode()
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) let (layout, apply) = node.asyncLayout()(self, params, nil)
node.contentSize = layout.contentSize node.contentSize = layout.contentSize
node.insets = layout.insets 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) { 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 { 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() let makeLayout = nodeValue.asyncLayout()
async { async {
@ -54,7 +96,7 @@ private let titleFont = Font.semibold(17.0)
private let textFont = Font.regular(16.0) private let textFont = Font.regular(16.0)
private let badgeFont = Font.regular(15.0) private let badgeFont = Font.regular(15.0)
class NotificationPermissionInfoItemNode: ListViewItemNode { class PermissionInfoItemNode: ListViewItemNode {
private let backgroundNode: ASDisplayNode private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode
@ -64,7 +106,7 @@ class NotificationPermissionInfoItemNode: ListViewItemNode {
let titleNode: TextNode let titleNode: TextNode
let textNode: TextNode let textNode: TextNode
private var item: NotificationPermissionInfoItem? private var item: PermissionInfoItem?
override var canBeSelected: Bool { override var canBeSelected: Bool {
return false return false
@ -103,7 +145,7 @@ class NotificationPermissionInfoItemNode: ListViewItemNode {
self.addSubnode(self.textNode) 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 makeLabelLayout = TextNode.asyncLayout(self.labelNode)
let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeTextLayout = TextNode.asyncLayout(self.textNode) let makeTextLayout = TextNode.asyncLayout(self.textNode)
@ -123,14 +165,33 @@ class NotificationPermissionInfoItemNode: ListViewItemNode {
updatedBadgeImage = generateStretchableFilledCircleImage(diameter: badgeDiameter, color: item.theme.list.itemDestructiveColor) 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 separatorHeight = UIScreenPixel
let itemBackgroundColor = item.theme.list.itemBlocksBackgroundColor let itemBackgroundColor = item.theme.list.itemBlocksBackgroundColor
let itemSeparatorColor = item.theme.list.itemBlocksSeparatorColor 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 (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 (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: "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 (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) let contentSize = CGSize(width: params.width, height: titleLayout.size.height + textLayout.size.height + 36.0)
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in
@ -156,18 +217,24 @@ class NotificationPermissionInfoItemNode: ListViewItemNode {
if strongSelf.bottomStripeNode.supernode == nil { if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
} }
switch neighbors.top { if let neighbors = neighbors {
case .sameSection(false): switch neighbors.top {
strongSelf.topStripeNode.isHidden = true case .sameSection(false):
default: strongSelf.topStripeNode.isHidden = true
strongSelf.topStripeNode.isHidden = false default:
strongSelf.topStripeNode.isHidden = false
}
} }
let bottomStripeInset: CGFloat let bottomStripeInset: CGFloat
switch neighbors.bottom { if let neighbors = neighbors {
case .sameSection(false): switch neighbors.bottom {
bottomStripeInset = leftInset case .sameSection(false):
default: bottomStripeInset = leftInset
bottomStripeInset = 0.0 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))) 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 ringing
case requesting(Bool) case requesting(Bool)
case connecting(Data?) case connecting(Data?)
case active(Double, Data) case active(Double, Int32?, Data)
case terminating case terminating
case terminated(CallSessionTerminationReason?) case terminated(CallSessionTerminationReason?)
} }
@ -173,6 +173,8 @@ public final class PresentationCall {
private var callContextState: OngoingCallContextState? private var callContextState: OngoingCallContextState?
private var ongoingContext: OngoingCallContext private var ongoingContext: OngoingCallContext
private var ongoingContextStateDisposable: Disposable? private var ongoingContextStateDisposable: Disposable?
private var reception: Int32?
private var receptionDisposable: Disposable?
private var reportedIncomingCall = false private var reportedIncomingCall = false
private var sessionStateDisposable: Disposable? private var sessionStateDisposable: Disposable?
@ -236,7 +238,7 @@ public final class PresentationCall {
self.sessionStateDisposable = (callSessionManager.callState(internalId: internalId) self.sessionStateDisposable = (callSessionManager.callState(internalId: internalId)
|> deliverOnMainQueue).start(next: { [weak self] sessionState in |> deliverOnMainQueue).start(next: { [weak self] sessionState in
if let strongSelf = self { 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 |> deliverOnMainQueue).start(next: { [weak self] contextState in
if let strongSelf = self { if let strongSelf = self {
if let sessionState = strongSelf.sessionState { 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 { } else {
strongSelf.callContextState = contextState 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 self.audioSessionDisposable = audioSession.push(audioSessionType: .voiceCall, manualActivate: { [weak self] control in
Queue.mainQueue().async { Queue.mainQueue().async {
if let strongSelf = self { if let strongSelf = self {
if let sessionState = strongSelf.sessionState { 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 { } else {
strongSelf.audioSessionControl = control strongSelf.audioSessionControl = control
} }
@ -267,7 +280,7 @@ public final class PresentationCall {
if let strongSelf = self { if let strongSelf = self {
strongSelf.updateIsAudioSessionActive(false) strongSelf.updateIsAudioSessionActive(false)
if let sessionState = strongSelf.sessionState { 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 { } else {
strongSelf.audioSessionControl = nil strongSelf.audioSessionControl = nil
} }
@ -342,6 +355,7 @@ public final class PresentationCall {
self.audioSessionActiveDisposable?.dispose() self.audioSessionActiveDisposable?.dispose()
self.sessionStateDisposable?.dispose() self.sessionStateDisposable?.dispose()
self.ongoingContextStateDisposable?.dispose() self.ongoingContextStateDisposable?.dispose()
self.receptionDisposable?.dispose()
self.audioSessionDisposable?.dispose() self.audioSessionDisposable?.dispose()
if let dropCallKitCallTimer = self.dropCallKitCallTimer { 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 previous = self.sessionState
let previousControl = self.audioSessionControl let previousControl = self.audioSessionControl
self.sessionState = sessionState self.sessionState = sessionState
self.callContextState = callContextState self.callContextState = callContextState
self.reception = reception
self.audioSessionControl = audioSessionControl self.audioSessionControl = audioSessionControl
if previousControl != nil && audioSessionControl == nil { if previousControl != nil && audioSessionControl == nil {
@ -429,7 +444,7 @@ public final class PresentationCall {
timestamp = CFAbsoluteTimeGetCurrent() timestamp = CFAbsoluteTimeGetCurrent()
self.activeTimestamp = timestamp self.activeTimestamp = timestamp
} }
presentationState = .active(timestamp, keyVisualHash) presentationState = .active(timestamp, reception, keyVisualHash)
} }
} else { } else {
presentationState = .connecting(keyVisualHash) presentationState = .connecting(keyVisualHash)

View File

@ -42,16 +42,18 @@ public final class PresentationData: Equatable {
public let strings: PresentationStrings public let strings: PresentationStrings
public let theme: PresentationTheme public let theme: PresentationTheme
public let chatWallpaper: TelegramWallpaper public let chatWallpaper: TelegramWallpaper
public let volumeControlStatusBarIcons: (UIImage, UIImage, UIImage)
public let fontSize: PresentationFontSize public let fontSize: PresentationFontSize
public let dateTimeFormat: PresentationDateTimeFormat public let dateTimeFormat: PresentationDateTimeFormat
public let nameDisplayOrder: PresentationPersonNameOrder public let nameDisplayOrder: PresentationPersonNameOrder
public let nameSortOrder: PresentationPersonNameOrder public let nameSortOrder: PresentationPersonNameOrder
public let disableAnimations: Bool 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.strings = strings
self.theme = theme self.theme = theme
self.chatWallpaper = chatWallpaper self.chatWallpaper = chatWallpaper
self.volumeControlStatusBarIcons = volumeControlStatusBarIcons
self.fontSize = fontSize self.fontSize = fontSize
self.dateTimeFormat = dateTimeFormat self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder self.nameDisplayOrder = nameDisplayOrder
@ -60,7 +62,7 @@ public final class PresentationData: Equatable {
} }
public static func ==(lhs: PresentationData, rhs: PresentationData) -> Bool { 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 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 { private func currentDateTimeFormat() -> PresentationDateTimeFormat {
let locale = Locale.current let locale = Locale.current
let dateFormatter = DateFormatter() let dateFormatter = DateFormatter()
@ -281,7 +287,7 @@ public func currentPresentationDataAndSettings(postbox: Postbox) -> Signal<Initi
let dateTimeFormat = currentDateTimeFormat() let dateTimeFormat = currentDateTimeFormat()
let nameDisplayOrder = currentPersonNameDisplayOrder() let nameDisplayOrder = currentPersonNameDisplayOrder()
let nameSortOrder = currentPersonNameSortOrder() 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 nameDisplayOrder = currentPersonNameDisplayOrder()
let nameSortOrder = currentPersonNameSortOrder() 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 nameSortOrder = currentPersonNameSortOrder()
let themeSettings = PresentationThemeSettings.defaultSettings 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 constructive: PresentationThemeItemDisclosureAction
public let accent: PresentationThemeItemDisclosureAction public let accent: PresentationThemeItemDisclosureAction
public let warning: 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.neutral1 = neutral1
self.neutral2 = neutral2 self.neutral2 = neutral2
self.destructive = destructive self.destructive = destructive
self.constructive = constructive self.constructive = constructive
self.accent = accent self.accent = accent
self.warning = warning 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 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 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 pushControllerImpl = { [weak controller] value in
(controller?.navigationController as? NavigationController)?.replaceAllButRootController(value, animated: true) (controller?.navigationController as? NavigationController)?.replaceAllButRootController(value, animated: true)

View File

@ -8,6 +8,7 @@ final class SolidRoundedButtonNode: ASDisplayNode {
private var theme: PresentationTheme private var theme: PresentationTheme
private let buttonBackgroundNode: ASImageNode private let buttonBackgroundNode: ASImageNode
private let buttonGlossNode: SolidRoundedButtonGlossNode
private let buttonNode: HighlightTrackingButtonNode private let buttonNode: HighlightTrackingButtonNode
private let labelNode: ImmediateTextNode private let labelNode: ImmediateTextNode
@ -37,6 +38,8 @@ final class SolidRoundedButtonNode: ASDisplayNode {
self.buttonBackgroundNode.displaysAsynchronously = false self.buttonBackgroundNode.displaysAsynchronously = false
self.buttonBackgroundNode.image = generateStretchableFilledCircleImage(radius: cornerRadius, color: theme.list.itemCheckColors.fillColor) 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.buttonNode = HighlightTrackingButtonNode()
self.labelNode = ImmediateTextNode() self.labelNode = ImmediateTextNode()
@ -45,6 +48,7 @@ final class SolidRoundedButtonNode: ASDisplayNode {
super.init() super.init()
self.addSubnode(self.buttonBackgroundNode) self.addSubnode(self.buttonBackgroundNode)
self.addSubnode(self.buttonGlossNode)
self.addSubnode(self.buttonNode) self.addSubnode(self.buttonNode)
self.addSubnode(self.labelNode) self.addSubnode(self.labelNode)
@ -69,6 +73,7 @@ final class SolidRoundedButtonNode: ASDisplayNode {
let buttonSize = CGSize(width: width - inset * 2.0, height: self.buttonHeight) let buttonSize = CGSize(width: width - inset * 2.0, height: self.buttonHeight)
let buttonFrame = CGRect(origin: CGPoint(x: inset, y: 0.0), size: buttonSize) let buttonFrame = CGRect(origin: CGPoint(x: inset, y: 0.0), size: buttonSize)
transition.updateFrame(node: self.buttonBackgroundNode, frame: buttonFrame) transition.updateFrame(node: self.buttonBackgroundNode, frame: buttonFrame)
transition.updateFrame(node: self.buttonGlossNode, frame: buttonFrame)
transition.updateFrame(node: self.buttonNode, frame: buttonFrame) transition.updateFrame(node: self.buttonNode, frame: buttonFrame)
if self.title != self.labelNode.attributedText?.string { if self.title != self.labelNode.attributedText?.string {
@ -86,3 +91,104 @@ final class SolidRoundedButtonNode: ASDisplayNode {
self.pressed?() 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.containerNode.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
self.controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in 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: { }, presentController: { _, _ in }, navigationController: {
return nil return nil
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in }, 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() super.init()
self.addSubnode(self.playerNode) self.addSubnode(self.playerNode)
self.addSubnode(self.imageNode) //self.addSubnode(self.imageNode)
if let image = webpageContent.image { if let image = webpageContent.image {
self.imageNode.setSignal(chatMessagePhoto(postbox: postbox, photoReference: .webPage(webPage: WebpageReference(webPage), media: image))) self.imageNode.setSignal(chatMessagePhoto(postbox: postbox, photoReference: .webPage(webPage: WebpageReference(webPage), media: image)))