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

View File

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

View File

@ -177,6 +177,14 @@ public final class AvatarNode: ASDisplayNode {
self.addSubnode(self.imageNode)
}
override public func didLoad() {
super.didLoad()
if #available(iOSApplicationExtension 11.0, *), !self.isLayerBacked {
self.view.accessibilityIgnoresInvertColors = true
}
}
override public var frame: CGRect {
get {
return super.frame

View File

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

View File

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

View File

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

View File

@ -218,7 +218,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
self.textNode = ImmediateTextNode()
self.textNode.maximumNumberOfLines = 10
self.textNode.linkHighlightColor = UIColor(white: 1.0, alpha: 0.4)
self.textNode.linkHighlightColor = UIColor(rgb: 0x5ac8fa, alpha: 0.2)
self.authorNameNode = ASTextNode()
self.authorNameNode.maximumNumberOfLines = 1

View File

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

View File

@ -80,7 +80,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
self.highlightNode.isHidden = true
self.imageNode = TransformImageNode()
self.imageNode.isLayerBacked = true
self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
self.highlightNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - highlightSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - highlightSize.height) / 2.0)), size: highlightSize)

View File

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

View File

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

View File

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

View File

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

View File

@ -2,11 +2,14 @@ import Foundation
import UIKit
import AVFoundation
import Display
import TelegramCore
import SwiftSignalKit
import Photos
import CoreLocation
import Contacts
import AddressBook
import UserNotifications
import CoreTelephony
import LegacyComponents
@ -33,9 +36,13 @@ public enum DeviceAccessSubject {
case mediaLibrary(DeviceAccessMediaLibrarySubject)
case location(DeviceAccessLocationSubject)
case contacts
case notifications
case siri
case cellularData
}
private enum AccessType {
public enum AccessType {
case notDetermined
case allowed
case denied
case restricted
@ -49,11 +56,109 @@ public final class DeviceAccess {
return self.contactsPromise.get()
}
private static let notificationsPromise = Promise<AccessType?>(nil)
public static func isMicrophoneAccessAuthorized() -> Bool? {
return AVAudioSession.sharedInstance().recordPermission() == .granted
}
public static func authorizeAccess(to subject: DeviceAccessSubject, presentationData: PresentationData, present: @escaping (ViewController, Any?) -> Void, openSettings: @escaping () -> Void, displayNotificatoinFromBackground: @escaping (String) -> Void = { _ in }, _ completion: @escaping (Bool) -> Void) {
public static func authorizationStatus(account: Account, subject: DeviceAccessSubject) -> Signal<AccessType, NoError> {
switch subject {
case .notifications:
let status = Signal<AccessType, NoError> { subscriber in
if #available(iOSApplicationExtension 10.0, *) {
UNUserNotificationCenter.current().getNotificationSettings(completionHandler: { settings in
switch settings.authorizationStatus {
case .authorized:
subscriber.putNext(.allowed)
case .denied:
subscriber.putNext(.denied)
case .notDetermined:
subscriber.putNext(.notDetermined)
default:
subscriber.putNext(.notDetermined)
}
subscriber.putCompletion()
})
} else {
subscriber.putNext(.notDetermined)
subscriber.putCompletion()
}
return EmptyDisposable
}
return account.telegramApplicationContext.applicationBindings.applicationInForeground
|> distinctUntilChanged
|> mapToSignal { inForeground -> Signal<AccessType, NoError> in
return status
}
case .contacts:
let status = Signal<AccessType, NoError> { subscriber in
if #available(iOSApplicationExtension 9.0, *) {
switch CNContactStore.authorizationStatus(for: .contacts) {
case .notDetermined:
subscriber.putNext(.notDetermined)
case .authorized:
subscriber.putNext(.allowed)
default:
subscriber.putNext(.denied)
}
subscriber.putCompletion()
} else {
switch ABAddressBookGetAuthorizationStatus() {
case .notDetermined:
subscriber.putNext(.notDetermined)
case .authorized:
subscriber.putNext(.allowed)
default:
subscriber.putNext(.denied)
}
subscriber.putCompletion()
}
return EmptyDisposable
}
return status
|> then(self.contacts
|> mapToSignal { authorized -> Signal<AccessType, NoError> in
if let authorized = authorized {
return .single(authorized ? .allowed : .denied)
} else {
return .complete()
}
})
case .cellularData:
return Signal { subscriber in
if #available(iOSApplicationExtension 9.0, *) {
func statusForCellularState(_ state: CTCellularDataRestrictedState) -> AccessType? {
switch state {
case .restricted:
return .denied
case .notRestricted:
return .allowed
default:
return nil
}
}
let cellState = CTCellularData.init()
if let status = statusForCellularState(cellState.restrictedState) {
subscriber.putNext(status)
}
cellState.cellularDataRestrictionDidUpdateNotifier = { restrictedState in
if let status = statusForCellularState(restrictedState) {
subscriber.putNext(status)
}
}
} else {
subscriber.putNext(.notDetermined)
subscriber.putCompletion()
}
return EmptyDisposable
}
default:
return .single(.notDetermined)
}
}
public static func authorizeAccess(to subject: DeviceAccessSubject, presentationData: PresentationData, present: @escaping (ViewController, Any?) -> Void, openSettings: @escaping () -> Void, displayNotificationFromBackground: @escaping (String) -> Void = { _ in }, _ completion: @escaping (Bool) -> Void) {
switch subject {
case .camera:
let status = PGCamera.cameraAuthorizationStatus()
@ -108,7 +213,7 @@ public final class DeviceAccess {
openSettings()
})]), nil)
if case .voiceCall = microphoneSubject {
displayNotificatoinFromBackground(text)
displayNotificationFromBackground(text)
}
}
})
@ -231,6 +336,8 @@ public final class DeviceAccess {
}
}
})
default:
break
}
}
}

