Added Smart Invert Colors support
First take on deferred permissions request
22
Images.xcassets/Settings/Permissions/CellularData.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "Data@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "Data@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
BIN
Images.xcassets/Settings/Permissions/CellularData.imageset/Data@2x.png
vendored
Normal file
After Width: | Height: | Size: 8.7 KiB |
BIN
Images.xcassets/Settings/Permissions/CellularData.imageset/Data@3x.png
vendored
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
Images.xcassets/Settings/Permissions/Contacts.imageset/Contacts@2x.png
vendored
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Images.xcassets/Settings/Permissions/Contacts.imageset/Contacts@3x.png
vendored
Normal file
After Width: | Height: | Size: 20 KiB |
22
Images.xcassets/Settings/Permissions/Contacts.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "Contacts@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "Contacts@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
9
Images.xcassets/Settings/Permissions/Contents.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"provides-namespace" : true
|
||||||
|
}
|
||||||
|
}
|
22
Images.xcassets/Settings/Permissions/Notifications.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "Notifications@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "Notifications@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
BIN
Images.xcassets/Settings/Permissions/Notifications.imageset/Notifications@2x.png
vendored
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
Images.xcassets/Settings/Permissions/Notifications.imageset/Notifications@3x.png
vendored
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
Images.xcassets/Settings/RecentSessionsPlaceholder.imageset/AuthSessionsEmptyIcon@2x.png
vendored
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
Images.xcassets/Settings/RecentSessionsPlaceholder.imageset/AuthSessionsEmptyIcon@3x.png
vendored
Normal file
After Width: | Height: | Size: 2.2 KiB |
22
Images.xcassets/Settings/RecentSessionsPlaceholder.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "AuthSessionsEmptyIcon@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "AuthSessionsEmptyIcon@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
@ -58,6 +58,11 @@
|
|||||||
09874E582107A4C300E190B8 /* VimeoEmbedImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09874E3A21075BF400E190B8 /* VimeoEmbedImplementation.swift */; };
|
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 */,
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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?
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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):
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
207
TelegramUI/NotificationPermissionInfoItem.swift
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
import Foundation
|
||||||
|
import Display
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import SwiftSignalKit
|
||||||
|
|
||||||
|
class NotificationPermissionInfoItem: ListViewItem, ItemListItem {
|
||||||
|
let selectable: Bool = false
|
||||||
|
let sectionId: ItemListSectionId
|
||||||
|
|
||||||
|
let theme: PresentationTheme
|
||||||
|
let strings: PresentationStrings
|
||||||
|
|
||||||
|
init(theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId) {
|
||||||
|
self.theme = theme
|
||||||
|
self.strings = strings
|
||||||
|
self.sectionId = sectionId
|
||||||
|
}
|
||||||
|
|
||||||
|
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, () -> Void)) -> Void) {
|
||||||
|
async {
|
||||||
|
let node = NotificationPermissionInfoItemNode()
|
||||||
|
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||||
|
|
||||||
|
node.contentSize = layout.contentSize
|
||||||
|
node.insets = layout.insets
|
||||||
|
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
completion(node, {
|
||||||
|
return (nil, { apply() })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
if let nodeValue = node() as? NotificationPermissionInfoItemNode {
|
||||||
|
let makeLayout = nodeValue.asyncLayout()
|
||||||
|
|
||||||
|
async {
|
||||||
|
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
completion(layout, {
|
||||||
|
apply()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let titleFont = Font.semibold(17.0)
|
||||||
|
private let textFont = Font.regular(16.0)
|
||||||
|
private let badgeFont = Font.regular(15.0)
|
||||||
|
|
||||||
|
class NotificationPermissionInfoItemNode: ListViewItemNode {
|
||||||
|
private let backgroundNode: ASDisplayNode
|
||||||
|
private let topStripeNode: ASDisplayNode
|
||||||
|
private let bottomStripeNode: ASDisplayNode
|
||||||
|
|
||||||
|
let badgeNode: ASImageNode
|
||||||
|
let labelNode: TextNode
|
||||||
|
let titleNode: TextNode
|
||||||
|
let textNode: TextNode
|
||||||
|
|
||||||
|
private var item: NotificationPermissionInfoItem?
|
||||||
|
|
||||||
|
override var canBeSelected: Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.backgroundNode = ASDisplayNode()
|
||||||
|
self.backgroundNode.isLayerBacked = true
|
||||||
|
self.backgroundNode.backgroundColor = .white
|
||||||
|
|
||||||
|
self.topStripeNode = ASDisplayNode()
|
||||||
|
self.topStripeNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.bottomStripeNode = ASDisplayNode()
|
||||||
|
self.bottomStripeNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.badgeNode = ASImageNode()
|
||||||
|
self.badgeNode.displayWithoutProcessing = true
|
||||||
|
self.badgeNode.displaysAsynchronously = false
|
||||||
|
self.badgeNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.labelNode = TextNode()
|
||||||
|
self.labelNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
self.titleNode = TextNode()
|
||||||
|
self.titleNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
self.textNode = TextNode()
|
||||||
|
self.textNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
|
self.addSubnode(self.badgeNode)
|
||||||
|
self.addSubnode(self.labelNode)
|
||||||
|
self.addSubnode(self.titleNode)
|
||||||
|
self.addSubnode(self.textNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func asyncLayout() -> (_ item: NotificationPermissionInfoItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||||
|
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
||||||
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
|
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||||
|
|
||||||
|
let currentItem = self.item
|
||||||
|
|
||||||
|
return { item, params, neighbors in
|
||||||
|
let leftInset: CGFloat = 16.0 + params.leftInset
|
||||||
|
let rightInset: CGFloat = 16.0 + params.rightInset
|
||||||
|
|
||||||
|
var updatedTheme: PresentationTheme?
|
||||||
|
var updatedBadgeImage: UIImage?
|
||||||
|
|
||||||
|
let badgeDiameter: CGFloat = 20.0
|
||||||
|
if currentItem?.theme !== item.theme {
|
||||||
|
updatedTheme = item.theme
|
||||||
|
updatedBadgeImage = generateStretchableFilledCircleImage(diameter: badgeDiameter, color: item.theme.list.itemDestructiveColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
let insets = itemListNeighborsGroupedInsets(neighbors)
|
||||||
|
let separatorHeight = UIScreenPixel
|
||||||
|
let itemBackgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||||
|
let itemSeparatorColor = item.theme.list.itemBlocksSeparatorColor
|
||||||
|
|
||||||
|
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "!", font: badgeFont, textColor: item.theme.list.itemCheckColors.foregroundColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: badgeDiameter, height: badgeDiameter), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Turn ON Notifications", font: titleFont, textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - badgeDiameter - 8.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Don't miss important messages from your friends and coworkers.", font: textFont, textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 3, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
|
let contentSize = CGSize(width: params.width, height: titleLayout.size.height + textLayout.size.height + 36.0)
|
||||||
|
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.item = item
|
||||||
|
|
||||||
|
if let _ = updatedTheme {
|
||||||
|
strongSelf.topStripeNode.backgroundColor = itemSeparatorColor
|
||||||
|
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
|
||||||
|
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = labelApply()
|
||||||
|
let _ = titleApply()
|
||||||
|
let _ = textApply()
|
||||||
|
|
||||||
|
if strongSelf.backgroundNode.supernode == nil {
|
||||||
|
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||||
|
}
|
||||||
|
if strongSelf.topStripeNode.supernode == nil {
|
||||||
|
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
|
||||||
|
}
|
||||||
|
if strongSelf.bottomStripeNode.supernode == nil {
|
||||||
|
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
||||||
|
}
|
||||||
|
switch neighbors.top {
|
||||||
|
case .sameSection(false):
|
||||||
|
strongSelf.topStripeNode.isHidden = true
|
||||||
|
default:
|
||||||
|
strongSelf.topStripeNode.isHidden = false
|
||||||
|
}
|
||||||
|
let bottomStripeInset: CGFloat
|
||||||
|
switch neighbors.bottom {
|
||||||
|
case .sameSection(false):
|
||||||
|
bottomStripeInset = leftInset
|
||||||
|
default:
|
||||||
|
bottomStripeInset = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||||
|
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
|
||||||
|
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
|
||||||
|
|
||||||
|
if let updateBadgeImage = updatedBadgeImage {
|
||||||
|
if strongSelf.badgeNode.supernode == nil {
|
||||||
|
strongSelf.insertSubnode(strongSelf.badgeNode, belowSubnode: strongSelf.labelNode)
|
||||||
|
}
|
||||||
|
strongSelf.badgeNode.image = updateBadgeImage
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.badgeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 16.0), size: CGSize(width: badgeDiameter, height: badgeDiameter))
|
||||||
|
|
||||||
|
strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: strongSelf.badgeNode.frame.midX - labelLayout.size.width / 2.0, y: strongSelf.badgeNode.frame.minY + 1.0), size: labelLayout.size)
|
||||||
|
|
||||||
|
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: strongSelf.badgeNode.frame.maxX + 8.0, y: 16.0), size: titleLayout.size)
|
||||||
|
|
||||||
|
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: strongSelf.titleNode.frame.maxY + 9.0), size: textLayout.size)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||||
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateAdded(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,8 @@ private final class NotificationsAndSoundsArguments {
|
|||||||
let pushController: (ViewController) -> Void
|
let 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))
|
||||||
}
|
}
|
||||||
|
63
TelegramUI/PermissionController.swift
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import Foundation
|
||||||
|
import Display
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import SwiftSignalKit
|
||||||
|
import TelegramCore
|
||||||
|
|
||||||
|
final class PermissionController : ViewController {
|
||||||
|
private var controllerNode: PermissionControllerNode {
|
||||||
|
return self.displayNode as! PermissionControllerNode
|
||||||
|
}
|
||||||
|
|
||||||
|
private let account: Account
|
||||||
|
private let strings: PresentationStrings
|
||||||
|
private let theme: AuthorizationTheme
|
||||||
|
|
||||||
|
init(account: Account) {
|
||||||
|
self.account = account
|
||||||
|
self.strings = account.telegramApplicationContext.currentPresentationData.with { $0 }.strings
|
||||||
|
self.theme = defaultLightAuthorizationTheme
|
||||||
|
|
||||||
|
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: AuthorizationSequenceController.navigationBarTheme(theme), strings: NavigationBarStrings(presentationStrings: strings)))
|
||||||
|
|
||||||
|
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||||
|
|
||||||
|
self.statusBar.statusBarStyle = self.theme.statusBarStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
required init(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func loadDisplayNode() {
|
||||||
|
self.displayNode = PermissionControllerNode(theme: self.theme, strings: self.strings)
|
||||||
|
self.displayNodeDidLoad()
|
||||||
|
|
||||||
|
self.controllerNode.dismiss = { [weak self] in
|
||||||
|
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||||
|
}
|
||||||
|
self.controllerNode.allow = { [weak self] in
|
||||||
|
self?.account.telegramApplicationContext.applicationBindings.openSettings()
|
||||||
|
}
|
||||||
|
self.controllerNode.next = { [weak self] in
|
||||||
|
self?.dismiss(completion: nil)
|
||||||
|
}
|
||||||
|
self.controllerNode.openPrivacyPolicy = {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func dismiss(completion: (() -> Void)?) {
|
||||||
|
self.controllerNode.animateOut(completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateData(subject: DeviceAccessSubject, currentStatus: AccessType) {
|
||||||
|
self.controllerNode.updateData(subject: .notifications, currentStatus: currentStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
|
|
||||||
|
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
203
TelegramUI/PermissionControllerNode.swift
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
import Foundation
|
||||||
|
import Display
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import SwiftSignalKit
|
||||||
|
import TelegramCore
|
||||||
|
|
||||||
|
final class PermissionControllerNode: ASDisplayNode {
|
||||||
|
private let theme: AuthorizationTheme
|
||||||
|
private let strings: PresentationStrings
|
||||||
|
|
||||||
|
private let iconNode: ASImageNode
|
||||||
|
private let titleNode: ImmediateTextNode
|
||||||
|
private let textNode: ImmediateTextNode
|
||||||
|
private let buttonNode: SolidRoundedButtonNode
|
||||||
|
private let privacyPolicyNode: HighlightableButtonNode
|
||||||
|
private let nextNode: HighlightableButtonNode
|
||||||
|
|
||||||
|
private var layoutArguments: (ContainerViewLayout, CGFloat)?
|
||||||
|
|
||||||
|
private var title: String?
|
||||||
|
|
||||||
|
var allow: (() -> Void)?
|
||||||
|
var next: (() -> Void)? {
|
||||||
|
didSet {
|
||||||
|
self.nextNode.isHidden = self.next == nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var openPrivacyPolicy: (() -> Void)?
|
||||||
|
var dismiss: (() -> Void)?
|
||||||
|
|
||||||
|
init(theme: AuthorizationTheme, strings: PresentationStrings) {
|
||||||
|
self.theme = theme
|
||||||
|
self.strings = strings
|
||||||
|
|
||||||
|
self.iconNode = ASImageNode()
|
||||||
|
self.iconNode.isLayerBacked = true
|
||||||
|
self.iconNode.displayWithoutProcessing = true
|
||||||
|
self.iconNode.displaysAsynchronously = false
|
||||||
|
|
||||||
|
self.titleNode = ImmediateTextNode()
|
||||||
|
self.titleNode.maximumNumberOfLines = 0
|
||||||
|
self.titleNode.textAlignment = .center
|
||||||
|
self.titleNode.isUserInteractionEnabled = false
|
||||||
|
self.titleNode.displaysAsynchronously = false
|
||||||
|
|
||||||
|
self.textNode = ImmediateTextNode()
|
||||||
|
self.textNode.textAlignment = .center
|
||||||
|
self.textNode.maximumNumberOfLines = 0
|
||||||
|
self.textNode.displaysAsynchronously = false
|
||||||
|
let body = MarkdownAttributeSet(font: Font.regular(16.0), textColor: theme.primaryColor)
|
||||||
|
let link = MarkdownAttributeSet(font: Font.regular(16.0), textColor: theme.accentColor, additionalAttributes: [TelegramTextAttributes.URL: ""])
|
||||||
|
self.textNode.attributedText = parseMarkdownIntoAttributedString(strings.Login_TermsOfServiceLabel.replacingOccurrences(of: "]", with: "]()"), attributes: MarkdownAttributes(body: body, bold: body, link: link, linkAttribute: { _ in nil }), textAlignment: .center)
|
||||||
|
|
||||||
|
self.buttonNode = SolidRoundedButtonNode(theme: self.theme, height: 48.0, cornerRadius: 9.0)
|
||||||
|
|
||||||
|
self.privacyPolicyNode = HighlightableButtonNode()
|
||||||
|
self.privacyPolicyNode.setTitle("Privacy Policy", with: Font.regular(16.0), with: self.theme.accentColor, for: .normal)
|
||||||
|
|
||||||
|
self.nextNode = HighlightableButtonNode()
|
||||||
|
self.nextNode.setTitle("Skip", with: Font.regular(17.0), with: self.theme.accentColor, for: .normal)
|
||||||
|
self.nextNode.isHidden = true
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.setViewBlock({
|
||||||
|
return UITracingLayerView()
|
||||||
|
})
|
||||||
|
|
||||||
|
self.backgroundColor = self.theme.backgroundColor
|
||||||
|
|
||||||
|
self.addSubnode(self.iconNode)
|
||||||
|
self.addSubnode(self.titleNode)
|
||||||
|
self.addSubnode(self.textNode)
|
||||||
|
self.addSubnode(self.buttonNode)
|
||||||
|
self.addSubnode(self.privacyPolicyNode)
|
||||||
|
self.addSubnode(self.nextNode)
|
||||||
|
|
||||||
|
self.buttonNode.pressed = { [weak self] in
|
||||||
|
self?.allow?()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.privacyPolicyNode.addTarget(self, action: #selector(self.privacyPolicyPressed), forControlEvents: .touchUpInside)
|
||||||
|
self.nextNode.addTarget(self, action: #selector(self.nextPressed), forControlEvents: .touchUpInside)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateData(subject: DeviceAccessSubject, currentStatus: AccessType) {
|
||||||
|
var icon: UIImage?
|
||||||
|
var title = ""
|
||||||
|
var text = ""
|
||||||
|
var buttonTitle = ""
|
||||||
|
var hasPrivacyPolicy = false
|
||||||
|
|
||||||
|
switch subject {
|
||||||
|
case .contacts:
|
||||||
|
icon = UIImage(bundleImageName: "Settings/Permissions/Contacts")
|
||||||
|
title = "Sync Your Contacts"
|
||||||
|
text = "See who's on Telegram and switch seamlessly, without having to \"add\" to add your friends."
|
||||||
|
if currentStatus == .denied {
|
||||||
|
buttonTitle = "Allow in Settings"
|
||||||
|
} else {
|
||||||
|
buttonTitle = "Allow Access"
|
||||||
|
}
|
||||||
|
hasPrivacyPolicy = true
|
||||||
|
case .notifications:
|
||||||
|
icon = UIImage(bundleImageName: "Settings/Permissions/Notifications")
|
||||||
|
title = "Turn ON Notifications"
|
||||||
|
text = "Don't miss important messages from your friends and coworkers."
|
||||||
|
if currentStatus == .denied || currentStatus == .restricted {
|
||||||
|
buttonTitle = "Turn ON in Settings"
|
||||||
|
} else {
|
||||||
|
buttonTitle = "Turn Notifications ON"
|
||||||
|
}
|
||||||
|
case .cellularData:
|
||||||
|
icon = UIImage(bundleImageName: "Settings/Permissions/CellularData")
|
||||||
|
title = "Turn ON Mobile Data"
|
||||||
|
text = "Don't worry, Telegram keeps network usage to a minimum. You can further control this in Settings > Data and Storage."
|
||||||
|
buttonTitle = "Turn ON in Settings"
|
||||||
|
case .siri:
|
||||||
|
title = "Turn ON Siri"
|
||||||
|
text = "Use Siri to send messages and make calls."
|
||||||
|
if currentStatus == .denied {
|
||||||
|
buttonTitle = "Turn ON in Settings"
|
||||||
|
} else {
|
||||||
|
buttonTitle = "Turn Siri ON"
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
self.iconNode.image = icon
|
||||||
|
self.title = title
|
||||||
|
|
||||||
|
let body = MarkdownAttributeSet(font: Font.regular(16.0), textColor: self.theme.primaryColor)
|
||||||
|
let link = MarkdownAttributeSet(font: Font.regular(16.0), textColor: self.theme.accentColor, additionalAttributes: [TelegramTextAttributes.URL: ""])
|
||||||
|
self.textNode.attributedText = parseMarkdownIntoAttributedString(text.replacingOccurrences(of: "]", with: "]()"), attributes: MarkdownAttributes(body: body, bold: body, link: link, linkAttribute: { _ in nil }), textAlignment: .center)
|
||||||
|
|
||||||
|
self.buttonNode.title = buttonTitle
|
||||||
|
|
||||||
|
self.privacyPolicyNode.isHidden = !hasPrivacyPolicy
|
||||||
|
|
||||||
|
if let (layout, navigationHeight) = self.layoutArguments {
|
||||||
|
self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
self.layoutArguments = (layout, navigationBarHeight)
|
||||||
|
|
||||||
|
let insets = layout.insets(options: [.statusBar])
|
||||||
|
let fontSize: CGFloat
|
||||||
|
let sideInset: CGFloat
|
||||||
|
if layout.size.width > 330.0 {
|
||||||
|
fontSize = 22.0
|
||||||
|
sideInset = 38.0
|
||||||
|
} else {
|
||||||
|
fontSize = 18.0
|
||||||
|
sideInset = 20.0
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextSize = self.nextNode.measure(layout.size)
|
||||||
|
transition.updateFrame(node: self.nextNode, frame: CGRect(x: layout.size.width - insets.right - nextSize.width - 16.0, y: insets.top + 10.0 + 60.0, width: nextSize.width, height: nextSize.height))
|
||||||
|
|
||||||
|
self.titleNode.attributedText = NSAttributedString(string: self.title ?? "", font: Font.semibold(fontSize), textColor: self.theme.primaryColor)
|
||||||
|
|
||||||
|
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||||
|
let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||||
|
let buttonHeight = self.buttonNode.updateLayout(width: layout.size.width, transition: transition)
|
||||||
|
|
||||||
|
var items: [AuthorizationLayoutItem] = []
|
||||||
|
if let icon = self.iconNode.image {
|
||||||
|
items.append(AuthorizationLayoutItem(node: self.iconNode, size: icon.size, spacingBefore: AuthorizationLayoutItemSpacing(weight: 122.0, maxValue: 122.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 15.0, maxValue: 15.0)))
|
||||||
|
}
|
||||||
|
items.append(AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
|
||||||
|
items.append(AuthorizationLayoutItem(node: self.textNode, size: textSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 5.0, maxValue: 5.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 35.0, maxValue: 35.0)))
|
||||||
|
items.append(AuthorizationLayoutItem(node: self.buttonNode, size: CGSize(width: layout.size.width, height: buttonHeight), spacingBefore: AuthorizationLayoutItemSpacing(weight: 35.0, maxValue: 35.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 50.0, maxValue: 50.0)))
|
||||||
|
|
||||||
|
let _ = layoutAuthorizationItems(bounds: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top - insets.bottom - 20.0)), items: items, transition: transition, failIfDoesNotFit: false)
|
||||||
|
|
||||||
|
let privacyPolicySize = self.privacyPolicyNode.measure(layout.size)
|
||||||
|
transition.updateFrame(node: self.privacyPolicyNode, frame: CGRect(x: (layout.size.width - privacyPolicySize.width) / 2.0, y: self.buttonNode.frame.maxY + (layout.size.height - self.buttonNode.frame.maxY - insets.bottom - privacyPolicySize.height) / 2.0, width: privacyPolicySize.width, height: privacyPolicySize.height))
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func allowPressed() {
|
||||||
|
self.allow?()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func privacyPolicyPressed() {
|
||||||
|
self.openPrivacyPolicy?()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func nextPressed() {
|
||||||
|
self.next?()
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateOut(completion: (() -> Void)? = nil) {
|
||||||
|
self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: false, completion: { [weak self] _ in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.dismiss?()
|
||||||
|
}
|
||||||
|
completion?()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -604,7 +604,6 @@ public func recentSessionsController(account: Account) -> ViewController {
|
|||||||
let websitesSignal: Signal<([WebAuthorization], [PeerId : Peer])?, NoError> = .single(nil) |> then(webSessions(network: account.network) |> map(Optional.init))
|
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
|
||||||
|
95
TelegramUI/RecentSessionsEmptyStateItem.swift
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import Foundation
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import Display
|
||||||
|
|
||||||
|
final class RecentSessionsEmptyStateItem: ItemListControllerEmptyStateItem {
|
||||||
|
let theme: PresentationTheme
|
||||||
|
let strings: PresentationStrings
|
||||||
|
|
||||||
|
init(theme: PresentationTheme, strings: PresentationStrings) {
|
||||||
|
self.theme = theme
|
||||||
|
self.strings = strings
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEqual(to: ItemListControllerEmptyStateItem) -> Bool {
|
||||||
|
if let item = to as? RecentSessionsEmptyStateItem {
|
||||||
|
return self.theme === item.theme && self.strings === item.strings
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func node(current: ItemListControllerEmptyStateItemNode?) -> ItemListControllerEmptyStateItemNode {
|
||||||
|
if let current = current as? RecentSessionsEmptyStateItemNode {
|
||||||
|
current.item = self
|
||||||
|
return current
|
||||||
|
} else {
|
||||||
|
return RecentSessionsEmptyStateItemNode(item: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class RecentSessionsEmptyStateItemNode: ItemListControllerEmptyStateItemNode {
|
||||||
|
private let imageNode: ASImageNode
|
||||||
|
private let titleNode: ASTextNode
|
||||||
|
private let textNode: ASTextNode
|
||||||
|
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||||
|
|
||||||
|
var item: RecentSessionsEmptyStateItem {
|
||||||
|
didSet {
|
||||||
|
self.updateThemeAndStrings(theme: self.item.theme, strings: self.item.strings)
|
||||||
|
if let (layout, navigationHeight) = self.validLayout {
|
||||||
|
self.updateLayout(layout: layout, navigationBarHeight: navigationHeight, transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(item: RecentSessionsEmptyStateItem) {
|
||||||
|
self.item = item
|
||||||
|
|
||||||
|
self.imageNode = ASImageNode()
|
||||||
|
|
||||||
|
self.titleNode = ASTextNode()
|
||||||
|
self.titleNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
self.textNode = ASTextNode()
|
||||||
|
self.textNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.imageNode)
|
||||||
|
self.addSubnode(self.titleNode)
|
||||||
|
self.addSubnode(self.textNode)
|
||||||
|
|
||||||
|
self.updateThemeAndStrings(theme: self.item.theme, strings: self.item.strings)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
|
||||||
|
self.imageNode.image = generateTintedImage(image: UIImage(bundleImageName: "Settings/RecentSessionsPlaceholder"), color: theme.list.freeTextColor)
|
||||||
|
self.titleNode.attributedText = NSAttributedString(string: strings.AuthSessions_EmptyTitle, font: Font.bold(17.0), textColor: theme.list.freeTextColor, paragraphAlignment: .center)
|
||||||
|
self.textNode.attributedText = NSAttributedString(string: strings.AuthSessions_EmptyText, font: Font.regular(14.0), textColor: theme.list.freeTextColor, paragraphAlignment: .center)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
self.validLayout = (layout, navigationBarHeight)
|
||||||
|
var insets = layout.insets(options: [])
|
||||||
|
insets.top += navigationBarHeight + 128.0
|
||||||
|
|
||||||
|
let imageSpacing: CGFloat = 8.0
|
||||||
|
let textSpacing: CGFloat = 8.0
|
||||||
|
|
||||||
|
let imageSize = self.imageNode.image?.size ?? CGSize()
|
||||||
|
let imageHeight = layout.size.width < layout.size.height ? imageSize.height + imageSpacing : 0.0
|
||||||
|
|
||||||
|
let titleSize = self.titleNode.measure(CGSize(width: layout.size.width - 50.0, height: max(1.0, layout.size.height - insets.top - insets.bottom)))
|
||||||
|
let textSize = self.textNode.measure(CGSize(width: layout.size.width - 50.0, height: max(1.0, layout.size.height - insets.top - insets.bottom)))
|
||||||
|
|
||||||
|
let totalHeight = imageHeight + titleSize.height + textSpacing + textSize.height
|
||||||
|
let topOffset = insets.top + floor((layout.size.height - insets.top - insets.bottom - totalHeight) / 2.0)
|
||||||
|
|
||||||
|
transition.updateAlpha(node: self.imageNode, alpha: imageHeight > 0.0 ? 1.0 : 0.0)
|
||||||
|
transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - imageSize.width) / 2.0), y: topOffset), size: imageSize))
|
||||||
|
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: topOffset + imageHeight), size: titleSize))
|
||||||
|
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: self.titleNode.frame.maxY + textSpacing), size: textSize))
|
||||||
|
}
|
||||||
|
}
|
@ -89,14 +89,12 @@ final class SelectablePeerNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.avatarNode = AvatarNode(font: avatarFont)
|
self.avatarNode = 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)
|
||||||
|
@ -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)
|
||||||
|
88
TelegramUI/SolidRoundedButtonNode.swift
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import Foundation
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import Display
|
||||||
|
|
||||||
|
private let textFont: UIFont = Font.regular(16.0)
|
||||||
|
|
||||||
|
final class SolidRoundedButtonNode: ASDisplayNode {
|
||||||
|
private var theme: AuthorizationTheme
|
||||||
|
|
||||||
|
private let buttonBackgroundNode: ASImageNode
|
||||||
|
private let buttonNode: HighlightTrackingButtonNode
|
||||||
|
private let labelNode: ImmediateTextNode
|
||||||
|
|
||||||
|
private let buttonHeight: CGFloat
|
||||||
|
private let buttonCornerRadius: CGFloat
|
||||||
|
|
||||||
|
var pressed: (() -> Void)?
|
||||||
|
var validLayout: CGFloat?
|
||||||
|
|
||||||
|
var title: String? {
|
||||||
|
didSet {
|
||||||
|
if let width = self.validLayout {
|
||||||
|
_ = self.updateLayout(width: width, transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(title: String? = nil, theme: AuthorizationTheme, height: CGFloat = 48.0, cornerRadius: CGFloat = 24.0) {
|
||||||
|
self.theme = theme
|
||||||
|
self.buttonHeight = height
|
||||||
|
self.buttonCornerRadius = cornerRadius
|
||||||
|
self.title = title
|
||||||
|
|
||||||
|
self.buttonBackgroundNode = ASImageNode()
|
||||||
|
self.buttonBackgroundNode.isLayerBacked = true
|
||||||
|
self.buttonBackgroundNode.displayWithoutProcessing = true
|
||||||
|
self.buttonBackgroundNode.displaysAsynchronously = false
|
||||||
|
self.buttonBackgroundNode.image = generateStretchableFilledCircleImage(radius: cornerRadius, color: theme.accentColor)
|
||||||
|
|
||||||
|
self.buttonNode = HighlightTrackingButtonNode()
|
||||||
|
|
||||||
|
self.labelNode = ImmediateTextNode()
|
||||||
|
self.labelNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.buttonBackgroundNode)
|
||||||
|
self.addSubnode(self.buttonNode)
|
||||||
|
self.addSubnode(self.labelNode)
|
||||||
|
|
||||||
|
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||||
|
self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
||||||
|
if let strongSelf = self {
|
||||||
|
if highlighted {
|
||||||
|
strongSelf.buttonBackgroundNode.layer.removeAnimation(forKey: "opacity")
|
||||||
|
strongSelf.buttonBackgroundNode.alpha = 0.55
|
||||||
|
} else {
|
||||||
|
strongSelf.buttonBackgroundNode.alpha = 1.0
|
||||||
|
strongSelf.buttonBackgroundNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||||
|
self.validLayout = width
|
||||||
|
|
||||||
|
let inset: CGFloat = 38.0
|
||||||
|
let buttonSize = CGSize(width: width - inset * 2.0, height: self.buttonHeight)
|
||||||
|
let buttonFrame = CGRect(origin: CGPoint(x: inset, y: 0.0), size: buttonSize)
|
||||||
|
transition.updateFrame(node: self.buttonBackgroundNode, frame: buttonFrame)
|
||||||
|
transition.updateFrame(node: self.buttonNode, frame: buttonFrame)
|
||||||
|
|
||||||
|
if self.title != self.labelNode.attributedText?.string {
|
||||||
|
self.labelNode.attributedText = NSAttributedString(string: self.title ?? "", font: Font.medium(17.0), textColor: self.theme.backgroundColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
let labelSize = self.labelNode.updateLayout(buttonSize)
|
||||||
|
let labelFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + floor((buttonFrame.width - labelSize.width) / 2.0), y: buttonFrame.minY + floor((buttonFrame.height - labelSize.height) / 2.0)), size: labelSize)
|
||||||
|
transition.updateFrame(node: self.labelNode, frame: labelFrame)
|
||||||
|
|
||||||
|
return buttonSize.height
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func buttonPressed() {
|
||||||
|
self.pressed?()
|
||||||
|
}
|
||||||
|
}
|
@ -64,7 +64,7 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
|
|||||||
|
|
||||||
override init() {
|
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()
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|