Added Smart Invert Colors support

First take on deferred permissions request
This commit is contained in:
Ilya Laktyushin 2018-11-23 22:10:51 +04:00
parent 7521078be0
commit 25a36b44b0
57 changed files with 1171 additions and 136 deletions

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

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

View File

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

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "Notifications@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "Notifications@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

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

View File

@ -58,6 +58,11 @@
09874E582107A4C300E190B8 /* VimeoEmbedImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09874E3A21075BF400E190B8 /* VimeoEmbedImplementation.swift */; }; 09874E582107A4C300E190B8 /* VimeoEmbedImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09874E3A21075BF400E190B8 /* VimeoEmbedImplementation.swift */; };
09874E592107BD4100E190B8 /* GenericEmbedImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09874E4021075C1700E190B8 /* GenericEmbedImplementation.swift */; }; 09874E592107BD4100E190B8 /* GenericEmbedImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09874E4021075C1700E190B8 /* GenericEmbedImplementation.swift */; };
09AE3823214C110900850BFD /* LegacySecureIdScanController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09AE3822214C110800850BFD /* LegacySecureIdScanController.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 */; }; 09C3466D2167D63A00B76780 /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C3466C2167D63A00B76780 /* Accessibility.swift */; };
09C500242142BA6400EF253E /* ItemListWebsiteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C500232142BA6400EF253E /* ItemListWebsiteItem.swift */; }; 09C500242142BA6400EF253E /* ItemListWebsiteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C500232142BA6400EF253E /* ItemListWebsiteItem.swift */; };
09C9EA33219F79F600E90146 /* ID3Artwork.m in Sources */ = {isa = PBXBuildFile; fileRef = 09C9EA31219F79F500E90146 /* ID3Artwork.m */; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 09C9EA31219F79F500E90146 /* ID3Artwork.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ID3Artwork.m; sourceTree = "<group>"; };
@ -2332,6 +2342,27 @@
path = TelegramUI/Resources/WebEmbed; path = TelegramUI/Resources/WebEmbed;
sourceTree = "<group>"; 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 */ = { 09CC52A7210615AA000578F8 /* Web Embed */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -2563,6 +2594,7 @@
D01B279C1E394A500022A4C0 /* NotificationsAndSounds.swift */, D01B279C1E394A500022A4C0 /* NotificationsAndSounds.swift */,
D0A749961E3AA25200AD786E /* NotificationSoundSelection.swift */, D0A749961E3AA25200AD786E /* NotificationSoundSelection.swift */,
D02C81722177AC5900CD1006 /* NotificationSearchItem.swift */, D02C81722177AC5900CD1006 /* NotificationSearchItem.swift */,
09B4EE5521A8149C00847FA6 /* NotificationPermissionInfoItem.swift */,
); );
name = Notifications; name = Notifications;
sourceTree = "<group>"; sourceTree = "<group>";
@ -4155,6 +4187,7 @@
D0430AFE1FF456F400A35ADD /* Web */, D0430AFE1FF456F400A35ADD /* Web */,
D0F8C3952017747300236FC5 /* Feed */, D0F8C3952017747300236FC5 /* Feed */,
0941A99E210B053300EBE194 /* Open In */, 0941A99E210B053300EBE194 /* Open In */,
09B4EE5721A82F5900847FA6 /* Permissions */,
); );
name = Controllers; name = Controllers;
sourceTree = "<group>"; sourceTree = "<group>";
@ -4560,11 +4593,9 @@
D0FA0AC21E7742CE005BB9B7 /* Privacy and Security */ = { D0FA0AC21E7742CE005BB9B7 /* Privacy and Security */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
09B4EE4821A6D34900847FA6 /* Recent Sessions */,
D05A32DD1E6F0097002760B4 /* PrivacyAndSecurityController.swift */, D05A32DD1E6F0097002760B4 /* PrivacyAndSecurityController.swift */,
D08984EF2114AE0C00918162 /* DataPrivacySettingsController.swift */, D08984EF2114AE0C00918162 /* DataPrivacySettingsController.swift */,
D05A32ED1E6F25A0002760B4 /* ItemListRecentSessionItem.swift */,
09C500232142BA6400EF253E /* ItemListWebsiteItem.swift */,
D05A32E91E6F143C002760B4 /* RecentSessionsController.swift */,
D05A32EB1E6F1462002760B4 /* BlockedPeersController.swift */, D05A32EB1E6F1462002760B4 /* BlockedPeersController.swift */,
D05B724C1E720393000BD3AD /* SelectivePrivacySettingsController.swift */, D05B724C1E720393000BD3AD /* SelectivePrivacySettingsController.swift */,
D0EF40DC1E72F00E000DFCD4 /* SelectivePrivacySettingsPeersController.swift */, D0EF40DC1E72F00E000DFCD4 /* SelectivePrivacySettingsPeersController.swift */,
@ -4957,6 +4988,7 @@
D0471B5C1EFEB4F30074D609 /* BotPaymentFieldItemNode.swift in Sources */, D0471B5C1EFEB4F30074D609 /* BotPaymentFieldItemNode.swift in Sources */,
D0C27B3D1F4B454800A4E170 /* InstantPagePlayableVideoNode.swift in Sources */, D0C27B3D1F4B454800A4E170 /* InstantPagePlayableVideoNode.swift in Sources */,
D0EC6CD11EB9F58800EBF1C3 /* UrlHandling.swift in Sources */, D0EC6CD11EB9F58800EBF1C3 /* UrlHandling.swift in Sources */,
09B4EE4D21A7B73800847FA6 /* PermissionController.swift in Sources */,
D0FC4FBB1F751E8900B7443F /* SelectablePeerNode.swift in Sources */, D0FC4FBB1F751E8900B7443F /* SelectablePeerNode.swift in Sources */,
D0E9BAD21F0573C000F079A4 /* STPToken.m in Sources */, D0E9BAD21F0573C000F079A4 /* STPToken.m in Sources */,
D0EC6CD31EB9F58800EBF1C3 /* GenerateTextEntities.swift in Sources */, D0EC6CD31EB9F58800EBF1C3 /* GenerateTextEntities.swift in Sources */,
@ -5239,6 +5271,7 @@
D0EC6D631EB9F58800EBF1C3 /* ContactListActionItem.swift in Sources */, D0EC6D631EB9F58800EBF1C3 /* ContactListActionItem.swift in Sources */,
D0EC6D641EB9F58800EBF1C3 /* ContactsPeerItem.swift in Sources */, D0EC6D641EB9F58800EBF1C3 /* ContactsPeerItem.swift in Sources */,
D0B85C1E1FF6F76600E795B4 /* AuthorizationSequencePasswordRecoveryControllerNode.swift in Sources */, D0B85C1E1FF6F76600E795B4 /* AuthorizationSequencePasswordRecoveryControllerNode.swift in Sources */,
09B4EE4721A6D33F00847FA6 /* RecentSessionsEmptyStateItem.swift in Sources */,
D00BED201F73F60F00922292 /* ShareSearchContainerNode.swift in Sources */, D00BED201F73F60F00922292 /* ShareSearchContainerNode.swift in Sources */,
D0CE8CEC1F6FCCA300AA2DB0 /* TransformImageArguments.swift in Sources */, D0CE8CEC1F6FCCA300AA2DB0 /* TransformImageArguments.swift in Sources */,
D0EC6D661EB9F58800EBF1C3 /* ContactsSectionHeaderAccessoryItem.swift in Sources */, D0EC6D661EB9F58800EBF1C3 /* ContactsSectionHeaderAccessoryItem.swift in Sources */,
@ -5268,6 +5301,7 @@
D0EC6D731EB9F58800EBF1C3 /* AuthorizationSequenceSignUpController.swift in Sources */, D0EC6D731EB9F58800EBF1C3 /* AuthorizationSequenceSignUpController.swift in Sources */,
0979787C210642CB0077D77F /* WebEmbedPlayerNode.swift in Sources */, 0979787C210642CB0077D77F /* WebEmbedPlayerNode.swift in Sources */,
D0C12EB01F9A8D1300600BB2 /* ListMessageDateHeader.swift in Sources */, D0C12EB01F9A8D1300600BB2 /* ListMessageDateHeader.swift in Sources */,
09B4EE5221A7CC3E00847FA6 /* SolidRoundedButtonNode.swift in Sources */,
D0E9BA5D1F055A3300F079A4 /* STPBINRange.m in Sources */, D0E9BA5D1F055A3300F079A4 /* STPBINRange.m in Sources */,
D0EC6D741EB9F58800EBF1C3 /* AuthorizationSequenceSignUpControllerNode.swift in Sources */, D0EC6D741EB9F58800EBF1C3 /* AuthorizationSequenceSignUpControllerNode.swift in Sources */,
D0EC6D751EB9F58800EBF1C3 /* TelegramRootController.swift in Sources */, D0EC6D751EB9F58800EBF1C3 /* TelegramRootController.swift in Sources */,
@ -5348,6 +5382,7 @@
D0147BA7206E8B4F00E40378 /* SecureIdAuthAcceptNode.swift in Sources */, D0147BA7206E8B4F00E40378 /* SecureIdAuthAcceptNode.swift in Sources */,
D0E8174E2011FC3800B82BBB /* ChatMessageEventLogPreviousDescriptionContentNode.swift in Sources */, D0E8174E2011FC3800B82BBB /* ChatMessageEventLogPreviousDescriptionContentNode.swift in Sources */,
D0EC6D981EB9F58900EBF1C3 /* ChatMessageItemView.swift in Sources */, D0EC6D981EB9F58900EBF1C3 /* ChatMessageItemView.swift in Sources */,
09B4EE4F21A7B75D00847FA6 /* PermissionControllerNode.swift in Sources */,
09D304152173C0E900C00567 /* WatchManager.swift in Sources */, 09D304152173C0E900C00567 /* WatchManager.swift in Sources */,
9F06830921A404AB001D8EDB /* NotificationExceptionControllerNode.swift in Sources */, 9F06830921A404AB001D8EDB /* NotificationExceptionControllerNode.swift in Sources */,
D039FB1921711B5D00BD1BAD /* PlatformVideoContent.swift in Sources */, D039FB1921711B5D00BD1BAD /* PlatformVideoContent.swift in Sources */,
@ -5455,6 +5490,7 @@
D0BCC3D2203F0A6C008126C2 /* StringForMessageTimestampStatus.swift in Sources */, D0BCC3D2203F0A6C008126C2 /* StringForMessageTimestampStatus.swift in Sources */,
D0EC6DCF1EB9F58900EBF1C3 /* HorizontalStickerGridItem.swift in Sources */, D0EC6DCF1EB9F58900EBF1C3 /* HorizontalStickerGridItem.swift in Sources */,
D0EC6DD01EB9F58900EBF1C3 /* HashtagChatInputContextPanelNode.swift in Sources */, D0EC6DD01EB9F58900EBF1C3 /* HashtagChatInputContextPanelNode.swift in Sources */,
09B4EE5621A8149C00847FA6 /* NotificationPermissionInfoItem.swift in Sources */,
D0EC6DD11EB9F58900EBF1C3 /* HashtagChatInputPanelItem.swift in Sources */, D0EC6DD11EB9F58900EBF1C3 /* HashtagChatInputPanelItem.swift in Sources */,
D0EC6DD21EB9F58900EBF1C3 /* MentionChatInputContextPanelNode.swift in Sources */, D0EC6DD21EB9F58900EBF1C3 /* MentionChatInputContextPanelNode.swift in Sources */,
D00701A22029F6D0006B9E34 /* TGMimeTypeMap.m in Sources */, D00701A22029F6D0006B9E34 /* TGMimeTypeMap.m in Sources */,

View File

@ -1,6 +1,14 @@
import SwiftSignalKit import SwiftSignalKit
import UIKit import UIKit
func smartInvertColorsEnabled() -> Bool {
if #available(iOSApplicationExtension 11.0, *), UIAccessibilityIsInvertColorsEnabled() {
return true
} else {
return false
}
}
func reduceMotionEnabled() -> Signal<Bool, NoError> { func reduceMotionEnabled() -> Signal<Bool, NoError> {
return Signal { subscriber in return Signal { subscriber in
subscriber.putNext(UIAccessibility.isReduceMotionEnabled) subscriber.putNext(UIAccessibility.isReduceMotionEnabled)

View File

@ -177,6 +177,14 @@ public final class AvatarNode: ASDisplayNode {
self.addSubnode(self.imageNode) 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 { override public var frame: CGRect {
get { get {
return super.frame return super.frame

View File

@ -204,7 +204,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
self.highlightedBackgroundNode.isLayerBacked = true self.highlightedBackgroundNode.isLayerBacked = true
self.avatarNode = AvatarNode(font: avatarFont) self.avatarNode = AvatarNode(font: avatarFont)
self.avatarNode.isLayerBacked = true self.avatarNode.isLayerBacked = !smartInvertColorsEnabled()
self.titleNode = TextNode() self.titleNode = TextNode()
self.statusNode = TextNode() self.statusNode = TextNode()

View File

@ -106,7 +106,7 @@ private final class ChatContextResultPeekNode: ASDisplayNode, PeekControllerCont
self.imageNode = TransformImageNode() self.imageNode = TransformImageNode()
self.imageNode.contentAnimations = [.subsequentUpdates] self.imageNode.contentAnimations = [.subsequentUpdates]
self.imageNode.isLayerBacked = true self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
self.imageNode.displaysAsynchronously = false self.imageNode.displaysAsynchronously = false
var timebase: CMTimebase? var timebase: CMTimebase?

View File

@ -90,11 +90,11 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
func updateLayout(transition: ContainedViewLayoutTransition) -> CGSize { func updateLayout(transition: ContainedViewLayoutTransition) -> CGSize {
let buttonSize = CGSize(width: 38.0, height: 38.0) 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 var mentionsOffset: CGFloat = 0.0
if self.displayDownButton { if self.displayDownButton {
mentionsOffset = buttonSize.height + 8.0 mentionsOffset = buttonSize.height + 12.0
transition.updateAlpha(node: self.downButton, alpha: 1.0) transition.updateAlpha(node: self.downButton, alpha: 1.0)
transition.updateTransformScale(node: self.downButton, scale: 1.0) transition.updateTransformScale(node: self.downButton, scale: 1.0)
} else { } else {

View File

@ -218,7 +218,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
self.textNode = ImmediateTextNode() self.textNode = ImmediateTextNode()
self.textNode.maximumNumberOfLines = 10 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 = ASTextNode()
self.authorNameNode.maximumNumberOfLines = 1 self.authorNameNode.maximumNumberOfLines = 1

View File

@ -72,13 +72,13 @@ final class ChatMediaInputPeerSpecificItemNode: ListViewItemNode {
private let stickerFetchedDisposable = MetaDisposable() private let stickerFetchedDisposable = MetaDisposable()
init() { init() {
self.highlightNode = ASImageNode() self.highlightNode = ASImageNode()
self.highlightNode.isLayerBacked = true self.highlightNode.isLayerBacked = true
self.highlightNode.isHidden = true self.highlightNode.isHidden = true
self.avatarNode = AvatarNode(font: avatarFont) 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) self.avatarNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
let imageSize = CGSize(width: 32.0, height: 32.0) let imageSize = CGSize(width: 32.0, height: 32.0)

View File

@ -80,7 +80,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
self.highlightNode.isHidden = true self.highlightNode.isHidden = true
self.imageNode = TransformImageNode() 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) 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)

View File

@ -249,7 +249,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
self.inlineImageNode = TransformImageNode() self.inlineImageNode = TransformImageNode()
self.inlineImageNode.contentAnimations = [.subsequentUpdates] self.inlineImageNode.contentAnimations = [.subsequentUpdates]
self.inlineImageNode.isLayerBacked = true self.inlineImageNode.isLayerBacked = !smartInvertColorsEnabled()
self.inlineImageNode.displaysAsynchronously = false self.inlineImageNode.displaysAsynchronously = false
self.statusNode = ChatMessageDateAndStatusNode() self.statusNode = ChatMessageDateAndStatusNode()

View File

@ -42,13 +42,15 @@ final class ChatMessageAvatarAccessoryItemNode: ListViewAccessoryItemNode {
let avatarNode: AvatarNode let avatarNode: AvatarNode
override init() { override init() {
let isLayerBacked = !smartInvertColorsEnabled()
self.avatarNode = AvatarNode(font: avatarFont) 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)) self.avatarNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 38.0, height: 38.0))
super.init() super.init()
self.isLayerBacked = true self.isLayerBacked = isLayerBacked
self.addSubnode(self.avatarNode) self.addSubnode(self.avatarNode)
} }

View File

@ -38,13 +38,15 @@ final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
private var pulseImage: UIImage? private var pulseImage: UIImage?
override init() { override init() {
let isLayerBacked = !smartInvertColorsEnabled()
self.backgroundNode = ASImageNode() self.backgroundNode = ASImageNode()
self.backgroundNode.isLayerBacked = true self.backgroundNode.isLayerBacked = true
self.backgroundNode.displaysAsynchronously = false self.backgroundNode.displaysAsynchronously = false
self.backgroundNode.displayWithoutProcessing = true self.backgroundNode.displayWithoutProcessing = true
self.avatarNode = AvatarNode(font: avatarFont) self.avatarNode = AvatarNode(font: avatarFont)
self.avatarNode.isLayerBacked = true self.avatarNode.isLayerBacked = isLayerBacked
self.pulseNode = ASImageNode() self.pulseNode = ASImageNode()
self.pulseNode.isLayerBacked = true self.pulseNode.isLayerBacked = true
@ -54,7 +56,7 @@ final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
super.init() super.init()
self.isLayerBacked = true self.isLayerBacked = isLayerBacked
self.addSubnode(self.pulseNode) self.addSubnode(self.pulseNode)
self.addSubnode(self.backgroundNode) self.addSubnode(self.backgroundNode)

View File

@ -173,7 +173,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
if let applyImage = applyImage { if let applyImage = applyImage {
let imageNode = applyImage() let imageNode = applyImage()
if node.imageNode == nil { if node.imageNode == nil {
imageNode.isLayerBacked = true imageNode.isLayerBacked = !smartInvertColorsEnabled()
node.addSubnode(imageNode) node.addSubnode(imageNode)
node.imageNode = imageNode node.imageNode = imageNode
} }

View File

@ -2,11 +2,14 @@ import Foundation
import UIKit import UIKit
import AVFoundation import AVFoundation
import Display import Display
import TelegramCore
import SwiftSignalKit import SwiftSignalKit
import Photos import Photos
import CoreLocation import CoreLocation
import Contacts import Contacts
import AddressBook import AddressBook
import UserNotifications
import CoreTelephony
import LegacyComponents import LegacyComponents
@ -33,9 +36,13 @@ public enum DeviceAccessSubject {
case mediaLibrary(DeviceAccessMediaLibrarySubject) case mediaLibrary(DeviceAccessMediaLibrarySubject)
case location(DeviceAccessLocationSubject) case location(DeviceAccessLocationSubject)
case contacts case contacts
case notifications
case siri
case cellularData
} }
private enum AccessType { public enum AccessType {
case notDetermined
case allowed case allowed
case denied case denied
case restricted case restricted
@ -49,11 +56,109 @@ public final class DeviceAccess {
return self.contactsPromise.get() return self.contactsPromise.get()
} }
private static let notificationsPromise = Promise<AccessType?>(nil)
public static func isMicrophoneAccessAuthorized() -> Bool? { public static func isMicrophoneAccessAuthorized() -> Bool? {
return AVAudioSession.sharedInstance().recordPermission() == .granted 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 { switch subject {
case .camera: case .camera:
let status = PGCamera.cameraAuthorizationStatus() let status = PGCamera.cameraAuthorizationStatus()
@ -108,7 +213,7 @@ public final class DeviceAccess {
openSettings() openSettings()
})]), nil) })]), nil)
if case .voiceCall = microphoneSubject { if case .voiceCall = microphoneSubject {
displayNotificatoinFromBackground(text) displayNotificationFromBackground(text)
} }
} }
}) })
@ -231,6 +336,8 @@ public final class DeviceAccess {
} }
} }
}) })
default:
break
} }
} }
} }