View File

@ -559,6 +559,7 @@ final class ContactListNode: ASDisplayNode {
var activateSearch: (() -> Void)?
var openPeer: ((ContactListPeer) -> Void)?
var openPrivacyPolicy: (() -> Void)?
private let previousEntries = Atomic<[ContactListNodeEntry]?>(value: nil)
private let disposable = MetaDisposable()
@ -567,6 +568,8 @@ final class ContactListNode: ASDisplayNode {
private var presentationDataDisposable: Disposable?
private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, Bool)>
private let authorizationNode: PermissionControllerNode
init(account: Account, presentation: ContactListPresentation, filters: [ContactListFilter] = [.excludeSelf], selectionState: ContactListNodeGroupSelectionState? = nil) {
self.account = account
self.presentation = presentation
@ -579,6 +582,9 @@ final class ContactListNode: ASDisplayNode {
self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings, self.presentationData.dateTimeFormat, self.presentationData.nameSortOrder, self.presentationData.nameDisplayOrder, self.presentationData.disableAnimations))
self.authorizationNode = PermissionControllerNode(theme: defaultLightAuthorizationTheme, strings: self.presentationData.strings)
self.authorizationNode.updateData(subject: .contacts, currentStatus: .denied)
super.init()
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
@ -586,8 +592,9 @@ final class ContactListNode: ASDisplayNode {
self.selectionStateValue = selectionState
self.selectionStatePromise.set(.single(selectionState))
self.addSubnode(self.listNode)
//self.addSubnode(self.listNode)
self.addSubnode(self.authorizationNode)
let processingQueue = Queue()
let previousEntries = Atomic<[ContactListNodeEntry]?>(value: nil)
@ -808,6 +815,13 @@ final class ContactListNode: ASDisplayNode {
fixSearchableListNodeScrolling(strongSelf.listNode)
}
self.authorizationNode.allow = { [weak self] in
self?.account.telegramApplicationContext.applicationBindings.openSettings()
}
self.authorizationNode.openPrivacyPolicy = { [weak self] in
self?.openPrivacyPolicy?()
}
self.enableUpdates = true
}
@ -823,6 +837,11 @@ final class ContactListNode: ASDisplayNode {
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let result = super.hitTest(point, with: event)
return result
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
var insets = layout.insets(options: [.input])
insets.left += layout.safeInsets.left
@ -857,6 +876,10 @@ final class ContactListNode: ASDisplayNode {
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
let sublayout = layout.addedInsets(insets: UIEdgeInsetsMake(0.0, 0.0, 40.0, 0.0))
transition.updateFrame(node: self.authorizationNode, frame: self.bounds)
self.authorizationNode.containerLayoutUpdated(sublayout, navigationBarHeight: 0.0, transition: transition)
if !self.hasValidLayout {
self.hasValidLayout = true
self.dequeueTransitions()

View File

@ -21,7 +21,7 @@ public class ContactsController: ViewController {
private var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
private var contactsAccessDisposable: Disposable?
private var authorizationDisposable: Disposable?
public init(account: Account) {
self.account = account
@ -49,18 +49,25 @@ public class ContactsController: ViewController {
}
self.presentationDataDisposable = (account.telegramApplicationContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
let previousTheme = strongSelf.presentationData.theme
let previousStrings = strongSelf.presentationData.strings
strongSelf.presentationData = presentationData
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
strongSelf.updateThemeAndStrings()
}
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
let previousTheme = strongSelf.presentationData.theme
let previousStrings = strongSelf.presentationData.strings
strongSelf.presentationData = presentationData
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
strongSelf.updateThemeAndStrings()
}
})
}
})
self.authorizationDisposable = (DeviceAccess.authorizationStatus(account: account, subject: .contacts)
|> deliverOnMainQueue).start(next: { [weak self] status in
if let strongSelf = self {
strongSelf.tabBarItem.badgeValue = status != .allowed ? "!" : nil
}
})
}
required public init(coder aDecoder: NSCoder) {
@ -69,7 +76,7 @@ public class ContactsController: ViewController {
deinit {
self.presentationDataDisposable?.dispose()
self.contactsAccessDisposable?.dispose()
self.authorizationDisposable?.dispose()
}
private func updateThemeAndStrings() {
@ -107,6 +114,12 @@ public class ContactsController: ViewController {
}*/
}
self.contactsNode.contactListNode.openPrivacyPolicy = { [weak self] in
if let strongSelf = self {
openExternalUrl(account: strongSelf.account, context: .generic, url: "https://telegram.org/privacy", forceExternal: true, presentationData: strongSelf.presentationData, applicationContext: strongSelf.account.telegramApplicationContext, navigationController: strongSelf.navigationController as? NavigationController, dismissInput: {})
}
}
self.contactsNode.contactListNode.activateSearch = { [weak self] in
self?.activateSearch()
}

View File

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

View File

@ -305,8 +305,6 @@ class GalleryController: ViewController {
private var performAction: (GalleryControllerInteractionTapAction) -> Void
private var openActionOptions: (GalleryControllerInteractionTapAction) -> Void
private let resolveDisposable = MetaDisposable()
init(account: Account, source: GalleryControllerItemSource, invertItemOrder: Bool = false, streamSingleVideo: Bool = false, synchronousLoad: Bool = false, replaceRootController: @escaping (ViewController, ValuePromise<Bool>?) -> Void, baseNavigationController: NavigationController?, actionInteraction: GalleryControllerActionInteraction? = nil) {
self.account = account
self.replaceRootController = replaceRootController
@ -686,7 +684,6 @@ class GalleryController: ViewController {
deinit {
self.disposable.dispose()
self.resolveDisposable.dispose()
self.centralItemAttributesDisposable.dispose()
if let hiddenMediaManagerIndex = self.hiddenMediaManagerIndex, let mediaManager = self.account.telegramApplicationContext.mediaManager {
mediaManager.galleryHiddenMediaManager.removeSource(hiddenMediaManagerIndex)

View File

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

View File

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

View File

@ -148,7 +148,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
self.imageNode = TransformImageNode()
self.imageNode.contentAnimations = [.subsequentUpdates]
self.imageNode.isLayerBacked = true
self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
self.imageNode.displaysAsynchronously = false
var timebase: CMTimebase?
@ -350,7 +350,8 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
}
}
let progressFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((nodeLayout.contentSize.width - 37) / 2), y: floorToScreenPixels((nodeLayout.contentSize.height - 37) / 2)), size: CGSize(width: 37, height: 37))
let progressSize = CGSize(width: 24.0, height: 24.0)
let progressFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((nodeLayout.contentSize.width - progressSize.width) / 2.0), y: floorToScreenPixels((nodeLayout.contentSize.height - progressSize.height) / 2.0)), size: progressSize)
strongSelf.statusNode.removeFromSupernode()
strongSelf.addSubnode(strongSelf.statusNode)

View File

@ -236,7 +236,7 @@ private enum InstalledStickerPacksEntry: ItemListNodeEntry {
arguments.openSuggestOptions()
})
case let .trending(theme, text, count):
return ItemListDisclosureItem(theme: theme, title: text, label: count == 0 ? "" : "\(count)", labelStyle: .badge, sectionId: self.section, style: .blocks, action: {
return ItemListDisclosureItem(theme: theme, title: text, label: count == 0 ? "" : "\(count)", labelStyle: .badge(theme.list.itemAccentColor), sectionId: self.section, style: .blocks, action: {
arguments.openFeatured()
})
case let .masks(theme, text):

View File

@ -36,7 +36,7 @@ final class InstantPageGalleryFooterContentNode: GalleryFooterContentNode {
self.textNode = ImmediateTextNode()
self.textNode.maximumNumberOfLines = 10
self.textNode.insets = UIEdgeInsets(top: 8.0, left: 0.0, bottom: 8.0, right: 0.0)
self.textNode.linkHighlightColor = UIColor(white: 1.0, alpha: 0.4)
self.textNode.linkHighlightColor = UIColor(rgb: 0x5ac8fa, alpha: 0.2)
super.init()

View File

@ -93,15 +93,17 @@ final class ItemListControllerTabBarItem: Equatable {
let title: String
let image: UIImage?
let selectedImage: UIImage?
let badgeValue: String?
init(title: String, image: UIImage?, selectedImage: UIImage?) {
init(title: String, image: UIImage?, selectedImage: UIImage?, badgeValue: String? = nil) {
self.title = title
self.image = image
self.selectedImage = selectedImage
self.badgeValue = badgeValue
}
static func ==(lhs: ItemListControllerTabBarItem, rhs: ItemListControllerTabBarItem) -> Bool {
return lhs.title == rhs.title && lhs.image === rhs.image && lhs.selectedImage === rhs.selectedImage
return lhs.title == rhs.title && lhs.image === rhs.image && lhs.selectedImage === rhs.selectedImage && lhs.badgeValue == rhs.badgeValue
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -220,8 +220,8 @@ final class MultiplexedVideoNode: UIScrollView, UIScrollViewDelegate {
self.visibleThumbnailLayers[item.fileReference.media.fileId] = thumbnailLayer
}
let progessSize = CGSize(width: 24.0, height: 24.0)
let progressFrame = CGRect(origin: CGPoint(x: item.frame.midX - progessSize.width / 2.0, y: item.frame.midY - progessSize.height / 2.0), size: progessSize)
let progressSize = CGSize(width: 24.0, height: 24.0)
let progressFrame = CGRect(origin: CGPoint(x: item.frame.midX - progressSize.width / 2.0, y: item.frame.midY - progressSize.height / 2.0), size: progressSize)
let updatedStatusSignal = account.postbox.mediaBox.resourceStatus(item.fileReference.media.resource)

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 soundSelectionDisposable: MetaDisposable
let enableNotifications: () -> Void
let updateMessageAlerts: (Bool) -> Void
let updateMessagePreviews: (Bool) -> Void
let updateMessageSound: (PeerMessageSound) -> Void
@ -37,11 +39,12 @@ private final class NotificationsAndSoundsArguments {
let openAppSettings: () -> Void
init(account: Account, presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, pushController: @escaping(ViewController)->Void, soundSelectionDisposable: MetaDisposable, updateMessageAlerts: @escaping (Bool) -> Void, updateMessagePreviews: @escaping (Bool) -> Void, updateMessageSound: @escaping (PeerMessageSound) -> Void, updateGroupAlerts: @escaping (Bool) -> Void, updateGroupPreviews: @escaping (Bool) -> Void, updateGroupSound: @escaping (PeerMessageSound) -> Void, updateChannelAlerts: @escaping (Bool) -> Void, updateChannelPreviews: @escaping (Bool) -> Void, updateChannelSound: @escaping (PeerMessageSound) -> Void, updateInAppSounds: @escaping (Bool) -> Void, updateInAppVibration: @escaping (Bool) -> Void, updateInAppPreviews: @escaping (Bool) -> Void, updateDisplayNameOnLockscreen: @escaping (Bool) -> Void, updateTotalUnreadCountStyle: @escaping (Bool) -> Void, updateIncludeTag: @escaping (PeerSummaryCounterTags, Bool) -> Void, updateTotalUnreadCountCategory: @escaping (Bool) -> Void, resetNotifications: @escaping () -> Void, updatedExceptionMode: @escaping(NotificationExceptionMode) -> Void, openAppSettings: @escaping () -> Void) {
init(account: Account, presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, pushController: @escaping(ViewController)->Void, soundSelectionDisposable: MetaDisposable, enableNotifications: @escaping () -> Void, updateMessageAlerts: @escaping (Bool) -> Void, updateMessagePreviews: @escaping (Bool) -> Void, updateMessageSound: @escaping (PeerMessageSound) -> Void, updateGroupAlerts: @escaping (Bool) -> Void, updateGroupPreviews: @escaping (Bool) -> Void, updateGroupSound: @escaping (PeerMessageSound) -> Void, updateChannelAlerts: @escaping (Bool) -> Void, updateChannelPreviews: @escaping (Bool) -> Void, updateChannelSound: @escaping (PeerMessageSound) -> Void, updateInAppSounds: @escaping (Bool) -> Void, updateInAppVibration: @escaping (Bool) -> Void, updateInAppPreviews: @escaping (Bool) -> Void, updateDisplayNameOnLockscreen: @escaping (Bool) -> Void, updateTotalUnreadCountStyle: @escaping (Bool) -> Void, updateIncludeTag: @escaping (PeerSummaryCounterTags, Bool) -> Void, updateTotalUnreadCountCategory: @escaping (Bool) -> Void, resetNotifications: @escaping () -> Void, updatedExceptionMode: @escaping(NotificationExceptionMode) -> Void, openAppSettings: @escaping () -> Void) {
self.account = account
self.presentController = presentController
self.pushController = pushController
self.soundSelectionDisposable = soundSelectionDisposable
self.enableNotifications = enableNotifications
self.updateMessageAlerts = updateMessageAlerts
self.updateMessagePreviews = updateMessagePreviews
self.updateMessageSound = updateMessageSound
@ -65,6 +68,7 @@ private final class NotificationsAndSoundsArguments {
}
private enum NotificationsAndSoundsSection: Int32 {
case permission
case messages
case groups
case channels
@ -75,6 +79,9 @@ private enum NotificationsAndSoundsSection: Int32 {
}
private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
case permissionInfo(PresentationTheme, PresentationStrings)
case permissionEnable(PresentationTheme, String)
case messageHeader(PresentationTheme, String)
case messageAlerts(PresentationTheme, String, Bool)
case messagePreviews(PresentationTheme, String, Bool)
@ -117,6 +124,8 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
var section: ItemListSectionId {
switch self {
case .permissionInfo, .permissionEnable:
return NotificationsAndSoundsSection.permission.rawValue
case .messageHeader, .messageAlerts, .messagePreviews, .messageSound, .messageNotice, .userExceptions:
return NotificationsAndSoundsSection.messages.rawValue
case .groupHeader, .groupAlerts, .groupPreviews, .groupSound, .groupNotice, .groupExceptions:
@ -136,75 +145,91 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
var stableId: Int32 {
switch self {
case .messageHeader:
case .permissionInfo:
return 0
case .messageAlerts:
case .permissionEnable:
return 1
case .messagePreviews:
case .messageHeader:
return 2
case .messageSound:
case .messageAlerts:
return 3
case .userExceptions:
case .messagePreviews:
return 4
case .messageNotice:
case .messageSound:
return 5
case .groupHeader:
case .userExceptions:
return 6
case .groupAlerts:
case .messageNotice:
return 7
case .groupPreviews:
case .groupHeader:
return 8
case .groupSound:
case .groupAlerts:
return 9
case .groupExceptions:
case .groupPreviews:
return 10
case .groupNotice:
case .groupSound:
return 11
case .channelHeader:
case .groupExceptions:
return 12
case .channelAlerts:
case .groupNotice:
return 13
case .channelPreviews:
case .channelHeader:
return 14
case .channelSound:
case .channelAlerts:
return 15
case .channelExceptions:
case .channelPreviews:
return 16
case .channelNotice:
case .channelSound:
return 17
case .inAppHeader:
case .channelExceptions:
return 18
case .inAppSounds:
case .channelNotice:
return 19
case .inAppVibrate:
case .inAppHeader:
return 20
case .inAppPreviews:
case .inAppSounds:
return 21
case .displayNamesOnLockscreen:
case .inAppVibrate:
return 22
case .displayNamesOnLockscreenInfo:
case .inAppPreviews:
return 23
case .badgeHeader:
case .displayNamesOnLockscreen:
return 24
case .unreadCountStyle:
case .displayNamesOnLockscreenInfo:
return 25
case .includePublicGroups:
case .badgeHeader:
return 26
case .includeChannels:
case .unreadCountStyle:
return 27
case .unreadCountCategory:
case .includePublicGroups:
return 28
case .unreadCountCategoryInfo:
case .includeChannels:
return 29
case .reset:
case .unreadCountCategory:
return 30
case .resetNotice:
case .unreadCountCategoryInfo:
return 31
case .reset:
return 32
case .resetNotice:
return 33
}
}
static func ==(lhs: NotificationsAndSoundsEntry, rhs: NotificationsAndSoundsEntry) -> Bool {
switch lhs {
case let .permissionInfo(lhsTheme, lhsStrings):
if case let .permissionInfo(rhsTheme, rhsStrings) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings {
return true
} else {
return false
}
case let .permissionEnable(lhsTheme, lhsText):
if case let .permissionEnable(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .messageHeader(lhsTheme, lhsText):
if case let .messageHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
@ -406,6 +431,12 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
func item(_ arguments: NotificationsAndSoundsArguments) -> ListViewItem {
switch self {
case let .permissionInfo(theme, strings):
return NotificationPermissionInfoItem(theme: theme, strings: strings, sectionId: self.section)
case let .permissionEnable(theme, text):
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.enableNotifications()
})
case let .messageHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .messageAlerts(theme, text, value):
@ -538,9 +569,20 @@ private func filteredGlobalSound(_ sound: PeerMessageSound) -> PeerMessageSound
}
}
private func notificationsAndSoundsEntries(globalSettings: GlobalNotificationSettingsSet, inAppSettings: InAppNotificationSettings, exceptions: (users: NotificationExceptionMode, groups: NotificationExceptionMode, channels: NotificationExceptionMode), presentationData: PresentationData) -> [NotificationsAndSoundsEntry] {
private func notificationsAndSoundsEntries(authorizationStatus: AccessType, globalSettings: GlobalNotificationSettingsSet, inAppSettings: InAppNotificationSettings, exceptions: (users: NotificationExceptionMode, groups: NotificationExceptionMode, channels: NotificationExceptionMode), presentationData: PresentationData) -> [NotificationsAndSoundsEntry] {
var entries: [NotificationsAndSoundsEntry] = []
switch authorizationStatus {
case .denied:
entries.append(.permissionInfo(presentationData.theme, presentationData.strings))
entries.append(.permissionEnable(presentationData.theme, "Turn ON in Settings"))
case .notDetermined:
entries.append(.permissionInfo(presentationData.theme, presentationData.strings))
entries.append(.permissionEnable(presentationData.theme, "Turn Notifications ON"))
default:
break
}
entries.append(.messageHeader(presentationData.theme, presentationData.strings.Notifications_MessageNotifications))
entries.append(.messageAlerts(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsAlert, globalSettings.privateChats.enabled))
entries.append(.messagePreviews(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsPreview, globalSettings.privateChats.displayPreviews))
@ -600,8 +642,6 @@ public func notificationsAndSoundsController(account: Account, exceptionsList: N
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
var pushControllerImpl: ((ViewController) -> Void)?
let notificationExceptions: Promise<(users: NotificationExceptionMode, groups: NotificationExceptionMode, channels: NotificationExceptionMode)> = Promise()
let updateNotificationExceptions:((users: NotificationExceptionMode, groups: NotificationExceptionMode, channels: NotificationExceptionMode)) -> Void = { value in
@ -612,7 +652,20 @@ public func notificationsAndSoundsController(account: Account, exceptionsList: N
presentControllerImpl?(controller, arguments)
}, pushController: { controller in
pushControllerImpl?(controller)
}, soundSelectionDisposable: MetaDisposable(), updateMessageAlerts: { value in
}, soundSelectionDisposable: MetaDisposable(), enableNotifications: {
let _ = (DeviceAccess.authorizationStatus(account: account, subject: .notifications)
|> take(1)
|> deliverOnMainQueue).start(next: { status in
switch status {
case .notDetermined:
account.telegramApplicationContext.applicationBindings.registerForNotifications()
case .denied, .restricted:
account.telegramApplicationContext.applicationBindings.openSettings()
default:
break
}
})
}, updateMessageAlerts: { value in
let _ = updateGlobalNotificationSettingsInteractively(postbox: account.postbox, { settings in
return settings.withUpdatedPrivateChats {
return $0.withUpdatedEnabled(value)
@ -799,8 +852,8 @@ public func notificationsAndSoundsController(account: Account, exceptionsList: N
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, preferences, notificationExceptions.get())
|> map { presentationData, view, exceptions -> (ItemListControllerState, (ItemListNodeState<NotificationsAndSoundsEntry>, NotificationsAndSoundsEntry.ItemGenerationArguments)) in
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, preferences, notificationExceptions.get(), DeviceAccess.authorizationStatus(account: account, subject: .notifications))
|> map { presentationData, view, exceptions, authorizationStatus -> (ItemListControllerState, (ItemListNodeState<NotificationsAndSoundsEntry>, NotificationsAndSoundsEntry.ItemGenerationArguments)) in
let viewSettings: GlobalNotificationSettingsSet
if let settings = view.values[PreferencesKeys.globalNotifications] as? GlobalNotificationSettings {
@ -817,7 +870,7 @@ public func notificationsAndSoundsController(account: Account, exceptionsList: N
}
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Notifications_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(entries: notificationsAndSoundsEntries(globalSettings: viewSettings, inAppSettings: inAppSettings, exceptions: exceptions, presentationData: presentationData), style: .blocks)
let listState = ItemListNodeState(entries: notificationsAndSoundsEntries(authorizationStatus: authorizationStatus, globalSettings: viewSettings, inAppSettings: inAppSettings, exceptions: exceptions, presentationData: presentationData), style: .blocks)
return (controllerState, (listState, arguments))
}

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))
websitesPromise.set(websitesSignal)
let previousMode = Atomic<RecentSessionsMode>(value: .sessions)
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, mode.get(), statePromise.get(), sessionsPromise.get(), websitesPromise.get())
@ -635,6 +634,8 @@ public func recentSessionsController(account: Account) -> ViewController {
var emptyStateItem: ItemListControllerEmptyStateItem?
if sessions == nil {
emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme)
} else if let sessions = sessions, sessions.count == 1 {
emptyStateItem = RecentSessionsEmptyStateItem(theme: presentationData.theme, strings: presentationData.strings)
}
let title: ItemListControllerTitle

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.frame = CGRect(origin: CGPoint(), size: CGSize(width: 60.0, height: 60.0))
self.avatarNode.isLayerBacked = true
self.avatarNode.isLayerBacked = !smartInvertColorsEnabled()
self.textNode = ASTextNode()
self.textNode.isUserInteractionEnabled = false
self.textNode.displaysAsynchronously = true
super.init()
self.avatarNodeContainer.addSubnode(self.avatarSelectionNode)

View File

@ -71,7 +71,7 @@ private enum SettingsEntry: ItemListNodeEntry {
case recentCalls(PresentationTheme, UIImage?, String)
case stickers(PresentationTheme, UIImage?, String, String, [ArchivedStickerPackItem]?)
case notificationsAndSounds(PresentationTheme, UIImage?, String, NotificationExceptionsList?)
case notificationsAndSounds(PresentationTheme, UIImage?, String, NotificationExceptionsList?, Bool)
case privacyAndSecurity(PresentationTheme, UIImage?, String)
case dataAndStorage(PresentationTheme, UIImage?, String)
case themes(PresentationTheme, UIImage?, String)
@ -209,8 +209,8 @@ private enum SettingsEntry: ItemListNodeEntry {
} else {
return false
}
case let .notificationsAndSounds(lhsTheme, lhsImage, lhsText, lhsExceptionsList):
if case let .notificationsAndSounds(rhsTheme, rhsImage, rhsText, rhsExceptionsList) = rhs, lhsTheme === rhsTheme, lhsImage === rhsImage, lhsText == rhsText, lhsExceptionsList == rhsExceptionsList {
case let .notificationsAndSounds(lhsTheme, lhsImage, lhsText, lhsExceptionsList, lhsWarning):
if case let .notificationsAndSounds(rhsTheme, rhsImage, rhsText, rhsExceptionsList, rhsWarning) = rhs, lhsTheme === rhsTheme, lhsImage === rhsImage, lhsText == rhsText, lhsExceptionsList == rhsExceptionsList, lhsWarning == rhsWarning {
return true
} else {
return false
@ -302,13 +302,13 @@ private enum SettingsEntry: ItemListNodeEntry {
arguments.openRecentCalls()
})
case let .stickers(theme, image, text, value, archivedPacks):
return ItemListDisclosureItem(theme: theme, icon: image, title: text, label: value, labelStyle: .badge, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
return ItemListDisclosureItem(theme: theme, icon: image, title: text, label: value, labelStyle: .badge(theme.list.itemAccentColor), sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.pushController(installedStickerPacksController(account: arguments.account, mode: .general, archivedPacks: archivedPacks, updatedPacks: { packs in
arguments.updateArchivedPacks(packs)
}))
})
case let .notificationsAndSounds(theme, image, text, exceptionsList):
return ItemListDisclosureItem(theme: theme, icon: image, title: text, label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
case let .notificationsAndSounds(theme, image, text, exceptionsList, warning):
return ItemListDisclosureItem(theme: theme, icon: image, title: text, label: warning ? "!" : "", labelStyle: warning ? .badge(theme.list.itemDestructiveColor) : .text, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.pushController(notificationsAndSoundsController(account: arguments.account, exceptionsList: exceptionsList))
})
case let .privacyAndSecurity(theme, image, text):
@ -366,7 +366,7 @@ private struct SettingsState: Equatable {
}
}
private func settingsEntries(presentationData: PresentationData, state: SettingsState, view: PeerView, proxySettings: ProxySettings, notifyExceptions: NotificationExceptionsList?, unreadTrendingStickerPacks: Int, archivedPacks: [ArchivedStickerPackItem]?, hasPassport: Bool, hasWatchApp: Bool) -> [SettingsEntry] {
private func settingsEntries(presentationData: PresentationData, state: SettingsState, view: PeerView, proxySettings: ProxySettings, notifyExceptions: NotificationExceptionsList?, notificationsAuthorizationStatus: AccessType, unreadTrendingStickerPacks: Int, archivedPacks: [ArchivedStickerPackItem]?, hasPassport: Bool, hasWatchApp: Bool) -> [SettingsEntry] {
var entries: [SettingsEntry] = []
if let peer = peerViewMainPeer(view) as? TelegramUser {
@ -398,7 +398,7 @@ private func settingsEntries(presentationData: PresentationData, state: Settings
entries.append(.recentCalls(presentationData.theme, SettingsItemIcons.recentCalls, presentationData.strings.CallSettings_RecentCalls))
entries.append(.stickers(presentationData.theme, SettingsItemIcons.stickers, presentationData.strings.ChatSettings_Stickers, unreadTrendingStickerPacks == 0 ? "" : "\(unreadTrendingStickerPacks)", archivedPacks))
entries.append(.notificationsAndSounds(presentationData.theme, SettingsItemIcons.notifications, presentationData.strings.Settings_NotificationsAndSounds, notifyExceptions))
entries.append(.notificationsAndSounds(presentationData.theme, SettingsItemIcons.notifications, presentationData.strings.Settings_NotificationsAndSounds, notifyExceptions, notificationsAuthorizationStatus != .allowed))
entries.append(.privacyAndSecurity(presentationData.theme, SettingsItemIcons.security, presentationData.strings.Settings_PrivacySettings))
entries.append(.dataAndStorage(presentationData.theme, SettingsItemIcons.dataAndStorage, presentationData.strings.Settings_ChatSettings))
entries.append(.themes(presentationData.theme, SettingsItemIcons.appearance, presentationData.strings.Settings_Appearance))
@ -673,19 +673,20 @@ public func settingsController(account: Account, accountManager: AccountManager)
}
updatePassport()
let notificationAuthorizationStatus = Promise<AccessType>(.allowed)
notificationAuthorizationStatus.set(DeviceAccess.authorizationStatus(account: account, subject: .notifications))
let notifyExceptions = Promise<NotificationExceptionsList?>(nil)
let updateNotifyExceptions: () -> Void = {
notifyExceptions.set(notificationExceptionsList(network: account.network) |> map(Optional.init))
}
// updateNotifyExceptions()
let hasWatchApp = Promise<Bool>(false)
if let context = account.applicationContext as? TelegramApplicationContext, let watchManager = context.watchManager {
hasWatchApp.set(watchManager.watchAppInstalled)
}
let signal = combineLatest(account.telegramApplicationContext.presentationData, statePromise.get(), peerView, combineLatest(account.postbox.preferencesView(keys: [PreferencesKeys.proxySettings]), notifyExceptions.get()), combineLatest(account.viewTracker.featuredStickerPacks(), archivedPacks.get()), combineLatest(hasPassport.get(), hasWatchApp.get()))
let signal = combineLatest(account.telegramApplicationContext.presentationData, statePromise.get(), peerView, combineLatest(account.postbox.preferencesView(keys: [PreferencesKeys.proxySettings]), notifyExceptions.get(), notificationAuthorizationStatus.get()), combineLatest(account.viewTracker.featuredStickerPacks(), archivedPacks.get()), combineLatest(hasPassport.get(), hasWatchApp.get()))
|> map { presentationData, state, view, preferencesAndExceptions, featuredAndArchived, hasPassportAndWatch -> (ItemListControllerState, (ItemListNodeState<SettingsEntry>, SettingsEntry.ItemGenerationArguments)) in
let proxySettings: ProxySettings = preferencesAndExceptions.0.values[PreferencesKeys.proxySettings] as? ProxySettings ?? ProxySettings.defaultSettings
@ -708,7 +709,7 @@ public func settingsController(account: Account, accountManager: AccountManager)
let (hasPassport, hasWatchApp) = hasPassportAndWatch
let listState = ItemListNodeState(entries: settingsEntries(presentationData: presentationData, state: state, view: view, proxySettings: proxySettings, notifyExceptions: preferencesAndExceptions.1, unreadTrendingStickerPacks: unreadTrendingStickerPacks, archivedPacks: featuredAndArchived.1, hasPassport: hasPassport, hasWatchApp: hasWatchApp), style: .blocks)
let listState = ItemListNodeState(entries: settingsEntries(presentationData: presentationData, state: state, view: view, proxySettings: proxySettings, notifyExceptions: preferencesAndExceptions.1, notificationsAuthorizationStatus: preferencesAndExceptions.2, unreadTrendingStickerPacks: unreadTrendingStickerPacks, archivedPacks: featuredAndArchived.1, hasPassport: hasPassport, hasWatchApp: hasWatchApp), style: .blocks)
return (controllerState, (listState, arguments))
} |> afterDisposed {
@ -717,8 +718,8 @@ public func settingsController(account: Account, accountManager: AccountManager)
let icon = UIImage(bundleImageName: "Chat List/Tabs/IconSettings")
let controller = ItemListController(account: account, state: signal, tabBarItem: (account.applicationContext as! TelegramApplicationContext).presentationData |> map { presentationData in
return ItemListControllerTabBarItem(title: presentationData.strings.Settings_Title, image: icon, selectedImage: icon)
let controller = ItemListController(account: account, state: signal, tabBarItem: combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, notificationAuthorizationStatus.get()) |> map { presentationData, status in
return ItemListControllerTabBarItem(title: presentationData.strings.Settings_Title, image: icon, selectedImage: icon, badgeValue: status != .allowed ? "!" : nil)
})
pushControllerImpl = { [weak controller] value in
(controller?.navigationController as? NavigationController)?.replaceAllButRootController(value, animated: true)

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() {
self.imageNode = TransformImageNode()
self.imageNode.isLayerBacked = true
self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
//self.imageNode.alphaTransitionOnFirstUpdate = true
self.textNode = ASTextNode()

View File

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

View File

@ -26,11 +26,12 @@ public final class TelegramApplicationBindings {
public let pushIdleTimerExtension: () -> Disposable
public let openSettings: () -> Void
public let openAppStorePage: () -> Void
public let registerForNotifications: () -> Void
public let getWindowHost: () -> WindowHost?
public let presentNativeController: (UIViewController) -> Void
public let dismissNativeController: () -> Void
public init(isMainApp: Bool, openUrl: @escaping (String) -> Void, openUniversalUrl: @escaping (String, TelegramApplicationOpenUrlCompletion) -> Void, canOpenUrl: @escaping (String) -> Bool, getTopWindow: @escaping () -> UIWindow?, displayNotification: @escaping (String) -> Void, applicationInForeground: Signal<Bool, NoError>, applicationIsActive: Signal<Bool, NoError>, clearMessageNotifications: @escaping ([MessageId]) -> Void, pushIdleTimerExtension: @escaping () -> Disposable, openSettings: @escaping () -> Void, openAppStorePage: @escaping () -> Void, getWindowHost: @escaping () -> WindowHost?, presentNativeController: @escaping (UIViewController) -> Void, dismissNativeController: @escaping () -> Void) {
public init(isMainApp: Bool, openUrl: @escaping (String) -> Void, openUniversalUrl: @escaping (String, TelegramApplicationOpenUrlCompletion) -> Void, canOpenUrl: @escaping (String) -> Bool, getTopWindow: @escaping () -> UIWindow?, displayNotification: @escaping (String) -> Void, applicationInForeground: Signal<Bool, NoError>, applicationIsActive: Signal<Bool, NoError>, clearMessageNotifications: @escaping ([MessageId]) -> Void, pushIdleTimerExtension: @escaping () -> Disposable, openSettings: @escaping () -> Void, openAppStorePage: @escaping () -> Void, registerForNotifications: @escaping () -> Void, getWindowHost: @escaping () -> WindowHost?, presentNativeController: @escaping (UIViewController) -> Void, dismissNativeController: @escaping () -> Void) {
self.isMainApp = isMainApp
self.openUrl = openUrl
self.openUniversalUrl = openUniversalUrl
@ -43,6 +44,7 @@ public final class TelegramApplicationBindings {
self.pushIdleTimerExtension = pushIdleTimerExtension
self.openSettings = openSettings
self.openAppStorePage = openAppStorePage
self.registerForNotifications = registerForNotifications
self.presentNativeController = presentNativeController
self.dismissNativeController = dismissNativeController
self.getWindowHost = getWindowHost

View File

@ -14,6 +14,7 @@ public final class TelegramRootController: NavigationController {
public var chatListController: ChatListController?
public var accountSettingsController: ViewController?
private var permissionsDisposable: Disposable?
private var presentationDataDisposable: Disposable?
private var presentationData: PresentationData
@ -24,6 +25,8 @@ public final class TelegramRootController: NavigationController {
super.init(mode: .automaticMasterDetail, theme: NavigationControllerTheme(presentationTheme: self.presentationData.theme))
//self.permissionsDisposable =
self.presentationDataDisposable = (account.telegramApplicationContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
@ -42,6 +45,7 @@ public final class TelegramRootController: NavigationController {
}
deinit {
self.permissionsDisposable?.dispose()
self.presentationDataDisposable?.dispose()
}
@ -112,4 +116,20 @@ public final class TelegramRootController: NavigationController {
}
presentedLegacyShortcutCamera(account: self.account, saveCapturedMedia: false, saveEditedPhotos: false, mediaGrouping: true, parentController: controller)
}
public func requestPermissions() {
guard let parentController = self.viewControllers.last as? ViewController else {
return
}
let _ = (DeviceAccess.authorizationStatus(account: self.account, subject: .notifications)
|> take(1)
|> deliverOnMainQueue).start(next: { status in
if status != .allowed {
let controller = PermissionController(account: self.account)
controller.updateData(subject: .notifications, currentStatus: status)
parentController.present(controller, in: .window(.root))
}
})
}
}

View File

@ -31,6 +31,14 @@ public class TransformImageNode: ASDisplayNode {
self.disposable.dispose()
}
override public func didLoad() {
super.didLoad()
if #available(iOSApplicationExtension 11.0, *), !self.isLayerBacked {
self.view.accessibilityIgnoresInvertColors = true
}
}
override public var frame: CGRect {
didSet {
if let overlayNode = self.overlayNode {

View File

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