Added Smart Invert Colors support
First take on deferred permissions request
22
Images.xcassets/Settings/Permissions/CellularData.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Data@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Data@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
Images.xcassets/Settings/Permissions/CellularData.imageset/Data@2x.png
vendored
Normal file
After Width: | Height: | Size: 8.7 KiB |
BIN
Images.xcassets/Settings/Permissions/CellularData.imageset/Data@3x.png
vendored
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
Images.xcassets/Settings/Permissions/Contacts.imageset/Contacts@2x.png
vendored
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Images.xcassets/Settings/Permissions/Contacts.imageset/Contacts@3x.png
vendored
Normal file
After Width: | Height: | Size: 20 KiB |
22
Images.xcassets/Settings/Permissions/Contacts.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Contacts@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Contacts@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
9
Images.xcassets/Settings/Permissions/Contents.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
22
Images.xcassets/Settings/Permissions/Notifications.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Notifications@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Notifications@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
Images.xcassets/Settings/Permissions/Notifications.imageset/Notifications@2x.png
vendored
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
Images.xcassets/Settings/Permissions/Notifications.imageset/Notifications@3x.png
vendored
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
Images.xcassets/Settings/RecentSessionsPlaceholder.imageset/AuthSessionsEmptyIcon@2x.png
vendored
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
Images.xcassets/Settings/RecentSessionsPlaceholder.imageset/AuthSessionsEmptyIcon@3x.png
vendored
Normal file
After Width: | Height: | Size: 2.2 KiB |
22
Images.xcassets/Settings/RecentSessionsPlaceholder.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "AuthSessionsEmptyIcon@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "AuthSessionsEmptyIcon@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
@ -58,6 +58,11 @@
|
||||
09874E582107A4C300E190B8 /* VimeoEmbedImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09874E3A21075BF400E190B8 /* VimeoEmbedImplementation.swift */; };
|
||||
09874E592107BD4100E190B8 /* GenericEmbedImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09874E4021075C1700E190B8 /* GenericEmbedImplementation.swift */; };
|
||||
09AE3823214C110900850BFD /* LegacySecureIdScanController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09AE3822214C110800850BFD /* LegacySecureIdScanController.swift */; };
|
||||
09B4EE4721A6D33F00847FA6 /* RecentSessionsEmptyStateItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE4621A6D33F00847FA6 /* RecentSessionsEmptyStateItem.swift */; };
|
||||
09B4EE4D21A7B73800847FA6 /* PermissionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE4C21A7B73800847FA6 /* PermissionController.swift */; };
|
||||
09B4EE4F21A7B75D00847FA6 /* PermissionControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE4E21A7B75D00847FA6 /* PermissionControllerNode.swift */; };
|
||||
09B4EE5221A7CC3E00847FA6 /* SolidRoundedButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE5121A7CC3E00847FA6 /* SolidRoundedButtonNode.swift */; };
|
||||
09B4EE5621A8149C00847FA6 /* NotificationPermissionInfoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE5521A8149C00847FA6 /* NotificationPermissionInfoItem.swift */; };
|
||||
09C3466D2167D63A00B76780 /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C3466C2167D63A00B76780 /* Accessibility.swift */; };
|
||||
09C500242142BA6400EF253E /* ItemListWebsiteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C500232142BA6400EF253E /* ItemListWebsiteItem.swift */; };
|
||||
09C9EA33219F79F600E90146 /* ID3Artwork.m in Sources */ = {isa = PBXBuildFile; fileRef = 09C9EA31219F79F500E90146 /* ID3Artwork.m */; };
|
||||
@ -1124,6 +1129,11 @@
|
||||
09874E4221075C3000E190B8 /* VKEmbedImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VKEmbedImplementation.swift; sourceTree = "<group>"; };
|
||||
09874E4421075C3F00E190B8 /* StreamableEmbedImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamableEmbedImplementation.swift; sourceTree = "<group>"; };
|
||||
09AE3822214C110800850BFD /* LegacySecureIdScanController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacySecureIdScanController.swift; sourceTree = "<group>"; };
|
||||
09B4EE4621A6D33F00847FA6 /* RecentSessionsEmptyStateItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentSessionsEmptyStateItem.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>"; };
|
||||
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>"; };
|
||||
09C3466C2167D63A00B76780 /* Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accessibility.swift; sourceTree = "<group>"; };
|
||||
09C500232142BA6400EF253E /* ItemListWebsiteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListWebsiteItem.swift; sourceTree = "<group>"; };
|
||||
09C9EA31219F79F500E90146 /* ID3Artwork.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ID3Artwork.m; sourceTree = "<group>"; };
|
||||
@ -2332,6 +2342,27 @@
|
||||
path = TelegramUI/Resources/WebEmbed;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
09B4EE4821A6D34900847FA6 /* Recent Sessions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D05A32E91E6F143C002760B4 /* RecentSessionsController.swift */,
|
||||
09B4EE4621A6D33F00847FA6 /* RecentSessionsEmptyStateItem.swift */,
|
||||
D05A32ED1E6F25A0002760B4 /* ItemListRecentSessionItem.swift */,
|
||||
09C500232142BA6400EF253E /* ItemListWebsiteItem.swift */,
|
||||
);
|
||||
name = "Recent Sessions";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
09B4EE5721A82F5900847FA6 /* Permissions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
09B4EE4C21A7B73800847FA6 /* PermissionController.swift */,
|
||||
09B4EE4E21A7B75D00847FA6 /* PermissionControllerNode.swift */,
|
||||
09B4EE5121A7CC3E00847FA6 /* SolidRoundedButtonNode.swift */,
|
||||
);
|
||||
name = Permissions;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
09CC52A7210615AA000578F8 /* Web Embed */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -2563,6 +2594,7 @@
|
||||
D01B279C1E394A500022A4C0 /* NotificationsAndSounds.swift */,
|
||||
D0A749961E3AA25200AD786E /* NotificationSoundSelection.swift */,
|
||||
D02C81722177AC5900CD1006 /* NotificationSearchItem.swift */,
|
||||
09B4EE5521A8149C00847FA6 /* NotificationPermissionInfoItem.swift */,
|
||||
);
|
||||
name = Notifications;
|
||||
sourceTree = "<group>";
|
||||
@ -4155,6 +4187,7 @@
|
||||
D0430AFE1FF456F400A35ADD /* Web */,
|
||||
D0F8C3952017747300236FC5 /* Feed */,
|
||||
0941A99E210B053300EBE194 /* Open In */,
|
||||
09B4EE5721A82F5900847FA6 /* Permissions */,
|
||||
);
|
||||
name = Controllers;
|
||||
sourceTree = "<group>";
|
||||
@ -4560,11 +4593,9 @@
|
||||
D0FA0AC21E7742CE005BB9B7 /* Privacy and Security */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
09B4EE4821A6D34900847FA6 /* Recent Sessions */,
|
||||
D05A32DD1E6F0097002760B4 /* PrivacyAndSecurityController.swift */,
|
||||
D08984EF2114AE0C00918162 /* DataPrivacySettingsController.swift */,
|
||||
D05A32ED1E6F25A0002760B4 /* ItemListRecentSessionItem.swift */,
|
||||
09C500232142BA6400EF253E /* ItemListWebsiteItem.swift */,
|
||||
D05A32E91E6F143C002760B4 /* RecentSessionsController.swift */,
|
||||
D05A32EB1E6F1462002760B4 /* BlockedPeersController.swift */,
|
||||
D05B724C1E720393000BD3AD /* SelectivePrivacySettingsController.swift */,
|
||||
D0EF40DC1E72F00E000DFCD4 /* SelectivePrivacySettingsPeersController.swift */,
|
||||
@ -4957,6 +4988,7 @@
|
||||
D0471B5C1EFEB4F30074D609 /* BotPaymentFieldItemNode.swift in Sources */,
|
||||
D0C27B3D1F4B454800A4E170 /* InstantPagePlayableVideoNode.swift in Sources */,
|
||||
D0EC6CD11EB9F58800EBF1C3 /* UrlHandling.swift in Sources */,
|
||||
09B4EE4D21A7B73800847FA6 /* PermissionController.swift in Sources */,
|
||||
D0FC4FBB1F751E8900B7443F /* SelectablePeerNode.swift in Sources */,
|
||||
D0E9BAD21F0573C000F079A4 /* STPToken.m in Sources */,
|
||||
D0EC6CD31EB9F58800EBF1C3 /* GenerateTextEntities.swift in Sources */,
|
||||
@ -5239,6 +5271,7 @@
|
||||
D0EC6D631EB9F58800EBF1C3 /* ContactListActionItem.swift in Sources */,
|
||||
D0EC6D641EB9F58800EBF1C3 /* ContactsPeerItem.swift in Sources */,
|
||||
D0B85C1E1FF6F76600E795B4 /* AuthorizationSequencePasswordRecoveryControllerNode.swift in Sources */,
|
||||
09B4EE4721A6D33F00847FA6 /* RecentSessionsEmptyStateItem.swift in Sources */,
|
||||
D00BED201F73F60F00922292 /* ShareSearchContainerNode.swift in Sources */,
|
||||
D0CE8CEC1F6FCCA300AA2DB0 /* TransformImageArguments.swift in Sources */,
|
||||
D0EC6D661EB9F58800EBF1C3 /* ContactsSectionHeaderAccessoryItem.swift in Sources */,
|
||||
@ -5268,6 +5301,7 @@
|
||||
D0EC6D731EB9F58800EBF1C3 /* AuthorizationSequenceSignUpController.swift in Sources */,
|
||||
0979787C210642CB0077D77F /* WebEmbedPlayerNode.swift in Sources */,
|
||||
D0C12EB01F9A8D1300600BB2 /* ListMessageDateHeader.swift in Sources */,
|
||||
09B4EE5221A7CC3E00847FA6 /* SolidRoundedButtonNode.swift in Sources */,
|
||||
D0E9BA5D1F055A3300F079A4 /* STPBINRange.m in Sources */,
|
||||
D0EC6D741EB9F58800EBF1C3 /* AuthorizationSequenceSignUpControllerNode.swift in Sources */,
|
||||
D0EC6D751EB9F58800EBF1C3 /* TelegramRootController.swift in Sources */,
|
||||
@ -5348,6 +5382,7 @@
|
||||
D0147BA7206E8B4F00E40378 /* SecureIdAuthAcceptNode.swift in Sources */,
|
||||
D0E8174E2011FC3800B82BBB /* ChatMessageEventLogPreviousDescriptionContentNode.swift in Sources */,
|
||||
D0EC6D981EB9F58900EBF1C3 /* ChatMessageItemView.swift in Sources */,
|
||||
09B4EE4F21A7B75D00847FA6 /* PermissionControllerNode.swift in Sources */,
|
||||
09D304152173C0E900C00567 /* WatchManager.swift in Sources */,
|
||||
9F06830921A404AB001D8EDB /* NotificationExceptionControllerNode.swift in Sources */,
|
||||
D039FB1921711B5D00BD1BAD /* PlatformVideoContent.swift in Sources */,
|
||||
@ -5455,6 +5490,7 @@
|
||||
D0BCC3D2203F0A6C008126C2 /* StringForMessageTimestampStatus.swift in Sources */,
|
||||
D0EC6DCF1EB9F58900EBF1C3 /* HorizontalStickerGridItem.swift in Sources */,
|
||||
D0EC6DD01EB9F58900EBF1C3 /* HashtagChatInputContextPanelNode.swift in Sources */,
|
||||
09B4EE5621A8149C00847FA6 /* NotificationPermissionInfoItem.swift in Sources */,
|
||||
D0EC6DD11EB9F58900EBF1C3 /* HashtagChatInputPanelItem.swift in Sources */,
|
||||
D0EC6DD21EB9F58900EBF1C3 /* MentionChatInputContextPanelNode.swift in Sources */,
|
||||
D00701A22029F6D0006B9E34 /* TGMimeTypeMap.m in Sources */,
|
||||
|
@ -1,6 +1,14 @@
|
||||
import SwiftSignalKit
|
||||
import UIKit
|
||||
|
||||
func smartInvertColorsEnabled() -> Bool {
|
||||
if #available(iOSApplicationExtension 11.0, *), UIAccessibilityIsInvertColorsEnabled() {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func reduceMotionEnabled() -> Signal<Bool, NoError> {
|
||||
return Signal { subscriber in
|
||||
subscriber.putNext(UIAccessibility.isReduceMotionEnabled)
|
||||
|
@ -177,6 +177,14 @@ public final class AvatarNode: ASDisplayNode {
|
||||
self.addSubnode(self.imageNode)
|
||||
}
|
||||
|
||||
override public func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
if #available(iOSApplicationExtension 11.0, *), !self.isLayerBacked {
|
||||
self.view.accessibilityIgnoresInvertColors = true
|
||||
}
|
||||
}
|
||||
|
||||
override public var frame: CGRect {
|
||||
get {
|
||||
return super.frame
|
||||
|
@ -204,7 +204,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
||||
self.highlightedBackgroundNode.isLayerBacked = true
|
||||
|
||||
self.avatarNode = AvatarNode(font: avatarFont)
|
||||
self.avatarNode.isLayerBacked = true
|
||||
self.avatarNode.isLayerBacked = !smartInvertColorsEnabled()
|
||||
|
||||
self.titleNode = TextNode()
|
||||
self.statusNode = TextNode()
|
||||
|
@ -106,7 +106,7 @@ private final class ChatContextResultPeekNode: ASDisplayNode, PeekControllerCont
|
||||
|
||||
self.imageNode = TransformImageNode()
|
||||
self.imageNode.contentAnimations = [.subsequentUpdates]
|
||||
self.imageNode.isLayerBacked = true
|
||||
self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
|
||||
self.imageNode.displaysAsynchronously = false
|
||||
|
||||
var timebase: CMTimebase?
|
||||
|
@ -90,11 +90,11 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
||||
|
||||
func updateLayout(transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
let buttonSize = CGSize(width: 38.0, height: 38.0)
|
||||
let completeSize = CGSize(width: buttonSize.width, height: buttonSize.height * 2.0 + 6.0)
|
||||
let completeSize = CGSize(width: buttonSize.width, height: buttonSize.height * 2.0 + 12.0)
|
||||
var mentionsOffset: CGFloat = 0.0
|
||||
|
||||
if self.displayDownButton {
|
||||
mentionsOffset = buttonSize.height + 8.0
|
||||
mentionsOffset = buttonSize.height + 12.0
|
||||
transition.updateAlpha(node: self.downButton, alpha: 1.0)
|
||||
transition.updateTransformScale(node: self.downButton, scale: 1.0)
|
||||
} else {
|
||||
|
@ -218,7 +218,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
|
||||
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.maximumNumberOfLines = 10
|
||||
self.textNode.linkHighlightColor = UIColor(white: 1.0, alpha: 0.4)
|
||||
self.textNode.linkHighlightColor = UIColor(rgb: 0x5ac8fa, alpha: 0.2)
|
||||
|
||||
self.authorNameNode = ASTextNode()
|
||||
self.authorNameNode.maximumNumberOfLines = 1
|
||||
|
@ -72,13 +72,13 @@ final class ChatMediaInputPeerSpecificItemNode: ListViewItemNode {
|
||||
|
||||
private let stickerFetchedDisposable = MetaDisposable()
|
||||
|
||||
init() {
|
||||
init() {
|
||||
self.highlightNode = ASImageNode()
|
||||
self.highlightNode.isLayerBacked = true
|
||||
self.highlightNode.isHidden = true
|
||||
|
||||
self.avatarNode = AvatarNode(font: avatarFont)
|
||||
self.avatarNode.isLayerBacked = true
|
||||
self.avatarNode.isLayerBacked = !smartInvertColorsEnabled()
|
||||
self.avatarNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||
|
||||
let imageSize = CGSize(width: 32.0, height: 32.0)
|
||||
|
@ -80,7 +80,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
||||
self.highlightNode.isHidden = true
|
||||
|
||||
self.imageNode = TransformImageNode()
|
||||
self.imageNode.isLayerBacked = true
|
||||
self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
|
||||
|
||||
self.highlightNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - highlightSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - highlightSize.height) / 2.0)), size: highlightSize)
|
||||
|
||||
|
@ -249,7 +249,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
|
||||
self.inlineImageNode = TransformImageNode()
|
||||
self.inlineImageNode.contentAnimations = [.subsequentUpdates]
|
||||
self.inlineImageNode.isLayerBacked = true
|
||||
self.inlineImageNode.isLayerBacked = !smartInvertColorsEnabled()
|
||||
self.inlineImageNode.displaysAsynchronously = false
|
||||
|
||||
self.statusNode = ChatMessageDateAndStatusNode()
|
||||
|
@ -42,13 +42,15 @@ final class ChatMessageAvatarAccessoryItemNode: ListViewAccessoryItemNode {
|
||||
let avatarNode: AvatarNode
|
||||
|
||||
override init() {
|
||||
let isLayerBacked = !smartInvertColorsEnabled()
|
||||
|
||||
self.avatarNode = AvatarNode(font: avatarFont)
|
||||
self.avatarNode.isLayerBacked = true
|
||||
self.avatarNode.isLayerBacked = isLayerBacked
|
||||
self.avatarNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 38.0, height: 38.0))
|
||||
|
||||
super.init()
|
||||
|
||||
self.isLayerBacked = true
|
||||
self.isLayerBacked = isLayerBacked
|
||||
self.addSubnode(self.avatarNode)
|
||||
}
|
||||
|
||||
|
@ -38,13 +38,15 @@ final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
|
||||
private var pulseImage: UIImage?
|
||||
|
||||
override init() {
|
||||
let isLayerBacked = !smartInvertColorsEnabled()
|
||||
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.displaysAsynchronously = false
|
||||
self.backgroundNode.displayWithoutProcessing = true
|
||||
|
||||
self.avatarNode = AvatarNode(font: avatarFont)
|
||||
self.avatarNode.isLayerBacked = true
|
||||
self.avatarNode.isLayerBacked = isLayerBacked
|
||||
|
||||
self.pulseNode = ASImageNode()
|
||||
self.pulseNode.isLayerBacked = true
|
||||
@ -54,7 +56,7 @@ final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
|
||||
|
||||
super.init()
|
||||
|
||||
self.isLayerBacked = true
|
||||
self.isLayerBacked = isLayerBacked
|
||||
|
||||
self.addSubnode(self.pulseNode)
|
||||
self.addSubnode(self.backgroundNode)
|
||||
|
@ -173,7 +173,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
if let applyImage = applyImage {
|
||||
let imageNode = applyImage()
|
||||
if node.imageNode == nil {
|
||||
imageNode.isLayerBacked = true
|
||||
imageNode.isLayerBacked = !smartInvertColorsEnabled()
|
||||
node.addSubnode(imageNode)
|
||||
node.imageNode = imageNode
|
||||
}
|
||||
|
@ -2,11 +2,14 @@ import Foundation
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
import Display
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import Photos
|
||||
import CoreLocation
|
||||
import Contacts
|
||||
import AddressBook
|
||||
import UserNotifications
|
||||
import CoreTelephony
|
||||
|
||||
import LegacyComponents
|
||||
|
||||
@ -33,9 +36,13 @@ public enum DeviceAccessSubject {
|
||||
case mediaLibrary(DeviceAccessMediaLibrarySubject)
|
||||
case location(DeviceAccessLocationSubject)
|
||||
case contacts
|
||||
case notifications
|
||||
case siri
|
||||
case cellularData
|
||||
}
|
||||
|
||||
private enum AccessType {
|
||||
public enum AccessType {
|
||||
case notDetermined
|
||||
case allowed
|
||||
case denied
|
||||
case restricted
|
||||
@ -49,11 +56,109 @@ public final class DeviceAccess {
|
||||
return self.contactsPromise.get()
|
||||
}
|
||||
|
||||
private static let notificationsPromise = Promise<AccessType?>(nil)
|
||||
|
||||
public static func isMicrophoneAccessAuthorized() -> Bool? {
|
||||
return AVAudioSession.sharedInstance().recordPermission() == .granted
|
||||
}
|
||||
|
||||
public static func authorizeAccess(to subject: DeviceAccessSubject, presentationData: PresentationData, present: @escaping (ViewController, Any?) -> Void, openSettings: @escaping () -> Void, displayNotificatoinFromBackground: @escaping (String) -> Void = { _ in }, _ completion: @escaping (Bool) -> Void) {
|
||||
public static func authorizationStatus(account: Account, subject: DeviceAccessSubject) -> Signal<AccessType, NoError> {
|
||||
switch subject {
|
||||
case .notifications:
|
||||
let status = Signal<AccessType, NoError> { subscriber in
|
||||
if #available(iOSApplicationExtension 10.0, *) {
|
||||
UNUserNotificationCenter.current().getNotificationSettings(completionHandler: { settings in
|
||||
switch settings.authorizationStatus {
|
||||
case .authorized:
|
||||
subscriber.putNext(.allowed)
|
||||
case .denied:
|
||||
subscriber.putNext(.denied)
|
||||
case .notDetermined:
|
||||
subscriber.putNext(.notDetermined)
|
||||
default:
|
||||
subscriber.putNext(.notDetermined)
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
} else {
|
||||
subscriber.putNext(.notDetermined)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
return EmptyDisposable
|
||||
}
|
||||
return account.telegramApplicationContext.applicationBindings.applicationInForeground
|
||||
|> distinctUntilChanged
|
||||
|> mapToSignal { inForeground -> Signal<AccessType, NoError> in
|
||||
return status
|
||||
}
|
||||
case .contacts:
|
||||
let status = Signal<AccessType, NoError> { subscriber in
|
||||
if #available(iOSApplicationExtension 9.0, *) {
|
||||
switch CNContactStore.authorizationStatus(for: .contacts) {
|
||||
case .notDetermined:
|
||||
subscriber.putNext(.notDetermined)
|
||||
case .authorized:
|
||||
subscriber.putNext(.allowed)
|
||||
default:
|
||||
subscriber.putNext(.denied)
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
} else {
|
||||
switch ABAddressBookGetAuthorizationStatus() {
|
||||
case .notDetermined:
|
||||
subscriber.putNext(.notDetermined)
|
||||
case .authorized:
|
||||
subscriber.putNext(.allowed)
|
||||
default:
|
||||
subscriber.putNext(.denied)
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
return EmptyDisposable
|
||||
}
|
||||
return status
|
||||
|> then(self.contacts
|
||||
|> mapToSignal { authorized -> Signal<AccessType, NoError> in
|
||||
if let authorized = authorized {
|
||||
return .single(authorized ? .allowed : .denied)
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
})
|
||||
case .cellularData:
|
||||
return Signal { subscriber in
|
||||
if #available(iOSApplicationExtension 9.0, *) {
|
||||
func statusForCellularState(_ state: CTCellularDataRestrictedState) -> AccessType? {
|
||||
switch state {
|
||||
case .restricted:
|
||||
return .denied
|
||||
case .notRestricted:
|
||||
return .allowed
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
let cellState = CTCellularData.init()
|
||||
if let status = statusForCellularState(cellState.restrictedState) {
|
||||
subscriber.putNext(status)
|
||||
}
|
||||
cellState.cellularDataRestrictionDidUpdateNotifier = { restrictedState in
|
||||
if let status = statusForCellularState(restrictedState) {
|
||||
subscriber.putNext(status)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
subscriber.putNext(.notDetermined)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
return EmptyDisposable
|
||||
}
|
||||
default:
|
||||
return .single(.notDetermined)
|
||||
}
|
||||
}
|
||||
|
||||
public static func authorizeAccess(to subject: DeviceAccessSubject, presentationData: PresentationData, present: @escaping (ViewController, Any?) -> Void, openSettings: @escaping () -> Void, displayNotificationFromBackground: @escaping (String) -> Void = { _ in }, _ completion: @escaping (Bool) -> Void) {
|
||||
switch subject {
|
||||
case .camera:
|
||||
let status = PGCamera.cameraAuthorizationStatus()
|
||||
@ -108,7 +213,7 @@ public final class DeviceAccess {
|
||||
openSettings()
|
||||
})]), nil)
|
||||
if case .voiceCall = microphoneSubject {
|
||||
displayNotificatoinFromBackground(text)
|
||||
displayNotificationFromBackground(text)
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -231,6 +336,8 @@ public final class DeviceAccess {
|
||||
}
|
||||
}
|
||||
})
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -559,6 +559,7 @@ final class ContactListNode: ASDisplayNode {
|
||||
|
||||
var activateSearch: (() -> Void)?
|
||||
var openPeer: ((ContactListPeer) -> Void)?
|
||||
var openPrivacyPolicy: (() -> Void)?
|
||||
|
||||
private let previousEntries = Atomic<[ContactListNodeEntry]?>(value: nil)
|
||||
private let disposable = MetaDisposable()
|
||||
@ -567,6 +568,8 @@ final class ContactListNode: ASDisplayNode {
|
||||
private var presentationDataDisposable: Disposable?
|
||||
private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, Bool)>
|
||||
|
||||
private let authorizationNode: PermissionControllerNode
|
||||
|
||||
init(account: Account, presentation: ContactListPresentation, filters: [ContactListFilter] = [.excludeSelf], selectionState: ContactListNodeGroupSelectionState? = nil) {
|
||||
self.account = account
|
||||
self.presentation = presentation
|
||||
@ -579,6 +582,9 @@ final class ContactListNode: ASDisplayNode {
|
||||
|
||||
self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings, self.presentationData.dateTimeFormat, self.presentationData.nameSortOrder, self.presentationData.nameDisplayOrder, self.presentationData.disableAnimations))
|
||||
|
||||
self.authorizationNode = PermissionControllerNode(theme: defaultLightAuthorizationTheme, strings: self.presentationData.strings)
|
||||
self.authorizationNode.updateData(subject: .contacts, currentStatus: .denied)
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
|
||||
@ -586,8 +592,9 @@ final class ContactListNode: ASDisplayNode {
|
||||
self.selectionStateValue = selectionState
|
||||
self.selectionStatePromise.set(.single(selectionState))
|
||||
|
||||
self.addSubnode(self.listNode)
|
||||
|
||||
//self.addSubnode(self.listNode)
|
||||
self.addSubnode(self.authorizationNode)
|
||||
|
||||
let processingQueue = Queue()
|
||||
let previousEntries = Atomic<[ContactListNodeEntry]?>(value: nil)
|
||||
|
||||
@ -808,6 +815,13 @@ final class ContactListNode: ASDisplayNode {
|
||||
fixSearchableListNodeScrolling(strongSelf.listNode)
|
||||
}
|
||||
|
||||
self.authorizationNode.allow = { [weak self] in
|
||||
self?.account.telegramApplicationContext.applicationBindings.openSettings()
|
||||
}
|
||||
self.authorizationNode.openPrivacyPolicy = { [weak self] in
|
||||
self?.openPrivacyPolicy?()
|
||||
}
|
||||
|
||||
self.enableUpdates = true
|
||||
}
|
||||
|
||||
@ -823,6 +837,11 @@ final class ContactListNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
let result = super.hitTest(point, with: event)
|
||||
return result
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
var insets = layout.insets(options: [.input])
|
||||
insets.left += layout.safeInsets.left
|
||||
@ -857,6 +876,10 @@ final class ContactListNode: ASDisplayNode {
|
||||
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
|
||||
let sublayout = layout.addedInsets(insets: UIEdgeInsetsMake(0.0, 0.0, 40.0, 0.0))
|
||||
transition.updateFrame(node: self.authorizationNode, frame: self.bounds)
|
||||
self.authorizationNode.containerLayoutUpdated(sublayout, navigationBarHeight: 0.0, transition: transition)
|
||||
|
||||
if !self.hasValidLayout {
|
||||
self.hasValidLayout = true
|
||||
self.dequeueTransitions()
|
||||
|
@ -21,7 +21,7 @@ public class ContactsController: ViewController {
|
||||
|
||||
private var presentationData: PresentationData
|
||||
private var presentationDataDisposable: Disposable?
|
||||
private var contactsAccessDisposable: Disposable?
|
||||
private var authorizationDisposable: Disposable?
|
||||
|
||||
public init(account: Account) {
|
||||
self.account = account
|
||||
@ -49,18 +49,25 @@ public class ContactsController: ViewController {
|
||||
}
|
||||
|
||||
self.presentationDataDisposable = (account.telegramApplicationContext.presentationData
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
let previousTheme = strongSelf.presentationData.theme
|
||||
let previousStrings = strongSelf.presentationData.strings
|
||||
|
||||
strongSelf.presentationData = presentationData
|
||||
|
||||
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
|
||||
strongSelf.updateThemeAndStrings()
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
let previousTheme = strongSelf.presentationData.theme
|
||||
let previousStrings = strongSelf.presentationData.strings
|
||||
|
||||
strongSelf.presentationData = presentationData
|
||||
|
||||
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
|
||||
strongSelf.updateThemeAndStrings()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
self.authorizationDisposable = (DeviceAccess.authorizationStatus(account: account, subject: .contacts)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
if let strongSelf = self {
|
||||
strongSelf.tabBarItem.badgeValue = status != .allowed ? "!" : nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
@ -69,7 +76,7 @@ public class ContactsController: ViewController {
|
||||
|
||||
deinit {
|
||||
self.presentationDataDisposable?.dispose()
|
||||
self.contactsAccessDisposable?.dispose()
|
||||
self.authorizationDisposable?.dispose()
|
||||
}
|
||||
|
||||
private func updateThemeAndStrings() {
|
||||
@ -107,6 +114,12 @@ public class ContactsController: ViewController {
|
||||
}*/
|
||||
}
|
||||
|
||||
self.contactsNode.contactListNode.openPrivacyPolicy = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
openExternalUrl(account: strongSelf.account, context: .generic, url: "https://telegram.org/privacy", forceExternal: true, presentationData: strongSelf.presentationData, applicationContext: strongSelf.account.telegramApplicationContext, navigationController: strongSelf.navigationController as? NavigationController, dismissInput: {})
|
||||
}
|
||||
}
|
||||
|
||||
self.contactsNode.contactListNode.activateSearch = { [weak self] in
|
||||
self?.activateSearch()
|
||||
}
|
||||
|
@ -316,7 +316,7 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
self.highlightedBackgroundNode.isLayerBacked = true
|
||||
|
||||
self.avatarNode = AvatarNode(font: avatarFont)
|
||||
self.avatarNode.isLayerBacked = true
|
||||
self.avatarNode.isLayerBacked = !smartInvertColorsEnabled()
|
||||
|
||||
self.titleNode = TextNode()
|
||||
self.statusNode = TextNode()
|
||||
|
@ -305,8 +305,6 @@ class GalleryController: ViewController {
|
||||
private var performAction: (GalleryControllerInteractionTapAction) -> Void
|
||||
private var openActionOptions: (GalleryControllerInteractionTapAction) -> Void
|
||||
|
||||
private let resolveDisposable = MetaDisposable()
|
||||
|
||||
init(account: Account, source: GalleryControllerItemSource, invertItemOrder: Bool = false, streamSingleVideo: Bool = false, synchronousLoad: Bool = false, replaceRootController: @escaping (ViewController, ValuePromise<Bool>?) -> Void, baseNavigationController: NavigationController?, actionInteraction: GalleryControllerActionInteraction? = nil) {
|
||||
self.account = account
|
||||
self.replaceRootController = replaceRootController
|
||||
@ -686,7 +684,6 @@ class GalleryController: ViewController {
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
self.resolveDisposable.dispose()
|
||||
self.centralItemAttributesDisposable.dispose()
|
||||
if let hiddenMediaManagerIndex = self.hiddenMediaManagerIndex, let mediaManager = self.account.telegramApplicationContext.mediaManager {
|
||||
mediaManager.galleryHiddenMediaManager.removeSource(hiddenMediaManagerIndex)
|
||||
|
@ -181,9 +181,9 @@ class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGestureRecog
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
/*let recognizer = SwipeToDismissGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
|
||||
recognizer.delegate = self
|
||||
self.view.addGestureRecognizer(recognizer)*/
|
||||
if #available(iOSApplicationExtension 11.0, *), !self.isLayerBacked {
|
||||
self.view.accessibilityIgnoresInvertColors = true
|
||||
}
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
|
@ -116,7 +116,7 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode {
|
||||
self.bottomStripeNode.isLayerBacked = true
|
||||
|
||||
self.imageNode = TransformImageNode()
|
||||
self.imageNode.isLayerBacked = true
|
||||
self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
|
||||
|
||||
self.notFoundNode = ASImageNode()
|
||||
self.notFoundNode.isLayerBacked = true
|
||||
|
@ -148,7 +148,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
|
||||
|
||||
self.imageNode = TransformImageNode()
|
||||
self.imageNode.contentAnimations = [.subsequentUpdates]
|
||||
self.imageNode.isLayerBacked = true
|
||||
self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
|
||||
self.imageNode.displaysAsynchronously = false
|
||||
|
||||
var timebase: CMTimebase?
|
||||
@ -350,7 +350,8 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
|
||||
}
|
||||
}
|
||||
|
||||
let progressFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((nodeLayout.contentSize.width - 37) / 2), y: floorToScreenPixels((nodeLayout.contentSize.height - 37) / 2)), size: CGSize(width: 37, height: 37))
|
||||
let progressSize = CGSize(width: 24.0, height: 24.0)
|
||||
let progressFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((nodeLayout.contentSize.width - progressSize.width) / 2.0), y: floorToScreenPixels((nodeLayout.contentSize.height - progressSize.height) / 2.0)), size: progressSize)
|
||||
|
||||
strongSelf.statusNode.removeFromSupernode()
|
||||
strongSelf.addSubnode(strongSelf.statusNode)
|
||||
|
@ -236,7 +236,7 @@ private enum InstalledStickerPacksEntry: ItemListNodeEntry {
|
||||
arguments.openSuggestOptions()
|
||||
})
|
||||
case let .trending(theme, text, count):
|
||||
return ItemListDisclosureItem(theme: theme, title: text, label: count == 0 ? "" : "\(count)", labelStyle: .badge, sectionId: self.section, style: .blocks, action: {
|
||||
return ItemListDisclosureItem(theme: theme, title: text, label: count == 0 ? "" : "\(count)", labelStyle: .badge(theme.list.itemAccentColor), sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openFeatured()
|
||||
})
|
||||
case let .masks(theme, text):
|
||||
|
@ -36,7 +36,7 @@ final class InstantPageGalleryFooterContentNode: GalleryFooterContentNode {
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.maximumNumberOfLines = 10
|
||||
self.textNode.insets = UIEdgeInsets(top: 8.0, left: 0.0, bottom: 8.0, right: 0.0)
|
||||
self.textNode.linkHighlightColor = UIColor(white: 1.0, alpha: 0.4)
|
||||
self.textNode.linkHighlightColor = UIColor(rgb: 0x5ac8fa, alpha: 0.2)
|
||||
|
||||
super.init()
|
||||
|
||||
|
@ -93,15 +93,17 @@ final class ItemListControllerTabBarItem: Equatable {
|
||||
let title: String
|
||||
let image: UIImage?
|
||||
let selectedImage: UIImage?
|
||||
let badgeValue: String?
|
||||
|
||||
init(title: String, image: UIImage?, selectedImage: UIImage?) {
|
||||
init(title: String, image: UIImage?, selectedImage: UIImage?, badgeValue: String? = nil) {
|
||||
self.title = title
|
||||
self.image = image
|
||||
self.selectedImage = selectedImage
|
||||
self.badgeValue = badgeValue
|
||||
}
|
||||
|
||||
static func ==(lhs: ItemListControllerTabBarItem, rhs: ItemListControllerTabBarItem) -> Bool {
|
||||
return lhs.title == rhs.title && lhs.image === rhs.image && lhs.selectedImage === rhs.selectedImage
|
||||
return lhs.title == rhs.title && lhs.image === rhs.image && lhs.selectedImage === rhs.selectedImage && lhs.badgeValue == rhs.badgeValue
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ enum ItemListDisclosureStyle {
|
||||
|
||||
enum ItemListDisclosureLabelStyle {
|
||||
case text
|
||||
case badge
|
||||
case badge(UIColor)
|
||||
case color(UIColor)
|
||||
}
|
||||
|
||||
@ -183,9 +183,11 @@ class ItemListDisclosureItemNode: ListViewItemNode {
|
||||
var updatedLabelBadgeImage: UIImage?
|
||||
var updatedLabelImage: UIImage?
|
||||
|
||||
var hasBadge = false
|
||||
if case .badge = item.labelStyle {
|
||||
hasBadge = item.label.count > 0
|
||||
var badgeColor: UIColor?
|
||||
if case let .badge(color) = item.labelStyle {
|
||||
if item.label.count > 0 {
|
||||
badgeColor = color
|
||||
}
|
||||
}
|
||||
if case let .color(color) = item.labelStyle {
|
||||
var updatedColor = true
|
||||
@ -201,11 +203,11 @@ class ItemListDisclosureItemNode: ListViewItemNode {
|
||||
if currentItem?.theme !== item.theme {
|
||||
updatedTheme = item.theme
|
||||
updateArrowImage = PresentationResourcesItemList.disclosureArrowImage(item.theme)
|
||||
if hasBadge {
|
||||
updatedLabelBadgeImage = generateStretchableFilledCircleImage(diameter: badgeDiameter, color: item.theme.list.itemAccentColor)
|
||||
if let badgeColor = badgeColor {
|
||||
updatedLabelBadgeImage = generateStretchableFilledCircleImage(diameter: badgeDiameter, color: badgeColor)
|
||||
}
|
||||
} else if hasBadge && !currentHasBadge {
|
||||
updatedLabelBadgeImage = generateStretchableFilledCircleImage(diameter: badgeDiameter, color: item.theme.list.itemAccentColor)
|
||||
} else if let badgeColor = badgeColor, !currentHasBadge {
|
||||
updatedLabelBadgeImage = generateStretchableFilledCircleImage(diameter: badgeDiameter, color: badgeColor)
|
||||
}
|
||||
|
||||
var updateIcon = false
|
||||
@ -336,26 +338,26 @@ class ItemListDisclosureItemNode: ListViewItemNode {
|
||||
|
||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleLayout.size)
|
||||
|
||||
let labelY: CGFloat
|
||||
if case .badge = item.labelStyle {
|
||||
labelY = 13.0
|
||||
} else {
|
||||
labelY = 11.0
|
||||
}
|
||||
strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: params.width - rightInset - labelLayout.size.width, y: labelY), size: labelLayout.size)
|
||||
|
||||
if let updateBadgeImage = updatedLabelBadgeImage {
|
||||
if strongSelf.labelBadgeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.labelBadgeNode, belowSubnode: strongSelf.labelNode)
|
||||
}
|
||||
strongSelf.labelBadgeNode.image = updateBadgeImage
|
||||
}
|
||||
if !hasBadge && strongSelf.labelBadgeNode.supernode != nil {
|
||||
if badgeColor == nil && strongSelf.labelBadgeNode.supernode != nil {
|
||||
strongSelf.labelBadgeNode.image = nil
|
||||
strongSelf.labelBadgeNode.removeFromSupernode()
|
||||
}
|
||||
strongSelf.labelBadgeNode.frame = CGRect(origin: CGPoint(x: params.width - rightInset - labelLayout.size.width - 5.0, y: 12.0), size: CGSize(width: max(badgeDiameter, labelLayout.size.width + 10.0), height: badgeDiameter))
|
||||
|
||||
let badgeWidth = max(badgeDiameter, labelLayout.size.width + 10.0)
|
||||
strongSelf.labelBadgeNode.frame = CGRect(origin: CGPoint(x: params.width - rightInset - badgeWidth, y: 12.0), size: CGSize(width: badgeWidth, height: badgeDiameter))
|
||||
|
||||
if case .badge = item.labelStyle {
|
||||
strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: params.width - rightInset - badgeWidth + (badgeWidth - labelLayout.size.width) / 2.0, y: 13.0), size: labelLayout.size)
|
||||
} else {
|
||||
strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: params.width - rightInset - labelLayout.size.width, y: 11.0), size: labelLayout.size)
|
||||
}
|
||||
|
||||
if case .color = item.labelStyle {
|
||||
if let updatedLabelImage = updatedLabelImage {
|
||||
strongSelf.labelImageNode.image = updatedLabelImage
|
||||
|
@ -200,7 +200,7 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
self.bottomStripeNode.isLayerBacked = true
|
||||
|
||||
self.avatarNode = AvatarNode(font: avatarFont)
|
||||
self.avatarNode.isLayerBacked = true
|
||||
self.avatarNode.isLayerBacked = !smartInvertColorsEnabled()
|
||||
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
|
@ -162,7 +162,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
|
||||
self.bottomStripeNode.isLayerBacked = true
|
||||
|
||||
self.imageNode = TransformImageNode()
|
||||
self.imageNode.isLayerBacked = true
|
||||
self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
|
||||
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
|
@ -59,7 +59,7 @@ public class LocationBroadcastActionSheetItemNode: ActionSheetItemNode {
|
||||
self.button = HighlightTrackingButton()
|
||||
|
||||
self.avatarNode = AvatarNode(font: avatarFont)
|
||||
self.avatarNode.isLayerBacked = true
|
||||
self.avatarNode.isLayerBacked = !smartInvertColorsEnabled()
|
||||
|
||||
self.label = ImmediateTextNode()
|
||||
self.label.isUserInteractionEnabled = false
|
||||
|
@ -220,8 +220,8 @@ final class MultiplexedVideoNode: UIScrollView, UIScrollViewDelegate {
|
||||
self.visibleThumbnailLayers[item.fileReference.media.fileId] = thumbnailLayer
|
||||
}
|
||||
|
||||
let progessSize = CGSize(width: 24.0, height: 24.0)
|
||||
let progressFrame = CGRect(origin: CGPoint(x: item.frame.midX - progessSize.width / 2.0, y: item.frame.midY - progessSize.height / 2.0), size: progessSize)
|
||||
let progressSize = CGSize(width: 24.0, height: 24.0)
|
||||
let progressFrame = CGRect(origin: CGPoint(x: item.frame.midX - progressSize.width / 2.0, y: item.frame.midY - progressSize.height / 2.0), size: progressSize)
|
||||
|
||||
let updatedStatusSignal = account.postbox.mediaBox.resourceStatus(item.fileReference.media.resource)
|
||||
|
||||
|
207
TelegramUI/NotificationPermissionInfoItem.swift
Normal file
@ -0,0 +1,207 @@
|
||||
import Foundation
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
|
||||
class NotificationPermissionInfoItem: ListViewItem, ItemListItem {
|
||||
let selectable: Bool = false
|
||||
let sectionId: ItemListSectionId
|
||||
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.sectionId = sectionId
|
||||
}
|
||||
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, () -> Void)) -> Void) {
|
||||
async {
|
||||
let node = NotificationPermissionInfoItemNode()
|
||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? NotificationPermissionInfoItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
Queue.mainQueue().async {
|
||||
completion(layout, {
|
||||
apply()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let titleFont = Font.semibold(17.0)
|
||||
private let textFont = Font.regular(16.0)
|
||||
private let badgeFont = Font.regular(15.0)
|
||||
|
||||
class NotificationPermissionInfoItemNode: ListViewItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
|
||||
let badgeNode: ASImageNode
|
||||
let labelNode: TextNode
|
||||
let titleNode: TextNode
|
||||
let textNode: TextNode
|
||||
|
||||
private var item: NotificationPermissionInfoItem?
|
||||
|
||||
override var canBeSelected: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.backgroundColor = .white
|
||||
|
||||
self.topStripeNode = ASDisplayNode()
|
||||
self.topStripeNode.isLayerBacked = true
|
||||
|
||||
self.bottomStripeNode = ASDisplayNode()
|
||||
self.bottomStripeNode.isLayerBacked = true
|
||||
|
||||
self.badgeNode = ASImageNode()
|
||||
self.badgeNode.displayWithoutProcessing = true
|
||||
self.badgeNode.displaysAsynchronously = false
|
||||
self.badgeNode.isLayerBacked = true
|
||||
|
||||
self.labelNode = TextNode()
|
||||
self.labelNode.isUserInteractionEnabled = false
|
||||
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
|
||||
self.textNode = TextNode()
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.badgeNode)
|
||||
self.addSubnode(self.labelNode)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: NotificationPermissionInfoItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||
|
||||
let currentItem = self.item
|
||||
|
||||
return { item, params, neighbors in
|
||||
let leftInset: CGFloat = 16.0 + params.leftInset
|
||||
let rightInset: CGFloat = 16.0 + params.rightInset
|
||||
|
||||
var updatedTheme: PresentationTheme?
|
||||
var updatedBadgeImage: UIImage?
|
||||
|
||||
let badgeDiameter: CGFloat = 20.0
|
||||
if currentItem?.theme !== item.theme {
|
||||
updatedTheme = item.theme
|
||||
updatedBadgeImage = generateStretchableFilledCircleImage(diameter: badgeDiameter, color: item.theme.list.itemDestructiveColor)
|
||||
}
|
||||
|
||||
let insets = itemListNeighborsGroupedInsets(neighbors)
|
||||
let separatorHeight = UIScreenPixel
|
||||
let itemBackgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||
let itemSeparatorColor = item.theme.list.itemBlocksSeparatorColor
|
||||
|
||||
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "!", font: badgeFont, textColor: item.theme.list.itemCheckColors.foregroundColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: badgeDiameter, height: badgeDiameter), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Turn ON Notifications", font: titleFont, textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - badgeDiameter - 8.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Don't miss important messages from your friends and coworkers.", font: textFont, textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 3, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let contentSize = CGSize(width: params.width, height: titleLayout.size.height + textLayout.size.height + 36.0)
|
||||
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
|
||||
if let _ = updatedTheme {
|
||||
strongSelf.topStripeNode.backgroundColor = itemSeparatorColor
|
||||
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
|
||||
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
|
||||
}
|
||||
|
||||
let _ = labelApply()
|
||||
let _ = titleApply()
|
||||
let _ = textApply()
|
||||
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
}
|
||||
if strongSelf.topStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
|
||||
}
|
||||
if strongSelf.bottomStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
||||
}
|
||||
switch neighbors.top {
|
||||
case .sameSection(false):
|
||||
strongSelf.topStripeNode.isHidden = true
|
||||
default:
|
||||
strongSelf.topStripeNode.isHidden = false
|
||||
}
|
||||
let bottomStripeInset: CGFloat
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
}
|
||||
|
||||
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.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
|
||||
|
||||
if let updateBadgeImage = updatedBadgeImage {
|
||||
if strongSelf.badgeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.badgeNode, belowSubnode: strongSelf.labelNode)
|
||||
}
|
||||
strongSelf.badgeNode.image = updateBadgeImage
|
||||
}
|
||||
|
||||
strongSelf.badgeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 16.0), size: CGSize(width: badgeDiameter, height: badgeDiameter))
|
||||
|
||||
strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: strongSelf.badgeNode.frame.midX - labelLayout.size.width / 2.0, y: strongSelf.badgeNode.frame.minY + 1.0), size: labelLayout.size)
|
||||
|
||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: strongSelf.badgeNode.frame.maxX + 8.0, y: 16.0), size: titleLayout.size)
|
||||
|
||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: strongSelf.titleNode.frame.maxY + 9.0), size: textLayout.size)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
}
|
||||
|
||||
override func animateAdded(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
}
|
@ -10,6 +10,8 @@ private final class NotificationsAndSoundsArguments {
|
||||
let pushController: (ViewController) -> Void
|
||||
let soundSelectionDisposable: MetaDisposable
|
||||
|
||||
let enableNotifications: () -> Void
|
||||
|
||||
let updateMessageAlerts: (Bool) -> Void
|
||||
let updateMessagePreviews: (Bool) -> Void
|
||||
let updateMessageSound: (PeerMessageSound) -> Void
|
||||
@ -37,11 +39,12 @@ private final class NotificationsAndSoundsArguments {
|
||||
|
||||
let openAppSettings: () -> Void
|
||||
|
||||
init(account: Account, presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, pushController: @escaping(ViewController)->Void, soundSelectionDisposable: MetaDisposable, 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, 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) {
|
||||
self.account = account
|
||||
self.presentController = presentController
|
||||
self.pushController = pushController
|
||||
self.soundSelectionDisposable = soundSelectionDisposable
|
||||
self.enableNotifications = enableNotifications
|
||||
self.updateMessageAlerts = updateMessageAlerts
|
||||
self.updateMessagePreviews = updateMessagePreviews
|
||||
self.updateMessageSound = updateMessageSound
|
||||
@ -65,6 +68,7 @@ private final class NotificationsAndSoundsArguments {
|
||||
}
|
||||
|
||||
private enum NotificationsAndSoundsSection: Int32 {
|
||||
case permission
|
||||
case messages
|
||||
case groups
|
||||
case channels
|
||||
@ -75,6 +79,9 @@ private enum NotificationsAndSoundsSection: Int32 {
|
||||
}
|
||||
|
||||
private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
|
||||
case permissionInfo(PresentationTheme, PresentationStrings)
|
||||
case permissionEnable(PresentationTheme, String)
|
||||
|
||||
case messageHeader(PresentationTheme, String)
|
||||
case messageAlerts(PresentationTheme, String, Bool)
|
||||
case messagePreviews(PresentationTheme, String, Bool)
|
||||
@ -117,6 +124,8 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .permissionInfo, .permissionEnable:
|
||||
return NotificationsAndSoundsSection.permission.rawValue
|
||||
case .messageHeader, .messageAlerts, .messagePreviews, .messageSound, .messageNotice, .userExceptions:
|
||||
return NotificationsAndSoundsSection.messages.rawValue
|
||||
case .groupHeader, .groupAlerts, .groupPreviews, .groupSound, .groupNotice, .groupExceptions:
|
||||
@ -136,75 +145,91 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
|
||||
|
||||
var stableId: Int32 {
|
||||
switch self {
|
||||
case .messageHeader:
|
||||
case .permissionInfo:
|
||||
return 0
|
||||
case .messageAlerts:
|
||||
case .permissionEnable:
|
||||
return 1
|
||||
case .messagePreviews:
|
||||
case .messageHeader:
|
||||
return 2
|
||||
case .messageSound:
|
||||
case .messageAlerts:
|
||||
return 3
|
||||
case .userExceptions:
|
||||
case .messagePreviews:
|
||||
return 4
|
||||
case .messageNotice:
|
||||
case .messageSound:
|
||||
return 5
|
||||
case .groupHeader:
|
||||
case .userExceptions:
|
||||
return 6
|
||||
case .groupAlerts:
|
||||
case .messageNotice:
|
||||
return 7
|
||||
case .groupPreviews:
|
||||
case .groupHeader:
|
||||
return 8
|
||||
case .groupSound:
|
||||
case .groupAlerts:
|
||||
return 9
|
||||
case .groupExceptions:
|
||||
case .groupPreviews:
|
||||
return 10
|
||||
case .groupNotice:
|
||||
case .groupSound:
|
||||
return 11
|
||||
case .channelHeader:
|
||||
case .groupExceptions:
|
||||
return 12
|
||||
case .channelAlerts:
|
||||
case .groupNotice:
|
||||
return 13
|
||||
case .channelPreviews:
|
||||
case .channelHeader:
|
||||
return 14
|
||||
case .channelSound:
|
||||
case .channelAlerts:
|
||||
return 15
|
||||
case .channelExceptions:
|
||||
case .channelPreviews:
|
||||
return 16
|
||||
case .channelNotice:
|
||||
case .channelSound:
|
||||
return 17
|
||||
case .inAppHeader:
|
||||
case .channelExceptions:
|
||||
return 18
|
||||
case .inAppSounds:
|
||||
case .channelNotice:
|
||||
return 19
|
||||
case .inAppVibrate:
|
||||
case .inAppHeader:
|
||||
return 20
|
||||
case .inAppPreviews:
|
||||
case .inAppSounds:
|
||||
return 21
|
||||
case .displayNamesOnLockscreen:
|
||||
case .inAppVibrate:
|
||||
return 22
|
||||
case .displayNamesOnLockscreenInfo:
|
||||
case .inAppPreviews:
|
||||
return 23
|
||||
case .badgeHeader:
|
||||
case .displayNamesOnLockscreen:
|
||||
return 24
|
||||
case .unreadCountStyle:
|
||||
case .displayNamesOnLockscreenInfo:
|
||||
return 25
|
||||
case .includePublicGroups:
|
||||
case .badgeHeader:
|
||||
return 26
|
||||
case .includeChannels:
|
||||
case .unreadCountStyle:
|
||||
return 27
|
||||
case .unreadCountCategory:
|
||||
case .includePublicGroups:
|
||||
return 28
|
||||
case .unreadCountCategoryInfo:
|
||||
case .includeChannels:
|
||||
return 29
|
||||
case .reset:
|
||||
case .unreadCountCategory:
|
||||
return 30
|
||||
case .resetNotice:
|
||||
case .unreadCountCategoryInfo:
|
||||
return 31
|
||||
case .reset:
|
||||
return 32
|
||||
case .resetNotice:
|
||||
return 33
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: NotificationsAndSoundsEntry, rhs: NotificationsAndSoundsEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .permissionInfo(lhsTheme, lhsStrings):
|
||||
if case let .permissionInfo(rhsTheme, rhsStrings) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .permissionEnable(lhsTheme, lhsText):
|
||||
if case let .permissionEnable(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .messageHeader(lhsTheme, lhsText):
|
||||
if case let .messageHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
@ -406,6 +431,12 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
|
||||
|
||||
func item(_ arguments: NotificationsAndSoundsArguments) -> ListViewItem {
|
||||
switch self {
|
||||
case let .permissionInfo(theme, strings):
|
||||
return NotificationPermissionInfoItem(theme: theme, strings: strings, sectionId: self.section)
|
||||
case let .permissionEnable(theme, text):
|
||||
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.enableNotifications()
|
||||
})
|
||||
case let .messageHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .messageAlerts(theme, text, value):
|
||||
@ -538,9 +569,20 @@ private func filteredGlobalSound(_ sound: PeerMessageSound) -> PeerMessageSound
|
||||
}
|
||||
}
|
||||
|
||||
private func notificationsAndSoundsEntries(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] = []
|
||||
|
||||
switch authorizationStatus {
|
||||
case .denied:
|
||||
entries.append(.permissionInfo(presentationData.theme, presentationData.strings))
|
||||
entries.append(.permissionEnable(presentationData.theme, "Turn ON in Settings"))
|
||||
case .notDetermined:
|
||||
entries.append(.permissionInfo(presentationData.theme, presentationData.strings))
|
||||
entries.append(.permissionEnable(presentationData.theme, "Turn Notifications ON"))
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
entries.append(.messageHeader(presentationData.theme, presentationData.strings.Notifications_MessageNotifications))
|
||||
entries.append(.messageAlerts(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsAlert, globalSettings.privateChats.enabled))
|
||||
entries.append(.messagePreviews(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsPreview, globalSettings.privateChats.displayPreviews))
|
||||
@ -600,8 +642,6 @@ public func notificationsAndSoundsController(account: Account, exceptionsList: N
|
||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
|
||||
|
||||
|
||||
let notificationExceptions: Promise<(users: NotificationExceptionMode, groups: NotificationExceptionMode, channels: NotificationExceptionMode)> = Promise()
|
||||
|
||||
let updateNotificationExceptions:((users: NotificationExceptionMode, groups: NotificationExceptionMode, channels: NotificationExceptionMode)) -> Void = { value in
|
||||
@ -612,7 +652,20 @@ public func notificationsAndSoundsController(account: Account, exceptionsList: N
|
||||
presentControllerImpl?(controller, arguments)
|
||||
}, pushController: { controller in
|
||||
pushControllerImpl?(controller)
|
||||
}, soundSelectionDisposable: MetaDisposable(), updateMessageAlerts: { value in
|
||||
}, soundSelectionDisposable: MetaDisposable(), enableNotifications: {
|
||||
let _ = (DeviceAccess.authorizationStatus(account: account, subject: .notifications)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { status in
|
||||
switch status {
|
||||
case .notDetermined:
|
||||
account.telegramApplicationContext.applicationBindings.registerForNotifications()
|
||||
case .denied, .restricted:
|
||||
account.telegramApplicationContext.applicationBindings.openSettings()
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
}, updateMessageAlerts: { value in
|
||||
let _ = updateGlobalNotificationSettingsInteractively(postbox: account.postbox, { settings in
|
||||
return settings.withUpdatedPrivateChats {
|
||||
return $0.withUpdatedEnabled(value)
|
||||
@ -799,8 +852,8 @@ public func notificationsAndSoundsController(account: Account, exceptionsList: N
|
||||
|
||||
|
||||
|
||||
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, preferences, notificationExceptions.get())
|
||||
|> map { presentationData, view, exceptions -> (ItemListControllerState, (ItemListNodeState<NotificationsAndSoundsEntry>, NotificationsAndSoundsEntry.ItemGenerationArguments)) in
|
||||
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, preferences, notificationExceptions.get(), DeviceAccess.authorizationStatus(account: account, subject: .notifications))
|
||||
|> map { presentationData, view, exceptions, authorizationStatus -> (ItemListControllerState, (ItemListNodeState<NotificationsAndSoundsEntry>, NotificationsAndSoundsEntry.ItemGenerationArguments)) in
|
||||
|
||||
let viewSettings: GlobalNotificationSettingsSet
|
||||
if let settings = view.values[PreferencesKeys.globalNotifications] as? GlobalNotificationSettings {
|
||||
@ -817,7 +870,7 @@ public func notificationsAndSoundsController(account: Account, exceptionsList: N
|
||||
}
|
||||
|
||||
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Notifications_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
||||
let listState = ItemListNodeState(entries: notificationsAndSoundsEntries(globalSettings: viewSettings, inAppSettings: inAppSettings, exceptions: exceptions, presentationData: presentationData), style: .blocks)
|
||||
let listState = ItemListNodeState(entries: notificationsAndSoundsEntries(authorizationStatus: authorizationStatus, globalSettings: viewSettings, inAppSettings: inAppSettings, exceptions: exceptions, presentationData: presentationData), style: .blocks)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
63
TelegramUI/PermissionController.swift
Normal file
@ -0,0 +1,63 @@
|
||||
import Foundation
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
|
||||
final class PermissionController : ViewController {
|
||||
private var controllerNode: PermissionControllerNode {
|
||||
return self.displayNode as! PermissionControllerNode
|
||||
}
|
||||
|
||||
private let account: Account
|
||||
private let strings: PresentationStrings
|
||||
private let theme: AuthorizationTheme
|
||||
|
||||
init(account: Account) {
|
||||
self.account = account
|
||||
self.strings = account.telegramApplicationContext.currentPresentationData.with { $0 }.strings
|
||||
self.theme = defaultLightAuthorizationTheme
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: AuthorizationSequenceController.navigationBarTheme(theme), strings: NavigationBarStrings(presentationStrings: strings)))
|
||||
|
||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
|
||||
self.statusBar.statusBarStyle = self.theme.statusBarStyle
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func loadDisplayNode() {
|
||||
self.displayNode = PermissionControllerNode(theme: self.theme, strings: self.strings)
|
||||
self.displayNodeDidLoad()
|
||||
|
||||
self.controllerNode.dismiss = { [weak self] in
|
||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
}
|
||||
self.controllerNode.allow = { [weak self] in
|
||||
self?.account.telegramApplicationContext.applicationBindings.openSettings()
|
||||
}
|
||||
self.controllerNode.next = { [weak self] in
|
||||
self?.dismiss(completion: nil)
|
||||
}
|
||||
self.controllerNode.openPrivacyPolicy = {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override func dismiss(completion: (() -> Void)?) {
|
||||
self.controllerNode.animateOut(completion: completion)
|
||||
}
|
||||
|
||||
func updateData(subject: DeviceAccessSubject, currentStatus: AccessType) {
|
||||
self.controllerNode.updateData(subject: .notifications, currentStatus: currentStatus)
|
||||
}
|
||||
|
||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
|
||||
}
|
||||
}
|
203
TelegramUI/PermissionControllerNode.swift
Normal file
@ -0,0 +1,203 @@
|
||||
import Foundation
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
|
||||
final class PermissionControllerNode: ASDisplayNode {
|
||||
private let theme: AuthorizationTheme
|
||||
private let strings: PresentationStrings
|
||||
|
||||
private let iconNode: ASImageNode
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let textNode: ImmediateTextNode
|
||||
private let buttonNode: SolidRoundedButtonNode
|
||||
private let privacyPolicyNode: HighlightableButtonNode
|
||||
private let nextNode: HighlightableButtonNode
|
||||
|
||||
private var layoutArguments: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
private var title: String?
|
||||
|
||||
var allow: (() -> Void)?
|
||||
var next: (() -> Void)? {
|
||||
didSet {
|
||||
self.nextNode.isHidden = self.next == nil
|
||||
}
|
||||
}
|
||||
var openPrivacyPolicy: (() -> Void)?
|
||||
var dismiss: (() -> Void)?
|
||||
|
||||
init(theme: AuthorizationTheme, strings: PresentationStrings) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.isLayerBacked = true
|
||||
self.iconNode.displayWithoutProcessing = true
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
|
||||
self.titleNode = ImmediateTextNode()
|
||||
self.titleNode.maximumNumberOfLines = 0
|
||||
self.titleNode.textAlignment = .center
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
self.titleNode.displaysAsynchronously = false
|
||||
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.textAlignment = .center
|
||||
self.textNode.maximumNumberOfLines = 0
|
||||
self.textNode.displaysAsynchronously = false
|
||||
let body = MarkdownAttributeSet(font: Font.regular(16.0), textColor: theme.primaryColor)
|
||||
let link = MarkdownAttributeSet(font: Font.regular(16.0), textColor: theme.accentColor, additionalAttributes: [TelegramTextAttributes.URL: ""])
|
||||
self.textNode.attributedText = parseMarkdownIntoAttributedString(strings.Login_TermsOfServiceLabel.replacingOccurrences(of: "]", with: "]()"), attributes: MarkdownAttributes(body: body, bold: body, link: link, linkAttribute: { _ in nil }), textAlignment: .center)
|
||||
|
||||
self.buttonNode = SolidRoundedButtonNode(theme: self.theme, height: 48.0, cornerRadius: 9.0)
|
||||
|
||||
self.privacyPolicyNode = HighlightableButtonNode()
|
||||
self.privacyPolicyNode.setTitle("Privacy Policy", with: Font.regular(16.0), with: self.theme.accentColor, for: .normal)
|
||||
|
||||
self.nextNode = HighlightableButtonNode()
|
||||
self.nextNode.setTitle("Skip", with: Font.regular(17.0), with: self.theme.accentColor, for: .normal)
|
||||
self.nextNode.isHidden = true
|
||||
|
||||
super.init()
|
||||
|
||||
self.setViewBlock({
|
||||
return UITracingLayerView()
|
||||
})
|
||||
|
||||
self.backgroundColor = self.theme.backgroundColor
|
||||
|
||||
self.addSubnode(self.iconNode)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
self.addSubnode(self.privacyPolicyNode)
|
||||
self.addSubnode(self.nextNode)
|
||||
|
||||
self.buttonNode.pressed = { [weak self] in
|
||||
self?.allow?()
|
||||
}
|
||||
|
||||
self.privacyPolicyNode.addTarget(self, action: #selector(self.privacyPolicyPressed), forControlEvents: .touchUpInside)
|
||||
self.nextNode.addTarget(self, action: #selector(self.nextPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
func updateData(subject: DeviceAccessSubject, currentStatus: AccessType) {
|
||||
var icon: UIImage?
|
||||
var title = ""
|
||||
var text = ""
|
||||
var buttonTitle = ""
|
||||
var hasPrivacyPolicy = false
|
||||
|
||||
switch subject {
|
||||
case .contacts:
|
||||
icon = UIImage(bundleImageName: "Settings/Permissions/Contacts")
|
||||
title = "Sync Your Contacts"
|
||||
text = "See who's on Telegram and switch seamlessly, without having to \"add\" to add your friends."
|
||||
if currentStatus == .denied {
|
||||
buttonTitle = "Allow in Settings"
|
||||
} else {
|
||||
buttonTitle = "Allow Access"
|
||||
}
|
||||
hasPrivacyPolicy = true
|
||||
case .notifications:
|
||||
icon = UIImage(bundleImageName: "Settings/Permissions/Notifications")
|
||||
title = "Turn ON Notifications"
|
||||
text = "Don't miss important messages from your friends and coworkers."
|
||||
if currentStatus == .denied || currentStatus == .restricted {
|
||||
buttonTitle = "Turn ON in Settings"
|
||||
} else {
|
||||
buttonTitle = "Turn Notifications ON"
|
||||
}
|
||||
case .cellularData:
|
||||
icon = UIImage(bundleImageName: "Settings/Permissions/CellularData")
|
||||
title = "Turn ON Mobile Data"
|
||||
text = "Don't worry, Telegram keeps network usage to a minimum. You can further control this in Settings > Data and Storage."
|
||||
buttonTitle = "Turn ON in Settings"
|
||||
case .siri:
|
||||
title = "Turn ON Siri"
|
||||
text = "Use Siri to send messages and make calls."
|
||||
if currentStatus == .denied {
|
||||
buttonTitle = "Turn ON in Settings"
|
||||
} else {
|
||||
buttonTitle = "Turn Siri ON"
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
self.iconNode.image = icon
|
||||
self.title = title
|
||||
|
||||
let body = MarkdownAttributeSet(font: Font.regular(16.0), textColor: self.theme.primaryColor)
|
||||
let link = MarkdownAttributeSet(font: Font.regular(16.0), textColor: self.theme.accentColor, additionalAttributes: [TelegramTextAttributes.URL: ""])
|
||||
self.textNode.attributedText = parseMarkdownIntoAttributedString(text.replacingOccurrences(of: "]", with: "]()"), attributes: MarkdownAttributes(body: body, bold: body, link: link, linkAttribute: { _ in nil }), textAlignment: .center)
|
||||
|
||||
self.buttonNode.title = buttonTitle
|
||||
|
||||
self.privacyPolicyNode.isHidden = !hasPrivacyPolicy
|
||||
|
||||
if let (layout, navigationHeight) = self.layoutArguments {
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.layoutArguments = (layout, navigationBarHeight)
|
||||
|
||||
let insets = layout.insets(options: [.statusBar])
|
||||
let fontSize: CGFloat
|
||||
let sideInset: CGFloat
|
||||
if layout.size.width > 330.0 {
|
||||
fontSize = 22.0
|
||||
sideInset = 38.0
|
||||
} else {
|
||||
fontSize = 18.0
|
||||
sideInset = 20.0
|
||||
}
|
||||
|
||||
let nextSize = self.nextNode.measure(layout.size)
|
||||
transition.updateFrame(node: self.nextNode, frame: CGRect(x: layout.size.width - insets.right - nextSize.width - 16.0, y: insets.top + 10.0 + 60.0, width: nextSize.width, height: nextSize.height))
|
||||
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.title ?? "", font: Font.semibold(fontSize), textColor: self.theme.primaryColor)
|
||||
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
let buttonHeight = self.buttonNode.updateLayout(width: layout.size.width, transition: transition)
|
||||
|
||||
var items: [AuthorizationLayoutItem] = []
|
||||
if let icon = self.iconNode.image {
|
||||
items.append(AuthorizationLayoutItem(node: self.iconNode, size: icon.size, spacingBefore: AuthorizationLayoutItemSpacing(weight: 122.0, maxValue: 122.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 15.0, maxValue: 15.0)))
|
||||
}
|
||||
items.append(AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
|
||||
items.append(AuthorizationLayoutItem(node: self.textNode, size: textSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 5.0, maxValue: 5.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 35.0, maxValue: 35.0)))
|
||||
items.append(AuthorizationLayoutItem(node: self.buttonNode, size: CGSize(width: layout.size.width, height: buttonHeight), spacingBefore: AuthorizationLayoutItemSpacing(weight: 35.0, maxValue: 35.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 50.0, maxValue: 50.0)))
|
||||
|
||||
let _ = layoutAuthorizationItems(bounds: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top - insets.bottom - 20.0)), items: items, transition: transition, failIfDoesNotFit: false)
|
||||
|
||||
let privacyPolicySize = self.privacyPolicyNode.measure(layout.size)
|
||||
transition.updateFrame(node: self.privacyPolicyNode, frame: CGRect(x: (layout.size.width - privacyPolicySize.width) / 2.0, y: self.buttonNode.frame.maxY + (layout.size.height - self.buttonNode.frame.maxY - insets.bottom - privacyPolicySize.height) / 2.0, width: privacyPolicySize.width, height: privacyPolicySize.height))
|
||||
}
|
||||
|
||||
@objc func allowPressed() {
|
||||
self.allow?()
|
||||
}
|
||||
|
||||
@objc func privacyPolicyPressed() {
|
||||
self.openPrivacyPolicy?()
|
||||
}
|
||||
|
||||
@objc func nextPressed() {
|
||||
self.next?()
|
||||
}
|
||||
|
||||
func animateOut(completion: (() -> Void)? = nil) {
|
||||
self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.dismiss?()
|
||||
}
|
||||
completion?()
|
||||
})
|
||||
}
|
||||
}
|
@ -604,7 +604,6 @@ public func recentSessionsController(account: Account) -> ViewController {
|
||||
let websitesSignal: Signal<([WebAuthorization], [PeerId : Peer])?, NoError> = .single(nil) |> then(webSessions(network: account.network) |> map(Optional.init))
|
||||
websitesPromise.set(websitesSignal)
|
||||
|
||||
|
||||
let previousMode = Atomic<RecentSessionsMode>(value: .sessions)
|
||||
|
||||
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, mode.get(), statePromise.get(), sessionsPromise.get(), websitesPromise.get())
|
||||
@ -635,6 +634,8 @@ public func recentSessionsController(account: Account) -> ViewController {
|
||||
var emptyStateItem: ItemListControllerEmptyStateItem?
|
||||
if sessions == nil {
|
||||
emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme)
|
||||
} else if let sessions = sessions, sessions.count == 1 {
|
||||
emptyStateItem = RecentSessionsEmptyStateItem(theme: presentationData.theme, strings: presentationData.strings)
|
||||
}
|
||||
|
||||
let title: ItemListControllerTitle
|
||||
|
95
TelegramUI/RecentSessionsEmptyStateItem.swift
Normal file
@ -0,0 +1,95 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
|
||||
final class RecentSessionsEmptyStateItem: ItemListControllerEmptyStateItem {
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
|
||||
init(theme: PresentationTheme, strings: PresentationStrings) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
}
|
||||
|
||||
func isEqual(to: ItemListControllerEmptyStateItem) -> Bool {
|
||||
if let item = to as? RecentSessionsEmptyStateItem {
|
||||
return self.theme === item.theme && self.strings === item.strings
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func node(current: ItemListControllerEmptyStateItemNode?) -> ItemListControllerEmptyStateItemNode {
|
||||
if let current = current as? RecentSessionsEmptyStateItemNode {
|
||||
current.item = self
|
||||
return current
|
||||
} else {
|
||||
return RecentSessionsEmptyStateItemNode(item: self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class RecentSessionsEmptyStateItemNode: ItemListControllerEmptyStateItemNode {
|
||||
private let imageNode: ASImageNode
|
||||
private let titleNode: ASTextNode
|
||||
private let textNode: ASTextNode
|
||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
var item: RecentSessionsEmptyStateItem {
|
||||
didSet {
|
||||
self.updateThemeAndStrings(theme: self.item.theme, strings: self.item.strings)
|
||||
if let (layout, navigationHeight) = self.validLayout {
|
||||
self.updateLayout(layout: layout, navigationBarHeight: navigationHeight, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(item: RecentSessionsEmptyStateItem) {
|
||||
self.item = item
|
||||
|
||||
self.imageNode = ASImageNode()
|
||||
|
||||
self.titleNode = ASTextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.imageNode)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
|
||||
self.updateThemeAndStrings(theme: self.item.theme, strings: self.item.strings)
|
||||
}
|
||||
|
||||
private func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
|
||||
self.imageNode.image = generateTintedImage(image: UIImage(bundleImageName: "Settings/RecentSessionsPlaceholder"), color: theme.list.freeTextColor)
|
||||
self.titleNode.attributedText = NSAttributedString(string: strings.AuthSessions_EmptyTitle, font: Font.bold(17.0), textColor: theme.list.freeTextColor, paragraphAlignment: .center)
|
||||
self.textNode.attributedText = NSAttributedString(string: strings.AuthSessions_EmptyText, font: Font.regular(14.0), textColor: theme.list.freeTextColor, paragraphAlignment: .center)
|
||||
}
|
||||
|
||||
override func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayout = (layout, navigationBarHeight)
|
||||
var insets = layout.insets(options: [])
|
||||
insets.top += navigationBarHeight + 128.0
|
||||
|
||||
let imageSpacing: CGFloat = 8.0
|
||||
let textSpacing: CGFloat = 8.0
|
||||
|
||||
let imageSize = self.imageNode.image?.size ?? CGSize()
|
||||
let imageHeight = layout.size.width < layout.size.height ? imageSize.height + imageSpacing : 0.0
|
||||
|
||||
let titleSize = self.titleNode.measure(CGSize(width: layout.size.width - 50.0, height: max(1.0, layout.size.height - insets.top - insets.bottom)))
|
||||
let textSize = self.textNode.measure(CGSize(width: layout.size.width - 50.0, height: max(1.0, layout.size.height - insets.top - insets.bottom)))
|
||||
|
||||
let totalHeight = imageHeight + titleSize.height + textSpacing + textSize.height
|
||||
let topOffset = insets.top + floor((layout.size.height - insets.top - insets.bottom - totalHeight) / 2.0)
|
||||
|
||||
transition.updateAlpha(node: self.imageNode, alpha: imageHeight > 0.0 ? 1.0 : 0.0)
|
||||
transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - imageSize.width) / 2.0), y: topOffset), size: imageSize))
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: topOffset + imageHeight), size: titleSize))
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: self.titleNode.frame.maxY + textSpacing), size: textSize))
|
||||
}
|
||||
}
|
@ -89,14 +89,12 @@ final class SelectablePeerNode: ASDisplayNode {
|
||||
|
||||
self.avatarNode = AvatarNode(font: avatarFont)
|
||||
self.avatarNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 60.0, height: 60.0))
|
||||
self.avatarNode.isLayerBacked = true
|
||||
self.avatarNode.isLayerBacked = !smartInvertColorsEnabled()
|
||||
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
self.textNode.displaysAsynchronously = true
|
||||
|
||||
|
||||
|
||||
super.init()
|
||||
|
||||
self.avatarNodeContainer.addSubnode(self.avatarSelectionNode)
|
||||
|
@ -71,7 +71,7 @@ private enum SettingsEntry: ItemListNodeEntry {
|
||||
case recentCalls(PresentationTheme, UIImage?, String)
|
||||
case stickers(PresentationTheme, UIImage?, String, String, [ArchivedStickerPackItem]?)
|
||||
|
||||
case notificationsAndSounds(PresentationTheme, UIImage?, String, NotificationExceptionsList?)
|
||||
case notificationsAndSounds(PresentationTheme, UIImage?, String, NotificationExceptionsList?, Bool)
|
||||
case privacyAndSecurity(PresentationTheme, UIImage?, String)
|
||||
case dataAndStorage(PresentationTheme, UIImage?, String)
|
||||
case themes(PresentationTheme, UIImage?, String)
|
||||
@ -209,8 +209,8 @@ private enum SettingsEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .notificationsAndSounds(lhsTheme, lhsImage, lhsText, lhsExceptionsList):
|
||||
if case let .notificationsAndSounds(rhsTheme, rhsImage, rhsText, rhsExceptionsList) = rhs, lhsTheme === rhsTheme, lhsImage === rhsImage, lhsText == rhsText, lhsExceptionsList == rhsExceptionsList {
|
||||
case let .notificationsAndSounds(lhsTheme, lhsImage, lhsText, lhsExceptionsList, lhsWarning):
|
||||
if case let .notificationsAndSounds(rhsTheme, rhsImage, rhsText, rhsExceptionsList, rhsWarning) = rhs, lhsTheme === rhsTheme, lhsImage === rhsImage, lhsText == rhsText, lhsExceptionsList == rhsExceptionsList, lhsWarning == rhsWarning {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -302,13 +302,13 @@ private enum SettingsEntry: ItemListNodeEntry {
|
||||
arguments.openRecentCalls()
|
||||
})
|
||||
case let .stickers(theme, image, text, value, archivedPacks):
|
||||
return ItemListDisclosureItem(theme: theme, icon: image, title: text, label: value, labelStyle: .badge, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||
return ItemListDisclosureItem(theme: theme, icon: image, title: text, label: value, labelStyle: .badge(theme.list.itemAccentColor), sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||
arguments.pushController(installedStickerPacksController(account: arguments.account, mode: .general, archivedPacks: archivedPacks, updatedPacks: { packs in
|
||||
arguments.updateArchivedPacks(packs)
|
||||
}))
|
||||
})
|
||||
case let .notificationsAndSounds(theme, image, text, exceptionsList):
|
||||
return ItemListDisclosureItem(theme: theme, icon: image, title: text, label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||
case let .notificationsAndSounds(theme, image, text, exceptionsList, warning):
|
||||
return ItemListDisclosureItem(theme: theme, icon: image, title: text, label: warning ? "!" : "", labelStyle: warning ? .badge(theme.list.itemDestructiveColor) : .text, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||
arguments.pushController(notificationsAndSoundsController(account: arguments.account, exceptionsList: exceptionsList))
|
||||
})
|
||||
case let .privacyAndSecurity(theme, image, text):
|
||||
@ -366,7 +366,7 @@ private struct SettingsState: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
private func settingsEntries(presentationData: PresentationData, state: SettingsState, view: PeerView, proxySettings: ProxySettings, notifyExceptions: NotificationExceptionsList?, unreadTrendingStickerPacks: Int, archivedPacks: [ArchivedStickerPackItem]?, hasPassport: Bool, hasWatchApp: Bool) -> [SettingsEntry] {
|
||||
private func settingsEntries(presentationData: PresentationData, state: SettingsState, view: PeerView, proxySettings: ProxySettings, notifyExceptions: NotificationExceptionsList?, notificationsAuthorizationStatus: AccessType, unreadTrendingStickerPacks: Int, archivedPacks: [ArchivedStickerPackItem]?, hasPassport: Bool, hasWatchApp: Bool) -> [SettingsEntry] {
|
||||
var entries: [SettingsEntry] = []
|
||||
|
||||
if let peer = peerViewMainPeer(view) as? TelegramUser {
|
||||
@ -398,7 +398,7 @@ private func settingsEntries(presentationData: PresentationData, state: Settings
|
||||
entries.append(.recentCalls(presentationData.theme, SettingsItemIcons.recentCalls, presentationData.strings.CallSettings_RecentCalls))
|
||||
entries.append(.stickers(presentationData.theme, SettingsItemIcons.stickers, presentationData.strings.ChatSettings_Stickers, unreadTrendingStickerPacks == 0 ? "" : "\(unreadTrendingStickerPacks)", archivedPacks))
|
||||
|
||||
entries.append(.notificationsAndSounds(presentationData.theme, SettingsItemIcons.notifications, presentationData.strings.Settings_NotificationsAndSounds, notifyExceptions))
|
||||
entries.append(.notificationsAndSounds(presentationData.theme, SettingsItemIcons.notifications, presentationData.strings.Settings_NotificationsAndSounds, notifyExceptions, notificationsAuthorizationStatus != .allowed))
|
||||
entries.append(.privacyAndSecurity(presentationData.theme, SettingsItemIcons.security, presentationData.strings.Settings_PrivacySettings))
|
||||
entries.append(.dataAndStorage(presentationData.theme, SettingsItemIcons.dataAndStorage, presentationData.strings.Settings_ChatSettings))
|
||||
entries.append(.themes(presentationData.theme, SettingsItemIcons.appearance, presentationData.strings.Settings_Appearance))
|
||||
@ -673,19 +673,20 @@ public func settingsController(account: Account, accountManager: AccountManager)
|
||||
}
|
||||
updatePassport()
|
||||
|
||||
let notificationAuthorizationStatus = Promise<AccessType>(.allowed)
|
||||
notificationAuthorizationStatus.set(DeviceAccess.authorizationStatus(account: account, subject: .notifications))
|
||||
|
||||
let notifyExceptions = Promise<NotificationExceptionsList?>(nil)
|
||||
let updateNotifyExceptions: () -> Void = {
|
||||
notifyExceptions.set(notificationExceptionsList(network: account.network) |> map(Optional.init))
|
||||
}
|
||||
// updateNotifyExceptions()
|
||||
|
||||
let hasWatchApp = Promise<Bool>(false)
|
||||
if let context = account.applicationContext as? TelegramApplicationContext, let watchManager = context.watchManager {
|
||||
hasWatchApp.set(watchManager.watchAppInstalled)
|
||||
}
|
||||
|
||||
let signal = combineLatest(account.telegramApplicationContext.presentationData, statePromise.get(), peerView, combineLatest(account.postbox.preferencesView(keys: [PreferencesKeys.proxySettings]), notifyExceptions.get()), combineLatest(account.viewTracker.featuredStickerPacks(), archivedPacks.get()), combineLatest(hasPassport.get(), hasWatchApp.get()))
|
||||
let signal = combineLatest(account.telegramApplicationContext.presentationData, statePromise.get(), peerView, combineLatest(account.postbox.preferencesView(keys: [PreferencesKeys.proxySettings]), notifyExceptions.get(), notificationAuthorizationStatus.get()), combineLatest(account.viewTracker.featuredStickerPacks(), archivedPacks.get()), combineLatest(hasPassport.get(), hasWatchApp.get()))
|
||||
|> map { presentationData, state, view, preferencesAndExceptions, featuredAndArchived, hasPassportAndWatch -> (ItemListControllerState, (ItemListNodeState<SettingsEntry>, SettingsEntry.ItemGenerationArguments)) in
|
||||
let proxySettings: ProxySettings = preferencesAndExceptions.0.values[PreferencesKeys.proxySettings] as? ProxySettings ?? ProxySettings.defaultSettings
|
||||
|
||||
@ -708,7 +709,7 @@ public func settingsController(account: Account, accountManager: AccountManager)
|
||||
|
||||
let (hasPassport, hasWatchApp) = hasPassportAndWatch
|
||||
|
||||
let listState = ItemListNodeState(entries: settingsEntries(presentationData: presentationData, state: state, view: view, proxySettings: proxySettings, notifyExceptions: preferencesAndExceptions.1, unreadTrendingStickerPacks: unreadTrendingStickerPacks, archivedPacks: featuredAndArchived.1, hasPassport: hasPassport, hasWatchApp: hasWatchApp), style: .blocks)
|
||||
let listState = ItemListNodeState(entries: settingsEntries(presentationData: presentationData, state: state, view: view, proxySettings: proxySettings, notifyExceptions: preferencesAndExceptions.1, notificationsAuthorizationStatus: preferencesAndExceptions.2, unreadTrendingStickerPacks: unreadTrendingStickerPacks, archivedPacks: featuredAndArchived.1, hasPassport: hasPassport, hasWatchApp: hasWatchApp), style: .blocks)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
} |> afterDisposed {
|
||||
@ -717,8 +718,8 @@ public func settingsController(account: Account, accountManager: AccountManager)
|
||||
|
||||
let icon = UIImage(bundleImageName: "Chat List/Tabs/IconSettings")
|
||||
|
||||
let controller = ItemListController(account: account, state: signal, tabBarItem: (account.applicationContext as! TelegramApplicationContext).presentationData |> map { presentationData in
|
||||
return ItemListControllerTabBarItem(title: presentationData.strings.Settings_Title, image: icon, selectedImage: icon)
|
||||
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: status != .allowed ? "!" : nil)
|
||||
})
|
||||
pushControllerImpl = { [weak controller] value in
|
||||
(controller?.navigationController as? NavigationController)?.replaceAllButRootController(value, animated: true)
|
||||
|
88
TelegramUI/SolidRoundedButtonNode.swift
Normal file
@ -0,0 +1,88 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
|
||||
private let textFont: UIFont = Font.regular(16.0)
|
||||
|
||||
final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
private var theme: AuthorizationTheme
|
||||
|
||||
private let buttonBackgroundNode: ASImageNode
|
||||
private let buttonNode: HighlightTrackingButtonNode
|
||||
private let labelNode: ImmediateTextNode
|
||||
|
||||
private let buttonHeight: CGFloat
|
||||
private let buttonCornerRadius: CGFloat
|
||||
|
||||
var pressed: (() -> Void)?
|
||||
var validLayout: CGFloat?
|
||||
|
||||
var title: String? {
|
||||
didSet {
|
||||
if let width = self.validLayout {
|
||||
_ = self.updateLayout(width: width, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(title: String? = nil, theme: AuthorizationTheme, height: CGFloat = 48.0, cornerRadius: CGFloat = 24.0) {
|
||||
self.theme = theme
|
||||
self.buttonHeight = height
|
||||
self.buttonCornerRadius = cornerRadius
|
||||
self.title = title
|
||||
|
||||
self.buttonBackgroundNode = ASImageNode()
|
||||
self.buttonBackgroundNode.isLayerBacked = true
|
||||
self.buttonBackgroundNode.displayWithoutProcessing = true
|
||||
self.buttonBackgroundNode.displaysAsynchronously = false
|
||||
self.buttonBackgroundNode.image = generateStretchableFilledCircleImage(radius: cornerRadius, color: theme.accentColor)
|
||||
|
||||
self.buttonNode = HighlightTrackingButtonNode()
|
||||
|
||||
self.labelNode = ImmediateTextNode()
|
||||
self.labelNode.isUserInteractionEnabled = false
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.buttonBackgroundNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
self.addSubnode(self.labelNode)
|
||||
|
||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
strongSelf.buttonBackgroundNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.buttonBackgroundNode.alpha = 0.55
|
||||
} else {
|
||||
strongSelf.buttonBackgroundNode.alpha = 1.0
|
||||
strongSelf.buttonBackgroundNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
self.validLayout = width
|
||||
|
||||
let inset: CGFloat = 38.0
|
||||
let buttonSize = CGSize(width: width - inset * 2.0, height: self.buttonHeight)
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: inset, y: 0.0), size: buttonSize)
|
||||
transition.updateFrame(node: self.buttonBackgroundNode, frame: buttonFrame)
|
||||
transition.updateFrame(node: self.buttonNode, frame: buttonFrame)
|
||||
|
||||
if self.title != self.labelNode.attributedText?.string {
|
||||
self.labelNode.attributedText = NSAttributedString(string: self.title ?? "", font: Font.medium(17.0), textColor: self.theme.backgroundColor)
|
||||
}
|
||||
|
||||
let labelSize = self.labelNode.updateLayout(buttonSize)
|
||||
let labelFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + floor((buttonFrame.width - labelSize.width) / 2.0), y: buttonFrame.minY + floor((buttonFrame.height - labelSize.height) / 2.0)), size: labelSize)
|
||||
transition.updateFrame(node: self.labelNode, frame: labelFrame)
|
||||
|
||||
return buttonSize.height
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
self.pressed?()
|
||||
}
|
||||
}
|
@ -64,7 +64,7 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
|
||||
|
||||
override init() {
|
||||
self.imageNode = TransformImageNode()
|
||||
self.imageNode.isLayerBacked = true
|
||||
self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
|
||||
//self.imageNode.alphaTransitionOnFirstUpdate = true
|
||||
|
||||
self.textNode = ASTextNode()
|
||||
|
@ -79,7 +79,6 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
|
||||
self.approximateDuration = approximateDuration
|
||||
|
||||
self.imageNode = TransformImageNode()
|
||||
self.imageNode.isLayerBacked = true
|
||||
|
||||
self.playerItem = AVPlayerItem(url: URL(string: url)!)
|
||||
let player = AVPlayer(playerItem: self.playerItem)
|
||||
|
@ -26,11 +26,12 @@ public final class TelegramApplicationBindings {
|
||||
public let pushIdleTimerExtension: () -> Disposable
|
||||
public let openSettings: () -> Void
|
||||
public let openAppStorePage: () -> Void
|
||||
public let registerForNotifications: () -> Void
|
||||
public let getWindowHost: () -> WindowHost?
|
||||
public let presentNativeController: (UIViewController) -> Void
|
||||
public let dismissNativeController: () -> Void
|
||||
|
||||
public init(isMainApp: Bool, openUrl: @escaping (String) -> Void, openUniversalUrl: @escaping (String, TelegramApplicationOpenUrlCompletion) -> Void, canOpenUrl: @escaping (String) -> Bool, getTopWindow: @escaping () -> UIWindow?, displayNotification: @escaping (String) -> Void, applicationInForeground: Signal<Bool, NoError>, applicationIsActive: Signal<Bool, NoError>, clearMessageNotifications: @escaping ([MessageId]) -> Void, pushIdleTimerExtension: @escaping () -> Disposable, openSettings: @escaping () -> Void, openAppStorePage: @escaping () -> Void, getWindowHost: @escaping () -> WindowHost?, presentNativeController: @escaping (UIViewController) -> Void, dismissNativeController: @escaping () -> Void) {
|
||||
public init(isMainApp: Bool, openUrl: @escaping (String) -> Void, openUniversalUrl: @escaping (String, TelegramApplicationOpenUrlCompletion) -> Void, canOpenUrl: @escaping (String) -> Bool, getTopWindow: @escaping () -> UIWindow?, displayNotification: @escaping (String) -> Void, applicationInForeground: Signal<Bool, NoError>, applicationIsActive: Signal<Bool, NoError>, clearMessageNotifications: @escaping ([MessageId]) -> Void, pushIdleTimerExtension: @escaping () -> Disposable, openSettings: @escaping () -> Void, openAppStorePage: @escaping () -> Void, registerForNotifications: @escaping () -> Void, getWindowHost: @escaping () -> WindowHost?, presentNativeController: @escaping (UIViewController) -> Void, dismissNativeController: @escaping () -> Void) {
|
||||
self.isMainApp = isMainApp
|
||||
self.openUrl = openUrl
|
||||
self.openUniversalUrl = openUniversalUrl
|
||||
@ -43,6 +44,7 @@ public final class TelegramApplicationBindings {
|
||||
self.pushIdleTimerExtension = pushIdleTimerExtension
|
||||
self.openSettings = openSettings
|
||||
self.openAppStorePage = openAppStorePage
|
||||
self.registerForNotifications = registerForNotifications
|
||||
self.presentNativeController = presentNativeController
|
||||
self.dismissNativeController = dismissNativeController
|
||||
self.getWindowHost = getWindowHost
|
||||
|
@ -14,6 +14,7 @@ public final class TelegramRootController: NavigationController {
|
||||
public var chatListController: ChatListController?
|
||||
public var accountSettingsController: ViewController?
|
||||
|
||||
private var permissionsDisposable: Disposable?
|
||||
private var presentationDataDisposable: Disposable?
|
||||
private var presentationData: PresentationData
|
||||
|
||||
@ -24,6 +25,8 @@ public final class TelegramRootController: NavigationController {
|
||||
|
||||
super.init(mode: .automaticMasterDetail, theme: NavigationControllerTheme(presentationTheme: self.presentationData.theme))
|
||||
|
||||
//self.permissionsDisposable =
|
||||
|
||||
self.presentationDataDisposable = (account.telegramApplicationContext.presentationData
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
@ -42,6 +45,7 @@ public final class TelegramRootController: NavigationController {
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.permissionsDisposable?.dispose()
|
||||
self.presentationDataDisposable?.dispose()
|
||||
}
|
||||
|
||||
@ -112,4 +116,20 @@ public final class TelegramRootController: NavigationController {
|
||||
}
|
||||
presentedLegacyShortcutCamera(account: self.account, saveCapturedMedia: false, saveEditedPhotos: false, mediaGrouping: true, parentController: controller)
|
||||
}
|
||||
|
||||
public func requestPermissions() {
|
||||
guard let parentController = self.viewControllers.last as? ViewController else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = (DeviceAccess.authorizationStatus(account: self.account, subject: .notifications)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { status in
|
||||
if status != .allowed {
|
||||
let controller = PermissionController(account: self.account)
|
||||
controller.updateData(subject: .notifications, currentStatus: status)
|
||||
parentController.present(controller, in: .window(.root))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,14 @@ public class TransformImageNode: ASDisplayNode {
|
||||
self.disposable.dispose()
|
||||
}
|
||||
|
||||
override public func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
if #available(iOSApplicationExtension 11.0, *), !self.isLayerBacked {
|
||||
self.view.accessibilityIgnoresInvertColors = true
|
||||
}
|
||||
}
|
||||
|
||||
override public var frame: CGRect {
|
||||
didSet {
|
||||
if let overlayNode = self.overlayNode {
|
||||
|
@ -117,7 +117,7 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
|
||||
|
||||
self.iconImageNode = TransformImageNode()
|
||||
self.iconImageNode.contentAnimations = [.subsequentUpdates]
|
||||
self.iconImageNode.isLayerBacked = true
|
||||
self.iconImageNode.isLayerBacked = !smartInvertColorsEnabled()
|
||||
self.iconImageNode.displaysAsynchronously = false
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
@ -327,9 +327,8 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
|
||||
|
||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel))
|
||||
|
||||
|
||||
let progressFrame = CGRect(origin: CGPoint(x: iconFrame.minX + floorToScreenPixels((iconFrame.width - 37) / 2), y: iconFrame.minY + floorToScreenPixels((iconFrame.height - 37) / 2)), size: CGSize(width: 37, height: 37))
|
||||
|
||||
let progressSize = CGSize(width: 24.0, height: 24.0)
|
||||
let progressFrame = CGRect(origin: CGPoint(x: iconFrame.minX + floorToScreenPixels((iconFrame.width - progressSize.width) / 2.0), y: iconFrame.minY + floorToScreenPixels((iconFrame.height - progressSize.height) / 2.0)), size: progressSize)
|
||||
|
||||
if let updatedStatusSignal = updatedStatusSignal {
|
||||
strongSelf.statusDisposable.set((updatedStatusSignal |> deliverOnMainQueue).start(next: { [weak strongSelf] status in
|
||||
|