View File

@ -559,6 +559,7 @@ final class ContactListNode: ASDisplayNode {
var activateSearch: (() -> Void)? var activateSearch: (() -> Void)?
var openPeer: ((ContactListPeer) -> Void)? var openPeer: ((ContactListPeer) -> Void)?
var openPrivacyPolicy: (() -> Void)?
private let previousEntries = Atomic<[ContactListNodeEntry]?>(value: nil) private let previousEntries = Atomic<[ContactListNodeEntry]?>(value: nil)
private let disposable = MetaDisposable() private let disposable = MetaDisposable()
@ -567,6 +568,8 @@ final class ContactListNode: ASDisplayNode {
private var presentationDataDisposable: Disposable? private var presentationDataDisposable: Disposable?
private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, Bool)> private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, Bool)>
private let authorizationNode: PermissionControllerNode
init(account: Account, presentation: ContactListPresentation, filters: [ContactListFilter] = [.excludeSelf], selectionState: ContactListNodeGroupSelectionState? = nil) { init(account: Account, presentation: ContactListPresentation, filters: [ContactListFilter] = [.excludeSelf], selectionState: ContactListNodeGroupSelectionState? = nil) {
self.account = account self.account = account
self.presentation = presentation 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.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() super.init()
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
@ -586,8 +592,9 @@ final class ContactListNode: ASDisplayNode {
self.selectionStateValue = selectionState self.selectionStateValue = selectionState
self.selectionStatePromise.set(.single(selectionState)) self.selectionStatePromise.set(.single(selectionState))
self.addSubnode(self.listNode) //self.addSubnode(self.listNode)
self.addSubnode(self.authorizationNode)
let processingQueue = Queue() let processingQueue = Queue()
let previousEntries = Atomic<[ContactListNodeEntry]?>(value: nil) let previousEntries = Atomic<[ContactListNodeEntry]?>(value: nil)
@ -808,6 +815,13 @@ final class ContactListNode: ASDisplayNode {
fixSearchableListNodeScrolling(strongSelf.listNode) 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 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) { func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
var insets = layout.insets(options: [.input]) var insets = layout.insets(options: [.input])
insets.left += layout.safeInsets.left 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 }) 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 { if !self.hasValidLayout {
self.hasValidLayout = true self.hasValidLayout = true
self.dequeueTransitions() self.dequeueTransitions()

View File

@ -21,7 +21,7 @@ public class ContactsController: ViewController {
private var presentationData: PresentationData private var presentationData: PresentationData
private var presentationDataDisposable: Disposable? private var presentationDataDisposable: Disposable?
private var contactsAccessDisposable: Disposable? private var authorizationDisposable: Disposable?
public init(account: Account) { public init(account: Account) {
self.account = account self.account = account
@ -49,18 +49,25 @@ public class ContactsController: ViewController {
} }
self.presentationDataDisposable = (account.telegramApplicationContext.presentationData self.presentationDataDisposable = (account.telegramApplicationContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in |> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self { if let strongSelf = self {
let previousTheme = strongSelf.presentationData.theme let previousTheme = strongSelf.presentationData.theme
let previousStrings = strongSelf.presentationData.strings let previousStrings = strongSelf.presentationData.strings
strongSelf.presentationData = presentationData strongSelf.presentationData = presentationData
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings { if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
strongSelf.updateThemeAndStrings() 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) { required public init(coder aDecoder: NSCoder) {
@ -69,7 +76,7 @@ public class ContactsController: ViewController {
deinit { deinit {
self.presentationDataDisposable?.dispose() self.presentationDataDisposable?.dispose()
self.contactsAccessDisposable?.dispose() self.authorizationDisposable?.dispose()
} }
private func updateThemeAndStrings() { 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.contactsNode.contactListNode.activateSearch = { [weak self] in
self?.activateSearch() self?.activateSearch()
} }

View File

@ -316,7 +316,7 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
self.highlightedBackgroundNode.isLayerBacked = true self.highlightedBackgroundNode.isLayerBacked = true
self.avatarNode = AvatarNode(font: avatarFont) self.avatarNode = AvatarNode(font: avatarFont)
self.avatarNode.isLayerBacked = true self.avatarNode.isLayerBacked = !smartInvertColorsEnabled()
self.titleNode = TextNode() self.titleNode = TextNode()
self.statusNode = TextNode() self.statusNode = TextNode()

View File

@ -305,8 +305,6 @@ class GalleryController: ViewController {
private var performAction: (GalleryControllerInteractionTapAction) -> Void private var performAction: (GalleryControllerInteractionTapAction) -> Void
private var openActionOptions: (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) { 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.account = account
self.replaceRootController = replaceRootController self.replaceRootController = replaceRootController
@ -686,7 +684,6 @@ class GalleryController: ViewController {
deinit { deinit {
self.disposable.dispose() self.disposable.dispose()
self.resolveDisposable.dispose()
self.centralItemAttributesDisposable.dispose() self.centralItemAttributesDisposable.dispose()
if let hiddenMediaManagerIndex = self.hiddenMediaManagerIndex, let mediaManager = self.account.telegramApplicationContext.mediaManager { if let hiddenMediaManagerIndex = self.hiddenMediaManagerIndex, let mediaManager = self.account.telegramApplicationContext.mediaManager {
mediaManager.galleryHiddenMediaManager.removeSource(hiddenMediaManagerIndex) mediaManager.galleryHiddenMediaManager.removeSource(hiddenMediaManagerIndex)

View File

@ -181,9 +181,9 @@ class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGestureRecog
override func didLoad() { override func didLoad() {
super.didLoad() super.didLoad()
/*let recognizer = SwipeToDismissGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) if #available(iOSApplicationExtension 11.0, *), !self.isLayerBacked {
recognizer.delegate = self self.view.accessibilityIgnoresInvertColors = true
self.view.addGestureRecognizer(recognizer)*/ }
} }
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {

View File

@ -116,7 +116,7 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode {
self.bottomStripeNode.isLayerBacked = true self.bottomStripeNode.isLayerBacked = true
self.imageNode = TransformImageNode() self.imageNode = TransformImageNode()
self.imageNode.isLayerBacked = true self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
self.notFoundNode = ASImageNode() self.notFoundNode = ASImageNode()
self.notFoundNode.isLayerBacked = true self.notFoundNode.isLayerBacked = true

View File

@ -148,7 +148,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
self.imageNode = TransformImageNode() self.imageNode = TransformImageNode()
self.imageNode.contentAnimations = [.subsequentUpdates] self.imageNode.contentAnimations = [.subsequentUpdates]
self.imageNode.isLayerBacked = true self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
self.imageNode.displaysAsynchronously = false self.imageNode.displaysAsynchronously = false
var timebase: CMTimebase? 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.statusNode.removeFromSupernode()
strongSelf.addSubnode(strongSelf.statusNode) strongSelf.addSubnode(strongSelf.statusNode)

View File

@ -236,7 +236,7 @@ private enum InstalledStickerPacksEntry: ItemListNodeEntry {
arguments.openSuggestOptions() arguments.openSuggestOptions()
}) })
case let .trending(theme, text, count): 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() arguments.openFeatured()
}) })
case let .masks(theme, text): case let .masks(theme, text):

View File

@ -36,7 +36,7 @@ final class InstantPageGalleryFooterContentNode: GalleryFooterContentNode {
self.textNode = ImmediateTextNode() self.textNode = ImmediateTextNode()
self.textNode.maximumNumberOfLines = 10 self.textNode.maximumNumberOfLines = 10
self.textNode.insets = UIEdgeInsets(top: 8.0, left: 0.0, bottom: 8.0, right: 0.0) 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() super.init()

View File

@ -93,15 +93,17 @@ final class ItemListControllerTabBarItem: Equatable {
let title: String let title: String
let image: UIImage? let image: UIImage?
let selectedImage: 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.title = title
self.image = image self.image = image
self.selectedImage = selectedImage self.selectedImage = selectedImage
self.badgeValue = badgeValue
} }
static func ==(lhs: ItemListControllerTabBarItem, rhs: ItemListControllerTabBarItem) -> Bool { 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
} }
} }

View File

@ -15,7 +15,7 @@ enum ItemListDisclosureStyle {
enum ItemListDisclosureLabelStyle { enum ItemListDisclosureLabelStyle {
case text case text
case badge case badge(UIColor)
case color(UIColor) case color(UIColor)
} }
@ -183,9 +183,11 @@ class ItemListDisclosureItemNode: ListViewItemNode {
var updatedLabelBadgeImage: UIImage? var updatedLabelBadgeImage: UIImage?
var updatedLabelImage: UIImage? var updatedLabelImage: UIImage?
var hasBadge = false var badgeColor: UIColor?
if case .badge = item.labelStyle { if case let .badge(color) = item.labelStyle {
hasBadge = item.label.count > 0 if item.label.count > 0 {
badgeColor = color
}
} }
if case let .color(color) = item.labelStyle { if case let .color(color) = item.labelStyle {
var updatedColor = true var updatedColor = true
@ -201,11 +203,11 @@ class ItemListDisclosureItemNode: ListViewItemNode {
if currentItem?.theme !== item.theme { if currentItem?.theme !== item.theme {
updatedTheme = item.theme updatedTheme = item.theme
updateArrowImage = PresentationResourcesItemList.disclosureArrowImage(item.theme) updateArrowImage = PresentationResourcesItemList.disclosureArrowImage(item.theme)
if hasBadge { if let badgeColor = badgeColor {
updatedLabelBadgeImage = generateStretchableFilledCircleImage(diameter: badgeDiameter, color: item.theme.list.itemAccentColor) updatedLabelBadgeImage = generateStretchableFilledCircleImage(diameter: badgeDiameter, color: badgeColor)
} }
} else if hasBadge && !currentHasBadge { } else if let badgeColor = badgeColor, !currentHasBadge {
updatedLabelBadgeImage = generateStretchableFilledCircleImage(diameter: badgeDiameter, color: item.theme.list.itemAccentColor) updatedLabelBadgeImage = generateStretchableFilledCircleImage(diameter: badgeDiameter, color: badgeColor)
} }
var updateIcon = false var updateIcon = false
@ -336,26 +338,26 @@ class ItemListDisclosureItemNode: ListViewItemNode {
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleLayout.size) 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 let updateBadgeImage = updatedLabelBadgeImage {
if strongSelf.labelBadgeNode.supernode == nil { if strongSelf.labelBadgeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.labelBadgeNode, belowSubnode: strongSelf.labelNode) strongSelf.insertSubnode(strongSelf.labelBadgeNode, belowSubnode: strongSelf.labelNode)
} }
strongSelf.labelBadgeNode.image = updateBadgeImage strongSelf.labelBadgeNode.image = updateBadgeImage
} }
if !hasBadge && strongSelf.labelBadgeNode.supernode != nil { if badgeColor == nil && strongSelf.labelBadgeNode.supernode != nil {
strongSelf.labelBadgeNode.image = nil strongSelf.labelBadgeNode.image = nil
strongSelf.labelBadgeNode.removeFromSupernode() 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 case .color = item.labelStyle {
if let updatedLabelImage = updatedLabelImage { if let updatedLabelImage = updatedLabelImage {
strongSelf.labelImageNode.image = updatedLabelImage strongSelf.labelImageNode.image = updatedLabelImage

View File

@ -200,7 +200,7 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode {
self.bottomStripeNode.isLayerBacked = true self.bottomStripeNode.isLayerBacked = true
self.avatarNode = AvatarNode(font: avatarFont) self.avatarNode = AvatarNode(font: avatarFont)
self.avatarNode.isLayerBacked = true self.avatarNode.isLayerBacked = !smartInvertColorsEnabled()
self.titleNode = TextNode() self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false self.titleNode.isUserInteractionEnabled = false

View File

@ -162,7 +162,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
self.bottomStripeNode.isLayerBacked = true self.bottomStripeNode.isLayerBacked = true
self.imageNode = TransformImageNode() self.imageNode = TransformImageNode()
self.imageNode.isLayerBacked = true self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
self.titleNode = TextNode() self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false self.titleNode.isUserInteractionEnabled = false

View File

@ -59,7 +59,7 @@ public class LocationBroadcastActionSheetItemNode: ActionSheetItemNode {
self.button = HighlightTrackingButton() self.button = HighlightTrackingButton()
self.avatarNode = AvatarNode(font: avatarFont) self.avatarNode = AvatarNode(font: avatarFont)
self.avatarNode.isLayerBacked = true self.avatarNode.isLayerBacked = !smartInvertColorsEnabled()
self.label = ImmediateTextNode() self.label = ImmediateTextNode()
self.label.isUserInteractionEnabled = false self.label.isUserInteractionEnabled = false

View File

@ -220,8 +220,8 @@ final class MultiplexedVideoNode: UIScrollView, UIScrollViewDelegate {
self.visibleThumbnailLayers[item.fileReference.media.fileId] = thumbnailLayer self.visibleThumbnailLayers[item.fileReference.media.fileId] = thumbnailLayer
} }
let progessSize = CGSize(width: 24.0, height: 24.0) let progressSize = 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 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) let updatedStatusSignal = account.postbox.mediaBox.resourceStatus(item.fileReference.media.resource)

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

View File

@ -10,6 +10,8 @@ private final class NotificationsAndSoundsArguments {
let pushController: (ViewController) -> Void let pushController: (ViewController) -> Void
let soundSelectionDisposable: MetaDisposable let soundSelectionDisposable: MetaDisposable
let enableNotifications: () -> Void
let updateMessageAlerts: (Bool) -> Void let updateMessageAlerts: (Bool) -> Void
let updateMessagePreviews: (Bool) -> Void let updateMessagePreviews: (Bool) -> Void
let updateMessageSound: (PeerMessageSound) -> Void let updateMessageSound: (PeerMessageSound) -> Void
@ -37,11 +39,12 @@ private final class NotificationsAndSoundsArguments {
let openAppSettings: () -> Void let openAppSettings: () -> Void
init(account: Account, presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, pushController: @escaping(ViewController)->Void, soundSelectionDisposable: MetaDisposable, 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.account = account
self.presentController = presentController self.presentController = presentController
self.pushController = pushController self.pushController = pushController
self.soundSelectionDisposable = soundSelectionDisposable self.soundSelectionDisposable = soundSelectionDisposable
self.enableNotifications = enableNotifications
self.updateMessageAlerts = updateMessageAlerts self.updateMessageAlerts = updateMessageAlerts
self.updateMessagePreviews = updateMessagePreviews self.updateMessagePreviews = updateMessagePreviews
self.updateMessageSound = updateMessageSound self.updateMessageSound = updateMessageSound
@ -65,6 +68,7 @@ private final class NotificationsAndSoundsArguments {
} }
private enum NotificationsAndSoundsSection: Int32 { private enum NotificationsAndSoundsSection: Int32 {
case permission
case messages case messages
case groups case groups
case channels case channels
@ -75,6 +79,9 @@ private enum NotificationsAndSoundsSection: Int32 {
} }
private enum NotificationsAndSoundsEntry: ItemListNodeEntry { private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
case permissionInfo(PresentationTheme, PresentationStrings)
case permissionEnable(PresentationTheme, String)
case messageHeader(PresentationTheme, String) case messageHeader(PresentationTheme, String)
case messageAlerts(PresentationTheme, String, Bool) case messageAlerts(PresentationTheme, String, Bool)
case messagePreviews(PresentationTheme, String, Bool) case messagePreviews(PresentationTheme, String, Bool)
@ -117,6 +124,8 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
var section: ItemListSectionId { var section: ItemListSectionId {
switch self { switch self {
case .permissionInfo, .permissionEnable:
return NotificationsAndSoundsSection.permission.rawValue
case .messageHeader, .messageAlerts, .messagePreviews, .messageSound, .messageNotice, .userExceptions: case .messageHeader, .messageAlerts, .messagePreviews, .messageSound, .messageNotice, .userExceptions:
return NotificationsAndSoundsSection.messages.rawValue return NotificationsAndSoundsSection.messages.rawValue
case .groupHeader, .groupAlerts, .groupPreviews, .groupSound, .groupNotice, .groupExceptions: case .groupHeader, .groupAlerts, .groupPreviews, .groupSound, .groupNotice, .groupExceptions:
@ -136,75 +145,91 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
var stableId: Int32 { var stableId: Int32 {
switch self { switch self {
case .messageHeader: case .permissionInfo:
return 0 return 0
case .messageAlerts: case .permissionEnable:
return 1 return 1
case .messagePreviews: case .messageHeader:
return 2 return 2
case .messageSound: case .messageAlerts:
return 3 return 3
case .userExceptions: case .messagePreviews:
return 4 return 4
case .messageNotice: case .messageSound:
return 5 return 5
case .groupHeader: case .userExceptions:
return 6 return 6
case .groupAlerts: case .messageNotice:
return 7 return 7
case .groupPreviews: case .groupHeader:
return 8 return 8
case .groupSound: case .groupAlerts:
return 9 return 9
case .groupExceptions: case .groupPreviews:
return 10 return 10
case .groupNotice: case .groupSound:
return 11 return 11
case .channelHeader: case .groupExceptions:
return 12 return 12
case .channelAlerts: case .groupNotice:
return 13 return 13
case .channelPreviews: case .channelHeader:
return 14 return 14
case .channelSound: case .channelAlerts:
return 15 return 15
case .channelExceptions: case .channelPreviews:
return 16 return 16
case .channelNotice: case .channelSound:
return 17 return 17
case .inAppHeader: case .channelExceptions:
return 18 return 18
case .inAppSounds: case .channelNotice:
return 19 return 19
case .inAppVibrate: case .inAppHeader:
return 20 return 20
case .inAppPreviews: case .inAppSounds:
return 21 return 21
case .displayNamesOnLockscreen: case .inAppVibrate:
return 22 return 22
case .displayNamesOnLockscreenInfo: case .inAppPreviews:
return 23 return 23
case .badgeHeader: case .displayNamesOnLockscreen:
return 24 return 24
case .unreadCountStyle: case .displayNamesOnLockscreenInfo:
return 25 return 25
case .includePublicGroups: case .badgeHeader:
return 26 return 26
case .includeChannels: case .unreadCountStyle:
return 27 return 27
case .unreadCountCategory: case .includePublicGroups:
return 28 return 28
case .unreadCountCategoryInfo: case .includeChannels:
return 29 return 29
case .reset: case .unreadCountCategory:
return 30 return 30
case .resetNotice: case .unreadCountCategoryInfo:
return 31 return 31
case .reset:
return 32
case .resetNotice:
return 33
} }
} }
static func ==(lhs: NotificationsAndSoundsEntry, rhs: NotificationsAndSoundsEntry) -> Bool { static func ==(lhs: NotificationsAndSoundsEntry, rhs: NotificationsAndSoundsEntry) -> Bool {
switch lhs { 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): case let .messageHeader(lhsTheme, lhsText):
if case let .messageHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { if case let .messageHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true return true
@ -406,6 +431,12 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
func item(_ arguments: NotificationsAndSoundsArguments) -> ListViewItem { func item(_ arguments: NotificationsAndSoundsArguments) -> ListViewItem {
switch self { 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): case let .messageHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .messageAlerts(theme, text, value): 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] = [] 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(.messageHeader(presentationData.theme, presentationData.strings.Notifications_MessageNotifications))
entries.append(.messageAlerts(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsAlert, globalSettings.privateChats.enabled)) entries.append(.messageAlerts(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsAlert, globalSettings.privateChats.enabled))
entries.append(.messagePreviews(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsPreview, globalSettings.privateChats.displayPreviews)) 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 presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
var pushControllerImpl: ((ViewController) -> Void)? var pushControllerImpl: ((ViewController) -> Void)?
let notificationExceptions: Promise<(users: NotificationExceptionMode, groups: NotificationExceptionMode, channels: NotificationExceptionMode)> = Promise() let notificationExceptions: Promise<(users: NotificationExceptionMode, groups: NotificationExceptionMode, channels: NotificationExceptionMode)> = Promise()
let updateNotificationExceptions:((users: NotificationExceptionMode, groups: NotificationExceptionMode, channels: NotificationExceptionMode)) -> Void = { value in 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) presentControllerImpl?(controller, arguments)
}, pushController: { controller in }, pushController: { controller in
pushControllerImpl?(controller) 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 let _ = updateGlobalNotificationSettingsInteractively(postbox: account.postbox, { settings in
return settings.withUpdatedPrivateChats { return settings.withUpdatedPrivateChats {
return $0.withUpdatedEnabled(value) 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()) let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, preferences, notificationExceptions.get(), DeviceAccess.authorizationStatus(account: account, subject: .notifications))
|> map { presentationData, view, exceptions -> (ItemListControllerState, (ItemListNodeState<NotificationsAndSoundsEntry>, NotificationsAndSoundsEntry.ItemGenerationArguments)) in |> map { presentationData, view, exceptions, authorizationStatus -> (ItemListControllerState, (ItemListNodeState<NotificationsAndSoundsEntry>, NotificationsAndSoundsEntry.ItemGenerationArguments)) in
let viewSettings: GlobalNotificationSettingsSet let viewSettings: GlobalNotificationSettingsSet
if let settings = view.values[PreferencesKeys.globalNotifications] as? GlobalNotificationSettings { 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 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)) return (controllerState, (listState, arguments))
} }

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

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

View File

@ -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)) let websitesSignal: Signal<([WebAuthorization], [PeerId : Peer])?, NoError> = .single(nil) |> then(webSessions(network: account.network) |> map(Optional.init))
websitesPromise.set(websitesSignal) websitesPromise.set(websitesSignal)
let previousMode = Atomic<RecentSessionsMode>(value: .sessions) let previousMode = Atomic<RecentSessionsMode>(value: .sessions)
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, mode.get(), statePromise.get(), sessionsPromise.get(), websitesPromise.get()) 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? var emptyStateItem: ItemListControllerEmptyStateItem?
if sessions == nil { if sessions == nil {
emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme) emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme)
} else if let sessions = sessions, sessions.count == 1 {
emptyStateItem = RecentSessionsEmptyStateItem(theme: presentationData.theme, strings: presentationData.strings)
} }
let title: ItemListControllerTitle let title: ItemListControllerTitle

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

View File

@ -89,14 +89,12 @@ final class SelectablePeerNode: ASDisplayNode {
self.avatarNode = AvatarNode(font: avatarFont) self.avatarNode = AvatarNode(font: avatarFont)
self.avatarNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 60.0, height: 60.0)) 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 = ASTextNode()
self.textNode.isUserInteractionEnabled = false self.textNode.isUserInteractionEnabled = false
self.textNode.displaysAsynchronously = true self.textNode.displaysAsynchronously = true
super.init() super.init()
self.avatarNodeContainer.addSubnode(self.avatarSelectionNode) self.avatarNodeContainer.addSubnode(self.avatarSelectionNode)

View File

@ -71,7 +71,7 @@ private enum SettingsEntry: ItemListNodeEntry {
case recentCalls(PresentationTheme, UIImage?, String) case recentCalls(PresentationTheme, UIImage?, String)
case stickers(PresentationTheme, UIImage?, String, String, [ArchivedStickerPackItem]?) 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 privacyAndSecurity(PresentationTheme, UIImage?, String)
case dataAndStorage(PresentationTheme, UIImage?, String) case dataAndStorage(PresentationTheme, UIImage?, String)
case themes(PresentationTheme, UIImage?, String) case themes(PresentationTheme, UIImage?, String)
@ -209,8 +209,8 @@ private enum SettingsEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .notificationsAndSounds(lhsTheme, lhsImage, lhsText, lhsExceptionsList): case let .notificationsAndSounds(lhsTheme, lhsImage, lhsText, lhsExceptionsList, lhsWarning):
if case let .notificationsAndSounds(rhsTheme, rhsImage, rhsText, rhsExceptionsList) = rhs, lhsTheme === rhsTheme, lhsImage === rhsImage, lhsText == rhsText, lhsExceptionsList == rhsExceptionsList { if case let .notificationsAndSounds(rhsTheme, rhsImage, rhsText, rhsExceptionsList, rhsWarning) = rhs, lhsTheme === rhsTheme, lhsImage === rhsImage, lhsText == rhsText, lhsExceptionsList == rhsExceptionsList, lhsWarning == rhsWarning {
return true return true
} else { } else {
return false return false
@ -302,13 +302,13 @@ private enum SettingsEntry: ItemListNodeEntry {
arguments.openRecentCalls() arguments.openRecentCalls()
}) })
case let .stickers(theme, image, text, value, archivedPacks): 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.pushController(installedStickerPacksController(account: arguments.account, mode: .general, archivedPacks: archivedPacks, updatedPacks: { packs in
arguments.updateArchivedPacks(packs) arguments.updateArchivedPacks(packs)
})) }))
}) })
case let .notificationsAndSounds(theme, image, text, exceptionsList): case let .notificationsAndSounds(theme, image, text, exceptionsList, warning):
return ItemListDisclosureItem(theme: theme, icon: image, title: text, label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: { 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)) arguments.pushController(notificationsAndSoundsController(account: arguments.account, exceptionsList: exceptionsList))
}) })
case let .privacyAndSecurity(theme, image, text): 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] = [] var entries: [SettingsEntry] = []
if let peer = peerViewMainPeer(view) as? TelegramUser { 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(.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(.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(.privacyAndSecurity(presentationData.theme, SettingsItemIcons.security, presentationData.strings.Settings_PrivacySettings))
entries.append(.dataAndStorage(presentationData.theme, SettingsItemIcons.dataAndStorage, presentationData.strings.Settings_ChatSettings)) entries.append(.dataAndStorage(presentationData.theme, SettingsItemIcons.dataAndStorage, presentationData.strings.Settings_ChatSettings))
entries.append(.themes(presentationData.theme, SettingsItemIcons.appearance, presentationData.strings.Settings_Appearance)) entries.append(.themes(presentationData.theme, SettingsItemIcons.appearance, presentationData.strings.Settings_Appearance))
@ -673,19 +673,20 @@ public func settingsController(account: Account, accountManager: AccountManager)
} }
updatePassport() updatePassport()
let notificationAuthorizationStatus = Promise<AccessType>(.allowed)
notificationAuthorizationStatus.set(DeviceAccess.authorizationStatus(account: account, subject: .notifications))
let notifyExceptions = Promise<NotificationExceptionsList?>(nil) let notifyExceptions = Promise<NotificationExceptionsList?>(nil)
let updateNotifyExceptions: () -> Void = { let updateNotifyExceptions: () -> Void = {
notifyExceptions.set(notificationExceptionsList(network: account.network) |> map(Optional.init)) notifyExceptions.set(notificationExceptionsList(network: account.network) |> map(Optional.init))
} }
// updateNotifyExceptions()
let hasWatchApp = Promise<Bool>(false) let hasWatchApp = Promise<Bool>(false)
if let context = account.applicationContext as? TelegramApplicationContext, let watchManager = context.watchManager { if let context = account.applicationContext as? TelegramApplicationContext, let watchManager = context.watchManager {
hasWatchApp.set(watchManager.watchAppInstalled) 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 |> 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 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 (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)) return (controllerState, (listState, arguments))
} |> afterDisposed { } |> afterDisposed {
@ -717,8 +718,8 @@ public func settingsController(account: Account, accountManager: AccountManager)
let icon = UIImage(bundleImageName: "Chat List/Tabs/IconSettings") let icon = UIImage(bundleImageName: "Chat List/Tabs/IconSettings")
let controller = ItemListController(account: account, state: signal, tabBarItem: (account.applicationContext as! TelegramApplicationContext).presentationData |> map { presentationData in let controller = ItemListController(account: account, state: signal, tabBarItem: combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, notificationAuthorizationStatus.get()) |> map { presentationData, status in
return ItemListControllerTabBarItem(title: presentationData.strings.Settings_Title, image: icon, selectedImage: icon) return ItemListControllerTabBarItem(title: presentationData.strings.Settings_Title, image: icon, selectedImage: icon, badgeValue: status != .allowed ? "!" : nil)
}) })
pushControllerImpl = { [weak controller] value in pushControllerImpl = { [weak controller] value in
(controller?.navigationController as? NavigationController)?.replaceAllButRootController(value, animated: true) (controller?.navigationController as? NavigationController)?.replaceAllButRootController(value, animated: true)

View File

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

View File

@ -64,7 +64,7 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
override init() { override init() {
self.imageNode = TransformImageNode() self.imageNode = TransformImageNode()
self.imageNode.isLayerBacked = true self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
//self.imageNode.alphaTransitionOnFirstUpdate = true //self.imageNode.alphaTransitionOnFirstUpdate = true
self.textNode = ASTextNode() self.textNode = ASTextNode()

View File

@ -79,7 +79,6 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
self.approximateDuration = approximateDuration self.approximateDuration = approximateDuration
self.imageNode = TransformImageNode() self.imageNode = TransformImageNode()
self.imageNode.isLayerBacked = true
self.playerItem = AVPlayerItem(url: URL(string: url)!) self.playerItem = AVPlayerItem(url: URL(string: url)!)
let player = AVPlayer(playerItem: self.playerItem) let player = AVPlayer(playerItem: self.playerItem)

View File

@ -26,11 +26,12 @@ public final class TelegramApplicationBindings {
public let pushIdleTimerExtension: () -> Disposable public let pushIdleTimerExtension: () -> Disposable
public let openSettings: () -> Void public let openSettings: () -> Void
public let openAppStorePage: () -> Void public let openAppStorePage: () -> Void
public let registerForNotifications: () -> Void
public let getWindowHost: () -> WindowHost? public let getWindowHost: () -> WindowHost?
public let presentNativeController: (UIViewController) -> Void public let presentNativeController: (UIViewController) -> Void
public let dismissNativeController: () -> 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.isMainApp = isMainApp
self.openUrl = openUrl self.openUrl = openUrl
self.openUniversalUrl = openUniversalUrl self.openUniversalUrl = openUniversalUrl
@ -43,6 +44,7 @@ public final class TelegramApplicationBindings {
self.pushIdleTimerExtension = pushIdleTimerExtension self.pushIdleTimerExtension = pushIdleTimerExtension
self.openSettings = openSettings self.openSettings = openSettings
self.openAppStorePage = openAppStorePage self.openAppStorePage = openAppStorePage
self.registerForNotifications = registerForNotifications
self.presentNativeController = presentNativeController self.presentNativeController = presentNativeController
self.dismissNativeController = dismissNativeController self.dismissNativeController = dismissNativeController
self.getWindowHost = getWindowHost self.getWindowHost = getWindowHost

View File

@ -14,6 +14,7 @@ public final class TelegramRootController: NavigationController {
public var chatListController: ChatListController? public var chatListController: ChatListController?
public var accountSettingsController: ViewController? public var accountSettingsController: ViewController?
private var permissionsDisposable: Disposable?
private var presentationDataDisposable: Disposable? private var presentationDataDisposable: Disposable?
private var presentationData: PresentationData private var presentationData: PresentationData
@ -24,6 +25,8 @@ public final class TelegramRootController: NavigationController {
super.init(mode: .automaticMasterDetail, theme: NavigationControllerTheme(presentationTheme: self.presentationData.theme)) super.init(mode: .automaticMasterDetail, theme: NavigationControllerTheme(presentationTheme: self.presentationData.theme))
//self.permissionsDisposable =
self.presentationDataDisposable = (account.telegramApplicationContext.presentationData self.presentationDataDisposable = (account.telegramApplicationContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in |> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self { if let strongSelf = self {
@ -42,6 +45,7 @@ public final class TelegramRootController: NavigationController {
} }
deinit { deinit {
self.permissionsDisposable?.dispose()
self.presentationDataDisposable?.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) 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))
}
})
}
} }

View File

@ -31,6 +31,14 @@ public class TransformImageNode: ASDisplayNode {
self.disposable.dispose() 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 { override public var frame: CGRect {
didSet { didSet {
if let overlayNode = self.overlayNode { if let overlayNode = self.overlayNode {

View File

@ -117,7 +117,7 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
self.iconImageNode = TransformImageNode() self.iconImageNode = TransformImageNode()
self.iconImageNode.contentAnimations = [.subsequentUpdates] self.iconImageNode.contentAnimations = [.subsequentUpdates]
self.iconImageNode.isLayerBacked = true self.iconImageNode.isLayerBacked = !smartInvertColorsEnabled()
self.iconImageNode.displaysAsynchronously = false self.iconImageNode.displaysAsynchronously = false
super.init(layerBacked: false, dynamicBounce: 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)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel))
let progressSize = CGSize(width: 24.0, height: 24.0)
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 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 { if let updatedStatusSignal = updatedStatusSignal {
strongSelf.statusDisposable.set((updatedStatusSignal |> deliverOnMainQueue).start(next: { [weak strongSelf] status in strongSelf.statusDisposable.set((updatedStatusSignal |> deliverOnMainQueue).start(next: { [weak strongSelf] status in